This time I want to talk a bit about CRUD & FLS in Salesforce. What do these acronyms mean? Well… it is the way that we have of allowing or restricting who can create, view, modify or delete objects and fields on the platform.
CRUD – Create / Read / Update / Delete
FLS – Field Level Security (visible, editable, hidden)
We can setup CRUD and FLS through Profiles and/or Permission Sets. This means if I want to restrict a user from seeing Account objects, I will need to assign to him a profile in which Account object does not have R (Read) permission (CRUD). Or for example, if I want the same user to be able of viewing Account records but with its Annual Revenue field hidden, I will need to assign to him a profile in which Account has R (Read) permission (CRUD), but AnnualRevenue field is hidden (FLS).
Permission Sets provide a more flexible way of configuring user permissions, as a user can have multiple Permission Sets assigned. CRUD and FLS can be set also at Permission Set level, but remember that Permission Sets always grant additional permissions. If you are going to use Permission Sets to set CRUD and FLS, you may need a base profile that restricts all CRUD / FLS permissions, in order your Permission Sets gradually grant the needed permissions.
Said this, let’s see what a developer should take into account in respect to CRUD and FLS. I have setup an org in which I have created a CRUD Test profile that has the next permissions:
- Accounts – No access
- Leads – R (Read)
- Contacts – R (Read)
Additionally, I have set all fields for those three objects as readable, except Contact Title field, which I have set as hidden (not readable, not editable) in the test profile (FLS). Actually, no matter which access Account fields would have, as we don’t have access for Account object at all!
Also I have a System Administrator profile, in which I have CRUD access for Accounts, Leads and Contacts, and any field restricted by FLS.
Then I have created two Visualforce pages for illustrating what happens with CRUD / FLS in two different situations.
The first Visualforce page has a controller in which we retrieve an Account, a Contact and a Lead, and try to show some of their fields on the page.
<apex:page controller="CRUDTestController"> <apex:form > <apex:pageBlock title="My Content" mode="edit"> <apex:pageBlockSection title="My Content Section" columns="2"> <apex:outputField value="{!myAccount.Name}" label="Account Name:"/> <apex:outputField value="{!myContact.Name}" label="Contact Name:"/> <apex:outputField value="{!myContact.Title}" label="Contact Title:"/> <apex:outputField value="{!myLead.Name}" label="Lead Name:"/> apex:pageBlockSection> apex:pageBlock> apex:form> apex:page> public class CRUDTestController { public Account myAccount {get; set;} public Contact myContact {get; set;} public Lead myLead {get; set;} public CRUDTestController() { myAccount = [SELECT Name FROM Account LIMIT 1]; myContact = [SELECT Name, Title FROM Contact LIMIT 1]; myLead = [SELECT Name FROM Lead LIMIT 1]; } }
If I login as a System Administrator, as I have the permissions to see everything related to the objects I am showing in the page, I see this:
But, if I login as a user that has my CRUD Test profile assigned, in which permissions have been restricted, this is what I can see in my page:
When the information arrives to the Visualforce page, it arrives in a form of an sObject, so, the Visualforce page is smart enough to understand that it has to enforce CRUD & FLS.
I cannot see any Account information, as I don’t have access to it. For Contacts and Leads I have read access at object level, but I can’t see Contact Title, because I have restricted it with FLS.
We see here that CRUD & FLS are correctly enforced in Visualforce as long as we pass an sObject to the page. The same would have applied if:
- My property is a List, or another collection, whose elements I can show through an apex:repeat.
- I pass the information to the Visualforce page using an AccountDomain class that wrapped the sObject (as long as I am able of accessing the account sObject that is wrapped directly eg: myAccount.mySObject.AnnualRevenue).
- I am not using and apex:outputField tag, and instead I am showing my records fields using html tags.
I wanted to clarify these situations because there is usually some confusion about this.
Then, when does the problem appears? Well, Apex doesn’t respect CRUD & FLS. It is this way be default because usually most of the processes that a developer has to implement in Apex have to surpass CRUD & FLS. It is left to the developer the responsibility of managing CRUD & FLS in Apex, this is, we will be in charge of deciding if our code has to respect CRUD & FLS or has not, depending on the use case.
Then, if we create a situation in which we are sending information to a Visualforce page without passing in an sObject, collection of sObjects or an sObject wrapper, we will be in trouble, as the page won’t be smart enough to know what permissions apply. If we change the previous Visualforce page and controller to be the next ones:
<apex:page controller="CRUDTestController3"> <apex:form > <apex:pageBlock title="My Content" mode="edit"> <apex:pageBlockSection title="My Content Section" columns="2"> <apex:outputText value="{!myAccountName}" label="Account Name:"/> <apex:outputText value="{!myContactName}" label="Contact Name:"/> <apex:outputText value="{!myContactTitle}" label="Contact Title:"/> <apex:outputText value="{!myLeadName}" label="Lead Name:"/> apex:pageBlockSection> apex:pageBlock> apex:form> apex:page>
public class CRUDTestController3 { public String myAccountName {get; set;} public String myContactName {get; set;} public String myContactTitle {get; set;} public String myLeadName {get; set;} public CRUDTestController3() { myAccountName = [SELECT Name FROM Account LIMIT 1].Name; Contact myContact = [SELECT Name, Title FROM Contact LIMIT 1]; myContactName = myContact.Name; myContactTitle = myContact.Title; myLeadName = [SELECT Name FROM Lead LIMIT 1].Name; } }
And we login as the user that has assigned my test profile, in which permissions were restricted:
We see that CRUD & FLS are not automatically enforced! How can we fix our controller to enforce CRUD & FLS?
public class CRUDTestController3 { public String myAccountName {get; set;} public String myContactName {get; set;} public String myContactTitle {get; set;} public String myLeadName {get; set;} public CRUDTestController3() { if (Schema.sObjectType.Account.fields.Name.isAccessible()) myAccountName = [SELECT Name FROM Account LIMIT 1].Name; Contact myContact = [SELECT Name, Title FROM Contact LIMIT 1]; if (Schema.sObjectType.Contact.fields.Name.isAccessible()) myContactName = myContact.Name; if (Schema.sObjectType.Contact.fields.Title.isAccessible()) myContactTitle = myContact.Title; if (Schema.sObjectType.Lead.fields.Name.isAccessible()) myLeadName = [SELECT Name FROM Lead LIMIT 1].Name; } }
Now, if we log as the user with restricted permissions:
Note that for Account object, I had setup the full object with no access (CRUD), despite their fields were set as readable (FLS). In this case, checking CRUD or checking FLS will return no access, as CRUD is a more restrictive layer than FLS. We can define this relationship imagining a CRUD and an FLS layer that wrap the sObject. FLS layer only would have effect if the outer CRUD layer is surpassed.
We have seen in these examples how objects/fields readability can be set through profiles/permission sets and how Apex and Visualforce will behave. But, what about the other permissions?
- U – Updatability:
- In the same way that our Visualforce page is smart enough to know that it must not show a non-readable field (as long as it knows that we are passing an sObject!), it will be smart enough to show an input field as editable or not editable depending on its CRUD/FLS.
- In the same way that we had to manually enforce readability when the Visualforce page did not directly receive an sObject, we will have to enforce updateability to decide whether the field should be shown as editable checking
Schema.sObjectType.Account.fields.Name.isUpdateable()
- Or, we can decide to defer the check until the point in which we do an update in Apex, because for example, we don’t want to restrict field editability at UI level, doing the same check.
- C – Creatability:
- When having an entry point in Apex in which a user can insert records, for example a controller method, we will need to check:
Schema.sObjectType.Account.isCreateable()
- When having an entry point in Apex in which a user can insert records, for example a controller method, we will need to check:
- D – Deletability:
- In the same way, when having an entry point in Apex in which a user can delete records, we will need to check:
Schema.sObjectType.Account.isDeletable()
- In the same way, when having an entry point in Apex in which a user can delete records, we will need to check:
In this post I have talked mainly about CRUD & FLS enforcement in Visualforce pages, BUT, do not forget that our global methods or webservices (REST or SOAP) have to enforce CRUD & FLS as well, as Apex by default does not do it for us! We can use the same methods that we have seen in the post for checking CRUD & FLS and enforcing that the all the entry points of our applications behave correctly in respect to these permissions.
A last quick tip that I want to add here:
- SOQL executed through REST API enforces CRUD & FLS by default. This means if you work with tools that use the REST API for querying Salesforce, and you don’t have access to certain fields, they won’t be available. This applies to Developer Console Query Editor or other tools that follow the same principle.
- Same applies to Tooling API: if we call Execute Anonymous on Developer Console, CRUD&FLS will be enforced by default.
I have one doubt instead of checking whether user has field access or not in the class, wouldn’t the FLS get applied if you the class is with sharing
LikeLike
No. With sharing enforces just record security. You have to specifically check for crud & fls in the class, always, to enforce object and field level security. That is a common mistake! Thanks for asking.
LikeLiked by 1 person