Monday 21 December 2020

Enforce Field- and Object-Level Security with Security.StripInaccessible

 Hi ,

Here we are going to learn about Security.StripInaccessible method.

  • This method mainly used for field- and object-level data protection
  • This allows developers to remove all fields from the records that the running user does not have access to.
  • This makes it easier to allow graceful degradation of application behavior on security violation by omitting fields rather than failing.

How does it work?

  • The field- and object-level data protection is accessed through the Security and SObjectAccessDecision classes.
  • We can use the StripInaccessible method to strip fields that the current user can’t access from query and subquery results.
  • It helps to remove inaccessible fields from sObjects before a DML operation to avoid exceptions.
  • This method helps sanitize sObjects that have been deserialized from an untrusted source.
  • The stripInaccesible method checks the source records for fields that don’t meet the field- and object-level security check for the current user and creates a return list of sObjects. 
  • The return list is identical to the source records, except that fields inaccessible to the current user are removed.
  • The sObjects returned by the getRecords method contain records in the same order as the sObjects in the sourceRecords parameter of the stripInaccessible method. 
  • Fields that aren’t queried are null in the return list, without causing an exception.
Note: Previously we were using dynamic apex for identifying access on particular object or field.

How to identify inaccessible fields that were removed:

We can use 'isSet' method.
For example, the return list contains the Contact object and the custom field social_security_number__c is inaccessible to the user. Because this custom field fails the field-level access check, the field is not set and "isSet" returns false.

SObjectAccessDecision securityDecision = Security.stripInaccessible(sourceRecords);
Contact c = securityDecision.getRecords()[0];
System.debug(c.isSet('social_security_number__c')); // prints "false"

Eg:(1)
This example removes fields from the query result that the current user does not have update access to.

SObjectAccessDecision securityDecision = 
       Security.stripInaccessible(AccessType.UPDATABLE,
             [SELECT Name, BudgetedCost, ActualCost FROM Campaign]);

Eg:(2)

This example performs a query and then removes inaccessible fields from the query result.

List<Contact> records = [SELECT Id, Name, Phone, HomePhone FROM Contact];
SObjectAccessDecision securityDecision = Security.stripInaccessible(AccessType.READABLE, records);

Example class before applying "stripInaccessible".


global with sharing class ApexSecurityRest {
    global static Contact doGet() {
        Id recordId = RestContext.request.params.get('id');
        Contact result;
        if (recordId == null) {
           throw new FunctionalException('Id parameter is required');
        }
        if (Schema.SObjectType.Contact.isAccessible()
          && Schema.SObjectType.Contact.fields.Name.isAccessible()
          && Schema.SObjectType.Contact.fields.Top_Secret__c.isAccessible()
        ) {
          List<Contact> results = [SELECT id, Name, Title, Top_Secret__c, Account.Name FROM Contact WHERE Id = :recordId];
          if (!results.isEmpty()) {
             result = results[0];
             if (Schema.sObjectType.Contact.fields.Description.isUpdateable()){
                 result.Description = result.Account?.Name;
                 }
             }
           } else {
             throw new SecurityException('You don\'t have access to all contact fields required to use this API');
           }
           return result;
      }
      public class FunctionalException extends Exception{}
      public class SecurityException extends Exception{}
}

After applying stripInaccessible


global with sharing class ApexSecurityRest {  
    global static Contact doGet() {
        Id recordId = RestContext.request.params.get('id');
        Contact result;
        if (recordId == null) {
           throw new FunctionalException('Id parameter is required');
        }
       
          List<Contact> results = [SELECT id, Name, Title, Top_Secret__c, Account.Name FROM Contact WHERE Id = :recordId];
          SObjectAccessDecision securityDecision = Security.stripInaccessible(AccessType.READABLE, results);
          if (!results.isEmpty()) {
             result = results[0];
             if (Schema.sObjectType.Contact.fields.Description.isUpdateable()){
                 result.Description = result.Account?.Name;
                }
            }
           
           return result;
      }
      public class FunctionalException extends Exception{}
      public class SecurityException extends Exception{}
}




Considerations:


References:
------------------



No comments:

Post a Comment

How to include a screen flow in a Lightning Web Component

 Hi, Assume  you have a flow called "Quick Contact Creation" and API Name for the same is "Quick_Contact_Creation". To i...