Wednesday 23 December 2020

JSON request body for creating an Account and Contact with Composite Graph API

 Hi,

Here we are going to learn 

How to do two composite graph requests each create an Account and then create related records:

Endpoint Url:

  1. <instance.salesforce.com(baseurl)>/services/data/vXX.X/composite/graph

Method Type: POST

Request Body:

{
    "graphs" : [
        {
            "graphId" : "1",
            "compositeRequest" : [
                {
                    "url" : "/services/data/v50.0/sobjects/Account/",
                    "body" : {
                        "name" : "Cloudy Consulting"
                    },
                    "method" : "POST",
                    "referenceId" : "reference_id_account_1"
                },
                {
                    "url" : "/services/data/v50.0/sobjects/Contact/",
                    "body" : {
                        "FirstName" : "Nellie",
                        "LastName" : "Cashman",
                        "AccountId" : "@{reference_id_account_1.id}"
                    },
                    "method" : "POST",
                    "referenceId" : "reference_id_contact_1"
                },
                {
                    "url" : "/services/data/v50.0/sobjects/Opportunity/",
                    "body" : {
                        "CloseDate" : "2024-05-22",
                        "StageName" : "Prospecting",
                        "Name" : "Opportunity 1",
                        "AccountId" : "@{reference_id_account_1.id}"
                    },
                    "method" : "POST",
                    "referenceId" : "reference_id_opportunity_1"
                }
            ]
        },
        {
            "graphId" : "2",
            "compositeRequest" : [
                {
                    "url" : "/services/data/v50.0/sobjects/Account/",
                    "body" : {
                        "name" : "Easy Spaces"
                    },
                    "method" : "POST",
                    "referenceId" : "reference_id_account_2"
                },
                {
                    "url" : "/services/data/v50.0/sobjects/Contact/",
                    "body" : {
                        "FirstName" : "Charlie",
                        "LastName" : "Dawson",
                        "AccountId" : "@{reference_id_account_2.id}"
                    },
                    "method" : "POST",
                    "referenceId" : "reference_id_contact_2"
                }
            ]
        }
    ]
}


Response:

{ "graphs" : [ { "graphId" : "1", "graphResponse" : { "compositeResponse" : [ { "body" : { "id" : "001R00000064wc7IAA", "success" : true, "errors" : [ ] }, "httpHeaders" : { "Location" : "/services/data/v50.0/sobjects/Account/001R00000064wc7IAA" }, "httpStatusCode" : 201, "referenceId" : "reference_id_account_1" }, { "body" : { "id" : "003R000000DDMlTIAX", "success" : true, "errors" : [ ] }, "httpHeaders" : { "Location" : "/services/data/v50.0/sobjects/Contact/003R000000DDMlTIAX" }, "httpStatusCode" : 201, "referenceId" : "reference_id_contact_1" }, { "body" : { "id" : "006R0000003FPYxIAO", "success" : true, "errors" : [ ] }, "httpHeaders" : { "Location" : "/services/data/v50.0/sobjects/Opportunity/006R0000003FPYxIAO" }, "httpStatusCode" : 201, "referenceId" : "reference_id_opportunity_1" } ] }, "isSuccessful" : true }, { "graphId" : "2", "graphResponse" : { "compositeResponse" : [ { "body" : { "id" : "001R00000064wc8IAA", "success" : true, "errors" : [ ] }, "httpHeaders" : { "Location" : "/services/data/v50.0/sobjects/Account/001R00000064wc8IAA" }, "httpStatusCode" : 201, "referenceId" : "reference_id_account_2" }, { "body" : { "id" : "003R000000DDMlUIAX", "success" : true, "errors" : [ ] }, "httpHeaders" : { "Location" : "/services/data/v50.0/sobjects/Contact/003R000000DDMlUIAX" }, "httpStatusCode" : 201, "referenceId" : "reference_id_contact_2" } ] }, "isSuccessful" : true } ] }

Reference:

Composite Graph API

 Hi,

Here we are going to learn what is Composite Graph API and how it works.

What is Composite Graph API:

  • It gives us the ability to execute multiple API requests in a single API call.
  • We can perform CRUD operations on a large number of related sObjects in a single API call, eliminating the need to orchestrate those relationships in custom code or perform multiple round trips to the server.
  • This increases the subrequest limit from 25 to 500, and also provides a number of optimizations that ensure records are processed efficiently and operations are rolled back if any steps are not completed.
  • Grouping collections of sObjects into graphs lets us do more with a single API call.

  • We can construct multiple graphs of related sObjects to insert multiple records within 
  • the same transaction. 
  • We can also access current records and then add records to them using reference ids.

