-
This commit is contained in:
353
internal/authorization/rbac.go
Normal file
353
internal/authorization/rbac.go
Normal file
@ -0,0 +1,353 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user