Profiles, Permission Sets, Role Hierarchy & Sharing

0/4 in this phase0/41 across the roadmap

📖 Concept

The Salesforce security model is one of the most critical topics for interviews and enterprise architecture. It operates on a principle of least privilege — start with the most restrictive access and open up selectively.

The security layer stack (evaluated from top to bottom):

1. Organization-Wide Defaults (OWD)     ← Most restrictive baseline
2. Role Hierarchy                        ← Opens access upward  
3. Sharing Rules                         ← Opens access laterally
4. Manual Sharing                        ← One-off record sharing
5. Apex Managed Sharing                  ← Programmatic sharing
6. Teams                                ← Opportunity/Account teams
7. Territory Management                  ← Geographic/account-based

Organization-Wide Defaults (OWD): Sets the baseline access level for each object across the entire org:

  • Private — Users can only see their own records (most restrictive)
  • Public Read Only — All users can see all records but only edit their own
  • Public Read/Write — All users can see and edit all records
  • Controlled by Parent — Child object inherits parent's sharing (Master-Detail only)

Rule: Always set OWD to the most restrictive level needed, then use sharing rules to open access.

Profiles: Define what a user can do — the baseline permissions:

  • Object permissions (CRUD — Create, Read, Update, Delete)
  • Field-Level Security (which fields are visible/editable)
  • App access, tab visibility
  • Login hours, IP restrictions
  • Page Layout assignments
  • Record Type access

Permission Sets & Permission Set Groups: Add additional permissions on top of Profiles:

  • Grant extra object/field access without changing the Profile
  • Stackable — a user can have multiple Permission Sets
  • Permission Set Groups — bundle Permission Sets for easy assignment
  • Best practice: Use minimal Profiles + Permission Sets for flexibility

