The Force.com platform includes a large set of powerful tools for configuring users’ access to application data.
These platform features can handle almost any security use case you have, but sometimes your requirements are so unique and specific that you have to write custom code to manage them. Unfortunately, when you do this, you have to play a little defense to protect the shares that you write against some of the automatic sharing behavior of the platform. In this blog post, we’ll discuss some advanced sharing features and coding strategies you can use to keep Force.com from messing with your sharing code.
If you’ve been coding sharing behavior, you already know that to write a share in an object table you need to provide:
Another parameter—the row cause, or reason for the share—is set to ‘manual’ for you by default. This is the same row cause that the platform writes when a user shares a record with another user via the manual share button.
And that’s where the problem starts….
There’s a basic assumption built into the platform that the owner of a record has ultimate control over it, including who can see it. So when the owner of a record is changed, the platform deletes all the manual shares associated with the record. In effect, we “clean the slate” for the new owner and let them decide whether they want to share it to anybody. And if you have been writing code that shares the record, your shares will get deleted, too, because they have the same ‘manual’ row cause—the platform cannot distinguish between a sharing row you created and a sharing row created through the UI.
So how do you protect your shares from this behavior?
If you are writing sharing code to control access to a custom object, you are in luck. Because you already have the “Author Apex” permission, you will see the following related list on your custom object definitions.
An Apex sharing reason is simply a row cause that you can define for yourself, then use in your code when you write rows in the sharing tables.
Because your row cause for these shares will no longer be ‘manual’, the platform won’t touch them when performing the change owner operation. In addition, you can create multiple sharing reasons for different purposes—for the same object. This capability can help you track why you wrote a particular share, and control in your code when and why that share should be deleted or updated in some way.
Well that’s great for custom objects, but what about sharing code that you need to write to control access to standard objects like accounts or contacts? The relationship between these objects can be complex, and there might be good reasons for the platform to change or delete a sharing row, even one that you have created programmatically. At other times, though, you might want to make sure the platform doesn’t alter the shares you have written. In the following sections, we will outline a few strategies you can use to achieve this goal.
This strategy is appropriate for use with standard objects, where you can’t use a custom sharing reason, and you are integrating with an assignment engine external to Salesforce. You can configure a workflow rule to detect when a record owner is changed, and use an outbound message to trigger your assignment engine to take appropriate action.
This strategy also applies to standard objects where you can’t use a custom sharing reason, but in this case you are integrating with an assignment engine built on the Salesforce platform.
The trigger could look like this.
trigger AccountTriggers on Account (after update) { if(trigger.isAfter && trigger.isUpdate) { //First Determine which accounts had an ownership change in the batch String[] accountsToProcess = new String[]{}; for (Account a:Trigger.new) { if(a.ownerId <> Trigger.oldMap.get(a.id).ownerId) accountsToProcess.add(a.id); } //If any accounts had an ownership change send them into the assignment engine if(accountsToProcess.size() > 0) { //This is your custom account assignment engine AssignmentEngine.assignAccounts(accountsToProcess); } } }
In some cases, your logic for standard objects might not be complex enough to justify building a full-blown assignment engine. You might be able to accomplish the same goal through the use of a trigger and a custom object that keeps track of your programmatic shares. This example uses an object that mimics the functionality of the standard Account Team object.
First, create and configure your object—let’s call it “Programmatic Share”—with the following fields.
Then, build a trigger on the Account object to detect owner changes and perform appropriate maintenance, based on the data stored in the Shadow Team object.
trigger AccountTriggers on Account (after update) { if(trigger.isAfter && trigger.isUpdate) { //First Determine which accounts had an ownership change String[] accountsToProcess = new String[]{}; for (Account a:Trigger.new) { if(a.ownerId <> Trigger.oldMap.get(a.id).ownerId) accountsToProcess.add(a.id); } //If any accounts had an ownership change proceed if(accountsToProcess.size() > 0) { //Retrieve the list of all cloned shares List<Programmatic_Share__c> psList = [select account__c, teamMember__c, accountAccess__c, opportunityAccess__c, caseAccess__c, contactAccess__c from Programmatic_Share__c where objectId__c in :accountsToProcess]; //If programmatic shares were found continue if(psList.size() > 0) { List<AccountShare> asList = new List<AccountShare>(); //Create AccountShare records for each cloned share for (Programmatic_Share__c ps: psList) asList.add(new AccountShare(accountId = ps.account__c, userOrGroupId = ps.teamMember__c, accountAccessLevel = ps.accountAccess__c, caseAccessLevel = ps.caseAccess__c, contactAccessLevel = ps.contactAccess__c, opportunityAccessLevel = ps.opportunityAccess__c)); //Insert the AccountShare records Database.insert(asList); } } } }
Note that the code samples above are simplified illustrations only, not best practice implementations for a production organization. There could be many other ways of accomplishing the same goals, and a full implementation will depend on your particular requirements and overall architecture.
When writing custom shares to standard objects, there are additional platform features that could impact the sharing system you have built, which you can’t code around.
In these cases you have architectural choices to make. For the end-user driven operations, you can accept the vulnerability, disable the sharing button or the account team related list in the UI, or replace those features with custom VF elements under your control. For the third, following best practices for code review and assessing the impact of new code on existing functionality should prevent this kind of collision from happening.
The programmatic sharing capabilities of the Force.com platform are very powerful and flexible, but you have to practice a bit of defensive coding to keep our code from undoing yours. With Apex sharing reasons or one of the strategies described in this post, you can keep the shares you create from being deleted when records change owners, and protect the integrity of your programmatic sharing features.
Bud Vieira and Sean Regan are members of the Technical Enablement team within the salesforce.com Customer-Centric Engineering group. The team’s mission is to help customers understand how to implement technically sound salesforce.com solutions. Check out all of the resources that this team maintains on the Architect Core Resources page of Developer Force.