From 7c4f32641072dfa8f5a808c682782de50a3eda34 Mon Sep 17 00:00:00 2001 From: Sisi Date: Fri, 1 May 2026 14:28:07 +0200 Subject: [PATCH] only lower id roles can be modfified, add new hub endpoints --- packages/convertions/convertions.go | 4 + packages/httpRequest/hubs.go | 239 ++++++++++++++++++++++------ packages/types/types.go | 38 ++++- todo.txt | 1 + 4 files changed, 227 insertions(+), 55 deletions(-) diff --git a/packages/convertions/convertions.go b/packages/convertions/convertions.go index 7852286..fc83e6a 100644 --- a/packages/convertions/convertions.go +++ b/packages/convertions/convertions.go @@ -73,3 +73,7 @@ func StringToUuidSlice(uuidStr string) ([]uuid.UUID, error) { func StringToTimestamp(str string) (time.Time, error) { return time.Parse(time.RFC3339, str) } + +func StringToBool(str string) bool { + return strings.ToLower(str) == "true" +} diff --git a/packages/httpRequest/hubs.go b/packages/httpRequest/hubs.go index 655379c..44e6389 100644 --- a/packages/httpRequest/hubs.go +++ b/packages/httpRequest/hubs.go @@ -3,13 +3,14 @@ package httpRequest import ( "context" "encoding/json" - "go-socket/packages/convertions" "maps" "net/http" "slices" "strings" "time" + "go-socket/packages/convertions" + "go-socket/packages/cache" "go-socket/packages/types" "go-socket/packages/wsServer" @@ -17,7 +18,7 @@ import ( "github.com/google/uuid" ) -func haveHubUserPermission(u *types.HubUser, h *types.Hub, needed types.Permissions) bool { +func haveHubUserPermission(u *types.HubUser, h *types.Hub, needed types.Permissions) (id uint8, has bool) { h.Mu.Lock() defer h.Mu.Unlock() for _, role := range h.Roles { @@ -27,11 +28,30 @@ func haveHubUserPermission(u *types.HubUser, h *types.Hub, needed types.Permissi if !u.Roles.Has(role.Id) { continue } - if (needed & role.Permissions) == needed { - return true + if (needed&role.Permissions) == needed && role.Id > id { + id = role.Id + has = true } } - return false + return id, has +} + +func hubUserHighestRoleId(u *types.HubUser, h *types.Hub) (id uint8) { + h.Mu.Lock() + defer h.Mu.Unlock() + + for _, role := range h.Roles { + if role == nil { + continue + } + if !u.Roles.Has(role.Id) { + continue + } + if role.Id > id { + id = role.Id + } + } + return id } func haveHubUserCachedPermissions(needed types.CachedUserPermissions, user *types.HubUser, channel *types.HubChannel) bool { @@ -222,24 +242,27 @@ func HandleGetHubs(response http.ResponseWriter, request *http.Request) { response.Write(converted) } -func hubPermissionContext(response http.ResponseWriter, request *http.Request, rt bodyLimit, needed types.Permissions) (*types.HubUser, *types.Hub, context.Context, bool) { +func hubPermissionContext(response http.ResponseWriter, request *http.Request, rt bodyLimit, needed types.Permissions) (*types.HubUser, *types.Hub, context.Context, uint8, bool) { if !validCheckWithResponseOnFail(response, request, rt) { - return nil, nil, nil, false + return nil, nil, nil, 0, false } ctx := request.Context() _, hubUser, hub, err := getHubUserIfValidWithResponseOnFail(ctx, response, request) if err != nil { - return nil, nil, nil, false + return nil, nil, nil, 0, false } - if !haveHubUserPermission(hubUser, hub, needed) { + + roleId, ok := haveHubUserPermission(hubUser, hub, needed) + + if !ok { http.Error(response, "", http.StatusForbidden) - return nil, nil, nil, false + return nil, nil, nil, 0, false } - return hubUser, hub, ctx, true + return hubUser, hub, ctx, roleId, true } func HandleHubSetName(response http.ResponseWriter, request *http.Request) { - _, hub, _, ok := hubPermissionContext(response, request, normal, types.PermissionSetHubName) + _, hub, _, _, ok := hubPermissionContext(response, request, normal, types.PermissionSetHubName) if !ok { return } @@ -253,7 +276,7 @@ func HandleHubSetName(response http.ResponseWriter, request *http.Request) { } func HandleHubSetColor(response http.ResponseWriter, request *http.Request) { - _, hub, _, ok := hubPermissionContext(response, request, normal, types.PermissionSetHubColor) + _, hub, _, _, ok := hubPermissionContext(response, request, normal, types.PermissionSetHubColor) if !ok { return } @@ -267,7 +290,7 @@ func HandleHubSetColor(response http.ResponseWriter, request *http.Request) { } func HandleHubRemove(response http.ResponseWriter, request *http.Request) { - _, hub, _, ok := hubPermissionContext(response, request, normal, types.PermissionRemoveHub) + _, hub, _, _, ok := hubPermissionContext(response, request, normal, types.PermissionRemoveHub) if !ok { return } @@ -276,7 +299,7 @@ func HandleHubRemove(response http.ResponseWriter, request *http.Request) { } func HandleHubToggleUserColorAllowed(response http.ResponseWriter, request *http.Request) { - _, hub, _, ok := hubPermissionContext(response, request, normal, types.PermissionSetUserColorAllowed) + _, hub, _, _, ok := hubPermissionContext(response, request, normal, types.PermissionSetUserColorAllowed) if !ok { return } @@ -285,7 +308,7 @@ func HandleHubToggleUserColorAllowed(response http.ResponseWriter, request *http } func HandleHubUserRemove(response http.ResponseWriter, request *http.Request) { - _, hub, _, ok := hubPermissionContext(response, request, normal, types.PermissionRemoveUser) + _, hub, _, usedRoleId, ok := hubPermissionContext(response, request, normal, types.PermissionRemoveUser) if !ok { return } @@ -294,19 +317,27 @@ func HandleHubUserRemove(response http.ResponseWriter, request *http.Request) { http.Error(response, "bad targetid", http.StatusBadRequest) return } - hub.Mu.Lock() - if _, exists := hub.Users[targetId]; !exists { + hub.Mu.RLock() + target, exists := hub.Users[targetId] + if !exists { hub.Mu.Unlock() http.Error(response, "user not found", http.StatusNotFound) return } + hub.Mu.RUnlock() + + if usedRoleId <= hubUserHighestRoleId(target, hub) { + http.Error(response, "target higher in hierarchy", http.StatusForbidden) + return + } + hub.Mu.Lock() delete(hub.Users, targetId) hub.Mu.Unlock() response.WriteHeader(http.StatusAccepted) } func HandleHubRenameUser(response http.ResponseWriter, request *http.Request) { - _, hub, _, ok := hubPermissionContext(response, request, normal, types.PermissionRenameUser) + _, hub, _, usedRoleId, ok := hubPermissionContext(response, request, normal, types.PermissionRenameUser) if !ok { return } @@ -317,10 +348,16 @@ func HandleHubRenameUser(response http.ResponseWriter, request *http.Request) { return } hub.Mu.RLock() - target, ok := hub.Users[targetId] + target, exists := hub.Users[targetId] + if !exists { + hub.Mu.Unlock() + http.Error(response, "user not found", http.StatusNotFound) + return + } hub.Mu.RUnlock() - if !ok { - http.Error(response, "no users found", http.StatusNotFound) + + if usedRoleId <= hubUserHighestRoleId(target, hub) { + http.Error(response, "target higher in hierarchy", http.StatusForbidden) return } target.Name = newName @@ -329,16 +366,16 @@ func HandleHubRenameUser(response http.ResponseWriter, request *http.Request) { } func HandleHubRenameSelf(response http.ResponseWriter, request *http.Request) { - hubUser, _, _, ok := hubPermissionContext(response, request, normal, types.PermissionSelfRename) + requestor, _, _, _, ok := hubPermissionContext(response, request, normal, types.PermissionSelfRename) if !ok { return } - hubUser.Name = request.FormValue("newname") + requestor.Name = request.FormValue("newname") response.WriteHeader(http.StatusAccepted) } func HandleHubToggleMuteUser(response http.ResponseWriter, request *http.Request) { - _, hub, _, ok := hubPermissionContext(response, request, normal, types.PermissionMuteUser) + _, hub, _, usedRoleId, ok := hubPermissionContext(response, request, normal, types.PermissionMuteUser) if !ok { return } @@ -348,18 +385,25 @@ func HandleHubToggleMuteUser(response http.ResponseWriter, request *http.Request return } hub.Mu.RLock() - target, ok := hub.Users[targetId] - hub.Mu.RUnlock() - if !ok { + target, exists := hub.Users[targetId] + if !exists { + hub.Mu.Unlock() http.Error(response, "user not found", http.StatusNotFound) return } + hub.Mu.RUnlock() + + if usedRoleId <= hubUserHighestRoleId(target, hub) { + http.Error(response, "target higher in hierarchy", http.StatusForbidden) + return + } + hub.Mu.RLock() target.IsMuted = !target.IsMuted response.WriteHeader(http.StatusAccepted) } func HandleHubUserAddRole(response http.ResponseWriter, request *http.Request) { - _, hub, _, ok := hubPermissionContext(response, request, normal, types.PermissionUserAddRole) + _, hub, _, usedRoleId, ok := hubPermissionContext(response, request, normal, types.PermissionUserAddRole) if !ok { return } @@ -373,6 +417,11 @@ func HandleHubUserAddRole(response http.ResponseWriter, request *http.Request) { http.Error(response, "bad roleid", http.StatusBadRequest) return } + if roleId > usedRoleId { + http.Error(response, "target role higher in hierarchy", http.StatusForbidden) + return + } + hub.Mu.RLock() target, ok := hub.Users[targetId] role := hub.Roles[roleId] @@ -390,7 +439,7 @@ func HandleHubUserAddRole(response http.ResponseWriter, request *http.Request) { } func HandleHubUserRemoveRole(response http.ResponseWriter, request *http.Request) { - _, hub, _, ok := hubPermissionContext(response, request, normal, types.PermissionUserRemoveRole) + _, hub, _, usedRoleId, ok := hubPermissionContext(response, request, normal, types.PermissionUserRemoveRole) if !ok { return } @@ -404,6 +453,11 @@ func HandleHubUserRemoveRole(response http.ResponseWriter, request *http.Request http.Error(response, "bad roleid", http.StatusBadRequest) return } + if roleId > usedRoleId { + http.Error(response, "target role higher in hierarchy", http.StatusForbidden) + return + } + hub.Mu.RLock() target, ok := hub.Users[targetId] hub.Mu.RUnlock() @@ -416,7 +470,7 @@ func HandleHubUserRemoveRole(response http.ResponseWriter, request *http.Request } func HandleHubCreateRole(response http.ResponseWriter, request *http.Request) { - _, hub, _, ok := hubPermissionContext(response, request, normal, types.PermissionCreateRole) + _, hub, _, usedRoleId, ok := hubPermissionContext(response, request, normal, types.PermissionCreateRole) if !ok { return } @@ -429,7 +483,7 @@ func HandleHubCreateRole(response http.ResponseWriter, request *http.Request) { var freeId uint8 found := false for i := uint8(1); i < types.HubBoundRolesMax; i++ { - if hub.Roles[i] == nil { + if hub.Roles[i] == nil && usedRoleId > i { freeId = i found = true break @@ -451,32 +505,37 @@ func HandleHubCreateRole(response http.ResponseWriter, request *http.Request) { } func HandleHubRemoveRole(response http.ResponseWriter, request *http.Request) { - _, hub, _, ok := hubPermissionContext(response, request, normal, types.PermissionRemoveRole) + _, hub, _, usedRoleId, ok := hubPermissionContext(response, request, normal, types.PermissionRemoveRole) if !ok { return } - roleId, err := convertions.StringToUint8(request.FormValue("roleid")) + targetRoleId, err := convertions.StringToUint8(request.FormValue("roleid")) if err != nil { http.Error(response, "bad roleid", http.StatusBadRequest) return } - if roleId == 0 || roleId == types.HubBoundRolesMax { - http.Error(response, "cannot remove system role", http.StatusForbidden) + if targetRoleId == 0 { + http.Error(response, "cannot remove root role", http.StatusForbidden) return } + if targetRoleId > usedRoleId { + http.Error(response, "target role higher in hierarchy", http.StatusForbidden) + return + } + hub.Mu.Lock() - if hub.Roles[roleId] == nil { + if hub.Roles[targetRoleId] == nil { hub.Mu.Unlock() http.Error(response, "role not found", http.StatusNotFound) return } - hub.Roles[roleId] = nil + hub.Roles[targetRoleId] = nil hub.Mu.Unlock() response.WriteHeader(http.StatusAccepted) } -func HandleHubSelfRoleRemove(response http.ResponseWriter, request *http.Request) { - hubUser, _, _, ok := hubPermissionContext(response, request, normal, types.PermissionSelfRoleRemove) +func HandlePermissionSetRoleName(response http.ResponseWriter, request *http.Request) { + _, hub, _, usedRoleId, ok := hubPermissionContext(response, request, normal, types.PermissionSetRoleName) if !ok { return } @@ -485,18 +544,8 @@ func HandleHubSelfRoleRemove(response http.ResponseWriter, request *http.Request http.Error(response, "bad roleid", http.StatusBadRequest) return } - hubUser.Roles.Remove(roleId) - response.WriteHeader(http.StatusAccepted) -} - -func PermissionSetRoleName(response http.ResponseWriter, request *http.Request) { - _, hub, _, ok := hubPermissionContext(response, request, normal, types.PermissionSetRoleName) - if !ok { - return - } - roleId, err := convertions.StringToUint8(request.FormValue("roleid")) - if err != nil { - http.Error(response, "bad roleid", http.StatusBadRequest) + if roleId > usedRoleId { + http.Error(response, "target role higher in hierarchy", http.StatusForbidden) return } newName := request.FormValue("newname") @@ -515,3 +564,89 @@ func PermissionSetRoleName(response http.ResponseWriter, request *http.Request) hub.Mu.Unlock() response.WriteHeader(http.StatusAccepted) } + +func HandlePermissionSetRoleColor(response http.ResponseWriter, request *http.Request) { + _, hub, _, usedRoleId, ok := hubPermissionContext(response, request, normal, types.PermissionSetRoleColor) + if !ok { + return + } + roleId, err := convertions.StringToUint8(request.FormValue("roleid")) + if err != nil { + http.Error(response, "bad roleid", http.StatusBadRequest) + return + } + if roleId > usedRoleId { + http.Error(response, "target role higher in hierarchy", http.StatusForbidden) + return + } + color, err := convertions.StringToRgba(request.FormValue("newcolor")) + if err != nil { + http.Error(response, "invalid newcolor", http.StatusBadRequest) + return + } + + hub.Mu.Lock() + role := hub.Roles[roleId] + if role == nil { + http.Error(response, "no such role", http.StatusNotFound) + return + } + role.Color = color + hub.Mu.Unlock() + response.WriteHeader(http.StatusAccepted) +} + +func HandlePermissionSetRolePermissions(response http.ResponseWriter, request *http.Request) { + requestor, hub, _, usedRoleId, ok := hubPermissionContext(response, request, normal, types.PermissionSetRolePermissions) + if !ok { + return + } + targetRoleId, err := convertions.StringToUint8(request.FormValue("roleid")) + if err != nil { + http.Error(response, "bad roleid", http.StatusBadRequest) + return + } + if targetRoleId > usedRoleId { + http.Error(response, "target role higher in hierarchy", http.StatusForbidden) + return + } + hub.Mu.Lock() + targetRole := hub.Roles[targetRoleId] + hub.Mu.Unlock() + if targetRole == nil { + http.Error(response, "no such role", http.StatusNotFound) + return + } + + query := request.URL.Query() + for key, values := range query { + permission, ok := types.PermissionLookup(key) + if !ok { + continue + } + permRoleId, ok := haveHubUserPermission(requestor, hub, permission) + if !ok { + http.Error(response, "permission needed: "+key, http.StatusNotFound) + } + if permRoleId <= targetRoleId { + http.Error(response, "target role higher in hierarchy", http.StatusForbidden) + return + } + + if convertions.StringToBool(values[0]) { + targetRole.Permissions.Grant(permission) + } else { + targetRole.Permissions.Revoke(permission) + } + } + response.WriteHeader(http.StatusAccepted) +} + +func HandlePermissionSelfRoleRemove(response http.ResponseWriter, request *http.Request) { + requestor, _, _, usedRoleId, ok := hubPermissionContext(response, request, normal, types.PermissionUserSelfRoleRemove) + if !ok { + return + } + requestor.Roles.Remove(usedRoleId) + response.WriteHeader(http.StatusAccepted) +} diff --git a/packages/types/types.go b/packages/types/types.go index 04363cd..1bd4a44 100644 --- a/packages/types/types.go +++ b/packages/types/types.go @@ -157,6 +157,7 @@ const ( PermissionMuteUser PermissionUserAddRole PermissionUserRemoveRole + PermissionUserSelfRoleRemove // Role permissions PermissionCreateRole @@ -165,9 +166,6 @@ const ( PermissionSetRoleColor PermissionSetRolePermissions - PermissionSelfRoleRemove - PermissionOnlySelfRoleRemove - // Channel permissions PermissionAddChannel PermissionRemoveChannel @@ -178,6 +176,40 @@ const ( PermissionSetChannelPermittedReadHistoryRoles ) +var permissionRegistry = map[string]Permissions{ + "set_hub_name": PermissionSetHubName, + "set_hub_icon": PermissionSetHubIcon, + "set_hub_bg": PermissionSetHubBg, + "set_hub_color": PermissionSetHubColor, + "remove_hub": PermissionRemoveHub, + "set_user_color_allowed": PermissionSetUserColorAllowed, + "invite_user": PermissionInviteUser, + "remove_user": PermissionRemoveUser, + "rename_user": PermissionRenameUser, + "self_rename": PermissionSelfRename, + "mute_user": PermissionMuteUser, + "user_add_role": PermissionUserAddRole, + "user_remove_role": PermissionUserRemoveRole, + "create_role": PermissionCreateRole, + "remove_role": PermissionRemoveRole, + "set_role_name": PermissionSetRoleName, + "set_role_color": PermissionSetRoleColor, + "set_role_permissions": PermissionSetRolePermissions, + "self_role_remove": PermissionUserSelfRoleRemove, + "add_channel": PermissionAddChannel, + "remove_channel": PermissionRemoveChannel, + "set_channel_name": PermissionSetChannelName, + "set_channel_icon": PermissionSetChannelIcon, + "set_channel_permitted_visible": PermissionSetChannelPermittedVisibleRoles, + "set_channel_permitted_send": PermissionSetChannelPermittedSendMessageRoles, + "set_channel_permitted_read": PermissionSetChannelPermittedReadHistoryRoles, +} + +func PermissionLookup(permission string) (Permissions, bool) { + p, ok := permissionRegistry[permission] + return p, ok +} + func (p *Permissions) Grant(permissions Permissions) { *p |= permissions } diff --git a/todo.txt b/todo.txt index 47cc668..54c4eb8 100644 --- a/todo.txt +++ b/todo.txt @@ -3,6 +3,7 @@ when user not ws connected collect count of unread messages for each conn (add d add hubs setting avatar and profilebg check when mutex needed +change api endpoints and body/url/header to follow same case user banners