package authorization import ( "context" "fmt" "strings" "go.uber.org/zap" "github.com/kms/api-key-service/internal/domain" "github.com/kms/api-key-service/internal/errors" ) // ResourceType represents different types of resources type ResourceType string const ( ResourceTypeApplication ResourceType = "application" ResourceTypeToken ResourceType = "token" ResourceTypePermission ResourceType = "permission" ResourceTypeUser ResourceType = "user" ) // Action represents different actions that can be performed type Action string const ( ActionRead Action = "read" ActionWrite Action = "write" ActionDelete Action = "delete" ActionCreate Action = "create" ) // AuthorizationContext holds context for authorization decisions type AuthorizationContext struct { UserID string UserEmail string ResourceType ResourceType ResourceID string Action Action OwnerInfo *domain.Owner } // AuthorizationService provides role-based access control type AuthorizationService struct { logger *zap.Logger } // NewAuthorizationService creates a new authorization service func NewAuthorizationService(logger *zap.Logger) *AuthorizationService { return &AuthorizationService{ logger: logger, } } // AuthorizeResourceAccess checks if a user can perform an action on a resource func (a *AuthorizationService) AuthorizeResourceAccess(ctx context.Context, authCtx *AuthorizationContext) error { if authCtx == nil { return errors.NewForbiddenError("Authorization context is required") } a.logger.Debug("Authorizing resource access", zap.String("user_id", authCtx.UserID), zap.String("resource_type", string(authCtx.ResourceType)), zap.String("resource_id", authCtx.ResourceID), zap.String("action", string(authCtx.Action))) // Check if user is a system admin if a.isSystemAdmin(authCtx.UserID) { a.logger.Debug("System admin access granted", zap.String("user_id", authCtx.UserID)) return nil } // Check resource ownership if authCtx.OwnerInfo != nil { if a.isResourceOwner(authCtx, authCtx.OwnerInfo) { a.logger.Debug("Resource owner access granted", zap.String("user_id", authCtx.UserID), zap.String("resource_id", authCtx.ResourceID)) return nil } } // Check specific resource-action combinations switch authCtx.ResourceType { case ResourceTypeApplication: return a.authorizeApplicationAccess(authCtx) case ResourceTypeToken: return a.authorizeTokenAccess(authCtx) case ResourceTypePermission: return a.authorizePermissionAccess(authCtx) case ResourceTypeUser: return a.authorizeUserAccess(authCtx) default: return errors.NewForbiddenError(fmt.Sprintf("Unknown resource type: %s", authCtx.ResourceType)) } } // AuthorizeApplicationOwnership checks if a user owns an application func (a *AuthorizationService) AuthorizeApplicationOwnership(userID string, app *domain.Application) error { if app == nil { return errors.NewValidationError("Application is required") } // System admins can access any application if a.isSystemAdmin(userID) { return nil } // Check if user is the owner if a.isOwner(userID, &app.Owner) { return nil } a.logger.Warn("Application ownership authorization failed", zap.String("user_id", userID), zap.String("app_id", app.AppID), zap.String("owner_type", string(app.Owner.Type)), zap.String("owner_name", app.Owner.Name)) return errors.NewForbiddenError("You do not have permission to access this application") } // AuthorizeTokenOwnership checks if a user owns a token func (a *AuthorizationService) AuthorizeTokenOwnership(userID string, token interface{}) error { // System admins can access any token if a.isSystemAdmin(userID) { return nil } // Extract owner information based on token type var owner *domain.Owner var tokenID string switch t := token.(type) { case *domain.StaticToken: owner = &t.Owner tokenID = t.ID.String() case *domain.UserToken: // For user tokens, the user ID should match if t.UserID == userID { return nil } tokenID = "user_token" default: return errors.NewValidationError("Unknown token type") } // Check ownership if owner != nil && a.isOwner(userID, owner) { return nil } a.logger.Warn("Token ownership authorization failed", zap.String("user_id", userID), zap.String("token_id", tokenID)) return errors.NewForbiddenError("You do not have permission to access this token") } // isSystemAdmin checks if a user is a system administrator func (a *AuthorizationService) isSystemAdmin(userID string) bool { // System admin users - this should be configurable systemAdmins := []string{ "admin@example.com", "system@internal.com", } for _, admin := range systemAdmins { if userID == admin { return true } } return false } // isResourceOwner checks if the user is the owner of a resource func (a *AuthorizationService) isResourceOwner(authCtx *AuthorizationContext, owner *domain.Owner) bool { return a.isOwner(authCtx.UserID, owner) } // isOwner checks if a user is the owner based on owner information func (a *AuthorizationService) isOwner(userID string, owner *domain.Owner) bool { switch owner.Type { case domain.OwnerTypeIndividual: // For individual ownership, check if the user ID matches the owner name return userID == owner.Name || userID == owner.Owner case domain.OwnerTypeTeam: // For team ownership, this would typically require a team membership check // For now, we'll check if the user is the team owner return userID == owner.Owner || a.isTeamMember(userID, owner.Name) default: return false } } // isTeamMember checks if a user is a member of a team (placeholder implementation) func (a *AuthorizationService) isTeamMember(userID, teamName string) bool { // In a real implementation, this would check team membership in a database // For now, we'll use a simple heuristic based on email domains if !strings.Contains(userID, "@") { return false } userDomain := strings.Split(userID, "@")[1] teamDomain := strings.ToLower(teamName) // Simple check: if team name looks like a domain and user's domain matches if strings.Contains(teamDomain, ".") && strings.Contains(userDomain, teamDomain) { return true } // Additional team membership logic would go here return false } // authorizeApplicationAccess handles application-specific authorization func (a *AuthorizationService) authorizeApplicationAccess(authCtx *AuthorizationContext) error { switch authCtx.Action { case ActionRead: // Users can read applications they have some relationship with // This could be expanded to check for shared access, etc. return errors.NewForbiddenError("You do not have permission to read this application") case ActionWrite: // Only owners can modify applications return errors.NewForbiddenError("You do not have permission to modify this application") case ActionDelete: // Only owners can delete applications return errors.NewForbiddenError("You do not have permission to delete this application") case ActionCreate: // Most users can create applications (with rate limiting) return nil default: return errors.NewForbiddenError(fmt.Sprintf("Unknown action: %s", authCtx.Action)) } } // authorizeTokenAccess handles token-specific authorization func (a *AuthorizationService) authorizeTokenAccess(authCtx *AuthorizationContext) error { switch authCtx.Action { case ActionRead: return errors.NewForbiddenError("You do not have permission to read this token") case ActionWrite: return errors.NewForbiddenError("You do not have permission to modify this token") case ActionDelete: return errors.NewForbiddenError("You do not have permission to delete this token") case ActionCreate: return errors.NewForbiddenError("You do not have permission to create tokens for this application") default: return errors.NewForbiddenError(fmt.Sprintf("Unknown action: %s", authCtx.Action)) } } // authorizePermissionAccess handles permission-specific authorization func (a *AuthorizationService) authorizePermissionAccess(authCtx *AuthorizationContext) error { switch authCtx.Action { case ActionRead: // Users can read permissions they have return nil case ActionWrite: // Only admins can modify permissions return errors.NewForbiddenError("You do not have permission to modify permissions") case ActionDelete: // Only admins can delete permissions return errors.NewForbiddenError("You do not have permission to delete permissions") case ActionCreate: // Only admins can create permissions return errors.NewForbiddenError("You do not have permission to create permissions") default: return errors.NewForbiddenError(fmt.Sprintf("Unknown action: %s", authCtx.Action)) } } // authorizeUserAccess handles user-specific authorization func (a *AuthorizationService) authorizeUserAccess(authCtx *AuthorizationContext) error { switch authCtx.Action { case ActionRead: // Users can read their own information if authCtx.ResourceID == authCtx.UserID { return nil } return errors.NewForbiddenError("You do not have permission to read this user's information") case ActionWrite: // Users can modify their own information if authCtx.ResourceID == authCtx.UserID { return nil } return errors.NewForbiddenError("You do not have permission to modify this user's information") case ActionDelete: // Users can delete their own account, admins can delete any if authCtx.ResourceID == authCtx.UserID { return nil } return errors.NewForbiddenError("You do not have permission to delete this user") default: return errors.NewForbiddenError(fmt.Sprintf("Unknown action: %s", authCtx.Action)) } } // AuthorizeListAccess checks if a user can list resources of a specific type func (a *AuthorizationService) AuthorizeListAccess(ctx context.Context, userID string, resourceType ResourceType) error { a.logger.Debug("Authorizing list access", zap.String("user_id", userID), zap.String("resource_type", string(resourceType))) // System admins can list anything if a.isSystemAdmin(userID) { return nil } // For now, allow users to list their own resources // This would be refined based on business requirements switch resourceType { case ResourceTypeApplication: return nil // Users can list applications (filtered by ownership) case ResourceTypeToken: return nil // Users can list their own tokens case ResourceTypePermission: return nil // Users can list available permissions case ResourceTypeUser: // Only admins can list users return errors.NewForbiddenError("You do not have permission to list users") default: return errors.NewForbiddenError(fmt.Sprintf("Unknown resource type: %s", resourceType)) } } // GetUserResourceFilter returns a filter for resources that a user can access func (a *AuthorizationService) GetUserResourceFilter(userID string, resourceType ResourceType) map[string]interface{} { filter := make(map[string]interface{}) // System admins see everything if a.isSystemAdmin(userID) { return filter // Empty filter means no restrictions } // Filter by ownership switch resourceType { case ResourceTypeApplication, ResourceTypeToken: // Users can only see resources they own filter["owner_email"] = userID case ResourceTypePermission: // Users can see all permissions (they're not user-specific) return filter case ResourceTypeUser: // Users can only see themselves filter["user_id"] = userID } return filter }