feat(ram): RAMGrant 建表迁移 + 菜单过滤 RAM 角色粒度集成(无策略保持全员可见)

This commit is contained in:
TermiNexus committed 2026-08-13 23:55:55 +08:00
1 parent feaa783885
commit 345abe88bb
2 files changed
+98 -9

No files matched your search

@@ -0,0 +1,43 @@
"""创建 RAMGrant 表——RAM 资源访问管理(Resource Access Management)授权记录
表由 gvsds 主项目管理(主库),gvsdsdk RAMGrant 模型为 managed=False 映射。
字段对齐 gvsdsdk/models.py RAMGrant:
主体 Principal(role/user)× 资源 Resource(app/menu/data_scope)× 操作 Action
(view/use/manage/grant)× 作用域(TenantUUID/CompanyUUID,null=全范围)
"""
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('gerp', '0029_fk_to_uuidfield'),
]
operations = [
migrations.RunSQL(
sql="""
CREATE TABLE IF NOT EXISTS `RAMGrant` (
`GrantUUID` BINARY(16) NOT NULL PRIMARY KEY,
`PrincipalType` VARCHAR(16) NOT NULL,
`PrincipalUUID` BINARY(16) NOT NULL,
`ResourceType` VARCHAR(32) NOT NULL,
`ResourceUUID` BINARY(16) NOT NULL,
`Action` VARCHAR(16) NOT NULL,
`TenantUUID` BINARY(16) NULL,
`CompanyUUID` BINARY(16) NULL,
`GrantStatus` SMALLINT NOT NULL DEFAULT 1,
`GrantBy` BINARY(16) NULL,
`GrantTime` DATETIME NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE UNIQUE INDEX `uniq_ram_grant` ON `RAMGrant`
(`PrincipalType`, `PrincipalUUID`, `ResourceType`, `ResourceUUID`, `Action`, `TenantUUID`, `CompanyUUID`);
CREATE INDEX `idx_ram_principal` ON `RAMGrant` (`PrincipalUUID`, `ResourceType`);
CREATE INDEX `idx_ram_resource` ON `RAMGrant` (`ResourceType`, `ResourceUUID`);
""",
reverse_sql="""
DROP TABLE IF EXISTS `RAMGrant`;
""",
),
]
+55 -9
View File
@@ -163,7 +163,10 @@ class MenuListCompatView(ELTAuthenticatedAPIView):
# 未安装应用(租户/公司/个人均未启用)的 Manifest 声明的全部菜单一律隐藏,
# 含散挂在其他父节点下的页面(如"邮箱管理"挂公司下)。
# 管理员仅应用中心管理(安装/停用)豁免,业务菜单同样按安装状态隐藏。
# RAM 角色粒度:配置了 RAM 策略(存在 app 级 view 授权)的 App,
# 仅对获得授权的角色可见;无策略的 App 保持全员可见(向后兼容)。
try:
from django.db.models import Q as _DJQ
from ..models import App
from ..app_manager import AppManager
from ..app_manifests import GetAppManifest
@@ -175,22 +178,25 @@ class MenuListCompatView(ELTAuthenticatedAPIView):
if _role_obj is not None:
current_tenant_uuid = _role_obj.TenantUUID
def _ToUUIDBytes(value):
if value is None:
return None
if hasattr(value, 'bytes'):
return value.bytes
if isinstance(value, bytes):
return value
return value
def _CollectAppSub(uuid_str, acc):
acc.add(uuid_str)
for uid, item in menu_map.items():
if item['ParentUUID'] == uuid_str and uid not in acc:
_CollectAppSub(uid, acc)
for _app in App.objects.filter(AppStatus=1):
if AppManager.CheckAccess(
current_tenant_uuid, user.UserUUID, _app.AppCode,
CompanyUUID=current_company,
) is not None:
continue
manifest = GetAppManifest(_app.AppCode)
def _HideAppMenus(app_code, manifest):
"""收集并移除指定应用 Manifest 声明的全部菜单(含子节点)"""
if manifest is None:
continue
# 收集该应用 Manifest 声明的全部菜单(MenuName 匹配 Id 字段)
return
app_menu_names = {s.MenuName for s in manifest.Menus}
_invalid = set()
for _uid, _item in menu_map.items():
@@ -198,6 +204,46 @@ class MenuListCompatView(ELTAuthenticatedAPIView):
_CollectAppSub(_uid, _invalid)
for _u in _invalid:
menu_map.pop(_u, None)
# RAM 策略/授权集合(bytes 维度,一次查询取齐)
ram_policy_apps = set()
ram_granted_apps = set()
try:
from gvsdsdk.models import RAMGrant as _SdkRAMGrant
from gvsdsdk.auth import RAMManager as _SdkRAMManager
_tenant_bytes = _ToUUIDBytes(current_tenant_uuid)
_company_bytes = _ToUUIDBytes(current_company)
ram_qs = _SdkRAMGrant.objects.filter(
ResourceType='app', Action='view', GrantStatus=1,
)
if _tenant_bytes is not None:
ram_qs = ram_qs.filter(
_DJQ(TenantUUID__isnull=True) | _DJQ(TenantUUID=_tenant_bytes))
if _company_bytes is not None:
ram_qs = ram_qs.filter(
_DJQ(CompanyUUID__isnull=True) | _DJQ(CompanyUUID=_company_bytes))
ram_policy_apps = set(ram_qs.values_list('ResourceUUID', flat=True))
if ram_policy_apps and role_uuid_bytes:
ram_granted_apps = set(_SdkRAMManager.ListResourcesForPrincipal(
role_uuid_bytes, 'app', 'view',
_tenant_bytes, _company_bytes,
))
except Exception:
logger.exception('MenuListCompatView RAM 策略解析失败')
for _app in App.objects.filter(AppStatus=1):
_app_uuid_bytes = _ToUUIDBytes(_app.AppUUID)
if AppManager.CheckAccess(
current_tenant_uuid, user.UserUUID, _app.AppCode,
CompanyUUID=current_company,
) is None:
# 未安装 → 隐藏(现状)
_HideAppMenus(_app.AppCode, GetAppManifest(_app.AppCode))
continue
# 已安装 → RAM 角色粒度过滤:配置了策略但当前角色未授权 → 隐藏
if (_app_uuid_bytes in ram_policy_apps
and _app_uuid_bytes not in ram_granted_apps):
_HideAppMenus(_app.AppCode, GetAppManifest(_app.AppCode))
except Exception:
logger.exception('MenuListCompatView 应用菜单过滤失败')