継承や内部クラス時のsharingキーワードの動作を知っていますか?

またこのネタかとお思いの方も多いとは思いますが、大事なことなので改めて言わせてクダサイ!


with sharingとwithout sharingはApex開発を行っている方々なら、常に念頭において開発されていることと思います。多くのBlogなどの記事でも紹介されておりますが、Salesforceの開発においてセキュリティに関する大変重要なキーワードです。

改めて簡単にその特徴を述べますと

with sharing
実行ユーザ権限でレコードを取得する。
without sharing
システム管理者権限でレコードを取得する。

となります。通常は実行ユーザが不要なレコードにアクセスしないためにもwith sharingを宣言して開発していることと思います。

また、with/without sharingを宣言しない場合は、そのクラスを呼び出したクラスの権限に依存します。
呼出し元クラスが存在しない場合(Visualforceのコントローラに指定されているクラスなど)、without sharingモードで動作するのはもっとも注意が必要なところですね。


継承したクラス

Apexクラスでももちろんをクラスを継承して実装することができます。その場合のwith/without sharingの挙動について改めて確認してみましょう。
例えば以下のサンプルコードでは、Visualforceページに2つのPageBlockTableを配置して、それぞれAccountレコードのリストを表示しています。
上のテーブルは、Superクラスのメソッドを直接呼出しており、下のテーブルはSubクラス経由でSuperクラスのメソッドを呼び出しています。
取引先(Account)オブジェクトの共有設定は非公開になっています。
この場合に、各テーブルに表示されるレコードはともにwith sharingで取得された結果になっているでしょうか?

■Visualforceページ

<apex:page controller="AccountListController" sidebar="false">
    <apex:pageBlock title="1.Superクラスのメソッドを呼出し">
        <apex:pageBlockTable value="{!AllAccounts}" var="acc">
            <apex:column value="{!acc.Name}"/>
            <apex:column value="{!acc.Phone}"/>
            <apex:column value="{!acc.WebSite}"/>
            <apex:column value="{!acc.Type}"/>
            <apex:column value="{!acc.OwnerId}"/>
        </apex:pageBlockTable>
	</apex:pageBlock>
    <apex:pageBlock title="2.Subクラス経由で呼出し">
        <apex:pageBlockTable value="{!AllAccountsByPass}" var="acc">
            <apex:column value="{!acc.Name}"/>
            <apex:column value="{!acc.Phone}"/>
            <apex:column value="{!acc.WebSite}"/>
            <apex:column value="{!acc.Type}"/>
            <apex:column value="{!acc.OwnerId}"/>
        </apex:pageBlockTable>
	</apex:pageBlock>
</apex:page>

■Superクラス(with sharingなし)

public virtual class AccountListSuperController {
    public List<Account> getAllAccounts(){
        return [SELECT Id,Name,Phone,website,Type,OwnerId 
                FROM Account ORDER BY NAME];
    }
}

■Controllerクラス(Subクラス with sharingを宣言)

public with sharing class AccountListController extends AccountListSuperController{
    public List<Account> getAllAccountsByPass(){
        AccountListSuperController sc = new AccountListSuperController();
        return sc.getAllAccounts();
    }
}

正解は・・・

上のテーブルはwithout sharing、下のテーブルはwith sharingで動作します。

上のテーブルでは、クエリを記述しているSuperクラスにはwith sharingが記載されていないため、Visualforceから直接呼び出されており、全レコードが参照できています(without sharing)。
一方下のテーブルのようにAccountListControllerクラス内で、AccountListSuperControllerクラスをNew()してからメソッドにアクセスした場合は、with sharingで動くことになります。


内部クラス

また、Apexクラス実装時に内部クラスを定義しその中でクエリを発行するケースもありますね。
下記のサンプルコードで作成されたVisualforceページはどのように動作するでしょうか?

■Visualforceページ

<apex:page controller="AccountListController2" sidebar="false">
    <apex:pageBlock title="3.Innerクラスのメソッドを呼出し">
        <apex:pageBlockTable value="{!innerCls.AllAccounts}" var="acc">
            <apex:column value="{!acc.Name}"/>
            <apex:column value="{!acc.Phone}"/>
            <apex:column value="{!acc.WebSite}"/>
            <apex:column value="{!acc.Type}"/>
            <apex:column value="{!acc.OwnerId}"/>
        </apex:pageBlockTable>
    </apex:pageBlock>
</apex:page>

■Controllerクラス(内部クラスあり)

public with sharing class AccountListController2{

    public innerClass innerCls{get;set;}
    public AccountListController2(){
        innerCls = new innerClass();
    }
 
    /**  内部クラス with sharingなし  */
    public class innerClass{
        public List<Account> getAllAccounts(){
            return [SELECT Id,Name,Phone,website,Type,OwnerId 
                    FROM Account ORDER BY NAME];
        }
    }
}

正解は・・・

これもwithout sharingで動作します。


まとめ

今回検証した内容は、Force.com Apexコード開発者ガイドにも記載がある内容ですが、中にはご自分の認識と違う挙動だった方もいらっしゃるのではないでしょうか?(自戒)

<Summer'15 日本語バージョンから抜粋>

with sharing または without sharing キーワードでは、次の点に留意してください。

  • メソッドがコールされるクラスの共有設定ではなく、メソッドが定義されているクラスの共有設定が適用されます。たとえば、with sharing が宣言されたクラス内に定義されているメソッドが、withoutsharing が宣言されたクラスでコールされる場合、そのメソッドの実行では共有ルールが適用されます。
  • with sharing も without sharing もクラスで宣言されていない場合、現在の共有ルールが有効となります。つまり、クラスが別のクラスから共有ルールを取得する場合を除き、共有ルールは強制実行されません。たとえば、共有が強制実行されている別のクラスからクラスがコールされた場合、コールされたクラスにも共有が強制実行されます。
  • 内部クラスと外部クラスは、どちらも with sharing として宣言できます。共有設定は、初期化コード、コンストラクタ、メソッドなどクラスに含まれているすべてのコードに適用されます。
  • 内部クラスはそのコンテナクラスから共有設定を継承しません。
  • クラスが別のクラスを拡張または実装している場合、親クラスからこの設定が継承されます。

特に最後の2点は見落としがちなところですが、一歩間違えると致命傷になりかねないので要注意ですね。