# 画面設計書 195-GPGキー一覧

## 概要

本ドキュメントは、ユーザーのGPGキー一覧画面の設計書である。GPGキーの登録・管理を行う画面の仕様を定義する。

### 本画面の処理概要

ユーザーが自身のアカウントに登録しているGPGキーを一覧表示し、新規GPGキーの追加および既存キーの削除・失効を行う画面である。GPGキーはGitコミットの署名検証に使用される。

**業務上の目的・背景**：GPGキーを使用したコミット署名は、コミットの作成者を暗号学的に検証する手段である。セキュリティが重視されるプロジェクトでは、署名付きコミットの使用が必須とされることがある。本画面は、GPGキーのライフサイクル管理（登録、検証状態確認、失効、削除）を行うために必要である。

**画面へのアクセス方法**：
1. 右上のユーザーアバターをクリック
2. 「設定」を選択
3. 左サイドバーから「GPGキー」を選択
4. または URL `/-/user_settings/gpg_keys` に直接アクセス

**主要な操作・処理内容**：
1. 登録済みGPGキーの一覧表示
2. 新規GPGキーの追加（公開鍵のペースト）
3. GPGキーの削除
4. GPGキーの失効（Revoke）

**画面遷移**：
- 遷移元: ユーザー設定メニュー
- 遷移先: なし（同一画面内で操作完結）

**権限による表示制御**：
- ログイン必須
- 自身のGPGキーのみ表示・管理可能

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 80 | SSHキー管理 | 主機能 | GPGキーの管理 |

## 画面種別

一覧 / 登録

## URL/ルーティング

```
GET    /-/user_settings/gpg_keys         # 一覧表示
POST   /-/user_settings/gpg_keys         # キー登録
DELETE /-/user_settings/gpg_keys/:id     # キー削除
PUT    /-/user_settings/gpg_keys/:id/revoke  # キー失効
```

## 入出力項目

### 新規GPGキー登録フォーム

| 項目名 | 項目ID | 入出力 | 型 | 必須 | 説明 |
|--------|--------|--------|-----|------|------|
| 公開鍵 | key | 入力 | text | Yes | PGP公開鍵ブロック |

## 表示項目

### キー一覧テーブル

| 項目名 | データソース | 説明 |
|--------|-------------|------|
| キーID | @gpg_keys[].primary_keyid | GPGキーの主キーID |
| フィンガープリント | @gpg_keys[].fingerprint | 40文字のフィンガープリント |
| サブキー | @gpg_keys[].subkeys | サブキー一覧 |
| メールアドレス | @gpg_keys[].emails_with_verified_status | 関連メールと検証状態 |
| ステータス | verified? / externally_verified | 検証済み/未検証/外部検証 |
| 作成日 | @gpg_keys[].created_at | 登録日時 |

### 統計情報

| 項目名 | データソース | 説明 |
|--------|-------------|------|
| キー数 | @gpg_keys.count | 登録済みキーの総数 |

## イベント仕様

### 1-GPGキー追加

**トリガー**: 「Add key」ボタン押下

**処理フロー**:
1. フォームデータのバリデーション（PGP公開鍵形式チェック）
2. POSTリクエストで `GpgKeys::CreateService` を呼び出し
3. フィンガープリントと主キーIDを自動抽出
4. サブキーを自動生成
5. 成功時: GPGキー一覧画面へリダイレクト
6. 失敗時: エラーメッセージを表示して画面に留まる
7. 未検証のGPG署名があれば非同期で再検証

### 2-GPGキー削除

**トリガー**: 削除ボタン押下

**処理フロー**:
1. 削除確認ダイアログを表示
2. 確認後、DELETE リクエスト
3. `GpgKeys::DestroyService` でキーとサブキーを削除
4. 一覧画面へリダイレクト

### 3-GPGキー失効

**トリガー**: 「Revoke」ボタン押下

