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

328 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 |