# 機能設計書 42-権限管理

## 概要

本ドキュメントは、Ghost CMSにおける権限管理機能の設計仕様を記載したものである。

### 本機能の処理概要

権限管理機能は、Ghost CMSにおけるアクセス制御の中核を担う機能である。Permission（権限）は、特定のオブジェクト（記事、ユーザー、設定など）に対して特定のアクション（作成、編集、削除など）を実行できるかどうかを定義する。この機能により、ロールベースのアクセス制御（RBAC）を実現し、各スタッフユーザーやAPIキーが実行可能な操作を厳密に管理する。

**業務上の目的・背景**：Ghost CMSはマルチユーザー環境での運用を前提としており、セキュリティと運用効率の両立が求められる。権限管理機能により、誤操作によるデータ破損や不正アクセスを防止しつつ、適切な権限を持つユーザーには必要な操作を許可する。システムの安全性と使いやすさを両立させるための基盤機能である。

**機能の利用シーン**：
- ユーザーがAPI操作を実行する前の権限チェック
- 管理画面での操作可否の判定
- APIキー経由でのアクセス時の権限検証
- ロールに基づく機能アクセス制御

**主要な処理内容**：
1. canThis()による権限チェックの開始
2. parseContextによるコンテキスト（ユーザー/APIキー/内部処理）の解析
3. providersによる権限データの取得（ユーザー権限・APIキー権限）
4. actionsMapによるアクション-オブジェクトタイプの対応管理
5. permissible関数によるモデル固有の追加権限チェック

**関連システム・外部連携**：
- ロール管理（Role Model）との連携
- ユーザー管理（User Model）との連携
- APIキー管理（ApiKey Model）との連携
- 全APIエンドポイントでの権限チェック

**権限による制御**：
- Ownerロールはすべての操作を実行可能
- 各ロールには定義済みの権限セットが割り当てられる
- internalコンテキストでは権限チェックをスキップ
- APIキーはロールに紐づく権限のみ実行可能

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 全画面 | - | 基盤機能 | すべての管理画面操作で権限チェックを実行 |
| 36 | ユーザー・権限設定 | 主画面 | 権限に基づくUI表示制御 |

## 機能種別

バリデーション / アクセス制御 / 権限チェック

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| context | Object/String | Yes | 実行コンテキスト（user, api_key, internal等） | internal/user/api_keyのいずれか |
| actionType | string | Yes | 実行するアクション（edit, add, destroy等） | actionsMapに存在すること |
| objectType | string | Yes | 対象オブジェクト（post, user, setting等） | actionsMapに存在すること |
| modelOrId | Model/string | No | 対象モデルまたはID | 有効なモデル/ID |
| unsafeAttrs | Object | No | 追加の属性情報 | モデルに依存 |

### 入力データソース

- APIリクエストのコンテキスト情報
- セッション情報（認証済みユーザー）
- APIキー情報（Admin/Content API）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| Promise<void> | Promise | 権限あり：resolve() |
| NoPermissionError | Error | 権限なし：reject(Error) |

### 出力先

- 権限チェック結果は呼び出し元に返却
- エラー時は403 Forbiddenレスポンス

## 処理フロー

### 処理シーケンス

```
1. canThis(context)呼び出し
   └─ CanThisResultインスタンス生成
2. beginCheck(context)実行
   └─ parseContext()でコンテキスト解析
3. 権限データ取得
   ├─ providers.user()でユーザー権限取得
   └─ providers.apiKey()でAPIキー権限取得
4. アクションハンドラ構築
   └─ buildObjectTypeHandlers()でハンドラ生成
5. 権限チェック実行
   ├─ Ownerの場合：即座にresolve
   ├─ internalの場合：即座にresolve
   ├─ 権限マッチング実行
   └─ モデル固有のpermissible()呼び出し
6. 結果返却
   └─ Promise resolve/reject
```

### フローチャート

```mermaid
flowchart TD
    A[canThis context 呼び出し] --> B[parseContext]
    B --> C{internal?}
    C -->|Yes| D[即座にresolve]
    C -->|No| E[権限データ取得]
    E --> F{Owner?}
    F -->|Yes| D
    F -->|No| G[userPermissions確認]
    G --> H{権限マッチ?}
    H -->|Yes| I[apiKeyPermissions確認]
    H -->|No| J[NoPermissionError]
    I --> K{permissible定義あり?}
    K -->|Yes| L[Model.permissible実行]
    K -->|No| M{両方の権限OK?}
    L --> M
    M -->|Yes| D
    M -->|No| J
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-42-001 | Owner全権限 | Ownerロールはすべての操作を実行可能 | ユーザーがOwnerロールを持つ場合 |
| BR-42-002 | Internal権限スキップ | internalコンテキストでは権限チェックをスキップ | context.internal=true |
| BR-42-003 | アクション-オブジェクト対応 | 権限はアクションとオブジェクトタイプの組み合わせで定義 | すべての権限チェック |
| BR-42-004 | Staff APIキー | ユーザーとAPIキーの両方が存在する場合、ユーザー権限を優先 | Staff APIキー使用時 |
| BR-42-005 | モデル固有チェック | モデルにpermissible関数がある場合は追加チェック | 対象モデルにpermissibleがある場合 |

### 計算ロジック

権限判定ロジック:
```javascript
// 1. Ownerチェック
if (isOwner) return resolve();

// 2. ユーザー権限チェック
hasUserPermission = permissions.some(p =>
    p.action_type === actionType &&
    p.object_type === objectType
);

// 3. APIキー権限チェック（Staff APIキーの場合はユーザー権限を使用）
if (loadedPermissions.user && loadedPermissions.apiKey) {
    hasApiKeyPermission = true; // Staff APIキー
} else {
    hasApiKeyPermission = apiKeyPermissions.some(p => ...);
}

// 4. 結果判定
if (hasUserPermission && hasApiKeyPermission) resolve();
else reject(NoPermissionError);
```

## データベース操作仕様

### 操作別データベース影響一覧

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 権限取得（ユーザー） | users, roles, roles.permissions, permissions | SELECT | ユーザーの全権限を取得 |
| 権限取得（APIキー） | api_keys, role, role.permissions | SELECT | APIキーの権限を取得 |
| 権限マスタ | permissions | SELECT | 全権限データをキャッシュ |

### テーブル別操作詳細

#### permissions

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | id, name, object_type, action_type, object_id | 全件取得（初期化時） | actionsMapキャッシュ用 |

#### permissions_roles

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | role_id, permission_id | role_idで検索 | ロールの権限取得時 |

#### permissions_users

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | user_id, permission_id | user_idで検索 | ユーザー個別権限取得時 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 403 | NoPermissionError | 権限がない操作を実行 | 適切な権限を持つユーザーで再実行 |
| 500 | InternalServerError | actionsMapが初期化されていない | permissions.init()を実行 |
| 401 | UnauthorizedError | ユーザーがアクティブでない | ユーザーステータスを確認 |
| 404 | NotFoundError | 指定ユーザー/APIキーが存在しない | 有効なユーザー/キーを使用 |

### リトライ仕様

該当なし（権限チェックはステートレスであり、リトライで結果は変わらない）

## トランザクション仕様

権限チェックは参照のみのため、トランザクション管理は不要。権限データは起動時にキャッシュされ、以降は再読み込みしない。

## パフォーマンス要件

- 権限チェック: 50ms以内（キャッシュ済みの場合）
- actionsMapは起動時にメモリにキャッシュ
- ユーザー権限もリクエスト内でキャッシュされる

## セキュリティ考慮事項

- internalコンテキストはサーバー内部処理のみで使用
- ユーザーステータスがactiveでない場合は拒否
- 権限チェックは必ずサーバーサイドで実行
- クライアントからのコンテキスト偽装を防止

## 備考

- permissions.init()は起動時に一度だけ実行される
- actionsMapが空の場合はエラーをスロー
- Staff APIキー（ユーザー紐付きAPIキー）の場合はユーザー権限を優先

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

まず、権限のデータモデルとスキーマを理解することが重要。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | schema.js | `ghost/core/core/server/data/schema/schema.js` | permissionsテーブルの定義（205-213行目） |
| 1-2 | schema.js | `ghost/core/core/server/data/schema/schema.js` | permissions_usersテーブル（214-218行目） |
| 1-3 | schema.js | `ghost/core/core/server/data/schema/schema.js` | permissions_rolesテーブル（219-223行目） |

**読解のコツ**: permissionsテーブルにはaction_typeとobject_typeの組み合わせで権限を定義している点に注目。

#### Step 2: エントリーポイントを理解する

権限チェックの開始点を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | index.js | `ghost/core/core/server/services/permissions/index.js` | 権限サービスの初期化とエクスポート |
| 2-2 | can-this.js | `ghost/core/core/server/services/permissions/can-this.js` | canThis関数のエントリーポイント |

**主要処理フロー**:
1. **8-16行目**: init関数でPermission.findAllを実行しactionsMapを初期化
2. **167-171行目**: canThis関数でCanThisResultインスタンスを生成

#### Step 3: コンテキスト解析を理解する

リクエストコンテキストの解析処理を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | parse-context.js | `ghost/core/core/server/services/permissions/parse-context.js` | コンテキスト解析ロジック |

**主要処理フロー**:
- **8-39行目**: parseContext関数でinternal, user, api_key, memberを解析
- **18-21行目**: internalコンテキストの判定
- **23-26行目**: userコンテキストの判定
- **28-32行目**: api_keyコンテキストの判定（typeがcontentの場合はpublic）

#### Step 4: 権限データ取得を理解する

ユーザー・APIキーの権限を取得する処理を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | providers.js | `ghost/core/core/server/services/permissions/providers.js` | 権限プロバイダー実装 |

**主要処理フロー**:
- **12-56行目**: user関数でユーザーとロールの権限を取得
- **22-24行目**: ユーザーステータスがactiveでない場合はUnauthorizedError
- **28-30行目**: ロールに紐づく権限を取得
- **58-74行目**: apiKey関数でAPIキーの権限を取得

#### Step 5: 権限チェックのコアロジックを理解する

実際の権限判定処理を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | can-this.js | `ghost/core/core/server/services/permissions/can-this.js` | CanThisResultクラス |
| 5-2 | actions-map-cache.js | `ghost/core/core/server/services/permissions/actions-map-cache.js` | actionsMapキャッシュ |

**主要処理フロー**:
- **16-107行目**: buildObjectTypeHandlersでオブジェクトタイプごとのハンドラ構築
- **40-42行目**: internalコンテキストの場合は即座にresolve
- **60-67行目**: checkPermission関数でaction_typeとobject_typeをマッチング
- **68-73行目**: Ownerの場合はhasUserPermission=true
- **76-88行目**: Staff APIキーとTraditional APIキーの処理分岐
- **91-95行目**: TargetModel.permissibleによる追加チェック

#### Step 6: Permissionモデルを理解する

Permissionモデルの構造を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 6-1 | permission.js | `ghost/core/core/server/models/permission.js` | Permissionモデル定義 |

**主要処理フロー**:
- **6-46行目**: Permissionモデルの完全な定義
- **8行目**: tableName: 'permissions'
- **10-13行目**: rolesとのリレーション定義
- **30-32行目**: rolesとのbelongsToMany関係

### プログラム呼び出し階層図

```
canThis(context)
    │
    └─ CanThisResult.beginCheck(context)
           │
           ├─ parseContext(context)
           │
           ├─ providers.user(context.user)
           │      └─ User.findOne({withRelated: ['permissions', 'roles', 'roles.permissions']})
           │
           ├─ providers.apiKey(context.api_key.id)
           │      └─ ApiKey.findOne({withRelated: ['role', 'role.permissions']})
           │
           └─ buildObjectTypeHandlers(objTypes, actType, context, permissionsLoad)
                  │
                  ├─ checkPermission(perm)
                  │
                  └─ TargetModel.permissible(...)
                         └─ モデル固有の権限チェック
```

### データフロー図

```
[入力]                      [処理]                         [出力]

context ─────────▶ parseContext() ─────────────────────────┐
                                                           │
user_id ─────────▶ providers.user() ─────────▶ userPerms ─┼─▶ checkPermission()
                                                           │       │
api_key_id ──────▶ providers.apiKey() ──────▶ apiKeyPerms ┘       │
                                                                   ▼
                                               ┌───────────────────────────┐
                                               │ hasUserPermission &&      │
                                               │ hasApiKeyPermission       │
                                               └───────────────────────────┘
                                                           │
                           ┌───────────────────────────────┼───────────────┐
                           ▼                               ▼               ▼
                    [Model.permissible]              [resolve()]    [reject(Error)]
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| permission.js | `ghost/core/core/server/models/permission.js` | ソース | Permissionモデル定義 |
| index.js | `ghost/core/core/server/services/permissions/index.js` | ソース | 権限サービスのエントリーポイント |
| can-this.js | `ghost/core/core/server/services/permissions/can-this.js` | ソース | 権限チェックのコア実装 |
| parse-context.js | `ghost/core/core/server/services/permissions/parse-context.js` | ソース | コンテキスト解析 |
| providers.js | `ghost/core/core/server/services/permissions/providers.js` | ソース | 権限データ取得 |
| actions-map-cache.js | `ghost/core/core/server/services/permissions/actions-map-cache.js` | ソース | actionsMapキャッシュ管理 |
| role-utils.js | `ghost/core/core/server/models/role-utils.js` | ソース | ロール判定ユーティリティ |
| schema.js | `ghost/core/core/server/data/schema/schema.js` | 設定 | DBスキーマ定義 |