**処理フロー**:
1. 失効確認ダイアログを表示
2. 確認後、PUT `/revoke` リクエスト
3. 関連するGPG署名を「unknown_key」状態に更新
4. キーを削除
5. 一覧画面へリダイレクト

## データベース更新仕様

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| GPGキー追加 | gpg_keys | INSERT | 新規GPGキーの登録 |
| GPGキー追加 | gpg_key_subkeys | INSERT | サブキーの登録 |
| GPGキー削除 | gpg_keys | DELETE | GPGキーの削除 |
| GPGキー削除 | gpg_key_subkeys | DELETE | サブキーの削除（CASCADE） |
| GPGキー失効 | gpg_signatures | UPDATE | 署名状態を unknown_key に更新 |
| GPGキー失効 | gpg_keys | DELETE | キーの削除 |

### テーブル別更新項目詳細

#### gpg_keys

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | user_id | current_user.id | キー所有者 |
| INSERT | key | フォーム入力値 | PGP公開鍵ブロック |
| INSERT | primary_keyid | 自動抽出 | 主キーID（16文字） |
| INSERT | fingerprint | 自動抽出 | フィンガープリント（40文字） |
| INSERT | externally_verified | false | 外部検証フラグ |
| INSERT | created_at | 現在日時 | 作成日時 |

#### gpg_key_subkeys

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | gpg_key_id | 親キーのID | 外部キー |
| INSERT | keyid | 自動抽出 | サブキーID |
| INSERT | fingerprint | 自動抽出 | サブキーフィンガープリント |

#### gpg_signatures（失効時）

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | gpg_key_id | NULL | キー参照をクリア |
| UPDATE | gpg_key_subkey_id | NULL | サブキー参照をクリア |
| UPDATE | verification_status | unknown_key | 検証状態を未知に更新 |
| UPDATE | updated_at | 現在日時 | 更新日時 |

## メッセージ仕様

| メッセージID | 種別 | メッセージ内容 | 表示条件 |
|-------------|------|--------------|---------|
| MSG-001 | 情報 | GPG keys allow you to verify signed commits. | ページ説明 |
| MSG-002 | エラー | is invalid. A valid public GPG key begins with '-----BEGIN PGP PUBLIC KEY BLOCK-----' and ends with '-----END PGP PUBLIC KEY BLOCK-----' | 形式エラー |
| MSG-003 | 情報 | Don't paste the private part of the GPG key. | プレースホルダー |

## 例外処理

| 例外 | 発生条件 | 対応処理 |
|-----|---------|---------|
| 未認証 | ログインしていない | ログイン画面へリダイレクト |
| バリデーションエラー | PGP形式が不正 | エラーメッセージを表示 |
| 重複エラー | 同じキーまたはフィンガープリントが存在 | エラーメッセージを表示 |

## 備考

- GPG公開鍵は `-----BEGIN PGP PUBLIC KEY BLOCK-----` で始まり `-----END PGP PUBLIC KEY BLOCK-----` で終わる必要がある
- キーに関連付けられたメールアドレスが GitLab アカウントで検証済みの場合、署名が「検証済み」として表示される
- Beyond Identity 連携が有効な場合、外部検証のステータスも表示される

---

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

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

### 推奨読解順序

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

GPGキー関連のデータ構造を理解することが重要である。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | gpg_key.rb | `app/models/gpg_key.rb` | GpgKeyモデルの属性・バリデーション・メソッドを確認 |
| 1-2 | gpg_key_subkey.rb | `app/models/gpg_key_subkey.rb` | サブキーモデルを確認 |

**読解のコツ**: `GpgKey` はメインの公開鍵を、`GpgKeySubkey` はサブキーを表す。`extract_fingerprint` と `extract_primary_keyid` でキー情報が自動抽出される。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | gpg_keys_controller.rb | `app/controllers/user_settings/gpg_keys_controller.rb` | index/create/destroy/revokeアクションを確認 |

