Sunday, December 27, 2020

Access Grant, Share and Maintenance Tables

Record Access Calculation

As there are many options for managing record level access, and as some of these options are affected by organizational dependencies, determining which records users can access can quickly become complicated. Rather than checking record access in real time, Salesforce calculates record access data only when configuration changes occur. The calculated results persist in a way that facilitates rapid scanning and minimizes the number of database table joins necessary to determine record access at runtime.

Access Grants

When an object has its OWD set to Private or Public Read Only, which is not the least restrictive option, Salesforce uses access grants to define how much access a user or group has to that object’s records. Each access grant gives a specific user or group access to a specific record, It also records the type of sharing tool: sharing rule, team, etc.

Salesforce uses 4 types of access grants:

Explicit grants:

Grants that occur when records are shared directly to users or groups. Examples:

  • A user or a queue becomes the owner of a record, as in Salesforce each record should have an owner, and whenever a record is owned by a user or a queue, an explicit share is used to grant access to the record for the owner or queue.
  • A sharing rule shares the record to a personal or public group, a queue, or a role.
  • An assignment rule shares the record to a user or a queue.
  • A territory assignment rule shares the record to a territory.
  • A user manually shares the record to a user, a personal or public group, a queue, or a role.
  • A user becomes a part of a team for an account, opportunity, or case.
  • A programmatic customization shares the record to a user, a group, or queue, or a role.
Group membership grants:

Grants that occur when a user, group, queue, role, or territory is a member of a group that has explicit access to the record. So as you know by now, an explicit share is used to grant access to group via sharing rules, manual sharing or Apex sharing, because of which all members of a group are granted access using the group membership access grant. For example, if a sharing rule grants the “Sales group” access to the Acme account record, and Bob is a member of the “Sales group”, Bob’s membership in the “Sales group” grants him access to the Acme account record.

Inherited grants:

Grants that occur when a user, group, queue, role, or territory inherits access through a role or territory hierarchy or is a member of a group that inherits access through a group hierarchy. For example, if a user Alice has a role that is higher in the role hierarchy than the role of another user Bob, then Alice can access the same records that Bob can access.

Implicit grants:

Grants that occur when non-configurable record-sharing behaviours built into Salesforce Sales, Service and Portal applications grant access to certain parent and child records. Examples:

  • Read-only access to the parent account for a user with access to a child record.
  • Access to child records for the owner of the parent account record.
  • Access to a community account and all associated contacts for all community users under than account
  • Access to data owned by community users associated with a sharing set for users member of the sharing set’s access group.

Object Share tables store the data that supports explicit and implicit grants.

SELECT Id, ParentId, UserOrGroup.Name, AccessLevel, RowCause FROM AccountShare

Group maintenance tables store the data that supports group membership and inherited access grants.

SELECT Id, Name, DeveloperName, Type, OwnerId FROM Group
Share This:    Facebook Twitter

Monday, December 7, 2020

Secure Salesforce Apex Programming

How permissions and sharing work on Salesforce

The Salesforce platform has two main ways of controlling access to records—permissions and sharing. Permissions in Salesforce focus on what you can do with a particular object in general. Sharing focuses on what records you can see for that object based on their ownership.

With Salesforce's permissions and sharing tools, you can build up a very granular set of visibility permissions that control exactly who can access the different records within your org. When you attempt to retrieve a record in Salesforce, these permissions are checked before any further processing occurs. Following this, sharing calculations are then run to verify whether you have access to the record or records that you are retrieving.

Sharing and performance

As an application grows on the platform, the volume of data stored on it will also inevitably grow. The obvious side effect of this is that querying for records and loading lists of data can become much slower. To help keep your application performant, you should reduce the amount of data every user can see to that which is strictly necessary. This will ensure that queries, list views, reports, and all manner of functionalities continue to run in an optimal way and don't slow down the user experience.

Enforcing sharing

By default, all Apex operations (and Process Builder and certain Flows) run in System Mode; that is, they execute as a generic system user that has access to all metadata and data within the org. Within our Apex configuration, sharing is enforced through the use of the with sharing keywords in our class definition. Declaring either with sharing or without sharing explicitly is a deliberate action for us to verify that we either do or do not want the sharing rules for the current user to be enforced. If we do not define a class as with sharing or without sharing explicitly (ClassC), then the current sharing rules remain in force.

public with sharing class ClassA {
	public static List<Account> getAccounts() {
		return ClassC.getAccounts();
	}
}

