Home Salesforce Enforce Object-level and Field-level permissions in Apex

Enforce Object-level and Field-level permissions in Apex

by Dhanik Lal Sahni

Apex code is mostly run in system context so it is not considering current user’s permission. It is creating data integrity issue. Using  with sharing keywords when declaring a class enforces Sharing Rules, but not object and field-level permissions.

In Spring 20 release some security enhancements are added to enforce object and field-level permissions.

Using Schema Methods

We can now use Schema.DescribeFieldResult to check current user has read, create, or update access for a field.

For example, if we want to check that logged user has read access on PersonEmail field of the Account Object, we can enclose the SOQL query inside an if block that checks for field access using the Schema methods described above.

if (Schema.sObjectType.Account.fields.PersonEmail.isAccessible()) {
   Contact c = [SELECT PersonEmail FROM Account WHERE Id= :Id];
}

Similarly we can use isCreateable, or isUpdateable method to check before inserting and updating specific fields.

Using WITH SECURITY_ENFORCED

WITH SECURITY_ENFORCED clause can be used in SOQL queries to enforce field and object level security permissions in Apex code. This will be applicable for subqueries and cross-object relationships as well.

Field-level permissions are checked for all the fields that are retrieved in the SELECT clause(s) of the query. Since this clause only works inside an SOQL query, it’s only useful when we want to check for read access on a field.

try{
    List<Account> acts = [SELECT Id, Name, Email, (SELECT LastName FROM Contacts)
        FROM Account WHERE Name like 'Universal' WITH SECURITY_ENFORCED];
} catch(System.QueryException){
    //TODO: Handle Errors
}

The above query will return the Id, Email and Name of Accounts, and the LastName of the related contacts, only if the user has read access to all of these three fields. If the user doesn’t have access to at least one of these fields, the query throws a System.QueryException exception, and no results are returned.

WITH SECURITY_ENFORCED is only applicable to field which is in select clause. If any field used in where clause then it will not check for that field.

List<Contact> contacts = [SELECT Id, Name, FROM Contact 
                               WHERE Image__c != null WITH SECURITY_ENFORCED];

In above query even user does not have read access to Image__c field, it will not throw any error.

Using stripInaccessible

stripInaccessible method will enforce field and object level security in Apex. This method will strip fields from sObject list for which current user does not have permission.

stripInaccessible Method Signature:

stripInaccessible(System.AccessType accessCheckType, List<SObject> sourceRecords, [Boolean enforceRootObjectCRUD])

System.AccessType (accessCheckType) – will show type of field level access check is being performed.

sourceRecords – List of sObject record on which method will perform access check.

enforceRootObjectCRUD: This indicates whether object-level access check has to be performed or not.

Let us take example of account object. This object has custom filed Customer_Image__c.  Let us take current user does not have access to insert value in this field.

List<Account> accts= new List<Account>{
    new Account(Name='Dhanik Sahni', Customer_Image__c='https://avatars3.githubusercontent.com/u/13779106?s=460&v=4'),
    new Account(Name='Poorvansh Sahni', Customer_Image__c='https://avatars3.githubusercontent.com/u/13779106?s=460&v=4'),
};

// Strip fields that are not creatable
SObjectAccessDecision decision = Security.stripInaccessible(
    AccessType.CREATABLE,
    accts);
try{
    // get field where user has access to insert
    insert decision.getRecords();
}catch(NoAccessException e){
    system.debug(e.getMessage());
}

// Print removed fields
System.debug(decision.getRemovedFields());

The DML operation written above runs successfully without exceptions, but the Customer_Image__c field on the inserted records would be blank because the current user doesn’t have appropriate permissions on it.

we can use insert decision; also but that will be insecure insertion so always use decision.getRecords() which is secure. This will return all fields where user has access to perform data insertion.

Reference:

https://releasenotes.docs.salesforce.com/en-us/summer19/release-notes/rn_apex_Security_stripInaccessible.htm

You may also like

Leave a Comment