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

611 lines
15 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"
"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 ""
}