Force.com object and record level security
Lately I've been involved in several discussions around how the Force.com platform handles object and record level security. I'm surprised that there's still a lot of confusion around this topic despite all of documentation available out there so I'll try to explain this topic in more detail. It
usually helps to start the conversation referring to Jason Ouellete’s
“Development with the Force.com Platform” book where he illustrates these
layers of security as a funnel.
Each request has to go through several layers starting with
CRUD and FLS checks and then moving to verifying org-wide default sharing model
and any exceptions to org-wide sharing model if applicable. If the request
“survives” through all of these checks then access is granted.
This funnel provides a great way to illustrate how data
security works at a high level but it still doesn't explain all the
nuts and bolts involved with sharing rules and how to enforce these levels of security when using Apex.
We have different layers when dealing with data security in
Force.com. Every time there’s a request, access is determined by analyzing two
levels of security: object-level and record-level.
Object-level security
Object-level security is defined by permissions in the
profile or permission set(s) and includes the following:
- CRUD
+ Create, Read, Update, Delete
permissions on the object
- Field Level Security (FLS)
+ Read and
Edit permissions for specific fields on the object
* If “View All” is selected, user can view all records
associated with this object, regardless of record-level security (see next
section).
* If “Modify All” is
selected, users can read, edit, delete, transfer, and approve all records
associated with this object, regardless of record-level security (see next
section).
Record-level security
Once we get “clearance” from the object-level security layer,
the next step is to check for record-level access. Record-level access is
determined by:
- Full ownership
If the user that submitted the request is the owner of the
record then he/she has full ownership and therefore it’s possible to perform
read and write operations on the record. If the user doesn’t have full
ownership then the next step is to look at the org-wide default sharing model.
- Org-wide sharing default (base sharing model for the object)
+ Public Read/Write
If the sharing model is set to “Public Read/Write” then any
user will have access to do Read/Write operations on any record (assuming
CRUD/FLS permissions provide the right level of access)
If the sharing model is “Public read” then only read
operations can be done on all records unless additional “sharing rules”
(exceptions to the sharing model) are
configured (see sharing rules section).
If the sharing mode is set to “Private” then a user can only
do read/write operations on records where he/she is the owner unless additional
“sharing rules” (exceptions to the sharing model) are configured (see sharing
rules section).
If a record is the detail of a master-detail relationship
then the access-level to this record is determined by the parent. The platform
provides an additional setting while defining a master-detail relationship
where we can specify the minimum level of visibility on the parent record in
order to be able to edit detail records. This setting is very important as the
default value is “Read/Write” so if you’re expecting other users will normally
get “Read-Only” on the parent object and you need to give access to detail
records, then you should also set the sharing setting in the master-detail
field to “Read Only” to make this consistent.
If the object is a “junction” object, meaning it has two
master detail relationships, then we must validate the sharing setting on both
relationships.
- Inherited access
If “Grant Access Using Hierarchies” is selected while defining the
org-wide sharing model then users higher
in the hierarchy will have the selected base level of access to the data owned
by or shared with their subordinates in the hierarchy. "Grant Access Using Hierarchies" is always selected on standard objects
(e.g. Account, Opportunity)
- Sharing rules (exceptions to org-wide sharing model)
Sharing rules provide exceptions to the org-wide sharing
model and they can never be more restrictive than the level of access defined
by the org-wide sharing model.
Objects with an org-wide sharing model different from “Public
Read/Write” have a separate object where sharing is maintained. For custom
objects, the name of this sharing object is followed by the suffix “__Share”
(e.g. CustomObject__Share) and for standard objects it’s just followed by
“Share” (e.g. ContactShare). This object stores records that represent
visibility and level of access to specific users or group of users.
Each record in the share object stores the target record Id
(ParentRecordId), the User or Group Id (UserOrGroupId) that has access to the
target record, the level of access to the record (AccessLevel) and a sharing
reason (RowCause).
To learn more about how access is granted to records and all
the tables involved with access grant you can read the following document:
There are 3 types methods for creating sharing rules and each
provides its own reasons (row cause) :
- Force.com managed Sharing
Reasons / Row Causes:
+ Owner
User is the owner of the record or
is in a UserRole above the record owner in the role hierarchy.
+ ImplicitChild (Account Sharing)
Indicates that the record has been
granted access because the Account related to the record has been shared to the
specified User or Group Id. Since the parent was shared, access to child
records has been granted with an “ImplicitChild” reason
+ ImplicitParent (Associated record owner or sharing)
Indicates that the User or Group
specified in the sharing record has separate access to an Opportunity or Case
associated with this Account, and so they are automatically given access to the
Account.
+ Team (Opportunity Sales Team, Account Sales Team)
Indicates that the user has access
to the record because he/she is part of the sales team for this record.
+ TerritoryRule (Territory Assignment Rule)
Indicates that a territory
assignment rule definition granted access for this record to the specified
Group.
For example: An account assignment
rule that assigns accounts to territories based on account fields. Only
available if territory management has been enabled for your organization.
+ Rule (Owner or Criteria based sharing Rule)
Indicates that the sharing was
created as part of an owner or criteria based sharing rule definition.
Owner
based sharing rules are used
when we have a well defined user or group of users that should always share records
with another user or groups of user.
Criteria-based
sharing rules are used when we have more dynamic sharing requirements, for
example based on certain field values instead of record ownership.
Reasons / Row Causes:
+ Manual (Manual Sharing)
Manual sharing also provides a very
dynamic way of sharing but it involves user interaction. With manual sharing,
the record owner (or a user with “Modify All” permissions) can share the record
with a specific user, public group, or role.
* It’s
important to know that manual
sharing is removed when the record owner changes or when the access granted in
the sharing does not grant additional access beyond the object's base
organization-wide sharing model. If you need to maintain the existing
sharing then you should look at the Apex Managed sharing.
+ TerritoryManual (Territory Manual Sharing)
Indicates that the record was manually assigned to a Territory.
- Apex-managed Sharing
Apex-managed sharing is a type of
"Programmatic Sharing" which allows you to define sharing records in
the previously mentioned sharing objects. It also allows us to create a custom
sharing reason (defined in the object’s detail page) to associate with your
programmatic share.
Apex-managed sharing can be used
for both standard and custom objects; however, custom sharing reasons can only
be defined for shares written to custom objects. One additional access level
developers can define with Apex-managed sharing is “Full Access”, which gives
view, edit, transfer, share and delete permissions.
Here’s a summary of access levels that can be defined through
Force.com, User and Apex-managed sharing:
Access
Level
|
API
Name
|
Description
|
Private
|
None
|
Only the record owner and users above the record
owner in the role hierarchy can view and edit the record. This access level
only applies to the AccountShare object.
|
Read Only
|
Read
|
The specified user or group can view the record
only.
|
Read/Write
|
Edit
|
The specified user or group can view and edit the
record.
|
Full Access
|
All
|
The specified user or group can view, edit,
transfer, share, and delete the record.
(This access level can only be granted with
Force.com managed sharing.)
|
Additional Considerations
High Volume Portal Users
High Volume Portal users (HVPU) have special considerations
when it comes down to the sharing model.
These type of users don’t have roles and they can’t be
included in any of the following:
●
Personal groups or public groups.
●
Sharing rules.
●
Account teams, opportunity teams, or case teams.
HVPU can only access records if any of the following criteria
is met:
●
They have “Update” access on the Account that they
belong to.
●
They own the record.
●
They can access a record's parent, and the
organization-wide sharing setting for that record is Controlled by Parent.
●
The organization-wide sharing setting for the object
is Public Read Only or Public Read/Write.
●
The record is the account or contact under which they
are enabled.
Additionally, access can be granted to objects through configuration of a sharing sets under
“Customer Portal” settings as long as:
●
Objects has an organization-wide sharing setting
different from Public Read/Write.
●
Objects is available for Customer Portal.
●
Custom object has a lookup field to account or
contact.
Apex
How these layers of data security are handled by Apex is
another big source of confusion. We first need to understand two concepts here
: “User Context” and “Security Context”.
User Context refers to
the user that invoked the transaction and Apex is always aware of this user.
For example, if User A inserted a record and this fired the execution of some
Apex code in a trigger or class then the user context for this event will be
User A and that won’t change during the whole transaction.
Security Context defines the
level of permission when executing Apex Code. There are two types of security
contexts: “with sharing” or “without sharing”.
It’s important to know that regardless of the context, CRUD and FLS are
not enforced automatically so we’ll need to add checks in the code to validate
this.
- “Without Sharing” context (system context)
Without sharing context a.k.a. “system” context is set when
“without sharing” keywords are used in the class definition or when no keywords
are specified at all. If this security context is used, then record access for
the invoking user context is not taken into account. In other words, Apex can
do whatever it wants.
- “With Sharing” context (running user context)
If we use “with sharing” a.k.a. “running user” context then
the code will enforce all sharing rules for the invoking user. For developers
that think that Apex runs in some sort of “GOD Mode” this could cause
unexpected behavior such as:
●
SOQL/SOSL Queries returning a limited set of records
●
Code trying to insert/update/delete a record throws
“Insufficient Privileges“ exception.
●
Code trying to set the value of a reference field
(lookup or master-detail) throws an “Insufficient Privileges“ exception.
Going back to the funnel diagram, interactions with the
Salesforce UI or standard Web Service API calls will automatically enforce the
first layer of security (object-level). However, if you build a custom apex
controller/extension or a custom web service then you need to manually enforce
CRUD/FLS permissions and record access validations, otherwise any user with
access to this class can perform CRUD operations on records that their profile
normally wouldn’t allow. The following points describe how we can enforce CRUD,
FLS and Record access.
To enforce CRUD and FLS in apex, the platform provides
functionality to “describe” the object metadata.
For example:
To verify if the user has access to update the “Name” field
in contact we can use:
Schema.sObjectType.Contact.fields.Name.isUpdateable()
To validate the user has access to delete records for the
Lead object we could use:
Lead.sObjectType.getDescribe().isDeletable()
This can even be validated in Visualforce by using:
<apex:commandButton
action="{!CustomDelete}" rendered="{!$ObjectType.Contact.Deletable}"
/>
You can learn more about describe calls here:
It’s also recommend adding the “Force.com ESAPI” library to
your codebase. It has a lot of handy methods to validate CRUD and FLS.
- Record-level security enforcement
What about record-level security in Apex? Enforcing this layer of
security in Apex is easy, you just need to use the “with sharing” keywords in
your class. By doing this, Apex will enforce all the rules related to
record-level security mentioned previously.
Here are some examples of how enforcing sharing rules could
impact your Apex classes:
●
SOQL/SOSL will only return records the user is allowed
to see , so queries may return less records.
●
DML operations may fail since write operations on
existing records will be validated to see if the current user has “Write”
access according to the record-level sharing rules. This could happen if the
user tries to set an Id in a lookup or master-detail field to which he/she has
no access or if the access level on the master record is not aligned with the
sharing settings on the master-detail field definition.
●
If a record already has an existing value for a lookup
field and it references an object that the user doesn’t have access to, then any
DML operation will execute fine as long as the value for this lookup field is
not being changed.
For more information on “with sharing” and “without sharing”
keywords please read the following article:
When to bypass CRUD/FLS enforcement
●
Interacting with objects that are completely hidden
the user
○
An example of this is a custom object that simulates a
“custom setting” and you don’t want to expose this object anywhere in the
Salesforce UI (Tabs, Reports, Detail view, etc...)
●
Updating fields that are visible to users of Profile B
when User from Profile A perform an action
○
Normally this should be done through triggers as
triggers should be able to execute operations that the user is normally not
allowed to do. However, if this action can’t be done through a trigger then you
should validate some context of the user and decide if you should bypass FLS
for the current user.
When to use “without sharing”
We should always try to enforce sharing in our Apex classes,
however there may be some situations where “without sharing” is needed. We need
to carefully analyze these situations and make sure we understand all the risks
involved when operating “without sharing”. Also, most of the class logic should
run using “with sharing” and use some secure logic to decide if we should
execute specific logic that runs in “without sharing” context based on the user
context. ESAPI library provides an easy way to set the sharing enforcement
context which is very helpful for these situations.
Here are some examples where using “without sharing” makes
sense:
●
Sharing model limitations
○
If you’re using High Volume Portal users and trying to
operate on a custom object that has a sharing model different from “Public
Read/Write” and the object has no lookup fields to account or contact then you
may have to use “without sharing”. A way of mitigating this is by adding the
appropriate lookup fields to create the right sharing set configuration however
sometimes this means a major change in the data model and things could get even
more complicated if you’re trying to add this logic for objects that are part
of managed package.
●
Users needs to execute an action to operate on all
object records
○
This scenario is normally a cause of debate since
operations on all records should ideally run in batch contexts and this could
be scheduled or manually executed by a system administrator which normally has
“view all and modify all” permissions. If this really can’t be part of your
process, then you should consider creating sharing rules to solve the
visibility problem. If none of these options solves your problem then you may
have a valid case to run “without sharing”.
●
Interacting with a custom object that simulates a
“custom setting”
○
There may be some cases where you have a custom object
used to store custom settings. In this scenario, always consider what type of
data is stored in this object. If the data contains secrets or “trusted”
configuration data then you should be very careful and consider saving these
values inside a protected custom setting in a managed package.
○
For more information on storing secrets in custom
settings read the following article : http://wiki.developerforce.com/page/Secure_Coding_Storing_Secrets#Apex_and_Visualforce_Applications
○
If the data is not sensitive you can explore the option of moving this to
a protected custom setting.
○
If the custom setting option is not possible and it’s
OK to expose this data to everyone then you can try setting the sharing model
to “Public Read Only”.
○
If “Public Read Only” is not acceptable then you
should set the sharing model for this object to “Private” and remove all CRUD
access for your users. You can still
explore using “sharing rules” to enforce sharing and just bypass CRUD/FLS
validations in the controller but if this is not possible then you’ll need to
use “without sharing” to make sure the controller has access to all the
records.
How to identify potential object or record level security issues
●
A good way to verify if record-level access is being
enforced is to login as one of your end users to workbench (https://workbench.developerforce.com/login.php) and see
what objects this user has access to (if your users don’t have "API
Enabled" you'll need to temporarily enable this permission). Once you're
logged into workbench take a look at all the objects exposed to this user and
then run some SOQL queries on the
objects just to get the count of records. Doing these steps can give you clues
to determine if you have any data security issues.
●
Search across your project for "without
sharing" keywords. You can use the Force.com IDE to import your metadata
to eclipse and then search across the project for any classes that contain
"without sharing". If you get any results, verify if it's really
necessary use "without sharing" in any of the classes/triggers that
matched your search.
●
Search across your project for SOQL, SOSL and DML
clues and verify if CRUD and FLS is enforced. For example, searching for the
following keywords will give you an idea on CRUD/FLS checks:
○
"SaveResult"
○
"Database.insert",
"Database.update", "Database.delete"
○
"insert " , "update " ,
"delete "
○
"Database.query", “search.query”
○
"[select ", “[FIND “
●
Check your org-wide default sharing model and make
sure that objects using "Public Read/Write" or "Public Read
Only" are OK with this base model. If not, consider moving to
"Private" and create the necessary sharing rules to give the right
level of visibility. If you want to search for this in the metadata use the
following keywords to search for sharingModel in object metadata.
○
“<sharingModel>ReadWrite</sharingModel>”
○
“<sharingModel>Read</sharingModel>”
●
Check your user profiles and look for "View
All" , "Modify All" permissions. Remember that having these
permissions will bypass record-level security checks and cause unexpected
behavior. To find these permissions in the metadata files, you can search for:
○
“<modifyAllRecords>true</modifyAllRecords>”
○
“<viewAllRecords>true</viewAllRecords>”
References
Ouellete, Jason. Development with the Force.com Platform :
Building Business Applications in the Cloud. Addison Wesley, 2011. Print.
Salesforce.com. “Sharing Rules Overview” Salesforce Online Help. Nov 2012.
Salesforce.com. “Granting High-Volume Portal Users (Service
Cloud Portal Users) Access to Records Salesforce
Online Help. Nov 2012.
<https://help.salesforce.com/HTViewHelpDoc?id=customer_portal_setting_light_users.htm&language=en_US>
Thanks Man ;) its a lot of info....
ReplyDeleteVery helpful article!
ReplyDeleteReally very helpful :)
ReplyDelete