diff --git a/api/queryAPI/adminHandlers.go b/api/queryAPI/adminHandlers.go index 288e4cc0..bad1115b 100644 --- a/api/queryAPI/adminHandlers.go +++ b/api/queryAPI/adminHandlers.go @@ -18,6 +18,12 @@ import ( "github.com/oapi-codegen/runtime/types" ) +const ( + adminCognitoListPageSize = 60 + maxAdminCognitoListPages = 100 + maxAdminCognitoListUsers = 6000 +) + // getAdminUserForAudit retrieves the admin user information for audit logging. // When DISABLE_AUTH is set to "true", it returns a default system user for audit purposes. // Otherwise, it attempts to extract user info from the JWT token in the request. @@ -95,6 +101,12 @@ func (ctrl *Controllers) CreateAdminUser(ctx echo.Context) error { }) } + httpClient := &http.Client{Timeout: 10 * time.Second} + permitConfig := ctrl.getPermitConfig() + if !canAdminModeCreateUser(ctrl.getAdminTeamMode(ctx, httpClient, permitConfig), string(req.Email), req.Roles) { + return adminUserForbidden(ctx) + } + // Create AWS config and Cognito client awsCfg, err := aws.GetAWSConfig(context.Background()) if err != nil { @@ -204,9 +216,6 @@ func (ctrl *Controllers) CreateAdminUser(ctx echo.Context) error { ) // Create user in Permit.io - httpClient := &http.Client{Timeout: 10 * time.Second} - permitConfig := ctrl.getPermitConfig() - permitSynced := false err = usermanagement.CreatePermitUser(httpClient, permitConfig, cognitoUser) if err != nil { @@ -354,14 +363,11 @@ func (ctrl *Controllers) GetAdminUser(ctx echo.Context, email UserEmail) error { }) } - // Get user roles from Permit.io - httpClient := &http.Client{Timeout: 10 * time.Second} - permitConfig := ctrl.getPermitConfig() - - roles, err := usermanagement.GetUserRoles(httpClient, permitConfig, cognitoUser.SubjectID) - if err != nil { - ctrl.cfg.GetAuthLogger().Warn("Failed to get user roles from Permit.io", "error", err, "email", email) + targetAccess := ctrl.getAdminTargetAccess(ctx, email, cognitoUser) + if !targetAccess.canRead(cognitoUser.Email) { + return adminUserNotFound(ctx, string(email)) } + roles := targetAccess.roles // Log successful retrieval usermanagement.LogUserListAction( @@ -422,6 +428,11 @@ func (ctrl *Controllers) CheckAdminUserExists(ctx echo.Context, email UserEmail) return ctx.NoContent(http.StatusNotFound) } + targetAccess := ctrl.getAdminTargetAccess(ctx, email, cognitoUser) + if !targetAccess.canRead(cognitoUser.Email) { + return ctx.NoContent(http.StatusNotFound) + } + return ctx.NoContent(http.StatusOK) } @@ -453,43 +464,34 @@ func (ctrl *Controllers) ListAdminUsers(ctx echo.Context, params ListAdminUsersP Region: ctrl.cfg.GetAuthRegion(), } - // Set default pagination parameters - page := int32(1) - pageSize := int32(50) - if params.Page != nil && *params.Page > 0 { - page = *params.Page - } - if params.PageSize != nil && *params.PageSize > 0 && *params.PageSize <= 60 { - pageSize = *params.PageSize - } + page, pageSize := adminUserListPagination(params) - // List users from Cognito (note: Cognito doesn't support traditional page numbers, only pagination tokens) - // For simplicity, we'll just return the first page - result, err := usermanagement.ListCognitoUsers(context.Background(), cognitoClient, cognitoConfig.UserPoolID, int(pageSize), nil) + allUsers, err := listAllAdminCognitoUsers(cognitoClient, cognitoConfig.UserPoolID) if err != nil { ctrl.cfg.GetAuthLogger().Error("Failed to list users from Cognito", "error", err) return ctx.JSON(http.StatusBadGateway, ErrorMessage{ Message: fmt.Sprintf("Failed to list users from Cognito: %v", err), }) } + filteredUsers := usermanagement.FilterUsersByEnvironmentID(allUsers, ctrl.getCognitoEnvironmentID()) - // Apply environment filtering when COGNITO_ENVIRONMENT_ID is configured - filteredUsers := usermanagement.FilterUsersByEnvironmentID(result.Users, ctrl.getCognitoEnvironmentID()) - - // Convert to AdminUserDetails format - users := make([]AdminUserDetails, 0, len(filteredUsers)) - for _, cognitoUser := range filteredUsers { - userDetail := AdminUserDetails{ - CognitoSubjectId: cognitoUser.SubjectID, - Email: types.Email(cognitoUser.Email), - FirstName: &cognitoUser.FirstName, - LastName: &cognitoUser.LastName, - Status: cognitoUser.Status, - Enabled: cognitoUser.Enabled, - } - users = append(users, userDetail) + httpClient := &http.Client{Timeout: 10 * time.Second} + permitConfig := ctrl.getPermitConfig() + adminMode := ctrl.getAdminTeamMode(ctx, httpClient, permitConfig) + if adminMode == adminTeamModeNone { + return adminUserForbidden(ctx) } + roleAssignments, err := usermanagement.ListAllPermitRoleAssignments(httpClient, permitConfig) + if err != nil { + ctrl.cfg.GetAuthLogger().Error("Failed to list role assignments from Permit.io", "error", err) + return ctx.JSON(http.StatusBadGateway, ErrorMessage{ + Message: fmt.Sprintf("Failed to list role assignments from Permit.io: %v", err), + }) + } + visibleUsers := ctrl.visibleAdminUsers(filteredUsers, adminMode, adminRoleAssignmentsByUser(roleAssignments)) + users, hasMore := paginateAdminUsers(visibleUsers, page, pageSize) + // Log list action usermanagement.LogUserListAction( ctrl.cfg.GetAuthLogger(), @@ -500,9 +502,7 @@ func (ctrl *Controllers) ListAdminUsers(ctx echo.Context, params ListAdminUsersP ) // Build response - hasMore := result.PaginationKey != nil && *result.PaginationKey != "" - // len(users) is bounded by pageSize (max 60), so this conversion is safe - total := int32(len(users)) // #nosec G115 -- len(users) is bounded by pageSize which is max 60 + total := int32(len(visibleUsers)) // #nosec G115 -- admin user lists are bounded by Cognito user pool size response := AdminUserList{ Users: users, Total: total, @@ -514,6 +514,106 @@ func (ctrl *Controllers) ListAdminUsers(ctx echo.Context, params ListAdminUsersP return ctx.JSON(http.StatusOK, response) } +func adminUserListPagination(params ListAdminUsersParams) (int32, int32) { + page := int32(1) + pageSize := int32(50) + if params.Page != nil && *params.Page > 0 { + page = *params.Page + } + if params.PageSize != nil && *params.PageSize > 0 && *params.PageSize <= 60 { + pageSize = *params.PageSize + } + return page, pageSize +} + +func listAllAdminCognitoUsers(cognitoClient *cognitoidentityprovider.Client, userPoolID string) ([]usermanagement.CognitoUserResponse, error) { + return collectAdminCognitoUsers(func(pageSize int, paginationToken *string) (*usermanagement.ListCognitoUsersResult, error) { + return usermanagement.ListCognitoUsers(context.Background(), cognitoClient, userPoolID, pageSize, paginationToken) + }) +} + +func collectAdminCognitoUsers(fetchPage func(pageSize int, paginationToken *string) (*usermanagement.ListCognitoUsersResult, error)) ([]usermanagement.CognitoUserResponse, error) { + var users []usermanagement.CognitoUserResponse + var paginationToken *string + for page := 1; page <= maxAdminCognitoListPages; page++ { + result, err := fetchPage(adminCognitoListPageSize, paginationToken) + if err != nil { + return nil, err + } + users = append(users, result.Users...) + if len(users) > maxAdminCognitoListUsers { + return nil, fmt.Errorf("admin Cognito user listing exceeded %d users", maxAdminCognitoListUsers) + } + if result.PaginationKey == nil || *result.PaginationKey == "" { + return users, nil + } + paginationToken = result.PaginationKey + } + return nil, fmt.Errorf("admin Cognito user listing exceeded %d pages", maxAdminCognitoListPages) +} + +func (ctrl *Controllers) visibleAdminUsers(users []usermanagement.CognitoUserResponse, mode adminTeamMode, rolesBySubject map[string][]string) []AdminUserDetails { + visibleUsers := make([]AdminUserDetails, 0, len(users)) + for _, cognitoUser := range users { + roles, ok := rolesForAdminListUser(cognitoUser, mode, rolesBySubject) + if !ok || !canAdminModeReadTarget(mode, cognitoUser.Email, roles) { + continue + } + visibleUsers = append(visibleUsers, adminUserDetailsFromCognito(cognitoUser, roles)) + } + return visibleUsers +} + +func rolesForAdminListUser(cognitoUser usermanagement.CognitoUserResponse, mode adminTeamMode, rolesBySubject map[string][]string) ([]string, bool) { + roles, ok := rolesBySubject[cognitoUser.SubjectID] + if !ok && mode == adminTeamModeUserAdmin { + return nil, false + } + if roles == nil { + roles = []string{} + } + return roles, true +} + +func adminRoleAssignmentsByUser(assignments []usermanagement.RoleAssignment) map[string][]string { + rolesBySubject := make(map[string][]string, len(assignments)) + for _, assignment := range assignments { + if assignment.User == "" || assignment.Role == "" { + continue + } + rolesBySubject[assignment.User] = append(rolesBySubject[assignment.User], assignment.Role) + } + return rolesBySubject +} + +func adminUserDetailsFromCognito(cognitoUser usermanagement.CognitoUserResponse, roles []string) AdminUserDetails { + if roles == nil { + roles = []string{} + } + userDetail := AdminUserDetails{ + CognitoSubjectId: cognitoUser.SubjectID, + Email: types.Email(cognitoUser.Email), + FirstName: &cognitoUser.FirstName, + LastName: &cognitoUser.LastName, + Status: cognitoUser.Status, + Enabled: cognitoUser.Enabled, + } + userDetail.Roles = &roles + return userDetail +} + +func paginateAdminUsers(visibleUsers []AdminUserDetails, page int32, pageSize int32) ([]AdminUserDetails, bool) { + start := int((page - 1) * pageSize) // #nosec G115 -- page and pageSize are positive int32 request parameters + end := start + int(pageSize) + if start > len(visibleUsers) { + start = len(visibleUsers) + } + if end > len(visibleUsers) { + end = len(visibleUsers) + } + return visibleUsers[start:end], end < len(visibleUsers) +} + // updateUserAttributesInCognito updates user attributes in Cognito and logs the operation. func (ctrl *Controllers) updateUserAttributesInCognito(ctx echo.Context, cognitoClient *cognitoidentityprovider.Client, cognitoConfig *usermanagement.CognitoConfig, email UserEmail, req AdminUserUpdate, adminUser cognitoauth.UserInfo, cognitoUser *usermanagement.CognitoUserResponse) error { if req.FirstName == nil && req.LastName == nil { @@ -576,7 +676,7 @@ func (ctrl *Controllers) updateUserAttributesInCognito(ctx echo.Context, cognito // updateUserRolesInPermit updates user roles in Permit.io and logs each operation. func (ctrl *Controllers) updateUserRolesInPermit(ctx echo.Context, email UserEmail, req AdminUserUpdate, adminUser cognitoauth.UserInfo, subjectID string, httpClient *http.Client, permitConfig *usermanagement.PermitConfig) { - if req.Roles == nil || len(*req.Roles) == 0 { + if req.Roles == nil { return } @@ -733,6 +833,16 @@ func (ctrl *Controllers) UpdateAdminUser(ctx echo.Context, email UserEmail) erro }) } + targetAccess := ctrl.getAdminTargetAccess(ctx, email, cognitoUser) + if !targetAccess.canMutate(cognitoUser.Email) { + return adminUserForbidden(ctx) + } + if !canAdminModeAssignRequestedRoles(targetAccess.mode, req.Roles) { + return adminUserForbidden(ctx) + } + httpClient := targetAccess.httpClient + permitConfig := targetAccess.permitConfig + // Update user attributes in Cognito if provided err = ctrl.updateUserAttributesInCognito(ctx, cognitoClient, cognitoConfig, email, req, adminUser, cognitoUser) if err != nil { @@ -742,9 +852,6 @@ func (ctrl *Controllers) UpdateAdminUser(ctx echo.Context, email UserEmail) erro } // Handle role updates in Permit.io if provided - httpClient := &http.Client{Timeout: 10 * time.Second} - permitConfig := ctrl.getPermitConfig() - ctrl.updateUserRolesInPermit(ctx, email, req, adminUser, cognitoUser.SubjectID, httpClient, permitConfig) // Get updated user from Cognito @@ -847,6 +954,13 @@ func (ctrl *Controllers) DeleteAdminUser(ctx echo.Context, email UserEmail, para }) } + targetAccess := ctrl.getAdminTargetAccess(ctx, email, cognitoUser) + if !targetAccess.canMutate(cognitoUser.Email) { + return adminUserForbidden(ctx) + } + httpClient := targetAccess.httpClient + permitConfig := targetAccess.permitConfig + // Delete user from Cognito cognitoDeleted := false err = usermanagement.DeleteCognitoUser(context.Background(), cognitoClient, cognitoConfig.UserPoolID, string(email)) @@ -879,9 +993,6 @@ func (ctrl *Controllers) DeleteAdminUser(ctx echo.Context, email UserEmail, para } // Delete user from Permit.io - httpClient := &http.Client{Timeout: 10 * time.Second} - permitConfig := ctrl.getPermitConfig() - permitDeleted := false err = usermanagement.DeletePermitUser(httpClient, permitConfig, cognitoUser.SubjectID) if err != nil { @@ -988,6 +1099,11 @@ func (ctrl *Controllers) DisableAdminUser(ctx echo.Context, email UserEmail) err }) } + targetAccess := ctrl.getAdminTargetAccess(ctx, email, cognitoUser) + if !targetAccess.canMutate(cognitoUser.Email) { + return adminUserForbidden(ctx) + } + // Disable user in Cognito err = usermanagement.DisableCognitoUser(context.Background(), cognitoClient, cognitoConfig.UserPoolID, string(email)) if err != nil { @@ -1099,6 +1215,11 @@ func (ctrl *Controllers) EnableAdminUser(ctx echo.Context, email UserEmail) erro }) } + targetAccess := ctrl.getAdminTargetAccess(ctx, email, cognitoUser) + if !targetAccess.canMutate(cognitoUser.Email) { + return adminUserForbidden(ctx) + } + // Enable user in Cognito err = usermanagement.EnableCognitoUser(context.Background(), cognitoClient, cognitoConfig.UserPoolID, string(email)) if err != nil { diff --git a/api/queryAPI/adminHandlers_access.go b/api/queryAPI/adminHandlers_access.go new file mode 100644 index 00000000..a7a39da9 --- /dev/null +++ b/api/queryAPI/adminHandlers_access.go @@ -0,0 +1,195 @@ +package queryapi + +import ( + "net/http" + "os" + "strings" + "time" + + "queryorchestration/internal/cognitoauth" + "queryorchestration/internal/usermanagement" + + "github.com/labstack/echo/v4" +) + +type adminTargetAccess struct { + httpClient *http.Client + permitConfig *usermanagement.PermitConfig + mode adminTeamMode + roles []string + roleLookupOK bool +} + +type adminTeamMode string + +const ( + adminTeamModeNone adminTeamMode = "none" + adminTeamModeSuperAdmin adminTeamMode = "super_admin" + adminTeamModeUserAdmin adminTeamMode = "user_admin" + adminTeamModeAuditor adminTeamMode = "auditor" + + aareteEmailDomain = "aarete.com" +) + +var rolesHiddenFromUserAdmin = map[string]struct{}{ + "auditor": {}, + "super_admin": {}, +} + +var rolesAssignableByUserAdmin = map[string]struct{}{ + "client_user": {}, + "user_admin": {}, +} + +func adminModeForRoles(roles []string) adminTeamMode { + if len(roles) == 0 { + return adminTeamModeNone + } + + for _, role := range roles { + if role == "super_admin" { + return adminTeamModeSuperAdmin + } + } + for _, role := range roles { + if role == "user_admin" || role == "client_admin" { + return adminTeamModeUserAdmin + } + } + for _, role := range roles { + if role == "auditor" { + return adminTeamModeAuditor + } + } + + return adminTeamModeNone +} + +func isAAreteEmail(email string) bool { + parts := strings.Split(strings.TrimSpace(email), "@") + return len(parts) == 2 && strings.EqualFold(parts[1], aareteEmailDomain) +} + +func userAdminCanSeeOrMutateTarget(email string, roles []string) bool { + if isAAreteEmail(email) { + return false + } + + for _, role := range roles { + if _, hidden := rolesHiddenFromUserAdmin[role]; hidden { + return false + } + } + + return true +} + +func canAdminModeReadTarget(mode adminTeamMode, email string, roles []string) bool { + switch mode { + case adminTeamModeSuperAdmin, adminTeamModeAuditor: + return true + case adminTeamModeUserAdmin: + return userAdminCanSeeOrMutateTarget(email, roles) + default: + return false + } +} + +func canAdminModeMutateTarget(mode adminTeamMode, email string, roles []string) bool { + switch mode { + case adminTeamModeSuperAdmin: + return true + case adminTeamModeUserAdmin: + return userAdminCanSeeOrMutateTarget(email, roles) + default: + return false + } +} + +func userAdminCanAssignRoles(roles []string) bool { + for _, role := range roles { + if _, allowed := rolesAssignableByUserAdmin[role]; !allowed { + return false + } + } + return true +} + +func canAdminModeCreateUser(mode adminTeamMode, email string, roles []string) bool { + switch mode { + case adminTeamModeSuperAdmin: + return true + case adminTeamModeUserAdmin: + return !isAAreteEmail(email) && userAdminCanAssignRoles(roles) + default: + return false + } +} + +func canAdminModeAssignRequestedRoles(mode adminTeamMode, roles *[]string) bool { + return roles == nil || mode != adminTeamModeUserAdmin || userAdminCanAssignRoles(*roles) +} + +func (ctrl *Controllers) getAdminTargetAccess(ctx echo.Context, email UserEmail, cognitoUser *usermanagement.CognitoUserResponse) adminTargetAccess { + httpClient := &http.Client{Timeout: 10 * time.Second} + permitConfig := ctrl.getPermitConfig() + roles, err := usermanagement.GetUserRoles(httpClient, permitConfig, cognitoUser.SubjectID) + roleLookupOK := err == nil + if err != nil { + ctrl.cfg.GetAuthLogger().Warn("Failed to get user roles from Permit.io", "error", err, "email", email) + } + + return adminTargetAccess{ + httpClient: httpClient, + permitConfig: permitConfig, + mode: ctrl.getAdminTeamMode(ctx, httpClient, permitConfig), + roles: roles, + roleLookupOK: roleLookupOK, + } +} + +func (access adminTargetAccess) canRead(email string) bool { + if access.mode == adminTeamModeUserAdmin && !access.roleLookupOK { + return false + } + return canAdminModeReadTarget(access.mode, email, access.roles) +} + +func (access adminTargetAccess) canMutate(email string) bool { + if access.mode == adminTeamModeUserAdmin && !access.roleLookupOK { + return false + } + return canAdminModeMutateTarget(access.mode, email, access.roles) +} + +func (ctrl *Controllers) getAdminTeamMode(ctx echo.Context, httpClient *http.Client, permitConfig *usermanagement.PermitConfig) adminTeamMode { + if strings.EqualFold(os.Getenv("DISABLE_AUTH"), "true") { + return adminTeamModeSuperAdmin + } + + if subjectID, ok := cognitoauth.GetUserSubject(ctx); ok && subjectID != "" { + roles, err := usermanagement.GetUserRoles(httpClient, permitConfig, subjectID) + if err == nil { + return adminModeForRoles(roles) + } + ctrl.cfg.GetAuthLogger().Warn("Failed to get admin roles from Permit.io", "error", err, "subject_id", subjectID) + } + + if userInfo, ok := cognitoauth.GetUserInfo(ctx); ok { + return adminModeForRoles(userInfo.Groups) + } + + return adminTeamModeNone +} + +func adminUserForbidden(ctx echo.Context) error { + return ctx.JSON(http.StatusForbidden, ErrorMessage{ + Message: "Insufficient permissions for this team management action", + }) +} + +func adminUserNotFound(ctx echo.Context, email string) error { + return ctx.JSON(http.StatusNotFound, ErrorMessage{ + Message: "User not found: " + email, + }) +} diff --git a/api/queryAPI/adminHandlers_access_test.go b/api/queryAPI/adminHandlers_access_test.go new file mode 100644 index 00000000..b5ce1349 --- /dev/null +++ b/api/queryAPI/adminHandlers_access_test.go @@ -0,0 +1,632 @@ +package queryapi + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "queryorchestration/internal/cognitoauth" + "queryorchestration/internal/usermanagement" + + "github.com/labstack/echo/v4" +) + +func TestAdminModeForRoles(t *testing.T) { + tests := []struct { + name string + roles []string + want adminTeamMode + }{ + {name: "super admin wins", roles: []string{"user_admin", "super_admin"}, want: adminTeamModeSuperAdmin}, + {name: "client admin maps to user admin", roles: []string{"client_admin"}, want: adminTeamModeUserAdmin}, + {name: "auditor without higher role", roles: []string{"auditor"}, want: adminTeamModeAuditor}, + {name: "unrecognized", roles: []string{"client_user"}, want: adminTeamModeNone}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := adminModeForRoles(tt.roles); got != tt.want { + t.Fatalf("adminModeForRoles(%v) = %s, want %s", tt.roles, got, tt.want) + } + }) + } +} + +func TestIsAAreteEmail(t *testing.T) { + tests := []struct { + email string + want bool + }{ + {email: "person@aarete.com", want: true}, + {email: "Person@AArete.com", want: true}, + {email: "person@sub.aarete.com", want: false}, + {email: "person@aarete.com.fake", want: false}, + {email: "not-an-email", want: false}, + } + + for _, tt := range tests { + t.Run(tt.email, func(t *testing.T) { + if got := isAAreteEmail(tt.email); got != tt.want { + t.Fatalf("isAAreteEmail(%q) = %v, want %v", tt.email, got, tt.want) + } + }) + } +} + +func TestAdminTargetVisibilityAndMutation(t *testing.T) { + clientRoles := []string{"client_user"} + userAdminRoles := []string{"user_admin"} + auditorRoles := []string{"auditor"} + superAdminRoles := []string{"super_admin"} + + tests := []struct { + name string + mode adminTeamMode + email string + roles []string + wantRead bool + wantMutate bool + }{ + {name: "user admin can read and mutate client user", mode: adminTeamModeUserAdmin, email: "client@example.com", roles: clientRoles, wantRead: true, wantMutate: true}, + {name: "user admin can read and mutate user admin", mode: adminTeamModeUserAdmin, email: "admin@example.com", roles: userAdminRoles, wantRead: true, wantMutate: true}, + {name: "user admin cannot read or mutate aarete domain", mode: adminTeamModeUserAdmin, email: "person@aarete.com", roles: clientRoles, wantRead: false, wantMutate: false}, + {name: "user admin cannot read or mutate auditor", mode: adminTeamModeUserAdmin, email: "auditor@example.com", roles: auditorRoles, wantRead: false, wantMutate: false}, + {name: "user admin cannot read or mutate super admin", mode: adminTeamModeUserAdmin, email: "super@example.com", roles: superAdminRoles, wantRead: false, wantMutate: false}, + {name: "auditor can read all", mode: adminTeamModeAuditor, email: "person@aarete.com", roles: superAdminRoles, wantRead: true, wantMutate: false}, + {name: "super admin can read and mutate all", mode: adminTeamModeSuperAdmin, email: "person@aarete.com", roles: superAdminRoles, wantRead: true, wantMutate: true}, + {name: "none cannot read or mutate", mode: adminTeamModeNone, email: "client@example.com", roles: clientRoles, wantRead: false, wantMutate: false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := canAdminModeReadTarget(tt.mode, tt.email, tt.roles); got != tt.wantRead { + t.Fatalf("canAdminModeReadTarget() = %v, want %v", got, tt.wantRead) + } + if got := canAdminModeMutateTarget(tt.mode, tt.email, tt.roles); got != tt.wantMutate { + t.Fatalf("canAdminModeMutateTarget() = %v, want %v", got, tt.wantMutate) + } + }) + } +} + +func TestAdminCreatePermissions(t *testing.T) { + tests := []struct { + name string + mode adminTeamMode + email string + roles []string + want bool + }{ + {name: "super admin can create all roles", mode: adminTeamModeSuperAdmin, email: "person@aarete.com", roles: []string{"super_admin"}, want: true}, + {name: "user admin can create client user", mode: adminTeamModeUserAdmin, email: "client@example.com", roles: []string{"client_user"}, want: true}, + {name: "user admin can create user admin", mode: adminTeamModeUserAdmin, email: "admin@example.com", roles: []string{"client_user", "user_admin"}, want: true}, + {name: "user admin cannot create aarete domain", mode: adminTeamModeUserAdmin, email: "person@aarete.com", roles: []string{"client_user"}, want: false}, + {name: "user admin cannot create auditor", mode: adminTeamModeUserAdmin, email: "auditor@example.com", roles: []string{"auditor"}, want: false}, + {name: "auditor cannot create", mode: adminTeamModeAuditor, email: "client@example.com", roles: []string{"client_user"}, want: false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := canAdminModeCreateUser(tt.mode, tt.email, tt.roles); got != tt.want { + t.Fatalf("canAdminModeCreateUser() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestUserAdminCanAssignRoles(t *testing.T) { + tests := []struct { + name string + roles []string + want bool + }{ + {name: "client user", roles: []string{"client_user"}, want: true}, + {name: "user admin", roles: []string{"user_admin"}, want: true}, + {name: "client user and user admin", roles: []string{"client_user", "user_admin"}, want: true}, + {name: "auditor rejected", roles: []string{"auditor"}, want: false}, + {name: "super admin rejected", roles: []string{"super_admin"}, want: false}, + {name: "unknown role rejected", roles: []string{"custom_role"}, want: false}, + {name: "mixed allowed and protected rejected", roles: []string{"client_user", "auditor"}, want: false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := userAdminCanAssignRoles(tt.roles); got != tt.want { + t.Fatalf("userAdminCanAssignRoles(%v) = %v, want %v", tt.roles, got, tt.want) + } + }) + } +} + +func TestCanAdminModeAssignRequestedRoles(t *testing.T) { + auditorRoles := []string{"auditor"} + clientRoles := []string{"client_user"} + + tests := []struct { + name string + mode adminTeamMode + roles *[]string + want bool + }{ + {name: "nil roles allowed", mode: adminTeamModeUserAdmin, roles: nil, want: true}, + {name: "user admin can assign client role", mode: adminTeamModeUserAdmin, roles: &clientRoles, want: true}, + {name: "user admin cannot assign auditor role", mode: adminTeamModeUserAdmin, roles: &auditorRoles, want: false}, + {name: "super admin can assign auditor role", mode: adminTeamModeSuperAdmin, roles: &auditorRoles, want: true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := canAdminModeAssignRequestedRoles(tt.mode, tt.roles); got != tt.want { + t.Fatalf("canAdminModeAssignRequestedRoles() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestGetAdminTeamMode(t *testing.T) { + server := newAdminListPermitServer(t) + defer server.Close() + + ctrl := NewControllers(&Services{}, &mockAuthConfig{}) + permitConfig := &usermanagement.PermitConfig{ + APIKey: "test-api-key", + BaseURL: server.URL, + } + + t.Run("disabled auth acts as super admin", func(t *testing.T) { + t.Setenv("DISABLE_AUTH", "true") + ctx, _ := newAdminAccessTestContext(http.MethodGet) + + if got := ctrl.getAdminTeamMode(ctx, server.Client(), permitConfig); got != adminTeamModeSuperAdmin { + t.Fatalf("getAdminTeamMode() = %s, want %s", got, adminTeamModeSuperAdmin) + } + }) + + t.Run("permit subject roles determine mode", func(t *testing.T) { + t.Setenv("DISABLE_AUTH", "") + ctx, _ := newAdminAccessTestContext(http.MethodGet) + ctx.Set("user_claims", map[string]interface{}{"sub": "user-admin-sub"}) + + if got := ctrl.getAdminTeamMode(ctx, server.Client(), permitConfig); got != adminTeamModeUserAdmin { + t.Fatalf("getAdminTeamMode() = %s, want %s", got, adminTeamModeUserAdmin) + } + }) + + t.Run("context groups are used when subject is unavailable", func(t *testing.T) { + t.Setenv("DISABLE_AUTH", "") + ctx, _ := newAdminAccessTestContext(http.MethodGet) + ctx.Set("user_info", cognitoauth.UserInfo{Groups: []string{"auditor"}}) + + if got := ctrl.getAdminTeamMode(ctx, server.Client(), permitConfig); got != adminTeamModeAuditor { + t.Fatalf("getAdminTeamMode() = %s, want %s", got, adminTeamModeAuditor) + } + }) + + t.Run("no auth context has no admin mode", func(t *testing.T) { + t.Setenv("DISABLE_AUTH", "") + ctx, _ := newAdminAccessTestContext(http.MethodGet) + + if got := ctrl.getAdminTeamMode(ctx, server.Client(), permitConfig); got != adminTeamModeNone { + t.Fatalf("getAdminTeamMode() = %s, want %s", got, adminTeamModeNone) + } + }) +} + +func TestGetAdminTargetAccess(t *testing.T) { + server := newAdminListPermitServer(t) + defer server.Close() + t.Setenv("DISABLE_AUTH", "false") + t.Setenv("PERMIT_IO_BASE_URL", server.URL) + + ctrl := NewControllers(&Services{}, &mockAuthConfig{}) + + t.Run("loads target roles and caller mode", func(t *testing.T) { + ctx, _ := newAdminAccessTestContext(http.MethodGet) + ctx.Set("user_claims", map[string]interface{}{"sub": "user-admin-sub"}) + cognitoUser := &usermanagement.CognitoUserResponse{ + SubjectID: "client-sub", + Email: "client@example.com", + } + + access := ctrl.getAdminTargetAccess(ctx, UserEmail(cognitoUser.Email), cognitoUser) + if access.mode != adminTeamModeUserAdmin { + t.Fatalf("getAdminTargetAccess().mode = %s, want %s", access.mode, adminTeamModeUserAdmin) + } + if !access.roleLookupOK { + t.Fatalf("getAdminTargetAccess().roleLookupOK = false, want true") + } + if len(access.roles) != 1 || access.roles[0] != "client_user" { + t.Fatalf("getAdminTargetAccess().roles = %v, want [client_user]", access.roles) + } + if !access.canRead(cognitoUser.Email) { + t.Fatalf("adminTargetAccess.canRead() = false, want true") + } + if !access.canMutate(cognitoUser.Email) { + t.Fatalf("adminTargetAccess.canMutate() = false, want true") + } + }) + + t.Run("user admin fail closes when target role lookup fails", func(t *testing.T) { + ctx, _ := newAdminAccessTestContext(http.MethodPatch) + ctx.Set("user_claims", map[string]interface{}{"sub": "user-admin-sub"}) + cognitoUser := &usermanagement.CognitoUserResponse{ + SubjectID: "role-error-sub", + Email: "client@example.com", + } + + access := ctrl.getAdminTargetAccess(ctx, UserEmail(cognitoUser.Email), cognitoUser) + if access.mode != adminTeamModeUserAdmin { + t.Fatalf("getAdminTargetAccess().mode = %s, want %s", access.mode, adminTeamModeUserAdmin) + } + if access.roleLookupOK { + t.Fatalf("getAdminTargetAccess().roleLookupOK = true, want false") + } + if access.canRead(cognitoUser.Email) { + t.Fatalf("adminTargetAccess.canRead() = true, want false") + } + if access.canMutate(cognitoUser.Email) { + t.Fatalf("adminTargetAccess.canMutate() = true, want false") + } + }) +} + +func TestAdminUserErrorResponses(t *testing.T) { + ctx, rec := newAdminAccessTestContext(http.MethodPatch) + if err := adminUserForbidden(ctx); err != nil { + t.Fatalf("adminUserForbidden() error = %v", err) + } + if rec.Code != http.StatusForbidden { + t.Fatalf("adminUserForbidden() status = %d, want %d", rec.Code, http.StatusForbidden) + } + if !strings.Contains(rec.Body.String(), "Insufficient permissions") { + t.Fatalf("adminUserForbidden() body = %q, want permissions message", rec.Body.String()) + } + + ctx, rec = newAdminAccessTestContext(http.MethodGet) + if err := adminUserNotFound(ctx, "hidden@example.com"); err != nil { + t.Fatalf("adminUserNotFound() error = %v", err) + } + if rec.Code != http.StatusNotFound { + t.Fatalf("adminUserNotFound() status = %d, want %d", rec.Code, http.StatusNotFound) + } + if !strings.Contains(rec.Body.String(), "User not found: hidden@example.com") { + t.Fatalf("adminUserNotFound() body = %q, want not found message", rec.Body.String()) + } +} + +func TestAdminUserListPaginationAndPage(t *testing.T) { + pageInput := int32(2) + pageSizeInput := int32(2) + page, pageSize := adminUserListPagination(ListAdminUsersParams{ + Page: &pageInput, + PageSize: &pageSizeInput, + }) + if page != 2 || pageSize != 2 { + t.Fatalf("adminUserListPagination() = (%d, %d), want (2, 2)", page, pageSize) + } + + tooLargePageSize := int32(100) + _, defaultedPageSize := adminUserListPagination(ListAdminUsersParams{PageSize: &tooLargePageSize}) + if defaultedPageSize != 50 { + t.Fatalf("adminUserListPagination() pageSize = %d, want default 50", defaultedPageSize) + } + + users := []AdminUserDetails{ + {CognitoSubjectId: "sub-1"}, + {CognitoSubjectId: "sub-2"}, + {CognitoSubjectId: "sub-3"}, + } + pageUsers, hasMore := paginateAdminUsers(users, 1, 2) + if len(pageUsers) != 2 || !hasMore { + t.Fatalf("paginateAdminUsers() = %d users, hasMore %v; want 2 users and hasMore true", len(pageUsers), hasMore) + } + + pageUsers, hasMore = paginateAdminUsers(users, 2, 2) + if len(pageUsers) != 1 || hasMore { + t.Fatalf("paginateAdminUsers(page 2) = %d users, hasMore %v; want 1 user and hasMore false", len(pageUsers), hasMore) + } +} + +func TestCollectAdminCognitoUsersStopsAtUserCap(t *testing.T) { + nextToken := "next" + _, err := collectAdminCognitoUsers(func(pageSize int, paginationToken *string) (*usermanagement.ListCognitoUsersResult, error) { + users := make([]usermanagement.CognitoUserResponse, pageSize+1) + for i := range users { + users[i] = usermanagement.CognitoUserResponse{SubjectID: "sub"} + } + return &usermanagement.ListCognitoUsersResult{Users: users, PaginationKey: &nextToken}, nil + }) + + if err == nil || !strings.Contains(err.Error(), "exceeded 6000 users") { + t.Fatalf("collectAdminCognitoUsers() error = %v, want user cap error", err) + } +} + +func TestCollectAdminCognitoUsersStopsAtPageCap(t *testing.T) { + nextToken := "next" + _, err := collectAdminCognitoUsers(func(pageSize int, paginationToken *string) (*usermanagement.ListCognitoUsersResult, error) { + return &usermanagement.ListCognitoUsersResult{ + Users: []usermanagement.CognitoUserResponse{{SubjectID: "sub"}}, + PaginationKey: &nextToken, + }, nil + }) + + if err == nil || !strings.Contains(err.Error(), "exceeded 100 pages") { + t.Fatalf("collectAdminCognitoUsers() error = %v, want page cap error", err) + } +} + +func TestVisibleAdminUsersAppliesDEVOPS722Visibility(t *testing.T) { + users := []usermanagement.CognitoUserResponse{ + {SubjectID: "client-sub", Email: "client@example.com", FirstName: "Client", LastName: "User", Status: "CONFIRMED", Enabled: true}, + {SubjectID: "user-admin-sub", Email: "admin@example.com", FirstName: "User", LastName: "Admin", Status: "CONFIRMED", Enabled: true}, + {SubjectID: "aarete-sub", Email: "person@aarete.com", FirstName: "AArete", LastName: "User", Status: "CONFIRMED", Enabled: true}, + {SubjectID: "auditor-sub", Email: "auditor@example.com", FirstName: "Audit", LastName: "User", Status: "CONFIRMED", Enabled: true}, + {SubjectID: "super-sub", Email: "super@example.com", FirstName: "Super", LastName: "User", Status: "CONFIRMED", Enabled: true}, + {SubjectID: "missing-role-sub", Email: "missing-role@example.com", FirstName: "Missing", LastName: "Role", Status: "CONFIRMED", Enabled: true}, + } + rolesBySubject := map[string][]string{ + "client-sub": {"client_user"}, + "user-admin-sub": {"user_admin"}, + "aarete-sub": {"client_user"}, + "auditor-sub": {"auditor"}, + "super-sub": {"super_admin"}, + } + + ctrl := NewControllers(&Services{}, &mockAuthConfig{}) + userAdminVisible := ctrl.visibleAdminUsers(users, adminTeamModeUserAdmin, rolesBySubject) + assertVisibleSubjects(t, userAdminVisible, []string{"client-sub", "user-admin-sub"}) + + auditorVisible := ctrl.visibleAdminUsers(users, adminTeamModeAuditor, rolesBySubject) + assertVisibleSubjects(t, auditorVisible, []string{"client-sub", "user-admin-sub", "aarete-sub", "auditor-sub", "super-sub", "missing-role-sub"}) + + superAdminVisible := ctrl.visibleAdminUsers(users, adminTeamModeSuperAdmin, rolesBySubject) + assertVisibleSubjects(t, superAdminVisible, []string{"client-sub", "user-admin-sub", "aarete-sub", "auditor-sub", "super-sub", "missing-role-sub"}) +} + +func TestAdminRoleAssignmentsByUserGroupsRoles(t *testing.T) { + got := adminRoleAssignmentsByUser([]usermanagement.RoleAssignment{ + {User: "sub-1", Role: "client_user", Tenant: "default"}, + {User: "sub-1", Role: "user_admin", Tenant: "default"}, + {User: "sub-2", Role: "auditor", Tenant: "default"}, + {User: "", Role: "super_admin", Tenant: "default"}, + {User: "sub-3", Role: "", Tenant: "default"}, + }) + + assertVisibleRoleMap(t, got, map[string][]string{ + "sub-1": {"client_user", "user_admin"}, + "sub-2": {"auditor"}, + }) +} + +func TestAdminUserDetailsRolesMarshalAsEmptyArray(t *testing.T) { + detail := adminUserDetailsFromCognito(usermanagement.CognitoUserResponse{ + SubjectID: "sub-without-roles", + Email: "client@example.com", + }, nil) + + body, err := json.Marshal(detail) + if err != nil { + t.Fatalf("marshal admin user details: %v", err) + } + if strings.Contains(string(body), `"roles":null`) { + t.Fatalf("admin user details marshaled roles as null: %s", body) + } + if !strings.Contains(string(body), `"roles":[]`) { + t.Fatalf("admin user details missing empty roles array: %s", body) + } +} + +func TestUpdateUserRolesInPermitEmptyArrayClearsRoles(t *testing.T) { + var deletedRoles []string + + mux := http.NewServeMux() + mux.HandleFunc("/v2/api-key/scope", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + if err := json.NewEncoder(w).Encode(map[string]string{ + "organization_id": "test-org", + "project_id": "test-project", + "environment_id": "test-env", + }); err != nil { + t.Fatalf("encode scope response: %v", err) + } + }) + mux.HandleFunc("/v2/facts/test-project/test-env/role_assignments", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + switch r.Method { + case http.MethodGet: + if err := json.NewEncoder(w).Encode([]map[string]string{ + {"role": "client_user", "user": "target-sub", "tenant": "default"}, + {"role": "user_admin", "user": "target-sub", "tenant": "default"}, + }); err != nil { + t.Fatalf("encode role assignments: %v", err) + } + case http.MethodDelete: + var assignment usermanagement.RoleAssignment + if err := json.NewDecoder(r.Body).Decode(&assignment); err != nil { + t.Fatalf("decode delete role assignment: %v", err) + } + deletedRoles = append(deletedRoles, assignment.Role) + w.WriteHeader(http.StatusNoContent) + default: + t.Fatalf("unexpected method %s", r.Method) + } + }) + roleServer := httptest.NewServer(mux) + defer roleServer.Close() + + ctrl := NewControllers(&Services{}, &mockAuthConfig{}) + ctx, _ := newAdminAccessTestContext(http.MethodPatch) + emptyRoles := []string{} + ctrl.updateUserRolesInPermit( + ctx, + UserEmail("target@example.com"), + AdminUserUpdate{Roles: &emptyRoles}, + cognitoauth.UserInfo{Email: "admin@example.com"}, + "target-sub", + roleServer.Client(), + &usermanagement.PermitConfig{APIKey: "test-api-key", BaseURL: roleServer.URL}, + ) + + assertVisibleRoleMap(t, map[string][]string{"deleted": deletedRoles}, map[string][]string{ + "deleted": {"client_user", "user_admin"}, + }) +} + +func TestUpdateUserRolesInPermitAddsAndRemovesRoles(t *testing.T) { + var assignedRoles []string + var deletedRoles []string + + mux := http.NewServeMux() + mux.HandleFunc("/v2/api-key/scope", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + if err := json.NewEncoder(w).Encode(map[string]string{ + "organization_id": "test-org", + "project_id": "test-project", + "environment_id": "test-env", + }); err != nil { + t.Fatalf("encode scope response: %v", err) + } + }) + mux.HandleFunc("/v2/facts/test-project/test-env/role_assignments", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + switch r.Method { + case http.MethodGet: + if err := json.NewEncoder(w).Encode([]map[string]string{ + {"role": "client_user", "user": "target-sub", "tenant": "default"}, + {"role": "user_admin", "user": "target-sub", "tenant": "default"}, + }); err != nil { + t.Fatalf("encode role assignments: %v", err) + } + case http.MethodPost: + var assignment usermanagement.RoleAssignment + if err := json.NewDecoder(r.Body).Decode(&assignment); err != nil { + t.Fatalf("decode assign role assignment: %v", err) + } + assignedRoles = append(assignedRoles, assignment.Role) + w.WriteHeader(http.StatusCreated) + case http.MethodDelete: + var assignment usermanagement.RoleAssignment + if err := json.NewDecoder(r.Body).Decode(&assignment); err != nil { + t.Fatalf("decode delete role assignment: %v", err) + } + deletedRoles = append(deletedRoles, assignment.Role) + w.WriteHeader(http.StatusNoContent) + default: + t.Fatalf("unexpected method %s", r.Method) + } + }) + roleServer := httptest.NewServer(mux) + defer roleServer.Close() + + ctrl := NewControllers(&Services{}, &mockAuthConfig{}) + ctx, _ := newAdminAccessTestContext(http.MethodPatch) + requestedRoles := []string{"client_user", "auditor"} + ctrl.updateUserRolesInPermit( + ctx, + UserEmail("target@example.com"), + AdminUserUpdate{Roles: &requestedRoles}, + cognitoauth.UserInfo{Email: "admin@example.com"}, + "target-sub", + roleServer.Client(), + &usermanagement.PermitConfig{APIKey: "test-api-key", BaseURL: roleServer.URL}, + ) + + assertVisibleRoleMap(t, map[string][]string{ + "assigned": assignedRoles, + "deleted": deletedRoles, + }, map[string][]string{ + "assigned": {"auditor"}, + "deleted": {"user_admin"}, + }) +} + +func newAdminListPermitServer(t *testing.T) *httptest.Server { + t.Helper() + + mux := http.NewServeMux() + mux.HandleFunc("/v2/api-key/scope", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + if err := json.NewEncoder(w).Encode(map[string]string{ + "organization_id": "test-org", + "project_id": "test-project", + "environment_id": "test-env", + }); err != nil { + t.Fatalf("encode scope response: %v", err) + } + }) + mux.HandleFunc("/v2/facts/test-project/test-env/role_assignments", func(w http.ResponseWriter, r *http.Request) { + user := r.URL.Query().Get("user") + if user == "role-error-sub" { + http.Error(w, "permit unavailable", http.StatusInternalServerError) + return + } + + roleByUser := map[string]string{ + "client-sub": "client_user", + "user-admin-sub": "user_admin", + "aarete-sub": "client_user", + "auditor-sub": "auditor", + "super-sub": "super_admin", + } + role := roleByUser[user] + assignments := []map[string]string{} + if role != "" { + assignments = append(assignments, map[string]string{"role": role, "user": user, "tenant": "default"}) + } + w.Header().Set("Content-Type", "application/json") + if err := json.NewEncoder(w).Encode(assignments); err != nil { + t.Fatalf("encode role assignments: %v", err) + } + }) + + return httptest.NewServer(mux) +} + +func newAdminAccessTestContext(method string) (echo.Context, *httptest.ResponseRecorder) { + e := echo.New() + req := httptest.NewRequest(method, "/admin/users", nil) + rec := httptest.NewRecorder() + return e.NewContext(req, rec), rec +} + +func assertVisibleSubjects(t *testing.T, users []AdminUserDetails, want []string) { + t.Helper() + + got := make([]string, 0, len(users)) + for _, user := range users { + got = append(got, user.CognitoSubjectId) + } + if len(got) != len(want) { + t.Fatalf("visible subjects = %v, want %v", got, want) + } + for i := range want { + if got[i] != want[i] { + t.Fatalf("visible subjects = %v, want %v", got, want) + } + } +} + +func assertVisibleRoleMap(t *testing.T, got map[string][]string, want map[string][]string) { + t.Helper() + + if len(got) != len(want) { + t.Fatalf("role map = %v, want %v", got, want) + } + for subject, wantRoles := range want { + gotRoles, ok := got[subject] + if !ok { + t.Fatalf("role map missing subject %q: %v", subject, got) + } + if len(gotRoles) != len(wantRoles) { + t.Fatalf("role map[%q] = %v, want %v", subject, gotRoles, wantRoles) + } + for i := range wantRoles { + if gotRoles[i] != wantRoles[i] { + t.Fatalf("role map[%q] = %v, want %v", subject, gotRoles, wantRoles) + } + } + } +} diff --git a/api/queryAPI/api.gen.go b/api/queryAPI/api.gen.go index 1b4584b4..cc007d38 100644 --- a/api/queryAPI/api.gen.go +++ b/api/queryAPI/api.gen.go @@ -246,7 +246,7 @@ type AdminUserDetails struct { // LastName User's family name retrieved from Cognito LastName *string `json:"last_name,omitempty"` - // Roles Roles assigned in Permit.io. Only populated by GET /admin/users/{email}. Not populated by GET /admin/users (list endpoint) for performance reasons. + // Roles Roles assigned in Permit.io. Populated by GET /admin/users and GET /admin/users/{email} so Team Management clients can enforce role-based visibility and action rules. Roles *[]string `json:"roles,omitempty"` // Status Current Cognito user account status @@ -4147,448 +4147,454 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "4r39fvdPAAAA//94J1oWMqcCAA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/docs/ai.generated/03-api-documentation.md b/docs/ai.generated/03-api-documentation.md index f85e2546..647e525f 100644 --- a/docs/ai.generated/03-api-documentation.md +++ b/docs/ai.generated/03-api-documentation.md @@ -1280,6 +1280,8 @@ Creates a new user in both AWS Cognito and Permit.io systems with optional role **Security**: Requires JWT authentication +**Team Management Permissions**: Super Admin can create any user. User Admin and Client Admin can create only non-AArete users with `client_user` and/or `user_admin` roles. Auditor is read-only and cannot create users. + **Environment Scoping**: When `COGNITO_ENVIRONMENT_ID` is configured on the server, newly created users are automatically tagged with the `custom:environment_id` attribute matching the server's value. This ensures users are scoped to the correct environment. **Creation Flow**: @@ -1341,11 +1343,13 @@ Lists users from AWS Cognito with pagination, filtering, and sorting support. **Environment Filtering**: When `COGNITO_ENVIRONMENT_ID` is configured, the response is filtered to include only users whose `custom:environment_id` matches the server's value or users with no environment tag (legacy/untagged users). Users belonging to other environments are excluded from the results. +**Team Management Visibility**: User Admin and Client Admin receive only non-AArete users that do not have `auditor` or `super_admin` roles. Super Admin and Auditor receive all environment-visible users. The `roles` field is populated from Permit.io for list responses. + **Query Parameters**: | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | page | integer | 1 | Page number (1-10000) | -| page_size | integer | 50 | Results per page (1-100) | +| page_size | integer | 50 | Results per page (1-60) | | search | string | - | Search term for email/name filtering | | status | string | all | Filter by status: `enabled`, `disabled`, `all` | | sort_by | string | email | Sort field: `email`, `created_at`, `last_name` | @@ -1383,6 +1387,8 @@ Retrieves user details from both AWS Cognito and Permit.io by email address. **Environment Ownership**: When `COGNITO_ENVIRONMENT_ID` is configured, returns `404 Not Found` for users belonging to a different environment to prevent information leakage. Untagged users and users matching the server's environment are accessible. +**Team Management Visibility**: Hidden users return `404 Not Found` so User Admin and Client Admin callers cannot enumerate AArete, Auditor, or Super Admin users. + **Path Parameters**: - `email`: URL-encoded user email address (max 320 chars) @@ -1397,6 +1403,8 @@ Checks if a user exists in Cognito and/or Permit.io without returning full detai **Environment Ownership**: When `COGNITO_ENVIRONMENT_ID` is configured, returns `404` for users belonging to a different environment. +**Team Management Visibility**: Hidden users return `404 Not Found` so User Admin and Client Admin callers cannot enumerate AArete, Auditor, or Super Admin users. + **Response Headers**: - `X-User-Exists-Cognito`: boolean @@ -1415,6 +1423,8 @@ Updates user attributes (first_name, last_name) in Cognito and manages role assi **Environment Ownership**: When `COGNITO_ENVIRONMENT_ID` is configured, returns `404 Not Found` for users belonging to a different environment. +**Team Management Permissions**: Super Admin can update any environment-visible user. User Admin and Client Admin can update only non-AArete client-level users and can assign only `client_user` and `user_admin` roles. Auditor is read-only. + **Role Management (REPLACE Strategy)**: - Providing a `roles` array REPLACES all existing roles (not additive) @@ -1443,6 +1453,8 @@ Permanently deletes a user from both AWS Cognito and Permit.io. This operation i **Environment Ownership**: When `COGNITO_ENVIRONMENT_ID` is configured, returns `404 Not Found` for users belonging to a different environment. +**Team Management Permissions**: Super Admin can delete any environment-visible user. User Admin and Client Admin can delete only non-AArete client-level users. Auditor is read-only. + **Query Parameters**: - `confirm` (required): Must be `true` to confirm deletion @@ -1470,6 +1482,8 @@ Disables a user in AWS Cognito, preventing authentication. User data and roles a **Environment Ownership**: When `COGNITO_ENVIRONMENT_ID` is configured, returns `404 Not Found` for users belonging to a different environment. +**Team Management Permissions**: Super Admin can disable any environment-visible user. User Admin and Client Admin can disable only non-AArete client-level users. Auditor is read-only. + **Response** (`AdminUserActionResponse`): ```json @@ -1490,6 +1504,8 @@ Re-enables a previously disabled user in AWS Cognito. Restores authentication ca **Environment Ownership**: When `COGNITO_ENVIRONMENT_ID` is configured, returns `404 Not Found` for users belonging to a different environment. +**Team Management Permissions**: Super Admin can enable any environment-visible user. User Admin and Client Admin can enable only non-AArete client-level users. Auditor is read-only. + **Response**: Same as disable endpoint with `"action": "enable"` --- diff --git a/docs/ai.generated/queryapi.summary.md b/docs/ai.generated/queryapi.summary.md index bc8a69d5..b3a5f2b2 100644 --- a/docs/ai.generated/queryapi.summary.md +++ b/docs/ai.generated/queryapi.summary.md @@ -268,6 +268,7 @@ This document provides a comprehensive summary of all REST API endpoints availab - **POST /admin/users** - Create a new user - Request Body: `{email, first_name, last_name, roles[]}` - Creates user in both AWS Cognito and Permit.io + - Team Management: Super Admin can create any user; User Admin and Client Admin can create only non-AArete users with `client_user`/`user_admin` roles; Auditor is read-only - Roles: Must exist in Permit.io (e.g., `super_admin`, `user_admin`, `auditor`, `client_user`) - Returns: `{cognito_subject_id, email, first_name, last_name, status, enabled, permit_synced, roles_assigned[], created_at}` (201 Created or 200 OK if already exists) - **Idempotent**: Returns 200 OK if user already exists with matching attributes @@ -275,18 +276,22 @@ This document provides a comprehensive summary of all REST API endpoints availab - **GET /admin/users** - List users with pagination, filtering, and sorting - Query Parameters: - `page` (optional, default: 1, max: 10000) - Page number - - `page_size` (optional, default: 50, max: 100) - Results per page + - `page_size` (optional, default: 50, max: 60) - Results per page - `search` (optional) - Search term for email/name filtering - `status` (optional, default: `all`) - Filter by status: `enabled`, `disabled`, `all` - `sort_by` (optional, default: `email`) - Sort field: `email`, `created_at`, `last_name` - `sort_order` (optional, default: `asc`) - Sort direction: `asc`, `desc` + - Team Management: User Admin and Client Admin see only non-AArete users without `auditor`/`super_admin` roles; Super Admin and Auditor see all environment-visible users + - `roles` is populated from Permit.io for each returned user - Returns: `{users[], total, page, page_size, has_more}` #### Individual User Operations - **GET /admin/users/{email}** - Get user by email + - Hidden users return 404 for User Admin and Client Admin callers - Returns: `{cognito_subject_id, email, first_name, last_name, status, enabled, roles[], created_at, updated_at}` - **HEAD /admin/users/{email}** - Check if user exists + - Hidden users return 404 for User Admin and Client Admin callers - Returns: 200 OK with headers: - `X-User-Exists-Cognito: boolean` - Whether user exists in Cognito - `X-User-Exists-PermitIO: boolean` - Whether user exists in Permit.io @@ -294,6 +299,7 @@ This document provides a comprehensive summary of all REST API endpoints availab - **PATCH /admin/users/{email}** - Update user attributes and roles - Request Body: `{first_name?, last_name?, roles[]?}` + - Team Management: Super Admin can update any environment-visible user; User Admin and Client Admin can update only non-AArete client-level users and assign only `client_user`/`user_admin`; Auditor is read-only - **Role Management**: Providing `roles[]` REPLACES all existing roles (not additive) - To add a role: Include all current roles PLUS the new role - To remove a role: Include only roles you want to keep @@ -304,6 +310,7 @@ This document provides a comprehensive summary of all REST API endpoints availab - **DELETE /admin/users/{email}** - Delete user permanently - Query Parameters: - `confirm=true` (required) - Must be set to confirm deletion + - Team Management: Super Admin can delete any environment-visible user; User Admin and Client Admin can delete only non-AArete client-level users; Auditor is read-only - Deletes from both AWS Cognito and Permit.io - **Irreversible operation** - Returns: `{success, email, cognito_subject_id, deleted_from{cognito, permit}, timestamp}` (200 OK or 207 Multi-Status for partial deletion) @@ -311,12 +318,14 @@ This document provides a comprehensive summary of all REST API endpoints availab #### User Account Status Management - **POST /admin/users/{email}/disable** - Disable user - Disables user in AWS Cognito, preventing authentication + - Team Management: Super Admin can disable any environment-visible user; User Admin and Client Admin can disable only non-AArete client-level users; Auditor is read-only - User data and roles are preserved - **Idempotent**: Safe to call multiple times - Returns: `{success, email, action, cognito_subject_id, timestamp}` (200 OK) - **POST /admin/users/{email}/enable** - Enable user - Re-enables a previously disabled user in AWS Cognito + - Team Management: Super Admin can enable any environment-visible user; User Admin and Client Admin can enable only non-AArete client-level users; Auditor is read-only - Restores authentication capability - **Idempotent**: Safe to call multiple times - Returns: `{success, email, action, cognito_subject_id, timestamp}` (200 OK) diff --git a/internal/usermanagement/permitio.go b/internal/usermanagement/permitio.go index 3ad9570a..ff85ebab 100644 --- a/internal/usermanagement/permitio.go +++ b/internal/usermanagement/permitio.go @@ -13,6 +13,11 @@ import ( "strings" ) +const ( + permitRoleAssignmentsPerPage = 100 + maxPermitRoleAssignmentPages = 100 +) + // CreatePermitUser creates a new user in Permit.io using data from a Cognito user. // The Cognito Subject ID is used as the Permit.io user key for consistency across systems. // @@ -504,42 +509,98 @@ func GetPermitUserRoles(client *http.Client, config *PermitConfig, userKey strin return nil, fmt.Errorf("error reading response: %w", err) } - roles := []string{} // Initialize as empty slice, not nil - var assignments []interface{} - - // First try to unmarshal as direct array (common case) - var directAssignments []interface{} - if err := json.Unmarshal(body, &directAssignments); err == nil { - assignments = directAssignments - } else { - // If that fails, try as object with data/items/etc - var response map[string]interface{} - if err := json.Unmarshal(body, &response); err != nil { - return nil, fmt.Errorf("error parsing response as object or array: %w", err) - } - - // Look for role assignments in common response structures - if data, ok := response["data"].([]interface{}); ok { - assignments = data - } else if items, ok := response["items"].([]interface{}); ok { - assignments = items - } else if results, ok := response["results"].([]interface{}); ok { - assignments = results - } + assignments, err := decodePermitRoleAssignments(body) + if err != nil { + return nil, err } - // Extract role names from assignments + roles := make([]string, 0, len(assignments)) for _, assignment := range assignments { - if roleAssignment, ok := assignment.(map[string]interface{}); ok { - if role, ok := roleAssignment["role"].(string); ok { - roles = append(roles, role) - } + if assignment.Role != "" { + roles = append(roles, assignment.Role) } } - return roles, nil } +// ListAllPermitRoleAssignments retrieves all role assignments in the configured Permit.io environment. +func ListAllPermitRoleAssignments(client *http.Client, config *PermitConfig) ([]RoleAssignment, error) { + projectID, err := config.GetProjectID(client) + if err != nil { + return nil, fmt.Errorf("failed to get project ID: %w", err) + } + envID, err := config.GetEnvID(client) + if err != nil { + return nil, fmt.Errorf("failed to get environment ID: %w", err) + } + + assignments := []RoleAssignment{} + for page := 1; page <= maxPermitRoleAssignmentPages; page++ { + endpoint := fmt.Sprintf("%s/v2/facts/%s/%s/role_assignments?page=%d&per_page=%d", + config.BaseURL, projectID, envID, page, permitRoleAssignmentsPerPage) + + req, err := http.NewRequest("GET", endpoint, nil) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + req.Header.Set("Authorization", "Bearer "+config.APIKey) + req.Header.Set("Content-Type", "application/json") + + resp, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("error making request: %w", err) + } + + body, readErr := io.ReadAll(resp.Body) + closeErr := resp.Body.Close() + if readErr != nil { + return nil, fmt.Errorf("error reading response: %w", readErr) + } + if closeErr != nil { + return nil, fmt.Errorf("error closing response body: %w", closeErr) + } + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("API error (status %d): %s", resp.StatusCode, string(body)) + } + + pageAssignments, err := decodePermitRoleAssignments(body) + if err != nil { + return nil, err + } + assignments = append(assignments, pageAssignments...) + if len(pageAssignments) < permitRoleAssignmentsPerPage { + return assignments, nil + } + } + return nil, fmt.Errorf("Permit.io role assignment listing exceeded %d pages", maxPermitRoleAssignmentPages) +} + +func decodePermitRoleAssignments(body []byte) ([]RoleAssignment, error) { + var directAssignments []RoleAssignment + if err := json.Unmarshal(body, &directAssignments); err == nil { + return directAssignments, nil + } + + var response struct { + Data []RoleAssignment `json:"data"` + Items []RoleAssignment `json:"items"` + Results []RoleAssignment `json:"results"` + } + if err := json.Unmarshal(body, &response); err != nil { + return nil, fmt.Errorf("error parsing response as object or array: %w", err) + } + switch { + case response.Data != nil: + return response.Data, nil + case response.Items != nil: + return response.Items, nil + case response.Results != nil: + return response.Results, nil + default: + return nil, fmt.Errorf("unexpected Permit.io role assignments response format") + } +} + // GetExistingRoles retrieves all role definitions from the specified Permit.io project/environment. // // Parameters: diff --git a/internal/usermanagement/permitio_test.go b/internal/usermanagement/permitio_test.go index 7d245dad..58419ace 100644 --- a/internal/usermanagement/permitio_test.go +++ b/internal/usermanagement/permitio_test.go @@ -89,6 +89,82 @@ func TestGetPermitUserRolesEscapesUserKey(t *testing.T) { assert.Equal(t, []string{"super_admin"}, roles) } +func TestListAllPermitRoleAssignmentsDirectArray(t *testing.T) { + requests := 0 + server, config := createMockPermitServer(t, map[string]http.HandlerFunc{ + "/v2/facts/test-project/test-env/role_assignments": func(w http.ResponseWriter, r *http.Request) { + requests++ + assert.Empty(t, r.URL.Query().Get("user")) + assert.Equal(t, "1", r.URL.Query().Get("page")) + assert.Equal(t, "100", r.URL.Query().Get("per_page")) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _, err := w.Write([]byte(`[{"role":"client_user","user":"sub-1","tenant":"default"},{"role":"user_admin","user":"sub-1","tenant":"default"}]`)) + require.NoError(t, err) + }, + }) + defer server.Close() + + assignments, err := ListAllPermitRoleAssignments(server.Client(), config) + + require.NoError(t, err) + require.Len(t, assignments, 2) + assert.Equal(t, "client_user", assignments[0].Role) + assert.Equal(t, "sub-1", assignments[0].User) + assert.Equal(t, 1, requests) +} + +func TestListAllPermitRoleAssignmentsObjectWrappedPagination(t *testing.T) { + requests := 0 + server, config := createMockPermitServer(t, map[string]http.HandlerFunc{ + "/v2/facts/test-project/test-env/role_assignments": func(w http.ResponseWriter, r *http.Request) { + requests++ + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + if r.URL.Query().Get("page") == "1" { + page := make([]map[string]string, 0, 100) + for i := 0; i < 100; i++ { + page = append(page, map[string]string{"role": "client_user", "user": "sub-page-1", "tenant": "default"}) + } + err := json.NewEncoder(w).Encode(map[string]interface{}{"data": page}) + require.NoError(t, err) + return + } + err := json.NewEncoder(w).Encode(map[string]interface{}{ + "data": []map[string]string{{"role": "auditor", "user": "sub-page-2", "tenant": "default"}}, + }) + require.NoError(t, err) + }, + }) + defer server.Close() + + assignments, err := ListAllPermitRoleAssignments(server.Client(), config) + + require.NoError(t, err) + assert.Len(t, assignments, 101) + assert.Equal(t, "auditor", assignments[100].Role) + assert.Equal(t, "sub-page-2", assignments[100].User) + assert.Equal(t, 2, requests) +} + +func TestListAllPermitRoleAssignmentsRejectsUnknownSuccessfulShape(t *testing.T) { + server, config := createMockPermitServer(t, map[string]http.HandlerFunc{ + "/v2/facts/test-project/test-env/role_assignments": func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _, err := w.Write([]byte(`{"unexpected":[]}`)) + require.NoError(t, err) + }, + }) + defer server.Close() + + assignments, err := ListAllPermitRoleAssignments(server.Client(), config) + + require.Error(t, err) + assert.Nil(t, assignments) + assert.Contains(t, err.Error(), "unexpected Permit.io role assignments response format") +} + // TestGetAPIKeyScope_OrgLevelKey tests API key with only organization scope func TestGetAPIKeyScope_OrgLevelKey(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { diff --git a/pkg/queryAPI/api.gen.go b/pkg/queryAPI/api.gen.go index 895e85e3..e3ffedf0 100644 --- a/pkg/queryAPI/api.gen.go +++ b/pkg/queryAPI/api.gen.go @@ -244,7 +244,7 @@ type AdminUserDetails struct { // LastName User's family name retrieved from Cognito LastName *string `json:"last_name,omitempty"` - // Roles Roles assigned in Permit.io. Only populated by GET /admin/users/{email}. Not populated by GET /admin/users (list endpoint) for performance reasons. + // Roles Roles assigned in Permit.io. Populated by GET /admin/users and GET /admin/users/{email} so Team Management clients can enforce role-based visibility and action rules. Roles *[]string `json:"roles,omitempty"` // Status Current Cognito user account status diff --git a/serviceAPIs/queryAPI.yaml b/serviceAPIs/queryAPI.yaml index 939d396a..16c5ee08 100644 --- a/serviceAPIs/queryAPI.yaml +++ b/serviceAPIs/queryAPI.yaml @@ -1659,6 +1659,9 @@ paths: description: >- Creates a new user in both AWS Cognito and Permit.io systems with optional role assignments. Idempotent - returns 200 OK if user already exists with identical attributes. + Team Management permissions are enforced before user creation: Super Admin can + create any user, User Admin and Client Admin can create only non-AArete users + with client_user/user_admin roles, and Auditor cannot create users. **Creation Flow:** @@ -1745,9 +1748,12 @@ paths: summary: List users description: >- Lists users from AWS Cognito with pagination, filtering, and sorting support. - Note: The roles field is not populated in this response. To retrieve roles for - a specific user, call GET /admin/users/{email} which returns the full user - details including Permit.io roles. + Results are filtered according to the viewer's Team Management role. User + Admin and Client Admin viewers only receive non-AArete users that do not have + auditor or super_admin roles, while Super Admin and Auditor viewers receive all + environment-visible users. + The roles field is populated from Permit.io so clients can enforce the same + visibility and action rules locally. security: - jwtAuth: [] parameters: @@ -1767,7 +1773,7 @@ paths: type: integer format: int32 minimum: 1 - maximum: 100 + maximum: 60 default: 50 - name: search in: query @@ -1828,6 +1834,8 @@ paths: summary: Get user by email description: >- Retrieves user details from both AWS Cognito and Permit.io by email address. + Team Management visibility is enforced. Hidden users return 404 so User Admin + and Client Admin callers cannot enumerate AArete, Auditor, or Super Admin users. security: - jwtAuth: [] responses: @@ -1859,6 +1867,8 @@ paths: summary: Check if user exists description: >- Checks if a user exists in Cognito and/or Permit.io without returning full details. + Team Management visibility is enforced. Hidden users return 404 so User Admin + and Client Admin callers cannot enumerate AArete, Auditor, or Super Admin users. security: - jwtAuth: [] responses: @@ -1898,6 +1908,10 @@ paths: description: >- Updates user attributes (first_name, last_name) in Cognito and manages role assignments in Permit.io. Email address cannot be changed (use email path parameter to identify user). + Team Management mutation rules are enforced: Super Admin can update any + environment-visible user, User Admin and Client Admin can update only non-AArete + client-level users and can assign only client_user/user_admin roles, and Auditor + is read-only. **Attribute Updates:** @@ -1986,6 +2000,9 @@ paths: description: >- Permanently deletes a user from both AWS Cognito and Permit.io. This operation is irreversible. Requires confirm=true query parameter. + Team Management mutation rules are enforced: Super Admin can delete any + environment-visible user, User Admin and Client Admin can delete only non-AArete + client-level users, and Auditor is read-only. security: - jwtAuth: [] parameters: @@ -2038,6 +2055,9 @@ paths: description: >- Disables a user in AWS Cognito, preventing authentication. User data and roles are preserved. Idempotent. + Team Management mutation rules are enforced: Super Admin can disable any + environment-visible user, User Admin and Client Admin can disable only non-AArete + client-level users, and Auditor is read-only. security: - jwtAuth: [] responses: @@ -2074,6 +2094,9 @@ paths: description: >- Re-enables a previously disabled user in AWS Cognito. Restores authentication capability. Idempotent. + Team Management mutation rules are enforced: Super Admin can enable any + environment-visible user, User Admin and Client Admin can enable only non-AArete + client-level users, and Auditor is read-only. security: - jwtAuth: [] responses: @@ -5326,8 +5349,9 @@ components: maxLength: 100 pattern: "^.+$" description: >- - Roles assigned in Permit.io. Only populated by GET /admin/users/{email}. - Not populated by GET /admin/users (list endpoint) for performance reasons. + Roles assigned in Permit.io. Populated by GET /admin/users and + GET /admin/users/{email} so Team Management clients can enforce + role-based visibility and action rules. example: ["admin", "viewer"] created_at: type: string @@ -5362,7 +5386,6 @@ components: example: Doe-Smith roles: type: array - minItems: 1 maxItems: 50 items: type: string