Trailhead – Practice simplifying object- and field-level security checks with new Spring ’19 in Apex code

Historically, fully vetting object- and field-level security compliance in Apex code has required using verbose and potentially complex checks on all fields that are included in a query. This can lead to code duplication and additional maintenance work to ensure code stays secure when queries are updated. Practice simplifying your code by making use of the WITH SECURITY_ENFORCED clause in SOQL queries run from Apex. 
In this exercise, you’ll begin with code that contains manual field- and object-level security checks before a SOQL query. You will then refactor the code into a simplified implementation that relies on WITH SECURITY_ENFORCED to handle field- and object-level security checks.

  • Create the Secret Key custom text field on the Contact object as specified in the Get Ready for the Hands-on Challenge section above.
  • Create a new Apex class named SecureApexRest.
  • Copy and paste the SecureApexRest code provided above. This code is already secured with conventional field- and object-level access checks.
  • Add the WITH SECURITY_ENFORCED clause to the SOQL query on line 13 in the code provided. This will make the manual Schema.SObjectType checks redundant.
  • Refactor the code to remove the redundant object and field level access checks.
  • Maintain existing behavior by ensuring that failing results are checked in a SecurityException, rather than any other type of exception. This will require catching the System.QueryException that WITH SECURITY_ENFORCED throws and throwing a new SecurityException.
  • Optional: If you would like to test the behavior of these changes, you can make use of workbench to invoke the Apex REST service.
@RestResource(urlMapping='/secureApexRest')
global with sharing class SecureApexRest {
    @HttpGet
    global static Contact doGet(){
        Id recordId = RestContext.request.params.get('id');
        Contact result;
        if (recordId == null){
            throw new FunctionalException('Id parameter is required');
        }
        try {
            List<Contact> results = [SELECT id, Name, Secret_Key__c FROM Contact WHERE Id = :recordId WITH SECURITY_ENFORCED];
            if (!results.isEmpty()) {
                result = results[0];
            } 
        }catch(System.QueryException e){
        // Perform logic here
        	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{}
}

Leave a Comment