こんなことやりたい
Apex で SOQL を実行してレコードを取得するとき、実行ユーザが編集権限のあるレコードだけ返したい。
このとき、レコードに対し参照権限だけあると SOQL では取得できてしまうため適宜フィルターする必要がある。
先にまとめ
UserRecordAccess
オブジェクトというのがあるので、それを使いましょう。
UserRecordAccess | SOAP API 開発者ガイド | Salesforce Developers
UserRecordAccess
には Has*Access
( *
は Edit
、Delete
など) という項目があるので、チェックしたい権限に応じた項目を使います。
また API バージョン 30.0 以降であれば UserRecordAccess
は各 SObject のリレーション項目として取得できます。
例:SELECT Id, Name, Email, UserRecordAccess.HasEditAccess FROM Contact
方法
ほぼほぼこちらのテラスカイさんの記事にある通りです。感謝。
が、少々アップデートがあるみたいなので順を追って記載します。
ブログ記事にあるように、あるユーザが特定のレコードに対して参照・編集・削除などの権限を有しているかを判定するためのオブジェクトとして UserRecordAccess
というオブジェクトがあります。
UserRecordAccess | SOAP API 開発者ガイド | Salesforce Developers
上のオブジェクト定義を見るとわかりますが、 UserRecordAccess
には以下の項目があります。
- 各種の権限があるかどうかのフラグ項目
HasReadAccess
: 参照HasEditAccess
: 編集HasDeleteAccess
: 削除- ほか
UserId
RecordId
UserId
、RecordId
を WHERE 句に指定することで、「あるユーザが特定のレコードに対して権限を有しているか」を判断できるわけです。
なので、冒頭に記載したように「編集権限のあるレコードだけ返す」を実現するためには、こういったメソッドを用意すればいいでしょう。
public List<Contact> getEditableContacts() { Map<Id, Contact> contactMap = new Map<Id, Contact>([ SELECT Id, Name, Email FROM Contact ]); List<UserRecordAccess> accesses = [ SELECT RecordId, HasEditAccess FROM UserRecordAccess WHERE UserId = :UserInfo.getUserId() AND RecordId IN :contactMap.keySet() ]; List<Contact> result = new List<Contact>(); for (UserRecordAccess access : accesses) { if (access.HasEditAccess) { result.add(contactMap.get(access.RecordId)); } } return result; }
制限事項
というわけで便利なオブジェクトですが、制限事項がいくつかあります。
↑のテラスカイさんのブログ記事でも述べられていますが、個人的に気になったものを挙げると
RecordId
は単一または複数のレコードを WHERE 句に指定できるが、UserId
は単一のみUserId IN ***
とするとError: Can filter on only UserId = [single ID], either RecordId = [single ID] or RecordId IN [list of IDs], and Has*Access = true
というエラーが出る
- 一度に取得できるレコードは 200 件までで、SOQL の結果が 200 件を超えると QueryException が発生する
特に後者の制限が厳しいですね。。。
より良い方法
制限事項を回避できる何か良い方法はないのかと SOAP API 開発者ガイド を最後まで読むと、
API バージョン 30.0 以降では、UserRecordAccess はレコードの外部キーになります。このオブジェクトをルックアップまたは外部キーとして使用する場合は、UserId または RecordId 項目を検索条件に使用したり、指定したりすることはできません。前記のサンプルクエリは、次のように実行できます。
SELECT Id, Name, UserRecordAccess.HasReadAccess, UserRecordAccess.HasTransferAccess, UserRecordAccess.MaxAccessLevel FROM Account
お! UserRecordAccess
は API バージョン 30.0 以降だと任意の SObject のリレーション項目として一緒に取得できるようです。
なので、先ほどのサンプルは以下のように書き直すことができます。
/** Since API version 30.0 **/ public List<Contact> getEditableContacts() { List<Contact> contacts = [ SELECT Id, Name, Email, UserRecordAccess.HasEditAccess FROM Contact ]; List<Contact> result = new List<Contact>(); for (Contact contact : contacts) { System.debug('Contact [' + contact.Name + '] is editable?: ' + contact.UserRecordAccess.HasEditAccess); if (contact.UserRecordAccess.HasEditAccess) { result.add(contact); } } return result; }
これにより、制限事項のうち「一度に取得できるレコードは 200 件まで」は回避できました。よかった。
ただし、 UserRecordAccess.Has*Access
は WHERE 句に直接は指定できず、以下のエラーになります。
Error: You cannot filter on UserRecordAccess when used in a relationship
注意事項
Task, Event など一部の標準オブジェクトについては、UserRecordAccess を項目として取得する方法は使えないようです。
UserRecordAccess オブジェクトへのクエリが一度に 200 件までという制限があることなど、内部的にパフォーマンスに影響ありそうなことをやってそうな香りがしますね。
実際に使うときはパフォーマンス面での影響を少し意識しなければ。
サンプルコード
https://github.com/zaki-yama/salesforce-samples/tree/master/user-record-access