117 lines
5.3 KiB
Python
117 lines
5.3 KiB
Python
"""scripts/load_path.py — pbl_domain_ext(M8)RBAC 路径注册。
|
||
|
||
规则(module-development-spec):新增契约必须在此显式注册 .dspy/.ui 路径,
|
||
**禁止通配符**('*' / 前缀模糊匹配一律不允许),否则新端点上线即 403。
|
||
路径与 wwwroot/ 下实际文件一一对应(13 个契约 dspy + 1 个 index.ui)。
|
||
"""
|
||
|
||
MODULE_NAME = 'pbl_domain_ext'
|
||
|
||
# 契约端点(设计 modules/pbl_domain_ext.md §3 的 13 个接口,一一对应)
|
||
API_PATHS = [
|
||
# §3.1 关联管理(5)
|
||
'/pbl_domain_ext/api/pbl_domain_ref_bind.dspy', # bind_ref
|
||
'/pbl_domain_ext/api/pbl_domain_ref_unbind.dspy', # unbind_ref
|
||
'/pbl_domain_ext/api/pbl_domain_ref_get.dspy', # get_ref
|
||
'/pbl_domain_ext/api/pbl_domain_ref_list.dspy', # list_refs
|
||
'/pbl_domain_ext/api/pbl_domain_ref_update.dspy', # update_ref
|
||
# §3.2 租户隔离查询封装(5)
|
||
'/pbl_domain_ext/api/pbl_world_list_by_tenant.dspy', # list_worlds_by_tenant
|
||
'/pbl_domain_ext/api/pbl_scene_list_by_world.dspy', # list_scenes_by_world
|
||
'/pbl_domain_ext/api/pbl_entity_list_by_scene.dspy', # list_entities_by_scene
|
||
'/pbl_domain_ext/api/pbl_world_get_context.dspy', # get_world_with_pbl_context
|
||
'/pbl_domain_ext/api/pbl_domain_ref_check_access.dspy', # check_ref_access
|
||
# §3.3 团队/班级维度(3)
|
||
'/pbl_domain_ext/api/pbl_team_list_by_class.dspy', # list_teams_by_class
|
||
'/pbl_domain_ext/api/pbl_team_bind_world.dspy', # bind_team_to_world
|
||
'/pbl_domain_ext/api/pbl_team_world_list.dspy', # get_team_worlds
|
||
]
|
||
|
||
# 前端页面
|
||
UI_PATHS = [
|
||
'/pbl_domain_ext/index.ui',
|
||
]
|
||
|
||
# 角色授权:教师(pbl_teacher)可管理关联;学生(pbl_student)只读查询 + 访问校验;
|
||
# 管理员(admin)全量。角色名沿用 pbl_governance / rbac 既有定义,本模块不新增角色。
|
||
ROLE_GRANTS = {
|
||
'/pbl_domain_ext/api/pbl_domain_ref_bind.dspy': ['admin', 'pbl_teacher'],
|
||
'/pbl_domain_ext/api/pbl_domain_ref_unbind.dspy': ['admin', 'pbl_teacher'],
|
||
'/pbl_domain_ext/api/pbl_domain_ref_get.dspy': ['admin', 'pbl_teacher', 'pbl_student'],
|
||
'/pbl_domain_ext/api/pbl_domain_ref_list.dspy': ['admin', 'pbl_teacher'],
|
||
'/pbl_domain_ext/api/pbl_domain_ref_update.dspy': ['admin', 'pbl_teacher'],
|
||
'/pbl_domain_ext/api/pbl_world_list_by_tenant.dspy': ['admin', 'pbl_teacher', 'pbl_student'],
|
||
'/pbl_domain_ext/api/pbl_scene_list_by_world.dspy': ['admin', 'pbl_teacher', 'pbl_student'],
|
||
'/pbl_domain_ext/api/pbl_entity_list_by_scene.dspy': ['admin', 'pbl_teacher', 'pbl_student'],
|
||
'/pbl_domain_ext/api/pbl_world_get_context.dspy': ['admin', 'pbl_teacher', 'pbl_student'],
|
||
'/pbl_domain_ext/api/pbl_domain_ref_check_access.dspy': ['admin', 'pbl_teacher', 'pbl_student'],
|
||
'/pbl_domain_ext/api/pbl_team_list_by_class.dspy': ['admin', 'pbl_teacher', 'pbl_student'],
|
||
'/pbl_domain_ext/api/pbl_team_bind_world.dspy': ['admin', 'pbl_teacher'],
|
||
'/pbl_domain_ext/api/pbl_team_world_list.dspy': ['admin', 'pbl_teacher', 'pbl_student'],
|
||
'/pbl_domain_ext/index.ui': ['admin', 'pbl_teacher', 'pbl_student'],
|
||
}
|
||
|
||
ALL_PATHS = API_PATHS + UI_PATHS
|
||
|
||
|
||
def get_paths():
|
||
"""返回本模块需注册的全部路径(供宿主 rbac 批量 load_path 使用)。"""
|
||
return list(ALL_PATHS)
|
||
|
||
|
||
def get_roles(path):
|
||
"""返回某路径的授权角色列表;未注册路径返回空(fail-closed,不放行)。"""
|
||
return list(ROLE_GRANTS.get(path, []))
|
||
|
||
|
||
def register(env=None):
|
||
"""向宿主 rbac 注册路径(幂等)。env 缺省自动取 ServerEnv。"""
|
||
if env is None:
|
||
try:
|
||
from ahserver.serverenv import ServerEnv # type: ignore
|
||
env = ServerEnv()
|
||
except Exception:
|
||
env = None
|
||
registered = []
|
||
for path in ALL_PATHS:
|
||
roles = ROLE_GRANTS.get(path, [])
|
||
if env is not None:
|
||
for name in ('load_path', 'rbac_load_path', 'add_path', 'register_path'):
|
||
func = getattr(env, name, None)
|
||
if callable(func):
|
||
try:
|
||
func(path, roles)
|
||
except TypeError:
|
||
func(path)
|
||
break
|
||
registered.append({'path': path, 'roles': roles, 'module': MODULE_NAME})
|
||
return registered
|
||
|
||
|
||
def selfcheck():
|
||
"""自检:路径无通配符、API/UI 全部有角色授权、无重复。"""
|
||
problems = []
|
||
seen = set()
|
||
for path in ALL_PATHS:
|
||
if '*' in path or '?' in path:
|
||
problems.append('通配符路径禁止注册: %s' % path)
|
||
if path in seen:
|
||
problems.append('重复注册: %s' % path)
|
||
seen.add(path)
|
||
if not ROLE_GRANTS.get(path):
|
||
problems.append('缺少角色授权: %s' % path)
|
||
if not path.startswith('/%s/' % MODULE_NAME):
|
||
problems.append('路径未落在模块前缀下: %s' % path)
|
||
return problems
|
||
|
||
|
||
if __name__ == '__main__':
|
||
issues = selfcheck()
|
||
print('module=%s paths=%d api=%d ui=%d' % (MODULE_NAME, len(ALL_PATHS),
|
||
len(API_PATHS), len(UI_PATHS)))
|
||
if issues:
|
||
for issue in issues:
|
||
print('FAIL %s' % issue)
|
||
raise SystemExit(1)
|
||
print('OK 全部路径显式注册、无通配符、角色授权齐备')
|