# 機能設計書 84-アクセス権限管理

## 概要

本ドキュメントは、Fat Free CRMにおける「アクセス権限管理」機能の設計仕様を定義する。CanCanとPermissionモデルを活用した、エンティティレベルでの柔軟なアクセス制御機能である。

### 本機能の処理概要

アクセス権限管理機能は、各エンティティ（取引先、キャンペーン、リード、連絡先、商談）に対して、公開範囲（Public/Private/Shared）を設定し、ユーザーやグループ単位でのアクセス制御を実現する機能である。

**業務上の目的・背景**：CRMシステムでは、顧客情報や商談情報に対するアクセス制御が重要である。例えば、特定の大口顧客は担当者のみがアクセスできるようにしたい、チーム内でのみ共有したい、といった要件がある。アクセス権限管理により、データの機密性を保ちながら必要なユーザーには適切にアクセスを許可できる。

**機能の利用シーン**：
- 営業担当者が自身の顧客情報を他の担当者に見られないようにする場合（Private）
- 全社員がアクセスできる共通顧客情報を登録する場合（Public）
- チームメンバー間でのみ商談情報を共有する場合（Shared）
- 管理者が全エンティティにアクセスする場合（Admin権限）

**主要な処理内容**：
1. アクセス権限設定：エンティティ作成/編集時にaccess属性を設定
2. 共有ユーザー/グループ指定：Shared選択時に共有対象を指定
3. 権限チェック：エンティティアクセス時にCanCanで権限検証
4. 権限スコープ適用：一覧表示時にユーザーのアクセス可能範囲でフィルタ

**関連システム・外部連携**：
- CanCan gem：能力ベースの認可フレームワーク
- Permissionモデル：共有権限の永続化
- uses_user_permissions：権限機能を提供するmixin

**権限による制御**：
- 管理者（admin）：全エンティティにアクセス可能
- 一般ユーザー：自身が作成・担当・共有されたエンティティにアクセス可能
- Public：全ユーザーがアクセス可能
- Private：作成者・担当者のみアクセス可能
- Shared：指定されたユーザー/グループのみアクセス可能

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 9 | 取引先新規作成フォーム | 主画面 | 取引先のアクセス権限設定 |
| 10 | 取引先編集フォーム | 主画面 | 取引先のアクセス権限設定 |
| 13 | キャンペーン新規作成フォーム | 主画面 | キャンペーンのアクセス権限設定 |
| 14 | キャンペーン編集フォーム | 主画面 | キャンペーンのアクセス権限設定 |
| 17 | リード新規作成フォーム | 主画面 | リードのアクセス権限設定 |
| 18 | リード編集フォーム | 主画面 | リードのアクセス権限設定 |
| 22 | 連絡先新規作成フォーム | 主画面 | 連絡先のアクセス権限設定 |
| 23 | 連絡先編集フォーム | 主画面 | 連絡先のアクセス権限設定 |
| 26 | 商談新規作成フォーム | 主画面 | 商談のアクセス権限設定 |
| 27 | 商談編集フォーム | 主画面 | 商談のアクセス権限設定 |

## 機能種別

認可・アクセス制御

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| access | String | No | アクセスレベル（Public/Private/Shared） | 有効な値のみ許可（デフォルト: Public） |
| user_ids | Array[Integer] | No | 共有ユーザーIDの配列 | Shared時のみ有効 |
| group_ids | Array[Integer] | No | 共有グループIDの配列 | Shared時のみ有効 |

### 入力データソース

- 画面入力：エンティティ作成/編集フォームのアクセス権限セクション
- API：RESTful APIからのアクセス権限パラメータ

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| entity.access | String | アクセスレベル |
| entity.user_ids | Array[Integer] | 共有ユーザーID配列 |
| entity.group_ids | Array[Integer] | 共有グループID配列 |
| entity.permissions | Array[Permission] | 権限レコードの配列 |

### 出力先

- データベース：permissionsテーブル
- 画面表示：アクセス権限設定フォーム、エンティティ詳細画面

## 処理フロー

### 処理シーケンス