How to Organize Subrequests Within a Graph:

  • The body of a Composite Graph API request consists of a number of graphs, each of which may contain multiple composite subrequests. 
  • Think of each graph as its own grouping of related sObject records.
Sample:

{
  "graphs": [
    {
      "graphId": "graphId",
      "compositeRequest": [
        compositeSubrequest1, 
        compositeSubrequest2, 
        ...]
    },
    {
      "graphId": "graphId2",
      "compositeRequest": [
        compositeSubrequest3, 
        compositeSubrequest4, 
        ...]
    }
  ]
}

Example:
{
  "graphs": [
    {
      "graphId": "graph1",
      "compositeRequest": [
        {
          "method": "PATCH",
          "url": "/services/data/v50.0/sobjects/Account/ExternalAcctId__c/ID12345",
          "referenceId": "newAccount",
          "body": {
            "Name": "Trailblazers",
            "Website": "TrailblazerOutfiters.com"
          }
        },
        {
          "method": "POST",
          "url": "/services/data/v50.0/sobjects/Order__c",
          "referenceId": "newOrder1",
          "body": {
            "Account__c": "@{newAccount.id}"
          }
        },
        {
          "method": "POST",
          "url": "/services/data/v50.0/sobjects/OrderItem__c",
          "referenceId": "newProduct1",
          "body": {
            "Order__c": "@{newOrder1.id}",
            "Product__c": {
              "External_Id__c": "EB1213"
            },
            "Qty_L__c": "1",
            "Price__c": "500"
          }
        }
      ]
    }
  ]
}

Here
  • Each subrequest contains a referenceId that can be used to relate records that follow the subrequest.
  • In the example above, the Account record is upserted and the referenceId is set to newAccount.
  • In the subrequest that follows, an order record is inserted and the Account__c field value is set to @{newAccount.Id}
  • which references the account that was just inserted.
  • Then that order record's referenceId (newOrder1) is used for the Order__c field value (@{newOrder1.id}).
curl Request:

curl --request POST \
--header "Authorization: Bearer token" \
--header "Content-Type: application/json" \
--data @data.json \
instance.salesforce.com/services/data/vXX.X/composite/graph

Sample HTTP Request:

String baseURL = URL.getSalesforceBaseUrl().toExternalForm(); 
String compositeGraphURL = baseURL + '/services/data/v50.0/composite/graph/';  
  
HttpRequest request = new HttpRequest();  
request.setMethod('POST');   
request.setHeader('Authorization', 'OAuth ' + UserInfo.getSessionId());        
request.setHeader('Authorization', 'Bearer ' + UserInfo.getSessionID()); 
request.setHeader('Content-Type', 'application/json');
 //Here we are getting data in JSON format from static resource

StaticResource resource = [
  SELECT Id, Body 
  FROM StaticResource 
  WHERE Name = 'graphs' 
  LIMIT 1
];
String body = resource.Body.toString();
  
request.setBody(body);  
request.setEndpoint(compositeGraphURL);  
String prettyResponse = '';
try {  
  Http http = new Http();   
  HttpResponse response = http.send(request);  
  if (response.getStatusCode() == 200 ) {  
    prettyResponse = JSON.serializePretty( JSON.deserializeUntyped(response.getBody()) );  
  } else {  
    System.debug(' response ' + response.getBody() );  
    throw new CalloutException( response.getBody() );  
  }   
} catch( System.Exception e) {  
  System.debug('ERROR: '+ e);  
  throw e;  
}  
System.debug('Response: ' + prettyResponse );




Monday 21 December 2020

Removing inaccessible relationship fields from the query result with Security.stripInaccessible

 Hi,

Let's see the following code how it handles when user doesn’t have permission to insert the Account__c field, which is a lookup from MyCustomObject__c to Account.

// Account__c is a lookup from MyCustomObject__c to Account

