You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
OneAuth/auth/auth.go

975 lines
26 KiB
Go

1 week ago
//
// Copyright (C) 2024 veypi <i@veypi.com>
// 2025-02-14 16:08:06
// Distributed under terms of the MIT license.
//
package auth
import (
"context"
"errors"
1 week ago
"fmt"
"regexp"
1 week ago
"strings"
"time"
"github.com/veypi/vbase/cfg"
"github.com/veypi/vbase/libs/cache"
1 week ago
"github.com/veypi/vbase/models"
"github.com/veypi/vigo"
"github.com/veypi/vigo/contrib/event"
"gorm.io/gorm"
1 week ago
)
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
}
1 week ago
// Auth 权限管理接口
type Auth interface {
UserID(x *vigo.X) string
OrgID(x *vigo.X) string
// 加载组织信息 (中间件/手动调用)
LoadOrg(x *vigo.X) error
1 week ago
// ========== 中间件生成 ==========
// 基础权限检查
Perm(permissionID string) func(*vigo.X) error
1 week ago
// 特定资源权限检查 (自动从 Path/Query 获取资源ID)
PermOnResource(permissionID, resourceKey string) func(*vigo.X) error
1 week ago
// 满足任一权限
PermAny(permissionIDs ...string) func(*vigo.X) error
1 week ago
// 满足所有权限
PermAll(permissionIDs ...string) func(*vigo.X) error
// ========== 角色管理 ==========
// 添加角色定义
// policies 格式: "resource:action",例如 "user:read", "*:*"
AddRole(roleCode, roleName string, policies ...string) error
1 week ago
// ========== 权限管理 ==========
// 授予角色
1 week ago
GrantRole(ctx context.Context, userID, orgID, roleCode string) error
1 week ago
// 撤销角色
RevokeRole(ctx context.Context, userID, orgID, roleCode string) error
// 授予特定资源权限
1 week ago
GrantResourcePerm(ctx context.Context, userID, orgID, permissionID, resourceID string) error
1 week ago
// 撤销特定资源权限
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
1 week ago
// 列出用户权限
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, "普通用户",
"user:read",
"org:read",
"org:create",
"oauth-client:read",
"oauth-client:create",
"oauth-client:update",
"oauth-client:delete",
)
// 注册权限初始化回调到 cfg 包
// 这样 models.InitDB() 可以在合适的时机调用,避免循环依赖
event.Add("vb.init.auth", Factory.init)
}
1 week ago
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]
}
1 week ago
auth := &appAuth{
scope: scope,
roleDefs: make(map[string]roleDefinition),
policies: make(map[string][][2]string),
roleInitDone: make(map[string]bool),
}
f.apps[scope] = auth
1 week ago
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))
}
}
1 week ago
// Init 初始化所有注册的权限配置
// - 检查不同 app 之间是否有冲突
// - 同步 Permission 到数据库
// - 建立预设角色
func (f *authFactory) init() error {
1 week ago
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 单个权限域的权限管理
1 week ago
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
1 week ago
}
// 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 {
1 week ago
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 == "*" {
1 week ago
continue
}
permID := fmt.Sprintf("%s:%s:%s", a.scope, resource, action)
1 week ago
if _, exists := permMap[permID]; !exists {
permMap[permID] = models.Permission{
ID: permID,
Scope: a.scope,
1 week ago
Resource: resource,
Action: action,
Description: fmt.Sprintf("%s %s on %s", a.scope, action, resource),
1 week ago
}
}
}
}
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{}
}
1 week ago
// 查找或创建系统角色
var role models.Role
err := cfg.DB().Where("code = ? AND org_id IS NULL", roleDef.code).First(&role).Error
1 week ago
if err != nil {
// 创建新角色
role = models.Role{
OrgID: nil,
Code: roleDef.code,
Name: roleDef.name,
Description: roleDef.description,
1 week ago
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)
1 week ago
}
}
// 同步角色权限
hasWildcard := false
for _, policy := range policies {
resource, action := policy[0], policy[1]
// 处理通配符权限
if resource == "*" && action == "*" {
hasWildcard = true
1 week ago
continue
}
permID := fmt.Sprintf("%s:%s:%s", a.scope, resource, action)
1 week ago
// 检查关联是否存在
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
1 week ago
return nil
}
// ========== 中间件实现 ==========
func (a *appAuth) UserID(x *vigo.X) string {
return GetUserID(x)
}
1 week ago
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")
1 week ago
}
return vigo.ErrInternalServer.WithError(err)
1 week ago
}
x.Set(CtxKeyOrgID, orgID)
return nil
1 week ago
}
func (a *appAuth) Perm(permissionID string) func(*vigo.X) error {
validatePermissionID(permissionID)
1 week ago
return func(x *vigo.X) error {
userID := GetUserID(x)
1 week ago
if userID == "" {
return vigo.ErrUnauthorized
1 week ago
}
orgID := GetOrgID(x)
if err := a.checkPermission(x.Context(), userID, orgID, permissionID, ""); err != nil {
return err
}
return nil
1 week ago
}
}
// PermOnResource 检查当前用户对特定资源实例是否有指定权限 (ACL)
//
// 鉴权逻辑:
// 1. 全局权限检查: 如果用户拥有全局权限 (如 "user:*", "*:*"),直接通过。
// 2. 实例权限检查: 检查 user_permissions 表中是否有 (permissionID, resourceID) 的记录。
//
// 最佳实践:
// - 配合 GrantResourcePerm 使用: 在创建资源时,必须显式赋予创建者权限。
// - 适用于高价值、需共享的资源 (如 User, Org, Project)。
// - 对于私有/高频资源 (如 Order, Log),建议使用 Manual Check (在业务逻辑中直接检查 OwnerID)。
1 week ago
func (a *appAuth) PermOnResource(permissionID, resourceKey string) func(*vigo.X) error {
validatePermissionID(permissionID)
1 week ago
return func(x *vigo.X) error {
userID := GetUserID(x)
1 week ago
if userID == "" {
return vigo.ErrUnauthorized
1 week ago
}
orgID := GetOrgID(x)
1 week ago
// 尝试从 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 {
1 week ago
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)
}
1 week ago
return func(x *vigo.X) error {
userID := GetUserID(x)
1 week ago
if userID == "" {
return vigo.ErrUnauthorized
1 week ago
}
orgID := GetOrgID(x)
1 week ago
for _, pid := range permissionIDs {
if err := a.checkPermission(x.Context(), userID, orgID, pid, ""); err == nil {
1 week ago
return nil
}
}
return vigo.ErrNoPermission
1 week ago
}
}
func (a *appAuth) PermAll(permissionIDs ...string) func(*vigo.X) error {
for _, pid := range permissionIDs {
validatePermissionID(pid)
}
1 week ago
return func(x *vigo.X) error {
userID := GetUserID(x)
1 week ago
if userID == "" {
return vigo.ErrUnauthorized
1 week ago
}
orgID := GetOrgID(x)
1 week ago
for _, pid := range permissionIDs {
if err := a.checkPermission(x.Context(), userID, orgID, pid, ""); err != nil {
1 week ago
return err
}
}
return nil
}
}
// ========== 权限管理实现 ==========
1 week ago
func (a *appAuth) GrantRole(ctx context.Context, userID, orgID, roleCode string) error {
1 week ago
// 查找角色
var role models.Role
1 week ago
query := cfg.DB().Where("code = ?", roleCode)
if orgID != "" {
query = query.Where("org_id = ?", orgID)
1 week ago
} else {
query = query.Where("org_id IS NULL")
1 week ago
}
if err := query.First(&role).Error; err != nil {
1 week ago
// 如果指定了 OrgID 但没找到,尝试查找全局角色
1 week ago
if orgID != "" {
query = cfg.DB().Where("code = ? AND org_id IS NULL", roleCode)
1 week ago
if err := query.First(&role).Error; err != nil {
1 week ago
return fmt.Errorf("role not found: %s", roleCode)
1 week ago
}
} else {
1 week ago
return fmt.Errorf("role not found: %s", roleCode)
1 week ago
}
1 week ago
}
// 检查是否已存在
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)
1 week ago
if count > 0 {
return nil // 已存在
}
var orgIDPtr *string
if orgID != "" {
orgIDPtr = &orgID
}
1 week ago
userRole := models.UserRole{
1 week ago
UserID: userID,
OrgID: orgIDPtr,
1 week ago
RoleID: role.ID,
1 week ago
ExpireAt: nil, // 默认不过期
1 week ago
}
if err := cfg.DB().Create(&userRole).Error; err != nil {
return err
}
incUserPermVersion(userID)
return nil
1 week ago
}
func (a *appAuth) RevokeRole(ctx context.Context, userID, orgID, roleCode string) error {
var role models.Role
1 week ago
// 优先查找组织特定角色
1 week ago
query := cfg.DB().Where("code = ?", roleCode)
if orgID != "" {
query = query.Where("org_id = ?", orgID)
} else {
query = query.Where("org_id IS NULL")
1 week ago
}
if err := query.First(&role).Error; err != nil {
1 week ago
// 如果没找到,尝试查找全局角色
if orgID != "" {
if err := cfg.DB().Where("code = ? AND org_id IS NULL", roleCode).First(&role).Error; err != nil {
1 week ago
return nil // 角色不存在,无需撤销
}
} else {
return nil
}
1 week ago
}
// 构建删除条件
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
1 week ago
}
1 week ago
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)
1 week ago
}
1 week ago
// 检查权限是否存在
var perm models.Permission
1 week ago
if err := cfg.DB().Where("id = ?", permissionID).First(&perm).Error; err != nil {
return fmt.Errorf("permission not found: %s", permissionID)
1 week ago
}
// 检查是否已存在
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
1 week ago
if err == nil {
1 week ago
// 已存在
return nil
1 week ago
}
var orgIDPtr *string
if orgID != "" {
orgIDPtr = &orgID
}
1 week ago
userPerm := models.UserPermission{
1 week ago
UserID: userID,
OrgID: orgIDPtr,
1 week ago
PermissionID: permissionID,
ResourceID: resourceID,
ExpireAt: nil, // 默认不过期
GrantedBy: "", // 默认空
1 week ago
}
if err := cfg.DB().Create(&userPerm).Error; err != nil {
return err
}
incUserPermVersion(userID)
return nil
1 week ago
}
func (a *appAuth) RevokeResourcePerm(ctx context.Context, userID, orgID, permissionID, resourceID string) error {
1 week ago
if strings.Count(permissionID, ":") == 1 {
permissionID = fmt.Sprintf("%s:%s", a.scope, permissionID)
1 week ago
}
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
1 week ago
}
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 {
1 week ago
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 {
1 week ago
return err
}
incUserPermVersion(userID)
1 week ago
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 week ago
// 1. 检查用户是否有该权限的角色(包括当前组织角色和系统全局角色)
1 week ago
var roleIDs []string
1 week ago
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)
1 week ago
} else {
roleQuery = roleQuery.Where("org_id IS NULL")
1 week ago
}
if err := roleQuery.Pluck("role_id", &roleIDs).Error; err != nil {
1 week ago
return false, err
}
if len(roleIDs) > 0 {
1 week ago
// 构造可能的通配符权限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))
1 week ago
}
1 week ago
// 检查这些角色是否有所需权限
var count int64
if err := cfg.DB().Model(&models.RolePermission{}).
1 week ago
Where("role_id IN ? AND permission_id IN ?", roleIDs, permsToCheck).
1 week ago
Count(&count).Error; err != nil {
return false, err
}
if count > 0 {
return true, nil
}
}
// 2. 检查用户是否有特定的资源权限
1 week ago
// 构造可能的通配符权限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))
1 week ago
}
1 week ago
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")
}
1 week ago
1 week ago
if resourceID != "" {
query = query.Where("resource_id = ? OR resource_id = '*'", resourceID)
} else {
query = query.Where("resource_id = '*'")
1 week ago
}
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).
1 week ago
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)
}