- Multi-tenant RBAC: permission catalog, roles, role-permission mapping, user-role assignment, and external IdP role mapping (zitadel/ldap/scim). - Casbin enforcer with Redis-backed adapter and Pub/Sub reload for multi-instance policy sync; HTTP middleware enforces (tenant, role, path, method) with platform admin bypass. - /api/v1/permissions routes: catalog, me, policy/reload, roles CRUD, role permissions, user roles, role mappings. - New error scope (31) for Permission and biz code descriptions. - Wire Permission module into ServiceContext, config, mongo-index, and add cmd/permission-seed CLI plus etc/rbac.conf model. - Redis client gains lazy PubSubClient helper (go-zero wrapper lacks Subscribe). - Rewrite internal/model/member/README to cover Tenant/Member/Identity. Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|---|---|---|
| .. | ||
| config | ||
| domain | ||
| repository | ||
| seed | ||
| usecase | ||
| README.md | ||
README.md
Permission Module
本模組提供 Gateway 多租戶 B2B 自定義 RBAC:平台級 Permission Catalog + 租戶級 Role / RolePermission / UserRole / RoleMapping,搭配 Casbin enforcer 進行 HTTP path/method 授權。設計參考
docs/identity-member-design.md§6 / §7.3 / §13。
0. TL;DR
flowchart LR
subgraph Platform["平台層 (Platform-wide)"]
Catalog[Permission Catalog]
end
subgraph Tenant["租戶層 (per-tenant)"]
Role[Role]
RP[RolePermission]
UR[UserRole]
RM[RoleMapping]
end
Catalog -- 勾選 --> RP --> Role
Role -- 指派 --> UR
Role -- 對應 --> RM
Tenant -- LoadPolicy --> Casbin[(Casbin Enforcer<br/>Redis adapter)]
Casbin -- Check --> Middleware[CasbinRBAC Middleware]
- Permission 平台 seed 全局(
cmd/permission-seed),租戶不可新增;只能勾選。 - Role / RolePermission / UserRole 租戶獨立;同名 role 可在不同租戶共存。
- Role.Key 一旦建立 不可改;外部 IdP(ZITADEL / LDAP / SCIM)以 Key 作對應。
- 多 pod 同步:Redis Pub/Sub 即時通知 + 5min cron 兜底。
1. 核心概念
| 概念 | 簡述 | 關鍵欄位 |
|---|---|---|
| Permission | 平台級權限節點(樹狀,dot notation) | name 唯一、http_methods + http_path 命中 Casbin policy |
| Role | 租戶內的角色 | tenant_id + key unique;is_system=true 不可刪 |
| RolePermission | Role 勾選了哪些 Permission | 自動補齊 parent permission ID |
| UserRole | 使用者被指派的角色(多角色) | source 區分 manual / zitadel / ldap / scim |
| RoleMapping | 外部 group/role → 內部 Role.Key | SyncFromX 用來翻譯 IdP claims |
| Casbin Policy | 物化後的授權規則(Redis Set) | (tenant, role, path, methods, name) |
1.1 Permission Tree 範例
member.info.management ← 分類(無 HTTP)
├── member.basic.info ← 二級分類
│ ├── member.info.select GET /api/v1/members/me
│ └── member.info.update PATCH /api/v1/members/me
├── member.admin.list GET /api/v1/members
└── member.admin.read GET /api/v1/members/:uid
permission.role.management ← 分類
├── permission.role.read GET /api/v1/permissions/roles
├── permission.role.write POST/PUT/DELETE /api/v1/permissions/roles*
└── permission.assign.write POST/DELETE /api/v1/permissions/users/*/roles*
分類節點(無
http_path)不會寫入 Casbin policy;它們只是 UI 樹狀渲染與 parent closure 用。
2. 目錄結構
internal/model/permission/
├── README.md # 本文件
├── config/
│ └── config.go # CasbinConfig / CacheConfig / ReloadConfig
├── domain/
│ ├── const.go # BSON 欄位 / Casbin / Role.Key 規則
│ ├── errors.go # 模組共用 sentinel errors
│ ├── redis.go # Redis key helpers (casbin / user_roles / role_perms)
│ ├── entity/
│ │ ├── permission.go # Permission catalog node
│ │ ├── role.go
│ │ ├── role_permission.go
│ │ ├── user_role.go
│ │ └── role_mapping.go
│ ├── enum/
│ │ ├── status.go # open / close + Permissions map
│ │ ├── permission_type.go # backend_user / frontend_user
│ │ └── role_source.go # manual / zitadel / ldap / scim
│ ├── repository/ # 介面(+ Casbin adapter port)
│ │ ├── permission.go
│ │ ├── role.go
│ │ ├── role_permission.go
│ │ ├── user_role.go
│ │ ├── role_mapping.go
│ │ └── casbin_adapter.go
│ └── usecase/ # 介面 + DTO
│ ├── permission.go
│ ├── role.go
│ ├── role_permission.go
│ ├── user_role.go
│ ├── role_mapping.go
│ ├── rbac.go
│ └── authorization_query.go
├── repository/ # Mongo + Redis 實作
│ ├── index.go # EnsureMongoIndexes + bsonOpSet
│ ├── permission_mongo.go
│ ├── role_mongo.go
│ ├── role_permission_mongo.go
│ ├── user_role_mongo.go
│ ├── role_mapping_mongo.go
│ └── casbin_redis.go # tenant-scoped policy Redis Set
├── usecase/ # atomic primitives (7)
│ ├── module.go # NewModuleFromParam
│ ├── errors.go # wrapRepoErr → errs.For(code.Permission)
│ ├── permission_tree.go # buildTree / filterOpenNodes / parent closure
│ ├── permission_usecase.go
│ ├── role_usecase.go
│ ├── role_permission_usecase.go
│ ├── user_role_usecase.go
│ ├── role_mapping_usecase.go
│ ├── authorization_query_usecase.go
│ └── rbac_usecase.go # Casbin enforcer + LoadPolicy + Pub/Sub reload
└── seed/
├── catalog.go # embed + Apply + DefaultSystemRoles
└── catalog.json # 平台 seed 資料
3. 模組依賴
flowchart TD
Logic[logic/permission] --> SVC[svc.ServiceContext]
SVC --> AuthQ[AuthorizationQueryUseCase]
SVC --> Perm[PermissionUseCase]
SVC --> Role[RoleUseCase]
SVC --> RolePerm[RolePermissionUseCase]
SVC --> UserRole[UserRoleUseCase]
SVC --> Mapping[RoleMappingUseCase]
SVC --> RBAC[RBACUseCase]
AuthQ --> RoleR[(roles)]
AuthQ --> PermR[(permissions)]
AuthQ --> RPR[(role_permissions)]
AuthQ --> URR[(user_roles)]
Perm --> PermR
Role --> RoleR
Role --> URR
RolePerm --> RPR
RolePerm --> RoleR
RolePerm --> PermR
UserRole --> URR
UserRole --> RoleR
Mapping --> RMR[(role_mappings)]
Mapping --> RoleR
RBAC --> RoleR
RBAC --> PermR
RBAC --> RPR
RBAC --> URR
RBAC --> Adapter[Casbin Redis Adapter]
Adapter --> Redis[(Redis)]
RBAC --> Pub[Redis Pub/Sub]
4. UseCase 介面(7 個)
| UseCase | 主要方法 | 注入 |
|---|---|---|
PermissionUseCase |
GetCatalogTree / List / UpsertCatalog / UpdateStatus |
PermissionRepository |
RoleUseCase |
Create / Get / List / Update / Delete |
Role + RolePermission + UserRole |
RolePermissionUseCase |
List / Replace |
Role + Permission + RolePermission + Reloader |
UserRoleUseCase |
Assign / Revoke / List / ReplaceForSource |
Role + UserRole + Reloader |
RoleMappingUseCase |
Upsert / Delete / GetByExternal / List |
Role + RoleMapping |
AuthorizationQueryUseCase |
Me |
Role + Permission + RolePermission + UserRole |
RBACUseCase |
Check / LoadPolicy / LoadAllPolicies / BroadcastReload / Start/StopReloadSubscriber |
All repos + Redis |
5. 資料儲存
5.1 MongoDB
| Collection | 索引 | 用途 |
|---|---|---|
permissions |
name(uniq) / parent / status / type |
平台 Permission Catalog(樹狀) |
roles |
(tenant_id, key)(uniq) / (tenant_id, is_system) |
租戶角色 |
role_permissions |
(tenant_id, role_id, permission_id)(uniq) / (tenant_id, permission_id) |
Role↔Permission 多對多 |
user_roles |
(tenant_id, uid, role_id)(uniq) / (tenant_id, role_id) / (tenant_id, uid, source) |
User↔Role 多對多 |
role_mappings |
(tenant_id, external_source, external_key)(uniq) / (tenant_id, internal_role_id) |
外部 group → 內部 Role |
啟動時呼叫 permrepo.EnsureMongoIndexes(ctx, &c.Mongo)(已掛在 cmd/mongo-index)。
5.2 Redis Key
| Key | 內容 | TTL | 由誰寫 |
|---|---|---|---|
permission:casbin:rules:{tenant_id} |
Set of JSON-encoded []string rules |
永久 | RBACUseCase.LoadPolicy / BroadcastReload |
perm:user_roles:{tenant_id}:{uid} |
List of role keys(讀取快取,預留) | Cache.UserRolesTTLSeconds |
預留 |
perm:role_perms:{tenant_id}:{role_id} |
List of permission names(預留) | Cache.RolePermsTTLSeconds |
預留 |
permission:tree:open |
序列化的全局 open tree(預留) | Cache.CatalogTTLSeconds |
預留 |
(channel) casbin:reload |
Pub/Sub payload {tenant_id, ts} |
— | RBACUseCase.BroadcastReload |
Redis Set + JSON 編碼是為了讓 SaveAll 用 pipelined
DEL + SADD一致性更新;Pub/Sub 走獨立 go-redis client(go-zero 沒有 Subscribe),詳見internal/library/redis/pubsub.go。
6. 核心流程時序圖
6.1 NewModuleFromParam — 模組組裝
sequenceDiagram
participant Boot as svc.NewServiceContext
participant Mod as permission.NewModuleFromParam
participant Cfg as config.Defaults()
participant Repo as Mongo Repos (5)
participant Casbin as RBACUseCase
participant Redis as PolicyAdapter
Boot->>Mod: FactoryParam{MongoConf, Redis, Config}
Mod->>Cfg: cfg = Config.Defaults()
Mod->>Repo: NewPermission/Role/.../RoleMapping Repository
Note over Mod: 若已注入 repo(測試)跳過
alt cfg.Casbin.Enabled && Redis 有
Mod->>Casbin: NewRBACUseCase(repos+Redis)
Casbin-->>Mod: rbacUC
Mod->>Redis: RedisAdapterFactory = NewCasbinRedisAdapter
Mod->>Mod: reloader = rbacUC.BroadcastReload
else 無 Redis 或 Disabled
Mod->>Mod: rbacUC = nil(Check 永遠 deny)
end
Mod->>Mod: New {Permission, Role, RolePermission, UserRole, RoleMapping, AuthorizationQuery}
Mod-->>Boot: *Module(7 usecases + 5 repos)
6.2 Permission Catalog Seed
sequenceDiagram
participant CLI as cmd/permission-seed
participant Cfg as config.Mongo
participant Idx as permrepo.EnsureMongoIndexes
participant Seed as seed.Apply
participant Cat as Permissions
participant Roles as Roles + RolePermissions
CLI->>Cfg: load -f etc/gateway.dev.yaml
CLI->>Idx: 建立 5 collections 索引
CLI->>Seed: Apply(perms, roles, rolePerms, opts)
alt SkipCatalog == false
Seed->>Cat: 第一輪 UpsertByName(不含 parent)
Seed->>Cat: GetAll → 建 name→ID index
Seed->>Cat: 第二輪 UpsertByName(補 parent ID)
end
loop opts.TenantIDs
Seed->>Roles: GetByKey or Insert is_system role
Seed->>Roles: SetForRole(roleID, [permIDs]) ← 全量取代
end
Seed-->>CLI: Report{ catalog, roles, role_perms }
CLI-->>CLI: stdout summary
預設 5 個 system role:
tenant_owner/tenant_admin/member_manager/member/viewer,定義於seed/catalog.go::DefaultSystemRoles。
6.3 Role 建立 / 更新 / 刪除
sequenceDiagram
participant API as POST/PATCH/DELETE /permissions/roles
participant Logic as logic.permission.*
participant UC as RoleUseCase
participant Repo as RoleRepository
participant URR as UserRoleRepository
API->>Logic: req + actor (tenant_id, uid)
Logic->>UC: Create / Update / Delete
alt Create
UC->>UC: validateRoleKey(^[a-z][a-z0-9._-]+$、不可 system./platform_)
UC->>Repo: Insert(role) ← unique (tenant_id, key)
else Update
UC->>Repo: GetByID
UC->>UC: 阻擋 is_system 改 status
UC->>Repo: FindOneAndUpdate
else Delete
UC->>Repo: GetByID
UC->>UC: 阻擋 is_system
UC->>URR: ListByRole(仍有指派 → 拒絕)
UC->>Repo: DeleteByRole(role_perms)
UC->>Repo: Delete(role)
end
UC-->>Logic: role
Logic-->>API: types.RoleData
6.4 RolePermission 全量取代(PUT /roles/:id/permissions)
sequenceDiagram
participant API as PUT /permissions/roles/:id/permissions
participant Logic as logic.replaceRolePermissions
participant UC as RolePermissionUseCase
participant Roles as RoleRepository
participant Perms as PermissionRepository
participant RP as RolePermissionRepository
participant RBAC as RBACUseCase
API->>Logic: req{ID, PermissionIDs}
Logic->>UC: Replace(tenantID, roleID, ids)
UC->>Roles: GetByID(驗證 tenant 一致)
UC->>Perms: GetAll(拿到 catalog 全表)
UC->>UC: 檢查 ids ⊆ catalog
UC->>UC: getFullParentPermissionIDs(ids, all)
UC->>RP: SetForRole(tenantID, roleID, closure)
Note over RP: DeleteMany + InsertMany 原子化
UC->>RBAC: BroadcastReload(tenantID)
RBAC-->>UC: ok(fire-and-forget)
UC-->>Logic: nil
Logic-->>API: 200 OK
6.5 UserRole 指派 / 撤銷
sequenceDiagram
participant API as POST /permissions/users/:uid/roles
participant UC as UserRoleUseCase
participant Roles as RoleRepository
participant URR as UserRoleRepository
participant RBAC as RBACUseCase
API->>UC: Assign{tenant, uid, role_id, source=manual}
UC->>Roles: GetByID (tenant scope check)
UC->>URR: Insert(unique tenant+uid+role)
UC->>RBAC: BroadcastReload(tenant)
UC-->>API: UserRole
6.6 SyncFromX 流程(外部 IdP 來源同步)
sequenceDiagram
participant Sync as auth/provisioning
participant UC as UserRoleUseCase
participant Map as RoleMappingUseCase
participant Roles as RoleRepository
participant URR as UserRoleRepository
participant RBAC as RBACUseCase
Sync->>Map: GetByExternal(tenant, source=zitadel, externalKey)
Map-->>Sync: RoleMapping(internal_role_key)
Note over Sync: 收齊 IdP 端所有 roles → keys
Sync->>UC: ReplaceForSource(tenant, uid, source=zitadel, [roleKeys])
UC->>UC: 阻擋 source==manual(防誤洗)
loop key in roleKeys
UC->>Roles: GetByKey (skip 不存在的)
end
UC->>URR: ReplaceForSource(tenant, uid, source, [roleIDs])
Note over URR: DeleteMany source=zitadel + BulkInsert<br/>※ source=manual 紀錄不動
UC->>RBAC: BroadcastReload(tenant)
6.7 LoadPolicy(Casbin 規則載入)
sequenceDiagram
participant Trigger as Replace / Reload / Boot
participant RBAC as RBACUseCase
participant Roles as RoleRepository
participant RP as RolePermissionRepository
participant Perms as PermissionRepository
participant Enf as casbin.SyncedEnforcer
participant Adp as Redis Adapter
Trigger->>RBAC: LoadPolicy(tenantID)
RBAC->>Roles: ListByTenant
RBAC->>RP: ListByRoles(roleIDs)
RBAC->>Perms: GetByIDs(unique perm ids)
RBAC->>RBAC: 過濾 IsLeaf() && Status=open
RBAC->>RBAC: rules = [tenant, role.key, http_path, http_methods, perm.name]
RBAC->>Enf: ClearPolicy + AddPolicies
RBAC->>Adp: SaveAll(tenant, rules) ← Redis pipelined DEL+SADD
RBAC-->>Trigger: nil
6.8 Check(授權檢查)
sequenceDiagram
participant MW as middleware.CasbinRBAC
participant Logic as ActorFromContext
participant RBAC as RBACUseCase
participant URR as UserRoleRepository
participant Roles as RoleRepository
participant Enf as casbin.SyncedEnforcer
MW->>Logic: actor (tenant, uid)
MW->>RBAC: Check{tenant, uid, path, method}
RBAC->>RBAC: enforcerFor(tenant)(lazy clone model + AddPolicies)
RBAC->>URR: ListByUser(tenant, uid)
RBAC->>Roles: ListByTenantAndIDs(過濾 status=open)
loop role in roles(any-allow)
RBAC->>Enf: EnforceEx(tenant, role.key, path, method)
alt allow
RBAC-->>MW: CheckResult{Allow=true, MatchedRoleKey, MatchedPolicyRow}
end
end
MW->>MW: result.Allow ? next : 403 (errs.AuthForbidden)
6.9 Pub/Sub 多 Pod Reload
sequenceDiagram
participant PodA as Pod A (Replace)
participant Redis
participant PodB as Pod B (Subscribe)
participant PodC as Pod C (Subscribe)
PodA->>PodA: RolePermission.Replace + LoadPolicy(本地)
PodA->>Redis: PUBLISH casbin:reload {tenant, ts}
Redis-->>PodB: 推 message
Redis-->>PodC: 推 message
PodB->>PodB: handleReload → LoadPolicy(tenant)
PodC->>PodC: handleReload → LoadPolicy(tenant)
Note over PodB,PodC: 2-3ms 內三個 pod 同步
兜底:每個 pod 可定時跑
LoadAllPolicies(5min cron,未在本模組內排程;建議 svc 層或 cron-worker 觸發)。掃 Redispermission:casbin:rules:*key 推導 tenant 列表。
6.10 GET /permissions/me(前端選單渲染)
sequenceDiagram
participant Front as Frontend
participant API as GET /permissions/me
participant UC as AuthorizationQueryUseCase
participant URR as UserRoleRepository
participant Roles as RoleRepository
participant RP as RolePermissionRepository
participant Perms as PermissionRepository
Front->>API: Bearer JWT
API->>UC: Me(tenant, uid, includeTree)
UC->>URR: ListByUser
UC->>Roles: ListByTenantAndIDs(過濾 status=open)
UC->>RP: ListByRoles(roleIDs)
UC->>Perms: GetByIDs(unique perm ids)
UC->>UC: permission map = name→status
alt includeTree
UC->>UC: buildPermissionTree + filterOpenNodes
end
UC-->>API: { uid, tenant_id, roles, permissions, tree? }
API-->>Front: 200 OK
7. Casbin 模型(etc/rbac.conf)
[request_definition]
r = tenant, role, path, method
[policy_definition]
p = tenant, role, path, methods, name
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = r.tenant == p.tenant && r.role == p.role && keyMatch2(r.path, p.path) && regexMatch(r.method, p.methods)
keyMatch2:支援/api/v1/members/*萬用 pathregexMatch:GET|POST|PATCH多 method 同一 policy- 平台 Admin bypass 不寫進 matcher,由 middleware 預檢(保留 audit)
8. ServiceContext 注入
sc.PermissionCatalog // Permission catalog reader (tree / list / status)
sc.PermissionRole // Role CRUD(含 system role 防呆)
sc.PermissionRolePermission // Replace(含 parent closure)
sc.PermissionUserRole // Assign / Revoke / ReplaceForSource
sc.PermissionRoleMapping // 外部 group → Role.Key
sc.PermissionAuthQuery // GET /me 用
sc.PermissionRBAC // Casbin enforcer(Mongo+Redis 全到位才有)
sc.PermissionRoleRepo // 給 SCIM / SyncFromX 等下游使用
未啟用 Casbin 時 PermissionRBAC == nil,Check() 永遠 deny;middleware 會拒絕所有請求(除非 AllowMissingActor=true)。
9. HTTP API(前綴 /api/v1/permissions)
| Method | Path | Handler | 說明 |
|---|---|---|---|
| GET | /catalog |
getPermissionCatalog |
全局 Catalog(tree=true 取樹狀) |
| GET | /me |
getMePermissions |
當前 user 的 role / permission map |
| GET | /roles |
listRoles |
租戶角色清單 |
| POST | /roles |
createRole |
建立角色(key 不可改) |
| PATCH | /roles/:id |
updateRole |
更新 display_name / status(system role 限制) |
| DELETE | /roles/:id |
deleteRole |
刪角色(system / 仍有指派 → 拒絕) |
| GET | /roles/:id/permissions |
getRolePermissions |
角色目前的 permission 集合 |
| PUT | /roles/:id/permissions |
replaceRolePermissions |
全量取代 + 補 parent + Pub/Sub reload |
| GET | /users/:uid/roles |
listUserRoles |
使用者目前指派的 role |
| POST | /users/:uid/roles |
assignUserRole |
指派角色(source 預設 manual) |
| DELETE | /users/:uid/roles/:role_id |
revokeUserRole |
撤銷單一角色 |
| GET | /role-mappings |
listRoleMappings |
外部映射列表(分頁) |
| PUT | /role-mappings |
upsertRoleMapping |
Upsert 外部 group → Role.Key |
| DELETE | /role-mappings |
deleteRoleMapping |
刪除外部映射 |
| POST | /policy/reload |
reloadPolicy |
強制重載(單租戶或 *) |
完整錯誤碼註解參見 generate/api/permission.api,由 make gen-doc 出 OpenAPI。
10. 設定範例(etc/gateway.dev.example.yaml)
Permission:
Casbin:
Enabled: false # 預設關閉,啟用後 RBAC enforcement 生效
ModelPath: etc/rbac.conf
PolicyAdapter: auto # auto / redis / mongo
Cache:
UserRolesTTLSeconds: 300
RolePermsTTLSeconds: 300
CatalogTTLSeconds: 600
Reload:
Channel: casbin:reload
DebounceMilliseconds: 200
HeartbeatSeconds: 60
11. CLI / 操作指南
# 1) 建索引
make mongo-index
# 2) 撰寫 / 修改 catalog
$EDITOR internal/model/permission/seed/catalog.json
# 3) 全平台 seed catalog(不為任何 tenant 建 role)
go run ./cmd/permission-seed -f etc/gateway.dev.yaml
# 4) 同時為 dev tenant seed 5 個 system role
go run ./cmd/permission-seed -f etc/gateway.dev.yaml -tenant TEN-100001
# 5) 多租戶
go run ./cmd/permission-seed -f etc/gateway.dev.yaml -tenant TEN-100001,TEN-100002
# 6) 只 reseed tenant role(catalog 已存在)
go run ./cmd/permission-seed -f etc/gateway.dev.yaml -tenant TEN-100001 -skip-catalog
# 7) 強制全部 pod 重載 policy(HTTP)
curl -X POST http://localhost:8888/api/v1/permissions/policy/reload \
-H "Content-Type: application/json" \
-H "X-Tenant-ID: TEN-100001" -H "X-UID: TEN-100001-OWNER" \
-d '{"tenant_id": "*"}'
12. 中介層(middleware/casbin_rbac.go)
現況: middleware 已寫好,但 尚未掛入 routes.go(避免影響現有 dev 模式)。要啟用:
import perm "gateway/internal/middleware"
server.AddRoutes(routes,
rest.WithMiddlewares(
[]rest.Middleware{
middleware.CloudEPJWT(serverCtx.AuthToken), // 已存在
middleware.CasbinRBAC(serverCtx.PermissionRBAC, middleware.CasbinRBACOptions{
AllowMissingActor: false,
SkipPaths: map[string]struct{}{
"/api/v1/health": {},
},
}),
}...,
),
rest.WithPrefix("/api/v1/members"),
)
要先:
- 跑 seed CLI 把 catalog + system role 建好
- 為平台 admin tenant 建
platform_super_adminrole + bypass allowlist - 開啟
Permission.Casbin.Enabled = true - 設好
Permission.Reload.Channel(多 pod 才需要)
13. 測試
# 全模組 unit test
go test ./internal/model/permission/...
# 含整合(需要 Mongo + Redis 在 docker compose 起著)
make deps-up
go test -tags=integration ./internal/model/permission/...
14. 設計權衡 / 注意事項
| 議題 | 決策 | 原因 |
|---|---|---|
Permission name 改名 |
禁止 | 被 RolePermission、UI i18n、Casbin policy.name 引用;廢棄走 status=close 然後新建 |
Role key 改名 |
禁止 | 外部 IdP mapping 直接綁 key;改名會切斷映射 |
is_system role 刪除 |
拒絕 | 平台預設角色保留 |
is_system role 改 status |
拒絕 | 維持平台預期行為 |
manual source ReplaceForSource |
拒絕 | 防 SyncFromX 誤洗手動指派 |
Permission 有 * 萬用 path |
不建議裸 *;至少帶資源根 |
防 keyMatch2 貪婪命中跨資源 |
| Casbin 多 enforcer | 一 tenant 一個 enforcer,lazy 建 | 比一個 enforcer + filtered policy 簡單,且記憶體可預測 |
| 多 pod 同步 | Pub/Sub 即時 + 5min cron 兜底 | 即時通知 + reboot 不漏 |
| Pub/Sub client | 獨立 go-redis,不走 go-zero pool | go-zero 沒包 Subscribe,且 Subscribe 會佔住 conn |
| Permission Catalog 改動 | seed CLI 即可(idempotent) | UI 端不直接改 catalog;seed JSON 是 SoT |
15. 後續工作
| 項目 | 預估 |
|---|---|
| Platform admin allowlist + audit log | 後續 |
| RoleMapping 用 SyncFromX 落地(Zitadel / LDAP / SCIM) | 隨對應 SyncFromX usecase 推進 |
| Policy reload cron worker(5 min) | 取自 svc 啟動 ticker |
| Role permission 編輯 UI(不在 Gateway 內,由前端取資) | 前端 |
細粒度欄位過濾(.plain_code 變體) |
logic 層額外查 sub-permission |