Introduction
Data security is a crucial aspect of any application, and Salesforce Apex provides a powerful development framework for building robust solutions. However, Apex operates in the most privileged mode called System mode, granting unrestricted access to all data.
In this blog post, we will explore the challenges posed by this mode and discuss Salesforce’s new approach to address them.
- Restricting Data Access in Apex: ideally, when users interact with your Salesforce app, they should only be able to access the data they have been explicitly granted permission for. However, Apex’s default execution provides access to all data, which poses a significant security risk when the developer is not aware. To mitigate this, Salesforce has been actively working on implementing mechanisms to restrict data access.
- The Struggle to Degrade SYSTEM_MODE: Over the years, Salesforce has tried to degrade the execution mode from System mode to limit data accessibility. However, achieving this transition has proven challenging. Senior Director Chris Peterson and Apex Product Manager Daniel Ballinger, have shared insights into the difficulties encountered during these attempts.
If you have been using the infamous Schema.SObjectType.*.isAccessible
with checking field by field with Schema.SObjectType.Account.fields.*.isAccessible
, or you have used WITH SECURITY_ENFORCED
or you are not aware of any of those, read this article.
New Approach
The new approach consists in not degrading on top of SYSTEM_MODE anymore, but applying the right data access to the user for every single query or DML done.
DML operations
//Using as user
insert as user new Account(Name = 'Salesforce Inc.');
//Dynamic version
Database.insert( new Account(Name = 'Salesforce Inc.'), AccessLevel.USER_MODE);
a.Name = 'Salesforce';
update <strong>as system</strong> a;
Queries operations
Account a = [SELECT Id, Name FROM Account WHERE Name = 'Salesforce Inc.'<strong> WITH USER_MODE</strong>]
//Dynamic version
Account a = Database.query('SELECT Id, Name FROM Account WHERE Name = 'Salesforce Inc.',<strong> AccessLevel.USER_MODE</strong>)
//Also in aggregations
List<AggregateResult> groupedResults = [SELECT SUM(AMOUNT) total FROM Opportunity WHERE AccountId = :accountId
WITH USER_MODE];
How contradictions are handled?
What if I use without sharing
and the new model together? User mode overrides during this operation the without sharing
clause.
public without sharing MyClass {
...
Database.insert( new Account(Name = 'Salesforce Inc.'), AccessLevel.USER_MODE);
}
A bit inconvenient
User mode database operations generate security exceptions if a CRUD/FLS violation is found, so if you have the requirement to avoid exceptions but still enforce security then you need the Security.stringInaccessible()
method.
try {
integer count = Database.countQuery( SELECT COUNT(email) cnt FROM Contact, 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');
}
Important
- When the operation is finished the code come backs to SYSTEM MODE.
- If you want to force to use system mode just use WITH SYSTEM_MODE instead of WITH USER_MODE.
- It is recommended that you refactor your code that uses
WITH SECURITY_ENFORCED
toWITH USER_MODE
, which is the optimized fully compatible and future-proof version. - You should also consider to refactor your code to simplify the sequence of
Schema.SObjectType.Account.isAccessible
with checking field by field withSchema.SObjectType.Account.fields.*.isAccessible
. - New features in the future will work with this new syntax and approach, you don’t need to worry about.
- If you are an ISV, using these new forms will provide you less alerts from the security scanner.
- SOSL will not return any result if FLS if matched an inaccessible field.
Future options (not GA yet)
- Grant temporary access using a Permission set
AccessLevel appEscalated = AccessLevel.USER_MODE.withPermissionSetId('OPSxx00000006xQ');
Database.insert(new Account(Name'Salesforce Inc.'), appEscalated);
Account a = Database.query('SELECT Id, Name FROM Account, AppEscalated);
Conclusions
As Apex developers, we have a responsibility to ensure the privacy and security of our users’ data. Salesforce’s recognition of the challenges posed by Apex’s System mode and their continuous efforts to address these concerns are encouraging.
I hope and it looks like that finally Salesforce has come up with the right, simplified and reasonable approach.
Links
I recommend you to follow the sequence:
- Videos:
- Slides: https://speakerdeck.com/ca_peterson/tdx22-user-mode-db-ops?slide=15
- Article (link retired by Salesforce): https://developer.salesforce.com/blogs/2023/05/write-simplified-and-secure-apex-with-spring-23-updates
I hope it helps.
Deja un comentario