3

I know the differences between declaring a class inherited sharing, or leave it omitted, and between it and with sharing or without sharing, however I decided to test all the theory in the documentation in a real scenario.

-I have a trigger on account (API VERSION 45), it calls Account handler(API VERSION 45), which is with sharing.

-Account handler calls AccountUtil (API VERSION 48):

-AccountHandler relevant code:

public with sharing class AccountHandler implements ITrigger
{
    public void beforeInsert(list<SObject> newList)
    {
        AccountUtil.queryAccounts();
    }

-AccountUtil relevant code:

public inherited sharing class AccountUtil
{
    public static void queryAccounts()
    {
        //When called from trigger, no sharing specified = without sharing, and inherited sharing specified = without sharing too (WHY IS THIS IF IT IS CALLED FROM AccountHandler WHICH IS with sharing?)
        //When called as custom controller or controller extension for a vf page, no sharing specified = without sharing, and inherited sharing specified = with sharing

        //With sharing is respected, from a trigger, even if security is not enforced in anyway in any other place in the class
        for (Account acc : [SELECT Id, Name, SLASerialNumber__c FROM Account]) {
            System.debug('acc: ' + acc);
            if (String.isNotBlank(acc.SLASerialNumber__c)) {
                acc1 = acc;
                acc1Serial = acc.SLASerialNumber__c;
            }
        }

I have written down my conclusions as comments, however, my question is, why it runs as without sharing when it has inherited sharing (either specified or omitted) if it is called from AccountHandler, which is with sharing?

UPDATE:

I wanted to Test the possibility of AccountUtil being inheriting the sharing from the top level class, instead of doing it from the class that direcly called it.

To do that:

-I put AccountHandler (WITH SHARING) as a custom controller for a VF page, that calls Intermediary.cls

-I created Intermediary.cls class (WITHOUT SHARING, API VERSION 48) that calls AccountUtil

-AccountUtil, with inherited sharing, either splicitly declared or omitted, runs without sharing, following previous immediate previous class sharing. If Intermediary.cls is changed to be WITH SHARING, AccountUtil will runs under WITH SHARING too.

Basically:

-Class a with sharing is custom controller and call class b

-Class b is without sharing and call class c

-Class c has inherithed sharing, either declared explicitly or omitted, and runs as without sharing

-If b is changed to with sharing, then c runs as with sharing too

So class c does not actually follow class a top level class sharing, but b, where is it called from.

Update:

I just checked that, if a user is using a with sharing class and trying to update a record that he does not have record level access to, it will run in a insufficient access record, while this will not happen if the class is without sharing.

3
  • OK, so a few questions: 1. what API versions have you got on your various classes and your trigger? 2. Is AccountUtil inheriting sharing (you didn't include the class declaration)? Please edit your question to add these details, rather than commenting.
    – Phil W
    Commented Jun 25, 2020 at 17:30
  • I have provided that information, would you like to know anything else? Commented Jun 25, 2020 at 17:37
  • No, I think that covers all the bases. I'll leave my (partial) answer up and see if someone from Salesforce, or sfdcfox/David Read etc. have a better or confirmed answer.
    – Phil W
    Commented Jun 25, 2020 at 17:38

2 Answers 2

3
+50

I tried to do the same test and it worked for me. This is what I created:

// Trigger

trigger AccountTrigger on Account (before insert) {
    System.debug(AccountHandler.callAccountUtil());
}

// Account Handler

public with sharing class AccountHandler {
    public static List<Account> callAccountUtil() {
        return AccountUtil.queryAccounts();
    }
}

// Account Util

public inherited sharing class AccountUtil {
    public static List<Account> queryAccounts() {
        return [SELECT Name FROM Account];
    }
}

The test I did was: I set Account org wide defaults to private, then I created a user which has a Standard User profile, and I created one account with that user. I checked that only that account was visible for him. Then, I setup the debug logs with the sys admin and I created a second account with the standard user. The debug log correctly shows just the first created account (and not the others in the org, created by other users).

First, I did the test with all my classes on api 48.0. Then I changed the trigger and the AccountHandler class to API 45.0, and it worked the same.

So, maybe it's an issue with your with your test scenario setup? We can drop on a call and see what we have different, if you want.

6
  • Hi Alba, 1st of all, thank you for takin the time to review and help us with your answer. Before writing this command I executed again the same scenario I had at the time of writing this post, and now it works, you probably will not believe me, its been a week since I tried last time, but I am completly sure that then, explicit declared inherithed sharing was not working as expected, otherwise I would have bothered and take the time to write this post... anyway, it is good that now is works as expected. Commented Jul 3, 2020 at 11:09
  • However, 1 problem is still remaining, which is, if inherithed class is ommited instead of explicit, it is also working as without sharing, altough it should in fact run as with sharing if that is the declaration that has the class from where it is called. Commented Jul 3, 2020 at 11:14
  • If sharing is omitted the class works as without sharing "The sharing setting of the class where the method is defined is applied, not of the class where the method is called. " and "If the class is used as the entry point to an Apex transaction, an omitted sharing declaration runs as without sharing. "
    – ElkCor
    Commented Jul 4, 2020 at 2:49
  • @ElkCor, as you said, only if the class is the entry point of the apex transaction, it runs as without sharing, but in this case it is not the entry point, so you answer does not apply, neither the other one. If you read carefully the documentation, your text between quotes applies for with sharing and without sharing declarations, not for inherited sharing, either ommited or explicitly declared. Also, if that were true, it would not works as expected in a visualforce controller context, which it does. It only behaves unexpectedly in trigger context Commented Jul 4, 2020 at 16:16
  • Gabriel, I did the same text removing the inherited sharing keyword, and it works as expected for me too (with sharing). If you want to jump in a call I can show you the code and maybe check if there's something we are doing differently.
    – Alba Rivas
    Commented Jul 6, 2020 at 7:56
-1

Triggers run in "system mode", where user permissions and sharing are not applied.

By delegating to Apex "with sharing" you can get the current user's sharing to be applied.

I am guessing that, because triggers are a bit "special", there may be some different handling of undeclared (and perhaps explicitly declared) inherited sharing, even when invoked indirectly. I don't know if it is a bug or simply undocumented behaviour.

There is some possibly a little out-of-date detail in this other thread that may be of interest.

3
  • That is not true, you can try it by yourself. If you declare the class as with sharing, user will only retrieve those records which stricly has access too Commented Jun 25, 2020 at 17:27
  • Yes, sorry - I'll modify my answer. The trigger itself is without sharing, but sharing can be applied again by an explicit "with sharing".
    – Phil W
    Commented Jun 25, 2020 at 17:33
  • 2
    I checked that other thread before, unfortunately it does not really explain the issue. Maybe is, as you day, a bug, or simly undocumented, lets hope someone more expert in the matter can give us a full insight on this... Commented Jun 25, 2020 at 18:47

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .