Abstract

Force.com allows developers and administrators to control access to data at many different levels. You can control access at the object-level, the record-level and even at the field-level. This article will focus on methods for controlling access to data at the record-level. In the vast majority of cases, the appropriate Force.com sharing settings can be defined declaratively by simply pointing and clicking. In some cases, developers may need the ability to define even more sophisticated sharing settings, and this is where Apex Managed Sharing comes in.

Apex Managed Sharing allows you to use Apex Code to build sophisticated and dynamic sharing settings that aren’t otherwise possible. For example, a developer can use Apex Managed Sharing to write a trigger that will automatically share a custom object record with a user that has been specified in a lookup field. You can also use Apex Managed Sharing to write custom Visualforce controllers that implement your sharing logic.

After an introduction to sharing, this article looks at the components of Apex Managed Sharing and how you can use Apex Managed Sharing in your own applications. It also provides sample code for the trigger described above. If you need an introduction to these topics, see An Introduction to the Force.com Database and An Overview of Force.com Security.

Understanding Sharing

Organization Wide Defaults are the least granular level of sharing. Organization Wide Defaults allow an administrator to specify a user’s default access level to an object. From most restrictive to least restrictive, the available default settings are: "Private", "Public Read Only" and "Public Read/Write". All record-level sharing settings, including the ones created by Apex Managed Sharing, are exceptions to these Organization Wide Default sharing settings.

Record-level sharing settings can only be used to grant more permissive access to records. Record-level sharing settings cannot be used to restrict access to records. This also means that sharing settings can only be implemented for objects whose object-level sharing defaults are restricted – i.e. either Public Read Only or Private. It is not necessary to grant additional permissions on objects that have a default sharing access of Public Read/Write because users already have read/write default access.

The declarative sharing settings that the Force.com platform provides are very easy to configure and are very powerful, so before writing any Apex Managed Sharing code, you should verify that the declarative sharing settings will not meet your requirements. Here's a look at the types of sharing settings available to you.

Force.com Managed Sharing

Force.com Managed Sharing involves sharing access granted by Force.com through record ownership, the role hierarchy, territory hierarchy, teams, and sharing rules:

  • Record Ownership: Each record is owned by a user. The record owner is automatically granted Full Access, allowing them to view, edit, transfer, share, and delete the record.
  • Role Hierarchy: The role hierarchy enables users above another user in the hierarchy to have the same level of access to records owned by or shared with users below them.
  • Territory Hierarchy: Salesforce Territory Management provides a way to grant access to accounts based on characteristics of those accounts. The territory hierarchy enables users to view data shared to their territories or territories below them in the hierarchy.
  • Teams: Account Teams, Sales Teams and Case Teams provide access to a group of users who work on a particular account, opportunity or case.
  • Sharing Rules: Sharing rules are defined by administrators. They automatically grant users within a given group/role access to records owned by a specific group of users. Criteria based sharing rules automatically grant a users within a given group/role access to records based on field values within those records.

User Managed Sharing, also known as Manual Sharing

User managed sharing allows the record owner, or any user with Full Access to a record, to share the record with another user/group of users. This is generally done by an end-user, for a single record. User managed 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 organization-wide sharing default access level.

Apex Managed Sharing

Apex managed sharing is a type of "Programatic Sharing" which allows you to define a custom sharing reason to associate with your programatic share. Standard Salesforce objects support "Programatic Sharing" while custom objects support Apex managed sharing. More specifically, object shares can be written to both standard and custom objects, however custom sharing reasons can only be defined for shares written to custom objects.

Apex managed sharing provides developers with the ability to support an application’s particular sharing requirements programmatically via Apex code.

The sample use case for Apex managed sharing that we will explore in this article is an "after insert" trigger that grants 'edit' access on a private record to a user specified in a lookup field on that record.

Another example use case would be a trigger that shares a record with a group of users once the record meets certain criteria - you could automatically grant 'read' access on a record to a team once the record reaches a certain stage or currency amount.

You're not limited to writing triggers though-you can also use Apex managed sharing in custom Visualforce controllers for example.

Now let's dive in and learn how records are shared on the platform.

Important Objects Related to Apex Managed Sharing

Sharing is a fairly sophisticated topic, so some explanation of the underlying mechanisms may be helpful.

Sharing Table

All objects that have a default sharing setting of the either "Private" or "Public Read Only" also have a related "Share" object that is similar to an access control list (ACL) found in other platforms. All share objects for custom objects are named as MyCustomObject__Share, where MyCustomObject__c is the name of the related custom object. A share object includes records supporting all three types of sharing: Force.com managed sharing, user managed sharing, and Apex managed sharing.

A custom object’s share object allows four pieces of information to be defined:

  • The record being shared.
  • The User or Group with whom the object is being shared.
  • The permission level being granted to the User or Group.
  • The reason why the User or Group has been granted sharing access.

This information corresponds with the following fields in a share object:

ParentId The Id of the record being shared. This field cannot be updated.
UserOrGroupId The Id of the User to whom you are granting access. May also be a Public Group Id, Role Id, or Territory Id. This field cannot be updated.
AccessLevel The level of access that the specified User or Group has been granted.

Valid values for Apex managed sharing are: Edit, Read.

This field must be set to an access level that is higher than the organization’s default access level for the parent object. For more information, see Access Levels.

RowCause (aka Sharing Reasons) The reason why the user or group is being granted access. The reason determines the type of sharing, which in turn controls who can alter the sharing record. This field cannot be updated.

Apex Sharing Reasons

Apex code can create records in share tables with either a custom RowCause or with a RowCause that is set as "Manual". A "Manual" RowCause means that the share record will be treated as if it was created by User managed sharing. True Apex managed sharing is when a record in a share table has a custom RowCause, which is known as an Apex sharing reason.

Apex sharing reasons are a way for a developer to track why they shared a record with a user or group of users. Using multiple Apex sharing reasons simplifies the coding required to make updates and deletions of sharing records. They also enable developers to share with the same user or group multiple times using different reasons although larger organizations should use caution when doing this since redundant sharing has performance implications.

Each Apex sharing reason has a label and a name. The "label" value is displayed in the "Reason" column when viewing the sharing for a record in the user interface. This allows users and administrators to understand the source of the sharing. The "name" value is used when referencing the reason in the API and Apex.

An example sharing Reason Label might be, "My Sharing Reason". The corresponding Reason Name would be, "My_Sharing_Reason__c".

This Apex sharing reason would be referenced in Apex as follows: Schema.MyCustomObject__Share.rowCause.My_Sharing_Reason__c

Reasons to Implement Apex Managed Sharing

Building sophisticated and dynamic sharing settings isn’t the only reason to use Apex managed sharing. You may also find advantage in the fact that share records created by Apex managed sharing behave differently than the other forms of record-level sharing:

  • Sharing records created by Apex managed sharing are maintained across record owner changes.
  • The only users that can add, edit or delete sharing records created by Apex managed sharing are users with the "Modify All Data" permission.
  • A record can be shared multiple times with a user or group using different Apex sharing reasons.

Apex Managed Sharing

Now that we’ve discussed sharing in depth, let’s take a look at a sample use case and how it might be solved with Apex managed sharing.

Example Use Case

The simple use case that we will use to explain the utility of Apex managed sharing is: "Automatically share a record to a User based on a User-lookup field."

For example, imagine that we’ve just built a recruiting application for our company. In our application, we have a "Job" custom object:

AMS Job.png

In this example, the Organization Wide Default Sharing Setting for our Job object is set to "Private". This means that when a recruiter at our company creates a new Job record, only the recruiter will have access to that record – the hiring manager for the Job won’t be able to edit or even view the record that corresponds with her job opening!

As we can see if we click on the Job record’s "Sharing" button, only the recruiter that created the Job record has access to it:

AMS Job Share.png

Clearly the hiring manager should have access to her job record as well. This is the perfect use case for Apex managed sharing.

To get started, we'll create an Apex sharing reason. Then, we'll write an Apex trigger that will automatically create the appropriate Job_Share record every time a new Job record is created.

Example Apex Sharing Reason

Before we can start writing any Apex managed sharing code, we must create an Apex sharing reason. To do that:

  • Click Setup | Create | Objects.
  • Select the custom object. (In this case, the "Job" custom object.)
  • Click New in the Apex Sharing Reasons related list. (If you don't see this related list, Apex managed sharing has not been configured for your Org - please contact your support representative.)
  • Enter a label for the Apex sharing reason.
  • Enter a name for the Apex sharing reason.
  • Click Save.

The end result will look like this: AMS Job Custom Object.png

Using the naming scheme explained previously, this Apex Sharing Reason will be called Hiring_Manager_Access__c. Let's now look at the code which uses this.

Example Code:

Our Apex Trigger will look like this:

trigger Hiring_Manager_Job_Share on Job__c (after insert) {

    // We only execute the trigger after a Job record has been inserted 
    // because we need the Id of the Job record to already exist.
    if(trigger.isInsert){
	 
    // Job__Share is the "Share" table that was created when the
    // Organization Wide Default sharing setting was set to "Private".
    // Allocate storage for a list of Job__Share records.
    List<Job__Share> jobShares  = new List<Job__Share>();

    // For each of the Job records being inserted, do the following:
    for(Job__c job : trigger.new){

        // Create a new Job__Share record to be inserted in to the Job_Share table.
        Job__Share hiringManagerShare = new Job__Share();
            
        // Populate the Job__Share record with the ID of the record to be shared.
        hiringManagerShare.ParentId = job.Id;
            
        // Then, set the ID of user or group being granted access. In this case,
        // we’re setting the Id of the Hiring Manager that was specified by 
        // the Recruiter in the Hiring_Manager__c lookup field on the Job record.  
        // (See Image 1 to review the Job object's schema.)
        hiringManagerShare.UserOrGroupId = job.Hiring_Manager__c;
            
        // Specify that the Hiring Manager should have edit access for 
        // this particular Job record.
        hiringManagerShare.AccessLevel = 'edit';
            
        // Specify that the reason the Hiring Manager can edit the record is 
        // because he’s the Hiring Manager.
        // (Hiring_Manager_Access__c is the Apex Sharing Reason that we defined earlier.)
        hiringManagerShare.RowCause = Schema.Job__Share.RowCause.Hiring_Manager_Access__c;
            
        // Add the new Share record to the list of new Share records.
        jobShares.add(hiringManagerShare);
    }
        
    // Insert all of the newly created Share records and capture save result 
    Database.SaveResult[] jobShareInsertResult = Database.insert(jobShares,false);
        
    // Error handling code omitted for readability.
    }
}

We've just written a properly "bulkified" trigger that for each new Job record:

  • Creates a Job_Share record.
  • Assigns the Id of the Job record to the Job_Share record's ParentId field.
  • Assigns the User Id of the Hiring Manager to the Job_Share record's UserOrGroupId field.
  • Specifies that the Job_Share's AccessLevel field have a value of 'edit'.
  • Assigns the Hiring_Manager_Access__c Apex Sharing Reason to the JobShare's RowCause field.
  • Inserts the Job_Share record in to the Job_Share table.
  • Note that this is an abbreviated example to highlight how to use Apex managed sharing, however a production ready solution would have to handle all interactions such as updates to the Hiring Manager.

Now, the next time that a recruiter creates a new Job record, the Hiring Manager will automatically be given access to the record with a custom Sharing Reason of “Hiring Manager”: AMS Job Custom Sharing Reason.png

That's it!

Summary

This article describes Apex managed sharing, a technique that allows you to use Apex Code to build sophisticated and dynamic sharing settings for records on Force.com. After creating the Sharing Reasons, it's simply a matter of coding the creation of the share record. Besides the straightforward example shown in this article, there are any number of other use cases that Apex managed sharing can support as well - it gives you full control over the record sharing. You should be able to use this code as a starting point and modify it to implement your own custom sharing logic.

References

  • The following Help topics provide additional context. Search for them in the "Help" module in your Developer Edition environment.
    • Sharing Considerations
    • Managing the Sharing Settings
    • Creating Apex Sharing Reasons
  • The Force.com Platform Developer Guide contains a section that discusses Apex Managed Sharing.
  • The Force.com Apex Code Developer's Guide describes how to share a record programmatically, as we have done here.

About the Author

Jesse Lorenz is a Technical Evangelist at salesforce.com, helping independent software vendors, product teams and other partners to architect and build innovative applications on Force.com.