# 通知設計書 10-レガシーAPIトークン警告（LegacyApiTokenAdministrativeMonitor）

## 概要

本ドキュメントは、レガシーAPIトークンを使用しているユーザーが存在する場合に管理者へ警告を表示するLegacyApiTokenAdministrativeMonitor（レガシーAPIトークン警告）の通知設計について記述する。

### 本通知の処理概要

LegacyApiTokenAdministrativeMonitorは、Jenkins上のユーザーがレガシー（旧形式）のAPIトークンを保持している場合を検出し、管理者に対して警告メッセージを表示し、トークンの移行または削除を促す通知機能である。

**業務上の目的・背景**：Jenkins 2.129以降、APIトークンの形式が変更され、複数のトークンを持てる新形式が導入された。レガシートークンは、固定で予測可能な形式であり、セキュリティ上の懸念がある。また、トークンの使用状況追跡ができない。本通知は、管理者がレガシートークンを使用しているユーザーを特定し、新形式への移行を促進することで、セキュリティを向上させることを目的とする。

**通知の送信タイミング**：Jenkins管理画面（/manage）にアクセスした際に、`isActivated()`メソッドが呼び出され、全ユーザーをスキャンしてApiTokenPropertyにレガシートークンを持つユーザーが1人でも存在する場合に通知が有効化される。

**通知の受信者**：Jenkins管理画面にアクセスできる管理者が対象となる。セキュリティ関連通知として分類される。

**通知内容の概要**：レガシーAPIトークンを使用しているユーザーが存在する旨のメッセージが表示される。詳細管理画面では、影響を受けるユーザーの一覧、各トークンの作成日・最終使用日・使用回数、新しいトークンの有無などが表示される。管理者は選択したユーザーのトークンを一括で取り消すことができる。

**期待されるアクション**：管理者は詳細管理画面でレガシートークンを使用しているユーザーを確認する。各ユーザーに新形式のトークンへの移行を促すか、使用されていないトークンを取り消す。「Fresh Token」「Recently Used Token」列を参考に、安全に取り消せるトークンを特定する。

## 通知種別

アプリ内通知（管理画面バナー表示 + 詳細管理画面）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 同期（画面レンダリング時） |
| 優先度 | 高（セキュリティ関連） |
| リトライ | 無（画面表示のため不要） |

### 送信先決定ロジック

Jenkins管理画面（/manage）にアクセスした管理者に対して表示される。レガシートークンを持つユーザーが1人以上存在する場合に有効化。

## 通知テンプレート

### アプリ内通知の場合

| 項目 | 内容 |
|-----|------|
| 表示位置 | 管理画面上部 |
| アラートタイプ | セキュリティ関連（isSecurity = true） |
| 形式 | HTML |

### 詳細管理画面（manage.jelly）

詳細管理画面では以下の情報が表示される：

| 列名 | 説明 |
|------|------|
| (チェックボックス) | 一括取り消し用 |
| User ID | ユーザーID |
| User Full Name | ユーザーのフルネーム |
| Token Name | トークン名（通常「Legacy Token」） |
| Days Since Creation | 作成からの日数 |
| Num Of Use | 使用回数 |
| Days Since Last Use | 最終使用からの日数 |
| Has Fresh Token | 新しいトークンが作成済みか |
| Has More Recently Used Token | より最近使用されたトークンがあるか |

### 添付ファイル

