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

1069 lines
29 KiB
Go

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

//
// 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"
"fmt"
"regexp"
"strings"
"time"
"github.com/veypi/vbase/cfg"
"github.com/veypi/vbase/libs/cache"
"github.com/veypi/vbase/libs/jwt"
"github.com/veypi/vbase/models"
"github.com/veypi/vigo"
vigoauth "github.com/veypi/vigo/contrib/auth"
"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"
)
// ========== Token 提取 ==========
// extractToken 从 Header 或 Query 中提取 JWT token
func extractToken(x *vigo.X) string {
auth := x.Request.Header.Get("Authorization")
if auth != "" {
if len(auth) > 7 && strings.HasPrefix(auth, "Bearer ") {
return auth[7:]
}
}
return x.Request.URL.Query().Get("access_token")
}
// getOrgID 从请求中提取组织ID (Header/Query/Path)
func getOrgID(x *vigo.X) string {
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")
}
return orgID
}
// Auth 权限管理接口 (继承 Vigo auth.Auth 并扩展 LoadOrg)
// 注意appAuth 同时实现了此接口和 vigoauth.Auth 接口
type Auth interface {
vigoauth.Auth
// 加载组织信息 (中间件/手动调用)
LoadOrg(x *vigo.X) error
// 检查权限 (兼容旧接口)
CheckPermission(ctx context.Context, userID, orgID, permissionID, resourceID string) bool
}
// 全局 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
}
// GetRole 获取角色定义
func (a *appAuth) GetRole(roleCode string) (*vigoauth.Role, error) {
roleDef, exists := a.roleDefs[roleCode]
if !exists {
return nil, fmt.Errorf("role not found: %s", roleCode)
}
// 从数据库获取完整角色信息
var role models.Role
err := cfg.DB().Where("code = ? AND org_id IS NULL", roleCode).First(&role).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, fmt.Errorf("role not found: %s", roleCode)
}
return nil, err
}
// 转换策略为 Vigo 格式
policies := make([]string, 0)
if rolePolicies, ok := a.policies[roleCode]; ok {
for _, p := range rolePolicies {
policies = append(policies, fmt.Sprintf("%s:%s", p[0], p[1]))
}
}
return &vigoauth.Role{
Code: roleDef.code,
Name: roleDef.name,
Policies: policies,
Description: roleDef.description,
}, nil
}
// ListRoles 列出所有角色定义
func (a *appAuth) ListRoles() ([]*vigoauth.Role, error) {
result := make([]*vigoauth.Role, 0)
for code, roleDef := range a.roleDefs {
if code == "_app_info" {
continue
}
policies := make([]string, 0)
if rolePolicies, ok := a.policies[code]; ok {
for _, p := range rolePolicies {
policies = append(policies, fmt.Sprintf("%s:%s", p[0], p[1]))
}
}
result = append(result, &vigoauth.Role{
Code: roleDef.code,
Name: roleDef.name,
Policies: policies,
Description: roleDef.description,
})
}
return result, 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
}
// ========== 中间件实现 ==========
// PermLogin JWT 认证中间件
// 解析 token、验证黑名单、设置 CtxKeyUserID
func (a *appAuth) PermLogin(x *vigo.X) error {
// 1. 提取 token
tokenString := extractToken(x)
if tokenString == "" {
return vigo.ErrUnauthorized.WithString("missing token")
}
// 2. 解析 token
claims, err := jwt.ParseToken(tokenString)
if err != nil {
if err == jwt.ErrExpiredToken {
return vigo.ErrTokenExpired
}
return vigo.ErrTokenInvalid
}
// 3. 检查 token 黑名单
if cache.IsEnabled() {
blacklisted, _ := cache.IsTokenBlacklisted(claims.ID)
if blacklisted {
return vigo.ErrUnauthorized.WithString("token has been revoked")
}
}
// 4. 设置用户ID到上下文
x.Set(CtxKeyUserID, claims.UserID)
return nil
}
func (a *appAuth) UserID(x *vigo.X) string {
if userID, ok := x.Get(CtxKeyUserID).(string); ok {
return userID
}
return ""
}
func (a *appAuth) OrgID(x *vigo.X) string {
if orgID, ok := x.Get(CtxKeyOrgID).(string); ok {
return orgID
}
return ""
}
// LoadOrg 加载组织信息
func (a *appAuth) LoadOrg(x *vigo.X) error {
orgID := getOrgID(x)
if orgID == "" {
return vigo.ErrInvalidArg.WithString("missing org_id")
}
userID := a.UserID(x)
if userID == "" {
return vigo.ErrUnauthorized
}
// 检查是否是管理员(拥有 *:* 权限),管理员可以访问所有组织
isAdmin := a.CheckPerm(x.Context(), userID, "", "vb:*:*", "")
if isAdmin {
x.Set(CtxKeyOrgID, orgID)
return nil
}
// 检查用户是否为组织成员
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 := a.UserID(x)
if userID == "" {
return vigo.ErrUnauthorized
}
if err := a.checkPermission(x.Context(), userID, "", 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 := a.UserID(x)
if userID == "" {
return vigo.ErrUnauthorized
}
orgID := a.OrgID(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 := a.UserID(x)
if userID == "" {
return vigo.ErrUnauthorized
}
orgID := a.OrgID(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 := a.UserID(x)
if userID == "" {
return vigo.ErrUnauthorized
}
orgID := a.OrgID(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
}
// GrantRoles 批量授予角色
func (a *appAuth) GrantRoles(ctx context.Context, userID, orgID string, roleCodes ...string) error {
for _, roleCode := range roleCodes {
if err := a.GrantRole(ctx, userID, orgID, roleCode); err != nil {
return err
}
}
return nil
}
// ListUserRoles 查询用户的角色列表
func (a *appAuth) ListUserRoles(ctx context.Context, userID, orgID string) ([]string, error) {
var roleIDs []string
query := cfg.DB().Model(&models.UserRole{}).
Where("user_id = ?", userID)
if orgID != "" {
query = query.Where("org_id = ? OR org_id IS NULL", orgID)
} else {
query = query.Where("org_id IS NULL")
}
if err := query.Pluck("role_id", &roleIDs).Error; err != nil {
return nil, err
}
if len(roleIDs) == 0 {
return []string{}, nil
}
// 获取角色代码
var roles []models.Role
if err := cfg.DB().Where("id IN ?", roleIDs).Pluck("code", &roles).Error; err != nil {
return nil, err
}
codes := make([]string, 0, len(roles))
for _, role := range roles {
codes = append(codes, role.Code)
}
return codes, 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
}
// RevokeRoles 批量撤销角色
func (a *appAuth) RevokeRoles(ctx context.Context, userID, orgID string, roleCodes ...string) error {
for _, roleCode := range roleCodes {
if err := a.RevokeRole(ctx, userID, orgID, roleCode); err != nil {
return err
}
}
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) ([]*vigoauth.UserPermission, error) {
result := make([]*vigoauth.UserPermission, 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 {
parts := strings.Split(permID, ":")
resource := "*"
if len(parts) >= 2 {
resource = parts[len(parts)-2]
}
result = append(result, &vigoauth.UserPermission{
Resource: resource,
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 {
parts := strings.Split(up.PermissionID, ":")
resource := "*"
if len(parts) >= 2 {
resource = parts[len(parts)-2]
}
result = append(result, &vigoauth.UserPermission{
Resource: resource,
ResourceID: up.ResourceID,
Actions: []string{"*"},
})
}
return result, nil
}
func (a *appAuth) ListResourceUsers(ctx context.Context, orgID, permissionID, resourceID string) ([]*vigoauth.ResourceUser, error) {
result := make([]*vigoauth.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, &vigoauth.ResourceUser{
UserID: userID,
Actions: actions,
})
}
return result, 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)
}