Creating Apex Sharing rules

An important piece of the Salesforce security puzzle is apex sharing rules. In this blog, couple of examples of Apex sharing rules are explained.
Apex sharing rules allow giving users (or groups) access to certain records programmatically. As an example let’s say that we want to give access to “IBM” Account to all users that are from US. These are the only users that should have access to the IBM Account record. This type of functionality can be implemented by using Apex Sharing rules.

To implement such a functionality, as a first step we will set the Organization-Wide-Default setting of Account to Private. This is available via SetupàSecurity ControlsàSharing Settings. This will ensure that Accounts are accessible only to owners and other users above the owner in the role hierarchy.

To make selective records of standard and custom objects available to certain users programmatically, sharing rules need to be implemented. Basically this involves creating a “share” record, updating its fields and then inserting the share record. The share objects for standard object have “share” suffixed after the object name. As an example share object for Account is AccountShare, and share object for Case is CaseShare. For custom objects, also share objects have similar names. As an example for custom object book__c, share object will be called book__share (note that c after the custom object name is removed).
In the first example we create a share for a custom object. 


List<Book__Share> bookShares  = new List<Book__Share>();
/* Iterate over all users. The users that meet our criteria, for them we will create share   objects*/
for(User u : [Select u.Id from User u where …someConditionHere…]) {
/* Create a new book share */
Book__Share bookShare = new book__Share();
bookShare.ParentId = bookItem.id; // The book record that we need to give access to.
bookShare.UserOrGroupId = u.Id; // The user to which access is being granted
bookShare.AccessLevel = 'Read';
bookShares.add(bookShare); // Add this book share to the array
}
/* To bulkify the trigger, we insert share objects for all users together. This will reduce the number of SOQL calls. */          
// Insert all elements of array (all content item shares into the array)
Database.SaveResult[] insertResults = Database.insert(bookShares,false);  // The false parameter allows for partial processing if multiple records passed into the operation. 
// Error handling - did the insert work as intended.   
if(insertResults[0].isSuccess()){                        // Indicates success     
System.debug('Recrords for BookShare inserted successfully');
return;
}
else {
// Get first save result error. 
Database.Error err = insertResults[0].getErrors()[0];
// Check if the error is related to trivial access level.  Access levels equal or more permissive than the object's default access level (OWD) are not allowed.  
// These sharing records are not required and thus an insert exception is acceptable.  
if(err.getStatusCode() == StatusCode.FIELD_FILTER_VALIDATION_EXCEPTION  && 
err.getMessage().contains('AccessLevel')){                        // Indicates success. 
System.debug(' Access level of inserted book share record was same as natural access level ' );
}
else {
// Indicates failure. 
System.debug(Logginglevel.ERROR, ' Unable to insert share record for book ' + err.getMessage());  
}
}

The insertion of share record fails if the share rule is granting access that is less than what is available via Organization-wide-default. For example, if we are trying to create a share to grant read access to certain Accounts for some users, and Account standard object have Organization-wide-default set to Public Read-Write, then insertion of share will generate an exception. The example above handles this exception as successful execution.

The example below gives similar code for AccountShare object (without the error handling).

                        /* The example below creates an account share record and inserts it. This will give the user access to “parentAccount” account. */
                        AccountShare accountShare = new AccountShare();
                        accountShare.AccountId = parentAccount; // The account that we are trying to grant access to.    
                        accountShare.UserOrGroupId = u.Id; // The user to which access is being granted
                        accountShare.AccountAccessLevel = 'Read';
                        accountShare.OpportunityAccessLevel = 'Read';
                        Database.SaveResult accountInsertResults = Database.insert(accountShare, false); 

The fields of share object for custom objects that should be filled in are explained below

 

Field name in custom object

Description

ParentId

The Id of the object to which access is being granted

UserOrGroupId

The id of user or group to which access is being granted

AccessLevel

“Read” or “Edit” or “All”

In standard objects, the field ParentId and AccessLevel have object name prefixed. As an example in AccountShare object, these fields are called AccountId and AccountAccessLevel respectively.
The share objects can be inserted using Apex code either in Triggers or in Visualforce controllers.