4

We're working on a managed package and planning to list it on AppExchange. We are using the fflib-apex-common library in our app.

Now the question: what's the best way to configure Read and FLS when using selector: via fflib_SObjectSelector or fflib_QueryFactory?

By default the library enforces Read check on object only. We also want to check read access on fields as well. And there are basically 2 ways to do that with fflib:

1st - use fflib_QueryFactory:

// newQueryFactory(Boolean assertCRUD, Boolean enforceFLS, Boolean includeSelectorFields)
fflib_QueryFactory query = newQueryFactory(true, true, true);

2nd - use overloaded constructors from fflib_SObjectSelector:

public with sharing class MyObjectsSelector extends fflib_SObjectSelector
{
    public MyObjectsSelector(Boolean includeFieldSetFields, Boolean enforceCRUD, Boolean enforceFLS)
    {
        super(includeFieldSetFields, enforceCRUD, enforceFLS);
    }
}

I personally like the 2nd option as it provides flexibility by allowing the caller to decide whether to enforce security check on not. Using fflib_QueryFactory on the other hand hardcodes this behavior making it impossible to use the selector method in different contexts.

One more thing, how can I configure security check with the Application class?

public class Application
{    
      public static final fflib_Application.SelectorFactory selector =
       new fflib_Application.SelectorFactory(
          new Map<SObjectType, Type> {
             MyObject__c.sObjectType => MyObjectsSelector.class
          }
       );
}

Then in the selector class:

public with sharing class MyObjectsSelector extends fflib_SObjectSelector
{
    public static IAccountsSelector newInstance(){
        return (IMyObjectsSelector) Application.selector.newInstance(MyObject__c.SObjectType);
    }
}

And the a caller would use it as following:

List<MyObject__c> records = MyObjectsSelector.newInstance().selectById(recIds);

This would not allow the caller to configure security checks. So how is this usually handled?

  • 1
    You should always call a selector class and the methods within that class ... eg selectXxxByYyy(...) then exploit the queryfactory class to construct queries , especially composite queries. The selector class is referenced in Application.cls and can be mocked in test methods. Andrew Fawcett's book goes over all of this and is well worth studying – cropredy Jun 18 '19 at 13:37
  • @cropredy, yeah, you're right, this book is awesome. Yet event in it there are some client classed that use selector like MyObjectsSelector.newInstance().selectById(recIds);, and others that skip the app class, ie. new MyObjectsSelector().selectById(recIds);. This is frustrating and ambiguous. And my question is actually about how would I enforce security checks. Without the app class it's quite simple - new MyObjectsSelector(true, true, true).selectByd(recIds);, but I don't know how to enforce security here MyObjectsSelector.newInstance().selectById(recIds);. – Eduard Jun 18 '19 at 20:28
  • 1
    I always use XXXSelector.newInstance().selectByXXX because you get access to the factory and that means mocks can be injected and recognized during testmethods. You don't want to lose this benefit. I think for dynamic security enforcement; you either set it for all methods of the selector class (I do this for selectors on, say Log__c or Async_Request__c) or you make it an argument for a given selector method, setting the instance variables accordingly. – cropredy Jun 18 '19 at 23:26
  • I got it, thanks! Yeah, every decision is a trade-off between flexibility and convenience. Passing security enforcement as a method argument would work in my case. – Eduard Jun 19 '19 at 7:19
  • @cropredy, I just noticed in the Overriding the default behavior section from Chapter 7 of the Andrew Fawcett's book it is mentioned that Another motivation for disabling the selector default behavior would be a preference to code these checks in your controller logic more explicitly. Now I am wondering shouldn't we always perform security checks in controllers/batch classes/web services etc.? – Eduard Jun 19 '19 at 21:49
5

You should always call a selector class and the methods within that class ... eg selectXxxByYyy(...) then exploit the queryfactory class to construct queries , especially composite queries. The selector class is referenced in Application.cls and can be mocked in test methods. Andrew Fawcett's book goes over all of this and is well worth studying.

Let's say you have a Selector class on Account and invoke it by:

AccountsSelector.newInstance().selectByXXX(args)

This invokes a factory method

public static IAccountsSelector newInstance()    {
    return (IAccountsSelector) Application.Selector.newInstance(Account.SObjectType);
}

This in turn executes this method in fflib_Application

public fflib_ISObjectSelector newInstance(SObjectType sObjectType)
    {
        // Mock implementation?
        if(m_sObjectByMockSelector.containsKey(sObjectType))
            return m_sObjectByMockSelector.get(sObjectType);

        // Determine Apex class for Selector class          
        Type selectorClass = m_sObjectBySelectorType.get(sObjectType);
        if(selectorClass==null)
            throw new DeveloperException('Selector class not found for SObjectType ' + sObjectType);

        // Construct Selector class and query by Id for the records
        return (fflib_ISObjectSelector) selectorClass.newInstance();            
    }

The last line is the relevant one here:

return (fflib_ISObjectSelector) selectorClass.newInstance();

which returns an object of your Selector class (e.g. AccountsSelector.cls). Selector classes inherit from fflib_SObjectSelector and the no arg constructor of this super class defaults these fields that control CRUD and FLS:

m_includeFieldSetFields = false
m_enforceCRUD = true
m_enforceFLS = false
m_sortSelectFelds = true

Now, you want to preserve the use of the factory because it allows you to set up mock Selectors and have them be dependency injected in unit tests.

But fflib_Application has no provision for invoking the other constructors in fflib_Selector that allow for dynamic setting of enforceCRUD and/or enforceFLS

