Salesforceの項目レベルセキュリティをマスターする:きめ細やかなデータ保護

背景とアプリケーションシナリオ

現代のビジネス環境において、企業は大量の機密データを扱っています。これらのデータを適切に保護し、必要なユーザーにのみ必要な情報を提供する「最小権限の原則 (Principle of Least Privilege)」は、データガバナンスとコンプライアンスの基盤となります。Salesforceは強力なセキュリティモデルを提供しており、組織の共有設定 (Organization-Wide Defaults: OWD)ロール階層 (Role Hierarchy)共有ルール (Sharing Rules)プロファイル (Profiles)権限セット (Permission Sets) など、多層的なアプローチでデータアクセスを制御します。このセキュリティモデルの中で、項目レベルセキュリティ (Field-Level Security: FLS) は、特定のオブジェクトレコード内の個々の項目に対するアクセス権を管理する、極めて重要な要素です。

FLSの主なアプリケーションシナリオは以下の通りです。

  • 機密データの保護: 給与情報、健康情報、クレジットカード番号など、特定の機密性の高い項目を、許可されたユーザーグループのみが閲覧または編集できるように制限します。
  • 役割に応じた情報表示: 営業担当者には顧客の連絡先や商談情報のみを表示し、経理担当者には請求情報や財務関連の項目を表示するといった、ユーザーの役割に応じたデータ表示を実現します。
  • コンプライアンス要件への対応: GDPR、HIPAA、CCPAなどのデータプライバシー規制に対応するため、特定の個人識別情報 (Personally Identifiable Information: PII) へのアクセスを厳格に管理します。
  • ユーザーインターフェースの簡素化: 関連性の低い項目をユーザーから非表示にすることで、ユーザーインターフェースを整理し、効率的な作業を促進します。

原理説明

項目レベルセキュリティ (FLS) は、ユーザーが特定のオブジェクトのレコードにアクセスできる場合に、そのレコード内の個々の項目を表示、編集、または非表示にするかどうかを制御します。これは、レコードレベルのセキュリティ (例: OWD、共有ルール) がユーザーにレコードへのアクセスを許可した後に適用される「追加のフィルター」として機能します。

FLSの構成は、主に以下の2つの方法で行われます。

  1. プロファイル (Profiles):

    各プロファイルには、組織内のすべてのオブジェクトと項目に対するデフォルトのアクセス権限が設定されています。システム管理者は、プロファイルの設定を通じて、特定のオブジェクトの項目に対して「参照アクセス権 (Read Access)」または「編集アクセス権 (Edit Access)」を付与できます。プロファイルは、組織内のユーザーが持つことができるアクセス権のベースラインを定義します。

  2. 権限セット (Permission Sets):

    権限セットは、ユーザーに特定の追加権限を付与するためのツールです。プロファイルが定義するベースライン権限に加えて、特定の機能やオブジェクト、項目へのアクセス権を付与するために使用されます。FLSに関しては、権限セットを使用してプロファイルで許可されていない項目へのアクセス権を付与することが可能です。権限セットは、プロファイルよりも柔軟性が高く、最小限の権限付与の原則を実践する上で推奨される方法です。

FLSは、Salesforceプラットフォームの様々な側面で適用されます。これには以下が含まれます。

  • ユーザーインターフェース: 標準ページレイアウト、Lightningレコードページ、関連リストなど、ユーザーがSalesforce UIを通じてデータを閲覧する際に適用されます。アクセス権がない項目は表示されません。
  • レポートとダッシュボード: レポートやダッシュボードでデータが表示される際にもFLSが適用され、ユーザーがアクセス権を持たない項目は表示されません。
  • リストビューと検索: リストビューやグローバル検索の結果にもFLSが適用されます。
  • APIアクセス: SOQLクエリ、Apexコード、REST API、SOAP APIなどのAPI経由でデータにアクセスする際にもFLSは自動的に適用されます。ユーザーがアクセス権を持たない項目は、クエリ結果に含まれないか、DML操作時にエラーが発生します。

