Secure Apex Code with User Mode Database Operations

Apex security model next generation is almost here. Now you can now declare when Apex runs database operations in user mode or system mode. In this post you will learn about the dramatically simplified ways of writing Secure Apex Code with User Mode Database Operations ( Summer ’22 release ). This feature currently in Beta.

The new Database methods support an AccessLevel parameter that enables you to run database operations in user mode, instead of in the default system mode. To enhance the security context of Apex, you can specify user-mode access for database operations by invoking Apex in user mode. The field-level security (FLS), sharing rules, and CRUD permissions of the running user are respected in user mode, unlike in system mode.

What is System Mode?

System mode means running apex code by ignoring user’s permissions. User mode means running apex code by respecting user’s permissions and sharing of records. System mode is trusted, admin-vetted logic run with extra rights.
• For trusted business logic
• For data consistency

Apex User Mode Vs System Mode.

Image is picked from our Security in Salesforce session.

What is With Sharing and Without sharing in Apex?

Now question is what is With sharing & Without sharing keywords in APEX ? Will that help us to execute code in system mode to user mode?

Use the with sharing or without sharing keywords specify whether or not to enforce sharing rules, records being updated via Role, Sales Teams – any sort of sharing really.

With Sharing Keyword

If you use this keyword, then the Apex code will enforce the Sharing settings of current user to Apex code. This does not enforce the Profile permission, only the data level sharing settings.

Without Sharing Keyword

Class declared with this keyword executes in System mode.

Enforcing the current user’s sharing rules can impact:

  1. SOQL and SOSL queries. A query may return fewer rows than it would operating in system context.
  2. DML operations. An operation may fail because the current user doesn’t have the correct permissions.

Now again what all option we have to Enforcing Object & FLS Permissions in Apex? Let see that in details.

Enforcing Object & FLS Permissions in Apex

Apex doesn’t enforce object-level and field-level permissions by default. Let see how we can enforce the CRUD & FLS in Apex.

Read data (SOQL)Modify data (DML)
Schema methodsYesYes
WITH SECURITY_ENFORCEDYesNo
Security.stripInaccessible()YesYes
Database operations in user mode (beta)YesYes

In this post will talk about Secure Apex Code with User Mode Database Operations. If you want to learn about other option then check our old blog post Apex security in Salesforce.

Using WITH USER_MODE or WITH SYSTEM_MODE

If you need to fetch data from SOQL query then How we handle user mode today?

return [SELECT Name, Owner.Name FROM Account];

Let an example of Describe-based checks. For that we handle like below code

if (Schema.SObjectType.Account.isAccessible() &&
     Schema.SObjectType.Account.fields.Name.isAccessible() &&
     Schema.SObjectType.Account.fields.OwnerId.isAccessible() &&
     Schema.SObjectType.Account.fields.Support__c.isAccessible() &&
     Schema.SObjectType.Account.fields.OwnerId.getReferenceTo()[0].getDescribe().isAccessible()) {
 return [SELECT Name, Owner.Name FROM Account WHERE Support__C = 'Premier'];
} else {
 throw new AuraHandledException('Access Denied');
}

Now we have a better way to handle it using USER_MODE.

return [SELECT Name, Owner.Name FROM Account WHERE Support__C = 'Premier' WITH USER_MODE];

Database operations can specify user or system mode

Database operations can specify user or system mode. This example inserts a new account in user mode.

insert as user new Account(Name = 'ApexHours');

Account a = [SELECT Id, Name FROM Account WHERE Support__c = 'FREE TRAINING' WITH USER_MODE];

a.Rating = 'Hot';
update as system a;

Dynamic at runtime syntax.

The new AccessLevel class represents the two modes in which Apex runs database operations. Use this new class to define the execution mode as user mode or system mode. Use these new overloaded methods to perform DML and query operations.

  • Database.query methods
  • Search.query methods
  • Database DML methods (insertupdateupsertmergedeleteundelete, and convertLead)

Example of Database DML methods

Database.insert( new Account(Name = 'ApexHours'),
                 AccessLevel.USER_MODE
);

Here is example of Database.query

Account a = Database.query('SELECT Id, Name FROM Account WHERE Rating = \'Hot\'',
                   AccessLevel.USER_MODE
);

Now what about if any error will come FLS? How to handle it.

List<Database.SaveResult> srList = Database.insert(
   new SObject[] {
    new Account(Name='ApexHours', AnnualRevenue=1), // no FLS edit on AnnualRevenue
    new Contact(LastName='Chaudhary', Email='foo'), // no FLS view on Email
   },
   false, // allOrNone
   AccessLevel.USER_MODE
);

System.assert(!srList.get(0).getErrors()[0].getFields().contains('AnnualRevenue’),
'Missing Account.AnnualRevenue FLS');

System.assert(!srList.get(1).getErrors()[0].getFields().contains('Email’),
'Missing Contact.Email FLS')

Full Error Reporting – not just the first error!

try {
   integer count = Database.countQuery(
      'select count(email) cnt from Contact where Account.website =\'foo\'',
      AccessLevel.USER_MODE
   );
} catch (QueryException qe) {
 Map<String, Set<String>> inaccessibleFields = qe.getInaccessibleFields();
 System.assert(!inaccessibleFields.containsKey('Contact'), 'Missing Contact CRUD');
 System.assert(!inaccessibleFields.get('Contact').contains('Email'), 'Missing Contact.Email
FLS');
 System.assert(!inaccessibleFields.get('Account').contains('Website'), 'Missing Account.Website
FLS');
}

Let take a look for Search.query methods. SOSL returns no results if matched on inaccessible field

String searchquery = 'Find \'Apex\' IN ALL FIELDS RETURNING Account(id,name)';

List<List<SObject>> searchList = Search.query(searchquery, AccessLevel.USER_MODE);

Search.SearchResults sr = Search.find(searchquery, AccessLevel.USER_MODE);

Search.SuggestionResults sgr = Search.suggest('all', 'Account', null,
AccessLevel.USER_MODE);

List<List<SObject>> searchList = [FIND 'website123' RETURNING Account(id,name)
with user_mode];

Why this is Different

  • Historical focus on easier CRUD/FLS checks
  • Describes method don’t scale well

This means that as Salesforce launches new features, like Restriction Rules, that operate on unique security models we can support them fully.

Edge cases work right

  • WITH SECURITY_ENFORCED doesn’t work with Task.WhatId
  • User-mode Database Operations returns all errors, not just the first
  • SOSL is a great example of a place where CRUD/FLS wasn’t sufficient.

Do I need to refactor all my security checks? Share your finding and thoughts with us.

Amit Chaudhary
Amit Chaudhary

Amit Chaudhary is Salesforce Application & System Architect and working on Salesforce Platform since 2010. He is Salesforce MVP since 2017 and have 17 Salesforce Certificates.

He is a active blogger and founder of Apex Hours.

Articles: 460

8 Comments

  1. Hi Amit,

    This is wrong statement: “Class declared with this keyword executes in System mode.” under section ‘Without Sharing Keyword’.

    Correct one: Use the without sharing keyword when declaring a class to ensure that the sharing rules for the current user are not enforced.

    Thanks

  2. Hi Amit,
    I am using Portal guest User
    In My use case guest user insert Content Version and ContentDistribution record for public url.
    But it showing error for guest user to insert ContentDistribution record.
    Can you please suggest me solution?

Leave a Reply

Your email address will not be published. Required fields are marked *