That is, you can't do this:

Boolean enforceCRUD = false;
AccountsSelector.newInstance(enforceCRUD).selectByXXX(args);

so even if you added another factory invocation method to AccountsSelector that looked like this:

public static IAccountsSelector newInstance(Boolean enforceCRUD)    {
    return (IAccountsSelector) Application.Selector.newInstance(Account.SObjectType,enforceCRUD);
}

this wouldn't work because fflib_Application.Selector.newInstance() only supports a single argument sObjectType.

So, what to do?

Option 1: Some selectors should probably never enforce CRUD or FLS; particularly selectors on 'system-y' objects. For example, in my org, I have an Async_Request__c Sobject.

When fflib_Application.Selector executes its last line:

return (fflib_ISObjectSelector) selectorClass.newInstance();

The no arg constructor in AsyncRequestsSelector looks like this:

static final Boolean INCLUDE_FIELDSETS  = false;    // false is default, if true, class must include getSobjectFieldSetList
static final Boolean ENFORCE_CRUD       = false;    // true is default; 
static final Boolean ENFORCE_FLS        = false;    // this is default
static final Boolean SORT_SELECT_FIELDS = true;     // this is default

public AsyncRequestsSelector() {
    super(INCLUDE_FIELDSETS,ENFORCE_CRUD,ENFORCE_FLS,SORT_SELECT_FIELDS);
}

Thus, every call to AsyncRequestsSelector.newInstance().selectByXXX(args), regardless of how many methods in AsyncRequestsSelector will not enforce CRUD

Option 2: Individualize each selector method with fine-grained control over whether you want to enforceCRUD or FLS

Consider a method in AccountsSelector called selectByWebsite where you want FLS enforced

public Account[] selectByWebsite(Set<String> websites) {
  Boolean enforceCRUD = true;
  Boolean enforceFLS = true;
  Boolean includeSelectorFields = true; 
  fflib_QueryFactory acctQF = newQueryFactory(enforceCRUD,enforceFLS,includeSelectorFields)
    .setCondition('Website IN : websites');
  return Database.query(acctQF.toSOQL());
}

So, here, you are overriding the super class defaults for enforcing CRUD and FLS by constructing a queryFactory with selector-specific needs.

Option 2.1 - If you prefer the fluent style for queryFactories, you can accomplish the same in Option 2 with:

public Account[] selectByWebsite(Set<String> websites) {
  Boolean enforceCRUD = true;
  Boolean enforceFLS = true;
  Boolean includeSelectorFields = true; 
  fflib_QueryFactory acctQF = newQueryFactory(includeSelectorFields)
    .assertIsAccessible(enforceCRUD)
    .setEnforceFLS(enforceFLS)
    .setCondition('Website IN : websites');
  return Database.query(acctQF.toSOQL());
}   

Option 2.2 - If you need to have the selector method sometimes enforce CRUD or FLS, overload the method:

public Account[] selectByWebsite(Set<String> websites) {      
  return selectByWebsite(websites,super.isEnforcingCRUD,super.isEnforcingFLS);
}

public Account[] selectByWebSite(Set<String> websites, Boolean enforceCrud, Boolean enforceFLS) {
   Boolean includeSelectorFields = true;  
   fflib_QueryFactory acctQF = newQueryFactory(includeSelectorFields)
    .assertIsAccessible(enforceCRUD)
    .setEnforceFLS(enforceFLS)
    .setCondition('Website IN : websites');
  return Database.query(acctQF.toSOQL());
}
| improve this answer | |
  • 1
    Wow, that's a really nice breakdown of use of the selectors regarding security checks. I wish it was also included in the book :) Thanks for your time, @cropredy! – Eduard Jun 25 '19 at 7:27
  • One small question. What about cases when you need to use the same selector method in both system and user contexts. For instance, selectById(Set<Id> recordIds), if this method was used in both these scenarios, how would you address this problem? – Eduard Jun 25 '19 at 8:41
  • 1
    @eduard - updated answer with option 2.2 – cropredy Jun 25 '19 at 14:36
2

The fflib selector design pattern and its query factory has been an old-fashion and cumbersome design. Say if you wrote a function selectById with only a list of Id as the parameter, and has been put into use. But then some new features need to be added, e.g. orders, limits, or even more conditions. Now you have two options, write a completely new function in the selector, or refactor your existing function with more parameters. Either option looks sustainable. Moreover, fflib query factory only allows us to build the WHERE string manually.

An alternative of the fflib selector structure, is using the Query.apex, and get rid of the whole select layer. Starting with replacing the traditional selectById, you can do

List<Account> accounts = new Query('Account')
    .selectAllFields()
    .byId(yourIdList)
    .run();

If you want, you can reuse the Query instance to add more sorting, limits, condition, etc.

Query q = new Query('Account')
    .selectAllFields()
    .byId(yourIdList);

List<Account> accounts = q.run();

List<Account> anotherAccounts = q
    .addConditionLike('Name', 'Another%')
    .setLimit(10)
    .orderBy('CreatedDate', 'DESC')
    .run();

Enforce security check (throw exceptions when read permission is missing):

List<Account> accounts = new Query('Account').enforceSecurity().run();

You can even do aggregate functions with Query.apex. You may go to the repository to find out more.

| improve this answer | |
  • 1
    this is interesting - it would be nice if your github included how to mock the Query class since mocking the fflib Selector layer via apexmocks is especially useful in unit tests – cropredy Jan 2 at 16:58
  • @cropredy Thank you for the advice. I'll see how it can be done. – HenryLee Jan 3 at 22:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.