Protect Your Application from CRUD/FLS Vulnerabilities
By default, Apex data operations (SOQL, DML, and SOSL) run in system mode with full CRUD access to objects and fields. However, Apex also enables you to define access level for data operations. The AccessLevel class represents the two modes in which Apex runs database operations. Use this class to define the execution mode as USER MODE or SYSTEM MODE. An optional accessLevel parameter in Database and Search methods specifies whether the method runs in system mode (AccessLevel.SYSTEM_MODE) or user mode (AccessLevel.USER_MODE). Use these overridden methods to perform DML and query operations.
- Database.query method
- Database.getQueryLocator methods
- Database.countQuery method
- Search.query method
- Database DML methods (insert, update, upsert, merge, delete)
These methods require the accessLevel parameter.
Use WITH USER_MODE or WITH SYSTEM_MODE in your SOQL or SOSL query to indicate the mode of the operation. Execute data operations in USER_MODE to ensure that sharing rules, CRUD, and FLS permissions are enforced. For more details on USER_MODE and SYSTEM_MODE, see Enforce User Mode for Database Operations.
Learn About User Mode Operations
Use USER_MODE to ensure your SOQL, SOSL, and DML operations abide by the user’s sharing rules, CRUD, FLS, and restriction rules.
- Access Records:
Use WITH USER_MODE in SOQL queries to enforce Sharing rules, CRUD, FLS, and Restriction Rules.
System mode privileges are temporarily lowered to retrieve records that are accessible to the user. System mode resumes when the query execution is complete.
1List<Account> acc = [SELECT Id FROM Account WITH USER_MODE]; - Insert Records:
Ensure that the user has create and edit permissions on the Opportunity.Amount field (FLS check must be implemented).
For example, to create an opportunity with a value of $500, create a new record and edit the Opportunity.Amount field (Field-Level Security check). This ensures that the insert operations proceed only if the user possesses the necessary permissions.
1Opportunity o = new Opportunity(); 2// specify other fields 3o.Amount=500; 4insert as user o;or
1Opportunity o = new Opportunity(); 2// specify other fields 3o.Amount=500; 4database.insert(o,AccessLevel.USER_MODE); 5 - Update Records:
Use update as user or database.update() with AccessLevel.USER_MODE to update records while enforcing user permissions.
1Account a = [SELECT Id,Name,Website FROM Account WHERE Id=:recordId]; 2// specify other fields 3a.Website='https://example.com'; 4update as user a; - SOSL Queries:
Execute SOSL queries in USER_MODE using search.query() with AccessLevel.USER_MODE. This ensures that the search results are filtered based on the user's permissions.
1String queryString='FIND :searchString IN ALL FIELDS RETURNING '; queryString+='Lead(Id,Salutation,FirstName,LastName,Name,Email,Company,Phone),'; 2queryString+='Contact(Id, Salutation,FirstName,LastName,Name,Email,Phone),'; 3queryString+='Account(Id,Name,Phone)'; 4List<List<SObject>> searchResults= search.query(queryString,AccessLevel.USER_MODE);
Use CRUD/FLS Check Methods
Enforce object-level and field-level permissions in your code by using Schema.DescribeSObjectResult and Schema.DescribeFieldResult methods to check your user's current access permission levels. These methods identify whether your user can perform a DML operation or query.
For example, you can use isAccessible, isCreateable, or isUpdateable methods of Schema.DescribeSObjectResult to verify whether the current user has read, create, or update access to an sObject. Similarly, with Schema.DescribeFieldResult you can use the access control methods to check whether the current user has read, create, or update access for a field. Furthermore, you can use the isDeletable method in Schema.DescribeSObjectResult to check if the current user has permission to delete the records of an sObject.
Here’s the list of the DescribeSObjectResult class helper functions that you can use to verify a user’s level of access.
- IsCreateable():
Checks if a user has Create permission on an object and Edit permission on the fields.
If a user wants to create an Opportunity, your code should verify that the user has Create permissions on the Opportunity object and Edit permissions on the Opportunity.Amount field using the isCreateable () method.
1if (!Schema.sObjectType.Opportunity.isCreateable() || !Schema.sObjectType.Opportunity.fields.Amount.isCreateable()){ 2 ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR, 3 'Error: Insufficient Access')); 4 return null; 5} 6Opportunity o = new Opportunity(); 7o.Amount=500; 8database.insert(o); 9 - IsAccessible():
Verifies if a user has permissions to access and retrieve a field from the object.
If a user wants to access the Expected Revenue field in an Opportunity, your Apex code should check if the user has read permission on Opportunity.ExpectedRevenue using the isAccessible() method.
1if (!Schema.sObjectType.Opportunity.isAccessible() || !Schema.sObjectType.Opportunity.fields.ExpectedRevenue.isAccessible()){ 2 ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR,'Error: Insufficient Access')); 3 return null; 4} 5Opportunity [] myList = [SELECT ExpectedRevenue FROM Opportunity LIMIT 1000]; - IsUpdateable():
Checks if a user has Edit permission on a field and the object.
If a user wants to update an Opportunity to mark the stage as “Closed Won”. Your code should check if the user has the isUpdateable() permission on Opportunity.StageName.
1if (!Schema.sObjectType.Opportunity.isUpdateable() || !Schema.sObjectType.Opportunity.fields.StageName.isUpdateable()){ 2 ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR,'Error: Insufficient Access')); 3 return null; 4} 5o.StageName=’Closed Won’; update o; - IsDeletable():
Checks if a user can delete records of an object. Note that unlike update, create, and access, delete performs only a CRUD check, verifying that the user can delete the records of an object.
Since you delete entire records in SOQL and not fields, you need to check only the user's CRUD access to the object.
1if (!Lead.sObjectType.getDescribe().isDeleteable()){ 2 3 return null; 4} 5delete l;
Enforce Field and Object-level Data Protection
Use of stripInaccessible() enforces field and object-level data protection. Use this method to strip inaccessible field and relationship fields from query and subquery results for your users. The method also removes inaccessible sObject fields before DML operations and sanitizes sObjects received from untrusted sources to prevent exceptions.
This method checks source records for fields that don't meet field-level security checks. You can also verify user access to lookup or master-detail relationship fields.
The stripInaccessible() method creates a return list of sObjects identical to the source records, excluding fields that are removed or inaccessible. The sObjects returned are in the same order as the source records listed in the sourceRecords parameter of the stripInaccessible() method. Fields that aren't queried are set to null (empty) in the return list without causing an exception.
Furthermore, this method doesn't support the AggregateResult SObject type. Use of this type in source records results in an exception.
To identify inaccessible fields that were removed, use the isSet method. For example, if the return list contains the Contact object and the custom field social_security_number__c is inaccessible to the user, this custom field fails the field-level access check. The field isn't set, and isSet returns false.
1SObjectAccessDecision securityDecision = Security.stripInaccessible(sourceRecords);
2Contact c = securityDecision.getRecords()[0];
3System.debug(c.isSet('social_security_number__c')); // prints "false"