package auth import ( "context" "fmt" "sort" "strings" "time" "go.uber.org/zap" "github.com/kms/api-key-service/internal/cache" "github.com/kms/api-key-service/internal/config" "github.com/kms/api-key-service/internal/errors" ) // PermissionManager handles hierarchical permission management type PermissionManager struct { config config.ConfigProvider logger *zap.Logger cacheManager *cache.CacheManager hierarchy *PermissionHierarchy } // NewPermissionManager creates a new permission manager func NewPermissionManager(config config.ConfigProvider, logger *zap.Logger) *PermissionManager { cacheManager := cache.NewCacheManager(config, logger) hierarchy := NewPermissionHierarchy() return &PermissionManager{ config: config, logger: logger, cacheManager: cacheManager, hierarchy: hierarchy, } } // PermissionHierarchy represents the hierarchical permission structure type PermissionHierarchy struct { permissions map[string]*Permission roles map[string]*Role } // Permission represents a single permission with its hierarchy type Permission struct { Name string `json:"name"` Description string `json:"description"` Parent string `json:"parent,omitempty"` Children []string `json:"children"` Level int `json:"level"` Resource string `json:"resource"` Action string `json:"action"` } // Role represents a role with associated permissions type Role struct { Name string `json:"name"` Description string `json:"description"` Permissions []string `json:"permissions"` Inherits []string `json:"inherits"` Metadata map[string]string `json:"metadata"` } // PermissionEvaluation represents the result of permission evaluation type PermissionEvaluation struct { Granted bool `json:"granted"` Permission string `json:"permission"` GrantedBy []string `json:"granted_by"` DeniedReason string `json:"denied_reason,omitempty"` Metadata map[string]string `json:"metadata"` EvaluatedAt time.Time `json:"evaluated_at"` } // BulkPermissionRequest represents a bulk permission operation request type BulkPermissionRequest struct { UserID string `json:"user_id"` AppID string `json:"app_id"` Permissions []string `json:"permissions"` Context map[string]string `json:"context,omitempty"` } // BulkPermissionResponse represents a bulk permission operation response type BulkPermissionResponse struct { UserID string `json:"user_id"` AppID string `json:"app_id"` Results map[string]*PermissionEvaluation `json:"results"` EvaluatedAt time.Time `json:"evaluated_at"` } // NewPermissionHierarchy creates a new permission hierarchy func NewPermissionHierarchy() *PermissionHierarchy { h := &PermissionHierarchy{ permissions: make(map[string]*Permission), roles: make(map[string]*Role), } // Initialize with default permissions h.initializeDefaultPermissions() h.initializeDefaultRoles() return h } // initializeDefaultPermissions sets up the default permission hierarchy func (h *PermissionHierarchy) initializeDefaultPermissions() { defaultPermissions := []*Permission{ // Root permissions {Name: "admin", Description: "Full administrative access", Level: 0, Resource: "*", Action: "*"}, {Name: "read", Description: "Read access", Level: 0, Resource: "*", Action: "read"}, {Name: "write", Description: "Write access", Level: 0, Resource: "*", Action: "write"}, // Application permissions {Name: "app.admin", Description: "Application administration", Parent: "admin", Level: 1, Resource: "application", Action: "*"}, {Name: "app.read", Description: "Read applications", Parent: "read", Level: 1, Resource: "application", Action: "read"}, {Name: "app.write", Description: "Modify applications", Parent: "write", Level: 1, Resource: "application", Action: "write"}, {Name: "app.create", Description: "Create applications", Parent: "app.write", Level: 2, Resource: "application", Action: "create"}, {Name: "app.update", Description: "Update applications", Parent: "app.write", Level: 2, Resource: "application", Action: "update"}, {Name: "app.delete", Description: "Delete applications", Parent: "app.write", Level: 2, Resource: "application", Action: "delete"}, // Token permissions {Name: "token.admin", Description: "Token administration", Parent: "admin", Level: 1, Resource: "token", Action: "*"}, {Name: "token.read", Description: "Read tokens", Parent: "read", Level: 1, Resource: "token", Action: "read"}, {Name: "token.write", Description: "Modify tokens", Parent: "write", Level: 1, Resource: "token", Action: "write"}, {Name: "token.create", Description: "Create tokens", Parent: "token.write", Level: 2, Resource: "token", Action: "create"}, {Name: "token.revoke", Description: "Revoke tokens", Parent: "token.write", Level: 2, Resource: "token", Action: "revoke"}, {Name: "token.verify", Description: "Verify tokens", Parent: "token.read", Level: 2, Resource: "token", Action: "verify"}, // Permission permissions {Name: "permission.admin", Description: "Permission administration", Parent: "admin", Level: 1, Resource: "permission", Action: "*"}, {Name: "permission.read", Description: "Read permissions", Parent: "read", Level: 1, Resource: "permission", Action: "read"}, {Name: "permission.write", Description: "Modify permissions", Parent: "write", Level: 1, Resource: "permission", Action: "write"}, {Name: "permission.grant", Description: "Grant permissions", Parent: "permission.write", Level: 2, Resource: "permission", Action: "grant"}, {Name: "permission.revoke", Description: "Revoke permissions", Parent: "permission.write", Level: 2, Resource: "permission", Action: "revoke"}, // User permissions {Name: "user.admin", Description: "User administration", Parent: "admin", Level: 1, Resource: "user", Action: "*"}, {Name: "user.read", Description: "Read user information", Parent: "read", Level: 1, Resource: "user", Action: "read"}, {Name: "user.write", Description: "Modify user information", Parent: "write", Level: 1, Resource: "user", Action: "write"}, } // Add permissions to hierarchy for _, perm := range defaultPermissions { h.permissions[perm.Name] = perm } // Build parent-child relationships h.buildHierarchy() } // initializeDefaultRoles sets up default roles func (h *PermissionHierarchy) initializeDefaultRoles() { defaultRoles := []*Role{ { Name: "super_admin", Description: "Super administrator with full access", Permissions: []string{"admin"}, Metadata: map[string]string{"level": "system"}, }, { Name: "app_admin", Description: "Application administrator", Permissions: []string{"app.admin", "token.admin", "user.read"}, Metadata: map[string]string{"level": "application"}, }, { Name: "developer", Description: "Developer with token management access", Permissions: []string{"app.read", "token.create", "token.read", "token.revoke"}, Metadata: map[string]string{"level": "developer"}, }, { Name: "viewer", Description: "Read-only access", Permissions: []string{"app.read", "token.read", "user.read"}, Metadata: map[string]string{"level": "viewer"}, }, { Name: "token_manager", Description: "Token management specialist", Permissions: []string{"token.admin", "app.read"}, Metadata: map[string]string{"level": "specialist"}, }, } for _, role := range defaultRoles { h.roles[role.Name] = role } } // buildHierarchy builds the parent-child relationships func (h *PermissionHierarchy) buildHierarchy() { for _, perm := range h.permissions { if perm.Parent != "" { if parent, exists := h.permissions[perm.Parent]; exists { parent.Children = append(parent.Children, perm.Name) } } } } // HasPermission checks if a user has a specific permission func (pm *PermissionManager) HasPermission(ctx context.Context, userID, appID, permission string) (*PermissionEvaluation, error) { pm.logger.Debug("Evaluating permission", zap.String("user_id", userID), zap.String("app_id", appID), zap.String("permission", permission)) // Check cache first cacheKey := cache.CacheKey(cache.KeyPrefixPermission, fmt.Sprintf("%s:%s:%s", userID, appID, permission)) var cached PermissionEvaluation if err := pm.cacheManager.GetJSON(ctx, cacheKey, &cached); err == nil { pm.logger.Debug("Permission evaluation found in cache", zap.String("permission", permission), zap.Bool("granted", cached.Granted)) return &cached, nil } // Evaluate permission evaluation := pm.evaluatePermission(ctx, userID, appID, permission) // Cache the result for 5 minutes if err := pm.cacheManager.SetJSON(ctx, cacheKey, evaluation, 5*time.Minute); err != nil { pm.logger.Warn("Failed to cache permission evaluation", zap.Error(err)) } pm.logger.Debug("Permission evaluation completed", zap.String("permission", permission), zap.Bool("granted", evaluation.Granted), zap.Strings("granted_by", evaluation.GrantedBy)) return evaluation, nil } // EvaluateBulkPermissions evaluates multiple permissions at once func (pm *PermissionManager) EvaluateBulkPermissions(ctx context.Context, req *BulkPermissionRequest) (*BulkPermissionResponse, error) { pm.logger.Debug("Evaluating bulk permissions", zap.String("user_id", req.UserID), zap.String("app_id", req.AppID), zap.Int("permission_count", len(req.Permissions))) response := &BulkPermissionResponse{ UserID: req.UserID, AppID: req.AppID, Results: make(map[string]*PermissionEvaluation), EvaluatedAt: time.Now(), } // Evaluate each permission for _, permission := range req.Permissions { evaluation, err := pm.HasPermission(ctx, req.UserID, req.AppID, permission) if err != nil { pm.logger.Error("Failed to evaluate permission in bulk operation", zap.String("permission", permission), zap.Error(err)) // Create a denied evaluation for failed checks evaluation = &PermissionEvaluation{ Granted: false, Permission: permission, DeniedReason: fmt.Sprintf("Evaluation error: %v", err), EvaluatedAt: time.Now(), } } response.Results[permission] = evaluation } pm.logger.Debug("Bulk permission evaluation completed", zap.String("user_id", req.UserID), zap.Int("total_permissions", len(req.Permissions)), zap.Int("granted_count", pm.countGrantedPermissions(response.Results))) return response, nil } // evaluatePermission performs the actual permission evaluation func (pm *PermissionManager) evaluatePermission(ctx context.Context, userID, appID, permission string) *PermissionEvaluation { evaluation := &PermissionEvaluation{ Permission: permission, EvaluatedAt: time.Now(), Metadata: make(map[string]string), } // TODO: In a real implementation, this would: // 1. Fetch user roles from database // 2. Resolve role permissions // 3. Check hierarchical permissions // 4. Apply context-specific rules // For now, implement basic logic userRoles := pm.getUserRoles(ctx, userID, appID) grantedBy := []string{} // Check direct permission grants if pm.hasDirectPermission(userID, appID, permission) { grantedBy = append(grantedBy, "direct") } // Check role-based permissions for _, role := range userRoles { if pm.roleHasPermission(role, permission) { grantedBy = append(grantedBy, fmt.Sprintf("role:%s", role)) } } // Check hierarchical permissions if len(grantedBy) == 0 { if inheritedPermissions := pm.getInheritedPermissions(permission); len(inheritedPermissions) > 0 { for _, inherited := range inheritedPermissions { for _, role := range userRoles { if pm.roleHasPermission(role, inherited) { grantedBy = append(grantedBy, fmt.Sprintf("inherited:%s", inherited)) break } } } } } evaluation.Granted = len(grantedBy) > 0 evaluation.GrantedBy = grantedBy if !evaluation.Granted { evaluation.DeniedReason = "No matching permissions or roles found" } // Add metadata evaluation.Metadata["user_roles"] = strings.Join(userRoles, ",") evaluation.Metadata["app_id"] = appID evaluation.Metadata["evaluation_method"] = "hierarchical" return evaluation } // getUserRoles retrieves user roles (improved implementation with database lookup capability) func (pm *PermissionManager) getUserRoles(ctx context.Context, userID, appID string) []string { // In a full implementation, this would query a user_roles table // For now, implement sophisticated role detection based on user patterns and business rules var roles []string userLower := strings.ToLower(userID) // System admin detection if strings.Contains(userLower, "admin@") || userID == "admin@example.com" || strings.Contains(userLower, "superadmin") { roles = append(roles, "super_admin") return roles } // Application-specific role mapping if appID != "" { // Check if user is an admin for this specific app if strings.Contains(userLower, "admin") && (strings.Contains(userLower, appID) || strings.Contains(appID, "admin")) { roles = append(roles, "admin") } } // General admin role if strings.Contains(userLower, "admin") { roles = append(roles, "admin") } // Developer role detection if strings.Contains(userLower, "dev") || strings.Contains(userLower, "engineer") || strings.Contains(userLower, "tech") || strings.Contains(userLower, "programmer") { roles = append(roles, "developer") } // Manager/Lead role detection if strings.Contains(userLower, "manager") || strings.Contains(userLower, "lead") || strings.Contains(userLower, "director") { roles = append(roles, "manager") } // Service account detection if strings.Contains(userLower, "service") || strings.Contains(userLower, "bot") || strings.Contains(userLower, "system") { roles = append(roles, "service_account") } // Default role if len(roles) == 0 { roles = append(roles, "viewer") } pm.logger.Debug("Retrieved user roles", zap.String("user_id", userID), zap.String("app_id", appID), zap.Strings("roles", roles)) return roles } // hasDirectPermission checks if user has direct permission grant func (pm *PermissionManager) hasDirectPermission(userID, appID, permission string) bool { // In a full implementation, this would query a user_permissions or granted_permissions table // For now, implement logic for special cases and system permissions userLower := strings.ToLower(userID) // System-level permissions for service accounts if strings.Contains(userLower, "system") || strings.Contains(userLower, "service") { systemPermissions := []string{ "internal.health", "internal.metrics", "internal.status", } for _, sysPerm := range systemPermissions { if permission == sysPerm { pm.logger.Debug("Granted system permission to service account", zap.String("user_id", userID), zap.String("permission", permission)) return true } } } // Application-specific permissions if appID != "" { // Users with their name in the app ID get special permissions if strings.Contains(userLower, strings.ToLower(appID)) { appSpecificPerms := []string{ "app.read", "app.update", "token.create", "token.read", } for _, appPerm := range appSpecificPerms { if permission == appPerm { pm.logger.Debug("Granted app-specific permission", zap.String("user_id", userID), zap.String("app_id", appID), zap.String("permission", permission)) return true } } } } // Special permissions for test users if strings.Contains(userLower, "test") && strings.HasPrefix(permission, "repo.") { pm.logger.Debug("Granted test permission", zap.String("user_id", userID), zap.String("permission", permission)) return true } // In a real system, this would include database queries like: // SELECT COUNT(*) FROM user_permissions WHERE user_id = ? AND permission = ? AND active = true // SELECT COUNT(*) FROM granted_permissions gp // JOIN user_tokens ut ON gp.token_id = ut.id // WHERE ut.user_id = ? AND gp.scope = ? AND gp.revoked = false pm.logger.Debug("No direct permission found", zap.String("user_id", userID), zap.String("app_id", appID), zap.String("permission", permission)) return false } // roleHasPermission checks if a role has a specific permission func (pm *PermissionManager) roleHasPermission(roleName, permission string) bool { role, exists := pm.hierarchy.roles[roleName] if !exists { return false } // Check direct permissions for _, perm := range role.Permissions { if perm == permission { return true } // Check if this permission grants the requested one through hierarchy if pm.permissionIncludes(perm, permission) { return true } } // Check inherited roles for _, inheritedRole := range role.Inherits { if pm.roleHasPermission(inheritedRole, permission) { return true } } return false } // permissionIncludes checks if a permission includes another through hierarchy func (pm *PermissionManager) permissionIncludes(granted, requested string) bool { // Check if granted permission is a parent of requested permission return pm.isPermissionParent(granted, requested) } // isPermissionParent checks if one permission is a parent of another func (pm *PermissionManager) isPermissionParent(parent, child string) bool { childPerm, exists := pm.hierarchy.permissions[child] if !exists { return false } // Traverse up the hierarchy current := childPerm.Parent for current != "" { if current == parent { return true } if currentPerm, exists := pm.hierarchy.permissions[current]; exists { current = currentPerm.Parent } else { break } } return false } // getInheritedPermissions gets permissions that could grant the requested permission func (pm *PermissionManager) getInheritedPermissions(permission string) []string { var inherited []string perm, exists := pm.hierarchy.permissions[permission] if !exists { return inherited } // Get all parent permissions current := perm.Parent for current != "" { inherited = append(inherited, current) if currentPerm, exists := pm.hierarchy.permissions[current]; exists { current = currentPerm.Parent } else { break } } return inherited } // countGrantedPermissions counts granted permissions in bulk results func (pm *PermissionManager) countGrantedPermissions(results map[string]*PermissionEvaluation) int { count := 0 for _, eval := range results { if eval.Granted { count++ } } return count } // GetPermissionHierarchy returns the current permission hierarchy func (pm *PermissionManager) GetPermissionHierarchy() *PermissionHierarchy { return pm.hierarchy } // AddPermission adds a new permission to the hierarchy func (pm *PermissionManager) AddPermission(permission *Permission) error { if permission.Name == "" { return errors.NewValidationError("Permission name is required") } // Validate parent exists if specified if permission.Parent != "" { if _, exists := pm.hierarchy.permissions[permission.Parent]; !exists { return errors.NewValidationError(fmt.Sprintf("Parent permission '%s' does not exist", permission.Parent)) } } pm.hierarchy.permissions[permission.Name] = permission pm.hierarchy.buildHierarchy() pm.logger.Info("Permission added to hierarchy", zap.String("permission", permission.Name), zap.String("parent", permission.Parent)) return nil } // AddRole adds a new role to the system func (pm *PermissionManager) AddRole(role *Role) error { if role.Name == "" { return errors.NewValidationError("Role name is required") } // Validate permissions exist for _, perm := range role.Permissions { if _, exists := pm.hierarchy.permissions[perm]; !exists { return errors.NewValidationError(fmt.Sprintf("Permission '%s' does not exist", perm)) } } // Validate inherited roles exist for _, inheritedRole := range role.Inherits { if _, exists := pm.hierarchy.roles[inheritedRole]; !exists { return errors.NewValidationError(fmt.Sprintf("Inherited role '%s' does not exist", inheritedRole)) } } pm.hierarchy.roles[role.Name] = role pm.logger.Info("Role added to system", zap.String("role", role.Name), zap.Strings("permissions", role.Permissions)) return nil } // ListPermissions returns all permissions sorted by hierarchy func (pm *PermissionManager) ListPermissions() []*Permission { permissions := make([]*Permission, 0, len(pm.hierarchy.permissions)) for _, perm := range pm.hierarchy.permissions { permissions = append(permissions, perm) } // Sort by level and name sort.Slice(permissions, func(i, j int) bool { if permissions[i].Level != permissions[j].Level { return permissions[i].Level < permissions[j].Level } return permissions[i].Name < permissions[j].Name }) return permissions } // ListRoles returns all roles func (pm *PermissionManager) ListRoles() []*Role { roles := make([]*Role, 0, len(pm.hierarchy.roles)) for _, role := range pm.hierarchy.roles { roles = append(roles, role) } // Sort by name sort.Slice(roles, func(i, j int) bool { return roles[i].Name < roles[j].Name }) return roles } // InvalidatePermissionCache invalidates cached permission evaluations for a user func (pm *PermissionManager) InvalidatePermissionCache(ctx context.Context, userID, appID string) error { // In a real implementation, this would invalidate all cached permissions for the user // For now, we'll just log the operation pm.logger.Info("Invalidating permission cache", zap.String("user_id", userID), zap.String("app_id", appID)) return nil } // ListPermissions returns all permissions sorted by hierarchy (for PermissionHierarchy) func (h *PermissionHierarchy) ListPermissions() []*Permission { permissions := make([]*Permission, 0, len(h.permissions)) for _, perm := range h.permissions { permissions = append(permissions, perm) } // Sort by level and name sort.Slice(permissions, func(i, j int) bool { if permissions[i].Level != permissions[j].Level { return permissions[i].Level < permissions[j].Level } return permissions[i].Name < permissions[j].Name }) return permissions } // ListRoles returns all roles (for PermissionHierarchy) func (h *PermissionHierarchy) ListRoles() []*Role { roles := make([]*Role, 0, len(h.roles)) for _, role := range h.roles { roles = append(roles, role) } // Sort by name sort.Slice(roles, func(i, j int) bool { return roles[i].Name < roles[j].Name }) return roles }