Newer Version Available

This content describes an older version of this product. View Latest

Enforce Security with the stripInaccessible Method

Use the stripInaccessible method to enforce field-level and object-level data protection. This method can be used to strip the fields and relationship fields from query and subquery results that the user can’t access. The method can also be used to remove inaccessible sObject fields before DML operations to avoid exceptions and to sanitize sObjects that have been deserialized from an untrusted source.

Where possible, we changed noninclusive terms to align with our company value of Equality. We maintained certain terms to avoid any effect on customer implementations.

Important

The field- and object-level data protection is accessed through the Security and SObjectAccessDecision classes. The access check is based on the field-level permission of the current user in the context of the specified operation—create, read, update, or upsert. The Security.stripInaccessible() method checks the source records for fields that don’t meet the field-level security check for the current user. The method also checks the source records for lookup or master-detail relationship fields to which the current user doesn’t have access. The method creates a return list of sObjects that is identical to the source records, except that the fields that are inaccessible to the current user are removed. The sObjects returned by the getRecords method contain records in the same order as the sObjects in the sourceRecords parameter of the stripInaccessible method.

The Security.stripInaccessible() method takes a permission set ID as a parameter and enforces field-level and object-level access as per the specified permission set, in addition to the running user’s permissions.

The ID field is never stripped by the stripInaccessible method to avoid issues when performing DML on the result.

Note

To identify inaccessible fields that were removed, you can use the SObject.isSet() method. For example, the return list contains the Contact object and the custom field social_security_number__c is inaccessible to the user. Because this custom field fails the field-level access check, the field isn’t set and isSet returns false.

1SObjectAccessDecision securityDecision = Security.stripInaccessible(AccessType.READABLE, sourceRecords);
2Contact c = securityDecision.getRecords()[0];
3System.debug(c.isSet('social_security_number__c')); // prints "false"

The stripInaccessible method doesn’t support AggregateResult SObject. If the source records are of AggregateResult SObject type, an exception is thrown.

Note

To enforce object and field permissions on the User object and hide a user’s personal information from other users in orgs with Experience Cloud sites, see Enforcing Object and Field Permissions.

The following are some examples where the stripInaccessible method can be used.

Example

This example code removes inaccessible fields from the query result. A display table for campaign data must always show the BudgetedCost. The ActualCost must be shown only to users who have permission to read that field.
1SObjectAccessDecision securityDecision = 
2         Security.stripInaccessible(AccessType.READABLE,
3                 [SELECT Name, BudgetedCost, ActualCost FROM Campaign]                 );
4
5    // Construct the output table
6    if (securityDecision.getRemovedFields().get('Campaign').contains('ActualCost')) {
7        for (Campaign c : securityDecision.getRecords()) {
8        //System.debug Output: Name, BudgetedCost
9        }
10    } else {
11        for (Campaign c : securityDecision.getRecords()) {
12        //System.debug Output: Name, BudgetedCost, ActualCost
13        }
14}

Example

This example code removes inaccessible fields from the subquery result. The user doesn’t have permission to read the Phone field of a Contacts object.
1List<Account> accountsWithContacts =
2	[SELECT Id, Name, Phone,
3	    (SELECT Id, LastName, Phone FROM Account.Contacts)
4	FROM Account];
5  
6   // Strip fields that are not readable
7   SObjectAccessDecision decision = Security.stripInaccessible(
8	                                   AccessType.READABLE,
9	                                   accountsWithContacts);
10 
11// Print stripped records
12   for (Integer i = 0; i < accountsWithContacts.size(); i++) 
13  {
14      System.debug('Insecure record access: '+accountsWithContacts[i]);
15      System.debug('Secure record access: '+decision.getRecords()[i]);
16   }
17 
18// Print modified indexes
19   System.debug('Records modified by stripInaccessible: '+decision.getModifiedIndexes());
20 
21// Print removed fields
22   System.debug('Fields removed by stripInaccessible: '+decision.getRemovedFields());

Example

This example code removes inaccessible fields from sObjects before DML operations. The user who doesn’t have permission to create Rating for an Account can still create an Account. The method ensures that no Rating is set and doesn’t throw an exception.
1List<Account> newAccounts = new List<Account>();
2Account a = new Account(Name='Acme Corporation');
3Account b = new Account(Name='Blaze Comics', Rating=’Warm);
4newAccounts.add(a);
5newAccounts.add(b);
6
7SObjectAccessDecision securityDecision = Security.stripInaccessible(
8                                         AccessType.CREATABLE, newAccounts);
9
10// No exceptions are thrown and no rating is set
11insert securityDecision.getRecords();
12
13System.debug(securityDecision.getRemovedFields().get('Account')); // Prints "Rating"
14System.debug(securityDecision.getModifiedIndexes()); // Prints "1"

Example

This example code sanitizes sObjects that have been deserialized from an untrusted source. The user doesn’t have permission to update the AnnualRevenue of an Account.
1String jsonInput =
2'[' +
3'{' +
4'"Name": "InGen",' +
5'"AnnualRevenue": "100"' +
6'},' +
7'{' +
8'"Name": "Octan"' +
9'}' +
10']';
11
12List<Account> accounts = (List<Account>)JSON.deserializeStrict(jsonInput, List<Account>.class);
13SObjectAccessDecision securityDecision = Security.stripInaccessible(
14                                         AccessType.UPDATABLE, accounts);
15
16// Secure update
17update securityDecision.getRecords(); // Doesn’t update AnnualRevenue field
18System.debug(String.join(securityDecision.getRemovedFields().get('Account'), ', ')); // Prints "AnnualRevenue"
19System.debug(String.join(securityDecision.getModifiedIndexes(), ', ')); // Prints "0”

Example

This example code removes inaccessible relationship fields from the query result. The user doesn’t have permission to insert the Account__c field, which is a lookup from MyCustomObject__c to Account.
1// Account__c is a lookup from MyCustomObject__c to Account
2@IsTest
3   public class TestCustomObjectLookupStripped {
4      @IsTest static void caseCustomObjectStripped() {
5         Account a = new Account(Name='foo');
6         insert a;
7         List<MyCustomObject__c> records = new List<MyCustomObject__c>{
8            new MyCustomObject__c(Name='Custom0', Account__c=a.id)
9         };
10         insert records;
11         records = [SELECT Id, Account__c FROM MyCustomObject__c];
12         SObjectAccessDecision securityDecision = Security.stripInaccessible
13                                                  (AccessType.READABLE, records);
14         
15         // Verify stripped records
16         System.assertEquals(1, securityDecision.getRecords().size());
17         for (SObject strippedRecord : securityDecision.getRecords()) {
18             System.debug('Id should be set as Id fields are ignored: ' + 
19                           strippedRecord.isSet('Id')); // prints true
20             System.debug('Lookup field FLS is not READABLE to running user, 
21                           should not be set: ' +
22                           strippedRecord.isSet('Account__c')); // prints false
23         }
24      }
25   }