This commit is contained in:
2026-08-17 02:12:17 +08:00
parent 560ae46b82
commit 6a05803e08
96 changed files with 2888 additions and 685 deletions
+106 -42
View File
@@ -77,11 +77,12 @@ func (r *authorityAccessRepo) CreateAuthority(ctx context.Context, value *biz.Au
}
}
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
var count int64
if err := tx.Model(&authorityPO{}).Where("authority_id = ?", value.AuthorityID).Count(&count).Error; err != nil {
return err
}
if count > 0 {
// The administration contract checks existence with First and treats every result other than
// ErrRecordNotFound as a duplicate-role error. Keep that precedence so
// duplicate IDs win over strict-tree validation and the API message stays
// compatible.
var existing authorityPO
if err := tx.Where("authority_id = ?", value.AuthorityID).First(&existing).Error; !errors.Is(err, gorm.ErrRecordNotFound) {
return errors.New("存在相同角色id")
}
if value.DefaultRouter == "" {
@@ -92,6 +93,8 @@ func (r *authorityAccessRepo) CreateAuthority(ctx context.Context, value *biz.Au
return err
}
value.CreatedAt, value.UpdatedAt = po.CreatedAt, po.UpdatedAt
value.DataScope, value.DefaultRouter = po.DataScope, po.DefaultRouter
value.Menus = []*biz.Menu{{ID: 1, Path: "dashboard", Name: "dashboard", Component: "view/dashboard/index.vue", Sort: 1, Title: "仪表盘", Icon: "setting"}}
var dashboard menuPO
if err := tx.Where("name = ?", "dashboard").First(&dashboard).Error; err == nil {
if err = tx.Create(&authorityMenuPO{SysAuthorityAuthorityID: value.AuthorityID, SysBaseMenuID: dashboard.ID}).Error; err != nil {
@@ -113,10 +116,18 @@ func (r *authorityAccessRepo) CopyAuthority(ctx context.Context, sourceID uint,
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
config := r.data.runtime.Admin()
actor, hasActor := biz.ActorFromContext(ctx)
// Strict-authority checks apply to every actor. A root actor is
// allowed to create/copy a role directly below itself; non-root actors
// may target themselves' descendants. Do not special-case 888 here:
// the configured strict-auth switch is the source of truth.
// Reject a duplicate target ID before performing the
// hierarchy/Casbin checks that happen later in UpdateCasbin.
var existing authorityPO
if err := tx.Where("authority_id = ?", value.AuthorityID).First(&existing).Error; !errors.Is(err, gorm.ErrRecordNotFound) {
return errors.New("存在相同角色id")
}
// UpdateCasbin checks that the newly-created role is in the actor's
// strict-authority subtree. Before inserting it, the equivalent check is
// that its requested parent is the actor or one of the actor's allowed
// descendants. This preserves the compatible error and prevents a transient role
// from being visible when the check is guaranteed to fail.
strict := hasActor && config != nil && config.System != nil && config.System.UseStrictAuth
if strict {
allowed, err := r.strictAuthorityIDs(ctx, actor.AuthorityID)
@@ -128,18 +139,12 @@ func (r *authorityAccessRepo) CopyAuthority(ctx context.Context, sourceID uint,
return errors.New("您提交的角色ID不合法")
}
}
var count int64
if err := tx.Model(&authorityPO{}).Where("authority_id = ?", value.AuthorityID).Count(&count).Error; err != nil {
return err
}
if count != 0 {
return errors.New("存在相同角色id")
}
po := authorityPO{AuthorityID: value.AuthorityID, AuthorityName: value.AuthorityName, ParentID: value.ParentID, DataScope: value.DataScope, DefaultRouter: value.DefaultRouter}
if err := tx.Create(&po).Error; err != nil {
return err
}
value.CreatedAt, value.UpdatedAt = po.CreatedAt, po.UpdatedAt
value.DataScope, value.DefaultRouter = po.DataScope, po.DefaultRouter
copyLinks := func(table string, destination any, columns map[string]any) error {
return tx.Table(table).Where(columns).Find(destination).Error
}
@@ -155,6 +160,20 @@ func (r *authorityAccessRepo) CopyAuthority(ctx context.Context, sourceID uint,
return err
}
}
if len(menus) > 0 {
menuIDs := make([]uint, 0, len(menus))
for _, menu := range menus {
menuIDs = append(menuIDs, menu.SysBaseMenuID)
}
var copiedMenus []menuPO
if err := tx.Where("id IN ?", menuIDs).Order("sort").Find(&copiedMenus).Error; err != nil {
return err
}
value.Menus = make([]*biz.Menu, 0, len(copiedMenus))
for _, menu := range copiedMenus {
value.Menus = append(value.Menus, menuFromPO(menu))
}
}
apis, err := policyRowsForAuthority(tx, sourceID)
if err != nil {
return err
@@ -163,7 +182,7 @@ func (r *authorityAccessRepo) CopyAuthority(ctx context.Context, sourceID uint,
seenPolicies := make(map[string]struct{}, len(apis))
for _, api := range apis {
if strict {
allowed, err := policyExists(tx, actor.AuthorityID, api.V1, api.V2)
allowed, err := r.copyPolicyAllowed(ctx, tx, actor.AuthorityID, api.V1, api.V2)
if err != nil {
return err
}
@@ -198,6 +217,28 @@ func (r *authorityAccessRepo) CopyAuthority(ctx context.Context, sourceID uint,
return nil
})
}
// copyPolicyAllowed mirrors the administration API-list and policy validation.
// A policy must refer to a currently registered API. Root roles
// may copy any registered API, while non-root roles may copy only APIs present
// in their own Casbin policy set.
func (r *authorityAccessRepo) copyPolicyAllowed(ctx context.Context, tx *gorm.DB, actorID uint, path, method string) (bool, error) {
var actor authorityPO
if err := tx.WithContext(ctx).Where("authority_id = ?", actorID).First(&actor).Error; err != nil {
return false, err
}
var apiCount int64
if err := tx.WithContext(ctx).Model(&apiPO{}).Where("path = ? AND method = ?", path, method).Count(&apiCount).Error; err != nil {
return false, err
}
if apiCount == 0 {
return false, nil
}
if actor.ParentID == nil || *actor.ParentID == 0 {
return true, nil
}
return policyExists(tx.WithContext(ctx), actorID, path, method)
}
func (r *authorityAccessRepo) UpdateAuthority(ctx context.Context, value *biz.Authority) error {
db := r.data.gormDB.WithContext(ctx)
var current authorityPO
@@ -217,11 +258,21 @@ func (r *authorityAccessRepo) DeleteAuthority(ctx context.Context, id uint) erro
return err
}
var users, children int64
if err := tx.Model(&userAuthorityPO{}).Where("sys_authority_authority_id = ?", id).Count(&users).Error; err != nil {
// Associated-user checks only block on users that still
// exist (and are not soft-deleted). Counting raw join rows would make an
// orphaned association impossible to clean up, which is observably
// different from the administration contract.
var linkedUserIDs []uint
if err := tx.Model(&userAuthorityPO{}).Where("sys_authority_authority_id = ?", id).Pluck("sys_user_id", &linkedUserIDs).Error; err != nil {
return err
}
if users > 0 {
return errors.New("此角色有用户正在使用禁止删除")
if len(linkedUserIDs) > 0 {
if err := tx.Model(&userPO{}).Where("id IN ?", linkedUserIDs).Count(&users).Error; err != nil {
return err
}
if users > 0 {
return errors.New("此角色有用户正在使用禁止删除")
}
}
if err := tx.Model(&userPO{}).Where("authority_id = ?", id).Count(&users).Error; err != nil {
return err
@@ -235,6 +286,9 @@ func (r *authorityAccessRepo) DeleteAuthority(ctx context.Context, id uint) erro
if children > 0 {
return errors.New("此角色存在子角色不允许删除")
}
if err := tx.Where("sys_authority_authority_id = ?", id).Delete(&userAuthorityPO{}).Error; err != nil {
return err
}
if err := tx.Where("sys_authority_authority_id = ?", id).Delete(&authorityMenuPO{}).Error; err != nil {
return err
}
@@ -253,18 +307,28 @@ func (r *authorityAccessRepo) DeleteAuthority(ctx context.Context, id uint) erro
})
}
func (r *authorityAccessRepo) ListAuthorities(ctx context.Context) ([]*biz.Authority, error) {
var pos []authorityPO
if err := r.data.gormDB.WithContext(ctx).Find(&pos).Error; err != nil {
return nil, err
}
db := r.data.gormDB.WithContext(ctx)
var allowed map[uint]bool
config := r.data.runtime.Admin()
if actor, ok := biz.ActorFromContext(ctx); ok && config != nil && config.System != nil && config.System.UseStrictAuth {
var err error
allowed, err = r.strictAuthorityIDs(ctx, actor.AuthorityID)
if err != nil {
if actor, ok := biz.ActorFromContext(ctx); ok {
// The current authority is loaded even when strict mode is disabled;
// an invalid token authority therefore fails the list request instead of
// exposing the complete role tree.
var current authorityPO
if err := db.Where("authority_id = ?", actor.AuthorityID).First(&current).Error; err != nil {
return nil, err
}
if config != nil && config.System != nil && config.System.UseStrictAuth {
var err error
allowed, err = r.strictAuthorityIDs(ctx, actor.AuthorityID)
if err != nil {
return nil, err
}
}
}
var pos []authorityPO
if err := db.Find(&pos).Error; err != nil {
return nil, err
}
out := make([]*biz.Authority, 0, len(pos))
for _, po := range pos {
@@ -285,15 +349,6 @@ func (r *authorityAccessRepo) SetAuthorityUsers(ctx context.Context, id uint, id
if err := tx.Where("sys_authority_authority_id = ?", id).Delete(&userAuthorityPO{}).Error; err != nil {
return err
}
links := make([]userAuthorityPO, 0, len(ids))
for _, uid := range ids {
links = append(links, userAuthorityPO{SysUserID: uid, SysAuthorityAuthorityID: id})
}
if len(links) > 0 {
if err := tx.Create(&links).Error; err != nil {
return err
}
}
selected := map[uint]bool{}
for _, uid := range ids {
selected[uid] = true
@@ -305,16 +360,25 @@ func (r *authorityAccessRepo) SetAuthorityUsers(ctx context.Context, id uint, id
var user userPO
if err := tx.First(&user, uid).Error; err == nil && user.AuthorityID == id {
var another userAuthorityPO
if err = tx.Where("sys_user_id = ?", uid).First(&another).Error; errors.Is(err, gorm.ErrRecordNotFound) {
if err = tx.Where("sys_user_id = ?", uid).First(&another).Error; err != nil {
// Missing users and association lookup errors are ignored
// while choosing a replacement role.
continue
} else if err != nil {
return err
}
if err = tx.Model(&user).Update("authority_id", another.SysAuthorityAuthorityID).Error; err != nil {
return err
}
}
}
links := make([]userAuthorityPO, 0, len(ids))
for _, uid := range ids {
links = append(links, userAuthorityPO{SysUserID: uid, SysAuthorityAuthorityID: id})
}
if len(links) > 0 {
if err := tx.Create(&links).Error; err != nil {
return err
}
}
return nil
})
}
@@ -328,7 +392,7 @@ func (r *authorityAccessRepo) SetDataScope(ctx context.Context, id uint, scope i
if err := tx.Model(&authorityPO{}).Where("authority_id = ?", id).Update("data_scope", scope).Error; err != nil {
return err
}
if err := tx.Where("authority_id = ?", id).Delete(&authorityDepartmentPO{}).Error; err != nil {
if err := tx.Where("sys_authority_authority_id = ?", id).Delete(&authorityDepartmentPO{}).Error; err != nil {
return err
}
if scope == 5 {
@@ -345,7 +409,7 @@ func (r *authorityAccessRepo) SetDataScope(ctx context.Context, id uint, scope i
}
func (r *authorityAccessRepo) DataScopeDepartmentIDs(ctx context.Context, id uint) ([]uint, error) {
var ids []uint
err := r.data.gormDB.WithContext(ctx).Model(&authorityDepartmentPO{}).Where("authority_id = ?", id).Pluck("department_id", &ids).Error
err := r.data.gormDB.WithContext(ctx).Model(&authorityDepartmentPO{}).Where("sys_authority_authority_id = ?", id).Pluck("sys_department_id", &ids).Error
return ids, err
}
func (r *authorityAccessRepo) ResolveDataScope(ctx context.Context, authorityID, userID uint) (biz.DataScope, error) {