@isTest

   public class TestCustomObjectLookupStripped {

      @isTest static void caseCustomObjectStripped() {

         Account a = new Account(Name='foo');

         insert a;

         List<MyCustomObject__c> records = new List<MyCustomObject__c>{

            new MyCustomObject__c(Name='Custom0', Account__c=a.id)

         };

         insert records;

         records = [SELECT Id, Account__c FROM MyCustomObject__c];

         SObjectAccessDecision securityDecision = Security.stripInaccessible

                                                  (AccessType.READABLE, records);

         

         // Verify stripped records

         System.assertEquals(1, securityDecision.getRecords().size());

         for (SObject strippedRecord : securityDecision.getRecords()) {

             System.debug('Id should be set as Id fields are ignored: ' + 

                           strippedRecord.isSet('Id')); // prints true

             System.debug('Lookup field FLS is not READABLE to running user, 

                           should not be set: ' +

                           strippedRecord.isSet('Account__c')); // prints false

         }

      }

   }


Reference:

https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_classes_with_security_stripInaccessible.htm

How to sanitize sObjects that have been deserialized from an untrusted source with Security.stripInaccessible

 Hi ,

Let's assume we recieved  a json string with "Name" and "Annual Revenue" to update on Account.

But user doesn't have permisson to update Annual Revenue on Account object. 

Then we can avoid updating Annual Revenue on Account object as shown below.


String jsonInput =

'[' +

'{' +

'"Name": "InGen",' +

'"AnnualRevenue": "100"' +

'},' +

'{' +

'"Name": "Octan"' +

'}' +

']';


List<Account> accounts = (List<Account>)JSON.deserializeStrict(jsonInput, List<Account>.class);

SObjectAccessDecision securityDecision = Security.stripInaccessible(

                                         AccessType.UPDATABLE, accounts);

// Secure update

update securityDecision.getRecords(); // Doesn’t update AnnualRevenue field

System.debug(String.join(securityDecision.getRemovedFields().get('Account'), ', ')); // Prints "AnnualRevenue"

System.debug(String.join(securityDecision.getModifiedIndexes(), ', ')); // Prints "0”



Reference:

https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_classes_with_security_stripInaccessible.htm

How to remove inaccessible fields from sObjects before DML operations with Security.stripInaccessible

 Hi,

Let's take a scenario that 

 The user who doesn’t have permission to create Rating for an Account can still create an Account.

Her the method "Security.stripInaccessible" ensures that no Rating is set and doesn’t throw an exception.


Eg:

List<Account> newAccounts = new List<Account>();

Account a = new Account(Name='Acme Corporation');

Account b = new Account(Name='Blaze Comics', Rating=’Warm’);

newAccounts.add(a);

newAccounts.add(b);

SObjectAccessDecision securityDecision = Security.stripInaccessible(

                                         AccessType.CREATABLE, newAccounts);

// No exceptions are thrown and no rating is set

insert securityDecision.getRecords();

System.debug(securityDecision.getRemovedFields().get('Account')); // Prints "Rating"

System.debug(securityDecision.getModifiedIndexes()); // Prints "1"

Reference:
https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_classes_with_security_stripInaccessible.htm


How to remove inaccessible fields from the subquery result by using Security.stripInaccessible

 Hi,

Here we are going to learn how to remove inaccessible fields from the subquery:

Scenario:

Remove the "Phone" field on Contact Object that the user doesn’t have read permission.

Code Snippet:

List<Account> accountsWithContacts =

[SELECT Id, Name, Phone,

    (SELECT Id, LastName, Phone FROM Account.Contacts)

FROM Account];  

   // Strip fields that are not readable

   SObjectAccessDecision decision = Security.stripInaccessible(

                                   AccessType.READABLE,

                                   accountsWithContacts);

 // Print stripped records

   for (Integer i = 0; i < accountsWithContacts.size(); i++) 

  {

      System.debug('Insecure record access: '+accountsWithContacts[i]);

      System.debug('Secure record access: '+decision.getRecords()[i]);

   }

// Print modified indexes

   System.debug('Records modified by stripInaccessible: '+decision.getModifiedIndexes());

 // Print removed fields

   System.debug('Fields removed by stripInaccessible: '+decision.getRemovedFields());


Reference:
https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_classes_with_security_stripInaccessible.htm

How to remove inaccessible fields from the query result with Security.stripInaccessible

 Hi,

Here we are going to learn how to remove inaccessible fields from  the query:

Let's take a scenario 

A display table for campaign data must always show the BudgetedCost. The ActualCost must be shown only to users who have permission to read that field.

Code Snippet:

