Saturday, December 28, 2019

Loopback calls - Local calls in salesforce




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


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
public with sharing class HttpService {

    public static String callout(String endPoint, String httpMethod, Map<String, String> headers, String body, Integer timeout) {
        Http http = new Http();
        HttpRequest httpRequest = new HttpRequest();
        httpRequest.setTimeout(timeout);
        httpRequest.setEndpoint(endPoint);
        httpRequest.setMethod(httpMethod);
        for(String key : headers.keySet() ) {
            httpRequest.setHeader(key, headers.get(key) );
        }
        httpRequest.setBody(body);
        System.debug('HttpService.callout request : ' + httpRequest);
        HttpResponse httpResponse = http.send(httpRequest);
        String responseBody = httpResponse.getBody();
        System.debug('HttpService.callout responseBody : ' + responseBody);
        return responseBody;
    }


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


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
/**
 * Created by Chintan Shah on 12/27/2019.
 */

@RestResource(urlMapping='/DemoRestService/*')
global with sharing class DemoRestService {

    @HttpGet
    global static String doGet() {
        System.debug('DemoRestService.doGet currentUser ' + UserInfo.getUserName() );
        return [ select id from Account limit 1].Name;
    }

    @HttpPost
    global 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.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
    @TestVisible
    private static String getValueFromXMLString(string xmlString, string keyField){
        string valueFound = '';
        if( xmlString.contains('<' + keyField + '>') && xmlString.contains('</' + keyField + '>') ) {
            try{
                valueFound = xmlString.substring(xmlString.indexOf('<' + keyField + '>') + keyField.length() + 2, xmlString.indexOf('</' + keyField + '>'));
            } catch (exception e){
                System.debug('Error in getValueFromXMLString.  Details: ' + e.getMessage() + ' keyfield: ' + keyfield);
            }
        }
        return valueFound;
    }

    public static String getTokenNamedCredentials(String namedCredential, String version) {
        String body = '<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:partner.soap.sforce.com">'
                + '           <soapenv:Body> '
                + '              <urn:login> '
                + '                 <urn:username>{!$Credential.Username}</urn:username> '
                + '                 <urn:password>{!$Credential.Password}</urn:password> '
                + '              </urn:login> '
                + '           </soapenv:Body> '
                + '       </soapenv:Envelope>';
        String endPoint = 'callout:' + namedCredential + '/services/Soap/u/' + version ;
        Map<String, String> headers = new Map<String, String> {
            'SFDC_STACK_DEPTH' => '1',
            'SOAPAction' => 'DoesNotMatter',
            'Accept' => 'text/xml',
            'Content-type' => 'text/xml',
            'charset' => 'UTF-8'
        };
        String responseBody = callout(endPoint, 'POST', headers , body, 60000);
        System.debug('HttpService.getTokenNamedCredentials responseBody : ' + responseBody );
        String token = getValueFromXMLString( responseBody, 'sessionId');
        System.debug('HttpService.getTokenNamedCredentials token : ' + token );
        return token;
    }


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.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
 * Created by Chintan Shah on 12/27/2019.
 */

public with sharing class DemoRestServiceProxyClient {

    public static String callDemoServiceOverHttp() {
        String token = HttpService.getTokenNamedCredentials('LocalEntry','47.0');
        String endPoint = Url.getSalesforceBaseUrl().toExternalForm() + '/services/apexrest/DemoRestService';
        String body = JSON.serialize( new Map<String, Object> {
            'name' => 'Chintan'
        } );
        Map<String, String> headers = new Map<String, String> {
            'Authorization' => 'OAuth ' + token,
            'Content-Type' => 'application/json'
        };
        System.debug(' DemoRestServiceProxyClient.callDemoServiceOverHttp  endPoint ' + endPoint + ' token ' + token + ' body ' + body );
        String response = HttpService.callout(endPoint,  'POST', headers, body, 60000);
        System.debug(' DemoRestServiceProxyClient.callDemoServiceOverHttp response ' + response );
        return response;
    }

}


We can see the output. Salesforce doesn't print the session Id, but we can see the output of the call.