// // Copyright (C) 2024 veypi // 2025-02-14 16:08:06 // Distributed under terms of the MIT license. // package auth import ( "context" "errors" "fmt" "regexp" "strings" "time" "github.com/veypi/vbase/cfg" "github.com/veypi/vbase/libs/cache" "github.com/veypi/vbase/models" "github.com/veypi/vigo" "github.com/veypi/vigo/contrib/event" "gorm.io/gorm" ) const ( // CtxKeyUserID 用户ID上下文键 CtxKeyUserID = "auth:user_id" // CtxKeyOrgID 组织ID上下文键 CtxKeyOrgID = "auth:org_id" // CtxKeyOrgRoles 组织角色上下文键 CtxKeyOrgRoles = "auth:org_roles" // RoleCodeAdmin 管理员角色代码 RoleCodeAdmin = "admin" // RoleCodeUser 普通用户角色代码 RoleCodeUser = "user" ) // ========== 辅助函数 ========== func GetUserID(x *vigo.X) string { if userID, ok := x.Get(CtxKeyUserID).(string); ok { return userID } return "" } func GetOrgID(x *vigo.X) string { if orgID, ok := x.Get(CtxKeyOrgID).(string); ok { return orgID } return "" } func GetOrgRoles(x *vigo.X) []string { if roles, ok := x.Get(CtxKeyOrgRoles).([]string); ok { return roles } return nil } // Auth 权限管理接口 type Auth interface { UserID(x *vigo.X) string OrgID(x *vigo.X) string // 加载组织信息 (中间件/手动调用) LoadOrg(x *vigo.X) error // ========== 中间件生成 ========== // 基础权限检查 Perm(permissionID string) func(*vigo.X) error // 特定资源权限检查 (自动从 Path/Query 获取资源ID) PermOnResource(permissionID, resourceKey string) func(*vigo.X) error // 满足任一权限 PermAny(permissionIDs ...string) func(*vigo.X) error // 满足所有权限 PermAll(permissionIDs ...string) func(*vigo.X) error // ========== 角色管理 ========== // 添加角色定义 // policies 格式: "resource:action",例如 "user:read", "*:*" AddRole(roleCode, roleName string, policies ...string) error // ========== 权限管理 ========== // 授予角色 GrantRole(ctx context.Context, userID, orgID, roleCode string) error // 撤销角色 RevokeRole(ctx context.Context, userID, orgID, roleCode string) error // 授予特定资源权限 GrantResourcePerm(ctx context.Context, userID, orgID, permissionID, resourceID string) error // 撤销特定资源权限 RevokeResourcePerm(ctx context.Context, userID, orgID, permissionID, resourceID string) error // 撤销用户所有权限 RevokeAll(ctx context.Context, userID, orgID string) error // ========== 权限查询 ========== // 检查权限 CheckPermission(ctx context.Context, userID, orgID, permissionID, resourceID string) bool CheckPerm(ctx context.Context, userID, orgID, permissionID, resourceID string) bool // 列出用户权限 ListUserPermissions(ctx context.Context, userID, orgID string) ([]models.UserPermissionResult, error) // 列出资源授权用户 ListResourceUsers(ctx context.Context, orgID, permissionID, resourceID string) ([]models.ResourceUser, error) } // 全局 Auth 工厂 var Factory = &authFactory{ apps: make(map[string]*appAuth), } // VBaseAuth vbase 自身的权限管理实例 // 由 vbase 包在初始化时注入 var VBaseAuth = Factory.New("vb") func init() { // 为 VBaseAuth 添加默认角色 VBaseAuth.AddRole(RoleCodeAdmin, "管理员", "*:*") VBaseAuth.AddRole(RoleCodeUser, "普通用户", "org:create", "org:read", "oauth-client:read", "oauth-client:create", "oauth-client:update", "oauth-client:delete", ) // 注册权限初始化回调到 cfg 包 // 这样 models.InitDB() 可以在合适的时机调用,避免循环依赖 event.Add("vb.init.auth", Factory.init) } type authFactory struct { apps map[string]*appAuth // appKey -> auth实例 } // New 创建权限管理实例(注册权限域) func (f *authFactory) New(scope string) Auth { if _, exists := f.apps[scope]; exists { return f.apps[scope] } auth := &appAuth{ scope: scope, roleDefs: make(map[string]roleDefinition), policies: make(map[string][][2]string), roleInitDone: make(map[string]bool), } f.apps[scope] = auth return auth } var validResourceRegex = regexp.MustCompile(`^[a-zA-Z][a-zA-Z0-9_-]*$`) func validatePermissionID(permissionID string) { if permissionID == "*:*" { return } parts := strings.Split(permissionID, ":") // 允许 app:resource:action 或 resource:action 格式 // 如果是 app:resource:action,则 parts 长度为 3 // 如果是 resource:action,则 parts 长度为 2 if len(parts) != 2 && len(parts) != 3 { panic(fmt.Sprintf("invalid permission format: %s, expected 'resource:action' or 'app:resource:action'", permissionID)) } resource := parts[len(parts)-2] if !validResourceRegex.MatchString(resource) { panic(fmt.Sprintf("invalid resource identifier: %s, must start with letter and contain only letters, numbers, '-' or '_'", resource)) } } // Init 初始化所有注册的权限配置 // - 检查不同 app 之间是否有冲突 // - 同步 Permission 到数据库 // - 建立预设角色 func (f *authFactory) init() error { for appKey, auth := range f.apps { if err := auth.init(); err != nil { return fmt.Errorf("failed to init auth for %s: %w", appKey, err) } } return nil } // roleDefinition 角色定义(内部使用) type roleDefinition struct { code string name string description string policies []string // 权限列表: ["resource:action", "*:*"] } // appAuth 单个权限域的权限管理 type appAuth struct { scope string // 权限域标识 roleDefs map[string]roleDefinition // roleCode -> role definition policies map[string][][2]string // roleCode -> list of [resource, action] pairs roleInitDone map[string]bool // roleCode -> whether role is initialized in DB } // AddRole 添加角色定义 // policies 格式: "resource:action",例如 "user:read", "*:*" func (a *appAuth) AddRole(roleCode, roleName string, policies ...string) error { if roleCode == "" || roleName == "" { return fmt.Errorf("role code and name cannot be empty") } // 解析并验证权限格式 parsedPolicies := make([][2]string, 0, len(policies)) for _, policy := range policies { // 严格检查格式: resource:action parts := strings.Split(policy, ":") if len(parts) != 2 { return fmt.Errorf("invalid policy format: %s, expected 'resource:action'", policy) } resource, action := parts[0], parts[1] // 验证 resource 和 action 不为空 if resource == "" || action == "" { return fmt.Errorf("resource and action cannot be empty in policy: %s", policy) } // 验证 resource 格式(如果不是通配符) if resource != "*" { if !validResourceRegex.MatchString(resource) { return fmt.Errorf("invalid resource identifier: %s in policy: %s, must start with letter and contain only letters, numbers, '-' or '_'", resource, policy) } } // 验证 action 格式(如果不是通配符) if action != "*" { if !validResourceRegex.MatchString(action) { return fmt.Errorf("invalid action identifier: %s in policy: %s, must start with letter and contain only letters, numbers, '-' or '_'", action, policy) } } parsedPolicies = append(parsedPolicies, [2]string{resource, action}) } // 存储角色定义 a.roleDefs[roleCode] = roleDefinition{ code: roleCode, name: roleName, } a.policies[roleCode] = parsedPolicies // 如果已经初始化过,立即同步到数据库 if len(a.roleInitDone) > 0 { return a.initRole(roleCode) } return nil } // init 初始化应用的权限配置 func (a *appAuth) init() error { // 1. 同步权限定义到数据库 for _, permDef := range a.extractPermissions() { var perm models.Permission err := cfg.DB().Where("id = ?", permDef.ID).First(&perm).Error if err != nil { // 不存在则创建 perm = permDef if err := cfg.DB().Create(&perm).Error; err != nil { return fmt.Errorf("failed to create permission %s: %w", permDef.ID, err) } } } // 2. 创建系统预设角色(跳过 _app_info) for roleCode := range a.roleDefs { if roleCode == "_app_info" { continue } if err := a.initRole(roleCode); err != nil { return err } } return nil } // extractPermissions 从角色定义中提取所有权限 func (a *appAuth) extractPermissions() []models.Permission { permMap := make(map[string]models.Permission) for roleCode, policies := range a.policies { if roleCode == "_app_info" { continue } for _, policy := range policies { resource, action := policy[0], policy[1] // 跳过通配符权限的特殊处理 if resource == "*" && action == "*" { continue } permID := fmt.Sprintf("%s:%s:%s", a.scope, resource, action) if _, exists := permMap[permID]; !exists { permMap[permID] = models.Permission{ ID: permID, Scope: a.scope, Resource: resource, Action: action, Description: fmt.Sprintf("%s %s on %s", a.scope, action, resource), } } } } result := make([]models.Permission, 0, len(permMap)) for _, perm := range permMap { result = append(result, perm) } return result } // initRole 初始化系统预设角色 func (a *appAuth) initRole(roleCode string) error { roleDef, exists := a.roleDefs[roleCode] if !exists { return fmt.Errorf("role not found: %s", roleCode) } policies, hasPolicies := a.policies[roleCode] if !hasPolicies { policies = [][2]string{} } // 查找或创建系统角色 var role models.Role err := cfg.DB().Where("code = ? AND org_id IS NULL", roleDef.code).First(&role).Error if err != nil { // 创建新角色 role = models.Role{ OrgID: nil, Code: roleDef.code, Name: roleDef.name, Description: roleDef.description, IsSystem: true, Status: 1, } if err := cfg.DB().Create(&role).Error; err != nil { return fmt.Errorf("failed to create role %s: %w", roleDef.code, err) } } // 同步角色权限 hasWildcard := false for _, policy := range policies { resource, action := policy[0], policy[1] // 处理通配符权限 if resource == "*" && action == "*" { hasWildcard = true continue } permID := fmt.Sprintf("%s:%s:%s", a.scope, resource, action) // 检查关联是否存在 var count int64 cfg.DB().Model(&models.RolePermission{}). Where("role_id = ? AND permission_id = ?", role.ID, permID). Count(&count) if count == 0 { rp := models.RolePermission{ RoleID: role.ID, PermissionID: permID, Condition: "none", } if err := cfg.DB().Create(&rp).Error; err != nil { return fmt.Errorf("failed to create role permission: %w", err) } } } // 为通配符权限创建记录 if hasWildcard { wildcardPermID := fmt.Sprintf("%s:*:*", a.scope) // 先确保通配符 permission 存在 var perm models.Permission err := cfg.DB().Where("id = ?", wildcardPermID).First(&perm).Error if err != nil { // 创建通配符 permission perm = models.Permission{ ID: wildcardPermID, Scope: a.scope, Resource: "*", Action: "*", Description: fmt.Sprintf("%s wildcard permission", a.scope), } if err := cfg.DB().Create(&perm).Error; err != nil { return fmt.Errorf("failed to create wildcard permission: %w", err) } } // 创建 role_permission 关联 var count int64 cfg.DB().Model(&models.RolePermission{}). Where("role_id = ? AND permission_id = ?", role.ID, wildcardPermID). Count(&count) if count == 0 { rp := models.RolePermission{ RoleID: role.ID, PermissionID: wildcardPermID, Condition: "none", } if err := cfg.DB().Create(&rp).Error; err != nil { return fmt.Errorf("failed to create wildcard role permission: %w", err) } } } a.roleInitDone[roleCode] = true return nil } // ========== 中间件实现 ========== func (a *appAuth) UserID(x *vigo.X) string { return GetUserID(x) } func (a *appAuth) OrgID(x *vigo.X) string { return GetOrgID(x) } func (a *appAuth) LoadOrg(x *vigo.X) error { orgID := x.Request.Header.Get("X-Org-ID") if orgID == "" { orgID = x.Request.URL.Query().Get("org_id") } if orgID == "" { orgID = x.PathParams.Get("org_id") } if orgID == "" { // 没有指定组织 return vigo.ErrInvalidArg.WithString("missing org_id") } userID := GetUserID(x) if userID == "" { return vigo.ErrUnauthorized } // 检查用户是否为组织成员 var member models.OrgMember err := cfg.DB().Where("user_id = ? AND org_id = ? AND status = ?", userID, orgID, models.MemberStatusActive).First(&member).Error if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return vigo.ErrForbidden.WithString("not a member of this organization") } return vigo.ErrInternalServer.WithError(err) } x.Set(CtxKeyOrgID, orgID) return nil } func (a *appAuth) Perm(permissionID string) func(*vigo.X) error { validatePermissionID(permissionID) return func(x *vigo.X) error { userID := GetUserID(x) if userID == "" { return vigo.ErrUnauthorized } orgID := GetOrgID(x) if err := a.checkPermission(x.Context(), userID, orgID, permissionID, ""); err != nil { return err } return nil } } // PermOnResource 检查当前用户对特定资源实例是否有指定权限 (ACL) // // 鉴权逻辑: // 1. 全局权限检查: 如果用户拥有全局权限 (如 "user:*", "*:*"),直接通过。 // 2. 实例权限检查: 检查 user_permissions 表中是否有 (permissionID, resourceID) 的记录。 // // 最佳实践: // - 配合 GrantResourcePerm 使用: 在创建资源时,必须显式赋予创建者权限。 // - 适用于高价值、需共享的资源 (如 User, Org, Project)。 // - 对于私有/高频资源 (如 Order, Log),建议使用 Manual Check (在业务逻辑中直接检查 OwnerID)。 func (a *appAuth) PermOnResource(permissionID, resourceKey string) func(*vigo.X) error { validatePermissionID(permissionID) return func(x *vigo.X) error { userID := GetUserID(x) if userID == "" { return vigo.ErrUnauthorized } orgID := GetOrgID(x) // 尝试从 PathParams 获取 resourceID := x.PathParams.Get(resourceKey) if resourceID == "" { // 尝试从 Query 获取 resourceID = x.Request.URL.Query().Get(resourceKey) } if err := a.checkPermission(x.Context(), userID, orgID, permissionID, resourceID); err != nil { return err } return nil } } // 内部辅助检查方法,返回 error 以便于统一处理错误响应 func (a *appAuth) checkPermission(ctx context.Context, userID, orgID, permissionID, resourceID string) error { ok, err := a.checkPermissionDB(ctx, userID, orgID, permissionID, resourceID) if err != nil { return vigo.ErrInternalServer.WithError(err) } if !ok { return vigo.ErrForbidden } return nil } func (a *appAuth) PermAny(permissionIDs ...string) func(*vigo.X) error { for _, pid := range permissionIDs { validatePermissionID(pid) } return func(x *vigo.X) error { userID := GetUserID(x) if userID == "" { return vigo.ErrUnauthorized } orgID := GetOrgID(x) for _, pid := range permissionIDs { if err := a.checkPermission(x.Context(), userID, orgID, pid, ""); err == nil { return nil } } return vigo.ErrNoPermission } } func (a *appAuth) PermAll(permissionIDs ...string) func(*vigo.X) error { for _, pid := range permissionIDs { validatePermissionID(pid) } return func(x *vigo.X) error { userID := GetUserID(x) if userID == "" { return vigo.ErrUnauthorized } orgID := GetOrgID(x) for _, pid := range permissionIDs { if err := a.checkPermission(x.Context(), userID, orgID, pid, ""); err != nil { return err } } return nil } } // ========== 权限管理实现 ========== func (a *appAuth) GrantRole(ctx context.Context, userID, orgID, roleCode string) error { // 查找角色 var role models.Role query := cfg.DB().Where("code = ?", roleCode) if orgID != "" { query = query.Where("org_id = ?", orgID) } else { query = query.Where("org_id IS NULL") } if err := query.First(&role).Error; err != nil { // 如果指定了 OrgID 但没找到,尝试查找全局角色 if orgID != "" { query = cfg.DB().Where("code = ? AND org_id IS NULL", roleCode) if err := query.First(&role).Error; err != nil { return fmt.Errorf("role not found: %s", roleCode) } } else { return fmt.Errorf("role not found: %s", roleCode) } } // 检查是否已存在 var count int64 roleQuery := cfg.DB().Model(&models.UserRole{}). Where("user_id = ? AND role_id = ?", userID, role.ID) if orgID != "" { roleQuery = roleQuery.Where("org_id = ?", orgID) } else { roleQuery = roleQuery.Where("org_id IS NULL") } roleQuery.Count(&count) if count > 0 { return nil // 已存在 } var orgIDPtr *string if orgID != "" { orgIDPtr = &orgID } userRole := models.UserRole{ UserID: userID, OrgID: orgIDPtr, RoleID: role.ID, ExpireAt: nil, // 默认不过期 } if err := cfg.DB().Create(&userRole).Error; err != nil { return err } incUserPermVersion(userID) return nil } func (a *appAuth) RevokeRole(ctx context.Context, userID, orgID, roleCode string) error { var role models.Role // 优先查找组织特定角色 query := cfg.DB().Where("code = ?", roleCode) if orgID != "" { query = query.Where("org_id = ?", orgID) } else { query = query.Where("org_id IS NULL") } if err := query.First(&role).Error; err != nil { // 如果没找到,尝试查找全局角色 if orgID != "" { if err := cfg.DB().Where("code = ? AND org_id IS NULL", roleCode).First(&role).Error; err != nil { return nil // 角色不存在,无需撤销 } } else { return nil } } // 构建删除条件 deleteQuery := cfg.DB().Where("user_id = ? AND role_id = ?", userID, role.ID) if orgID != "" { deleteQuery = deleteQuery.Where("org_id = ?", orgID) } else { deleteQuery = deleteQuery.Where("org_id IS NULL") } if err := deleteQuery.Delete(&models.UserRole{}).Error; err != nil { return err } incUserPermVersion(userID) return nil } func (a *appAuth) GrantResourcePerm(ctx context.Context, userID, orgID, permissionID, resourceID string) error { if strings.Count(permissionID, ":") == 1 { permissionID = fmt.Sprintf("%s:%s", a.scope, permissionID) } // 检查权限是否存在 var perm models.Permission if err := cfg.DB().Where("id = ?", permissionID).First(&perm).Error; err != nil { return fmt.Errorf("permission not found: %s", permissionID) } // 检查是否已存在 var existing models.UserPermission query := cfg.DB().Where("user_id = ? AND permission_id = ? AND resource_id = ?", userID, permissionID, resourceID) if orgID != "" { query = query.Where("org_id = ?", orgID) } else { query = query.Where("org_id IS NULL") } err := query.First(&existing).Error if err == nil { // 已存在 return nil } var orgIDPtr *string if orgID != "" { orgIDPtr = &orgID } userPerm := models.UserPermission{ UserID: userID, OrgID: orgIDPtr, PermissionID: permissionID, ResourceID: resourceID, ExpireAt: nil, // 默认不过期 GrantedBy: "", // 默认空 } if err := cfg.DB().Create(&userPerm).Error; err != nil { return err } incUserPermVersion(userID) return nil } func (a *appAuth) RevokeResourcePerm(ctx context.Context, userID, orgID, permissionID, resourceID string) error { if strings.Count(permissionID, ":") == 1 { permissionID = fmt.Sprintf("%s:%s", a.scope, permissionID) } query := cfg.DB().Where("user_id = ? AND permission_id = ? AND resource_id = ?", userID, permissionID, resourceID) if orgID != "" { query = query.Where("org_id = ?", orgID) } else { query = query.Where("org_id IS NULL") } if err := query.Delete(&models.UserPermission{}).Error; err != nil { return err } incUserPermVersion(userID) return nil } func (a *appAuth) RevokeAll(ctx context.Context, userID, orgID string) error { // 删除用户角色 roleQuery := cfg.DB().Where("user_id = ?", userID) if orgID != "" { roleQuery = roleQuery.Where("org_id = ?", orgID) } else { roleQuery = roleQuery.Where("org_id IS NULL") } if err := roleQuery.Delete(&models.UserRole{}).Error; err != nil { return err } // 删除用户特定权限 permQuery := cfg.DB().Where("user_id = ?", userID) if orgID != "" { permQuery = permQuery.Where("org_id = ?", orgID) } else { permQuery = permQuery.Where("org_id IS NULL") } if err := permQuery.Delete(&models.UserPermission{}).Error; err != nil { return err } incUserPermVersion(userID) return nil } // ========== 权限查询实现 ========== func (a *appAuth) CheckPermission(ctx context.Context, userID, orgID, permissionID, resourceID string) bool { ok, _ := a.checkPermissionDB(ctx, userID, orgID, permissionID, resourceID) return ok } func (a *appAuth) CheckPerm(ctx context.Context, userID, orgID, permissionID, resourceID string) bool { return a.CheckPermission(ctx, userID, orgID, permissionID, resourceID) } func (a *appAuth) checkPermissionDB(ctx context.Context, userID, orgID, permissionID, resourceID string) (bool, error) { // 1. 检查用户是否有该权限的角色(包括当前组织角色和系统全局角色) var roleIDs []string roleQuery := cfg.DB().Model(&models.UserRole{}). Where("user_id = ? AND (expire_at IS NULL OR expire_at > ?)", userID, time.Now()) if orgID != "" { roleQuery = roleQuery.Where("org_id = ? OR org_id IS NULL", orgID) } else { roleQuery = roleQuery.Where("org_id IS NULL") } if err := roleQuery.Pluck("role_id", &roleIDs).Error; err != nil { return false, err } if len(roleIDs) > 0 { // 构造可能的通配符权限ID permsToCheck := []string{permissionID} parts := strings.Split(permissionID, ":") if len(parts) == 3 { // app:resource:* permsToCheck = append(permsToCheck, fmt.Sprintf("%s:%s:*", parts[0], parts[1])) // app:*:* permsToCheck = append(permsToCheck, fmt.Sprintf("%s:*:*", parts[0])) } else if len(parts) == 2 { // resource:action -> appKey:resource:action fullPermID := fmt.Sprintf("%s:%s", a.scope, permissionID) permsToCheck = append(permsToCheck, fullPermID) permsToCheck = append(permsToCheck, fmt.Sprintf("%s:%s:*", a.scope, parts[0])) permsToCheck = append(permsToCheck, fmt.Sprintf("%s:*:*", a.scope)) } // 检查这些角色是否有所需权限 var count int64 if err := cfg.DB().Model(&models.RolePermission{}). Where("role_id IN ? AND permission_id IN ?", roleIDs, permsToCheck). Count(&count).Error; err != nil { return false, err } if count > 0 { return true, nil } } // 2. 检查用户是否有特定的资源权限 // 构造可能的通配符权限ID (同上) permsToCheck := []string{permissionID} parts := strings.Split(permissionID, ":") if len(parts) == 3 { permsToCheck = append(permsToCheck, fmt.Sprintf("%s:%s:*", parts[0], parts[1])) permsToCheck = append(permsToCheck, fmt.Sprintf("%s:*:*", parts[0])) } else if len(parts) == 2 { // resource:action -> appKey:resource:action fullPermID := fmt.Sprintf("%s:%s", a.scope, permissionID) permsToCheck = append(permsToCheck, fullPermID) permsToCheck = append(permsToCheck, fmt.Sprintf("%s:%s:*", a.scope, parts[0])) permsToCheck = append(permsToCheck, fmt.Sprintf("%s:*:*", a.scope)) } var userPermCount int64 query := cfg.DB().Model(&models.UserPermission{}). Where("user_id = ? AND permission_id IN ? AND (expire_at IS NULL OR expire_at > ?)", userID, permsToCheck, time.Now()) if orgID != "" { query = query.Where("org_id = ?", orgID) } else { query = query.Where("org_id IS NULL") } if resourceID != "" { query = query.Where("resource_id = ? OR resource_id = '*'", resourceID) } else { query = query.Where("resource_id = '*'") } if err := query.Count(&userPermCount).Error; err != nil { return false, err } return userPermCount > 0, nil } func (a *appAuth) ListUserPermissions(ctx context.Context, userID, orgID string) ([]models.UserPermissionResult, error) { result := make([]models.UserPermissionResult, 0) // 1. 获取用户角色对应的权限 var roleIDs []string if err := cfg.DB().Model(&models.UserRole{}). Where("user_id = ? AND org_id = ? AND (expire_at IS NULL OR expire_at > ?)", userID, orgID, time.Now()). Pluck("role_id", &roleIDs).Error; err != nil { return nil, err } if len(roleIDs) > 0 { var permIDs []string if err := cfg.DB().Model(&models.RolePermission{}). Where("role_id IN ?", roleIDs). Pluck("permission_id", &permIDs).Error; err != nil { return nil, err } for _, permID := range permIDs { result = append(result, models.UserPermissionResult{ PermissionID: permID, ResourceID: "*", Actions: []string{"*"}, }) } } // 2. 获取用户特定资源权限 var userPerms []models.UserPermission if err := cfg.DB().Where("user_id = ? AND org_id = ? AND (expire_at IS NULL OR expire_at > ?)", userID, orgID, time.Now()). Find(&userPerms).Error; err != nil { return nil, err } for _, up := range userPerms { result = append(result, models.UserPermissionResult{ PermissionID: up.PermissionID, ResourceID: up.ResourceID, Actions: []string{"*"}, }) } return result, nil } func (a *appAuth) ListResourceUsers(ctx context.Context, orgID, permissionID, resourceID string) ([]models.ResourceUser, error) { result := make([]models.ResourceUser, 0) // 查询有该资源权限的用户 var userPerms []models.UserPermission query := cfg.DB().Where("org_id = ? AND permission_id = ?", orgID, permissionID) if resourceID != "" { query = query.Where("resource_id = ? OR resource_id = '*'", resourceID) } if err := query.Find(&userPerms).Error; err != nil { return nil, err } userMap := make(map[string][]string) for _, up := range userPerms { userMap[up.UserID] = append(userMap[up.UserID], "*") } for userID, actions := range userMap { result = append(result, models.ResourceUser{ UserID: userID, Actions: actions, }) } return result, nil } // ========== 辅助方法 ========== func (a *appAuth) isAdmin(ctx context.Context, userID, orgID string) (bool, error) { // 检查用户是否有管理员角色 var adminRoleIDs []string if err := cfg.DB().Model(&models.Role{}). Where("code = ?", RoleCodeAdmin). Pluck("id", &adminRoleIDs).Error; err != nil { return false, err } if len(adminRoleIDs) == 0 { return false, nil } var count int64 if err := cfg.DB().Model(&models.UserRole{}). Where("user_id = ? AND org_id = ? AND role_id IN ?", userID, orgID, adminRoleIDs). Count(&count).Error; err != nil { return false, err } return count > 0, nil } // ========== Cache Helpers ========== func getUserPermVersion(userID string) string { if !cache.IsEnabled() { return "0" } key := fmt.Sprintf("auth:user_ver:%s", userID) ver, err := cache.Client.Get(cache.Ctx, key).Result() if err != nil { return "0" } return ver } func incUserPermVersion(userID string) { if !cache.IsEnabled() { return } key := fmt.Sprintf("auth:user_ver:%s", userID) cache.Client.Incr(cache.Ctx, key) }