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