9

I have a large Salesforce application with hundreds of SOQL queries. Currently, the code uses Schema.DescribeFieldResult data to determine if the logged in user has rights to each field in the SELECT clauses.

I am planning on changing the code to use the new Security.StripInnacessible method or the WITH SECURITY_ENFORCED flag.

We are trying to use something like a “selector” layer to organize queries within a domain together, for maximum reuse. This means that sometimes the same query is run by a user via a VF UI where FLS would be important, and sometimes it is run by a trigger where FLS would be an obstacle to updating aggregates and other non-user data (see: Appleman article on security review process and non-user data)

My question is what is the best way to maximize SOQL query reuse while appropriately opting in and out of FLS checks based on the trigger context?

Here is some example code that I hope can be improved upon:

public static sobject getAccountDetails(id filterId) {
    Object returnObject; 

    // really?
    if (trigger.IsExecuting)  {
        returnObject = [SELECT id, name, AccountDollars__c 
                        FROM Account WHERE id = :filterId
                        //WITH SECURITY_ENFORCED];
                        ];
    }
    else {
        returnObject = [SELECT id, name, AccountDollars__c 
                    FROM Account WHERE id = :filterId 
                    WITH SECURITY_ENFORCED];
    }

    return returnObject;
}    
1

1 Answer 1

7

I am partial to stripInaccessible because WITH SECURITY ENFORCED because the queries throw an exception if any of the fields are not available to the user. I would not make it automatic to trigger execution, I would modify the triggers to pass this parameter. That will allow you to bypass FLS checks in other code that may need FLS bypassed (like an engine call, where you'd need all the fields to process stuff or when the user wouldn't see the values at all, but they're needed for the backend)

A quick way to do it is this:

public List<MyObject__c> getRecordsById(Set<Id> recordIds, Boolean checkFLS) {
        List<MyObject__c> records = [
                SELECT Id,
                        Name,
                        <blah>
                FROM MyObject__c
                WHERE Id IN :recordIds
        ];

        if (checkFLS) {
            records = Security.stripInaccessible(AccessType.READABLE, records).getRecords();
        }

        return records;
    }

Your code could look like this

List<MyObject__c> records = MyObjectSelector.getRecordsById(setOfIds, true);

Note that in the code above, the if statement is where you'd put your Trigger.isExecuting if you still wanted to use it.

Of course, you can also create an abstract class with a "Check FLS" property (or in the constructor, maybe), and have your selector classes implement that class and check against the common property.

In that case your selector method would no longer be static and the if statement would change to this:

if (this.checkFLS) {
    records = Security.stripInaccessible(AccessType.READABLE, records).getRecords();
}

Your code could look like this if you implement the abstract class

List<MyObject__c> records = new MyObjectSelector(true).getRecordsById(setOfIds);

PS: stripInaccessible is not going to be GA until Spring 20 (Safe Harbor) so be careful of including it in code that will go into a managed package.

2
  • 2
    Yes, that's where I am headed. I also prefer Security.stripInaccessible because of the ability wrap business logic around it. In terms of the call signature, I am partial to the Selector method "owning" the knowledge of trigger.Isexecuting, mostly because it seems repetitive for many callers to be obligated to pass that in. But, the callers should be able to override, with an opt-in or opt-out parameter. Roughly: boolean checkFLS = (Trigger.IsExecuting || optIn && !optOut);
    – SeanW
    Commented Jan 10, 2020 at 19:03
  • Sure, the "if" statement would work too.... Commented Jan 10, 2020 at 19:10

You must log in to answer this question.

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