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)

            + Public Read
           
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).

            + Private

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).

            + Controlled by Parent

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.


- User-managed Sharing


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

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

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.

- CRUD and FLS enforcement

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.


Comments

Post a Comment

Popular posts from this blog

Force.com commandments

almond