# 機能設計書 89-OAuth2クライアント

## 概要

本ドキュメントは、GitLabにおけるOAuth2クライアント機能の設計を記述する。GitLabは外部のOAuth2/OIDC認証プロバイダ（Google、GitHub、Twitter等）と連携し、ユーザーが外部サービスのアカウントでGitLabにサインインできる機能を提供する。OmniAuthを基盤として実装されている。

### 本機能の処理概要

**業務上の目的・背景**：ユーザーは複数のサービスに対して個別のパスワードを管理する負担を軽減したいというニーズがある。OAuth2クライアント機能により、Google、GitHub、Azure AD等の既存アカウントを使用してGitLabにサインインでき、ユーザー体験の向上とセキュリティの強化を実現する。

**機能の利用シーン**：
- GoogleアカウントでGitLabにサインイン
- GitHubアカウントでGitLabにサインイン
- 企業のAzure ADアカウントでSSOサインイン
- SAML IdPによるシングルサインオン
- 既存GitLabアカウントへの外部認証連携

**主要な処理内容**：
1. 外部プロバイダへの認証リクエスト
2. コールバック処理（認証結果受信）
3. ユーザーの検索・作成・連携
4. サインイン処理
5. 既存アカウントへのアイデンティティリンク
6. 二要素認証との連携

**関連システム・外部連携**：
- OmniAuth gem（認証フレームワーク）
- 外部OAuth2/OIDCプロバイダ
- SAML IdP
- LDAPサーバー
- Devise（認証基盤）

**権限による制御**：
- プロバイダの有効/無効は管理者設定
- サインアップ可否は設定で制御
- 特定プロバイダからの自動リンクは設定で制御

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 001 | サインイン | 主画面 | 外部プロバイダ選択 |
| 002 | サインアップ | 参照画面 | 外部プロバイダでの登録 |
| 114 | アカウント設定 | 参照画面 | アイデンティティ連携 |

## 機能種別

認証・認可 / SSO

## 入力仕様

### 入力パラメータ（OmniAuth認証）

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| provider | String | Yes | 認証プロバイダ名 | 有効なプロバイダ |
| redirect_fragment | String | No | リダイレクト先フラグメント | 予約文字不可 |
| remember_me | String | No | ログイン保持 | "1"または未指定 |
| SAMLResponse | String | Conditional | SAML認証レスポンス | SAMLプロバイダ時 |

### 入力パラメータ（コールバック）

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| omniauth.auth | Hash | Yes | 認証情報ハッシュ | OmniAuthフォーマット |
| omniauth.params | Hash | No | リクエストパラメータ | - |
| omniauth.error | Exception | Conditional | 認証エラー | 失敗時 |

### 入力データソース

- 外部認証プロバイダからのコールバック
- ユーザー操作（プロバイダ選択）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| user | User | 認証されたユーザー |
| identity | Identity | プロバイダ連携情報 |
| session | Hash | セッション情報 |

### 出力先

- セッション確立
- リダイレクト（成功/失敗）
- 監査ログ

## 処理フロー

### 処理シーケンス

```
1. 認証リクエスト
   └─ /users/auth/:provider へリダイレクト
2. 外部プロバイダ認証
   └─ プロバイダサイトでの認証
3. コールバック受信
   └─ OmniauthCallbacksController#handle_omniauth
4. ユーザー検索・作成
   └─ Gitlab::Auth::OAuth::User#find_user
5. サインイン処理
   └─ sign_in_user_flow
6. 監査ログ記録
   └─ log_audit_event
```

### フローチャート

