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:
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user