// Файл: internal/acl/manager.go // Назначение: Глобальный менеджер ACL для всей СУБД. // Управляет пользователями, ролями и разрешениями на уровне БД и коллекций. // Реализован с использованием sync.Map для wait-free доступа. package acl import ( "fmt" "sync" "time" "github.com/google/uuid" ) // PermissionType определяет тип разрешения type PermissionType string const ( PermRead PermissionType = "read" PermWrite PermissionType = "write" PermDelete PermissionType = "delete" PermAdmin PermissionType = "admin" ) // User представляет пользователя системы type User struct { ID string `msgpack:"id"` Username string `msgpack:"username"` Password string `msgpack:"password"` // В реальной системе - хеш Roles []string `msgpack:"roles"` CreatedAt int64 `msgpack:"created_at"` LastLogin int64 `msgpack:"last_login"` Active bool `msgpack:"active"` } // Role представляет роль с набором разрешений type Role struct { Name string `msgpack:"name"` Permissions []string `msgpack:"permissions"` // "database.collection:read" формат } // ACLManager управляет доступом к БД type ACLManager struct { users sync.Map // map[string]*User roles sync.Map // map[string]*Role sessionRoles sync.Map // map[string]string - sessionID -> role mu sync.RWMutex } // NewACLManager создаёт новый менеджер ACL func NewACLManager() *ACLManager { m := &ACLManager{} // Создаём роль администратора по умолчанию adminRole := &Role{ Name: "admin", Permissions: []string{"*:*"}, } m.roles.Store("admin", adminRole) // Создаём пользователя admin по умолчанию adminUser := &User{ ID: uuid.New().String(), Username: "admin", Password: "admin", // В продакшене использовать хеш! Roles: []string{"admin"}, CreatedAt: time.Now().UnixMilli(), Active: true, } m.users.Store("admin", adminUser) // Создаём роль guest с ограниченными правами guestRole := &Role{ Name: "guest", Permissions: []string{}, } m.roles.Store("guest", guestRole) return m } // CreateUser создаёт нового пользователя func (m *ACLManager) CreateUser(username, password string, roles []string) error { if _, exists := m.users.Load(username); exists { return fmt.Errorf("user %s already exists", username) } user := &User{ ID: uuid.New().String(), Username: username, Password: password, Roles: roles, CreatedAt: time.Now().UnixMilli(), Active: true, } m.users.Store(username, user) return nil } // Authenticate аутентифицирует пользователя func (m *ACLManager) Authenticate(username, password string) (string, error) { val, ok := m.users.Load(username) if !ok { return "", fmt.Errorf("user not found") } user := val.(*User) if !user.Active { return "", fmt.Errorf("user is disabled") } if user.Password != password { return "", fmt.Errorf("invalid password") } // Обновляем время последнего входа user.LastLogin = time.Now().UnixMilli() m.users.Store(username, user) // Создаём сессию sessionID := uuid.New().String() m.sessionRoles.Store(sessionID, user.Roles) return sessionID, nil } // Logout завершает сессию func (m *ACLManager) Logout(sessionID string) { m.sessionRoles.Delete(sessionID) } // CheckPermission проверяет разрешение для сессии func (m *ACLManager) CheckPermission(sessionID, database, collection, operation string) bool { rolesVal, ok := m.sessionRoles.Load(sessionID) if !ok { return false } roles := rolesVal.([]string) for _, roleName := range roles { roleVal, ok := m.roles.Load(roleName) if !ok { continue } role := roleVal.(*Role) for _, perm := range role.Permissions { if m.matchPermission(perm, database, collection, operation) { return true } } } return false } // matchPermission проверяет соответствие разрешения func (m *ACLManager) matchPermission(perm, database, collection, operation string) bool { // Формат: "database.collection:operation" или "*:*" для всех // или "database.*:read" для всех коллекций в БД parts := splitPermission(perm) if len(parts) != 2 { return false } resource := parts[0] // "database.collection" или "database.*" op := parts[1] // "read", "write", "delete", "admin" // Проверка операции if op != "*" && op != operation { return false } // Проверка ресурса if resource == "*:*" { return true } resourceParts := splitResource(resource) if len(resourceParts) != 2 { return false } dbPattern := resourceParts[0] collPattern := resourceParts[1] if dbPattern != "*" && dbPattern != database { return false } if collPattern != "*" && collPattern != collection { return false } return true } // GrantPermission выдаёт разрешение роли func (m *ACLManager) GrantPermission(roleName, permission string) error { val, ok := m.roles.Load(roleName) if !ok { return fmt.Errorf("role not found") } role := val.(*Role) role.Permissions = append(role.Permissions, permission) m.roles.Store(roleName, role) return nil } // CreateRole создаёт новую роль func (m *ACLManager) CreateRole(name string) error { if _, exists := m.roles.Load(name); exists { return fmt.Errorf("role %s already exists", name) } role := &Role{ Name: name, Permissions: []string{}, } m.roles.Store(name, role) return nil } // AddUserRole добавляет роль пользователю func (m *ACLManager) AddUserRole(username, roleName string) error { val, ok := m.users.Load(username) if !ok { return fmt.Errorf("user not found") } user := val.(*User) user.Roles = append(user.Roles, roleName) m.users.Store(username, user) return nil } // ListUsers возвращает список всех пользователей func (m *ACLManager) ListUsers() []string { users := make([]string, 0) m.users.Range(func(key, value interface{}) bool { users = append(users, key.(string)) return true }) return users } // ListRoles возвращает список всех ролей func (m *ACLManager) ListRoles() []string { roles := make([]string, 0) m.roles.Range(func(key, value interface{}) bool { roles = append(roles, key.(string)) return true }) return roles } // Helper functions func splitPermission(perm string) []string { for i := 0; i < len(perm); i++ { if perm[i] == ':' { return []string{perm[:i], perm[i+1:]} } } return []string{perm, ""} } func splitResource(resource string) []string { for i := 0; i < len(resource); i++ { if resource[i] == '.' { return []string{resource[:i], resource[i+1:]} } } return []string{resource, "*"} }