feat(auth): add Casbin RBAC and user activity tracking
Integrate Casbin for role-based access control with admin routes for role management. Add user activity logging service to track login and refresh events. Refactor audit service to use AI-based content moderation.
This commit is contained in:
225
internal/service/casbin_service.go
Normal file
225
internal/service/casbin_service.go
Normal file
@@ -0,0 +1,225 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"carrot_bbs/internal/cache"
|
||||
"carrot_bbs/internal/config"
|
||||
"carrot_bbs/internal/repository"
|
||||
|
||||
"github.com/casbin/casbin/v2"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 检查缓存
|
||||
if s.cache != nil {
|
||||
cacheKey := fmt.Sprintf("casbin:check:%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:check:%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 {
|
||||
// 添加到数据库
|
||||
if err := s.roleRepo.AddRoleForUser(ctx, userID, role); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 添加到 Casbin
|
||||
if _, err := s.enforcer.AddRoleForUser(userID, role); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 清除缓存
|
||||
return s.clearUserCache(ctx, userID)
|
||||
}
|
||||
|
||||
func (s *casbinService) DeleteRoleForUser(ctx context.Context, userID, role string) error {
|
||||
// 从数据库删除
|
||||
if err := s.roleRepo.DeleteRoleForUser(ctx, userID, role); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 从 Casbin 删除
|
||||
if _, err := s.enforcer.DeleteRoleForUser(userID, role); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 清除缓存
|
||||
return s.clearUserCache(ctx, userID)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (s *casbinService) clearUserCache(ctx context.Context, userID string) error {
|
||||
if s.cache != nil {
|
||||
cacheKey := fmt.Sprintf("casbin:user:roles:%s", userID)
|
||||
s.cache.Delete(cacheKey)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user