282 lines
7.9 KiB
Go
282 lines
7.9 KiB
Go
|
|
// Файл: 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, "*"}
|
|||
|
|
}
|