package system import ( "context" "errors" "kra/internal/biz/system" "gorm.io/gorm" ) var errInvalidDataScope = errors.New("数据权限范围不合法") type authorityAccessRepo struct{ data Provider } func NewAuthorityAccessRepo(data Provider) system.AuthorityAccessRepo { return &authorityAccessRepo{data: data} } func (r *authorityAccessRepo) strictAuthorityIDs(ctx context.Context, actorID uint) (map[uint]bool, error) { allowed := make(map[uint]bool) if actorID == 0 { return allowed, nil } var actor authorityPO if err := r.data.DB().WithContext(ctx).Where("authority_id = ?", actorID).First(&actor).Error; err != nil { return nil, err } var authorities []authorityPO if err := r.data.DB().WithContext(ctx).Find(&authorities).Error; err != nil { return nil, err } children := make(map[uint][]uint) for _, authority := range authorities { if authority.ParentID != nil { children[*authority.ParentID] = append(children[*authority.ParentID], authority.AuthorityID) } } var walk func(uint) walk = func(id uint) { for _, child := range children[id] { if !allowed[child] { allowed[child] = true walk(child) } } } if actor.ParentID == nil || *actor.ParentID == 0 { allowed[actorID] = true } walk(actorID) if actor.ParentID != nil && *actor.ParentID != 0 { // A non-root authority manages descendants, never itself. Removing the // actor explicitly also keeps malformed cyclic trees from reopening the // self-management path. delete(allowed, actorID) } return allowed, nil } func (r *authorityAccessRepo) strictAuthorityAccess(ctx context.Context) (system.Actor, map[uint]bool, bool, error) { config := r.data.Runtime().Admin() if config == nil || config.System == nil || !config.System.UseStrictAuth { return system.Actor{}, nil, false, nil } actor, ok := system.ActorFromContext(ctx) if !ok { return system.Actor{}, nil, true, errors.New("您提交的角色ID不合法") } allowed, err := r.strictAuthorityIDs(ctx, actor.AuthorityID) if err != nil { return system.Actor{}, nil, true, err } return actor, allowed, true, nil } func (r *authorityAccessRepo) checkAuthorityIDsAuth(ctx context.Context, targetIDs []uint) error { _, allowed, strict, err := r.strictAuthorityAccess(ctx) if err != nil || !strict { return err } for _, targetID := range targetIDs { if !allowed[targetID] { return errors.New("您提交的角色ID不合法") } } return nil } func (r *authorityAccessRepo) checkAuthorityIDAuth(ctx context.Context, targetID uint) error { return r.checkAuthorityIDsAuth(ctx, []uint{targetID}) } func managedAuthorityParent(actor system.Actor, allowed map[uint]bool, targetID uint, parentID *uint, creating bool) (*uint, error) { if creating && (parentID == nil || *parentID == 0) { value := actor.AuthorityID return &value, nil } if !creating && targetID == actor.AuthorityID && allowed[actor.AuthorityID] && (parentID == nil || *parentID == 0) { return parentID, nil } if parentID == nil || *parentID == 0 || *parentID == targetID || (*parentID != actor.AuthorityID && !allowed[*parentID]) { return nil, errors.New("您提交的角色ID不合法") } return parentID, nil } func (r *authorityAccessRepo) ensureAuthorityParentAcyclic(ctx context.Context, targetID uint, parentID *uint) error { if parentID == nil || *parentID == 0 { return nil } current := *parentID visited := make(map[uint]struct{}) for current != 0 { if current == targetID { return errors.New("角色父级不能形成循环") } if _, seen := visited[current]; seen { return errors.New("角色层级存在循环") } visited[current] = struct{}{} var authority authorityPO if err := r.data.DB().WithContext(ctx).Select("authority_id", "parent_id").Where("authority_id = ?", current).First(&authority).Error; err != nil { return err } if authority.ParentID == nil { return nil } current = *authority.ParentID } return nil } func (r *authorityAccessRepo) CreateAuthority(ctx context.Context, value *system.Authority) error { actor, allowed, strict, err := r.prepareAuthorityCreate(ctx, value) if err != nil { return err } if strict { value.ParentID, err = managedAuthorityParent(actor, allowed, value.AuthorityID, value.ParentID, true) if err != nil { return err } } return r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error { // 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 == "" { value.DefaultRouter = "dashboard" } 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 value.Menus = []*system.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 { grantDashboard := true if strict && !allowed[actor.AuthorityID] { var count int64 if err = tx.Model(&authorityMenuPO{}).Where("sys_authority_authority_id = ? AND sys_base_menu_id = ?", actor.AuthorityID, dashboard.ID).Count(&count).Error; err != nil { return err } grantDashboard = count > 0 } if grantDashboard { if err = tx.Create(&authorityMenuPO{SysAuthorityAuthorityID: value.AuthorityID, SysBaseMenuID: dashboard.ID}).Error; err != nil { return err } } else { value.Menus = nil } } defaults := []struct{ path, method string }{{"/menu/getMenu", "POST"}, {"/jwt/jsonInBlacklist", "POST"}, {"/base/login", "POST"}, {"/user/changePassword", "POST"}, {"/user/setUserAuthority", "POST"}, {"/user/getUserInfo", "GET"}, {"/user/setSelfInfo", "PUT"}, {"/fileUploadAndDownload/upload", "POST"}, {"/sysDictionary/findSysDictionary", "GET"}} rules := make([]casbinRulePO, 0, len(defaults)) for _, item := range defaults { if strict { permitted, policyErr := r.copyPolicyAllowed(ctx, tx, actor.AuthorityID, item.path, item.method) if policyErr != nil { return policyErr } if !permitted { continue } } rules = append(rules, newPolicyRule(value.AuthorityID, item.path, item.method)) } if len(rules) > 0 { if err := tx.Create(&rules).Error; err != nil { return err } } return nil }) } func (r *authorityAccessRepo) prepareAuthorityCreate(ctx context.Context, value *system.Authority) (system.Actor, map[uint]bool, bool, error) { if value.DataScope == 0 { value.DataScope = 1 } else if value.DataScope < 1 || value.DataScope > 5 { return system.Actor{}, nil, false, errInvalidDataScope } if err := r.checkDataScopeGrant(ctx, value.DataScope, nil); err != nil { return system.Actor{}, nil, false, err } actor, allowed, strict, err := r.strictAuthorityAccess(ctx) if err != nil { return system.Actor{}, nil, false, err } return actor, allowed, strict, nil } func (r *authorityAccessRepo) CopyAuthority(ctx context.Context, sourceID uint, value *system.Authority) error { actor, allowed, strict, err := r.prepareAuthorityCreate(ctx, value) if err != nil { return err } return r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error { // 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. if strict { if sourceID != actor.AuthorityID && !allowed[sourceID] { return errors.New("您提交的角色ID不合法") } value.ParentID, err = managedAuthorityParent(actor, allowed, value.AuthorityID, value.ParentID, true) if err != nil { return err } } 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 } var menus []authorityMenuPO if err := copyLinks("sys_authority_menus", &menus, map[string]any{"sys_authority_authority_id": sourceID}); err != nil { return err } if strict && len(menus) > 0 { menuIDs := make([]uint, 0, len(menus)) for _, menu := range menus { menuIDs = append(menuIDs, menu.SysBaseMenuID) } if err := checkMenuAssignment(tx.WithContext(ctx), actor.AuthorityID, allowed[actor.AuthorityID], menuIDs); err != nil { return err } } for i := range menus { menus[i].SysAuthorityAuthorityID = value.AuthorityID } if len(menus) > 0 { if err := tx.Create(&menus).Error; err != nil { 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([]*system.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 } copiedRules := make([]casbinRulePO, 0, len(apis)) seenPolicies := make(map[string]struct{}, len(apis)) for _, api := range apis { if strict { allowed, err := r.copyPolicyAllowed(ctx, tx, actor.AuthorityID, api.V1, api.V2) if err != nil { return err } if !allowed { return errors.New("存在api不在权限列表中") } } key := api.V1 + "\x00" + api.V2 if _, ok := seenPolicies[key]; ok { continue } seenPolicies[key] = struct{}{} copiedRules = append(copiedRules, newPolicyRule(value.AuthorityID, api.V1, api.V2)) } if len(copiedRules) > 0 { if err := tx.Create(&copiedRules).Error; err != nil { return err } } var buttons []authorityButtonPO if err := copyLinks("sys_authority_btns", &buttons, map[string]any{"authority_id": sourceID}); err != nil { return err } if strict && len(buttons) > 0 { requested := make(map[uint][]uint) for _, button := range buttons { requested[button.MenuID] = append(requested[button.MenuID], button.ButtonID) } if err := checkButtonAssignment(tx.WithContext(ctx), actor.AuthorityID, allowed[actor.AuthorityID], requested); err != nil { return err } } for i := range buttons { buttons[i].AuthorityID = value.AuthorityID } if len(buttons) > 0 { if err := tx.Create(&buttons).Error; err != nil { return err } } 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 *system.Authority) error { if err := r.checkAuthorityIDAuth(ctx, value.AuthorityID); err != nil { return err } if value.DataScope < 0 || value.DataScope > 5 { return errInvalidDataScope } if value.DataScope != 0 { var departmentIDs []uint if value.DataScope == 5 { var err error departmentIDs, err = r.DataScopeDepartmentIDs(ctx, value.AuthorityID) if err != nil { return err } } if err := r.checkDataScopeGrant(ctx, value.DataScope, departmentIDs); err != nil { return err } } actor, allowed, strict, err := r.strictAuthorityAccess(ctx) if err != nil { return err } if strict { value.ParentID, err = managedAuthorityParent(actor, allowed, value.AuthorityID, value.ParentID, false) if err != nil { return err } } if err = r.ensureAuthorityParentAcyclic(ctx, value.AuthorityID, value.ParentID); err != nil { return err } db := r.data.DB().WithContext(ctx) var current authorityPO if err := db.Where("authority_id = ?", value.AuthorityID).First(¤t).Error; err != nil { return errors.New("查询角色数据失败") } updates := &authorityPO{AuthorityName: value.AuthorityName, ParentID: value.ParentID, DataScope: value.DataScope, DefaultRouter: value.DefaultRouter} return db.Model(¤t).Updates(updates).Error } func (r *authorityAccessRepo) DeleteAuthority(ctx context.Context, id uint) error { if err := r.checkAuthorityIDAuth(ctx, id); err != nil { return err } return r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error { var authority authorityPO if err := tx.Where("authority_id = ?", id).First(&authority).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return errors.New("该角色不存在") } return err } var users, children int64 // 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 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 } if users > 0 { return errors.New("此角色有用户正在使用禁止删除") } if err := tx.Model(&authorityPO{}).Where("parent_id = ?", id).Count(&children).Error; err != nil { return err } 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 } if err := deletePoliciesForAuthority(tx, id); err != nil { return err } if err := tx.Where("authority_id = ?", id).Delete(&authorityAPIPO{}).Error; err != nil { return err } if err := tx.Where("authority_id = ?", id).Delete(&authorityButtonPO{}).Error; err != nil { return err } // The compatible delete removes the role row permanently with Unscoped().Delete, // so reusing an authority ID after deletion behaves the same way. return tx.Unscoped().Delete(&authorityPO{}, "authority_id = ?", id).Error }) } func (r *authorityAccessRepo) ListAuthorities(ctx context.Context) ([]*system.Authority, error) { db := r.data.DB().WithContext(ctx) var allowed map[uint]bool config := r.data.Runtime().Admin() if actor, ok := system.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(¤t).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([]*system.Authority, 0, len(pos)) for _, po := range pos { if allowed != nil && !allowed[po.AuthorityID] { continue } v := toBizAuthority(po) out = append(out, &v) } return out, nil } func (r *authorityAccessRepo) SetAuthorityUsers(ctx context.Context, id uint, ids []uint) error { _, allowed, strict, err := r.strictAuthorityAccess(ctx) if err != nil { return err } if strict && !allowed[id] { return errors.New("您提交的角色ID不合法") } return r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error { var oldIDs []uint if err := tx.Model(&userAuthorityPO{}).Where("sys_authority_authority_id = ?", id).Pluck("sys_user_id", &oldIDs).Error; err != nil { return err } if strict { if err := checkManagedUserIDs(tx, ids, allowed, true); err != nil { return err } if err := checkManagedUserIDs(tx, oldIDs, allowed, false); err != nil { return err } } if err := tx.Where("sys_authority_authority_id = ?", id).Delete(&userAuthorityPO{}).Error; err != nil { return err } selected := map[uint]bool{} for _, uid := range ids { selected[uid] = true } for _, uid := range oldIDs { if selected[uid] { continue } 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; err != nil { // Missing users and association lookup errors are ignored // while choosing a replacement role. continue } 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 }) } func checkManagedUserIDs(tx *gorm.DB, ids []uint, allowedAuthorities map[uint]bool, requireAll bool) error { unique := make(map[uint]struct{}, len(ids)) for _, id := range ids { if id == 0 { if requireAll { return errors.New("您提交的用户ID不合法") } continue } unique[id] = struct{}{} } if len(unique) == 0 { return nil } userIDs := make([]uint, 0, len(unique)) for id := range unique { userIDs = append(userIDs, id) } var users []userPO if err := tx.Select("id", "authority_id").Where("id IN ?", userIDs).Find(&users).Error; err != nil { return err } if requireAll && len(users) != len(unique) { return errors.New("您提交的用户ID不合法") } managedUsers := make(map[uint]bool, len(users)) for _, user := range users { if !allowedAuthorities[user.AuthorityID] { return errors.New("您提交的用户ID不合法") } managedUsers[user.ID] = true } if len(managedUsers) == 0 { return nil } var links []userAuthorityPO if err := tx.Where("sys_user_id IN ?", userIDs).Find(&links).Error; err != nil { return err } for _, link := range links { if managedUsers[link.SysUserID] && !allowedAuthorities[link.SysAuthorityAuthorityID] { return errors.New("您提交的用户ID不合法") } } return nil } func (r *authorityAccessRepo) checkUserIDAuth(ctx context.Context, id uint, allowSelf bool) error { actor, allowed, strict, err := r.strictAuthorityAccess(ctx) if err != nil || !strict { return err } if allowSelf && actor.UserID != 0 && actor.UserID == id { return nil } return checkManagedUserIDs(r.data.DB().WithContext(ctx), []uint{id}, allowed, true) } func (r *authorityAccessRepo) checkDepartmentIDsAuth(ctx context.Context, ids []uint) error { actor, allowedAuthorities, strict, err := r.strictAuthorityAccess(ctx) if err != nil || !strict || len(ids) == 0 { return err } if allowedAuthorities[actor.AuthorityID] { return nil } var authority authorityPO if err := r.data.DB().WithContext(ctx).Select("authority_id", "data_scope").Where("authority_id = ?", actor.AuthorityID).First(&authority).Error; err != nil { return err } if authority.DataScope == 1 { return nil } scope, err := r.ResolveDataScope(ctx, actor.AuthorityID, actor.UserID) if err != nil { return err } visible := make(map[uint]bool, len(scope.DepartmentIDs)) for _, id := range scope.DepartmentIDs { visible[id] = true } for _, id := range ids { if id == 0 || !visible[id] { return errors.New("您提交的部门ID不合法") } } return nil } func (r *authorityAccessRepo) AuthorityUserIDs(ctx context.Context, id uint) ([]uint, error) { var ids []uint err := r.data.DB().WithContext(ctx).Model(&userAuthorityPO{}).Where("sys_authority_authority_id = ?", id).Pluck("sys_user_id", &ids).Error return ids, err } func (r *authorityAccessRepo) SetDataScope(ctx context.Context, id uint, scope int, deptIDs []uint) error { if err := r.checkAuthorityIDAuth(ctx, id); err != nil { return err } if scope < 1 || scope > 5 { return errInvalidDataScope } if err := r.checkDataScopeGrant(ctx, scope, deptIDs); err != nil { return err } return r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error { if err := tx.Model(&authorityPO{}).Where("authority_id = ?", id).Update("data_scope", scope).Error; err != nil { return err } if err := tx.Where("sys_authority_authority_id = ?", id).Delete(&authorityDepartmentPO{}).Error; err != nil { return err } if scope == 5 { links := make([]authorityDepartmentPO, 0, len(deptIDs)) for _, did := range deptIDs { links = append(links, authorityDepartmentPO{AuthorityID: id, DepartmentID: did}) } if len(links) > 0 { return tx.Create(&links).Error } } return nil }) } func (r *authorityAccessRepo) checkDataScopeGrant(ctx context.Context, requestedScope int, departmentIDs []uint) error { actor, allowedAuthorities, strict, err := r.strictAuthorityAccess(ctx) if err != nil || !strict { return err } requestedDepartments := make(map[uint]struct{}, len(departmentIDs)) if requestedScope == 5 { for _, departmentID := range departmentIDs { if departmentID == 0 { return errors.New("您提交的部门ID不合法") } requestedDepartments[departmentID] = struct{}{} } if len(requestedDepartments) > 0 { var count int64 if err := r.data.DB().WithContext(ctx).Model(&departmentPO{}).Where("id IN ?", departmentIDs).Count(&count).Error; err != nil { return err } if count != int64(len(requestedDepartments)) { return errors.New("您提交的部门ID不合法") } } } if allowedAuthorities[actor.AuthorityID] { return nil } var actorAuthority authorityPO if err := r.data.DB().WithContext(ctx).Select("authority_id", "data_scope").Where("authority_id = ?", actor.AuthorityID).First(&actorAuthority).Error; err != nil { return err } grantable := false switch actorAuthority.DataScope { case 1: grantable = true case 2: grantable = requestedScope == 2 || requestedScope == 3 || requestedScope == 4 || requestedScope == 5 case 3: grantable = requestedScope == 3 || requestedScope == 4 || requestedScope == 5 case 4: grantable = requestedScope == 4 case 5: grantable = requestedScope == 4 || requestedScope == 5 default: return errInvalidDataScope } if !grantable { return errInvalidDataScope } if requestedScope != 5 || len(requestedDepartments) == 0 || actorAuthority.DataScope == 1 { return nil } actorScope, err := r.ResolveDataScope(ctx, actor.AuthorityID, actor.UserID) if err != nil { return err } allowedDepartments := make(map[uint]bool, len(actorScope.DepartmentIDs)) for _, departmentID := range actorScope.DepartmentIDs { allowedDepartments[departmentID] = true } for departmentID := range requestedDepartments { if !allowedDepartments[departmentID] { return errors.New("您提交的部门ID不合法") } } return nil } func (r *authorityAccessRepo) DataScopeDepartmentIDs(ctx context.Context, id uint) ([]uint, error) { var ids []uint err := r.data.DB().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) (system.DataScope, error) { identity := system.DataScope{UserID: userID, AuthorityID: authorityID} var user userPO if err := r.data.DB().WithContext(ctx).Select("id", "dept_id").First(&user, userID).Error; err != nil { return identity, err } identity.PrimaryDeptID = user.DeptID var authority authorityPO if err := r.data.DB().WithContext(ctx).Select("authority_id", "data_scope").First(&authority, "authority_id = ?", authorityID).Error; err != nil { return identity, err } identity.Scope = authority.DataScope if identity.Scope < 1 || identity.Scope > 5 { return identity, errInvalidDataScope } identity.All = identity.Scope == 1 if identity.Scope == 4 { identity.OwnerUserID = userID } var ids []uint selected := make(map[uint]bool) if identity.Scope == 2 || identity.Scope == 3 { if err := r.data.DB().WithContext(ctx).Model(&userDepartmentPO{}).Where("sys_user_id = ?", userID).Pluck("sys_department_id", &ids).Error; err != nil { return identity, err } selected = make(map[uint]bool, len(ids)+1) for _, id := range ids { selected[id] = true } if user.DeptID != 0 { selected[user.DeptID] = true } ids = ids[:0] for id := range selected { ids = append(ids, id) } } if identity.Scope == 2 && len(ids) > 0 { // Match the administration subtree-union semantics: derive the visible set from // parent_id edges at read time, rather than trusting the denormalized // ancestors string (which may be stale after a department move). var departments []departmentPO if err := r.data.DB().WithContext(ctx).Select("id", "parent_id").Find(&departments).Error; err != nil { return identity, err } children := make(map[uint][]uint, len(departments)) for _, department := range departments { children[department.ParentID] = append(children[department.ParentID], department.ID) } queue := make([]uint, 0, len(selected)) for id := range selected { queue = append(queue, id) } visited := make(map[uint]struct{}, len(selected)) for len(queue) > 0 { current := queue[0] queue = queue[1:] if _, ok := visited[current]; ok { continue } visited[current] = struct{}{} for _, child := range children[current] { if _, ok := visited[child]; !ok { queue = append(queue, child) } } } ids = ids[:0] for id := range visited { ids = append(ids, id) } } else if identity.Scope == 5 { var err error ids, err = r.DataScopeDepartmentIDs(ctx, authorityID) if err != nil { return identity, err } } identity.DepartmentIDs = ids return identity, nil }