// // Copyright (C) 2024 veypi // 2025-02-14 16:08:06 // Distributed under terms of the MIT license. // package auth import ( "context" "fmt" "strings" "time" "github.com/veypi/vbase/cfg" "github.com/veypi/vbase/models" "github.com/veypi/vigo" ) // Auth 权限管理接口 type Auth interface { // ========== 中间件生成 ========== // 基础权限检查 Perm(permissionID string) func(*vigo.X) error // 资源所有者权限 PermWithOwner(permissionID, ownerKey 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 // ========== 权限管理 ========== // 授予角色 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, error) // 列出用户权限 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", models.AppConfig{ Name: "VBase", Description: "VBase 基础设施", DefaultRoles: []models.RoleDefinition{ {Code: "admin", Name: "管理员", Policies: []string{"*:*"}}, {Code: "user", Name: "普通用户", Policies: []string{ "user:read", "user:update", "org:read", "org:create", "oauth:client:read", "oauth:client:create", "oauth:client:update", "oauth:client:delete", }}, }, }) ) type authFactory struct { apps map[string]*appAuth // appKey -> auth实例 } // New 创建权限管理实例(注册应用) func (f *authFactory) New(appKey string, config models.AppConfig) Auth { if _, exists := f.apps[appKey]; exists { return f.apps[appKey] } auth := &appAuth{ appKey: appKey, config: config, } f.apps[appKey] = auth return auth } // 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 } // appAuth 单个应用的权限管理 type appAuth struct { appKey string config models.AppConfig } // 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. 创建系统预设角色 for _, roleDef := range a.config.DefaultRoles { if err := a.initRole(roleDef); err != nil { return err } } return nil } // extractPermissions 从角色定义中提取所有权限 func (a *appAuth) extractPermissions() []models.Permission { permMap := make(map[string]models.Permission) for _, roleDef := range a.config.DefaultRoles { for _, policy := range roleDef.Policies { // policy 格式: "resource:action" 或 "*:*" parts := strings.Split(policy, ":") if len(parts) != 2 { continue } resource, action := parts[0], parts[1] permID := fmt.Sprintf("%s:%s:%s", a.appKey, resource, action) if _, exists := permMap[permID]; !exists { permMap[permID] = models.Permission{ ID: permID, AppKey: a.appKey, Resource: resource, Action: action, Description: fmt.Sprintf("%s %s on %s", a.config.Name, action, resource), } } } } result := make([]models.Permission, 0, len(permMap)) for _, perm := range permMap { result = append(result, perm) } return result } // initRole 初始化系统预设角色 func (a *appAuth) initRole(roleDef models.RoleDefinition) error { // 查找或创建系统角色 var role models.Role err := cfg.DB().Where("code = ? AND org_id = ''", roleDef.Code).First(&role).Error if err != nil { // 创建新角色 role = models.Role{ OrgID: "", 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) } } // 同步角色权限 for _, policy := range roleDef.Policies { parts := strings.Split(policy, ":") if len(parts) != 2 { continue } resource, action := parts[0], parts[1] permID := fmt.Sprintf("%s:%s:%s", a.appKey, 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) } } } return nil } // ========== 中间件实现 ========== func (a *appAuth) Perm(permissionID string) func(*vigo.X) error { return func(x *vigo.X) error { userID := getUserID(x) if userID == "" { return vigo.ErrUnauthorized } orgID := getOrgID(x) ok, err := a.CheckPermission(x.Context(), userID, orgID, permissionID, "") if err != nil { return err } if !ok { return vigo.ErrForbidden } return nil } } func (a *appAuth) PermWithOwner(permissionID, ownerKey string) func(*vigo.X) error { return func(x *vigo.X) error { userID := getUserID(x) if userID == "" { return vigo.ErrUnauthorized } orgID := getOrgID(x) // 先检查是否有权限 ok, err := a.CheckPermission(x.Context(), userID, orgID, permissionID, "") if err != nil { return err } if !ok { return vigo.ErrForbidden } // 检查是否是所有者或管理员 ownerID, _ := x.Get(ownerKey).(string) if ownerID == "" { ownerID = x.PathParams.Get(ownerKey) } if ownerID == userID { return nil } // 检查是否是管理员 isAdmin, _ := a.isAdmin(x.Context(), userID, orgID) if isAdmin { return nil } return vigo.ErrForbidden } } func (a *appAuth) PermOnResource(permissionID, resourceKey string) func(*vigo.X) error { 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) } // 如果没有获取到 resourceID,仍然进行检查 (resourceID="") // 这意味着检查用户是否拥有该权限的一般访问权 (例如通过角色获得) // 如果想要强制检查特定资源,调用方应该确保 resourceKey 能获取到值 ok, err := a.CheckPermission(x.Context(), userID, orgID, permissionID, resourceID) if err != nil { return err } if !ok { return vigo.ErrForbidden } return nil } } func (a *appAuth) PermAny(permissionIDs []string) func(*vigo.X) error { return func(x *vigo.X) error { userID := getUserID(x) if userID == "" { return vigo.ErrUnauthorized } orgID := getOrgID(x) for _, permID := range permissionIDs { ok, err := a.CheckPermission(x.Context(), userID, orgID, permID, "") if err != nil { return err } if ok { return nil } } return vigo.ErrForbidden } } func (a *appAuth) PermAll(permissionIDs []string) func(*vigo.X) error { return func(x *vigo.X) error { userID := getUserID(x) if userID == "" { return vigo.ErrUnauthorized } orgID := getOrgID(x) for _, permID := range permissionIDs { ok, err := a.CheckPermission(x.Context(), userID, orgID, permID, "") if err != nil { return err } if !ok { return vigo.ErrForbidden } } 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 = ''") } if err := query.First(&role).Error; err != nil { // 如果指定了 OrgID 但没找到,尝试查找全局角色 if orgID != "" { query = cfg.DB().Where("code = ? AND org_id = ''", 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 cfg.DB().Model(&models.UserRole{}). Where("user_id = ? AND org_id = ? AND role_id = ?", userID, orgID, role.ID). Count(&count) if count > 0 { return nil // 已存在 } userRole := models.UserRole{ UserID: userID, OrgID: orgID, RoleID: role.ID, ExpireAt: nil, // 默认不过期 } return cfg.DB().Create(&userRole).Error } 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 = ''") } if err := query.First(&role).Error; err != nil { // 如果没找到,尝试查找全局角色 if orgID != "" { if err := cfg.DB().Where("code = ? AND org_id = ''", roleCode).First(&role).Error; err != nil { return nil // 角色不存在,无需撤销 } } else { return nil } } return cfg.DB().Where("user_id = ? AND org_id = ? AND role_id = ?", userID, orgID, role.ID). Delete(&models.UserRole{}).Error } func (a *appAuth) GrantResourcePerm(ctx context.Context, userID, orgID, permissionID, resourceID string) error { if strings.Count(permissionID, ":") == 1 { permissionID = fmt.Sprintf("%s:%s", a.appKey, 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 err := cfg.DB().Where("user_id = ? AND org_id = ? AND permission_id = ? AND resource_id = ?", userID, orgID, permissionID, resourceID). First(&existing).Error if err == nil { // 已存在 return nil } userPerm := models.UserPermission{ UserID: userID, OrgID: orgID, PermissionID: permissionID, ResourceID: resourceID, ExpireAt: nil, // 默认不过期 GrantedBy: "", // 默认空 } return cfg.DB().Create(&userPerm).Error } func (a *appAuth) RevokeResourcePerm(ctx context.Context, userID, orgID, permissionID, resourceID string) error { if strings.Count(permissionID, ":") == 1 { permissionID = fmt.Sprintf("%s:%s", a.appKey, permissionID) } return cfg.DB().Where("user_id = ? AND org_id = ? AND permission_id = ? AND resource_id = ?", userID, orgID, permissionID, resourceID). Delete(&models.UserPermission{}).Error } func (a *appAuth) RevokeAll(ctx context.Context, userID, orgID string) error { // 删除用户角色 if err := cfg.DB().Where("user_id = ? AND org_id = ?", userID, orgID). Delete(&models.UserRole{}).Error; err != nil { return err } // 删除用户特定权限 if err := cfg.DB().Where("user_id = ? AND org_id = ?", userID, orgID). Delete(&models.UserPermission{}).Error; err != nil { return err } return nil } // ========== 权限查询实现 ========== func (a *appAuth) CheckPermission(ctx context.Context, userID, orgID, permissionID, resourceID string) (bool, error) { if strings.Count(permissionID, ":") == 1 { permissionID = fmt.Sprintf("%s:%s", a.appKey, permissionID) } fmt.Printf("[DEBUG] CheckPermission: userID=%s, orgID=%s, permID=%s, resID=%s\n", userID, orgID, permissionID, resourceID) // 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 = ''", orgID) } else { roleQuery = roleQuery.Where("org_id = ''") } if err := roleQuery.Pluck("role_id", &roleIDs).Error; err != nil { fmt.Printf("[DEBUG] CheckPermission: failed to get roles: %v\n", err) return false, err } fmt.Printf("[DEBUG] CheckPermission: roleIDs=%v\n", roleIDs) 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])) } // 检查这些角色是否有所需权限 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 } fmt.Printf("[DEBUG] CheckPermission: role perm count=%d checked=%v\n", count, permsToCheck) 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])) } var userPermCount int64 query := cfg.DB().Model(&models.UserPermission{}). Where("user_id = ? AND org_id = ? AND permission_id IN ? AND (expire_at IS NULL OR expire_at > ?)", userID, orgID, permsToCheck, time.Now()) if resourceID != "" { query = query.Where("resource_id = ? OR resource_id = '*'", resourceID) } 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 = 'admin'"). 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 } // 从 context 获取用户ID func getUserID(x *vigo.X) string { if userID, ok := x.Get("user_id").(string); ok { return userID } return "" } // 从 context 获取组织ID func getOrgID(x *vigo.X) string { if orgID, ok := x.Get("org_id").(string); ok { return orgID } return "" }