```mermaid
flowchart TD
    A[プロバイダ選択] --> B[外部認証]
    B --> C[コールバック]
    C --> D{認証成功?}
    D -->|No| E[failure処理]
    E --> F[エラーカウント増加]
    F --> G[エラー画面]

    D -->|Yes| H{ログイン済み?}
    H -->|Yes| I{管理者モード?}
    I -->|Yes| J[admin_mode_flow]
    I -->|No| K[アイデンティティリンク]
    K --> L{認可必要?}
    L -->|Yes| M[認可画面]
    L -->|No| N[link_identity]
    M --> N
    N --> O[リダイレクト]

    H -->|No| P[sign_in_user_flow]
    P --> Q{ユーザー検索}
    Q --> R{uid/providerで検索}
    R -->|見つかった| S[既存ユーザー]
    R -->|見つからない| T{メールで検索}
    T -->|見つかった| U[auto_link判定]
    T -->|見つからない| V{サインアップ可?}
    V -->|Yes| W[新規ユーザー作成]
    V -->|No| X[SignupDisabledError]

    S --> Y{2FA有効?}
    U --> Y
    W --> Y
    Y -->|Yes| Z[2FA認証プロンプト]
    Y -->|No| AA[サインイン完了]
    Z --> AB{2FA成功?}
    AB -->|Yes| AA
    AB -->|No| G
    AA --> AC[監査ログ]
    AC --> O
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-89-01 | プロバイダ無効化 | 管理者は特定プロバイダでのサインインを無効化可能 | disabled_oauth_sign_in_sources |
| BR-89-02 | 自動サインアップ | allow_single_sign_on設定でサインアップ許可 | プロバイダ毎 |
| BR-89-03 | 自動リンク | auto_link_user設定でメールによる自動リンク | プロバイダ毎 |
| BR-89-04 | 外部ユーザー | external_providers設定で外部ユーザーとしてマーク | 新規作成時 |
| BR-89-05 | 2FAバイパス | allow_bypass_two_factor設定で2FAスキップ | プロバイダ毎 |
| BR-89-06 | ブロック | block_auto_created_users設定で自動作成ユーザーをブロック | 新規作成時 |
| BR-89-07 | プロファイル同期 | sync_profile_from_provider設定でプロファイル同期 | 認証毎 |
| BR-89-08 | LDAP自動リンク | auto_link_ldap_user設定でLDAPユーザーと自動リンク | LDAP有効時 |

### 計算ロジック

**ユーザー検索優先順位**：
1. uid + providerでIdentity検索
2. auto_link_user有効時、メールで検索
3. auto_link_ldap_user有効時、LDAPユーザー検索
4. signup_enabled時、新規作成

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| ユーザー検索 | identities | SELECT | uid/provider検索 |
| ユーザー検索 | users | SELECT | メール検索 |
| 新規作成 | users | INSERT | 新規ユーザー |
| 新規作成 | identities | INSERT | アイデンティティ |
| リンク | identities | INSERT/UPDATE | アイデンティティ追加・更新 |
| ログイン失敗 | users | UPDATE | failed_attempts増加 |

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

#### identities

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | provider | 認証プロバイダ | |
| SELECT | extern_uid | 外部UID | |
| INSERT | user_id | ユーザーID | |
| INSERT | provider | 認証プロバイダ | |
| INSERT | extern_uid | 外部UID | |
| UPDATE | extern_uid | 新しい外部UID | 信頼済みの場合 |

#### users

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | email | メールアドレス | auto_link時 |
| INSERT | name | ユーザー名 | 新規作成時 |
| INSERT | username | ユーザー名 | 新規作成時 |
| INSERT | email | メールアドレス | 新規作成時 |
| INSERT | external | true/false | external_provider時true |
| UPDATE | failed_attempts | +1 | ログイン失敗時 |
| UPDATE | last_credential_check_at | 現在日時 | 認証成功時 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | SignupDisabledError | サインアップ無効 | サインアップページへ誘導 |
| - | SigninDisabledForProviderError | プロバイダ無効 | エラーメッセージ表示 |
| - | IdentityWithUntrustedExternUidError | 信頼されていないUID | 再連携を促す |
| - | InvalidFragmentError | 無効なリダイレクトフラグメント | "Invalid state"表示 |

### リトライ仕様

認証失敗時はfailed_attemptsを増加。一定回数でアカウントロック。

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

ユーザー作成とアイデンティティ作成は同一トランザクション内で実行。

## パフォーマンス要件

- コールバック処理：2秒以内
- ユーザー検索：500ms以内

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

- CSRFトークンによる保護（SAMLは除外）
- リダイレクトフラグメントのバリデーション（予約文字禁止）
- 監査ログによる認証イベント追跡
- 信頼されていないextern_uidの検出
- 二要素認証との連携

## 備考

- INVALID_FRAGMENT_EXP = %r{[;/?:@&=+$,]+} で予約文字をチェック
- 複数プロバイダのエイリアスメソッドを動的に定義
- SAMLプロバイダは特別な処理フロー

---

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

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

### 推奨読解順序

#### Step 1: コントローラー層を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | omniauth_callbacks_controller.rb | `app/controllers/omniauth_callbacks_controller.rb` | メインコールバック処理 |

**主要処理フロー**:
- **3行目**: Devise::OmniauthCallbacksControllerを継承
- **16行目**: ACTIVE_SINCE_KEY定義
- **21行目**: INVALID_FRAGMENT_EXP（予約文字パターン）
- **27行目**: protect_from_forgeryの例外設定
- **34-42行目**: handle_omniauthメソッド（メイン処理分岐）
- **44-46行目**: プロバイダ毎のエイリアスメソッド動的定義
- **62-75行目**: failureメソッド（認証失敗処理）
- **154-198行目**: omniauth_flowメソッド（メイン認証フロー）
- **200-202行目**: admin_mode_request?判定
- **244-294行目**: sign_in_user_flowメソッド
- **339-341行目**: oauthメソッド（認証情報取得）
- **390-408行目**: log_audit_event（監査ログ）
- **410-418行目**: set_remember_me
- **515-517行目**: store_idp_two_factor_status

#### Step 2: ユーザー処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | user.rb | `lib/gitlab/auth/o_auth/user.rb` | OAuthユーザー処理 |

**主要処理フロー**:
- **11行目**: Gitlab::Auth::OAuth::Userクラス
- **15-24行目**: find_by_uid_and_providerクラスメソッド
- **26-29行目**: エラークラス定義
- **33-38行目**: initialize（プロファイル更新、アイデンティティ追加）
- **52-54行目**: valid_sign_in?
- **56-74行目**: saveメソッド
- **82-92行目**: find_userメソッド（検索優先順位）
- **100-107行目**: bypass_two_factor?
- **125-138行目**: add_or_update_user_identities
- **140-151行目**: find_or_build_ldap_user
- **161-163行目**: auto_link_ldap_user?
- **169-179行目**: ldap_person検索
- **198-205行目**: signup_enabled?
- **207-209行目**: external_provider?
- **211-217行目**: block_after_signup?
- **265-290行目**: update_profile（プロファイル同期）
- **300-304行目**: oauth_provider_disabled?

#### Step 3: アイデンティティ連携を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | identity.rb | `app/models/identity.rb` | アイデンティティモデル |

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

```
OmniauthCallbacksController#handle_omniauth
    │
    ├─ SAML? → #saml
    ├─ OIDC? → #openid_connect
    └─ その他 → #omniauth_flow(Gitlab::Auth::OAuth)
           │
           ├─ store_redirect_fragment
           ├─ current_user存在?
           │      │
           │      ├─ admin_mode_request? → admin_mode_flow
           │      │
           │      └─ identity_linker.link
           │             ├─ redirect_identity_linked
           │             ├─ redirect_identity_link_failed
           │             └─ redirect_identity_exists
           │
           └─ sign_in_user_flow(auth_user_class)
                  │
                  ├─ build_auth_user(auth_user_class)
                  │      └─ Gitlab::Auth::OAuth::User.new(oauth)
                  │             ├─ update_profile
                  │             └─ add_or_update_user_identities
                  │
                  ├─ auth_user.find_and_update!
                  │      ├─ find_user
                  │      │      ├─ find_by_uid_and_provider
                  │      │      ├─ find_by_email (auto_link_user)
                  │      │      ├─ find_or_build_ldap_user
                  │      │      └─ build_new_user
                  │      └─ save
                  │
                  ├─ two_factor_enabled? → prompt_for_two_factor
                  │
                  ├─ sign_in_and_redirect
                  │
                  └─ track_event (log_audit_event)
