// // 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 // 满足任一权限 PermAny(permissionIDs []string) func(*vigo.X) error // 满足所有权限 PermAll(permissionIDs []string) func(*vigo.X) error // ========== 权限管理 ========== // 授予角色 GrantRole(ctx context.Context, req models.GrantRoleRequest) error // 撤销角色 RevokeRole(ctx context.Context, userID, orgID, roleCode string) error // 授予特定资源权限 GrantResourcePerm(ctx context.Context, req models.GrantResourcePermRequest) error // 撤销特定资源权限 RevokeResourcePerm(ctx context.Context, userID, orgID, permissionID, resourceID string) error // 撤销用户所有权限 RevokeAll(ctx context.Context, userID, orgID string) error // ========== 权限查询 ========== // 检查权限 CheckPermission(ctx context.Context, req models.CheckPermRequest) (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"}}, }, }) ) 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(), models.CheckPermRequest{ UserID: userID, OrgID: orgID, PermissionID: 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(), models.CheckPermRequest{ UserID: userID, OrgID: orgID, PermissionID: permissionID, }) if err != nil { return err } if !ok { return vigo.ErrForbidden } // 检查是否是所有者或管理员 ownerID, _ := x.Get(ownerKey).(string) if ownerID == userID { return nil } // 检查是否是管理员 isAdmin, _ := a.isAdmin(x.Context(), userID, orgID) if isAdmin { return nil } return vigo.ErrForbidden } } 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(), models.CheckPermRequest{ UserID: userID, OrgID: orgID, PermissionID: 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(), models.CheckPermRequest{ UserID: userID, OrgID: orgID, PermissionID: permID, }) if err != nil { return err } if !ok { return vigo.ErrForbidden } } return nil } } // ========== 权限管理实现 ========== func (a *appAuth) GrantRole(ctx context.Context, req models.GrantRoleRequest) error { // 查找角色 var role models.Role query := cfg.DB().Where("code = ?", req.RoleCode) if req.OrgID != "" { query = query.Where("org_id = ?", req.OrgID) } else { query = query.Where("org_id = ''") } if err := query.First(&role).Error; err != nil { return fmt.Errorf("role not found: %s", req.RoleCode) } // 检查是否已存在 var count int64 cfg.DB().Model(&models.UserRole{}). Where("user_id = ? AND org_id = ? AND role_id = ?", req.UserID, req.OrgID, role.ID). Count(&count) if count > 0 { return nil // 已存在 } userRole := models.UserRole{ UserID: req.UserID, OrgID: req.OrgID, RoleID: role.ID, ExpireAt: req.ExpireAt, } 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 { 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, req models.GrantResourcePermRequest) error { // 检查权限是否存在 var perm models.Permission if err := cfg.DB().Where("id = ?", req.PermissionID).First(&perm).Error; err != nil { return fmt.Errorf("permission not found: %s", req.PermissionID) } // 检查是否已存在 var existing models.UserPermission err := cfg.DB().Where("user_id = ? AND org_id = ? AND permission_id = ? AND resource_id = ?", req.UserID, req.OrgID, req.PermissionID, req.ResourceID). First(&existing).Error if err == nil { // 更新过期时间 existing.ExpireAt = req.ExpireAt return cfg.DB().Save(&existing).Error } userPerm := models.UserPermission{ UserID: req.UserID, OrgID: req.OrgID, PermissionID: req.PermissionID, ResourceID: req.ResourceID, ExpireAt: req.ExpireAt, GrantedBy: req.GrantedBy, } return cfg.DB().Create(&userPerm).Error } func (a *appAuth) RevokeResourcePerm(ctx context.Context, userID, orgID, permissionID, resourceID string) error { 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, req models.CheckPermRequest) (bool, error) { // 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 > ?)", req.UserID, req.OrgID, time.Now()). Pluck("role_id", &roleIDs).Error; err != nil { return false, err } if len(roleIDs) > 0 { // 检查这些角色是否有所需权限 var count int64 if err := cfg.DB().Model(&models.RolePermission{}). Where("role_id IN ? AND permission_id = ?", roleIDs, req.PermissionID). Count(&count).Error; err != nil { return false, err } if count > 0 { return true, nil } } // 2. 检查用户是否有特定的资源权限 var userPermCount int64 query := cfg.DB().Model(&models.UserPermission{}). Where("user_id = ? AND org_id = ? AND permission_id = ? AND (expire_at IS NULL OR expire_at > ?)", req.UserID, req.OrgID, req.PermissionID, time.Now()) if req.ResourceID != "" { query = query.Where("resource_id = ? OR resource_id = '*'", req.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 "" }