The generation model for Apex Security is almost here. Now you can declare the database operation mode in the DML. You can specify if the DML will run in user mode or system mode.
The feature is in beta release check this out Summer 22′ release
The new database method accepts a new AccessLevel type parameter which will accept USER_MODE or SYSTEM_MODE.
The USER_MODE checks the following things
- Sharing Rules
- Object Access
- Field Access
What is System vs User Mode
With Sharing vs Without Sharing
There is a big misconception out there among developers about sharing and without sharing keywords.
with sharing – If you use with sharing keyword with a class then the Apex Code will enforce the sharing setting of the current user, user roles, manually sharing &, etc. All record level sharing will be enforced. This doesย not enforce the Profile permissionย settings.
without sharing – If you do without sharing keyword then Apex Code will execute in system mode.
Enforcing the current userโs sharing rules can impact:
- SOQL and SOSL queries. A query may return fewer rows than it would operate in the system context.
- DML operations. An operation may fail because the current user doesnโt have the correct permissions.
Now again what options do we have for Enforcing Object & FLS Permissions in Apex? Let’s see that in detail.
Enforcing Object & FLS Permissions
Apex doesnโt enforce object and field level permissions by default. As a developer, we have to forcefully enforce the CRUD & FLS in Apex.
Below is the table with more information about Field and Object Level Security.
Record Level Security
- WITH SECURITY_ENFORCED
- WITH USER_MODE ( currently in beta )
- StripInAccessible
- Schema Methods
Object Level Security
- Schema Methods
- insert as user ( currently in beta )
- StripinAccessible
- Database user mode operations ( currently in beta )
Using WITH USER_MODE or WITH SYSTEM_MODE
let’s first run a query without any keywords
[SELECT Id, Name, Rating, Owner.Name FROM Account];
Schema Class
The above query will run without checking the field-level access.ย Let’s make the changes and use the Schema class to check the accessibility of the Fields on Account Object.
if (Schema.SObjectType.Account.isAccessible() &&
Schema.SObjectType.Account.fields.Name.isAccessible() &&
Schema.SObjectType.Account.fields.Id.isAccessible() &&
Schema.SObjectType.Account.fields.OwnerId.isAccessible() &&
Schema.SObjectType.Account.fields.Rating.isAccessible() &&
Schema.SObjectType.Account.fields.OwnerId.getReferenceTo().get(0).getDescribe().isAccessible()) {
return [SELECT Id, Name, Rating, Owner.Name FROM Account];
} else {
throw new AuraHandledException('Access Denied');
}
WITH SECURITY_ENFORCED
The above code which we have written will work well without any error, now suppose that you are querying more than 20 fields then you have to put a check for all 20 fields.ย
So to solve this problem Salesforce Introduced the new keyword that is WITH Security_Enforced
Below is the example for the same which will check the field level permission.
Note: – WITH Security_Enforced keyword does not enforce the record level security
[SELECT Id, Name, Rating, Owner.Name FROM Account WITH SECURITY_ENFORCED];
WITH USER_MODE
Congratulations! you were able to write the SOQL query in one line which enforces all the security for the user. Now, if you also wanted to enforce the record level access then how you can do that from Query itself?
For this, Salesforce recently came with a new Keyword which is in Beta mode that is WITH USER_MODE
[SELECT Id, Name, Rating, Owner.Name FROM Account WITH USER_MODE];
StripInAccessible Class
WITH USER_MODE and WITH SECURITY_ENFORCED will throw an exception if the running user does not have permission to any field referred to in Query.
To run the query without any exception we have the StripInAccessible class which will strip the fields to which you do not have access.
We can write the same query in the following way
SObjectAccessDecision securityDecision = Security.stripInaccessible(AccessType.READABLE,
[SELECT Id, Name, Rating, Owner.Name FROM Account]
);
// Print Secured Access Records
System.debug('Secure record access: '+securityDecision.getRecords());
// Print modified indexes
System.debug('Records modified by stripInaccessible: '+securityDecision.getModifiedIndexes());
// Print removed fields
System.debug('Fields removed by stripInaccessible: '+securityDecision.getRemovedFields());
DML Enforcement
So far we talked about SOQL Enforcement. Now, let’s talk about various ways to enforce permission for DML.
Below is an example for a simple DML we always used to do
Case caseRecord = new Case(
Subject = 'Generator is Not Working',
Description = 'Generator is Not Working',
Status = 'Open',
Priority = 'Urgent'
);
insert caseRecord;
In the above example, the record will be created regardless if the running user has access to the case object or not because the DML is running in System Context.
Schema Class
The most common and widely used method is using Schema classes and enforcing the security for the object and each field being referenced.
Here is the modified version of the above DML
Case caseRecord = new Case(
Subject = 'Generator is Not Working',
Description = 'Generator is Not Working',
Status = 'Open',
Priority = 'Urgent'
);
if( Schema.SObjectType.Case.isCreateAble()
&& Schema.SObjectType.Case.fields.Subject.isCreateAble()
&& Schema.SObjectType.Case.fields.Description.isCreateAble()
&& Schema.SObjectType.Case.fields.Status.isCreateAble()
&& Schema.SObjectType.Case.fields.Priority.isCreateAble()){
insert caseRecord;
}else{
throw new AuraHandledException('Insufficient Access');
}
Database methods for User Mode
In the above example, We have put the check for every field which is included in DML again here the question is what if we have 30+ fields included?
To address this problem, Salesforce introduced user mode operations which are in beta version.
ACCESSLVEL is an apex class which have two variables that are used to make the DML or Query in either USER_MODE or SYSTEM_MODE
Let’s see how we can modify the above DML statement to use user mode
Case caseRecord = new Case(
Subject = 'Generator is Not Working',
Description = 'Generator is Not Working',
Status = 'Open',
Priority = 'Urgent'
);
insert as user caseRecord;
Case caseRecord = new Case(
Subject = 'Generator is Not Working',
Description = 'Generator is Not Working',
Status = 'Open',
Priority = 'Urgent'
);
Database.SaveResult sr = Database.insert( caseRecord, false, AccessLevel.USER_MODE);
if(!sr.isSuccess()){
String errors = String.join( sr.getErrors(), ' ' );
System.debug('Error While Creating the Recruiter Records \n '+ errors);
}
Log Error not just report
Case caseRecord = new Case(
Subject = 'Generator is Not Working',
Description = 'Generator is Not Working',
Status = 'Open',
Priority = 'Urgent'
);
try {
Database.SaveResult sr = Database.insert( caseRecord, false, AccessLevel.USER_MODE);
} catch (QueryException qe) {
Map> inaccessibleFields = qe.getInaccessibleFields();
// Log Error here
}