Role Hierarchy: Controls record-level access based on organizational structure:

  • Users higher in the hierarchy can see records owned by users below them (if OWD is Private)
  • Does NOT control object-level permissions (that's Profiles)
  • Mirrors the org chart (CEO → VP → Manager → Rep)

Sharing Rules: Open access laterally across the hierarchy:

  • Criteria-Based — Share records matching criteria (e.g., share all Cases with Status = 'Escalated')
  • Owner-Based — Share records owned by one role/group with another
  • Share with public groups, roles, or roles and subordinates

When sharing rules aren't enough — Apex Managed Sharing: For complex sharing logic that can't be expressed declaratively, use programmatic sharing via Apex:

  • Insert Share records (e.g., AccountShare, CustomObject__Share)
  • Set access level: Read, Edit, All
  • Set row cause: 'Manual' or custom Apex sharing reason

💻 Code Example

codeTap to expand ⛶
1// Salesforce Security Model in Apex
2
3public class SecurityModelExamples {
4
5 // 1. with sharing vs without sharing
6 public with sharing class SecureService {
7 // This class respects the running user's record-level security
8 public List<Account> getAccounts() {
9 // Only returns accounts the user has access to
10 return [SELECT Id, Name FROM Account LIMIT 100];
11 }
12 }
13
14 public without sharing class SystemService {
15 // This class runs in system context — sees ALL records
16 // Use carefully! Only when you need system-level access
17 public Integer countAllAccounts() {
18 return [SELECT COUNT() FROM Account];
19 }
20 }
21
22 // 2. Checking Field-Level Security before DML
23 public static void updateWithFLS(Account acc, String fieldName, Object value) {
24 Schema.DescribeFieldResult field = Schema.SObjectType.Account
25 .fields.getMap().get(fieldName).getDescribe();
26
27 if (!field.isUpdateable()) {
28 throw new SecurityException(
29 'Insufficient access to field: ' + fieldName
30 );
31 }
32
33 acc.put(fieldName, value);
34 update acc;
35 }
36
37 // Security.stripInaccessible (Winter '20+) — preferred method
38 public static List<Account> getAccountsSecure() {
39 List<Account> accounts = [
40 SELECT Id, Name, Phone, AnnualRevenue, Industry
41 FROM Account LIMIT 100
42 ];
43
44 // Strip fields the user doesn't have access to
45 SObjectAccessDecision decision = Security.stripInaccessible(
46 AccessType.READABLE, accounts
47 );
48
49 return (List<Account>) decision.getRecords();
50 // Fields the user can't read are automatically removed
51 }
52
53 // 3. Apex Managed Sharing — programmatic record sharing
54 public static void shareAccountWithUser(Id accountId, Id userId) {
55 AccountShare share = new AccountShare();
56 share.AccountId = accountId;
57 share.UserOrGroupId = userId;
58 share.AccountAccessLevel = 'Edit'; // Read, Edit, All
59 share.OpportunityAccessLevel = 'Read'; // Required for Account shares
60 share.RowCause = Schema.AccountShare.RowCause.Manual;
61
62 Database.SaveResult sr = Database.insert(share, false);
63 if (!sr.isSuccess()) {
64 System.debug('Sharing failed: ' + sr.getErrors()[0].getMessage());
65 }
66 }
67
68 // 4. Custom object sharing (Apex Sharing Reasons)
69 public static void shareCustomRecord(Id recordId, Id groupId) {
70 // For custom objects, the share object is ObjectName__Share
71 Project__Share share = new Project__Share();
72 share.ParentId = recordId;
73 share.UserOrGroupId = groupId;
74 share.AccessLevel = 'Edit';
75 share.RowCause = 'Team_Access__c'; // Custom Apex Sharing Reason
76
77 insert share;
78 }
79
80 // 5. Checking permissions programmatically
81 public static Map<String, Boolean> checkUserPermissions(String objectName) {
82 Schema.DescribeSObjectResult objDescribe =
83 Schema.getGlobalDescribe().get(objectName).getDescribe();
84
85 return new Map<String, Boolean>{
86 'isAccessible' => objDescribe.isAccessible(),
87 'isCreateable' => objDescribe.isCreateable(),
88 'isUpdateable' => objDescribe.isUpdateable(),
89 'isDeletable' => objDescribe.isDeletable()
90 };
91 }
92
93 // 6. Running code as a specific user (for testing)
94 @isTest
95 static void testWithLimitedUser() {
96 // Create a user with limited permissions
97 Profile limitedProfile = [
98 SELECT Id FROM Profile WHERE Name = 'Standard User'
99 ];
100 User limitedUser = new User(
101 FirstName = 'Test',
102 LastName = 'User',
103 Email = 'test@example.com',
104 Username = 'test@example.com.sandbox',
105 ProfileId = limitedProfile.Id,
106 Alias = 'tuser',
107 TimeZoneSidKey = 'America/Los_Angeles',
108 LocaleSidKey = 'en_US',
109 EmailEncodingKey = 'UTF-8',
110 LanguageLocaleKey = 'en_US'
111 );
112 insert limitedUser;
113
114 // Run test as the limited user
115 System.runAs(limitedUser) {
116 try {
117 List<Account> accounts = [SELECT Id, Name FROM Account];
118 // Should only see records shared with this user
119 System.assert(true, 'Query executed successfully');
120 } catch (Exception e) {
121 System.assert(false, 'User should be able to query accounts');
122 }
123 }
124 }
125}

🏋️ Practice Exercise

Security Model Exercises:

  1. Set the OWD for Account to Private and observe how visibility changes for different users
  2. Create a Role Hierarchy with 4 levels and test record visibility at each level
  3. Create a Criteria-Based Sharing Rule that shares escalated Cases with the Management group
  4. Write Apex Managed Sharing to programmatically share records based on custom criteria
  5. Create a Permission Set that grants access to a sensitive field and assign it to a user
  6. Use Security.stripInaccessible() to safely return data respecting FLS
  7. Write test classes using System.runAs() to verify security at different access levels
  8. Design a security model for a healthcare org (HIPAA — patients can only see their own records)
  9. Document the complete security model for your org: OWD, Roles, Sharing Rules, Permission Sets
  10. Create an Apex class that audits which users have access to a specific record and why

⚠️ Common Mistakes

  • Setting OWD to Public Read/Write for all objects — this is the most common security mistake. Start with Private and open up selectively

  • Confusing Profile permissions (CRUD) with record-level security (OWD + Sharing) — a user can have Read permission on Account but still not see specific records if OWD is Private

  • Not testing security with System.runAs() — your code works as an admin but fails for standard users because of FLS or sharing restrictions

  • Using 'without sharing' when not necessary — this bypasses ALL record-level security and should only be used for system-level operations

  • Forgetting that Apex runs in system context by default — without 'with sharing' keyword, Apex ignores record-level security, which is a security vulnerability

💼 Interview Questions

🎤 Mock Interview

Mock interview is powered by AI for Profiles, Permission Sets, Role Hierarchy & Sharing. Login to unlock this feature.