public without sharing class ClassB {
	public static List<Account> getAccounts() {
		return ClassC.getAccounts();
	}
}

public class ClassC {
	public static List<Account> getAccounts() {
		return [SELECT Name from Account];
	}
}

If ClassC was the entry point to our transaction, it would operate in without sharing mode by default.

In situations where we want to default to a with sharing context, but enable the code to run in a without sharing context when called from a class defined as without sharing, we can utilize the inherited sharing option as our default. Apex without a sharing declaration is insecure by default. An explicit inherited sharing declaration makes the intent clear, avoiding ambiguity arising from an omitted declaration or false positives from security analysis tooling.

So whenever we are defining our Apex classes, we should apply the following rules to ensure our sharing is actually enforced as we anticipate it to be:

  • Use with sharing when we know we want the sharing model to be enforced.
  • Use without sharing when we know we want the sharing model to be ignored.
  • Otherwise, use inherited sharing as a default.

Sharing records using Apex

Salesforce has several ways of sharing records with users and groups of users such as managed sharing, user-managed (or manual) sharing, and Apex managed sharing:

  • Managed sharing is the point-and-click sharing that most Salesforce developers and administrators are familiar with, and relies upon record ownership, the role hierarchy in the org, and any sharing rules.
  • User-managed sharing or manual sharing is when a user chooses to share a record with a user or group of users using the Share button.
  • Apex managed sharing is the sharing of records with a user or group of users through the use of Apex code.

All three of the methods described store records in the share object associated with the record within the Salesforce database. For every object, there is a corresponding share object. For standard objects, it is the object API name plus share, so AccountShare, ContactShare, and so on. For custom objects, __c in the object API name is replaced by __Share. Sharing via org-wide defaults, the role hierarchy, and permissions such as View All are not stored in these objects.

So as an example, to create an AccountShare record, we require to set the following information:

  • The ID of the record to be shared with the ParentId field.
  • The user of group to be shared within the UserOrGroupId field.
  • An access level, either Edit or Read, in the AccessLevel field.
  • A reason for sharing in the RowCause field. The default value is Manual, as we have set here, however custom reasons can be set by adding them through the setup menu.
AccountShare newShare = new AccountShare();
newShare.ParentId = accId;
newShare.UserOrGroupId = userId;
newShare.AccessLevel = 'Read';
newShare.RowCause = Schema.AccountShare.RowCause.Manual;
accShares.add(newShare);

Enforcing object and field permissions

We have two ways of enforcing our object-level and field-level permissions in Apex code, the first of which is to utilize the describe methods within Apex to verify that the user had the correct permissions. The methods to verify the permissions for an sObject are as follows and are utilized on the Schema.DescribeSObjectResult instance for the given sObject:

  • isAccessible
  • isCreateable
  • isUpdateable
  • isDeletable

For example, we can verify permissions on a Contact object as follows:

if(Schema.sObjectType.Contact.isAccessible()) {
   // Read Contact records
}

if (Schema.sObjectType.Contact.isCreateable()) {
   // Create Contact records
}

if (Schema.sObjectType.Contact.isUpdateable()) {
   // Create Contact records
}

if (Schema.sObjectType.Contact.isDeletable()) {
   // Create Contact records
}

Similarly, at the field level, on the Schema.DescribeFieldResult instance for a field, we have the following methods:

  • isAccessible
  • isCreateable
  • isUpdateable

All of these are available to us to verify that we have the correct permissions to manipulate a field:

if(Schema.sObjectType.Contact.fields.Email.isAccessible()) {
   // Read Contact record Email
}

if (Schema.sObjectType.Contact.fields.Email.isCreateable()) {
   // Populate Contact record Email
}

if (Schema.sObjectType.Contact.fields.Email.isUpdateable()) {
   // Edit Contact record Email
}

We can also enforce field and object permissions using the Security.stripInaccessible method, which takes two parameters. The first is an access level that we wish to verify against, and the second is a List<sObject>. The method then removes any fields that the user does not have the stated access level for. It is also particularly useful for sanitizing records as a whole, such as when we are providing an API and receiving sObject data from external users. Read more about the stripInaccessible method here.

In the following code, we are using the stripInaccessible method to remove any fields that the user does not have update permissions on:

String jsonBody = '[{"FirstName":"Alice", "LastName":"Jones", "Email": "ajones@test.com"}]';

List<Contact> contacts = (List<Contact>)JSON.deserializeStrict(jsonBody, List<Contact>.class);