SObjectAccessDecision securityDecision = 

         Security.stripInaccessible(AccessType.READABLE,

                 [SELECT Name, BudgetedCost, ActualCost FROM Campaign];

                 );


    // Construct the output table

    if (securityDecision.getRemovedFields().get('Campaign').contains('ActualCost')) {

        for (Campaign c : securityDecision.getRecords()) {

        //System.debug Output: Name, BudgetedCost

        }

    } else {

        for (Campaign c : securityDecision.getRecords()) {

        //System.debug Output: Name, BudgetedCost, ActualCost

        }

}

Reference:

https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_classes_with_security_stripInaccessible.htm


Enforce Field- and Object-Level Security with Security.StripInaccessible

 Hi ,

Here we are going to learn about Security.StripInaccessible method.

  • This method mainly used for field- and object-level data protection
  • This allows developers to remove all fields from the records that the running user does not have access to.
  • This makes it easier to allow graceful degradation of application behavior on security violation by omitting fields rather than failing.

How does it work?

  • The field- and object-level data protection is accessed through the Security and SObjectAccessDecision classes.
  • We can use the StripInaccessible method to strip fields that the current user can’t access from query and subquery results.
  • It helps to remove inaccessible fields from sObjects before a DML operation to avoid exceptions.
  • This method helps sanitize sObjects that have been deserialized from an untrusted source.
  • The stripInaccesible method checks the source records for fields that don’t meet the field- and object-level security check for the current user and creates a return list of sObjects. 
  • The return list is identical to the source records, except that fields inaccessible to the current user are removed.
  • The sObjects returned by the getRecords method contain records in the same order as the sObjects in the sourceRecords parameter of the stripInaccessible method. 
  • Fields that aren’t queried are null in the return list, without causing an exception.
Note: Previously we were using dynamic apex for identifying access on particular object or field.

How to identify inaccessible fields that were removed:

We can use 'isSet' method.
For example, the return list contains the Contact object and the custom field social_security_number__c is inaccessible to the user. Because this custom field fails the field-level access check, the field is not set and "isSet" returns false.

SObjectAccessDecision securityDecision = Security.stripInaccessible(sourceRecords);
Contact c = securityDecision.getRecords()[0];
System.debug(c.isSet('social_security_number__c')); // prints "false"

Eg:(1)
This example removes fields from the query result that the current user does not have update access to.

SObjectAccessDecision securityDecision = 
       Security.stripInaccessible(AccessType.UPDATABLE,
             [SELECT Name, BudgetedCost, ActualCost FROM Campaign]);

Eg:(2)

This example performs a query and then removes inaccessible fields from the query result.

List<Contact> records = [SELECT Id, Name, Phone, HomePhone FROM Contact];
SObjectAccessDecision securityDecision = Security.stripInaccessible(AccessType.READABLE, records);

Example class before applying "stripInaccessible".


global with sharing class ApexSecurityRest {
    global static Contact doGet() {
        Id recordId = RestContext.request.params.get('id');
        Contact result;
        if (recordId == null) {
           throw new FunctionalException('Id parameter is required');
        }
        if (Schema.SObjectType.Contact.isAccessible()
          && Schema.SObjectType.Contact.fields.Name.isAccessible()
          && Schema.SObjectType.Contact.fields.Top_Secret__c.isAccessible()
        ) {
          List<Contact> results = [SELECT id, Name, Title, Top_Secret__c, Account.Name FROM Contact WHERE Id = :recordId];
          if (!results.isEmpty()) {
             result = results[0];
             if (Schema.sObjectType.Contact.fields.Description.isUpdateable()){
                 result.Description = result.Account?.Name;
                 }
             }
           } else {
             throw new SecurityException('You don\'t have access to all contact fields required to use this API');
           }
           return result;
      }
      public class FunctionalException extends Exception{}
      public class SecurityException extends Exception{}
}

After applying stripInaccessible


global with sharing class ApexSecurityRest {  
    global static Contact doGet() {
        Id recordId = RestContext.request.params.get('id');
        Contact result;
        if (recordId == null) {
           throw new FunctionalException('Id parameter is required');
        }
       
          List<Contact> results = [SELECT id, Name, Title, Top_Secret__c, Account.Name FROM Contact WHERE Id = :recordId];
          SObjectAccessDecision securityDecision = Security.stripInaccessible(AccessType.READABLE, results);
          if (!results.isEmpty()) {
             result = results[0];
             if (Schema.sObjectType.Contact.fields.Description.isUpdateable()){
                 result.Description = result.Account?.Name;
                }
            }
           
           return result;
      }
      public class FunctionalException extends Exception{}
      public class SecurityException extends Exception{}
}