```
1. アクセス権限設定（作成/編集時）
   └─ accessパラメータでPublic/Private/Sharedを選択
2. 共有対象指定（Shared時）
   └─ user_ids/group_idsで共有対象を指定
3. 権限レコード管理
   └─ Permissionモデルで共有権限を永続化
4. 権限チェック（アクセス時）
   └─ CanCan Abilityで権限検証
5. 権限スコープ適用（一覧時）
   └─ accessible_byでユーザーのアクセス可能範囲をフィルタ
```

### フローチャート

```mermaid
flowchart TD
    A[エンティティ作成/編集] --> B{access値?}
    B -->|Public| C[全ユーザーアクセス可能]
    B -->|Private| D[作成者・担当者のみ]
    B -->|Shared| E[共有対象を指定]
    E --> F[user_ids/group_ids設定]
    F --> G[Permissionレコード作成/更新]
    C --> H[エンティティ保存]
    D --> H
    G --> H

    I[エンティティアクセス] --> J{権限チェック}
    J -->|Admin| K[アクセス許可]
    J -->|Not Admin| L{access値?}
    L -->|Public| K
    L -->|Private| M{作成者/担当者?}
    L -->|Shared| N{Permission存在?}
    M -->|Yes| K
    M -->|No| O[アクセス拒否]
    N -->|Yes| K
    N -->|No| O
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-84-01 | 管理者フルアクセス | 管理者は全エンティティにアクセス可能 | user.admin? == true |
| BR-84-02 | Public公開 | Public設定のエンティティは全ユーザーがアクセス可能 | access == 'Public' |
| BR-84-03 | Private制限 | Private設定は作成者・担当者のみアクセス可能 | access == 'Private' |
| BR-84-04 | Shared共有 | Shared設定は指定ユーザー/グループのみアクセス可能 | access == 'Shared' |
| BR-84-05 | Shared要件 | Shared設定時は最低1ユーザーまたは1グループの指定が必要 | access == 'Shared' |
| BR-84-06 | 権限継承 | グループに属するユーザーはグループの権限を継承 | group_ids指定時 |
| BR-84-07 | 権限クリア | access変更時に既存のPermissionレコードを削除 | access != 'Shared'に変更時 |

### 計算ロジック

- **権限スコープ**: `Permission.where(user_id: user.id).or(Permission.where(group_id: user.group_ids))`

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 権限設定 | permissions | INSERT/DELETE | 共有権限の作成/削除 |
| 権限チェック | permissions | SELECT | 権限の存在確認 |
| エンティティ更新 | accounts等 | UPDATE | access属性の更新 |

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

#### permissions

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | user_id, group_id, asset_id, asset_type | 共有対象ID、エンティティID、エンティティタイプ | Shared設定時 |
| DELETE | - | asset_id, asset_type | access変更時にクリア |
| SELECT | user_id, group_id | asset_id, asset_type指定 | 権限チェック時 |

#### 各エンティティテーブル（accounts等）

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | access | 'Public'/'Private'/'Shared' | アクセスレベル変更時 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | 共有先未指定 | Shared選択時にuser_ids/group_idsが空 | バリデーションエラー表示 |
| - | アクセス拒否 | 権限のないエンティティへのアクセス | CanCan::AccessDenied例外、リダイレクト |

### リトライ仕様

権限関連処理にリトライ機能はない。

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

エンティティの保存と同一トランザクション内でPermissionレコードの作成/削除が処理される。

## パフォーマンス要件

- permissionsテーブルのインデックスによる効率的な権限チェック
- accessible_byスコープによるN+1クエリの回避

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

- **認証**：ログインユーザーのみアクセス可能（authenticate_user!）
- **認可**：CanCanによるエンティティレベルのアクセス制御
- **データ保護**：Private/Sharedエンティティは権限のないユーザーには非表示
- **監査**：アクセスログはVersionモデルで記録

## 備考

- CanCan gemのAbilityクラスで権限ルールを一元管理
- uses_user_permissions mixinで権限機能を各モデルに提供
- Permissionレコードは意図的に:dependent => :destroyを設定せず、削除されたエンティティの権限履歴を保持

---

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

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

### 推奨読解順序

#### Step 1: Abilityクラスで権限ルールを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | ability.rb | `app/models/users/ability.rb` | CanCanの権限定義 |

**主要処理フロー**:
- **15行目**: `can(:create, User) if User.can_signup?`でユーザー登録権限
- **21行目**: `can :manage, User, id: user.id`で自身のユーザー情報管理権限
- **24-27行目**: タスクの権限設定（作成者、担当者、完了者）
- **30行目**: `can :manage, entities, access: 'Public'`でPublicエンティティへのアクセス
- **31-32行目**: 自身が作成・担当するエンティティへのアクセス
- **39-40行目**: 管理者は全権限（can :manage, :all）
- **43-53行目**: Permission経由の共有権限チェック

**読解のコツ**: CanCanの`can`メソッドは、アクション、対象クラス、条件ハッシュの順で指定。条件にマッチするレコードのみ操作可能。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | permission.rb | `app/models/users/permission.rb` | 共有権限の永続化モデル |

**主要処理フロー**:
- **20-23行目**: belongs_to関連（user, group, asset）
- **25-26行目**: user_idまたはgroup_idの必須バリデーション
- **28行目**: ユニーク制約（同じuser/group/assetの組み合わせは1つ）

#### Step 3: uses_user_permissions mixinを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | permissions.rb | `lib/fat_free_crm/permissions.rb` | 権限機能のmixin |

**主要処理フロー**:
- **15行目**: `uses_user_permissions`メソッド定義
- **23行目**: `has_many :permissions, as: :asset`で多態的関連
- **25行目**: `scope :my, ->(current_user) { accessible_by(current_user.ability) }`で権限スコープ
- **36-56行目**: `user_ids=`と`group_ids=`のセッター（Permission管理）
- **61-64行目**: `access=`セッター（Shared以外になったら権限クリア）
- **68-80行目**: `remove_permissions`で既存権限削除

#### Step 4: モデルでの権限機能使用を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | account.rb | `app/models/entities/account.rb` | uses_user_permissionsの使用例 |

**主要処理フロー**:
- **64行目**: `uses_user_permissions`で権限機能を有効化
- **143-145行目**: `users_for_shared_access`バリデーション（Shared時の共有先必須チェック）

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

```
ブラウザ（エンティティ操作）
    │
    └─ EntityController (create/update/show/index)
           │
           ├─ load_and_authorize_resource (before_action)
           │      │
           │      └─ CanCan
           │             │
           │             └─ Ability#initialize
           │                    │
           │                    ├─ user.admin? → can :manage, :all
           │                    │
           │                    └─ Permission.where(...)
           │                           └─ can :manage, entity_class, id: permission.asset_id
           │
           ├─ get_list_of_records
           │      │
           │      └─ entities.my(current_user)
           │             │
           │             └─ accessible_by(current_user.ability)
           │
           └─ entity.update(access:, user_ids:, group_ids:)
                  │
                  ├─ access= setter
                  │      └─ remove_permissions (if not Shared)
                  │
                  └─ user_ids= / group_ids= setters
                         └─ Permission.build / destroy
