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

Monday, October 12, 2020

Javascript: for-of loops

for...of iterates arrays, array-like objects, and generally any iterable (maps, sets, DOM collections). You can destructure the iterated item in place. On top of that, for...of has a short syntax.

const fruits = ['oranges', 'apples'];

for (const fruit of fruits) {  
   console.log(fruit);
}

// 'oranges'
// 'apples'

Note that in for-of loops you can use const. Think of it as a new const declaration being executed each time in a fresh scope.

The array method entries() can be used to access the index of the iterated item. The method returns at each iteration a pair of [index, item].

const arr = ['oranges', 'apples'];

for (const [index, elem] of arr.entries()) {
  console.log(`${index} -> ${elem}`);
}

// 0 -> oranges
// 1 -> apples

At each iteration arr.entries() returns a pair of index and value, which is destructured by const [index, elem] expression.

Iterating over an array of objects

Example 1:

const person = {
  name: 'John Smith',
  job: 'agent'
};

for (const [prop, value] of Object.entries(person)) {
  console.log(prop, value);
}

// 'name', 'John Smith'
// 'job', 'agent'

where Object.entries(person) returns an array of key and value tuples.

Example 2:

const persons = [
  { name: 'John Smith' },
  { name: 'Jane Doe' }
];

for (const { name } of persons) {  
   console.log(name);
}

// 'John Smith'
// 'Jane Doe'

The cycle for (const { name } of persons) iterates the objects of persons array, but also destructures the person object in place { name }.

Maps and Sets iteration

const names = new Map();
names.set(1, 'one');
names.set(2, 'two');

for (const [number, name] of names) {  
   console.log(number, name);
}


const colors = new Set(['white', 'blue', 'red', 'white']);

for (color of colors) {
   console.log(color);
}

String characters iteration

const message = 'hello';

for (const character of message) {  
   console.log(character);
}

// 'h'
// 'e'
// 'l'
// 'l'
// 'o'
Share This:    Facebook Twitter

Saturday, October 10, 2020

Javascript: In-Place Out-Of-Place Algorithms

In an in-place function, the changes made by the function remain after the call completes. In-place algorithms are sometimes called destructive, since the original input is modified during the function call.

"In-place" does not mean "without creating any additional variables!" Rather, it means "without creating a new copy of the input." In general, an in-place function will only create additional variables that are O(1) space.

An out-of-place function doesn't make any changes that are visible to other functions. Usually, those functions copy any data structures or objects before manipulating and changing them.

Here are two functions that do the same operation on an array, except one is in-place and the other is out-of-place:

function squareArrayInPlace(intArray) {

  intArray.forEach((int, index) => {
    intArray[index] *= int;
  });

  // NOTE: no need to return anything - we modified intArray in place
}


function squareArrayOutOfPlace(intArray) {

  // We allocate a new array that we'll fill in with the new values
  const squaredArray = [];

  intArray.forEach((int, index) => {
    squaredArray[index] = Math.pow(int, 2);
  });

  return squaredArray;
}

Working in-place is a good way to save time and space. An in-place algorithm avoids the cost of initializing or copying data structures, and it usually has an O(1) space cost.

But be careful: an in-place algorithm can cause side effects. Your input is altered, which can affect code outside of your function.

Generally, out-of-place algorithms are considered safer because they avoid side effects. You should only use an in-place algorithm if you're space constrained or you're positive you don't need the original input anymore, even for debugging.

Share This:    Facebook Twitter

Friday, June 26, 2020

Salesforce Apex: Tips and Tricks

Generate a set of Ids from a List of SObject
List<Account> accounts = [SELECT Id, Name FROM Account];
Set<Id> selectedIds = new Map<Id, SObject>(accounts).keySet();
System.debug(selectedIds);

Division of 2 numbers
public static Decimal divide(Integer numerator, Integer denominator) {
    Decimal result;
    if (denominator != 0) {
        result = Decimal.valueOf(Double.valueOf(numerator) / Double.valueOf(denominator));
        return result.setScale(2);
    }
    return 0;
}


Formatting time in Apex
https://paulforce.wordpress.com/2009/08/27/formatting-time-in-apex/
Share This:    Facebook Twitter

Sunday, March 8, 2020

