Files
skybridge/internal/authorization/rbac.go
2025-08-23 22:31:47 -04:00

353 lines
11 KiB
Go

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
}