Considerations:


References:
------------------



Sunday 20 December 2020

Safe Navigation Operator

Hi,

Here we learn how can we use Safe Navigation Operator:

We Use the safe navigation operator (?.) to replace explicit, sequential checks for null references.

This operator short-circuits expressions that attempt to operate on a null value and returns null instead of throwing a NullPointerException.

If the left-hand-side of the chain expression evaluates to null, the right-hand-side is not evaluated. Use the safe navigation operator (?.) in method, variable, and property chaining. The part of the expression that is not evaluated can include variable references, method references, or array expressions.

This example first evaluates a, and returns null if a is null. Otherwise, the return value is a.b.

a?.b // Evaluates to: a == null ? null : a.b

This example indicates that the type of the expression is the same whether the safe navigation operator is used in the expression or not.

Integer x = anObject?.anIntegerField; // The expression is of type Integer because the field is of type Integer

This example shows a single statement replacing a block of code that checks for nulls.

// Previous code checking for nulls

String profileUrl = null;

if (user.getProfileUrl() != null) {

   profileUrl = user.getProfileUrl().toExternalForm();

}

// New code using the safe navigation operator
String profileUrl = user.getProfileUrl()?.toExternalForm();

This example shows a single-row SOQL query using the safe navigation operator.

// Previous code checking for nulls

results = [SELECT Name FROM Account WHERE Id = :accId];

if (results.size() == 0) { // Account was deleted

    return null;

}

return results[0].Name;


// New code using the safe navigation operator

return [SELECT Name FROM Account WHERE Id = :accId]?.Name;



References:

https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/langCon_apex_SafeNavigationOperator.htm

https://trailhead.salesforce.com/en/content/learn/modules/platform-developer-i-certification-maintenance-winter-21/get-handson-with-field-and-objectlevel-security-and-safe-navigation-operator


Friday 18 December 2020

How to retrieve Lightning experience Theme and branding

 Hi,

Here we are going to learn how can we include Lightning Experience theme and Branding set in package.xml for retrieving and deploying the same.

<types>
        <members>"themeName"</members>
        <name>LightningExperienceTheme</name>
    </types>
    <types>
        <members>"BrandingSetName"</members>
        <name>BrandingSet</name>
    </types>

Reference:

https://developer.salesforce.com/docs/atlas.en-us.api_meta.meta/api_meta/meta_brandingset.htm



Wednesday 16 December 2020

Convert Source to Metadata Format and Deploy

 Hi ,

Here we are going learn how we can Convert Source format to Metadata Format and Deploy with the help of SFDX commands.

We can do this via command prompt or vs code terminal after opening the project.

Let's look into following simple steps:

  1. Create a folder to put the converted files called "mdapioutput" (any name can be given for Folder or directory name)
    • mkdir mdapioutput
  2. Run the convert command
    • sfdx force:source:convert -d mdapioutput/
  3. Deploy it to your testing environment or production 
    • sfdx force:mdapi:deploy -d mdapioutput/ 
  4. Assign permission set if there is any
    • sfdx force:user:permset:assign --permsetname <permset_name> --targetusername <username/alias>



Reference:

Sunday 13 December 2020

Check User Permissions for Lightning Web Components

 Hi,

When we develop Lightning web components we can customize a component’s behavior based on whether the current user has specific permission. 

To check whether a user has a permission, import Salesforce permissions from the @salesforce/userPermission and @salesforce/customPermission scoped modules and evaluate whether it’s true or undefined. Then if the user has the permission, the component can take a specific action.

Custom permissions can include a namespace. Orgs use namespaces as unique identifiers for their own customization and packages. If the custom permission was installed from a managed package, prepend the namespace followed by __ to the permission name.

Standard Permission Example:

import hasPermission from '@salesforce/userPermission/StandardPermissionName';

Custom Permission Examples: 

import hasPermission from '@salesforce/customPermission/CustomPermissionName';

import hasPermission from '@salesforce/customPermission/namespace__CustomPermissionName';

The name of the static reference is your choice. These examples chose the format hasPermission to indicate that the reference contains a Boolean.

Reference:

https://trailhead.salesforce.com/content/learn/modules/platform-developer-i-certification-maintenance-winter-21/learn-whats-new-in-lightning-web-components-and-visualforce

How to include a screen flow in a Lightning Web Component

 Hi, Assume  you have a flow called "Quick Contact Creation" and API Name for the same is "Quick_Contact_Creation". To i...