Merged in jmathison/DEVOPS-722 (pull request #226)
DEVOPS-722 enforce team management visibility * DEVOPS-722 enforce team management visibility * DEVOPS-722 reduce admin user list complexity * DEVOPS-722 cover admin access helpers * DEVOPS-722 address team management PR feedback * DEVOPS-722 fix admin access tests in CI * DEVOPS-722 fix remaining admin PR feedback
This commit is contained in:
+167
-46
@@ -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,42 +464,33 @@ 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())
|
||||
httpClient := &http.Client{Timeout: 10 * time.Second}
|
||||
permitConfig := ctrl.getPermitConfig()
|
||||
adminMode := ctrl.getAdminTeamMode(ctx, httpClient, permitConfig)
|
||||
if adminMode == adminTeamModeNone {
|
||||
return adminUserForbidden(ctx)
|
||||
}
|
||||
|
||||
// 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)
|
||||
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(
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+449
-443
@@ -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
|
||||
|
||||
@@ -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"`
|
||||
|
||||
---
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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{}
|
||||
assignments, err := decodePermitRoleAssignments(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// First try to unmarshal as direct array (common case)
|
||||
var directAssignments []interface{}
|
||||
roles := make([]string, 0, len(assignments))
|
||||
for _, assignment := range assignments {
|
||||
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 {
|
||||
assignments = directAssignments
|
||||
} else {
|
||||
// If that fails, try as object with data/items/etc
|
||||
var response map[string]interface{}
|
||||
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)
|
||||
}
|
||||
|
||||
// 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
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
// Extract role names from assignments
|
||||
for _, assignment := range assignments {
|
||||
if roleAssignment, ok := assignment.(map[string]interface{}); ok {
|
||||
if role, ok := roleAssignment["role"].(string); ok {
|
||||
roles = append(roles, role)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return roles, nil
|
||||
}
|
||||
|
||||
// GetExistingRoles retrieves all role definitions from the specified Permit.io project/environment.
|
||||
//
|
||||
// Parameters:
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user