Profiles, Permission Sets, Role Hierarchy & Sharing
📖 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
Sharerecords (e.g.,AccountShare,CustomObject__Share) - Set access level: Read, Edit, All
- Set row cause: 'Manual' or custom Apex sharing reason
💻 Code Example
1// Salesforce Security Model in Apex23public class SecurityModelExamples {45 // 1. with sharing vs without sharing6 public with sharing class SecureService {7 // This class respects the running user's record-level security8 public List<Account> getAccounts() {9 // Only returns accounts the user has access to10 return [SELECT Id, Name FROM Account LIMIT 100];11 }12 }1314 public without sharing class SystemService {15 // This class runs in system context — sees ALL records16 // Use carefully! Only when you need system-level access17 public Integer countAllAccounts() {18 return [SELECT COUNT() FROM Account];19 }20 }2122 // 2. Checking Field-Level Security before DML23 public static void updateWithFLS(Account acc, String fieldName, Object value) {24 Schema.DescribeFieldResult field = Schema.SObjectType.Account25 .fields.getMap().get(fieldName).getDescribe();2627 if (!field.isUpdateable()) {28 throw new SecurityException(29 'Insufficient access to field: ' + fieldName30 );31 }3233 acc.put(fieldName, value);34 update acc;35 }3637 // Security.stripInaccessible (Winter '20+) — preferred method38 public static List<Account> getAccountsSecure() {39 List<Account> accounts = [40 SELECT Id, Name, Phone, AnnualRevenue, Industry41 FROM Account LIMIT 10042 ];4344 // Strip fields the user doesn't have access to45 SObjectAccessDecision decision = Security.stripInaccessible(46 AccessType.READABLE, accounts47 );4849 return (List<Account>) decision.getRecords();50 // Fields the user can't read are automatically removed51 }5253 // 3. Apex Managed Sharing — programmatic record sharing54 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, All59 share.OpportunityAccessLevel = 'Read'; // Required for Account shares60 share.RowCause = Schema.AccountShare.RowCause.Manual;6162 Database.SaveResult sr = Database.insert(share, false);63 if (!sr.isSuccess()) {64 System.debug('Sharing failed: ' + sr.getErrors()[0].getMessage());65 }66 }6768 // 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__Share71 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 Reason7677 insert share;78 }7980 // 5. Checking permissions programmatically81 public static Map<String, Boolean> checkUserPermissions(String objectName) {82 Schema.DescribeSObjectResult objDescribe =83 Schema.getGlobalDescribe().get(objectName).getDescribe();8485 return new Map<String, Boolean>{86 'isAccessible' => objDescribe.isAccessible(),87 'isCreateable' => objDescribe.isCreateable(),88 'isUpdateable' => objDescribe.isUpdateable(),89 'isDeletable' => objDescribe.isDeletable()90 };91 }9293 // 6. Running code as a specific user (for testing)94 @isTest95 static void testWithLimitedUser() {96 // Create a user with limited permissions97 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;113114 // Run test as the limited user115 System.runAs(limitedUser) {116 try {117 List<Account> accounts = [SELECT Id, Name FROM Account];118 // Should only see records shared with this user119 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:
- Set the OWD for Account to Private and observe how visibility changes for different users
- Create a Role Hierarchy with 4 levels and test record visibility at each level
- Create a Criteria-Based Sharing Rule that shares escalated Cases with the Management group
- Write Apex Managed Sharing to programmatically share records based on custom criteria
- Create a Permission Set that grants access to a sensitive field and assign it to a user
- Use Security.stripInaccessible() to safely return data respecting FLS
- Write test classes using System.runAs() to verify security at different access levels
- Design a security model for a healthcare org (HIPAA — patients can only see their own records)
- Document the complete security model for your org: OWD, Roles, Sharing Rules, Permission Sets
- 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.