template-monorepo/internal/model/permission/SDD.md

328 lines
11 KiB
Markdown
Raw Permalink Normal View History

2026-05-21 23:52:39 +00:00
# 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 授權,並支援外部 IdPZITADEL / 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 | RedisCasbin 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<br/>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 policyReplace 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
#### permissionsMongoDB — 平台全局)
| 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 | — |
#### rolesMongoDB — 租戶範圍)
| 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_permissionsMongoDB
| 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_rolesMongoDB
| 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_mappingsMongoDB
| 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 Catalogtree/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 |