Monday, February 17, 2020

Accepting third Party Identity in Salesforce

Single Sign-On (SSO)

A process that allows the network to access all authorized networks without having to separately log in to each resource - one password fits all.
  • Federated Authentication: uses SAML and industry standards protocols. It is the default form of single sign-on. When enabled:
    • Salesforce doesn't validate the user's password
    • The platform receives an assertion in an HTTP POST
  • Delegated Authentication: a Salesforce-specific technology. When enabled:
    • Salesforce will make a Web service call to an external client to validate credentials. The web service must be SOAP and accessible over the internet.
NOTE:
  • While setting up SSO, you need to set up My Domain to allow redirection of users to an identity provider and allow access to Salesforce through deep links.
  • When you have an external identity provider and configure SSO for your Salesforce org, Salesforce is then acting as a service provider. You can also enable Salesforce as an identity provider and use SSO to connect to a different service provider. Only the service provider needs to configure SSO.

SAML

  • An OASIS standard for single sign-on
  • Security Assertion Markup Language (XML-based)
  • Designed to exchange authorization and authentication data
  • Trust is established during SAML configuration
    • The IdP's public key certificate is stored on the service provider's system
    • The IdP includes its public key certificate with the service provider in its assertions while making those HTTP POST requests.
    • The service provider uses the certificate to validate that the digital signature originated from the IdP, thereby validating the integrity of assertion.
  • Assertions can be encrypted optionally.

SAML Components

In a SAML SSO scenario, there are 3 key parties involved:
  1. A user or principal, who requires access to a resource (like Salesforce), which is provided by the Service Provider.  
  2. The Identity provider authenticates the user on behalf of the service provider. The Identity Provider is typically an Identity Management platform such as Ping Federate or Active Directory Federation Services (ADFS) and issues a SAML assertion token which is consumed by the service provider to authenticate the user.
  3. Service Provider – the website containing the resources the user wants to access. Eg Salesforce if federated identity used
So whatever authenticates you, that is the identity provider, while the service provider is whatever you are trying to get to. To understand the difference in layman's terms, check out this post.

Types of SAML Flow

  1. IdP Initiated: IdP sends an unsolicited SAML assertion to Salesforce
  2. SP Initiated: Starts with Salesforce, which solicits a SAML assertion from the IdP



SP Initiated Flow is the recommended Salesforce best practice.
  • Has the best user experience
  • Is required for using SSO with Salesforce mobile and desktop applications
  • Supports deep links
Check this video to see how to implement SSO in Salesforce: https://www.youtube.com/watch?v=ucmyYJ3iQoA

Delegated Authentication



  • Enable delegated authentication for a specific organization by contacting salesforce.com support
  • Download the AuthenticationService WSDL
    • Generate server-side stub and add specific implementation details
  • Specify SSO gateway
  • Modify profiles (specify which profiles will be enabled)
  • User credentials are encrypted and passed from Salesforce to DAS
NOTE: Do not enable SSO for the System Administrator profile. If the identity provider is unavailable, administrators will not be able to access the org.

Considerations
  • Activated at the profile level
  • Requires a proxy server that Salesforce can access
    • More cost for infrastructure and maintenance
  • Requires a valid certificate if an SSL or HTTPS connection is used with the authentication service

Which method to use?

  • User ID/password when basic configuration is required
  • SAML to provide single sign-on for the Web and applications
  • Delegated authentication with older infrastructures
  • OAuth when building an API client or mobile application

SAML with Multiple ORGs & Considerations

  • Adding additional orgs to single sign-on with SAML is just configuration
  • Many possible topologies are available:
    • Single enterprise identity provider, multiple service provider orgs
    • One org as identity provider, others as service provider
  • Check this link for more information: "Configure SSO Across Multiple Salesforce Orgs"

External Authentication Providers

  • External Authentication Providers allow ‘Social Sign-on’ using OAuth 2.0 and can be used to provision accounts, thereby letting your users log in to your Salesforce org using their login credentials from a third-party service.
  • Salesforce provides external authentication providers for apps that support the OpenID Connect protocol, such as Apple, Facebook, Google, Facebook, LinkedIn, and Twitter.
  • For apps that don’t support the OpenID Connect protocol but support OAuth or other authentication protocols, Salesforce provides an Apex Auth.AuthProviderPluginClass abstract class. With this class, you can create a custom authentication provider. This repo demonstrates the use of a Salesforce Custom External Authentication.
  • Salesforce can also be used as an authorisation provider for other orgs

