From 3512c561e6d519958733fc8072771681019afc6f Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Sat, 22 Jul 2023 22:43:13 +0800 Subject: [PATCH 01/10] Create basic permissions structs and package --- internal/auth/permissions/validation.go | 35 +++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 internal/auth/permissions/validation.go diff --git a/internal/auth/permissions/validation.go b/internal/auth/permissions/validation.go new file mode 100644 index 0000000..55dcf85 --- /dev/null +++ b/internal/auth/permissions/validation.go @@ -0,0 +1,35 @@ +package permissions + +import ( + "net/http" +) + +type PermissionGroup interface { + IsAuthorized(*http.Request) bool +} + +type AllOf struct { + Groups []PermissionGroup +} + +func (a AllOf) IsAuthorized(r *http.Request) bool { + for _, group := range a.Groups { + if !group.IsAuthorized(r) { + return false + } + } + return true +} + +type AnyOf struct { + Groups []PermissionGroup +} + +func (a AnyOf) IsAuthorized(r *http.Request) bool { + for _, group := range a.Groups { + if group.IsAuthorized(r) { + return true + } + } + return false +} From be516dab13cc8a1b7bdc61acf1d2c216195fe136 Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Sat, 22 Jul 2023 22:44:18 +0800 Subject: [PATCH 02/10] Create users' permission types --- .../auth/permissions/users/permissions.go | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 internal/auth/permissions/users/permissions.go diff --git a/internal/auth/permissions/users/permissions.go b/internal/auth/permissions/users/permissions.go new file mode 100644 index 0000000..bc0a2f7 --- /dev/null +++ b/internal/auth/permissions/users/permissions.go @@ -0,0 +1,20 @@ +package userpermissions + +type Permission string + +const ( + CanCreateUsers Permission = "can_create_users" + CanReadUsers Permission = "can_read_users" + CanUpdateUsers Permission = "can_update_users" + CanDeleteUsers Permission = "can_delete_users" + + CanCreateGroups Permission = "can_create_groups" + CanReadGroups Permission = "can_read_groups" + CanUpdateGroups Permission = "can_update_groups" + CanDeleteGroups Permission = "can_delete_groups" + + CanCreateStories Permission = "can_create_stories" + CanReadStories Permission = "can_read_stories" + CanUpdateStories Permission = "can_update_stories" + CanDeleteStories Permission = "can_delete_stories" +) From 92481fa2263302d526edc240115d411f236d2b8c Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Sat, 22 Jul 2023 22:46:09 +0800 Subject: [PATCH 03/10] Create users' permission struct --- internal/auth/permissions/users/role.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 internal/auth/permissions/users/role.go diff --git a/internal/auth/permissions/users/role.go b/internal/auth/permissions/users/role.go new file mode 100644 index 0000000..07fdd2b --- /dev/null +++ b/internal/auth/permissions/users/role.go @@ -0,0 +1,15 @@ +package userpermissions + +import ( + "net/http" +) + +type RolePermission struct { + Permission Permission +} + +func (p RolePermission) IsAuthorized(r *http.Request) bool { + // TODO: Implement. Blocked by request context missing + // user and group info. + return false +} From fe619707d17c61a5e10c2dbdef4410c769ba3440 Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Sat, 22 Jul 2023 22:50:27 +0800 Subject: [PATCH 04/10] Add users' group role to permissions mapper Some logic depends on user role enums and are thus commented out. --- internal/auth/permissions/users/users.go | 64 ++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 internal/auth/permissions/users/users.go diff --git a/internal/auth/permissions/users/users.go b/internal/auth/permissions/users/users.go new file mode 100644 index 0000000..ff2186e --- /dev/null +++ b/internal/auth/permissions/users/users.go @@ -0,0 +1,64 @@ +package userpermissions + +import ( + "github.com/source-academy/stories-backend/internal/auth/permissions" +) + +// TODO: Replace with more maintainable permissions matrix, +// perhaps defined on a per-user/group basis in the DB. +// TODO: Accept role as parameter once role enum is merged. +// func GetFromRole(role groupenums.Role) []permissions.PermissionGroup { +func GetFromRole() []permissions.PermissionGroup { + permissionsList := &[]Permission{} // FIXME: Hack to prevent errors. Remove when role enums are merged. + // var permissionsList *[]Permission + + // Get permissions for all users + standardPermissions := []Permission{ + CanCreateStories, + CanReadStories, + } + // if role == groupenums.RoleStandard { + // permissionsList = &standardPermissions + // } + + // Get additional permissions for moderators and administrators + moderatorPermissions := []Permission{ + CanUpdateStories, + CanDeleteStories, + } + moderatorPermissions = append(moderatorPermissions, standardPermissions...) + // if role == groupenums.RoleModerator { + // permissionsList = &moderatorPermissions + // } + + // Get additional permissions for administrators only + adminPermissions := []Permission{ + CanCreateUsers, + CanReadUsers, + CanUpdateUsers, + CanDeleteUsers, + CanCreateGroups, + CanReadGroups, + CanUpdateGroups, + CanDeleteGroups, + } + adminPermissions = append(adminPermissions, moderatorPermissions...) + // if role == groupenums.RoleAdmin { + // permissionsList = &adminPermissions + // } + + // Return the permissions group + + _ = adminPermissions // FIXME: Hack to prevent lint errors. Remove when role enums are merged. + + if permissionsList == nil { + // Illegal path - something must have went wrong + panic("Illegal path") + } + + group := make([]permissions.PermissionGroup, 0, len(standardPermissions)) + for i, p := range *permissionsList { + group[i] = RolePermission{Permission: p} + } + return group +} From 5f7947f8658a8bc12bea7c77e89f8f830e153a06 Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Sat, 22 Jul 2023 22:53:42 +0800 Subject: [PATCH 05/10] Add top-level permissions checker --- internal/auth/permission.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 internal/auth/permission.go diff --git a/internal/auth/permission.go b/internal/auth/permission.go new file mode 100644 index 0000000..3a804bd --- /dev/null +++ b/internal/auth/permission.go @@ -0,0 +1,13 @@ +package auth + +import ( + "net/http" + + "github.com/source-academy/stories-backend/internal/auth/permissions" +) + +func CheckPermissions(r *http.Request, requestedActionPermissions ...permissions.PermissionGroup) (bool, error) { + // TODO: check permissions + // TODO: perhaps utilize userpermissions.GetFromRole + return false, nil +} From 1fcf70dc6ecaa200be1156f4391ac7b531eaad6e Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Wed, 26 Jul 2023 00:22:38 +0800 Subject: [PATCH 06/10] Refactor role permissions logic * Handle checking of user authorization from within the role permission struct * Create utility function to return the correct authorization level from a specified permission --- internal/auth/permissions/users/role.go | 27 +++++++- internal/auth/permissions/users/users.go | 79 ++++++++++-------------- 2 files changed, 56 insertions(+), 50 deletions(-) diff --git a/internal/auth/permissions/users/role.go b/internal/auth/permissions/users/role.go index 07fdd2b..28614f0 100644 --- a/internal/auth/permissions/users/role.go +++ b/internal/auth/permissions/users/role.go @@ -2,14 +2,37 @@ package userpermissions import ( "net/http" + + "github.com/source-academy/stories-backend/internal/auth" + // groupenums "github.com/source-academy/stories-backend/internal/enums/groups" ) type RolePermission struct { Permission Permission + // Role groupenums.Role } func (p RolePermission) IsAuthorized(r *http.Request) bool { - // TODO: Implement. Blocked by request context missing - // user and group info. + userID, err := auth.GetUserIDFrom(r) + if err != nil { + return false + } + // TODO: Implement. Blocked by request context missing group info. + _ = userID + // role := getRole(userID, groupID) + // return isRoleGreaterThan(role, p.Role) return false } + +// TODO: Move comparison function to role enum +// func isRoleGreaterThan(role1, role2 groupenums.Role) bool { +// switch role1 { +// case groupenums.RoleAdmin: +// return true +// case groupenums.RoleModerator: +// return role2 != groupenums.RoleAdmin +// case groupenums.RoleStandard: +// return role2 == groupenums.RoleStandard +// } +// return false +// } diff --git a/internal/auth/permissions/users/users.go b/internal/auth/permissions/users/users.go index ff2186e..a75d6b0 100644 --- a/internal/auth/permissions/users/users.go +++ b/internal/auth/permissions/users/users.go @@ -1,38 +1,34 @@ package userpermissions import ( - "github.com/source-academy/stories-backend/internal/auth/permissions" + "errors" + // groupenums "github.com/source-academy/stories-backend/internal/enums/groups" ) -// TODO: Replace with more maintainable permissions matrix, -// perhaps defined on a per-user/group basis in the DB. -// TODO: Accept role as parameter once role enum is merged. -// func GetFromRole(role groupenums.Role) []permissions.PermissionGroup { -func GetFromRole() []permissions.PermissionGroup { - permissionsList := &[]Permission{} // FIXME: Hack to prevent errors. Remove when role enums are merged. - // var permissionsList *[]Permission - - // Get permissions for all users - standardPermissions := []Permission{ +// Gets the RolePermission from a Permission. To be called +// by external packages. +// TODO: Investigate possibility of defining roles on a +// per-user/group basis in the DB. +func GetRolePermission(p Permission) *RolePermission { + switch p { + case + // Permissions for all users CanCreateStories, - CanReadStories, - } - // if role == groupenums.RoleStandard { - // permissionsList = &standardPermissions - // } - - // Get additional permissions for moderators and administrators - moderatorPermissions := []Permission{ + CanReadStories: + return &RolePermission{ + Permission: p, + // Role: groupenums.RoleStandard, + } + case + // Additional permissions for moderators and administrators CanUpdateStories, - CanDeleteStories, - } - moderatorPermissions = append(moderatorPermissions, standardPermissions...) - // if role == groupenums.RoleModerator { - // permissionsList = &moderatorPermissions - // } - - // Get additional permissions for administrators only - adminPermissions := []Permission{ + CanDeleteStories: + return &RolePermission{ + Permission: p, + // Role: groupenums.RoleModerator, + } + case + // Additional permissions for administrators only CanCreateUsers, CanReadUsers, CanUpdateUsers, @@ -40,25 +36,12 @@ func GetFromRole() []permissions.PermissionGroup { CanCreateGroups, CanReadGroups, CanUpdateGroups, - CanDeleteGroups, - } - adminPermissions = append(adminPermissions, moderatorPermissions...) - // if role == groupenums.RoleAdmin { - // permissionsList = &adminPermissions - // } - - // Return the permissions group - - _ = adminPermissions // FIXME: Hack to prevent lint errors. Remove when role enums are merged. - - if permissionsList == nil { - // Illegal path - something must have went wrong - panic("Illegal path") - } - - group := make([]permissions.PermissionGroup, 0, len(standardPermissions)) - for i, p := range *permissionsList { - group[i] = RolePermission{Permission: p} + CanDeleteGroups: + return &RolePermission{ + Permission: p, + // Role: groupenums.RoleAdmin, + } } - return group + // Illegal path - all permissions should have been handled above + panic(errors.New("Illegal path - all permissions should have been handled above")) } From 607bd1ecf66c11c7f2665bd9aa736d94b8eec1c4 Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Wed, 26 Jul 2023 00:25:10 +0800 Subject: [PATCH 07/10] Implement CheckPermissions function --- internal/auth/permission.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/internal/auth/permission.go b/internal/auth/permission.go index 3a804bd..3a114ea 100644 --- a/internal/auth/permission.go +++ b/internal/auth/permission.go @@ -7,7 +7,8 @@ import ( ) func CheckPermissions(r *http.Request, requestedActionPermissions ...permissions.PermissionGroup) (bool, error) { - // TODO: check permissions - // TODO: perhaps utilize userpermissions.GetFromRole - return false, nil + requiredPermissions := permissions.AllOf{ + Groups: requestedActionPermissions, + } + return requiredPermissions.IsAuthorized(r), nil } From 1ed5cc291454c2281940a0726b00086a55514bad Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Sat, 22 Jul 2023 20:28:49 +0800 Subject: [PATCH 08/10] Create users' group role enum Adapted from the users' login provider enum. --- internal/enums/groups/role.go | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 internal/enums/groups/role.go diff --git a/internal/enums/groups/role.go b/internal/enums/groups/role.go new file mode 100644 index 0000000..2e71ccc --- /dev/null +++ b/internal/enums/groups/role.go @@ -0,0 +1,35 @@ +package groupenums + +type Role uint + +const ( + RoleStandard Role = iota + RoleModerator + RoleAdmin +) + +// We cannot name it String() because it will conflict with the String() method +func (role Role) ToString() string { + switch role { + case RoleStandard: + return "user" + case RoleModerator: + return "moderator" + case RoleAdmin: + return "admin" + } + return "unknown" +} + +func RoleFromString(role string) (Role, bool) { + switch role { + case "user": + return RoleStandard, true + case "moderator": + return RoleModerator, true + case "admin": + return RoleAdmin, true + } + // We fall back to standard role as default + return RoleStandard, false +} From e2065dd71f51e13a8f3761f68c9b863de3dc60e1 Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Wed, 26 Jul 2023 14:47:31 +0800 Subject: [PATCH 09/10] Integrate Role enum to role permissions check --- internal/auth/permissions/users/role.go | 19 +++---------------- internal/auth/permissions/users/users.go | 9 +++++---- internal/enums/groups/role.go | 12 ++++++++++++ 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/internal/auth/permissions/users/role.go b/internal/auth/permissions/users/role.go index 28614f0..9db590b 100644 --- a/internal/auth/permissions/users/role.go +++ b/internal/auth/permissions/users/role.go @@ -4,12 +4,12 @@ import ( "net/http" "github.com/source-academy/stories-backend/internal/auth" - // groupenums "github.com/source-academy/stories-backend/internal/enums/groups" + groupenums "github.com/source-academy/stories-backend/internal/enums/groups" ) type RolePermission struct { Permission Permission - // Role groupenums.Role + Role groupenums.Role } func (p RolePermission) IsAuthorized(r *http.Request) bool { @@ -20,19 +20,6 @@ func (p RolePermission) IsAuthorized(r *http.Request) bool { // TODO: Implement. Blocked by request context missing group info. _ = userID // role := getRole(userID, groupID) - // return isRoleGreaterThan(role, p.Role) + // return groupenums.IsRoleGreaterThan(role, p.Role) return false } - -// TODO: Move comparison function to role enum -// func isRoleGreaterThan(role1, role2 groupenums.Role) bool { -// switch role1 { -// case groupenums.RoleAdmin: -// return true -// case groupenums.RoleModerator: -// return role2 != groupenums.RoleAdmin -// case groupenums.RoleStandard: -// return role2 == groupenums.RoleStandard -// } -// return false -// } diff --git a/internal/auth/permissions/users/users.go b/internal/auth/permissions/users/users.go index a75d6b0..a6e8b8b 100644 --- a/internal/auth/permissions/users/users.go +++ b/internal/auth/permissions/users/users.go @@ -2,7 +2,8 @@ package userpermissions import ( "errors" - // groupenums "github.com/source-academy/stories-backend/internal/enums/groups" + + groupenums "github.com/source-academy/stories-backend/internal/enums/groups" ) // Gets the RolePermission from a Permission. To be called @@ -17,7 +18,7 @@ func GetRolePermission(p Permission) *RolePermission { CanReadStories: return &RolePermission{ Permission: p, - // Role: groupenums.RoleStandard, + Role: groupenums.RoleStandard, } case // Additional permissions for moderators and administrators @@ -25,7 +26,7 @@ func GetRolePermission(p Permission) *RolePermission { CanDeleteStories: return &RolePermission{ Permission: p, - // Role: groupenums.RoleModerator, + Role: groupenums.RoleModerator, } case // Additional permissions for administrators only @@ -39,7 +40,7 @@ func GetRolePermission(p Permission) *RolePermission { CanDeleteGroups: return &RolePermission{ Permission: p, - // Role: groupenums.RoleAdmin, + Role: groupenums.RoleAdmin, } } // Illegal path - all permissions should have been handled above diff --git a/internal/enums/groups/role.go b/internal/enums/groups/role.go index 2e71ccc..7b63026 100644 --- a/internal/enums/groups/role.go +++ b/internal/enums/groups/role.go @@ -33,3 +33,15 @@ func RoleFromString(role string) (Role, bool) { // We fall back to standard role as default return RoleStandard, false } + +func IsRoleGreaterThan(role1, role2 Role) bool { + switch role1 { + case RoleAdmin: + return true + case RoleModerator: + return role2 != RoleAdmin + case RoleStandard: + return role2 == RoleStandard + } + return false +} From 78362c1eaf6ef2928ca21395f02ef4f6e700c1df Mon Sep 17 00:00:00 2001 From: Richard Dominick <34370238+RichDom2185@users.noreply.github.com> Date: Wed, 26 Jul 2023 14:49:28 +0800 Subject: [PATCH 10/10] Replace TODO with FIXME Done due to the severity of the lack of implementation (breaks entire access control framework). --- internal/auth/permissions/users/role.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/auth/permissions/users/role.go b/internal/auth/permissions/users/role.go index 9db590b..3fb58c5 100644 --- a/internal/auth/permissions/users/role.go +++ b/internal/auth/permissions/users/role.go @@ -17,7 +17,7 @@ func (p RolePermission) IsAuthorized(r *http.Request) bool { if err != nil { return false } - // TODO: Implement. Blocked by request context missing group info. + // FIXME: Implement. Blocked by request context missing group info. _ = userID // role := getRole(userID, groupID) // return groupenums.IsRoleGreaterThan(role, p.Role)