About Us


Concretio was founded in 2013 by Abhinav Gupta (8 times Salesforce MVP), with a motive to build a team of passionate individuals, who want to develop high quality solutions, and enjoy challenges posed by rapidly changing technology.


Working Hours

Monday - Friday 9 AM - 10 PM IST

Top
Salesforce Managed Package - Protected Custom Settings Issue with Security.stripInaccessible()

Security.stripInaccessible() Bug with Protected Custom Settings

This post is my attempt to reach out to Salesforce with a full narration of a bug in Security.stripInaccessible() method with protected custom settings only. Also this could help any ISV Partners, who are using Security.stripInaccessible() with protected custom settings and running into issues.

Here is quick summary of the issue:

  • Create a Protected Custom setting either of Hierarchy or List Type
  • As part of Security Review Compliance, one will either check for CRUD/FLS via Apex Describe Calls or can use the newly released Security.stripInaccessible() Apex utility.
  • Add a class lets say Foo.cls, that uses Security.stripInaccessible() to verify Custom Setting access in any Aura, LWC, Batch, or other Apex code.
  • Do a managed release or beta package upload i.e. with a package prefix
  • Install the managed package in a client org.
  • In the client org, invoke Foo.cls and you will get a FALSE Exception from Security.stripInaccessible() despite having READ/WRITE access on the protected custom settings.

Please Note: This issue is not reproducable in the packaging org, it only comes in client/target org after installing the package.

Following video explains the situation

How to reproduce this error?

Install Managed Package

All the source code demonstrated in above vide can be installed via this package link: https://login.salesforce.com/packaging/installPackage.apexp?p0=04t2x000003D7Rb&isdtp=p1 Or deploy this video’s source from Github to a new Developer Edition org, and upload a managed package as released/beta after registering a prefix. You can check out this repo, and use normal SFDX development tooling/flows to deploy this code to your own org.

Setup a Target Org

  • Assign the Accessor permission set to get the required permissions on Classes and Custom Settings.
  • Create a class with the following source code, in case you deployed the Github source to your own org, replace the “striptest” package prefix in the following code, with your own namespace prefix.
public class AccessorPlayGround {
    public static void populate(){
        //populate sample data 
        striptest.Accessor.populateAll();
    }
    
    public static void assertAccess() {
        // Generate classic CRUD FLS report
        String accessReport = striptest.Accessor.generateClassicAccessReport();
        System.debug (accessReport);
		
        try {
	        striptest.Accessor.accessProtectedHierarchy(AccessType.READABLE);            
            System.debug (' ProtectedHierarchy is READABLE'); 
        } catch (Exception ex) {
            System.debug (' ProtectedHierarchy ' + ex.getMessage() + '\n' +  ex.getStackTraceString());
        }        

        try {
	        striptest.Accessor.accessProtectedList(AccessType.READABLE);            
            System.debug (' ProtectedList is READABLE'); 
        } catch (Exception ex) {
            System.debug (' ProtectedList ' + ex.getMessage() + '\n' +  ex.getStackTraceString());
        }        

        try {
	        striptest.Accessor.accessPublicHierarchy(AccessType.READABLE);            
            System.debug (' PublicHierarchy is READABLE'); 
        } catch (Exception ex) {
            System.debug (' PublicHierarchy ' + ex.getMessage() + '\n' +  ex.getStackTraceString());
        }        

        try {
	        striptest.Accessor.accessPublicList(AccessType.READABLE);            
            System.debug (' PublicList is READABLE'); 
        } catch (Exception ex) {
            System.debug (' PublicList ' + ex.getMessage() + '\n' +  ex.getStackTraceString());
        }        
        
    }
}

Execute following code to start observing the debug logs for exception traces regarding Protected Custom Settings

AccessorPlayGround.assertAccess();

If you have configured permission sets correct, the user_debug output in debug logs for the above code line should have something to the following. Observe the bug in Security.stripInaccessible() around Protected Custom Settings only. This is a bug because the CRUD/FLS permissions are given for UPDATE to all 4 custom settings, only the public custom settings work correctly.

