Enforcing Object Level and Field Level Permissions in Apex

Sharing is Caring

By default, Salesforce doesn’t enforce object level or field level permissions. This can be a problem because Apex code mostly runs in a system context so it ignores the current user’s permissions which can create data integrity issues and potential security issues.

The “with sharing” keyword only enforces Sharing Rules and not object and field level permissions. If you are developing a Salesforce package it’s really important that you make sure that the user has permissions to all of the fields and objects you will be interacting with.

This past year, Salesforce has finally released some security enhancements that can make our lives easier as developers.

Using Schema Methods

Using Schema Methods is the old way of enforcing security, it’s not a great way of doing things.

if (Schema.sObjectType.Contact.fields.Email.isAccessible()
  && Schema.sObjectType.Contact.fields.Phone.isAccessible()) {
   Contact c = [SELECT Email, Phone FROM Contact WHERE Id= :Id];
}

Imagine a query that might be using five or ten fields, it can start to really add up if we’re doing queries like this every where.

As you can imagine, maintaining this could would be really painful and very time consuming. I’ve previously blogged about how to use the the Salesforce Schema Methods to check if a user has permissions.

Using WITH SECURITY_ENFORCED

By using WITH SECURITY_ENFORCED, we can potentially avoid doing all of the above craziness on SELECT statements.

List<Opportunity> opps = [select Id, Name, Amount from Opportunity WITH SECURITY_ENFORCED]

// do something with the opps

When it runs that query Salesforce is doing the field and object level checks on our behalf and won’t return any data if there’s a violation. A System.QueryException will be thrown when anything is inaccessible to the user.

This shouldn’t really be a huge problem, you can just catch the error and probably move on.

try {
    List<Opportunity> opps = [select Id, Name, Amount from Opportunity WITH SECURITY_ENFORCED]
} catch(System.QueryException){
    //TODO: Handle Errors
}

If you try to use a field in the where clause that the user doesn’t have accessible to them the query will not throw an error. The field will not be checked and it’s possible you will end up with invalid data.

Using stripInaccessible

System.SObjectAccessDecision stripInaccessible is a great way to remove fields and relationship fields from a query or subquery for fields that a user doesn’t have access to.

Normally, we would do something like this regardless of whether the user had permission to create/update the Email address.

List<Lead> contactsToInsert = new List<Lead>(){
    new Lead(FirstName = 'John', LastName = 'JingleheimerSchmidt', Email = 'john.JingleheimerSchmidt@example.com'),
    new Lead(FirstName = 'James', LastName = 'Fake', Email = 'james.fake@example.com')
};

try {
    insert contactsToInsert;
} catch (Exception ex) {
    // do something with exception
}

As you know, this isn’t really secure. We could definitely use the Schema method and remove the fields they don’t have access to or we could use the SObjectAccessDecision way and write a lot less code!

List<Lead> contactsToInsert = new List<Lead>(){
    new Lead(FirstName = 'John', LastName = 'JingleheimerSchmidt', Email = 'john.JingleheimerSchmidt@example.com'),
    new Lead(FirstName = 'James', LastName = 'Fake', Email = 'james.fake@example.com')
};

try {
    SObjectAccessDecision securityDecision = Security.stripInaccessible(
        AccessType.CREATABLE,
        contactsToInsert );
    
    // if we wanted to we could now call securityDecision.getRemovedFields().get('Lead') and
// it would give us a Set of all the fields that have been removed.
    System.debug(securityDecision.getRemovedFields().get('Lead'));

    // this now returns a safe list of records that we can insert into the database.
    insert securityDecision.getRecords();
} catch (Exception ex) {
    // do something with exception
}

Wrapping It Up

There’s finally some really useful updates to Apex that make security quite a bit easier for developers. stripInaccessible and WITH SECURITY_ENFORCED can potentially make our jobs a lot easier.

Sharing is Caring

Brian is a software architect and technology leader living in Niagara Falls with 13+ years of development experience. He is passionate about automation, business process re-engineering, and building a better tomorrow.

Brian is a proud father of four: two boys, and two girls and has been happily married to Crystal for more than ten years. From time to time, Brian may post about his faith, his family, and definitely about technology.