Files
backend/internal/service/casbin_service.go

279 lines
7.9 KiB
Go
Raw Normal View History

package service
import (
"context"
"fmt"
"time"
"with_you/internal/cache"
"with_you/internal/config"
"with_you/internal/pkg/auth"
"with_you/internal/repository"
"github.com/casbin/casbin/v3"
)
// CasbinService Casbin 权限服务接口
type CasbinService interface {
// Enforce 检查权限
Enforce(ctx context.Context, sub, obj, act string) (bool, error)
// EnforceForUser 检查用户权限 (自动获取用户角色)
EnforceForUser(ctx context.Context, userID, obj, act string) (bool, error)
// AddRoleForUser 为用户添加角色
AddRoleForUser(ctx context.Context, userID, role string) error
// DeleteRoleForUser 移除用户角色
DeleteRoleForUser(ctx context.Context, userID, role string) error
// GetRolesForUser 获取用户所有角色
GetRolesForUser(ctx context.Context, userID string) ([]string, error)
// HasRoleForUser 检查用户是否拥有指定角色
HasRoleForUser(ctx context.Context, userID, role string) (bool, error)
// AddPolicy 添加策略
AddPolicy(ctx context.Context, role, resource, action string) error
// RemovePolicy 移除策略
RemovePolicy(ctx context.Context, role, resource, action string) error
// LoadPolicy 从数据库重新加载策略
LoadPolicy(ctx context.Context) error
// ClearCache 清除权限缓存
ClearCache(ctx context.Context) error
// GetPermissionsForRole 获取角色的所有权限
GetPermissionsForRole(ctx context.Context, role string) ([][]string, error)
// UpdatePermissionsForRole 更新角色的权限(先删除所有权限,再添加新权限)
UpdatePermissionsForRole(ctx context.Context, role string, permissions [][]string) error
// GetAllPermissions 获取系统中所有权限定义
GetAllPermissions(ctx context.Context) [][]string
}
type casbinService struct {
enforcer *casbin.Enforcer
cache cache.Cache
cacheTTL time.Duration
roleRepo repository.RoleRepository
skipRoutes map[string]bool
}
func NewCasbinService(
enforcer *casbin.Enforcer,
cache cache.Cache,
roleRepo repository.RoleRepository,
cfg *config.CasbinConfig,
) CasbinService {
skipRoutes := make(map[string]bool)
for _, route := range cfg.SkipRoutes {
skipRoutes[route] = true
}
return &casbinService{
enforcer: enforcer,
cache: cache,
cacheTTL: time.Duration(cfg.CacheTTL) * time.Second,
roleRepo: roleRepo,
skipRoutes: skipRoutes,
}
}
func (s *casbinService) Enforce(ctx context.Context, sub, obj, act string) (bool, error) {
// 检查是否跳过权限检查
if s.skipRoutes[obj] {
return true, nil
}
// 缓存键与策略维度对齐sub/obj/act 直接对应 p, role, resource, action。
// user_roles 表为用户-角色真源,由 EnforceForUser 解析后再调本方法;
// 这里 sub 已经是角色名,避免再走 g(user, role) 分组model 中 matcher 已不再依赖 g
if s.cache != nil {
cacheKey := fmt.Sprintf("casbin:policy:%s:%s:%s", sub, obj, act)
if cached, ok := s.cache.Get(cacheKey); ok {
if allowed, ok := cached.(bool); ok {
return allowed, nil
}
}
}
// 执行权限检查
allowed, err := s.enforcer.Enforce(sub, obj, act)
if err != nil {
return false, err
}
// 缓存结果
if s.cache != nil {
cacheKey := fmt.Sprintf("casbin:policy:%s:%s:%s", sub, obj, act)
s.cache.Set(cacheKey, allowed, s.cacheTTL)
}
return allowed, nil
}
func (s *casbinService) EnforceForUser(ctx context.Context, userID, obj, act string) (bool, error) {
// 获取用户角色
roles, err := s.GetRolesForUser(ctx, userID)
if err != nil {
return false, err
}
// 如果用户没有角色,使用默认角色
if len(roles) == 0 {
roles = []string{"user"} // 默认角色
}
// 检查每个角色的权限
for _, role := range roles {
allowed, err := s.Enforce(ctx, role, obj, act)
if err != nil {
return false, err
}
if allowed {
return true, nil
}
}
return false, nil
}
func (s *casbinService) AddRoleForUser(ctx context.Context, userID, role string) error {
// 真源策略user_roles 表为唯一真源Casbin 不再承载 g, user, role 分组策略。
// 减少双写漂移与缓存失效盲区EnforceForUser 内部先查 DB 角色再对每个角色调 Enforce(role,...)。
if err := s.roleRepo.AddRoleForUser(ctx, userID, role); err != nil {
return err
}
// 失效该用户的角色缓存,以及与该角色相关的策略缓存。
s.clearUserRoleCache(ctx, userID)
return s.clearPolicyCache(ctx)
}
func (s *casbinService) DeleteRoleForUser(ctx context.Context, userID, role string) error {
if err := s.roleRepo.DeleteRoleForUser(ctx, userID, role); err != nil {
return err
}
s.clearUserRoleCache(ctx, userID)
return s.clearPolicyCache(ctx)
}
func (s *casbinService) GetRolesForUser(ctx context.Context, userID string) ([]string, error) {
// 检查缓存
if s.cache != nil {
cacheKey := fmt.Sprintf("casbin:user:roles:%s", userID)
if cached, ok := s.cache.Get(cacheKey); ok {
if roles, ok := cached.([]string); ok {
return roles, nil
}
}
}
// 从数据库获取
roles, err := s.roleRepo.GetUserRoles(ctx, userID)
if err != nil {
return nil, err
}
// 缓存结果
if s.cache != nil {
cacheKey := fmt.Sprintf("casbin:user:roles:%s", userID)
s.cache.Set(cacheKey, roles, s.cacheTTL)
}
return roles, nil
}
func (s *casbinService) HasRoleForUser(ctx context.Context, userID, role string) (bool, error) {
return s.roleRepo.HasRole(ctx, userID, role)
}
func (s *casbinService) AddPolicy(ctx context.Context, role, resource, action string) error {
if _, err := s.enforcer.AddPolicy(role, resource, action); err != nil {
return err
}
return s.ClearCache(ctx)
}
func (s *casbinService) RemovePolicy(ctx context.Context, role, resource, action string) error {
if _, err := s.enforcer.RemovePolicy(role, resource, action); err != nil {
return err
}
return s.ClearCache(ctx)
}
func (s *casbinService) LoadPolicy(ctx context.Context) error {
return s.enforcer.LoadPolicy()
}
func (s *casbinService) ClearCache(ctx context.Context) error {
if s.cache != nil {
// 清除所有 casbin 相关缓存:策略缓存 + 用户角色缓存。
s.cache.DeleteByPrefix("casbin:")
}
return nil
}
// clearPolicyCache 失效所有策略缓存casbin:policy:*)。
//
// 策略变更或角色变更都应调用,确保下次 Enforce 重新查 Casbin。
func (s *casbinService) clearPolicyCache(ctx context.Context) error {
if s.cache != nil {
s.cache.DeleteByPrefix("casbin:policy:")
}
return nil
}
// clearUserRoleCache 仅失效某个用户的角色缓存casbin:user:roles:{userID})。
//
// 用于 AddRoleForUser/DeleteRoleForUser只刷新该用户的角色不波及策略缓存除非同时调 clearPolicyCache
// 同时失效认证管道的 Principal 缓存,让 RequireAuth 下次重新加载该用户的角色。
func (s *casbinService) clearUserRoleCache(ctx context.Context, userID string) error {
if s.cache != nil {
s.cache.Delete(fmt.Sprintf("casbin:user:roles:%s", userID))
s.cache.Delete(auth.PrincipalCacheKey(userID))
}
return nil
}
// clearUserCache Deprecated 兼容旧调用,内部转调 clearUserRoleCache。
func (s *casbinService) clearUserCache(ctx context.Context, userID string) error {
return s.clearUserRoleCache(ctx, userID)
}
func (s *casbinService) GetPermissionsForRole(ctx context.Context, role string) ([][]string, error) {
return s.enforcer.GetPermissionsForUser(role)
}
func (s *casbinService) UpdatePermissionsForRole(ctx context.Context, role string, permissions [][]string) error {
// 先删除该角色的所有权限
_, err := s.enforcer.DeletePermissionsForUser(role)
if err != nil {
return err
}
// 添加新权限
for _, perm := range permissions {
if len(perm) >= 2 {
resource := perm[0]
action := perm[1]
if _, err := s.enforcer.AddPolicy(role, resource, action); err != nil {
return err
}
}
}
// 清除缓存
return s.ClearCache(ctx)
}
func (s *casbinService) GetAllPermissions(ctx context.Context) [][]string {
permissions, _ := s.enforcer.GetPolicy()
return permissions
}