fetch_ml/internal/auth/permissions.go
Jeremie Fraeys ef11d88a75
refactor(auth): add tenant scoping and permission enhancements
Update authentication system for multi-tenant support:
- API key management with tenant scoping
- Permission checks for multi-tenant operations
- Database layer with tenant isolation
- Keychain integration with audit logging
2026-02-26 12:06:08 -05:00

212 lines
5.2 KiB
Go

package auth
import (
"fmt"
"strings"
)
// Permission constants for type safety
const (
// Job permissions
PermissionJobsCreate = "jobs:create"
PermissionJobsRead = "jobs:read"
PermissionJobsUpdate = "jobs:update"
PermissionJobsDelete = "jobs:delete"
// Data permissions
PermissionDataCreate = "data:create"
PermissionDataRead = "data:read"
PermissionDataUpdate = "data:update"
PermissionDataDelete = "data:delete"
// Model permissions
PermissionModelsCreate = "models:create"
PermissionModelsRead = "models:read"
PermissionModelsUpdate = "models:update"
PermissionModelsDelete = "models:delete"
// System permissions
PermissionSystemConfig = "system:config"
PermissionSystemMetrics = "system:metrics"
PermissionSystemLogs = "system:logs"
PermissionSystemUsers = "system:users"
// Wildcard permission
PermissionAll = "*"
)
// Role constants
const (
RoleAdmin = "admin"
RoleDataScientist = "data_scientist"
RoleDataEngineer = "data_engineer"
RoleViewer = "viewer"
RoleOperator = "operator"
)
// PermissionGroup represents a group of related permissions
type PermissionGroup struct {
Name string
Description string
Permissions []string
}
// PermissionGroups defines built-in permission groups.
var PermissionGroups = map[string]PermissionGroup{
"full_access": {
Name: "Full Access",
Permissions: []string{PermissionAll},
Description: "Complete system access",
},
"job_management": {
Name: "Job Management",
Permissions: []string{
PermissionJobsCreate,
PermissionJobsRead,
PermissionJobsUpdate,
PermissionJobsDelete,
},
Description: "Create, read, update, and delete ML jobs",
},
"data_access": {
Name: "Data Access",
Permissions: []string{
PermissionDataRead,
PermissionDataCreate,
PermissionDataUpdate,
PermissionDataDelete,
},
Description: "Access and manage datasets",
},
"readonly": {
Name: "Read Only",
Permissions: []string{
PermissionJobsRead,
PermissionDataRead,
PermissionModelsRead,
PermissionSystemMetrics,
},
Description: "View-only access to system resources",
},
"system_admin": {
Name: "System Administration",
Permissions: []string{
PermissionSystemConfig,
PermissionSystemLogs,
PermissionSystemUsers,
PermissionSystemMetrics,
},
Description: "System configuration and user management",
},
}
// GetPermissionGroup returns a permission group by name
func GetPermissionGroup(name string) (PermissionGroup, bool) {
group, exists := PermissionGroups[name]
return group, exists
}
// ValidatePermission checks if a permission string is valid
func ValidatePermission(permission string) error {
if permission == PermissionAll {
return nil
}
// Check if permission matches known patterns
validPrefixes := []string{"jobs:", "data:", "models:", "system:"}
for _, prefix := range validPrefixes {
if strings.HasPrefix(permission, prefix) {
return nil
}
}
return fmt.Errorf("invalid permission format: %s", permission)
}
// ValidateRole checks if a role is valid
func ValidateRole(role string) error {
validRoles := []string{RoleAdmin, RoleDataScientist, RoleDataEngineer, RoleViewer, RoleOperator}
for _, validRole := range validRoles {
if role == validRole {
return nil
}
}
return fmt.Errorf("invalid role: %s", role)
}
// ExpandPermissionGroups converts permission group names to actual permissions
func ExpandPermissionGroups(groups []string) ([]string, error) {
var permissions []string
for _, groupName := range groups {
if groupName == PermissionAll {
return []string{PermissionAll}, nil
}
group, exists := GetPermissionGroup(groupName)
if !exists {
return nil, fmt.Errorf("unknown permission group: %s", groupName)
}
permissions = append(permissions, group.Permissions...)
}
// Remove duplicates
unique := make(map[string]bool)
for _, perm := range permissions {
unique[perm] = true
}
result := make([]string, 0, len(unique))
for perm := range unique {
result = append(result, perm)
}
return result, nil
}
// PermissionCheckResult represents the result of a permission check
type PermissionCheckResult struct {
Permission string `json:"permission"`
User string `json:"user"`
Roles []string `json:"roles"`
Missing []string `json:"missing,omitempty"`
Allowed bool `json:"allowed"`
}
// CheckMultiplePermissions checks multiple permissions at once
func (u *User) CheckMultiplePermissions(permissions []string) []PermissionCheckResult {
results := make([]PermissionCheckResult, len(permissions))
for i, permission := range permissions {
allowed := u.HasPermission(permission)
missing := []string{}
if !allowed {
missing = []string{permission}
}
results[i] = PermissionCheckResult{
Allowed: allowed,
Permission: permission,
User: u.Name,
Roles: u.Roles,
Missing: missing,
}
}
return results
}
// GetEffectivePermissions returns all effective permissions for a user
func (u *User) GetEffectivePermissions() []string {
if u.Permissions[PermissionAll] {
return []string{PermissionAll}
}
permissions := make([]string, 0, len(u.Permissions))
for perm := range u.Permissions {
permissions = append(permissions, perm)
}
return permissions
}