2026-10-06 探脈產出,唯讀盤點,供討論稿引用。jedi-iam 路徑以
~/Projects/Jedicogy/module/jedi-python-package/jedi-iam/jedi_iam/為根。
最需要注意的三點:
user_roles 有 org_unit_id 和 scope 兩欄,但判定只比 tenant_id。api/ 下 61 支掛 jwt_required 的 route 檔裡,33 支沒有任何能力點守門。 OSCAL SSP 那一整組 13 支都在其中。user_service.py:522-529),二是 JWT 的 is_admin claim(jwt_mw.py:79)。ORM model 全部在 jedi-iam 的 infra/models/,BE 這邊沒有。
| 表 | 檔:行 | 欄位 |
|---|---|---|
| capabilities | capability.py:15-27 |
id、name(100, UNIQUE)、resource_type(100)、action(50)、description、is_platform(bool) |
| role_capabilities | role_capability.py:14-23 |
role_id + capability_id 複合主鍵,兩邊都是 FK、CASCADE;沒有 pattern/wildcard 欄 |
| route_capabilities | route_capability.py:18-33 |
route_id + capability_id 複合主鍵,另有 requirement(ANY/ALL enum) |
| user_roles | user_role.py:20-84 |
id、user_id、role_id、tenant_id、org_unit_id、starts_at、ends_at(timestamptz)、scope(預設 'tenant')、is_default |
| user_roles 約束 | user_role.py:26-44 |
CHECK tenant_id 或 org_unit_id 至少一個不為空;兩條部分唯一索引:(user,role,tenant,starts_at) WHERE org_unit_id IS NULL,(user,role,org_unit,starts_at) WHERE tenant_id IS NULL |
| roles | role.py:23-84 |
uid、pid、name、enable、is_admin(int)、is_delete、tenant_id;role.capabilities 是 association_proxy(role.py:76-84) |
role_capabilities 的讀取端:
| 用途 | 檔:行 | 有沒有套 active_user_role_conditions |
|---|---|---|
| 判定 | jedi-iam infra/repository/user_role_repo_impl.py:73-101(get_active_capability_names) |
有 |
| 選單可見性 | jedi-iam infra/repository/ui_route_repo_impl.py:70-141(子查詢在 :87-103) |
有 |
| 選單明細(每個節點列出持有的 caps) | jedi-iam app/service/user_service.py:522-529, 547 |
沒有:直接迭代 user.roles → role.capabilities,不過濾效期、停用、租戶 |
JWT claim is_admin |
jedi-iam middleware/jwt_mw.py:79 |
沒有:any(role.is_admin == 1 for role in user.roles),不看 role_capabilities,但同樣不過濾 |
| 角色 CRUD 寫入 | jedi-iam domain/service/role_domain_service.py:88-90(新增)、:96-117(先刪光再重建) |
不適用 |
| 角色 CRUD 讀出 | jedi-iam infra/mapper/role_mapper.py:32、app/dto/role.py:55、app/service/role_service.py:76,91 |
不適用 |
| BE 角色 CRUD 授權檢查 | app/auth/service/role_app_service.py:41,49-51(讀既有角色的 existing.capabilities id)、:54-91(_assert_new_capabilities_licensed,用 get_capabilities_by_id_list 取 resource_type) |
不適用 |
| BE 其他 | core/plugins/evidence_classification.py:140-142(viewer_has_capability);common/authz/capability.py 只是轉接到 jedi-iam 的 shim;DI 接線在 di_containers/auth/auth_containers.py:57,100,122 |
— |
active_user_role_conditions:infra/repository/active_role_conditions.py:19-39。條件依序是:
UserRole.starts_at <= now()UserRole.ends_at IS NULL OR ends_at >= now():含等號,now() 取 DB 時間,欄位是 timestamptzRole.enable == 1Role.is_delete == 0UserRole.tenant_id == tenant_idget_active_capability_names 的查詢形狀:user_role_repo_impl.py:81-101。
DISTINCT name。UserRole.user_id,不 join users(為了避開 RLS);查他人時才 join User 比 uid。org_unit_id 或 scope,也沒有子部門繼承。 只要 context 有 tenant_id,tenant_id IS NULL 的部門指派一律被排除。authz/capability.py:51-66。
capability_names_for 從 get_user_context().tenant_id 取租戶。flask.g,key 是 _authz_capability_names(:48, :99-106),沒有跨請求快取。viewer_has_capability 在 :115、require_capability 在 :135。set_guard_resolver 換掉(:85)。api/ 下有 jwt_required 的檔,共 61 支)分組方式是對整支檔 grep 守門字樣。G 組代表「檔內至少一處有守門」,不保證每個方法都有。jedi-iam 自己的 route(例如 role_route)不在 BE 的 api/ 下,沒算進來。
G 組:有 require_capability / platform_admin / super_admin / signed_token 等守門,28 支
ai_call_log/ai_call_log_routecloud_integration/:drive_app_credential_verify、google_drive_integration、google_drive_syncflow_control/project_routeflow_engine/flow_template_routemodule_frame/ 共 15 支:mf_ssp_export、components、control_default、control_objective_default、import、inventory、item、leveraged、party、reference_document、module_frame_route、ssp_resources、system_characteristic、template_import、ssp_import_templatenotify_config/notify_config_routeoscal/:framework/oscal_framework_version、module_frame_template_ssp、resource_libraryproject/project_routesupport/recent_error_routesystem_config/security_policy_routeJ 組:只有 jwt_required,33 支
| 模組 | 數量 | 檔案 |
|---|---|---|
| oscal(SSP) | 13 | ssp_components、ssp_control_impl_import、ssp_control_implementation、ssp_document_pool、ssp_docx_import、ssp_excel_import、ssp_export、ssp_inventory_items、ssp_leveraged、ssp_party、ssp_resources、ssp_scoped_excel_import、ssp_system_characteristic |
| oscal(framework) | 3 | framework_parse_job、framework_version_edit、oscal_framework_route |
| flow_engine | 5 | flow_engine_route、job_evidence、stage_advance、stage_object、stage_rollback |
| grc(flow_control) | 3 | assessment_plan、job_force_start、job_route |
| grc(project) | 3 | ap_docx_import、ar_import、audit_round |
| grc(project_summary_report) | 2 | project_summary_report_route、project_summary_report_history_route |
| system | 2 | system_config/system_config_route、setup/setup_route(設定精靈,可能是刻意不設守門) |
| 其他 | 2 | ai_quota/ai_quota_route、support/diagnostic_bundle_route |
注意:J 組的 SSP/flow_engine/grc 多數在 service 層有專案成員角色守門(見盤點 ②),「沒掛能力點」不等於「誰登入都能用」,但確實缺了租戶 RBAC 這一層。
is_platform = true。| resource 命名 | resource_type 種數 | 能力點筆數 |
|---|---|---|
| 底線 | 4 | 13 |
| 連字號 | 19 | 56 |
| 單字,無分隔 | 14 | 54 |
cloud_integration、notify_config、system_config、flow_template。export ×2(feedback、project)、assign ×1(project)、approve ×1(project)。cloud_integration.update、feedback.export、system_config.create/delete/update、workflow.create/delete/read/update。system_config 只有 create/delete/update,沒有 read;而對應的 system_config_route 剛好落在第 3 節的 J 組。scripts/init/04-seed-core.sql。只有 1 個角色:Administrator,id=2、tenant_id=1、is_admin = 1(:62)。從 :272 起是 123 列 role_capabilities,用名稱反查 id,等於全部能力點都配給它。scripts/init/gen_capability_seed.sql,由 gen_seed_sql.sh:86 呼叫,結尾在 :292-327 有筆數自檢。改用名稱反查的理由寫在 scripts/init/README.md:57-66(CM-1757)。app/service/tenant_provisioning_service.py:49 會建一個 "System Manager" 角色,配「能力點全集扣掉排除清單」,並在 :149 設成 is_admin = 1。scripts/init/migrate-capability-grants.sql 是模板,build 時產出 scripts/sql/packages/_capability_grants.sql。scripts/sql/2026-09-25-fr114-module-frame-read-capability-backfill.sql。is_admin = 1 的有 9 個。目前沒有。 角色端點只有 /roles/menu、/roles、/role、/role/<uid>、/role/status/<uid> 5 個,定義在 jedi-iam api/routing.py:107-111,全部是 CRUD 或狀態切換。