00:46:05.2 (147600827)|USER_DEBUG|[10]|DEBUG|
Access Report for Object: striptest__Protected_Hierarchy__c
 Object : striptest__Protected_Hierarchy__c is Accessible: true,  Createable: true, Updateable: true
 Field : Record ID            is Accessible: true,  Createable: false, Updateable: false
 Field : Deleted              is Accessible: true,  Createable: false, Updateable: false
 Field : Name                 is Accessible: true,  Createable: true, Updateable: true
 Field : Location             is Accessible: true,  Createable: true, Updateable: true
 Field : Created Date         is Accessible: true,  Createable: true, Updateable: false
 Field : Created By ID        is Accessible: true,  Createable: true, Updateable: false
 Field : Last Modified Date   is Accessible: true,  Createable: true, Updateable: false
 Field : Last Modified By ID  is Accessible: true,  Createable: true, Updateable: false
 Field : System Modstamp      is Accessible: true,  Createable: false, Updateable: false
 Field : Last Viewed Date     is Accessible: true,  Createable: false, Updateable: false
 Field : Last Referenced Date is Accessible: true,  Createable: false, Updateable: false
 Field : value                is Accessible: true,  Createable: true, Updateable: true



 Access Report for Object: striptest__Public_Hierarchy__c
 Object : striptest__Public_Hierarchy__c is Accessible: true,  Createable: true, Updateable: true
 Field : Record ID            is Accessible: true,  Createable: false, Updateable: false
 Field : Deleted              is Accessible: true,  Createable: false, Updateable: false
 Field : Name                 is Accessible: true,  Createable: true, Updateable: true
 Field : Location             is Accessible: true,  Createable: true, Updateable: true
 Field : Created Date         is Accessible: true,  Createable: true, Updateable: false
 Field : Created By ID        is Accessible: true,  Createable: true, Updateable: false
 Field : Last Modified Date   is Accessible: true,  Createable: true, Updateable: false
 Field : Last Modified By ID  is Accessible: true,  Createable: true, Updateable: false
 Field : System Modstamp      is Accessible: true,  Createable: false, Updateable: false
 Field : Last Viewed Date     is Accessible: true,  Createable: false, Updateable: false
 Field : Last Referenced Date is Accessible: true,  Createable: false, Updateable: false
 Field : Value                is Accessible: true,  Createable: true, Updateable: true



 Access Report for Object: striptest__Protected_List__c
 Object : striptest__Protected_List__c is Accessible: true,  Createable: true, Updateable: true
 Field : Record ID            is Accessible: true,  Createable: false, Updateable: false
 Field : Deleted              is Accessible: true,  Createable: false, Updateable: false
 Field : Name                 is Accessible: true,  Createable: true, Updateable: true
 Field : Location             is Accessible: true,  Createable: true, Updateable: true
 Field : Created Date         is Accessible: true,  Createable: true, Updateable: false
 Field : Created By ID        is Accessible: true,  Createable: true, Updateable: false
 Field : Last Modified Date   is Accessible: true,  Createable: true, Updateable: false
 Field : Last Modified By ID  is Accessible: true,  Createable: true, Updateable: false
 Field : System Modstamp      is Accessible: true,  Createable: false, Updateable: false
 Field : Last Viewed Date     is Accessible: true,  Createable: false, Updateable: false
 Field : Last Referenced Date is Accessible: true,  Createable: false, Updateable: false
 Field : Value                is Accessible: true,  Createable: true, Updateable: true



 Access Report for Object: striptest__Public_List__c
 Object : striptest__Public_List__c is Accessible: true,  Createable: true, Updateable: true
 Field : Record ID            is Accessible: true,  Createable: false, Updateable: false
 Field : Deleted              is Accessible: true,  Createable: false, Updateable: false
 Field : Name                 is Accessible: true,  Createable: true, Updateable: true
 Field : Location             is Accessible: true,  Createable: true, Updateable: true
 Field : Created Date         is Accessible: true,  Createable: true, Updateable: false
 Field : Created By ID        is Accessible: true,  Createable: true, Updateable: false
 Field : Last Modified Date   is Accessible: true,  Createable: true, Updateable: false
 Field : Last Modified By ID  is Accessible: true,  Createable: true, Updateable: false
 Field : System Modstamp      is Accessible: true,  Createable: false, Updateable: false
 Field : Last Viewed Date     is Accessible: true,  Createable: false, Updateable: false
 Field : Last Referenced Date is Accessible: true,  Createable: false, Updateable: false
 Field : Value                is Accessible: true,  Createable: true, Updateable: true