```

### データフロー図

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

外部プロバイダ        OmniauthCallbacksController      セッション
コールバック     ──▶  ├─認証情報取得              ──▶  確立
(omniauth.auth)       │   (oauth = request.env['omniauth.auth'])
                      │
                      ├─ユーザーフロー判定
                      │   ├─current_user → アイデンティティリンク
                      │   └─未ログイン → サインインフロー
                      │
                      ├─Gitlab::Auth::OAuth::User
                      │   ├─find_by_uid_and_provider
                      │   ├─find_by_email (auto_link)
                      │   ├─build_new_user (signup)
                      │   └─save
                      │
                      ├─2FA判定
                      │   └─bypass_two_factor? or prompt
                      │
                      └─sign_in_and_redirect
                                                      ▼
                                                   リダイレクト
                                                   監査ログ
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| omniauth_callbacks_controller.rb | `app/controllers/omniauth_callbacks_controller.rb` | コントローラー | メインコールバック |
| user.rb | `lib/gitlab/auth/o_auth/user.rb` | ライブラリ | OAuthユーザー処理 |
| auth_hash.rb | `lib/gitlab/auth/o_auth/auth_hash.rb` | ライブラリ | 認証ハッシュ処理 |
| identity_linker.rb | `lib/gitlab/auth/o_auth/identity_linker.rb` | ライブラリ | アイデンティティリンク |
| provider.rb | `lib/gitlab/auth/o_auth/provider.rb` | ライブラリ | プロバイダ設定 |
| identity.rb | `app/models/identity.rb` | モデル | アイデンティティ |
| user_synced_attributes_metadata.rb | `app/models/user_synced_attributes_metadata.rb` | モデル | 属性同期メタデータ |
| auth_helper.rb | `app/helpers/auth_helper.rb` | ヘルパー | 認証ヘルパー |