To set up an external authentication provider,
  • Set up the Authorization Provider (e.g. Facebook)
  • Create an Apex Registration Handler class. Check this link for sample code.
  • Define the Authentication Provider in the Org
  • Add the button (e.g. Facebook) to the Login Page

EXAMPLE: How to set up Google as an external authentication provider?

  • Create an OAuth Client ID of type of web application in Google.
  • Set up an authorization provider like below, and provide the consumer key and secret.
  • Update the redirect URI in the OAuth client ID with the callback URL generated after creating the auth provider.
  • Add the Google sign-in button by updating the Authentication Configuration in My Domain.

Just-in-Time Provisioning for SAML

  • With Just-in-Time provisioning, you can use a SAML assertion to create regular and portal users on the fly the first time they try to log in. This eliminates the need to create user accounts in advance. 
  • For example, if you recently added an employee to your organization, you don't need to manually create the user in Salesforce. When they log in with single sign-on, their account is automatically created for them, eliminating the time and effort with on-boarding the account. 
  • Just-in-Time provisioning works with your SAML identity provider to pass the correct user information to Salesforce in a SAML 2.0 assertion. You can both create and modify accounts this way. 
  • Because Just-in-Time provisioning uses SAML to communicate, your organization must have SAML-based single sign-on enabled.
  • JIT provisioning uses attributes in the SAML assertion to create and update user records in Salesforce.
  • The SAML assertion must provide (at least):
    • Email
    • LastName
    • UserName
    • ProfileId - 15 character ID or profile name
  • It can also include any other optional attributes, such as
    • FirstName
    • Phone
    • Manager

Troubleshooting SAML

  • In Salesforce, from Setup, enter Single Sign-On Settings in the Quick Find box, then select Single Sign-On Settings.
  • Click SAML Assertion Validator. The SAML Validator shows the last recorded SAML login failure with some details as to why it failed.
  • To test the SAML assertion from the Axiom app, copy the Formatted SAML Response from the Axiom app.
  • In the Salesforce SAML Validator, paste the SAML assertion in the SAML Response box at the bottom of the page.
  • Click Validate. The page displays some results to help you troubleshoot the assertion. For example, if the assertion was generated a while before it was used to log in, the timestamp expires and the login isn’t valid. In that case, regenerate the SAML assertion and try again.
Share This:    Facebook Twitter

Thursday, February 6, 2020

Salesforce Platform Events

Platform events are part of Salesforce’s enterprise messaging platform, providing an event-driven messaging architecture to enable apps to communicate inside and outside of Salesforce. An event-driven (or message-driven) software architecture consists of event producers, event consumers, and channels. The architecture is suitable for large distributed systems because it decouples event producers from event consumers, thereby simplifying the communication model in connected systems. One or more subscribers can listen to the same event and carry out actions.

Systems in request-response communication models make a request to a web service or database to obtain information about a certain state. The sender of the request establishes a connection to the service and depends on the availability of the service. Also, the external system pushing data into Salesforce is dependent on the completion of the transaction, i.e. if data is being pushed into an sobject and if there are triggers running on the data being pushed, then the external system must wait until all the business logic in the trigger completes its execution. 
In comparison, systems in an event-based model obtain information and can react to it in near real-time when the event occurs. Event producers don’t know the consumers that receive the events. Any number of consumers can receive and react to the same events. Both parties are also not dependent on each other.
The only dependency between producers and consumers is the semantic of the message content.

A platform event is a special kind of Salesforce entity, similar in many ways to an sObject. But
  1. Unlike custom or standard objects, you can’t update or delete event records. All platform event fields are read-only by default.
  2. You can set read and create permissions for platform events. Grant permissions to users in profiles or in permission sets. 
  3. You can’t restrict access to a particular field. Field-level security permissions don’t apply and the event message contains all fields.
  4. Because event publishing is equivalent to a DML insert operation, DML limits and other Apex governor limits apply.

Publish Immediately vs Publish After Commit