....
....
00:46:05.2 (176462805)|USER_DEBUG|[16]|DEBUG| ProtectedHierarchy No access to entity
Class.System.Security.stripInaccessible: line 15, column 1
Class.System.Security.stripInaccessible: line 10, column 1
Class.striptest.Accessor.assertAccess: line 177, column 1
Class.striptest.Accessor.accessProtectedHierarchy: line 65, column 1
Class.abhinav.AccessorPlayGround.assertAccess: line 13, column 1
AnonymousBlock: line 1, column 1
AnonymousBlock: line 1, column 1
....
....
00:46:05.2 (184833029)|USER_DEBUG|[23]|DEBUG| ProtectedList No access to entity
Class.System.Security.stripInaccessible: line 15, column 1
Class.System.Security.stripInaccessible: line 10, column 1
Class.striptest.Accessor.assertAccess: line 177, column 1
Class.striptest.Accessor.accessProtectedList: line 43, column 1
Class.abhinav.AccessorPlayGround.assertAccess: line 20, column 1
AnonymousBlock: line 1, column 1
AnonymousBlock: line 1, column 1

....
....

00:46:05.2 (203847426)|USER_DEBUG|[28]|DEBUG| PublicHierarchy is READABLE

....
....

00:46:05.2 (215334265)|USER_DEBUG|[35]|DEBUG| PublicList is READABLE

Possible Workarounds?

Avoid Security.stripInaccessible() only for custom settings by checking it dynamically, as shown below

public static void assertAccess(AccessType accessType, SObject[] records){
        Schema.DescribeSObjectResult dsr = records[0].getSObjectType().getDescribe();
        String objectName = dsr.getLabel();
        if (dsr.isCustomSetting()) {
            /*
                Security.stripInaccessible(...) doesnt works correctly with protected custom settings
            */
            Boolean hasAccess = false;
            switch on accessType {
                when READABLE {		
                    hasAccess = dsr.isAccessible();
                }	
                when CREATABLE {		
                    hasAccess = dsr.isCreateable();
                }	
                when UPDATABLE {		
                    hasAccess = dsr.isUpdateable();
                }	
                when UPSERTABLE {		
                    hasAccess = dsr.isUpdateable() && dsr.isCreateable();
                }	
                when else {		  
                    hasAccess = false;
                }
            }
            if (hasAccess == false) {
                String msg = String.format('"{0}" access missing on Object: "{1}"', 
                                                new String[]{
                                                             accessType.name(), objectName
                                                            });
                throw new YourException(msg);
            }

        } else {
            // Normal Object
            try {
                // Strip fields that are not updatable
                SObjectAccessDecision decision = Security.stripInaccessible(accessType, records);
               .. usual code 
       }

Managed Beta Upload Failure

With protected custom settings and this Github source, I ran into an issue of internal Salesforce error while uploading a Managed Beta package. I managed to get out of this issue, by doing a managed release upload. I know it might not be possible to always skip Beta packages. This issue happened with me and my peer developer as well, though after uploading a released package the beta packages are uploading as well 🙁

References

Abhinav Gupta

Subscribe to our newsletter

You have successfully subscribed to the newsletter

There was an error while trying to send your request. Please try again.

Concretio Apps will use the information you provide on this form to be in touch with you and to provide updates and marketing.