Chrome February 2020 update can break many integration which relies on cookies (which is heavily used in iframe based integration). Salesforce internally uses iframe to render VF pages on lightning, so that is broken as well as of now, till salesforce fixes it.
How does cookies work (especially in cross site or iframe - when multiple sites are involved)
E.g. let's say salesforce.com is hosting site, and inside the salesforce.com we are hosting facebook.com as an iframe.
1) user logs in to facebook.com, and upon login facebook saves the cookies about user session
2) user logs in to salesforce.com where we are hosting facebook.com iframe. When browser loads facebook in iframe, it passes the facebook cookies to facebook, so it is not challenged with username and password
This is good thing, however in certain cases if salesforce.com has bad code and/or facebook.com has vulnerable code, then facebook.com is vulnerable to cross side scripting attack.
With chrome February update, all cookies will be treated with SameSite=Lax if SameSite is not specified. Which means, in this case, browser will not send the cookies to facebook.com if it is coming from salesforce.com.
If you open facebook.com on separate browser tab, it would pass the cookies but not if embedded in any other site.
Solution
Other site will need to store the cookies with SameSite=None in order to get the old behavior.
For demo purpose, here is the Heroku web server, which stores cookies in different fashion and displays the cookies
displayCookies.html : displays cookies belongs to this site
storeCookies.html - stores cookies with no SameSite information (meaning it would be treated with None before February, and Lax after February release)
storeCookiesNone.html - stores cookies with SameSite=None
storeCookiesLax.html - stores cookies with SameSite=Lax
storeCookiesStrict.html - stores cookies with SameSite=Strict
On Salesforce I have visualforce page, which points to displayCookies.html
Default Chrome Behavior (before February 2020)
Scenario 1 : on browser go to storeCookies.html, then go to salesforce visual force page. This is working as expected
Scenario 2 : on browser goto storeCookiesNone.html, then go to salesforce visual force page. This is also working as expected. as it is default behavior.
Scenario 3 : on browser go to storeCookiesLax.html, then go to salesforce visual force page. This is removing cookies from iframe, which could cause undesired behavior.
Scenario 4 : on browser goto storeCookiesStrict.html, then go to salesforce visual force page. This is removing cookies from iframe, which could cause undesired behavior.
Default Chrome Behavior (after February 2020)
Scenario 1 : on browser goto storeCookies.html, then go to salesforce visual force page. This is removing cookies from iframe, which could cause undesired behavior. This would be working before February update.
Scenario 2 : on browser goto storeCookiesNone.html, then go to salesforce visual force page. This is working as expected!
Scenario 3 : on browser goto storeCookiesLax.html, then go to salesforce visual force page. This is removing cookies from iframe, which could cause undesired behavior.
similar to Scenario 1
Scenario 4 : on browser goto storeCookiesStrict.html, then go to salesforce visual force page.This is removing cookies from iframe, which could cause undesired behavior.
There are many situations when we have to make back to salesforce - e.g.
a) if we want to use tooling api or rest api for certain reason
b) if managed package is licensed to only certain user, and we want to proxy user
c) we want to perform action as some another user
Below solution is not highly advisable but it is hack that gets job done:
1) Named Credentials
Create named credential for the proxy user, this is the user which will be used to call web service
2) Generic HTTP Call out
Generic http callout method for calling out so we can reuse it multiple times
3) A demo web service
This is demo rest service which just prints the current user in session - inside we can put any logic that needs to be executed as proxy user
/*** Created by Chintan Shah on 12/27/2019.*/@RestResource(urlMapping='/DemoRestService/*')
globalwith sharing classDemoRestService {
@HttpGetglobal static String doGet() {
System.debug('DemoRestService.doGet currentUser '+ UserInfo.getUserName() );
return [ select idfromAccountlimit1].Name;
}
@HttpPostglobal static String doPost(String name) {
System.debug('DemoRestService.doPost currentUser '+ UserInfo.getUserName() +' name '+ name );
return name;
}
}
We will also need to setup security so that proxy user's profile has access to the Apex class
4) Get proxy user's session id
A service to get session id token using named credential, which can be used to call any rest service or soap service internally using the proxy user
To get token using rest service, you need to create connected app, but with SOAP that is not needed. Hence, I just use soap service to login as proxy user via named credentials and get the session id back. A little XML parsing doesn't hurt much either.
5) Call Demo service
Actually calling the demo service using the proxy user's session id. The code is quite simple here.
Line 8 gets session id for proxy user
Line 9 constructs the rest service url
Line 10 generates the payload
We do have to set the Authorization token in line 14 for header, and then callout is quite simple on line 18.
In Salesforce classic, we can use URL hack to clone the record and pre populate the data. In lightning, URL hack doesn't work. However, we can use e.force:createRecordcomponent to initialize the values for create record.
For cloning, we need to see previous records and sometime it is custom logic that we need to implement to initialize the value for cloning. Hence, we wrote generic component to take care of this. Currently it only supports fieldset.
We started with Base Clone Component (CloneBaseComponent)
/**
* Created by Spring7 on 9/19/2019.
*/
({
closeQuickActionModal : function(component){
var dismissActionPanel = $A.get('e.force:closeQuickAction');
dismissActionPanel.fire();
},
createRecord : function(component, defaultFieldValues) {
delete defaultFieldValues['Id'];
var createRecordEvent = $A.get('e.force:createRecord');
var sObjectApiName = component.get("v.sObjectApiName");
createRecordEvent.setParams({
entityApiName: sObjectApiName,
defaultFieldValues: defaultFieldValues,
});
createRecordEvent.fire();
}
})
How to use it
In Salesforce, we can not pass parameter to component when it is bound to a button. Hence we created a new component with just a few lines, which extends the base component and tie to a button.
CloneAccount Component
It calls CloneBaseComponent with fieldset (CloneAccount) which will be used for copy
1
2
3
4
5
6
7
8
<!-- - Created by Spring7 on 9/19/2019. --><aura:componentdescription="CloneAccount"extends="c:CloneComponentBase"implements="force:lightningQuickActionWithoutHeader,lightning:actionOverride,force:hasRecordId"><aura:setattribute="sObjectApiName"value="Account"><aura:setattribute="fieldSetName"value="CloneAccount"></aura:set></aura:set></aura:component>
CloneAccount Button
We will need to bind this component to button
CloneAccount FieldSet
We will need to create the field set which will be used by clone base component on which fields to be copied.
In salesforce, with UI, we can schedule the job on daily bases, and using cron expression we can do hourly. There is no facility to run the job every minute, or every 5 minutes. However salesforce allows to start the job a few seconds from now or a few minutes from now. We can use that hack to run the job every 5 minutes.
E.g. below : during the execute call, we can reschedule the job, which will abort existing job and create new job based on frequency provided.
This will run the job every 5 minutes. This is not best practice as we might be over using salesforce cloud resources, but it can come handy for certain situations.
Usually there are a lot of things needs to be done when doing sandbox refresh, e.g. activating uses, changing email address of contacts or other objects, etc.. We can plugin the sandbox refresh script mentioned here. However, we still have to write a lot of code or use tools to achieve many things needed depending on fullcopy or dev refresh.
We wrote framework to simplify the refresh of the sandbox, where we can add multiple plugins, and also added static resource configuration file which can help with plug in configuration.
Most of the script initiates the batch file, as the full copy data could be quite huge. Now all we need to do is to specify the class SandboxRefresh during the refresh of the sandbox.
I know it has been around for a long time, but recently found this useful feature, where I can request the logs of all emails that has been sent. It can be super useful for troubleshooting.
It would be nice to have some heroku app which can download this periodically and create cases for emails that are not delivered.
As Salesforce has introduced powerful mock framework for testing, we wrote thin layer on top of it to take full advantage of the dynamic mock class that you can create.
It is always a good practice to break down your classes, and methods into small chunks, and mostly take further reusable methods in services. However, at the time of testing, it gets hard to test your code if it relies on too many services method. You have to have to knowledge of the service.
In code, it may look like
/*** Created by Chintan Shah on 10/18/2019.*//*** this is the service that will be called by caller
* it would be faked out during the testing framework.*/
public with sharing classMockDemoService {
public List<String> toUpper(List<String> inputs) {
List<String> outputs = new List<String>();
for(String input : inputs ) {
outputs.add( input.toUpperCase() );
}
return outputs;
}
public String concat(List<String> inputs) {
String output = null;
for(String input : inputs ) {
output = ( output == null ?'' : ( output +' ' ) ) +input;
}
return output;
}
}
And caller
/*** Created by Chintan Shah on 10/18/2019.*//*** demo of how to use this framework.** MockDemoServiceCaller -> MockDemoService
** During testing, we will just mock the entire MockDemoService
**/
public with sharing classMockDemoServiceCaller {
/*** instance of mockDemoService
*/
public MockDemoService mockDemoService;
/*** regular constructor
*/
public MockDemoServiceCaller() {
mockDemoService = new MockDemoService();
}
/*** entry point for fake service
**@param*/
public MockDemoServiceCaller(MockDemoService mockDemoService) {
this.mockDemoService = mockDemoService;
}
/*** sample routine that we are going to test.*/
public String sampleRoutine(List<String> inputs) {
try {
System.debug(' MockDemoServiceCaller.sampleRoutine inputs '+ JSON.serialize(inputs) );
List<String> outputs = mockDemoService.toUpper( new List<String> { 'John', 'Doe' } );
System.debug(' MockDemoServiceCaller.sampleRoutine outputs '+ JSON.serialize(outputs) );
String output = mockDemoService.concat( outputs );
System.debug(' MockDemoServiceCaller.sampleRoutine output '+ output );
return output;
} catch(Exception e) {
throw new MockException('What\' up? '+ e.getStackTraceString() );
}
}
}
Now when we write the test class for MockDemoServiceCaller, it would be good to have full control over the output of mock demo service. We should be able to return various result including the exception. Salesforce mock framework allows you to mock the service so you have full control when you write the test class for service caller.
Here is example of the code. You can see in line 19 and 20, we create dynamic service instance using mock framework, as well as send mock individual method and return the response.
On line 34, we can see, we can even return the exception in the dynamic mock instance that we create, and hence we have full control on how to test the service caller.
/*** Created by Chintan Shah on 10/18/2019.*//*** test classfor MockDemoServiceCaller
*/@isTest
public with sharing classMockDemoServiceCallerTest {
@testSetup
public static void testSetup() {
}
@isTest
public static void testSampleRoutine1() {
MockProvider mockDeMockServiceProvider = new MockProvider( MockDemoService.class);
MockDemoService mockDemoService = (MockDemoService) mockDeMockServiceProvider.getMock();
mockDeMockServiceProvider.addReturnValue( 'toUpper', new List<String> { 'HELLO', 'WORLD' } );
mockDeMockServiceProvider.addReturnValue( 'concat', 'HELLO TEST' );
// initialize MockDemoServiceCaller with mock MockDemoService
MockDemoServiceCaller mockDemoServiceCaller = new MockDemoServiceCaller( mockDemoService );
String output = mockDemoServiceCaller.sampleRoutine( new List<String> { 'John', 'Doe'} );
System.assertEquals( output, 'HELLO TEST', 'It should be HELLO TEST based on mock ');
}
@isTest
public static void testSampleRoutine2() {
MockProvider mockDeMockServiceProvider = new MockProvider( MockDemoService.class);
MockDemoService mockDemoService = (MockDemoService) mockDeMockServiceProvider.getMock();
mockDeMockServiceProvider.addReturnValue( 'toUpper', new List<String> { 'HELLO', 'WORLD' } );
mockDeMockServiceProvider.addReturnValue( 'concat', new MockException('Test Exception') );
// initialize MockDemoServiceCaller with mock MockDemoService
MockDemoServiceCaller mockDemoServiceCaller = new MockDemoServiceCaller( mockDemoService );
try {
String output = mockDemoServiceCaller.sampleRoutine( new List<String> { 'John', 'Doe'} );
System.assert( false, 'Exception was expected, success was not expected ');
} catch(Exception e) {
System.assert( true, 'Exception was expected. ');
}
}
}
With wrapper around salesforce framework, we don't need to write individual mock classes, but we can declarative use what to mock and what it would return.
Web Service Mock
Web service mock framework has been around for years, however we have to write mock class for each web service we are trying to call. With this framework, we can declarative create dynamic instance of the mock class and test the class which is calling web service. e.g. below
/*** Created by Chintan Shah on 10/19/2019.*/@isTest
public with sharing classMockDemoWebServiceCallerTest {
@testSetup
public static void testSetup() {
}
@isTest
public static void testGetAllEmployees() {
MockHttpCallout mockHttpCallout = new MockHttpCallout();
mockHttpCallout.setMock();
mockHttpCallout.addJsonHttpResponse('https://basic-authentication-ws.herokuapp.com/hr/employees', '[{"ExternalId":"E-001","Name":"Chintan Shah","DisplayUrl":"https://external.com/E-001","EmployeeAccountKey":"Account-001"},{"ExternalId":"E-002","Name":"Chuck Summer","DisplayUrl":"https://external.com/E-002","EmployeeAccountKey":"Account-001"},{"ExternalId":"E-003","Name":"Chris Walker","DisplayUrl":"https://external.com/E-003","EmployeeAccountKey":"Account-001"},{"ExternalId":"E-004","Name":"Dan Topper","DisplayUrl":"https://external.com/E-004","EmployeeAccountKey":"Account-002"},{"ExternalId":"E-005","Name":"Drew Berry","DisplayUrl":"https://external.com/E-005","EmployeeAccountKey":"Account-002"},{"ExternalId":"E-006","Name":"Don Aiken","DisplayUrl":"https://external.com/E-006","EmployeeAccountKey":"Account-002"}]');
System.assertEquals( 6, new MockDemoWebServiceCaller().getAllEmployees().size(), 'Should be 6');
}
@isTest
public static void testGetEmployee() {
//
MockHttpCallout mockHttpCallout = new MockHttpCallout();
mockHttpCallout.setMock();
mockHttpCallout.addJsonHttpResponse('https://basic-authentication-ws.herokuapp.com/hr/employee/Name/Chintan', '[{"ExternalId":"E-001","Name":"Chintan Shah","DisplayUrl":"https://external.com/E-001","EmployeeAccountKey":"Account-001"}]');
System.assertEquals( 1, new MockDemoWebServiceCaller().getEmployee('Name','Chintan').size() , 'Should be 1');
}
@isTest
public static void testGetAllEmployees2() {
MockHttpCallout mockHttpCallout = new MockHttpCallout();
mockHttpCallout.setMock();
mockHttpCallout.addErrorResponse('https://basic-authentication-ws.herokuapp.com/hr/employees', 401 );
try {
new MockDemoWebServiceCaller().getAllEmployees().size();
System.assert( false, 'Exception was expected, success was not expected ');
} catch(Exception e) {
System.assert( true, 'Exception was expected. ');
}
}
@isTest
public static void testGetEmployee2() {
//
MockHttpCallout mockHttpCallout = new MockHttpCallout();
mockHttpCallout.setMock();
mockHttpCallout.addErrorResponse('https://basic-authentication-ws.herokuapp.com/hr/employee/Name/Chintan', 401);
try {
new MockDemoWebServiceCaller().getEmployee('Name','Chintan').size();
System.assert( false, 'Exception was expected, success was not expected ');
} catch(Exception e) {
System.assert( true, 'Exception was expected. ');
}
}
}
As you can see, we don't have to write the mock class for each web service, we simply just need to use the http mock callout and set the response as mentioned in line 25 and 33.