該当なし

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| it.impactedUserList | 影響を受けるユーザーリスト | getImpactedUserList() | Yes |
| legacyToken | ユーザーのレガシートークン | getLegacyTokenOf(user) | Yes |
| legacyStats | トークンの統計情報 | getLegacyStatsOf(user, legacyToken) | Yes |
| hasFreshToken | 新しいトークンがあるか | hasFreshToken(user, legacyStats) | Yes |
| hasMoreRecentlyUsedToken | より最近使用されたトークンがあるか | hasMoreRecentlyUsedToken(user, legacyStats) | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| 画面操作 | 管理画面アクセス | User.getAll()にレガシートークン保持者が存在 | いずれかのユーザーがレガシートークンを保持 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| 全ユーザーのレガシートークンが存在しない場合 | すべてのユーザーが新形式に移行済み |
| ユーザーがレガシートークンを取り消した場合 | 取り消しにより条件を満たさなくなった場合 |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[管理画面アクセス] --> B{isActivated?}
    B -->|No| C[表示しない]
    B -->|Yes| D[警告表示]
    D --> E[詳細管理画面へ]
    E --> F[影響ユーザー一覧表示]
    F --> G{ユーザーアクション}
    G -->|個別確認| H[各ユーザーのトークン状態確認]
    G -->|一括選択| I[対象トークン選択]
    I --> J[Revoke All Selected]
    J --> K[doRevokeAllSelected実行]
    K --> L[トークン取り消し]
    L --> M[ユーザー保存]
    M --> N[終了]
    H --> N
    C --> N
```

## データベース参照・更新仕様

### 参照テーブル一覧

該当なし（ファイルベースの設定を参照）

### 参照データ構造

| データ構造 | 用途 | 備考 |
|-----------|------|------|
| User.getAll() | 全ユーザーの取得 | Jenkins登録ユーザー |
| ApiTokenProperty | ユーザーのAPIトークン情報 | User.getProperty(ApiTokenProperty.class) |
| ApiTokenStore | トークンストア | hasLegacyToken()、getLegacyToken() |
| ApiTokenStats | トークン統計情報 | 使用回数、最終使用日 |

### 更新ファイル

| ファイル | 操作 | 概要 |
|---------|------|------|
| $JENKINS_HOME/users/{user}/config.xml | UPDATE | トークン取り消し時に更新 |

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| ユーザー取得失敗 | User.getById()がnull | ログ出力（INFO）、スキップ |
| ApiTokenProperty未設定 | user.getProperty()がnull | ログ出力（INFO）、スキップ |
| トークン取り消し失敗 | revokeToken()がnull | ログ出力（INFO）、スキップ |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | 0（個別エラーはスキップして継続） |
| リトライ間隔 | - |
| リトライ対象エラー | - |

## 配信設定

### レート制限

該当なし（画面表示のため）

### 配信時間帯

制限なし（常時有効）

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

- 本通知はセキュリティ関連（`isSecurity()` = true）としてマークされている
- doRevokeAllSelected()は`@RequirePOST`で保護されている
- @Restricted(NoExternalUse.class)で外部使用制限
- トークン取り消しはログに記録される

## 備考

- Symbol: `legacyApiTokenUsage`
- パッケージ: jenkins.security.apitoken
- ID: "legacyApiToken"
- 関連クラス: ApiTokenProperty、ApiTokenStore、ApiTokenStats
- メッセージリソース: jenkins/security/apitoken/Messages.properties

---

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

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

### 推奨読解順序

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

ApiTokenProperty、ApiTokenStore、ApiTokenStatsの関係を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | ApiTokenProperty.java | `core/src/main/java/jenkins/security/ApiTokenProperty.java` | ユーザーのトークンプロパティ |
| 1-2 | ApiTokenStore.java | `core/src/main/java/jenkins/security/apitoken/ApiTokenStore.java` | トークンストア、hasLegacyToken() |
| 1-3 | ApiTokenStats.java | `core/src/main/java/jenkins/security/apitoken/ApiTokenStats.java` | トークン統計情報 |

**読解のコツ**: hasLegacyToken()でレガシートークンの存在を判定、getLegacyToken()でトークン情報を取得する点に注目。

#### Step 2: モニター処理を理解する

LegacyApiTokenAdministrativeMonitorのisActivated()とヘルパーメソッド。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | LegacyApiTokenAdministrativeMonitor.java | `core/src/main/java/jenkins/security/apitoken/LegacyApiTokenAdministrativeMonitor.java` | isActivated()、getImpactedUserList()、判定メソッド群 |

**主要処理フロー**:
1. **68-74行目**: `isActivated()` - 全ユーザーをスキャンしレガシートークン保持者を検出
2. **86-94行目**: `getImpactedUserList()` - 影響を受けるユーザーのリスト取得
3. **97-101行目**: `getLegacyTokenOf()` - ユーザーのレガシートークン取得
4. **104-114行目**: `getLegacyStatsOf()` - トークン統計情報取得
5. **120-138行目**: `hasFreshToken()` - 新しいトークンが存在するか判定
6. **143-162行目**: `hasMoreRecentlyUsedToken()` - より最近使用されたトークンがあるか判定

#### Step 3: 取り消し処理を理解する

doRevokeAllSelected()の実装。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | LegacyApiTokenAdministrativeMonitor.java | `core/src/main/java/jenkins/security/apitoken/LegacyApiTokenAdministrativeMonitor.java` | doRevokeAllSelected()、RevokeAllSelectedModel |

**主要処理フロー**:
- **164-197行目**: `doRevokeAllSelected()` - 選択されたトークンの一括取り消し
- **172-195行目**: 各ユーザーのトークン取り消しループ
- **185-191行目**: トークン取り消しと保存

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

```
Jenkins.getAdministrativeMonitors()
    │
    ├─ LegacyApiTokenAdministrativeMonitor.isActivated()
    │      │
    │      └─ User.getAll().stream()
    │             │
    │             └─ user.getProperty(ApiTokenProperty.class)
    │                    │
    │                    └─ apiTokenProperty.hasLegacyToken()
    │
    └─ 詳細管理画面
           │
           ├─ getImpactedUserList()
           │
           ├─ getLegacyTokenOf(user)
           │      └─ apiTokenProperty.getTokenStore().getLegacyToken()
           │
           ├─ getLegacyStatsOf(user, legacyToken)
           │      └─ apiTokenProperty.getTokenStats().findTokenStatsById()
           │
           ├─ hasFreshToken(user, legacyStats)
           │      └─ apiTokenProperty.getTokenList().stream()
           │
           └─ hasMoreRecentlyUsedToken(user, legacyStats)
                  └─ apiTokenProperty.getTokenList().stream()
