Friday, 24 January 2020

Enforce Security With the stripInaccessible Method


In this post we will talk about the new way to enforce the security in apex with stripInaccessible() method. From Winter 20, stripInaccessible() security feature for field-level data protection is available for beta in production. In winter 20 Salesforce extended the feature and added enum value UPSERTABLE to System.AccessType.

stripInaccessible() is useful to strip the field that current user don't have access from query and sub-query. We can use it to remove inaccessible field from sObjects before DML operation to avoid exceptions. This method also provides the option an option to enforce the Object level access check.

Syntax :- 


public static System.SObjectAccessDecision stripInaccessible(System.AccessType accessCheckType,
                                                             List<SObject> sourceRecords,
                                                             Boolean enforceRootObjectCRUD )
  • accessCheckType : This parameter determines the type of field-level access check to be performed
  • sourceRecords : A list of sObjects to be checked for fields that aren’t accessible in the context of the current user’s operation
  • enforceRootObjectCRUD : Indicates whether an object-level access check is performed
 

Use Case 1: No access on Field

In this example, User don't have access on field called accountNumber on Account Object.

        List<Account> accounts =[SELECT Id, Name,AccountNumber
                                 FROM Account limit 2];
         
        // Strip fields that are not readable
        SObjectAccessDecision decision = Security.stripInaccessible(
                                               AccessType.READABLE,
                                               accounts);
        
        // Print stripped records
        for (Integer i = 0; i < accounts.size(); i++) {
              System.debug('Insecure record access: '+accounts[i]);
              System.debug('Secure record access: '+decision.getRecords()[i]);
        }
        
        // Print modified indexes
        System.debug('Records modified by stripInaccessible: '+decision.getModifiedIndexes());
        
        // Print removed fields
        System.debug('Fields removed by stripInaccessible: '+decision.getRemovedFields());             
In above example user dont have access on accountNumber field. After using the Security.scripInaccessible method we can simply strip out the same field. Which all field are removed we can check with "getRemovedFields" method. Here is output of above code.



Use Case 2: No access on sObject

Let see another example when user don't have access on object itself. For demo I simply removed the access from Account object. Then we got the below exception.


 System.NoAccessException: No access to entity: Account
we have "enforceRootObjectCRUD" optional parameter which is true by default we can set that as false to get null value.


Use Case 3: SubQuery.

What about if you are try to access field from subquery ?

 List<Account> accountsWithContacts =
    [SELECT Name, AccountNumber,
        (SELECT LastName, Phone FROM Account.Contacts)
    FROM Account  limit 2];
 
// Strip fields that are not readable
   SObjectAccessDecision decision = Security.stripInaccessible(
                                       AccessType.READABLE,
                                       accountsWithContacts);

// Print stripped records
   for (Integer i = 0; i < accountsWithContacts.size(); i++)
  {
      System.debug('Insecure record access: '+accountsWithContacts[i]);
      System.debug('Secure record access: '+decision.getRecords()[i]);
   }

// Print modified indexes
   System.debug('Records modified by stripInaccessible: '+decision.getModifiedIndexes());

// Print removed fields
   System.debug('Fields removed by stripInaccessible: '+decision.getRemovedFields());
  This will remove the field on which user dont have access.



Use Case 4: DML.


What about if use dont have access on AccountNumber field and we will try to add value of AccountNumber by DML ?

public static void testDML(){
    Account acc = new Account(Name='Test', AccountNumber ='TestRating');
    insert acc ;
    System.debug('---->'+acc );
}   
This will insert the record with AccountNumber value in same user context even user dont have access on same field. How we can stop the same with Security.stripInaccessible. Let see

public static void testDML()
{  
    Account acc = new Account(Name='Test' ,AccountNumber ='Demo');
    System.debug('---->'+acc );
    List<Account> lstAcc = new List<Account>();
    lstAcc.add(acc);
   
    SObjectAccessDecision securityDecision = Security.stripInaccessible(
                                                AccessType.CREATABLE,
                                                lstAcc );
    System.debug('---ecurityDecision.getRecords()->'+securityDecision.getRecords());                                           
    insert securityDecision.getRecords();
    System.debug(securityDecision.getRemovedFields().get('Account'));
}      
here is output


You can also use the method to sanitize sObjects that have been deserialized from an untrusted source





Further Reading
  1. https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_classes_with_security_stripInaccessible.htm
  2. https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_class_System_Security.htm#topic-title


2 comments:

  1. Thank you so much for explaining it so clearly !!!
    In the last Execution Log, the debug statement [7]DEBUG ----> I am in inside insert event.
    This Debug statement is not present here but may be some other class from where you might have invoked it or something ?

    ReplyDelete
  2. How to check for the delete DML ?

    ReplyDelete