Salesforce項目レベルセキュリティをマスターする:Apex開発者向け徹底ガイド

背景と応用シナリオ

Salesforceプラットフォームにおけるセキュリティは、顧客データの信頼性と完全性を維持するための最重要基盤です。その多層的なセキュリティモデルの中でも、Field-Level Security (FLS、項目レベルセキュリティ) は、個々の項目へのアクセスを制御する上で極めて重要な役割を果たします。FLSは、ユーザープロファイルや権限セットに基づいて、特定の項目に対する参照(Read)アクセスと編集(Edit)アクセスをきめ細かく設定する機能です。

例えば、人事部門のユーザーには従業員の給与項目を閲覧・編集する権限を与え、一般の従業員にはその項目を非表示にする、といったシナリオが考えられます。また、営業担当者には商談の「金額」項目を編集させ、営業アシスタントには閲覧のみを許可する、といった制御もFLSによって実現されます。これにより、組織は最小権限の原則を徹底し、データ漏洩のリスクを最小限に抑え、GDPRやCCPAといったデータプライバシー規制へのコンプライアンスを強化することができます。

私たち Salesforce Developer (Salesforce 開発者) にとって、このFLSを理解することは単なるベストプラクティスに留まりません。Apex、Visualforce、Lightning Web Components (LWC) を用いてカスタム機能を開発する際、FLSをコードレベルで適切に考慮・強制することが不可欠です。なぜなら、Apexはデフォルトで「システムモード」で実行され、FLSの設定をバイパスしてしまうからです。つまり、開発者が明示的にセキュリティチェックを実装しない限り、UI上では非表示になっているはずの項目にApexコード経由でアクセスし、データを読み書きできてしまうのです。これは深刻なセキュリティホールとなり得ます。

この記事では、Salesforce開発者の視点から、Apexコード内でFLSを確実に強制するための原理、具体的な実装方法、そしてベストプラクティスについて詳細に解説していきます。


原理説明

ApexにおけるFLSの強制は、主に2つのアプローチに大別されます。1つはデータ取得時(SOQLクエリ)、もう1つはデータ操作時(DML操作)です。

1. データ取得時:SOQLにおけるセキュリティ強制

従来、SOQLクエリを実行する前に、開発者はクエリ対象の各項目に対してアクセス権があるかどうかをスキーマ情報を使って手動でチェックする必要がありました。これは煩雑で、コードの可読性を損なう原因ともなっていました。

この課題を解決するために、Salesforceは WITH SECURITY_ENFORCED 句をSOQLクエリに導入しました。この句をクエリに追加するだけで、Salesforceプラットフォームが実行ユーザーのFLSとオブジェクト権限を自動的にチェックしてくれます。もしユーザーがクエリに含まれるいずれかの項目やオブジェクトに対するアクセス権を持っていない場合、システムはクエリの実行を中止し、System.QueryException をスローします。これにより、開発者は例外処理を実装するだけで、安全にデータ取得を行うことができるようになりました。これは現在、SOQLにおけるFLSチェックの最も推奨される方法です。

2. データ操作時:DML操作前のアクセス権チェック

レコードの作成(insert)や更新(update)といった DML (Data Manipulation Language) 操作を行う場合、WITH SECURITY_ENFORCED のようなシンプルな構文は存在しません。そのため、開発者は操作対象の項目に対して、ユーザーが作成可能(isCreateable)、更新可能(isUpdateable)、またはアクセス可能(isAccessible)であるかを、DML文を実行する前に手動でチェックする必要があります。

このチェックには、Schema クラスのメソッド群が利用されます。具体的には、SObjectType トークンを通じてオブジェクトの記述情報 (Describe Result) を取得し、そこからさらに各項目の記述情報にアクセスします。各項目の記述結果 (DescribeFieldResult) オブジェクトには、isAccessible(), isCreateable(), isUpdateable() といった、現在のユーザーコンテキストでのアクセス権を示すboolean型のメソッドが用意されています。これらのメソッドを呼び出し、返り値が true であることを確認した上でDML操作を実行するのが、安全なコーディングの基本となります。

これらの原理を理解し、適切な場面で正しいアプローチを選択することが、堅牢でセキュアなSalesforceアプリケーションを構築する鍵となります。


示例代码

ここでは、Salesforceの公式ドキュメントに基づいたコード例を用いて、具体的なFLSの強制方法を解説します。