Platform event messages are published either immediately or after a transaction is committed, depending on the publish behavior you set in the platform event definition.
  1. Publish Immediately: The event message is published when the publish call executes. Select this option if you want the event message to be published regardless of whether the transaction succeeds. Also choose this option if the publisher and subscribers are independent, and subscribers don't rely on data committed by the publisher. For example, the immediate publishing behavior is suitable for an event used for logging purposes. With this option, a subscriber might receive the event message before data is committed by the publisher transaction. The allOrNone header is ignored when publishing through the APIs. Some events can be published even when others fail in the same call.
  2. Publish After Commit: The event message is published only after a transaction commits successfully. Select this option if subscribers rely on data that the publishing transaction commits. For example, a process publishes an event message and creates a task record. A second process that is subscribed to the event is fired and expects to find the task record. Another reason for choosing this behavior is when you don't want the event message to be published if the transaction fails. The allOrNone header value takes effect. If allOrNone is set to true, no events are published if at least one event fails in the same call.

High-Volume Platform Events

In API version 45.0 and later, your new custom event definitions are high volume by default. High-volume platform events offer better scalability than standard-volume platform events.
High-volume platform events are published asynchronously. After the publishing call returns with a successful result, the publish request is queued in Salesforce. The event message might not be published immediately.
High-volume platform event messages are stored for 72 hours. You can retrieve past event messages when using CometD clients to subscribe to a channel.
To know about high-volume platform event default allocations, check the documentation.

Publishing Platform Events

  • To publish event messages with Process Builder, add a Create a Record action to the appropriate process. Where you'd usually pick an object to create, select the custom platform event.

  • To publish event messages with flows, add a Create Records element to the appropriate flow. Where you'd usually pick an object to create, select the custom platform event.

  • To publish event messages with Apex, call the EventBus.publish method. Each EventBus.publish method call is considered a DML statement, and DML limits apply. The below code is taken from the documentation to show how to publish platform events with Apex.
    List<Low_Ink__e> inkEvents = new List<Low_Ink__e>(); 
    inkEvents.add(new Low_Ink__e(Printer_Model__c='XZO-5', Serial_Number__c='12345', Ink_Percentage__c=0.2)); 
    List<Database.SaveResult> results = EventBus.publish(inkEvents); 
    for (Database.SaveResult sr : results) { 
        if (sr.isSuccess()) { 
            System.debug('Successfully published event.'); 
        } else { 
            for(Database.Error err : sr.getErrors()) { 
                System.debug('Error returned: ' + err.getStatusCode() + ' - ' + err.getMessage()); 
            } 
        } 
    }

  • Publish events with Salesforce APIs such as SOAP API, REST API, or Bulk API. To publish a platform event message using REST API, send a POST request to the following endpoint: /services/data/v48.0/sobjects/Event_Name__e/

When publishing an event message, the result that the API returns contains information about whether the operation was successful and the errors encountered. If the success field is true, the event was published for a standard-volume event. For a high-volume event, the publish request is queued in Salesforce and the event message might not be published immediately. If the success field is false, the event publish operation resulted in errors, which are returned in the errors field.
The returned result also contains the Id system field. The Id field value is not included in the event message delivered to subscribers. It is not used to identify an event message, and is not always unique. Subscribers can use the ReplayId system field, which is included in the delivered message, to identify the position of the event in the stream.