```

### データフロー図

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

access params       ──────▶  entity.access=               ──────▶  access属性更新
                                │
                                ▼
                          (access != 'Shared')
                                │
                                ▼
                          remove_permissions             ──────▶  Permissionレコード削除

user_ids params     ──────▶  entity.user_ids=            ──────▶  Permission INSERT/DELETE
                                │
                                ▼
                          permissions.build(:user_id => id)

group_ids params    ──────▶  entity.group_ids=           ──────▶  Permission INSERT/DELETE


[権限チェック]                   [処理]                          [出力]

current_user        ──────▶  Ability#initialize          ──────▶  can/cannot定義
                                │
                                ▼
                          Permission.where(...)
                                │
                                ▼
entity access       ──────▶  authorize!                  ──────▶  許可/AccessDenied
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| ability.rb | `app/models/users/ability.rb` | ソース | CanCan権限定義 |
| permission.rb | `app/models/users/permission.rb` | ソース | 共有権限モデル |
| permissions.rb | `lib/fat_free_crm/permissions.rb` | ソース | 権限機能mixin |
| account.rb | `app/models/entities/account.rb` | ソース | uses_user_permissions使用例 |
| entities_controller.rb | `app/controllers/entities_controller.rb` | ソース | load_and_authorize_resource |
| application_controller.rb | `app/controllers/application_controller.rb` | ソース | AccessDenied例外処理 |
| 20100928030602_create_permissions.rb | `db/migrate/` | マイグレーション | permissionsテーブル作成 |
| 20100928030624_add_index_on_permissions.rb | `db/migrate/` | マイグレーション | インデックス追加 |
