588 lines
19 KiB
Go
588 lines
19 KiB
Go
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 (placeholder implementation)
|
|
func (pm *PermissionManager) getUserRoles(ctx context.Context, userID, appID string) []string {
|
|
// TODO: Implement actual role retrieval from database
|
|
// For now, return default roles based on user patterns
|
|
|
|
if strings.Contains(userID, "admin") {
|
|
return []string{"super_admin"}
|
|
}
|
|
if strings.Contains(userID, "dev") {
|
|
return []string{"developer"}
|
|
}
|
|
return []string{"viewer"}
|
|
}
|
|
|
|
// hasDirectPermission checks if user has direct permission grant
|
|
func (pm *PermissionManager) hasDirectPermission(userID, appID, permission string) bool {
|
|
// TODO: Implement database lookup for direct permission grants
|
|
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
|
|
}
|