Protecting Force.com Custom Sharing Code

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 by record owner changes and maintain the integrity of your custom sharing model.

The Force.com platform includes a large set of powerful tools for configuring users’ access to application data.

  • Organization-wide default sharing models
  • Role hierarchy
  • Public groups
  • Ownership- and criteria-based sharing rules
  • The Salesforce Territory Management feature

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.

Surviving Owner Change Operations

If you’ve been coding sharing behavior, you already know that to write a share in an object table you need to provide:

  • A record ID
  • A user or group ID
  • An access user level

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?

Using Apex Sharing Reasons

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.

Using Outbound Messaging

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.

  1. From Setup, click Create | Workflow & Approvals | Workflow Rules.
  2. Click New Rule, select “Account,” and click Next
  3. Enter a rule name and a description for the rule.
  4. To set the appropriate evaluation criteria, select “created, and every time it’s edited.”
  5. To set the appropriate rule criteria, select “formula evaluates to true.”
  6. Enter “OwnerId <>PRIORVALUE(OwnerID)” for the formula.
  7. Click Save & Next.
  8. Select “New Outbound Message” and configure your outbound message.

Using a Trigger

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);
        }
    }
}

Using a Shadow Table

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.

  • Account: Lookup to Account
  • Team Member: Lookup to User
  • Account Access: Picklist (Read, Edit)
  • Opportunity Access: Picklist (None, Read, Edit)
  • Case Access: Picklist (None, Read, Edit)
  • Contact Access: Picklist (None, Read, Edit)
  • Team Role: Picklist (Account Manager, Channel Manager, Executive Sponsor, Lead Qualifier, Pre-Sales Consultant, Sales Manager, Sales Rep)

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.

Completing the 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.

  • A user with appropriate access to a record can change or remove your programmatic shares through the sharing button on the record’s detail page.
  • A user with permission to update the membership of an account team can change or remove shares your code has written to manage team membership. This does not apply to sales teams, because the platform now includes the ability to define triggers for the sales team object that you can use to protect your shares.
  • Any Apex or API operation performing DML on the sharing objects could also impact your sharing system.

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.

Summary

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.

Related Resources

About the Authors and CCE Technical Enablement

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.

Published
February 27, 2013

Leave your comments...

Protecting Force.com Custom Sharing Code