package auth import ( "fmt" "sync" "github.com/jfraeys/fetch_ml/internal/fileutil" "gopkg.in/yaml.v3" ) // PermissionConfig represents the permissions configuration type PermissionConfig struct { Roles map[string]RoleConfig `yaml:"roles"` Groups map[string]GroupConfig `yaml:"groups"` Hierarchy map[string]HierarchyConfig `yaml:"hierarchy"` Defaults DefaultsConfig `yaml:"defaults"` } // RoleConfig defines a role and its permissions type RoleConfig struct { Description string `yaml:"description"` Permissions []string `yaml:"permissions"` } // GroupConfig defines a permission group type GroupConfig struct { Description string `yaml:"description"` Inherits []string `yaml:"inherits"` Permissions []string `yaml:"permissions"` } // HierarchyConfig defines resource hierarchy type HierarchyConfig struct { Children map[string]interface{} `yaml:"children"` Special map[string]string `yaml:"special"` } // DefaultsConfig defines default settings type DefaultsConfig struct { NewUserRole string `yaml:"new_user_role"` AdminUsers []string `yaml:"admin_users"` } // PermissionManager manages permissions from YAML file type PermissionManager struct { config *PermissionConfig rolePerms map[string]map[string]bool groupPerms map[string]map[string]bool mu sync.RWMutex loaded bool } // NewPermissionManager creates a new permission manager func NewPermissionManager(configPath string) (*PermissionManager, error) { pm := &PermissionManager{} if err := pm.loadConfig(configPath); err != nil { return nil, fmt.Errorf("failed to load permissions: %w", err) } return pm, nil } // loadConfig loads permissions from YAML file func (pm *PermissionManager) loadConfig(configPath string) error { pm.mu.Lock() defer pm.mu.Unlock() data, err := fileutil.SecureFileRead(configPath) if err != nil { return fmt.Errorf("failed to read permissions file: %w", err) } var config PermissionConfig if err := yaml.Unmarshal(data, &config); err != nil { return fmt.Errorf("failed to parse permissions file: %w", err) } pm.config = &config pm.rolePerms = make(map[string]map[string]bool) pm.groupPerms = make(map[string]map[string]bool) // Process role permissions for roleName, role := range config.Roles { perms := make(map[string]bool) for _, perm := range role.Permissions { perms[perm] = true } pm.rolePerms[roleName] = perms } // Process group permissions for groupName, group := range config.Groups { perms := make(map[string]bool) // Add direct permissions for _, perm := range group.Permissions { perms[perm] = true } // Inherit permissions from other roles/groups for _, inherit := range group.Inherits { if rolePerms, exists := pm.rolePerms[inherit]; exists { for perm, value := range rolePerms { perms[perm] = value } } if groupPerms, exists := pm.groupPerms[inherit]; exists { for perm, value := range groupPerms { perms[perm] = value } } } pm.groupPerms[groupName] = perms } pm.loaded = true return nil } // GetRolePermissions returns permissions for a role func (pm *PermissionManager) GetRolePermissions(role string) map[string]bool { pm.mu.RLock() defer pm.mu.RUnlock() if !pm.loaded { return make(map[string]bool) } if perms, exists := pm.rolePerms[role]; exists { result := make(map[string]bool) for perm, value := range perms { result[perm] = value } return result } return make(map[string]bool) } // GetGroupPermissions returns permissions for a group func (pm *PermissionManager) GetGroupPermissions(group string) map[string]bool { pm.mu.RLock() defer pm.mu.RUnlock() if !pm.loaded { return make(map[string]bool) } if perms, exists := pm.groupPerms[group]; exists { result := make(map[string]bool) for perm, value := range perms { result[perm] = value } return result } return make(map[string]bool) } // GetAllRoles returns all available roles func (pm *PermissionManager) GetAllRoles() map[string]RoleConfig { pm.mu.RLock() defer pm.mu.RUnlock() if !pm.loaded { return make(map[string]RoleConfig) } result := make(map[string]RoleConfig) for name, role := range pm.config.Roles { result[name] = role } return result } // GetAllGroups returns all available groups func (pm *PermissionManager) GetAllGroups() map[string]GroupConfig { pm.mu.RLock() defer pm.mu.RUnlock() if !pm.loaded { return make(map[string]GroupConfig) } result := make(map[string]GroupConfig) for name, group := range pm.config.Groups { result[name] = group } return result } // GetDefaultRole returns the default role for new users func (pm *PermissionManager) GetDefaultRole() string { pm.mu.RLock() defer pm.mu.RUnlock() if !pm.loaded || pm.config.Defaults.NewUserRole == "" { return "viewer" } return pm.config.Defaults.NewUserRole } // IsAdminUser checks if a username should have admin rights func (pm *PermissionManager) IsAdminUser(username string) bool { pm.mu.RLock() defer pm.mu.RUnlock() if !pm.loaded { return false } for _, adminUser := range pm.config.Defaults.AdminUsers { if adminUser == username { return true } } return false } // ReloadConfig reloads the permissions configuration func (pm *PermissionManager) ReloadConfig(configPath string) error { return pm.loadConfig(configPath) } // ValidatePermission checks if a permission string is valid func (pm *PermissionManager) ValidatePermission(permission string) bool { pm.mu.RLock() defer pm.mu.RUnlock() if !pm.loaded { return false } // Wildcard is always valid if permission == "*" { return true } // Check if permission matches any defined role permissions for _, rolePerms := range pm.rolePerms { if _, exists := rolePerms[permission]; exists { return true } } // Check if permission matches any group permissions for _, groupPerms := range pm.groupPerms { if _, exists := groupPerms[permission]; exists { return true } } return false } // GetPermissionHierarchy returns the hierarchy for a resource func (pm *PermissionManager) GetPermissionHierarchy(resource string) map[string]interface{} { pm.mu.RLock() defer pm.mu.RUnlock() if !pm.loaded { return make(map[string]interface{}) } if hierarchy, exists := pm.config.Hierarchy[resource]; exists { return hierarchy.Children } return make(map[string]interface{}) } // Global permission manager instance var globalPermissionManager *PermissionManager var permissionManagerOnce sync.Once // GetGlobalPermissionManager returns the global permission manager func GetGlobalPermissionManager() *PermissionManager { permissionManagerOnce.Do(func() { // Try to load from default location if pm, err := NewPermissionManager("configs/schema/permissions.yaml"); err == nil { globalPermissionManager = pm } else { // Fallback to empty manager globalPermissionManager = &PermissionManager{ rolePerms: make(map[string]map[string]bool), groupPerms: make(map[string]map[string]bool), loaded: false, } } }) return globalPermissionManager }