160 lines
4.5 KiB
Go
160 lines
4.5 KiB
Go
package rbac
|
|
|
|
import (
|
|
"Yimaru-Backend/internal/domain"
|
|
"Yimaru-Backend/internal/ports"
|
|
"context"
|
|
"fmt"
|
|
"log/slog"
|
|
"sync/atomic"
|
|
)
|
|
|
|
type snapshot struct {
|
|
rolePerms map[string]map[string]struct{} // roleName -> set of permissionKeys
|
|
}
|
|
|
|
type Service struct {
|
|
store ports.RBACStore
|
|
cache atomic.Value // holds *snapshot
|
|
logger *slog.Logger
|
|
}
|
|
|
|
func NewService(store ports.RBACStore, logger *slog.Logger) *Service {
|
|
s := &Service{
|
|
store: store,
|
|
logger: logger,
|
|
}
|
|
// Initialize with empty snapshot
|
|
s.cache.Store(&snapshot{rolePerms: make(map[string]map[string]struct{})})
|
|
return s
|
|
}
|
|
|
|
// HasPermission checks if a role has a specific permission key.
|
|
// SUPER_ADMIN always returns true.
|
|
func (s *Service) HasPermission(roleName, permKey string) bool {
|
|
if roleName == string(domain.RoleSuperAdmin) {
|
|
return true
|
|
}
|
|
snap := s.cache.Load().(*snapshot)
|
|
perms, ok := snap.rolePerms[roleName]
|
|
if !ok {
|
|
return false
|
|
}
|
|
_, has := perms[permKey]
|
|
return has
|
|
}
|
|
|
|
// Reload rebuilds the in-memory cache from the database.
|
|
func (s *Service) Reload(ctx context.Context) error {
|
|
rolePerms, err := s.store.GetAllRolesWithPermissions(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("rbac reload: %w", err)
|
|
}
|
|
s.cache.Store(&snapshot{rolePerms: rolePerms})
|
|
s.logger.Info("RBAC cache reloaded", "roles", len(rolePerms))
|
|
return nil
|
|
}
|
|
|
|
// SeedPermissions upserts all permission definitions into the database.
|
|
func (s *Service) SeedPermissions(ctx context.Context) error {
|
|
for _, p := range AllPermissions {
|
|
if _, err := s.store.UpsertPermission(ctx, p); err != nil {
|
|
return fmt.Errorf("seed permission %s: %w", p.Key, err)
|
|
}
|
|
}
|
|
s.logger.Info("RBAC permissions seeded", "count", len(AllPermissions))
|
|
return nil
|
|
}
|
|
|
|
// SeedDefaultRolePermissions assigns the default permission sets to each system role.
|
|
// It only assigns permissions if the role currently has zero permissions (first-time setup).
|
|
func (s *Service) SeedDefaultRolePermissions(ctx context.Context) error {
|
|
for roleName, permKeys := range DefaultRolePermissions {
|
|
role, err := s.store.GetRoleByName(ctx, roleName)
|
|
if err != nil {
|
|
s.logger.Warn("skipping role permission seed: role not found", "role", roleName)
|
|
continue
|
|
}
|
|
|
|
// Insert missing permissions without wiping existing role permissions.
|
|
var permIDs []int64
|
|
for _, key := range permKeys {
|
|
perm, err := s.store.GetPermissionByKey(ctx, key)
|
|
if err != nil {
|
|
s.logger.Warn("permission key not found during seed", "key", key, "role", roleName)
|
|
continue
|
|
}
|
|
permIDs = append(permIDs, perm.ID)
|
|
}
|
|
|
|
if len(permIDs) == 0 {
|
|
continue
|
|
}
|
|
|
|
if err := s.store.AddRolePermissions(ctx, role.ID, permIDs); err != nil {
|
|
return fmt.Errorf("seed role permissions for %s: %w", roleName, err)
|
|
}
|
|
s.logger.Info("ensured default permissions for role", "role", roleName, "count", len(permIDs))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// --- Role CRUD (pass-through to store + reload cache) ---
|
|
|
|
func (s *Service) CreateRole(ctx context.Context, name, description string) (domain.RoleRecord, error) {
|
|
role, err := s.store.CreateRole(ctx, name, description, false)
|
|
if err != nil {
|
|
return domain.RoleRecord{}, err
|
|
}
|
|
_ = s.Reload(ctx)
|
|
return role, nil
|
|
}
|
|
|
|
func (s *Service) GetRoleByID(ctx context.Context, id int64) (domain.RoleRecord, error) {
|
|
return s.store.GetRoleByID(ctx, id)
|
|
}
|
|
|
|
func (s *Service) ListRoles(ctx context.Context, filter domain.RoleListFilter) ([]domain.RoleRecord, int64, error) {
|
|
return s.store.ListRoles(ctx, filter)
|
|
}
|
|
|
|
func (s *Service) UpdateRole(ctx context.Context, id int64, name, description string) error {
|
|
if err := s.store.UpdateRole(ctx, id, name, description); err != nil {
|
|
return err
|
|
}
|
|
_ = s.Reload(ctx)
|
|
return nil
|
|
}
|
|
|
|
func (s *Service) DeleteRole(ctx context.Context, id int64) error {
|
|
if err := s.store.DeleteRole(ctx, id); err != nil {
|
|
return err
|
|
}
|
|
_ = s.Reload(ctx)
|
|
return nil
|
|
}
|
|
|
|
// --- Permission queries ---
|
|
|
|
func (s *Service) ListPermissions(ctx context.Context) ([]domain.Permission, error) {
|
|
return s.store.ListPermissions(ctx)
|
|
}
|
|
|
|
func (s *Service) ListPermissionGroups(ctx context.Context) ([]string, error) {
|
|
return s.store.ListPermissionGroups(ctx)
|
|
}
|
|
|
|
// --- Role-Permission management ---
|
|
|
|
func (s *Service) SetRolePermissions(ctx context.Context, roleID int64, permissionIDs []int64) error {
|
|
if err := s.store.SetRolePermissions(ctx, roleID, permissionIDs); err != nil {
|
|
return err
|
|
}
|
|
_ = s.Reload(ctx)
|
|
return nil
|
|
}
|
|
|
|
func (s *Service) GetRolePermissions(ctx context.Context, roleID int64) ([]domain.Permission, error) {
|
|
return s.store.GetRolePermissions(ctx, roleID)
|
|
}
|