FLSは「制限的な」セキュリティモデルであり、常にユーザーのアクセスを制限する方向に機能します。つまり、項目がプロファイルと権限セットの両方で「参照可能」である場合のみ参照でき、両方で「編集可能」である場合のみ編集できます。どちらか一方でもアクセスが拒否されている場合、その項目にはアクセスできません。


サンプルコード

Apexコードでは、開発者がユーザーの項目レベルセキュリティを尊重することが重要です。ユーザーがアクセスできない項目を、意図せず表示したり更新したりすることを避けるため、Apexの組み込みメソッドを使用してFLSチェックを行うことができます。

最も一般的な方法は、Schema.DescribeFieldResult クラスのメソッド、特に isAccessible()isCreateable()isUpdateable() を使用することです。これらのメソッドは、現在のユーザーが特定の項目にアクセスできるかどうかをチェックします。

以下のサンプルコードは、Accountオブジェクトの 'Phone' 項目と 'AnnualRevenue' 項目に対する現在のユーザーのアクセス権限をチェックし、それに基づいて処理を行う例です。

public class FieldLevelSecurityExample {

    /**
     * 現在のユーザーが特定の項目にアクセスできるか、作成できるか、更新できるかをチェックするメソッド。
     */
    public static void checkAccountFieldPermissions() {
        // Accountオブジェクトのスキーマ情報を取得
        Schema.SObjectDescribeResult accountDescribe = Schema.SObjectType.Account.getDescribe();

        // 'Phone' 項目のスキーマ情報を取得
        Schema.DescribeFieldResult phoneField = accountDescribe.fields.getMap().get('Phone').getDescribe();
        // 'AnnualRevenue' 項目のスキーマ情報を取得
        Schema.DescribeFieldResult annualRevenueField = accountDescribe.fields.getMap().get('AnnualRevenue').getDescribe();

        System.debug('--- Account.Phone の権限チェック ---');
        // 'Phone' 項目が現在のユーザーから参照可能かチェック
        if (phoneField.isAccessible()) {
            System.debug('Account.Phone は参照可能です。');
        } else {
            System.debug('Account.Phone は参照できません。');
        }

        // 'Phone' 項目が現在のユーザーから作成可能かチェック
        if (phoneField.isCreateable()) {
            System.debug('Account.Phone は作成可能です。');
        } else {
            System.debug('Account.Phone は作成できません。');
        }

        // 'Phone' 項目が現在のユーザーから更新可能かチェック
        if (phoneField.isUpdateable()) {
            System.debug('Account.Phone は更新可能です。');
        } else {
            System.debug('Account.Phone は更新できません。');
        }

        System.debug('\n--- Account.AnnualRevenue の権限チェック ---');
        // 'AnnualRevenue' 項目が現在のユーザーから参照可能かチェック
        if (annualRevenueField.isAccessible()) {
            System.debug('Account.AnnualRevenue は参照可能です。');
        } else {
            System.debug('Account.AnnualRevenue は参照できません。');
        }

        // 'AnnualRevenue' 項目が現在のユーザーから作成可能かチェック
        if (annualRevenueField.isCreateable()) {
            System.debug('Account.AnnualRevenue は作成可能です。');
        } else {
            System.debug('Account.AnnualRevenue は作成できません。');
        }

        // 'AnnualRevenue' 項目が現在のユーザーから更新可能かチェック
        if (annualRevenueField.isUpdateable()) {
            System.debug('Account.AnnualRevenue は更新可能です。');
        } else {
            System.debug('Account.AnnualRevenue は更新できません。');
        }
    }