1. SOQLクエリにおける `WITH SECURITY_ENFORCED` の使用

最もシンプルかつ強力な方法です。以下のコードは、取引先責任者 (Contact) のID、FirstName、LastName、Phone項目を、実行ユーザーのFLSを尊重して取得します。もしユーザーがこれらの項目の一つでもアクセス権を持っていない場合、コードは try-catch ブロック内の catch 節に移行します。

// FLSを強制して取引先責任者を取得する
try {
    // WITH SECURITY_ENFORCED句を追加することで、項目レベルとオブジェクトレベルのセキュリティが自動的に適用される
    List<Contact> contacts = [
        SELECT Id, FirstName, LastName, Phone
        FROM Contact
        WITH SECURITY_ENFORCED
    ];

    // 取得したデータに対する処理
    for (Contact c : contacts) {
        System.debug('Contact Name: ' + c.FirstName + ' ' + c.LastName);
    }
} catch (System.QueryException e) {
    // ユーザーが必要な権限を持っていない場合にQueryExceptionがスローされる
    System.debug('FLSまたはオブジェクト権限の違反が検出されました: ' + e.getMessage());
    // ここでユーザーフレンドリーなエラーメッセージを表示するなどのエラーハンドリングを行う
}

詳細な注釈:

  • Line 4: WITH SECURITY_ENFORCED がこのコードの核心です。この句があることで、SOQLエンジンは実行前にユーザーの権限を検証します。
  • Line 1: try-catch ブロックでクエリを囲むことは必須です。権限違反は例外として処理されるため、この構造がないとコードが予期せず停止してしまいます。
  • Line 12: catch (System.QueryException e) で、権限違反によって発生した例外を捕捉します。例外メッセージには、どの項目またはオブジェクトでアクセスが拒否されたかの詳細が含まれているため、デバッグに役立ちます。

2. DML操作前のSchemaメソッドによる手動チェック

動的に項目を扱う場合や、DML操作を行う前には、Schemaメソッドを使用した明示的なチェックが必要です。以下の例では、取引先 (Account) レコードを更新する前に、更新対象の各項目に対する更新権限をチェックしています。

// 更新対象の取引先IDと更新したい項目のMap
Id accountId = '001xx000003DGb2AAG'; // 実際のIDに置き換えてください
Map<String, Object> fieldsToUpdate = new Map<String, Object>{
    'Name' => 'Updated Account Name',
    'Phone' => '555-555-1234',
    'AnnualRevenue' => 5000000
};

// オブジェクトタイプを取得
SObjectType accountType = Schema.getGlobalDescribe().get('Account');
// オブジェクトの項目Mapを取得
Map<String, Schema.SObjectField> fieldMap = accountType.getDescribe().fields.getMap();

Account accToUpdate = new Account(Id = accountId);
Boolean hasUpdateAccess = true;

// 更新したい各項目についてループし、更新権限をチェック
for (String fieldName : fieldsToUpdate.keySet()) {
    // 項目名が有効で、かつユーザーが更新権限を持っているか確認
    if (fieldMap.containsKey(fieldName.toLowerCase()) && fieldMap.get(fieldName.toLowerCase()).getDescribe().isUpdateable()) {
        // 権限があれば、更新用SObjectに値をセット
        accToUpdate.put(fieldName, fieldsToUpdate.get(fieldName));
    } else {
        // 権限がない項目が見つかった場合
        System.debug('ユーザーには項目 ' + fieldName + ' の更新権限がありません。');
        hasUpdateAccess = false;
        // ユースケースによってはここで処理を中断(break)しても良い
    }
}

// 権限のある項目のみで構成されたSObjectを更新
// 少なくとも1つの項目に更新権限があった場合にのみDMLを実行
if (accToUpdate.getPopulatedFieldsAsMap().size() > 1) { // Id以外に項目がセットされているか
    try {
        update accToUpdate;
        System.debug('取引先の更新に成功しました。');
    } catch (DmlException e) {
        System.debug('DMLエラーが発生しました: ' + e.getMessage());
    }
} else {
    System.debug('更新可能な項目がなかったため、DMLは実行されませんでした。');
}