**主要処理フロー**:
1. **9-12行目**: `index` アクション - キー一覧取得（with_subkeys でサブキーも含む）
2. **14-22行目**: `create` アクション - GpgKeys::CreateService によるキー登録
3. **24-31行目**: `destroy` アクション - GpgKeys::DestroyService によるキー削除
4. **33-40行目**: `revoke` アクション - キーの失効処理

#### Step 3: ビューレイヤーを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | index.html.haml | `app/views/user_settings/gpg_keys/index.html.haml` | 全体構造を確認 |
| 3-2 | _form.html.haml | `app/views/user_settings/gpg_keys/_form.html.haml` | 登録フォームを確認 |
| 3-3 | _key_table.html.haml | `app/views/user_settings/gpg_keys/_key_table.html.haml` | キー一覧テーブルを確認 |

#### Step 4: サービスレイヤーを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | create_service.rb | `app/services/gpg_keys/create_service.rb` | キー登録ロジック |
| 4-2 | destroy_service.rb | `app/services/gpg_keys/destroy_service.rb` | キー削除ロジック |

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

```
UserSettings::GpgKeysController#index
    │
    ├─ current_user.gpg_keys.with_subkeys
    │      └─ @gpg_keys
    │
    └─ GpgKey.new
           └─ @gpg_key (空のキーオブジェクト)

UserSettings::GpgKeysController#create
    │
    └─ GpgKeys::CreateService#execute
           ├─ GpgKey.new(params)
           ├─ extract_fingerprint (before_validation)
           ├─ extract_primary_keyid (before_validation)
           ├─ key.save
           ├─ generate_subkeys (after_create)
           └─ InvalidGpgSignatureUpdateWorker.perform_async (after_commit)

GpgKey#revoke
    │
    ├─ CommitSignatures::GpgSignature.with_key_and_subkeys(self)
    │      └─ update_all(verification_status: :unknown_key)
    │
    └─ destroy
```

### データフロー図

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

PGP公開鍵 ───▶ Gitlab::Gpg.fingerprints_from_key ───▶ fingerprint
    │                      │
    │         Gitlab::Gpg.primary_keyids_from_key ───▶ primary_keyid
    │                      │
    │         Gitlab::Gpg.subkeys_from_key ───▶ サブキー情報
    │                      │
    └──────────────────────┼──────────────────────▶ gpg_keys テーブル
                           │
                           └─ generate_subkeys ───▶ gpg_key_subkeys テーブル

キー登録完了 ───▶ InvalidGpgSignatureUpdateWorker ───▶ gpg_signatures 再検証
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| gpg_keys_controller.rb | `app/controllers/user_settings/gpg_keys_controller.rb` | コントローラー | リクエスト処理 |
| index.html.haml | `app/views/user_settings/gpg_keys/index.html.haml` | テンプレート | 一覧画面 |
| _form.html.haml | `app/views/user_settings/gpg_keys/_form.html.haml` | パーシャル | 登録フォーム |
| _key_table.html.haml | `app/views/user_settings/gpg_keys/_key_table.html.haml` | パーシャル | キー一覧テーブル |
| _key.html.haml | `app/views/user_settings/gpg_keys/_key.html.haml` | パーシャル | キー行表示 |
| gpg_key.rb | `app/models/gpg_key.rb` | モデル | GPGキーデータ定義 |
| gpg_key_subkey.rb | `app/models/gpg_key_subkey.rb` | モデル | サブキーデータ定義 |
| create_service.rb | `app/services/gpg_keys/create_service.rb` | サービス | キー登録ロジック |
| destroy_service.rb | `app/services/gpg_keys/destroy_service.rb` | サービス | キー削除ロジック |
| user_settings.rb | `config/routes/user_settings.rb` | 設定 | ルーティング定義 |
| gpg.rb | `lib/gitlab/gpg.rb` | ライブラリ | GPGキーパース |