    /**
     * FLSを尊重してレコードを更新する例。
     * @param accountToUpdate 更新するAccountレコード
     * @param newPhone 新しい電話番号
     * @param newAnnualRevenue 新しい年間収益
     */
    public static void updateAccountSafely(Account accountToUpdate, String newPhone, Decimal newAnnualRevenue) {
        // 更新対象のレコードがnullでないことを確認
        if (accountToUpdate == null) {
            System.debug('更新するAccountレコードが指定されていません。');
            return;
        }

        // Accountオブジェクトのスキーマ情報を取得
        Schema.SObjectDescribeResult accountDescribe = Schema.SObjectType.Account.getDescribe();
        Schema.DescribeFieldResult phoneField = accountDescribe.fields.getMap().get('Phone').getDescribe();
        Schema.DescribeFieldResult annualRevenueField = accountDescribe.fields.getMap().get('AnnualRevenue').getDescribe();

        // Phone項目の更新可能性をチェック
        if (phoneField.isUpdateable()) {
            accountToUpdate.Phone = newPhone;
            System.debug('Account.Phone を更新しました: ' + newPhone);
        } else {
            System.debug('Account.Phone は更新できません。スキップします。');
        }

        // AnnualRevenue項目の更新可能性をチェック
        if (annualRevenueField.isUpdateable()) {
            accountToUpdate.AnnualRevenue = newAnnualRevenue;
            System.debug('Account.AnnualRevenue を更新しました: ' + newAnnualRevenue);
        } else {
            System.debug('Account.AnnualRevenue は更新できません。スキップします。');
        }

        // 更新可能な項目のみをDML操作に含める。
        // SObjectをDMLする際、ユーザーが更新権限を持たない項目をセットしても
        // 基本的にはエラーにならないが、明示的なチェックは堅牢性を高める。
        // より推奨されるのは Security.stripInaccessible() を使用する方法。
        try {
            update accountToUpdate;
            System.debug('Accountレコードを正常に更新しました。');
        } catch (DmlException e) {
            System.debug('Accountレコードの更新中にエラーが発生しました: ' + e.getMessage());
        }
    }
}

Security.stripInaccessible() メソッドも非常に有用です。このメソッドは、リスト内のSObjectから現在のユーザーがアクセスできない項目を自動的に削除し、安全にDML操作を行うためのクリーンなSObjectリストを返します。これは特に、SOQLクエリで取得したSObjectのリストをユーザーに表示したり、ユーザーからの入力に基づいてSObjectを更新したりする際に役立ちます。

public class SecurityStripInaccessibleExample {

    /**
     * Security.stripInaccessible() を使用して、ユーザーがアクセスできない項目を自動的に除外する例。
     */
    public static void processAccountsSafely() {
        // すべてのAccountレコードを取得(通常は特定のIDで取得)
        // このクエリはシステムモードで実行されるため、FLSは無視してすべての項目を取得する
        // ただし、その後 stripInaccessible() でユーザーのFLSを適用する
        List<Account> accounts = [SELECT Id, Name, Phone, AnnualRevenue, Description FROM Account LIMIT 10];

        if (accounts.isEmpty()) {
            System.debug('処理するAccountレコードが見つかりませんでした。');
            return;
        }

        System.debug('--- 処理前のAccountレコード ---');
        for (Account acc : accounts) {
            System.debug('Name: ' + acc.Name + ', Phone: ' + acc.Phone + ', AnnualRevenue: ' + acc.AnnualRevenue);
        }

        // Security.stripInaccessible() を使用して、ユーザーが参照できない項目を削除
        // Mode.READABLE は参照権限がない項目を null に設定する
        // Mode.UPDATEABLE は更新権限がない項目を null に設定し、その項目を削除
        List<Account> sanitizedAccounts = Security.stripInaccessible(AccessType.READABLE, accounts).getRecords();

        System.debug('\n--- Security.stripInaccessible() 適用後のAccountレコード ---');
        for (Account acc : sanitizedAccounts) {
            // アクセス権がない項目はnullになっているか、参照できなくなる
            System.debug('Name: ' + acc.Name + ', Phone: ' + acc.Phone + ', AnnualRevenue: ' + acc.AnnualRevenue);
        }

        // 例: 更新する場合
        // sanitizedAccounts = Security.stripInaccessible(AccessType.UPDATABLE, accounts).getRecords();
        // update sanitizedAccounts; // これにより、更新権限のない項目は更新されない
    }
}

