chore: 并入 iotlcm-admin-svr 历史到 admin-svr/(原提交 49818c6)
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
package rolepolicy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/entity"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
)
|
||||
|
||||
func WithMenuAncestors(ctx context.Context, ids []int64) ([]int64, error) {
|
||||
var (
|
||||
seen = make(map[int64]bool, len(ids))
|
||||
queue = slices.Clone(ids)
|
||||
)
|
||||
for _, id := range ids {
|
||||
seen[id] = true
|
||||
}
|
||||
for len(queue) > 0 {
|
||||
var menus []*entity.SysMenu
|
||||
if err := dao.SysMenu.Ctx(ctx).Master().WhereIn("menu_id", queue).Where("status", consts.SysMenuStatusNormal).Scan(&menus); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
queue = nil
|
||||
for _, menu := range menus {
|
||||
if menu.ParentId > 0 && !seen[menu.ParentId] {
|
||||
seen[menu.ParentId] = true
|
||||
queue = append(queue, menu.ParentId)
|
||||
}
|
||||
}
|
||||
}
|
||||
result := make([]int64, 0, len(seen))
|
||||
for id := range seen {
|
||||
result = append(result, id)
|
||||
}
|
||||
slices.Sort(result)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func NormalizeAssignableMenus(ctx context.Context, tenantID string, deptID, userID int64, ids []int64) ([]int64, error) {
|
||||
allowed, err := RestrictAssignableMenus(ctx, tenantID, deptID, userID, ids)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(allowed) > 0 {
|
||||
var menus []*entity.SysMenu
|
||||
if err := dao.SysMenu.Ctx(ctx).Master().WhereIn("menu_id", allowed).Where("status", consts.SysMenuStatusNormal).Scan(&menus); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
allowed = make([]int64, 0, len(menus))
|
||||
for _, menu := range menus {
|
||||
allowed = append(allowed, menu.MenuId)
|
||||
}
|
||||
}
|
||||
visible, err := WithMenuAncestors(ctx, allowed)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, id := range ids {
|
||||
if !slices.Contains(visible, id) {
|
||||
return nil, gerror.Newf("角色授权超出机构或模板范围,或菜单已停用(菜单ID:%d),请刷新授权列表后重试", id)
|
||||
}
|
||||
}
|
||||
slices.Sort(allowed)
|
||||
return slices.Compact(allowed), nil
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package rolepolicy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
_ "github.com/gogf/gf/contrib/drivers/sqlite/v2"
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
func TestNormalizeAssignableMenus(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
ids []int64
|
||||
mutation string
|
||||
want []int64
|
||||
wantError bool
|
||||
}{
|
||||
{name: "补齐多级父节点但不把父权限写入角色", ids: []int64{1, 10, 100}, want: []int64{100}},
|
||||
{name: "重复选择去重", ids: []int64{100, 100}, want: []int64{100}},
|
||||
{name: "空选择不自动授权", ids: []int64{}, want: []int64{}},
|
||||
{name: "无关菜单仍拒绝", ids: []int64{100, 200}, wantError: true},
|
||||
{name: "父节点单独不能获得权限", ids: []int64{10}, wantError: true},
|
||||
{name: "不存在菜单仍拒绝", ids: []int64{999}, wantError: true},
|
||||
{name: "停用菜单不能继续下发", ids: []int64{100}, mutation: "UPDATE sys_menu SET status='1' WHERE menu_id=100", wantError: true},
|
||||
{name: "模板保存前后不裁剪已下发权限", ids: []int64{100}, mutation: "DELETE FROM sys_role_menu WHERE role_id=82 AND menu_id=100", want: []int64{100}},
|
||||
{name: "上级角色调整不裁剪本机构权限", ids: []int64{100}, mutation: "DELETE FROM sys_role_menu WHERE role_id=120 AND menu_id=100", want: []int64{100}},
|
||||
{name: "本机构默认角色撤权后不能继续下放", ids: []int64{100}, mutation: "DELETE FROM sys_role_menu WHERE role_id=121", wantError: true},
|
||||
{name: "操作员无权限不能借机构权限授权", ids: []int64{100}, mutation: "DELETE FROM sys_role_menu WHERE role_id=122", wantError: true},
|
||||
{name: "操作员角色停用不能授权", ids: []int64{100}, mutation: "UPDATE sys_role SET status='1' WHERE role_id=122", wantError: true},
|
||||
{name: "菜单循环不会无限遍历", ids: []int64{1, 10, 100}, mutation: "UPDATE sys_menu SET parent_id=100 WHERE menu_id=1", want: []int64{100}},
|
||||
}
|
||||
for _, test := range cases {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
setupAssignableTestDB(t)
|
||||
ctx := context.Background()
|
||||
if test.mutation != "" {
|
||||
if _, err := g.DB().Exec(ctx, test.mutation); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
got, err := NormalizeAssignableMenus(ctx, "000000", 199, 162, test.ids)
|
||||
if test.wantError {
|
||||
if err == nil || !strings.Contains(err.Error(), "菜单ID") {
|
||||
t.Fatalf("expected actionable denial, got %v, %v", got, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil || !reflect.DeepEqual(got, test.want) {
|
||||
t.Fatalf("got %v, %v; want %v", got, err, test.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func setupAssignableTestDB(t *testing.T) {
|
||||
t.Helper()
|
||||
if err := gdb.SetConfig(gdb.Config{"default": {{Type: "sqlite", Link: "sqlite::@file(:memory:)"}}}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ctx := context.Background()
|
||||
t.Cleanup(func() { _ = g.DB().Close(ctx) })
|
||||
for _, statement := range []string{
|
||||
"CREATE TABLE sys_config (tenant_id TEXT, config_key TEXT, config_value TEXT)",
|
||||
"CREATE TABLE sys_role (role_id INTEGER PRIMARY KEY, tenant_id TEXT, dept_id INTEGER, created_dept INTEGER, status TEXT, data_scope TEXT, deleted_at DATETIME)",
|
||||
"CREATE TABLE sys_dept (dept_id INTEGER PRIMARY KEY, tenant_id TEXT, parent_id INTEGER, dept_role INTEGER, status TEXT, deleted_at DATETIME)",
|
||||
"CREATE TABLE sys_role_menu (tenant_id TEXT, role_id INTEGER, menu_id INTEGER, data_scope TEXT)",
|
||||
"CREATE TABLE sys_user_role (tenant_id TEXT, user_id INTEGER, role_id INTEGER)",
|
||||
"CREATE TABLE sys_menu (menu_id INTEGER PRIMARY KEY, parent_id INTEGER, status TEXT, perms TEXT)",
|
||||
"INSERT INTO sys_config VALUES ('000000','agent.role.template.role_id','82')",
|
||||
"INSERT INTO sys_dept VALUES (100,'000000',0,1,'0',NULL),(198,'000000',100,120,'0',NULL),(199,'000000',198,121,'0',NULL)",
|
||||
"INSERT INTO sys_role VALUES (82,'000000',100,100,'0','4',NULL),(120,'000000',198,198,'0','4',NULL),(121,'000000',199,199,'0','4',NULL),(122,'000000',199,199,'0','4',NULL)",
|
||||
"INSERT INTO sys_menu VALUES (1,0,'0',''),(10,1,'0','system:parent:list'),(100,10,'0','system:child:view'),(200,1,'0','system:other:view')",
|
||||
"INSERT INTO sys_role_menu VALUES ('000000',82,100,'0'),('000000',120,100,'0'),('000000',121,100,'0'),('000000',122,100,'0')",
|
||||
"INSERT INTO sys_user_role VALUES ('000000',162,122)",
|
||||
} {
|
||||
if _, err := g.DB().Exec(ctx, statement); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Runtime permissions come from the user's roles; delegation additionally uses
|
||||
// the organization's current default role, never the template's menu list.
|
||||
func TestIndividualRolePermissionsAndDelegation(t *testing.T) {
|
||||
setupAssignableTestDB(t)
|
||||
ctx := context.Background()
|
||||
for _, statement := range []string{
|
||||
"INSERT INTO sys_role_menu VALUES ('000000',122,200,'0')",
|
||||
"DELETE FROM sys_role_menu WHERE role_id IN (82,120) AND menu_id=100",
|
||||
} {
|
||||
if _, err := g.DB().Exec(ctx, statement); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
got, err := RestrictMenus(ctx, "000000", 199, []int64{100, 200})
|
||||
if err != nil || !reflect.DeepEqual(got, []int64{100, 200}) {
|
||||
t.Fatalf("individual role expansion must survive runtime checks: %v %v", got, err)
|
||||
}
|
||||
if _, err := NormalizeAssignableMenus(ctx, "000000", 199, 162, []int64{200}); err == nil {
|
||||
t.Fatal("operator cannot delegate outside local default role")
|
||||
}
|
||||
if _, err := g.DB().Exec(ctx, "INSERT INTO sys_role_menu VALUES ('000000',121,200,'0')"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got, err = NormalizeAssignableMenus(ctx, "000000", 199, 162, []int64{200})
|
||||
if err != nil || !reflect.DeepEqual(got, []int64{200}) {
|
||||
t.Fatalf("platform expansion of local default role must become delegable: %v %v", got, err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package rolepolicy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/entity"
|
||||
)
|
||||
|
||||
// ValidateUserRoleAssignment prevents local operators from obtaining platform-only
|
||||
// permissions by binding a more privileged role instead of editing its menus.
|
||||
// Existing bindings may be retained when editing unrelated user information.
|
||||
func ValidateUserRoleAssignment(ctx context.Context, tenantID string, deptID int64, requested, existing []int64) error {
|
||||
if contexts.IsSuperAdmin(ctx) {
|
||||
return nil
|
||||
}
|
||||
template, err := Template(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if template == nil || tenantID != template.TenantId {
|
||||
return nil
|
||||
}
|
||||
if contexts.GetTenantId(ctx) != tenantID {
|
||||
return gerror.New("不能跨租户分配角色")
|
||||
}
|
||||
for _, roleID := range requested {
|
||||
if roleID == consts.SuperAdminRoleId {
|
||||
return gerror.New("不能分配平台超级管理员角色")
|
||||
}
|
||||
if slices.Contains(existing, roleID) {
|
||||
continue
|
||||
}
|
||||
var role *entity.SysRole
|
||||
if err := dao.SysRole.Ctx(ctx).Master().Where("tenant_id", tenantID).Where("dept_id", deptID).Where("role_id", roleID).Where("status", "0").Scan(&role); err != nil {
|
||||
return err
|
||||
}
|
||||
if role == nil {
|
||||
return gerror.Newf("角色%d不存在、已停用或不属于用户所在机构", roleID)
|
||||
}
|
||||
menus, err := MenuIDs(ctx, roleID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := NormalizeAssignableMenus(ctx, tenantID, contexts.GetDeptId(ctx), contexts.GetUserId(ctx), menus); err != nil {
|
||||
return gerror.Wrapf(err, "角色%d包含超出当前账号可分配范围的权限,请联系平台管理员绑定", roleID)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package rolepolicy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
|
||||
)
|
||||
|
||||
func TestUserRoleAssignmentCannotBypassDelegation(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
requested, existing []int64
|
||||
super, denied bool
|
||||
}{
|
||||
{name: "可分配的普通角色", requested: []int64{124}},
|
||||
{name: "不能通过绑定特殊角色扩权", requested: []int64{123}, denied: true},
|
||||
{name: "修改资料允许保留平台已有授权", requested: []int64{123}, existing: []int64{123}},
|
||||
{name: "平台可以单独绑定特殊角色", requested: []int64{123}, super: true},
|
||||
{name: "本地账号不能授予超级管理员角色", requested: []int64{1}, denied: true},
|
||||
{name: "不接受其他机构角色", requested: []int64{120}, denied: true},
|
||||
{name: "可以移除已有角色", requested: []int64{}, existing: []int64{123}},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
setupAssignableTestDB(t)
|
||||
actor := &model.Identity{BaseClaims: model.BaseClaims{ID: 162, TenantId: "000000", DeptId: 199}}
|
||||
if test.super {
|
||||
actor.Roles = []model.Role{{RoleId: consts.SuperAdminRoleId}}
|
||||
}
|
||||
ctx := contexts.Set(context.Background(), &model.Context{User: actor})
|
||||
for _, statement := range []string{
|
||||
"INSERT INTO sys_role VALUES(123,'000000',199,199,'0','4',NULL),(124,'000000',199,199,'0','4',NULL)",
|
||||
"INSERT INTO sys_role_menu VALUES('000000',123,200,'0'),('000000',124,100,'0')",
|
||||
} {
|
||||
if _, err := g.DB().Exec(ctx, statement); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
err := ValidateUserRoleAssignment(ctx, "000000", 199, test.requested, test.existing)
|
||||
if (err != nil) != test.denied {
|
||||
t.Fatalf("denied=%v, error=%v", test.denied, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
package rolepolicy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/entity"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const TemplateConfigKey = "agent.role.template.role_id"
|
||||
|
||||
func TemplateForRoles(ctx context.Context, roleIDs ...int64) (*entity.SysRole, error) {
|
||||
var configs []*entity.SysConfig
|
||||
if err := dao.SysConfig.Ctx(ctx).Master().Where("tenant_id", consts.DefaultSystemTenantCode).Where("config_key", TemplateConfigKey).Scan(&configs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, config := range configs {
|
||||
roleID, err := strconv.ParseInt(strings.TrimSpace(config.ConfigValue), 10, 64)
|
||||
if err != nil || roleID <= 0 {
|
||||
continue
|
||||
}
|
||||
for _, targetID := range roleIDs {
|
||||
if targetID != roleID {
|
||||
continue
|
||||
}
|
||||
if len(configs) != 1 {
|
||||
return nil, gerror.New("代理模板角色系统参数重复")
|
||||
}
|
||||
var role *entity.SysRole
|
||||
if err := dao.SysRole.Ctx(ctx).Master().Where("role_id", roleID).Where("tenant_id", consts.DefaultSystemTenantCode).Scan(&role); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if role == nil {
|
||||
role = &entity.SysRole{RoleId: roleID, TenantId: consts.DefaultSystemTenantCode}
|
||||
}
|
||||
return role, nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func Template(ctx context.Context) (*entity.SysRole, error) {
|
||||
var configs []*entity.SysConfig
|
||||
if err := dao.SysConfig.Ctx(ctx).Master().Where("tenant_id", consts.DefaultSystemTenantCode).Where("config_key", TemplateConfigKey).Scan(&configs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(configs) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
if len(configs) != 1 {
|
||||
return nil, gerror.New("代理模板角色系统参数重复")
|
||||
}
|
||||
return ValidateTemplate(ctx, configs[0].ConfigValue)
|
||||
}
|
||||
|
||||
func ValidateTemplate(ctx context.Context, value string) (*entity.SysRole, error) {
|
||||
roleID, err := strconv.ParseInt(strings.TrimSpace(value), 10, 64)
|
||||
if err != nil || roleID <= 0 || roleID == consts.SuperAdminRoleId {
|
||||
return nil, gerror.New("代理模板参数必须填写非超级管理员的有效角色ID")
|
||||
}
|
||||
var role *entity.SysRole
|
||||
if err = dao.SysRole.Ctx(ctx).Master().Where("role_id", roleID).Where("tenant_id", consts.DefaultSystemTenantCode).Scan(&role); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if role == nil || role.Status != "0" || (role.CreatedDept > 0 && role.CreatedDept != role.DeptId) || role.DeptId <= 0 {
|
||||
return nil, gerror.New("代理模板参数对应角色须正常启用、绑定本级机构且非上级继承角色")
|
||||
}
|
||||
if role.DataScope != consts.SysRoleDataScopeDeptAndBelow {
|
||||
return nil, gerror.New("代理模板数据范围必须为本机构及下级")
|
||||
}
|
||||
var dept *entity.SysDept
|
||||
if err := dao.SysDept.Ctx(ctx).Master().Where("tenant_id", role.TenantId).Where("dept_id", role.DeptId).Where("status", "0").Scan(&dept); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if dept == nil {
|
||||
return nil, gerror.New("代理模板所属机构不存在或已停用")
|
||||
}
|
||||
invalidScopes, err := dao.SysRoleMenu.Ctx(ctx).Master().Where("tenant_id", role.TenantId).Where("role_id", role.RoleId).WhereIn("data_scope", []string{consts.SysRoleDataScopeAll, consts.SysRoleDataScopeCustom}).Count()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if invalidScopes > 0 {
|
||||
return nil, gerror.New("代理模板按钮不允许全部或自定义数据范围")
|
||||
}
|
||||
return role, nil
|
||||
}
|
||||
|
||||
func MenuIDs(ctx context.Context, roleID int64) ([]int64, error) {
|
||||
var menus []*entity.SysRoleMenu
|
||||
if err := dao.SysRoleMenu.Ctx(ctx).Master().Where("role_id", roleID).Scan(&menus); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ids := make([]int64, 0, len(menus))
|
||||
for _, menu := range menus {
|
||||
ids = append(ids, menu.MenuId)
|
||||
}
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
func Intersect(left, right []int64) []int64 {
|
||||
allowed := make(map[int64]bool, len(right))
|
||||
for _, id := range right {
|
||||
allowed[id] = true
|
||||
}
|
||||
result := make([]int64, 0, len(left))
|
||||
for _, id := range left {
|
||||
if allowed[id] {
|
||||
result = append(result, id)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func CurrentSuperAdmin(ctx context.Context, userID int64) (bool, error) {
|
||||
if userID <= 0 {
|
||||
return false, nil
|
||||
}
|
||||
var relations []*entity.SysUserRole
|
||||
if err := dao.SysUserRole.Ctx(ctx).Master().Where("tenant_id", consts.DefaultSystemTenantCode).Where("user_id", userID).Where("role_id", consts.SuperAdminRoleId).Scan(&relations); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if len(relations) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
var role *entity.SysRole
|
||||
if err := dao.SysRole.Ctx(ctx).Master().Where("tenant_id", consts.DefaultSystemTenantCode).Where("role_id", consts.SuperAdminRoleId).Where("status", "0").Scan(&role); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return role != nil, nil
|
||||
}
|
||||
|
||||
// RestrictMenus validates organization membership and role bindings. Templates are
|
||||
// overwrite sources, not runtime permission ceilings: platform role edits must
|
||||
// remain effective until the next explicit template distribution.
|
||||
func RestrictMenus(ctx context.Context, tenantID string, deptID int64, ids []int64) ([]int64, error) {
|
||||
template, err := Template(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if template == nil {
|
||||
return ids, nil
|
||||
}
|
||||
seen := make(map[int64]bool)
|
||||
for deptID > 0 {
|
||||
if seen[deptID] {
|
||||
return nil, gerror.New("机构上下级存在循环")
|
||||
}
|
||||
seen[deptID] = true
|
||||
var dept *entity.SysDept
|
||||
if err := dao.SysDept.Ctx(ctx).Master().Where("tenant_id", tenantID).Where("dept_id", deptID).Scan(&dept); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if dept == nil || dept.Status != "0" {
|
||||
return []int64{}, nil
|
||||
}
|
||||
if tenantID == template.TenantId && deptID == template.DeptId {
|
||||
break
|
||||
}
|
||||
if dept.DeptRole == consts.SuperAdminRoleId {
|
||||
return nil, gerror.Newf("机构%d不能使用超级管理员角色作为代理权限上限,请核对机构角色绑定", dept.DeptId)
|
||||
}
|
||||
if dept.DeptRole <= 0 {
|
||||
return nil, gerror.Newf("机构%d未关联默认角色,请联系平台管理员核对机构角色绑定", dept.DeptId)
|
||||
}
|
||||
var role *entity.SysRole
|
||||
if err := dao.SysRole.Ctx(ctx).Master().Where("tenant_id", tenantID).Where("dept_id", deptID).Where("role_id", dept.DeptRole).Where("status", "0").Scan(&role); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if role == nil {
|
||||
return nil, gerror.Newf("机构%d默认角色不存在、已停用或归属不匹配,请联系平台管理员核对", dept.DeptId)
|
||||
}
|
||||
deptID = dept.ParentId
|
||||
}
|
||||
if tenantID == template.TenantId && !seen[template.DeptId] {
|
||||
return []int64{}, nil
|
||||
}
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
func RestrictAssignableMenus(ctx context.Context, tenantID string, deptID, userID int64, ids []int64) ([]int64, error) {
|
||||
allowed, err := RestrictMenus(ctx, tenantID, deptID, ids)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Local administrators can delegate only their organization's current default
|
||||
// role permissions and their own active permissions. Ancestor/template menus
|
||||
// do not cap a platform-authorized individual expansion.
|
||||
template, err := Template(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if template != nil && (tenantID != template.TenantId || deptID != template.DeptId) {
|
||||
var dept *entity.SysDept
|
||||
if err := dao.SysDept.Ctx(ctx).Master().Where("tenant_id", tenantID).Where("dept_id", deptID).Scan(&dept); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if dept == nil {
|
||||
return []int64{}, nil
|
||||
}
|
||||
menus, err := MenuIDs(ctx, dept.DeptRole)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
allowed = Intersect(allowed, menus)
|
||||
}
|
||||
var relations []*entity.SysUserRole
|
||||
if err := dao.SysUserRole.Ctx(ctx).Master().Where("tenant_id", tenantID).Where("user_id", userID).Scan(&relations); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
roleIDs := make([]int64, 0, len(relations))
|
||||
for _, relation := range relations {
|
||||
if relation.RoleId != consts.SuperAdminRoleId {
|
||||
roleIDs = append(roleIDs, relation.RoleId)
|
||||
}
|
||||
}
|
||||
if len(roleIDs) == 0 {
|
||||
return []int64{}, nil
|
||||
}
|
||||
var roles []*entity.SysRole
|
||||
if err := dao.SysRole.Ctx(ctx).Master().Where("tenant_id", tenantID).WhereIn("role_id", roleIDs).Where("status", "0").Scan(&roles); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
roleIDs = roleIDs[:0]
|
||||
for _, role := range roles {
|
||||
roleIDs = append(roleIDs, role.RoleId)
|
||||
}
|
||||
if len(roleIDs) == 0 {
|
||||
return []int64{}, nil
|
||||
}
|
||||
var menus []*entity.SysRoleMenu
|
||||
if err := dao.SysRoleMenu.Ctx(ctx).Master().Where("tenant_id", tenantID).WhereIn("role_id", roleIDs).Scan(&menus); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
owned := make([]int64, 0, len(menus))
|
||||
for _, menu := range menus {
|
||||
owned = append(owned, menu.MenuId)
|
||||
}
|
||||
return Intersect(allowed, owned), nil
|
||||
}
|
||||
Reference in New Issue
Block a user