SObjectAccessDecision accessDecision = Security.stripInaccessible(AccessType.UPDATABLE, contacts);
update accessDecision.getRecords();

If you still want to throw an exception if the user has no access to any one of the fields using <code>stripInaccessible</code> method, then you can use the below Utils method:

public static List<SObject> checkFieldLevelSecurity(List<SObject> sobjects, AccessType accessCheckType) {
    SObjectAccessDecision decision = Security.stripInaccessible(accessCheckType, sobjects);
    System.debug(LoggingLevel.INFO, decision);
    if (decision.getRemovedFields().size() > 0) {
        throw new QueryException('User cannot read these fields: ' + decision.getRemovedFields());
    }
    return decision.getRecords();
}

And to use checkFieldLevelSecurity method, pass the list of sobjects and the AccessType.

(List<Task>) Utils.checkFieldLevelSecurity(sobjects, AccessType.READABLE);

Enforcing permissions and security within SOQL

Salesforce added the WITH SECURITY_ENFORCED clause to the SOQL language. Unlike the stripInaccessible method, if the user is lacking permissions for a field, an exception is thrown rather than the field simply being removed.

To apply this clause, we simply include WITH SECURITY_ENFORCED after any WHERE clause and before any ORDER BY, LIMIT, OFFSET, or aggregate function clauses. For example, consider the following:

List<Contact> contacts = [SELECT FirstName, LastName, Secret_Field__c FROM Contact WITH SECURITY_ENFORCED];

In the preceding query, if the user has no access to Secret_Field__c, then an exception will be thrown, indicating insufficient permissions.

Avoiding SOQL injection vulnerabilities

The first and most simple is to ensure we are using Apex binding variables and static queries. By default, Apex binding variables are automatically escaped and so will ensure that the query would run as expected.

public String searchName {get; set;}

public PageReference search() {
   return [SELECT Id, LastName, Email FROM Contact WHERE LastName Like :searchName];
}

There are instances where we must use a dynamic query. In these instances, we should ensure that we escape any input from the end user using the escapeSingleQuotes method:

public String searchName {get; set;}

public PageReference search() {
   return Database.query('SELECT Id, LastName, Email FROM Contact WHERE LastName Like \'%' + String.escapeSingleQuotes(searchName) + '%\'');
}
Share This:    Facebook Twitter

Total Pageviews

My Social Profiles

View Sonal's profile on LinkedIn

Tags

__proto__ $Browser Access Grants Accessor properties Admin Ajax AllowsCallouts Apex Apex Map Apex Sharing AssignmentRuleHeader AsyncApexJob Asynchronous Auth Provider AWS Callbacks Connected app constructor Cookie CPU Time CSP Trusted Sites CSS Custom settings CustomLabels Data properties Database.Batchable Database.BatchableContext Database.query Describe Result Destructuring Dynamic Apex Dynamic SOQL Einstein Analytics enqueueJob Enterprise Territory Management Enumeration escapeSingleQuotes featured Flows geolocation getGlobalDescribe getOrgDefaults() getPicklistValues getRecordTypeId() getRecordTypeInfosByName() getURLParameters Google Maps Governor Limits hasOwnProperty() Heap Heap Size IIFE Immediately Invoked Function Expression Interview questions isCustom() Javascript Javascript Array jsForce Lightning Lightning Components Lightning Events lightning-record-edit-form lightning:combobox lightning:icon lightning:input lightning:select LockerService Lookup LWC Manual Sharing Map Modal Module Pattern Named Credentials NodeJS OAuth Object.freeze() Object.keys() Object.preventExtensions() Object.seal() Organization Wide Defaults Override PDF Reader Performance performance.now() Permission Sets Picklist Platform events Popup Postman Primitive Types Profiles Promise propertyIsEnumerable() prototype Query Selectivity Queueable Record types Reference Types Regex Regular Expressions Relationships Rest API Rest Operator Revealing Module Pattern Role Hierarchy Salesforce Salesforce Security Schema.DescribeFieldResult Schema.DescribeSObjectResult Schema.PicklistEntry Schema.SObjectField Schema.SObjectType Security Service Components Shadow DOM Sharing Sharing Rules Singleton Slots SOAP API SOAP Web Services SOQL SOQL injection Spread Operator Star Rating stripInaccessible svg svgIcon Synchronous this Token Triggers uiObjectInfoApi Upload Files VSCode Web Services XHR
Scroll To Top