詳細な注釈:

  • Line 12: accountType.getDescribe().fields.getMap() で、Accountオブジェクトの全項目情報をMap形式で効率的に取得します。
  • Line 19: isUpdateable() メソッドがFLSチェックの核心です。このメソッドは、現在のユーザーがこの項目を更新できる場合に true を返します。同様に、参照権限は isAccessible()、作成権限は isCreateable() でチェックします。
  • Line 21: accToUpdate.put(fieldName, value) を使用して、動的にSObjectに値を設定しています。これにより、権限のある項目だけが更新対象となります。
  • Line 31: getPopulatedFieldsAsMap().size() > 1 は、Id以外に更新対象の項目が1つ以上存在することを確認するための安全策です。これにより、空のDML操作を防ぎます。


注意事項

FLSをApexで実装する際には、以下の点に注意が必要です。

  • システムモードの挙動: Apexはデフォルトでシステムモードで動作し、FLSを無視します。開発者が明示的にチェックを実装する責任があります。WITH USER_MODEWITH SYSTEM_MODE といったキーワードも将来的に導入される可能性がありますが、本記事執筆時点では WITH SECURITY_ENFORCED がクエリにおける標準的なベストプラクティスです。
  • 例外処理: WITH SECURITY_ENFORCED は権限違反時に例外をスローします。必ず try-catch ブロックで囲み、ユーザーに適切なフィードバックを返すなどのエラーハンドリングを実装してください。さもなければ、予期せぬエラーでプロセスが停止してしまいます。
  • パフォーマンスへの影響: describe 呼び出し(特にループ内での多用)は、パフォーマンスに影響を与える可能性があります。可能な限り、ループの前に一度だけオブジェクトの項目情報をまとめて取得し、Mapに格納しておく(前述のコード例のような)方法が推奨されます。
  • オブジェクトレベルセキュリティ (Object-Level Security): FLSは項目レベルのアクセス権を制御しますが、オブジェクトそのものへのアクセス権(参照、作成、編集、削除)も重要です。WITH SECURITY_ENFORCED はオブジェクト権限もチェックしますが、DML操作前の手動チェックでは、sObjectType.getDescribe().isAccessible()isUpdateable() などでオブジェクト自体の権限も確認することがより堅牢な設計につながります。
  • 管理パッケージとセキュリティレビュー: AppExchangeでアプリケーションを公開する場合、Salesforceのセキュリティレビューに合格する必要があります。このレビューでは、FLSやCRUD(Create, Read, Update, Delete)権限がコード内で適切に強制されているかが厳しくチェックされます。これらのベストプラクティスに従うことは、レビュー合格の必須条件です。

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

Field-Level Security (FLS) は、Salesforceのデータセキュリティを支える重要な柱です。私たちSalesforce開発者は、その設定を尊重し、カスタムコード内で確実に強制する責任を負っています。

以下に、本記事で解説した内容に基づくベストプラクティスをまとめます。

  1. SOQLでは WITH SECURITY_ENFORCED を第一選択とする:

    データ取得時には、可能な限り WITH SECURITY_ENFORCED 句を使用してください。コードが簡潔になり、保守性が向上し、Salesforceプラットフォームによる確実なセキュリティチェックの恩恵を受けられます。

  2. DML操作前には必ずSchemaメソッドで権限をチェックする:

    insert, update, upsert, delete を実行する前には、必ず対象オブジェクトと各項目に対する isCreateable(), isUpdateable(), isDeletable(), isAccessible() といったメソッドで権限を確認してください。これにより、意図しないデータの作成や変更を防ぎます。

  3. セキュリティチェックを共通化・ユーティリティ化する:

    大規模なプロジェクトでは、FLSやオブジェクト権限をチェックするロジックが様々な場所で必要になります。これらのロジックを再利用可能なユーティリティクラス(例: SecurityUtil.cls)にまとめることで、コードの重複を避け、一貫性のあるセキュリティ実装を促進できます。

  4. 多層防御を意識する:

    FLSはセキュリティモデルの一部に過ぎません。オブジェクト権限、共有ルールやロール階層といったレコードレベルのセキュリティ、そしてApexコード内でのFLS強制を組み合わせることで、初めて堅牢なセキュリティが実現します。常に全体像を意識した設計を心がけてください。

これらの原則を日々の開発業務に取り入れることで、私たちはより安全で、信頼性の高い、プロフェッショナルなSalesforceアプリケーションを構築することができるのです。

コメント