Last active
March 28, 2026 18:46
-
-
Save pgajek2/e0f00f0614df86ea7698153cb77bbea3 to your computer and use it in GitHub Desktop.
FLS + Sharing in Apex SOQL
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * Tests covering all FLS, OLS, and Sharing Mode combinations from the SOQL security matrix: | |
| * | |
| * SOQL Keyword | Class Sharing | OLS+FLS | Sharing Mode | |
| * -----------------------|-------------------|---------|------------- | |
| * WITH USER_MODE | with sharing | ✅ | ✅ | |
| * WITH USER_MODE | inherited sharing | ✅ | ✅ | |
| * WITH USER_MODE | without sharing | ✅ | ✅ | |
| * WITH SYSTEM_MODE | with sharing | ❌ | ✅ | |
| * WITH SYSTEM_MODE | inherited sharing | ❌ | 🔄 | |
| * WITH SYSTEM_MODE | without sharing | ❌ | ❌ | |
| * WITH SECURITY_ENFORCED | with sharing | ✅ | ✅ | |
| * WITH SECURITY_ENFORCED | inherited sharing | ✅ | 🔄 | |
| * WITH SECURITY_ENFORCED | without sharing | ✅ | ❌ | |
| * | |
| * ✅ Enforced ❌ Not enforced 🔄 Inherited from calling context | |
| * | |
| * Minimum Access User object access: | |
| * - HAS access to Task → *WhenHasAccessToObject methods | |
| * - Does NOT have access to Case → *WhenDoesntHaveAccessToObject methods | |
| * Organization-Wide Default: | |
| * - Case: Private | |
| * - Task (Activity): Private | |
| */ | |
| @IsTest | |
| private without sharing class FlsSharingTest { | |
| // ─── WITH SHARING ───────────────────────────────────────────────────────── | |
| @IsTest | |
| static void withSharingUserModeHasAccessToObject() { | |
| insertTasks(); | |
| System.runAs(minimumAccessUser()) { | |
| List<Task> tasks; | |
| Test.startTest(); | |
| tasks = new WithSharing().withUserModeWhenHasAccessToObject(); | |
| Test.stopTest(); | |
| Assert.areEqual(0, tasks.size(), 'WITH USER_MODE in with sharing class enforces sharing - records not visible.'); | |
| } | |
| } | |
| @IsTest | |
| static void withSharingUserModeDoesntHaveAccessToObject() { | |
| insertCases(); | |
| System.runAs(minimumAccessUser()) { | |
| Exception queryException; | |
| Test.startTest(); | |
| try { | |
| new WithSharing().withUserModeWhenDoesntHaveAccessToObject(); | |
| } catch (Exception e) { | |
| queryException = e; | |
| } | |
| Test.stopTest(); | |
| Assert.isNotNull(queryException, 'WITH USER_MODE should enforce OLS - querying inaccessible object must throw.'); | |
| Assert.isTrue(queryException.getMessage().contains('sObject type \'Case\' is not supported.'), 'The exception message should indicate that the object is not accessible.'); | |
| } | |
| } | |
| @IsTest | |
| static void withSharingSystemModeHasAccessToObject() { | |
| insertTasks(); | |
| System.runAs(minimumAccessUser()) { | |
| List<Task> tasks; | |
| Test.startTest(); | |
| tasks = new WithSharing().withSystemModeWhenHasAccessToObject(); | |
| Test.stopTest(); | |
| Assert.areEqual(0, tasks.size(), 'WITH SYSTEM_MODE in with sharing class enforces sharing - records not visible.'); | |
| } | |
| } | |
| @IsTest | |
| static void withSharingSystemModeDoesntHaveAccessToObject() { | |
| insertCases(); | |
| System.runAs(minimumAccessUser()) { | |
| List<Case> cases; | |
| Test.startTest(); | |
| cases = new WithSharing().withSystemModeWhenDoesntHaveAccessToObject(); | |
| Test.stopTest(); | |
| Assert.areEqual(0, cases.size(), 'WITH SYSTEM_MODE bypasses OLS - query succeeds but with sharing enforces record visibility.'); | |
| } | |
| } | |
| @IsTest | |
| static void withSharingSecurityEnforcedHasAccessToObject() { | |
| insertTasks(); | |
| System.runAs(minimumAccessUser()) { | |
| List<Task> tasks; | |
| Test.startTest(); | |
| tasks = new WithSharing().withSecurityEnforcedWhenHasAccessToObject(); | |
| Test.stopTest(); | |
| Assert.areEqual(0, tasks.size(), 'WITH SECURITY_ENFORCED in with sharing class enforces sharing - records not visible.'); | |
| } | |
| } | |
| @IsTest | |
| static void withSharingSecurityEnforcedDoesntHaveAccessToObject() { | |
| insertCases(); | |
| System.runAs(minimumAccessUser()) { | |
| Exception queryException; | |
| Test.startTest(); | |
| try { | |
| new WithSharing().withSecurityEnforcedWhenDoesntHaveAccessToObject(); | |
| } catch (Exception e) { | |
| queryException = e; | |
| } | |
| Test.stopTest(); | |
| Assert.isNotNull(queryException, 'WITH SECURITY_ENFORCED should enforce OLS - querying inaccessible object must throw.'); | |
| Assert.isTrue(queryException.getMessage().contains('Insufficient permissions: secure query included inaccessible field'), 'The exception message should indicate insufficient permissions.'); | |
| } | |
| } | |
| // ─── INHERITED SHARING ──────────────────────────────────────────────────── | |
| @IsTest | |
| static void inheritedSharingUserModeHasAccessToObject() { | |
| insertTasks(); | |
| System.runAs(minimumAccessUser()) { | |
| List<Task> tasks; | |
| Test.startTest(); | |
| tasks = new InheritedSharing().withUserModeWhenHasAccessToObject(); | |
| Test.stopTest(); | |
| Assert.areEqual(0, tasks.size(), 'WITH USER_MODE always enforces sharing regardless of class keyword.'); | |
| } | |
| } | |
| @IsTest | |
| static void inheritedSharingUserModeDoesntHaveAccessToObject() { | |
| insertCases(); | |
| System.runAs(minimumAccessUser()) { | |
| Exception queryException; | |
| Test.startTest(); | |
| try { | |
| new InheritedSharing().withUserModeWhenDoesntHaveAccessToObject(); | |
| } catch (Exception e) { | |
| queryException = e; | |
| } | |
| Test.stopTest(); | |
| Assert.isNotNull(queryException, 'WITH USER_MODE should enforce OLS - querying inaccessible object must throw.'); | |
| Assert.isTrue(queryException.getMessage().contains('sObject type \'Case\' is not supported.'), 'The exception message should indicate that the object is not accessible.'); | |
| } | |
| } | |
| @IsTest | |
| static void inheritedSharingSystemModeHasAccessToObject() { | |
| insertTasks(); | |
| System.runAs(minimumAccessUser()) { | |
| List<Task> tasks; | |
| Test.startTest(); | |
| tasks = new InheritedSharing().withSystemModeWhenHasAccessToObject(); | |
| Test.stopTest(); | |
| Assert.areEqual(0, tasks.size(), 'WITH SYSTEM_MODE in inherited sharing class inherits with sharing from calling context - records not visible.'); | |
| } | |
| } | |
| @IsTest | |
| static void inheritedSharingSystemModeDoesntHaveAccessToObject() { | |
| insertCases(); | |
| System.runAs(minimumAccessUser()) { | |
| List<Case> cases; | |
| Test.startTest(); | |
| cases = new InheritedSharing().withSystemModeWhenDoesntHaveAccessToObject(); | |
| Test.stopTest(); | |
| Assert.areEqual(2, cases.size(), 'WITH SYSTEM_MODE bypasses OLS but inherited sharing inherits with sharing from calling context - records not visible.'); | |
| } | |
| } | |
| @IsTest | |
| static void inheritedSharingSecurityEnforcedHasAccessToObject() { | |
| insertTasks(); | |
| System.runAs(minimumAccessUser()) { | |
| List<Task> tasks; | |
| Test.startTest(); | |
| tasks = new InheritedSharing().withSecurityEnforcedWhenHasAccessToObject(); | |
| Test.stopTest(); | |
| Assert.areEqual(0, tasks.size(), 'WITH SECURITY_ENFORCED in inherited sharing class inherits with sharing from calling context - records not visible.'); | |
| } | |
| } | |
| @IsTest | |
| static void inheritedSharingSecurityEnforcedDoesntHaveAccessToObject() { | |
| insertCases(); | |
| System.runAs(minimumAccessUser()) { | |
| Exception queryException; | |
| Test.startTest(); | |
| try { | |
| new InheritedSharing().withSecurityEnforcedWhenDoesntHaveAccessToObject(); | |
| } catch (Exception e) { | |
| queryException = e; | |
| } | |
| Test.stopTest(); | |
| Assert.isNotNull(queryException, 'WITH SECURITY_ENFORCED should enforce OLS - querying inaccessible object must throw.'); | |
| Assert.isTrue(queryException.getMessage().contains('Insufficient permissions: secure query included inaccessible field'), 'The exception message should indicate insufficient permissions.'); | |
| } | |
| } | |
| // ─── WITHOUT SHARING ────────────────────────────────────────────────────── | |
| @IsTest | |
| static void withoutSharingUserModeHasAccessToObject() { | |
| insertTasks(); | |
| System.runAs(minimumAccessUser()) { | |
| List<Task> tasks; | |
| Test.startTest(); | |
| tasks = new WithoutSharing().withUserModeWhenHasAccessToObject(); | |
| Test.stopTest(); | |
| Assert.areEqual(0, tasks.size(), 'WITH USER_MODE always enforces sharing even inside a without sharing class.'); | |
| } | |
| } | |
| @IsTest | |
| static void withoutSharingUserModeDoesntHaveAccessToObject() { | |
| insertCases(); | |
| System.runAs(minimumAccessUser()) { | |
| Exception queryException; | |
| Test.startTest(); | |
| try { | |
| new WithoutSharing().withUserModeWhenDoesntHaveAccessToObject(); | |
| } catch (Exception e) { | |
| queryException = e; | |
| } | |
| Test.stopTest(); | |
| Assert.isNotNull(queryException, 'WITH USER_MODE should enforce OLS even inside a without sharing class.'); | |
| Assert.isTrue(queryException.getMessage().contains('sObject type \'Case\' is not supported.'), 'The exception message should indicate that the object is not accessible.'); | |
| } | |
| } | |
| @IsTest | |
| static void withoutSharingSystemModeHasAccessToObject() { | |
| insertTasks(); | |
| System.runAs(minimumAccessUser()) { | |
| List<Task> tasks; | |
| Test.startTest(); | |
| tasks = new WithoutSharing().withSystemModeWhenHasAccessToObject(); | |
| Test.stopTest(); | |
| Assert.areEqual(2, tasks.size(), 'WITH SYSTEM_MODE in without sharing class does not enforce sharing - all records visible.'); | |
| } | |
| } | |
| @IsTest | |
| static void withoutSharingSystemModeDoesntHaveAccessToObject() { | |
| insertCases(); | |
| System.runAs(minimumAccessUser()) { | |
| List<Case> cases; | |
| Test.startTest(); | |
| cases = new WithoutSharing().withSystemModeWhenDoesntHaveAccessToObject(); | |
| Test.stopTest(); | |
| Assert.areEqual(2, cases.size(), 'WITH SYSTEM_MODE bypasses OLS and without sharing does not enforce sharing - all records visible.'); | |
| } | |
| } | |
| @IsTest | |
| static void withoutSharingSecurityEnforcedHasAccessToObject() { | |
| insertTasks(); | |
| System.runAs(minimumAccessUser()) { | |
| List<Task> tasks; | |
| Test.startTest(); | |
| tasks = new WithoutSharing().withSecurityEnforcedWhenHasAccessToObject(); | |
| Test.stopTest(); | |
| Assert.areEqual(2, tasks.size(), 'WITH SECURITY_ENFORCED in without sharing class does not enforce sharing - all records visible.'); | |
| } | |
| } | |
| @IsTest | |
| static void withoutSharingSecurityEnforcedDoesntHaveAccessToObject() { | |
| insertCases(); | |
| System.runAs(minimumAccessUser()) { | |
| Exception queryException; | |
| Test.startTest(); | |
| try { | |
| new WithoutSharing().withSecurityEnforcedWhenDoesntHaveAccessToObject(); | |
| } catch (Exception e) { | |
| queryException = e; | |
| } | |
| Test.stopTest(); | |
| Assert.isNotNull(queryException, 'WITH SECURITY_ENFORCED should enforce OLS even inside a without sharing class.'); | |
| Assert.isTrue(queryException.getMessage().contains('Insufficient permissions: secure query included inaccessible field'), 'The exception message should indicate insufficient permissions.'); | |
| } | |
| } | |
| // ─── INNER CLASSES ──────────────────────────────────────────────────────── | |
| private with sharing class WithSharing { | |
| public List<Task> withUserModeWhenHasAccessToObject() { | |
| return [SELECT Id FROM Task WITH USER_MODE]; | |
| } | |
| public List<Case> withUserModeWhenDoesntHaveAccessToObject() { | |
| return [SELECT Id, Status, Origin FROM Case WITH USER_MODE]; | |
| } | |
| public List<Task> withSystemModeWhenHasAccessToObject() { | |
| return [SELECT Id, Subject, Type FROM Task WITH SYSTEM_MODE]; | |
| } | |
| public List<Case> withSystemModeWhenDoesntHaveAccessToObject() { | |
| return [SELECT Id, Status, Origin FROM Case WITH SYSTEM_MODE]; | |
| } | |
| public List<Task> withSecurityEnforcedWhenHasAccessToObject() { | |
| return [SELECT Id FROM Task WITH SECURITY_ENFORCED]; | |
| } | |
| public List<Case> withSecurityEnforcedWhenDoesntHaveAccessToObject() { | |
| return [SELECT Id, Status, Origin FROM Case WITH SECURITY_ENFORCED]; | |
| } | |
| } | |
| private inherited sharing class InheritedSharing { | |
| public List<Task> withUserModeWhenHasAccessToObject() { | |
| return [SELECT Id FROM Task WITH USER_MODE]; | |
| } | |
| public List<Case> withUserModeWhenDoesntHaveAccessToObject() { | |
| return [SELECT Id, Status, Origin FROM Case WITH USER_MODE]; | |
| } | |
| public List<Task> withSystemModeWhenHasAccessToObject() { | |
| return [SELECT Id, Subject, Type FROM Task WITH SYSTEM_MODE]; | |
| } | |
| public List<Case> withSystemModeWhenDoesntHaveAccessToObject() { | |
| return [SELECT Id, Status, Origin FROM Case WITH SYSTEM_MODE]; | |
| } | |
| public List<Task> withSecurityEnforcedWhenHasAccessToObject() { | |
| return [SELECT Id FROM Task WITH SECURITY_ENFORCED]; | |
| } | |
| public List<Case> withSecurityEnforcedWhenDoesntHaveAccessToObject() { | |
| return [SELECT Id, Status, Origin FROM Case WITH SECURITY_ENFORCED]; | |
| } | |
| } | |
| private without sharing class WithoutSharing { | |
| public List<Task> withUserModeWhenHasAccessToObject() { | |
| return [SELECT Id FROM Task WITH USER_MODE]; | |
| } | |
| public List<Case> withUserModeWhenDoesntHaveAccessToObject() { | |
| return [SELECT Id, Status, Origin FROM Case WITH USER_MODE]; | |
| } | |
| public List<Task> withSystemModeWhenHasAccessToObject() { | |
| return [SELECT Id, Subject, Type FROM Task WITH SYSTEM_MODE]; | |
| } | |
| public List<Case> withSystemModeWhenDoesntHaveAccessToObject() { | |
| return [SELECT Id, Status, Origin FROM Case WITH SYSTEM_MODE]; | |
| } | |
| public List<Task> withSecurityEnforcedWhenHasAccessToObject() { | |
| return [SELECT Id FROM Task WITH SECURITY_ENFORCED]; | |
| } | |
| public List<Case> withSecurityEnforcedWhenDoesntHaveAccessToObject() { | |
| return [SELECT Id, Status, Origin FROM Case WITH SECURITY_ENFORCED]; | |
| } | |
| } | |
| // ─── HELPERS ────────────────────────────────────────────────────────────── | |
| static void insertTasks() { | |
| insert new List<Task>{ | |
| new Task(Subject = 'Test', Type = 'Other'), | |
| new Task(Subject = 'Test', Type = 'Other') | |
| }; | |
| } | |
| static void insertCases() { | |
| insert new List<Case>{ | |
| new Case(Status = 'New', Origin = 'Web'), | |
| new Case(Status = 'New', Origin = 'Web') | |
| }; | |
| } | |
| static User minimumAccessUser() { | |
| return new User( | |
| Alias = 'minUser', | |
| Email = 'flssharingtest@testorg.com', | |
| EmailEncodingKey = 'UTF-8', | |
| LastName = 'FlsSharingTest', | |
| LanguageLocaleKey = 'en_US', | |
| LocaleSidKey = 'en_US', | |
| Profile = new Profile(Name = 'Minimum Access - Salesforce'), | |
| TimeZoneSidKey = 'America/Los_Angeles', | |
| Username = 'flssharingtest@testorg.com' | |
| ); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment