@ -32,10 +32,15 @@ type Auth interface {
PermOnResource ( permissionID , resourceKey string ) func ( * vigo . X ) error
// 满足任一权限
PermAny ( permissionIDs [ ] string ) func ( * vigo . X ) error
PermAny ( permissionIDs ... string ) func ( * vigo . X ) error
// 满足所有权限
PermAll ( permissionIDs [ ] string ) func ( * vigo . X ) error
PermAll ( permissionIDs ... string ) func ( * vigo . X ) error
// ========== 角色管理 ==========
// 添加角色定义
// policies 格式: "resource:action",例如 "user:read", "*:*"
AddRole ( roleCode , roleName string , policies ... string ) error
// ========== 权限管理 ==========
// 授予角色
@ -71,43 +76,46 @@ var Factory = &authFactory{
// 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" ,
} } ,
} ,
} )
)
var VBaseAuth = Factory . New ( "vb" )
func init ( ) {
// 为 VBaseAuth 添加默认角色
VBaseAuth . AddRole ( "admin" , "管理员" , "*:*" )
VBaseAuth . AddRole ( "user" , "普通用户" ,
"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 ]
}
// 验证默认角色中的权限格式
for _ , role := range config . DefaultRoles {
for _ , policy := range role . Policies {
validatePermissionID ( policy )
}
// New 创建权限管理实例(注册权限域)
func ( f * authFactory ) New ( scope string ) Auth {
if _ , exists := f . apps [ scope ] ; exists {
return f . apps [ scope ]
}
auth := & appAuth {
appKey : appKey ,
config : config ,
}
f . apps [ appKey ] = auth
scope : scope ,
roleDefs : make ( map [ string ] roleDefinition ) ,
policies : make ( map [ string ] [ ] [ 2 ] string ) ,
roleInitDone : make ( map [ string ] bool ) ,
}
// 设置权限域信息
auth . roleDefs [ "_scope_info" ] = roleDefinition {
code : "_scope_info" ,
name : scope ,
description : scope + " scope" ,
}
f . apps [ scope ] = auth
return auth
}
@ -145,10 +153,77 @@ func (f *authFactory) Init() error {
return nil
}
// appAuth 单个应用的权限管理
// roleDefinition 角色定义(内部使用)
type roleDefinition struct {
code string
name string
description string
policies [ ] string // 权限列表: ["resource:action", "*:*"]
}
// appAuth 单个权限域的权限管理
type appAuth struct {
appKey string
config models . AppConfig
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" )
}
if roleCode == "_scope_info" {
return fmt . Errorf ( "reserved role code: _scope_info" )
}
// 解析并验证权限格式
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
}
// init 初始化应用的权限配置
@ -166,9 +241,12 @@ func (a *appAuth) init() error {
}
}
// 2. 创建系统预设角色
for _ , roleDef := range a . config . DefaultRoles {
if err := a . initRole ( roleDef ) ; err != nil {
// 2. 创建系统预设角色(跳过 _app_info)
for roleCode := range a . roleDefs {
if roleCode == "_app_info" {
continue
}
if err := a . initRole ( roleCode ) ; err != nil {
return err
}
}
@ -180,24 +258,25 @@ func (a *appAuth) init() error {
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 {
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
}
resource , action := parts [ 0 ] , parts [ 1 ]
permID := fmt . Sprintf ( "%s:%s:%s" , a . appKey , resource , action )
permID := fmt . Sprintf ( "%s:%s:%s" , a . scope , resource , action )
if _ , exists := permMap [ permID ] ; ! exists {
permMap [ permID ] = models . Permission {
ID : permID ,
AppKey: a . appKey ,
Scope: a . scope ,
Resource : resource ,
Action : action ,
Description : fmt . Sprintf ( "%s %s on %s" , a . config. Nam e, action , resource ) ,
Description : fmt . Sprintf ( "%s %s on %s" , a . scop e, action , resource ) ,
}
}
}
@ -211,34 +290,44 @@ func (a *appAuth) extractPermissions() []models.Permission {
}
// initRole 初始化系统预设角色
func ( a * appAuth ) initRole ( roleDef models . RoleDefinition ) error {
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
err := cfg . DB ( ) . Where ( "code = ? AND org_id IS NULL" , roleDef . c ode) . First ( & role ) . Error
if err != nil {
// 创建新角色
role = models . Role {
OrgID : nil ,
Code : roleDef . Code ,
Name : roleDef . Name ,
Description : roleDef . D escription,
Code : roleDef . c ode,
Name : roleDef . n ame,
Description : roleDef . d escription,
IsSystem : true ,
Status : 1 ,
}
if err := cfg . DB ( ) . Create ( & role ) . Error ; err != nil {
return fmt . Errorf ( "failed to create role %s: %w" , roleDef . C ode, err )
return fmt . Errorf ( "failed to create role %s: %w" , roleDef . c ode, err )
}
}
// 同步角色权限
for _ , policy := range roleDef . Policies {
parts := strings . Split ( policy , ":" )
if len ( parts ) != 2 {
hasWildcard := false
for _ , policy := range policies {
resource , action := policy [ 0 ] , policy [ 1 ]
// 处理通配符权限
if resource == "*" && action == "*" {
hasWildcard = true
continue
}
resource , action := parts [ 0 ] , parts [ 1 ]
permID := fmt . Sprintf ( "%s:%s:%s" , a . appKey , resource , action )
permID := fmt . Sprintf ( "%s:%s:%s" , a . scope , resource , action )
// 检查关联是否存在
var count int64
@ -258,6 +347,26 @@ func (a *appAuth) initRole(roleDef models.RoleDefinition) error {
}
}
// 为通配符权限创建特殊记录
if hasWildcard {
wildcardPermID := fmt . Sprintf ( "%s:*:*" , a . scope )
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
}
@ -366,7 +475,7 @@ func (a *appAuth) checkPermission(ctx context.Context, userID, orgID, permission
return nil
}
func ( a * appAuth ) PermAny ( permissionIDs [ ] string ) func ( * vigo . X ) error {
func ( a * appAuth ) PermAny ( permissionIDs ... string ) func ( * vigo . X ) error {
for _ , pid := range permissionIDs {
validatePermissionID ( pid )
}
@ -396,7 +505,7 @@ func (a *appAuth) PermAny(permissionIDs []string) func(*vigo.X) error {
}
}
func ( a * appAuth ) PermAll ( permissionIDs [ ] string ) func ( * vigo . X ) error {
func ( a * appAuth ) PermAll ( permissionIDs ... string ) func ( * vigo . X ) error {
for _ , pid := range permissionIDs {
validatePermissionID ( pid )
}
@ -511,7 +620,7 @@ func (a *appAuth) RevokeRole(ctx context.Context, userID, orgID, roleCode string
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 )
permissionID = fmt . Sprintf ( "%s:%s" , a . scope , permissionID )
}
// 检查权限是否存在
var perm models . Permission
@ -557,7 +666,7 @@ func (a *appAuth) GrantResourcePerm(ctx context.Context, userID, orgID, permissi
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 )
permissionID = fmt . Sprintf ( "%s:%s" , a . scope , permissionID )
}
query := cfg . DB ( ) . Where ( "user_id = ? AND permission_id = ? AND resource_id = ?" ,
userID , permissionID , resourceID )
@ -604,7 +713,7 @@ func (a *appAuth) RevokeAll(ctx context.Context, userID, orgID string) error {
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 )
permissionID = fmt . Sprintf ( "%s:%s" , a . scope , permissionID )
}
// Check cache
@ -659,6 +768,12 @@ func (a *appAuth) checkPermissionDB(ctx context.Context, userID, orgID, permissi
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 ) )
}
// 检查这些角色是否有所需权限
@ -680,12 +795,24 @@ func (a *appAuth) checkPermissionDB(ctx context.Context, userID, orgID, permissi
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 org_id = ? AND permission_id IN ? AND (expire_at IS NULL OR expire_at > ?)" ,
userID , orgID , permsToCheck , time . Now ( ) )
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 )