# Permission Service # Permission Service — SRS/SDD Document | Version | Version Date | Editor | Memo | |---------|--------------|--------|------| | 1.0.0 | 2026/05/21 | Gateway Team | 初版:Gateway Permission 模組 SDD(對齊 frontend Permission Service SDD 格式) | | 1.1.0 | — | — | 新增 category 樹狀 Permission Catalog | | 1.2.0 | — | — | 多租戶 B2B RBAC + Casbin + RoleMapping | --- ## 1. Introduction ### 1.1 Purpose Permission 模組提供 Gateway **多租戶 B2B 自定義 RBAC**:平台級 Permission Catalog + 租戶級 Role / RolePermission / UserRole / RoleMapping,搭配 Casbin enforcer 進行 HTTP path/method 授權,並支援外部 IdP(ZITADEL / LDAP / SCIM)角色映射同步。 ### 1.2 Scope **範圍內(Multiple tenants):** - 平台 Permission Catalog(樹狀,dot notation) - 租戶 Role CRUD(含 system role 防呆) - Role ↔ Permission 多對多(含 parent closure) - User ↔ Role 多對多(source: manual / zitadel / ldap / scim) - 外部 group → 內部 Role.Key 映射(RoleMapping) - Casbin policy 物化(Redis Set)+ 多 pod Pub/Sub reload - GET /permissions/me 前端選單渲染 **範圍外:** - JWT 簽發 → Auth 模組 - 會員資料 → Member 模組 - Platform admin bypass → middleware 預檢(保留 audit) ### 1.3 Definitions, Acronyms, and Abbreviation | 縮寫 | 說明 | |------|------| | **RBAC** | Role-Based Access Control | | **Permission** | 平台級權限節點(可為分類或 leaf API 權限) | | **Role** | 租戶內角色,`key` 唯一且不可改 | | **RolePermission** | Role 勾選的 Permission 集合 | | **UserRole** | 使用者被指派的角色 | | **RoleMapping** | 外部 IdP group/role → 內部 Role.Key | | **Casbin** | 授權引擎,policy 存 Redis | | **Leaf Permission** | 有 http_path + http_methods 的可 enforcement 節點 | ### 1.4 Technologies to be used | 項目 | 技術 | |------|------| | Application Language | Go 1.22+ | | Framework | go-zero | | Cache / Policy Store | Redis(Casbin rules Set + Pub/Sub) | | Database | MongoDB | | Authorization Engine | Casbin(`keyMatch2` + `regexMatch`) | | API Codegen | goctl / `.api` 檔 | ### 1.5 Overview ```mermaid flowchart LR subgraph Platform["平台層"] Catalog[Permission Catalog] end subgraph Tenant["租戶層"] Role[Role] RP[RolePermission] UR[UserRole] RM[RoleMapping] end Catalog --> RP --> Role Role --> UR Role --> RM Tenant --> Casbin[(Casbin Enforcer
Redis adapter)] Casbin --> MW[CasbinRBAC Middleware] ``` - Permission **平台 seed 全局**,租戶不可新增,只能勾選 - Role / RolePermission / UserRole **租戶獨立** - Role.Key 一旦建立 **不可改**(外部 IdP 對應用) - 多 pod 同步:**Redis Pub/Sub 即時 + cron 兜底** --- ## 2. System Overview Permission 模組是 Gateway 授權子系統: 1. 平台維護 Permission Catalog(`cmd/permission-seed`) 2. 租戶管理員建立 Role、勾選 Permission 3. 指派 UserRole(手動或 SyncFromX 同步) 4. RBACUseCase 物化 Casbin policy → middleware 檢查 `(tenant, role.key, path, method)` 前端透過 `GET /permissions/me` 取得當前使用者的 role keys 與 permission map(含可選 tree)。 --- ## 3. System Architecture ### 3.1 System Architecture ```mermaid 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] RBAC --> Adapter[Casbin Redis Adapter] Adapter --> Redis[(Redis)] RBAC --> Pub[Redis Pub/Sub casbin:reload] Perm --> PermR[(permissions)] Role --> RoleR[(roles)] RolePerm --> RPR[(role_permissions)] UserRole --> URR[(user_roles)] Mapping --> RMR[(role_mappings)] ``` ### 3.2 Decomposition Description | 套件 | 職責 | |------|------| | `config/` | CasbinConfig、CacheConfig、ReloadConfig | | `domain/entity/` | Permission、Role、RolePermission、UserRole、RoleMapping | | `domain/enum/` | Status、PermissionType、RoleSource | | `domain/repository/` | 5 個 Mongo repo 介面 + Casbin adapter port | | `domain/usecase/` | 7 個 usecase 介面 + DTO | | `repository/` | Mongo + Redis Casbin adapter | | `usecase/` | 7 個 atomic usecase 實作 + permission_tree | | `seed/` | catalog.json + Apply CLI | ### 3.3 RBAC Model Casbin 模型(`etc/rbac.conf`): ```ini [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) ``` **授權檢查(any-allow):** 使用者所有 open role 逐一 EnforceEx,任一 allow 即通過。 ```mermaid sequenceDiagram participant MW as CasbinRBAC Middleware participant RBAC as RBACUseCase participant URR as UserRoleRepository participant Enf as casbin.Enforcer MW->>RBAC: Check{tenant, uid, path, method} RBAC->>URR: ListByUser loop each open role RBAC->>Enf: EnforceEx(tenant, role.key, path, method) alt allow RBAC-->>MW: Allow end end RBAC-->>MW: Deny → 403 ``` ### 3.4 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 permission.role.management ├── permission.role.read GET /api/v1/permissions/roles └── permission.role.write POST/PUT/DELETE /api/v1/permissions/roles* ``` 分類節點(無 `http_path`)不寫入 Casbin policy;Replace RolePermission 時自動補齊 parent closure。 ### 3.5 Policy Reload(多 Pod) ```mermaid sequenceDiagram participant PodA as Pod A participant Redis participant PodB as Pod B PodA->>PodA: RolePermission.Replace + LoadPolicy PodA->>Redis: PUBLISH casbin:reload {tenant_id} Redis-->>PodB: message PodB->>PodB: LoadPolicy(tenant) ``` --- ## 4. Data Design ### 4.1 Data Dictionary #### permissions(MongoDB — 平台全局) | Field | Type | Comment | Index | |-------|------|---------|-------| | _id | ObjectId | PK | PK | | parent | String | 父節點 ObjectId hex | parent | | name | String | dot notation 唯一名稱 | Unique | | http_methods | String | GET 或 GET\|POST\|PATCH | — | | http_path | String | keyMatch2 path pattern | — | | status | String | open / close | status | | type | String | backend_user / frontend_user | type | | create_at | Int64 | 建立時間 ms | — | | update_at | Int64 | 更新時間 ms | — | #### roles(MongoDB — 租戶範圍) | Field | Type | Comment | Index | |-------|------|---------|-------| | _id | ObjectId | PK | PK | | tenant_id | String | 租戶 ID | Unique(tenant_id, key) | | key | String | 角色 key(不可改) | Unique(tenant_id, key) | | display_name | String | 顯示名稱 | — | | creator_uid | String | 建立者 UID | — | | status | String | open / close | (tenant_id, is_system) | | is_system | Bool | 平台預設角色 | (tenant_id, is_system) | | create_at | Int64 | 建立時間 ms | — | | update_at | Int64 | 更新時間 ms | — | #### role_permissions(MongoDB) | Field | Type | Comment | Index | |-------|------|---------|-------| | _id | ObjectId | PK | PK | | tenant_id | String | 租戶 ID | Unique(tenant_id, role_id, permission_id) | | role_id | String | Role ObjectId hex | (tenant_id, role_id) | | permission_id | String | Permission ObjectId hex | (tenant_id, permission_id) | | create_at | Int64 | 建立時間 ms | — | #### user_roles(MongoDB) | Field | Type | Comment | Index | |-------|------|---------|-------| | _id | ObjectId | PK | PK | | tenant_id | String | 租戶 ID | Unique(tenant_id, uid, role_id) | | uid | String | 會員 UID | (tenant_id, uid, source) | | role_id | String | Role ObjectId hex | (tenant_id, role_id) | | source | String | manual / zitadel / ldap / scim | (tenant_id, uid, source) | | create_at | Int64 | 建立時間 ms | — | #### role_mappings(MongoDB) | Field | Type | Comment | Index | |-------|------|---------|-------| | _id | ObjectId | PK | PK | | tenant_id | String | 租戶 ID | Unique(tenant_id, external_source, external_key) | | external_source | String | zitadel / ldap / scim | Unique(tenant_id, external_source, external_key) | | external_key | String | 外部 group/role key | Unique(tenant_id, external_source, external_key) | | internal_role_id | String | 內部 Role ID | (tenant_id, internal_role_id) | | create_at | Int64 | 建立時間 ms | — | | update_at | Int64 | 更新時間 ms | — | #### Redis Keys | Key Pattern | 內容 | TTL | |-------------|------|-----| | permission:casbin:rules:{tenant_id} | Set of JSON Casbin rules | 永久 | | perm:user_roles:{tenant_id}:{uid} | role keys 快取(預留) | 300s | | perm:role_perms:{tenant_id}:{role_id} | permission names 快取(預留) | 300s | | (channel) casbin:reload | Pub/Sub reload payload | — | **Casbin rule 格式:** `[tenant, role.key, http_path, http_methods, perm.name]` --- ## 5. API Design **Base Path:** `/api/v1/permissions` | Method | Path | 說明 | |--------|------|------| | GET | `/catalog` | 全局 Permission Catalog(tree/list) | | GET | `/me` | 當前 user 的 roles + permissions | | GET | `/roles` | 租戶角色清單 | | POST | `/roles` | 建立角色 | | PATCH | `/roles/:id` | 更新 display_name / status | | DELETE | `/roles/:id` | 刪除角色 | | GET | `/roles/:id/permissions` | 角色 permission 集合 | | PUT | `/roles/:id/permissions` | 全量取代 + parent closure + reload | | GET | `/users/:uid/roles` | 使用者 role 列表 | | POST | `/users/:uid/roles` | 指派角色 | | DELETE | `/users/:uid/roles/:role_id` | 撤銷角色 | | GET | `/role-mappings` | 外部映射列表 | | PUT | `/role-mappings` | Upsert 外部映射 | | DELETE | `/role-mappings` | 刪除外部映射 | | POST | `/policy/reload` | 強制重載 policy(單租戶或 `*`) | 完整 schema 見 `generate/api/permission.api`。 --- ## 6. Resource | 資源 | 路徑 | |------|------| | API 定義 | `generate/api/permission.api` | | Logic | `internal/logic/permission/` | | Middleware | `internal/middleware/casbin_rbac.go` | | 模組原始碼 | `internal/model/permission/` | | 開發 README | `internal/model/permission/README.md` | | Casbin 模型 | `etc/rbac.conf` | | Seed 資料 | `internal/model/permission/seed/catalog.json` | | Seed CLI | `cmd/permission-seed` | | 設定範例 | `etc/gateway.dev.example.yaml` → `Permission` 區塊 | | 設計參考 | `docs/identity-member-design.md` §6 / §7.3 / §13 |