Sunday, September 11, 2016

Apex Sharing - "with sharing" & "without sharing"

Introduction


I have come across many developers who use "with sharing" and "without sharing" without actually knowing what it really means. So if you are new to Apex, it is good to start using it right, right from day one. And if you are a veteran who just writes "with sharing" in all classes you create without knowing why, please do read further.

System vs User mode


To understand what the keywords "with sharing" and "without sharing" really mean, it is important to understand the difference between Apex code execution in system mode as opposed to the user mode.

In System mode, Apex code is executed with all sharing ignored. This means queries will retrieve data for which the current / running user has no access. Also in the system mode, other security features such as FLS (Field Level Security) and object permissions are ignored meaning users will see fields in retrieved records which are supposed to be hidden e.g. customer social security numbers, sales rep performance scores etc.

In the user mode, all sharing rules that apply to the current user, be it from role hierarchy, OWD (Org Wide Defaults), Sharing rules, Manual Sharing are applied. Field Level Security and object access permissions are also respected.

System mode is always the default when running Apex code. That is why we need the "with sharing" and "without sharing" mechanism to control access to data when executing Apex code.

With Sharing


This is what the documentation says about with sharing:

     "The with sharing keyword allows you to specify that the sharing rules for the current user be taken into account for a class."

Do you see how it fails to say anything about object permissions and Field Level Security (FLS). My assumption will be that, "with sharing" really only affects sharing and not object permissions and FLS. But to be sure, let's find out for ourselves in the section below, so keep reading.

Does with sharing respect Field Level Security?


In this small experiment, I have created an Object called Financial_Statement__c. This object has a  lookup to the account object Account__c. I have also created a user and given him CRUD ( create, read, update & delete ) rights to the Financial_Statement__c object but no access to the Account__c field. His profile looks like this;



Next I have the following Visualforce page and Controller Extension that create the Financial Statement records;

 public with sharing class FinancialStatementControllerExt {  
   public Financial_Statement__c financialStatement { get; set; }  
   public ApexPages.StandardController stdController { get; set; }  
   public FinancialStatementControllerExt(ApexPages.StandardController stdController){  
     this.financialStatement = (Financial_Statement__c)stdController.getRecord();  
     this.stdController = stdController;  
   }  
   public PageReference saveFinancialStatement(){  
     try {  
       if(this.financialStatement.Account__c == null)  
         this.financialStatement.Account__c = this.getDefaultAccountId();  
       insert this.financialStatement;  
       return new PageReference('/' + this.financialStatement.Id);  
     } catch(Exception e) {  
       ApexPages.addMessage(new ApexPages.Message(  
         ApexPages.Severity.Error,   
         'An error occurred while inserted a new financial statement record. Please contact your admin!')  
       );  
     }  
     return null;  
   }  
   public PageReference cancelOperation(){  
     return this.stdController.cancel();  
   }  
   private Id getDefaultAccountId(){  
     try {  
       return [SELECT Id FROM Account WHERE Name = 'Default Account' LIMIT 1].Id;  
     } catch (Exception e){  
       ApexPages.addMessage(new ApexPages.Message(  
         ApexPages.Severity.Error,   
         'An error occurred retrieving the default Account. Please contact your admin!')  
       );  
     }  
     return null;  
   }  
 }  



 <apex:page standardController="Financial_Statement__c"   
      extensions="FinancialStatementControllerExt" showHeader="true" sidebar="true">  
      <apex:form>  
           <apex:sectionHeader title="Create Financial Statement"   
                subtitle="New Financial Statement" rendered="{!Financial_Statement__c.Id == null}" />  
           <apex:sectionHeader title="Update Financial Statement"   
                subtitle="{!Financial_Statement__c.Name}" rendered="{!Financial_Statement__c.Id != null}" />  
           <apex:pageBlock title="Financial Statement">  
                <apex:pageBlockButtons location="top">  
                     <apex:commandButton value="Save" action="{!saveFinancialStatement}" />  
                     <apex:commandButton value="Cancel" action="{!cancelOperation}" />  
                </apex:pageBlockButtons>  
                <apex:pageBlockSection title="Enter a new Financial Statement">  
                     <apex:inputField value="{!Financial_Statement__c.Name}" />  
                     <apex:inputField value="{!Financial_Statement__c.Account__c}" />  
                </apex:pageBlockSection>  
           </apex:pageBlock>  
      </apex:form>  
 </apex:page>  