Subscribing to Platform Events

  • Both processes and flows can subscribe to platform events and receive event messages published through Apex, APIs, flows, or other processes. Both of them provide an auto subscription mechanism.

  • To subscribe to event notifications with Apex Triggers, write an after insert trigger on the event object type. Only after insert triggers are supported for platform events because event notifications can’t be updated. They’re only inserted (published).
    trigger LowInkTrigger on Low_Ink__e (after insert) {    
        for (Low_Ink__e event : Trigger.New) {
         ...
         ...
        }
    }

    NOTE: In platform event triggers, if you create a Salesforce record that contains an ownerId field, set the ownerId field explicitly to the appropriate user. Platform event triggers run under the Automated Process entity. If you don’t set the ownerId field on records that contain this field, the system sets the default value of Automated Process.

  • Subscribe to platform events with the empApi component in your Lightning web component or Aura component.

  • Use CometD to subscribe to platform events in an external client. The process of subscribing to platform event notifications through CometD is similar to subscribing to PushTopics or generic events. The only difference is the channel name. The platform event channel name is case-sensitive and is in the following format: /event/Event_Name__e. Use this CometD endpoint with the API version appended to it: /cometd/48.0. You can refer the below code to subscribe to a platform event in a Node application.
  • const jsforce = require("jsforce");
    const dotenv = require("dotenv");
    const express = require("express");
    const session = require("express-session");
    
    dotenv.config({
        path: "./config/config.env"
    });
    
    const app = express();
    
    // Configure express-session to store Session Id and Instance URL
    app.use(
        session({ secret: "keyboard cat", resave: true, saveUninitialized: true })
    );
    
    const oauth2 = new jsforce.OAuth2({
        loginUrl: process.env.SF_LOGIN_URL,
        clientId: process.env.SF_CLIENT_ID,
        clientSecret: process.env.SF_CLIENT_SECRET,
        redirectUri: process.env.SF_REDIRECT_URI
    });
    
    app.get("/oauth2/auth", function(req, res) {
        res.redirect(
            oauth2.getAuthorizationUrl({ scope: "api id web refresh_token" })
        );
    });
    
    app.get("/oauth2/callback", function(req, res) {
        var conn = new jsforce.Connection({ oauth2: oauth2 });
        conn.authorize(req.query.code, function(err, userInfo) {
            if (err) {
                return console.error(err);
            }
            console.log(userInfo);
            console.log(conn.accessToken, conn.refreshToken, conn.instanceUrl);
    
            req.session.accessToken = conn.accessToken;
            req.session.instanceUrl = conn.instanceUrl;
    
            res.redirect("/");
        });
    });
    
    app.get("/", function(req, res) {
        if (!req.session.accessToken || !req.session.instanceUrl) {
            res.redirect("/oauth2/auth");
            return;
        }
        // Connect and output query results
        const conn = new jsforce.Connection({
            accessToken: req.session.accessToken,
            instanceUrl: req.session.instanceUrl
        });
        res.send(req.session.instanceUrl);
        subscribeToEvents(conn, res);
    });
    
    function subscribeToEvents(conn, res) {
        conn.streaming.topic("/event/DemoEvent__e").subscribe(function(message) {
            console.log(message);
        });
    }
    
    const PORT = process.env.PORT || 3000;
    
    app.listen(PORT, console.log(`Server running on ${PORT}`));

ReplayId System Field

Each event message is assigned an opaque ID contained in the ReplayId field. The ReplayId field value, which is populated by the system when the event is delivered to subscribers, refers to the position of the event in the event stream. Replay ID values are not guaranteed to be contiguous for consecutive events. A subscriber can store a replay ID value and use it on resubscription to retrieve events that are within the retention window.
You can replay platform events that were sent in the past 24 hours. You can replay platform events through the API (CometD) but not Apex. The process of replaying platform events is the same as for other Streaming API events. You can use workbench to replay the platform events.


Standard Platform Events

Salesforce provides events with predefined fields, called standard platform events. An example of a standard platform event is AssetTokenEvent, which monitors OAuth 2.0 authentication activity. Another example is BatchApexErrorEvent, which reports errors encountered in batch Apex jobs (check the code for force-brf - a batch retry framework, to understand how to handle such event). You can subscribe to a standard platform event stream using the subscription mechanism that the event supports. Check the documentation to go through all the standard platform events.

Limits

Check the documentation to learn about the allocations available for platform event definitions, publishing and subscribing to platform events, and event delivery in CometD clients. The below table provides information about the default allocation for the high-volume events.
Please note the following points:
  1. The maximum event message size that you can publish is 1 MB. If your event object has hundreds of custom fields or many long text area fields, you could hit this limit. In this case, the publishing call gets an error.
  2. Non-CometD clients, including Apex triggers, processes, and flows, don’t count against the event delivery limit.
  3. The empApi Lightning component is a CometD client. As a result, the event delivery allocation applies to the component and it is per channel per unique browser session.
  4. Platform events ARE quite capable when only used “on-platform”. Read this blog to understand in detail.

Some important StackExchange links

Other important links



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