[Tips] Apexで特定のユーザが参照できるレコードだけを取得する方法

2023.04.17

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

Apexの処理は基本的にシステム権限で実行されます。そのため、非公開レコードなどにもアクセスして処理を行うことができます。
これはこれで便利なのですが、時に特定のユーザ権限で実行し、当該ユーザがアクセスできないレコードは除外して処理を行いたい場合もあります。 この記事では特定ユーザの権限でレコード取得を行う方法について記述します。

System.runAsメソッドはテストクラスでしか使えない

テストクラスであれば、System.runAsメソッドを用いて実行ユーザを簡単に切り替えることができます。

@isTest
private class TestRunAs {
   public static testMethod void testRunAs() {
        // ユーザを検索
        User user = [SELECT Id FROM User Name='Some User' LIMIT 1];

        System.runAs(user) {
              // このブロックの中ではユーザ(Some User)が実行ユーザになる
              System.debug('Current User: ' + UserInfo.getUserName());
              System.debug('Current Profile: ' + UserInfo.getProfileId());
          }
    }
}

System.runAsメソッドは便利なのですが、テストクラスでしか使えません。
テストクラス以外のクラスで実行ユーザを切り替えることは基本できません(2023/04現在)。

UserRecordAccessを使う

実行ユーザを切り替えることはできませんが、特定ユーザがアクセスできないレコードは除外して取得することはできます。 UserRecordAccessオブジェクトを利用します。

UserRecordAccessオブジェクトにはユーザのレコードに対するアクセス権が保持されていますので、特定ユーザが参照権限を持っているかチェックする処理を組み込むことができます。

// ユーザを検索
User user = [SELECT Id FROM User Name='Some User' LIMIT 1];

// 取得したいオブジェクト(ここではカスタムオブジェクトSomeObject__c)を検索する
List<SomeObject__c> someObjects = [
    SELECT
        Id
    FROM
        SomeObject__c
];
List<Id> someObjectIds = new FieldSlice(someObjects).retrieveIds('Id');

// ユーザが読み取りできるレコードのIdのみを抽出
List<UserRecordAccess> userRecordAccesses = [
    SELECT
        RecordId
    FROM
        UserRecordAccess
    WHERE
        UserId = :user.Id
        AND
        RecordId IN :someObjectIds
        AND
        HasReadAccess = True
];
List<Id> readableSomeObjectIds = new FieldSlice(userRecordAccesses).retrieveIds('RecordId');

if ( readableSomeObjectIds.size() > 0 ) {
    // 読み取り可能なレコードに対して処理を行う
    // :
}

SOQLのクエリ結果からIdリストの抽出に自作クラスのFieldSliceを使っています。実装の詳細はリンク先の記事を参照してください。

上記の例において、SOQLを二つに分けて実行しています。しかし、下記のようにすれば一つのSOQLで処理できるのでは?と思われた方もいるでしょう。

// ユーザが読み取りできるレコードのIdのみを抽出
List<UserRecordAccess> userRecordAccesses = [
    SELECT
        RecordId
    FROM
        UserRecordAccess
    WHERE
        UserId = :user.Id
        AND
        RecordId IN (
            SELECT
                Id
            FROM
                SomeObject__c
        )
        AND
        HasReadAccess = True
];
List<Id> readableSomeObjectIds = new FieldSlice(userRecordAccesses).retrieveIds('RecordId');

残念ながらバグがあり、この方法を使うとエラーになります。
面倒ですが、最初に権限チェックする対象のレコードIdをList型で取得しておき、それをUserRecordAccessに対するクエリのIN句に指定する必要があります。

参考)
IN clause of RecordId field in UserRecordAccess object is not working

注意点

UserRecordAccessには次の2点、注意点があります。

  1. UserRecordAccess は、ユーザのアクセスが制限ルールによってブロックされているかどうかを考慮しません。
  2. 一回のUserRecordAccessへのクエリで照会できるレコード数は200個に制限されています。したがって大量レコードに対しての利用には適しません。

まとめ

いくつかの注意点はあるものの、実行ユーザを切り替えられなくてもUserRecordAccessを活用することにより、特定ユーザの権限でレコード取得を行う方法を示しました。セキュリティを考慮したApexコードの開発などに役立てていただければと思います。