注意事項

  • 権限の階層: FLSは、レコードレベルの共有 (OWD、共有ルール、ロール階層) とは独立して機能します。ユーザーがレコードを参照できる場合でも、特定の項目に対するアクセス権がない限り、その項目は表示されません。また、Apexコードが「共有設定の強制 (with sharing)」モードで実行されている場合、レコードレベルの共有設定が適用されますが、FLSも自動的に考慮されます。
  • APIとデータローダ: Salesforce API (SOAP, REST)、データローダ、インテグレーションツールを使用する場合でも、FLSは常に適用されます。ユーザーまたはインテグレーションユーザーが項目に対する適切な権限を持っていない場合、クエリの結果にその項目は含まれないか、更新/挿入操作時にエラーが発生します。
  • システムモードでのApex: Apexコードはデフォルトで「システムモード (System Mode)」で実行されます。これは、現在のユーザーのレコードレベルの共有設定(OWD、共有ルールなど)をバイパスして、すべてのレコードにアクセスできることを意味します。しかし、項目レベルセキュリティはシステムモードであっても自動的に適用されます。つまり、開発者が明示的にFLSをチェックしない限り、ユーザーがアクセスできない項目を誤って表示したり、更新したりする可能性があります。上記の `isAccessible()` や `Security.stripInaccessible()` の使用が強く推奨されます。
  • VisualforceとLightningコンポーネント:
    • Visualforceページ: 標準コントローラーを使用する場合、FLSは自動的に適用されます。カスタムコントローラーを使用する場合は、Apex内でFLSを明示的にチェックする必要があります。
    • Lightning Web Components (LWC) および Aura Components: Salesforce Lightning Data Service (LDS) を使用する場合、FLSは自動的に適用されます。Apexコントローラーを呼び出す場合は、上記と同様にApex内でFLSチェックを実装する必要があります。特に、@wire サービスや uiRecordApi を使用すると、宣言的にFLSが考慮されます。
  • エラー処理: FLSによってアクセスが拒否された場合、DML操作(Insert, Update)は `DmlException` をスローする可能性があります。ApexコードでDML操作を行う際は、適切なエラーハンドリング(`try-catch` ブロック)を実装することが重要です。
  • パフォーマンス: FLSのチェックはSalesforceプラットフォームの基盤レベルで最適化されており、通常、アプリケーションのパフォーマンスに大きな影響を与えることはありません。

まとめとベストプラクティス

項目レベルセキュリティは、Salesforceにおけるデータの機密性と整合性を維持するために不可欠なセキュリティレイヤーです。適切に設定・管理することで、最小限の権限の原則を効果的に適用し、データ漏洩のリスクを軽減できます。

ベストプラクティス:

  1. 権限セットの活用: プロファイルは基本的なユーザータイプ(例: 営業、マーケティング)に特化した最小限の権限セットを提供し、追加の項目アクセス権限は権限セットを介して付与することを推奨します。これにより、変更管理が容易になり、権限の割り当てがより柔軟になります。
  2. 最小権限の原則の徹底: ユーザーに付与する項目へのアクセス権限は、そのユーザーが職務を遂行するために「必要最低限」のものに限定します。
  3. FLSの定期的なレビュー: ビジネス要件の変化や組織の拡大に伴い、FLSの設定が最新かつ適切であることを定期的に見直し、必要に応じて調整します。
  4. ApexコードでのFLS尊重: 常に Schema.DescribeFieldResultisAccessible()isCreateable()isUpdateable()Security.stripInaccessible() メソッドを使用して、ユーザーのFLS設定を尊重したコードを記述します。これにより、データ整合性を保ち、セキュリティ脆弱性を防ぎます。
  5. テストと検証: 異なるプロファイルや権限セットを持つテストユーザーアカウントを使用して、FLSの設定が意図した通りに機能していることを検証します。特に、新しい機能や変更をリリースする前に徹底的なテストを行います。
  6. 分かりやすい項目ラベルとヘルプテキスト: ユーザーがどの項目にアクセスできるかに関わらず、項目名やヘルプテキストは明確にし、データの意味を理解しやすくすることが重要です。

これらのベストプラクティスを遵守することで、Salesforce環境全体のセキュリティを強化し、ユーザーエクスペリエンスを最適化し、規制要件への準拠を確実にすることができます。

コメント