328 lines
11 KiB
Markdown
328 lines
11 KiB
Markdown
# 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<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 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 |
|