```

### データフロー図

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

User.getAll() ───▶ ApiTokenProperty取得 ───▶ hasLegacyToken()
                         │
                         └─ レガシートークン保持者検出
                                    │
                                    ▼
                              警告バナー表示
                                    │
                                    ▼
                              詳細管理画面
                                    │
                   ┌────────────────┼────────────────┐
                   ▼                ▼                ▼
            getImpactedUserList  getLegacyTokenOf  hasFreshToken
                   │                │                │
                   └────────────────┴────────────────┘
                                    │
                                    ▼
                              ユーザー一覧表示
                                    │
                                    ▼
                         doRevokeAllSelected()
                                    │
                                    ▼
                         トークン取り消し + user.save()
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| LegacyApiTokenAdministrativeMonitor.java | `core/src/main/java/jenkins/security/apitoken/LegacyApiTokenAdministrativeMonitor.java` | ソース | メインクラス |
| AdministrativeMonitor.java | `core/src/main/java/hudson/model/AdministrativeMonitor.java` | ソース | 基底クラス |
| ApiTokenProperty.java | `core/src/main/java/jenkins/security/ApiTokenProperty.java` | ソース | トークンプロパティ |
| ApiTokenStore.java | `core/src/main/java/jenkins/security/apitoken/ApiTokenStore.java` | ソース | トークンストア |
| ApiTokenStats.java | `core/src/main/java/jenkins/security/apitoken/ApiTokenStats.java` | ソース | トークン統計 |
| manage.jelly | `core/src/main/resources/jenkins/security/apitoken/LegacyApiTokenAdministrativeMonitor/manage.jelly` | テンプレート | 詳細管理画面 |
| Messages.properties | `core/src/main/resources/jenkins/security/apitoken/Messages.properties` | リソース | メッセージ定義 |
| LegacyApiTokenAdministrativeMonitorTest.java | `test/src/test/java/jenkins/security/apitoken/LegacyApiTokenAdministrativeMonitorTest.java` | テスト | 単体テスト |
