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 }