And here are the results;








You will notice that the results are the same. If I declared the class with "with sharing" or without  "with sharing", the results are the same, the Default account is attached to the Financial Statement even though the test user has no access to this field i.e. his access is blocked by FLS. This is confirmed by looking at the UI. The test user does not see the input field for the account field. If he had FLS for this field, the UI would look like this;



This is because Visualforce always enforces Sharing and FLS even if the APEX Controller doesn't.

So with can safely assume that "with sharing" only controls sharing of records as established by the different sharing models we have on the platform namely; OWDs (Organization-wide Defaults ), Role Hierarchy, Sharing Rules and Manual Sharing.

With Sharing & Controller Extensions


The other day a colleague asked me if he should also use "with sharing" in a controller extension. The reason he asked this question is simple, logic in the standard controller will always execute in the "user context" meaning sharing, FLS and object permissions will be respected. So if this is the case, if we extend the standard controller using an extension, do we still need the "with sharing" keyword?

The answer is YES. This is what the documentation says;

      If a controller extension extends a standard controller, the logic from the standard controller doesn’t execute in system mode. Instead, it executes in user mode, in which the permissions, field-level security, and sharing rules of the current user apply.

Notice it says logic in the standard controller doesn't execute in system mode. For example if you call the save() method of the standard controller, it will execute in user mode. Whereas if we had our own save method in the extension such as saveFinancialStatement() in the example above, it will respect whatever context we have chosen for the extension to run in, user context if we have used "with sharing" or system context if we have left it out or explicitly defined "without sharing".

Without Sharing


Without sharing simple means that Apex code will run in system mode i.e. no sharing rules will be respected. But this is the default right? meaning if we do not declare a class with "with sharing" or "without sharing", it will run in system mode. So why should we even bother declaring a class with "without sharing"?
Simply because a class can inherit "with sharing" from another class that calls it. We might however want the class that is called to still execute in system mode despite being called by a class that has "with sharing" and is executing in user mode.

Sharing Setting in Triggers


I haven't found this documented anywhere but it has been my experience, that triggers always run in System mode i.e. it doesn't matter if you use the "with sharing" keyword in the Trigger handler class or not.

To test this, I have created an a Trigger on Account, that creates a Financial Statement record every time an account is inserted. A financial statement was created every time, even when the test user had "no Access" on his profile for the Financial_Statement__c object. Here is my code snippet;

 trigger AccountTrigger on Account (after insert) {  
      if(Trigger.isAfter){  
           AccountTriggerHandler.createFinancialStatement(Trigger.new);  
      }  
 }  


 public with sharing class AccountTriggerHandler {  
      public static void createFinancialStatement(List<Account> accounts){  
           List<Financial_Statement__c> financialStatements = new List<Financial_Statement__c>();  
           for(Account acct : accounts){  
                financialStatements.add(new Financial_Statement__c(Name = acct.Name + '_FS', Account__c = acct.Id));  
           }  
           if(!financialStatements.isEmpty()){  
                try {  
                     insert financialStatements;  
                } catch(Exception e){  
                     accounts[0].addError('An error occurred while attaching a Financial Statement to an Account');  
                }  
           }  
      }  
 }  

This blogpost A Common Myth : Triggers “always” play in GOD Mode (System Context) ! explains situations in which an Apex Trigger will not run in System mode. I haven't tried to reproduce it but may be you can take it upon yourself to do this.

Gotchas


We have already mention two gotchas above, but i will repeat them here for the sake of completenes

  1. As with the case of the Standard Controller and controller extension, if a class runs in user mode ( has with sharing ), all methods of this class will execute in user mode even if they are called from a class running in system mode
  2. The second gotcha has to do with the importance of "without sharing" as described above. If a class has "with sharing" and calls a method of class which is not declared with either "with" or "without" sharing, that method will execute in the context of the calling class, i.e user context
  3. Inner classes do not inherit the context of the outer class
  4. When classes extend or implement other classes they inherit their sharing setting

Conclusion


The keywords "with sharing" and "without sharing" are very important because of the security and Data Access implications they have. So when designing your code, do not fail to give careful thought to these keywords and make sure that they are aligned with the general security and data access strategy of the whole org.

As usual it is always a pleasure sharing my knowledge with you. I appreciate you feedback, questions and critique. So do not be shy to leave a comment Thanks for reading.

No comments:

Post a Comment