Wednesday, December 30, 2015

Twitter API integration with Salesforce :- User Authentication


             Sometimes we have to pull tweets from Twitter or post status to Twitter from Salesforce through Apex callouts. To interact with Twitter API on behalf of a user, an authentication has to be implemented. Twitter provides different types of authentication mechanisms. Here we can use  3-legged authorization.  The implementation includes the following steps.
  1. Register a Twitter Application
  2. Obtain the request token from Twitter
  3. Authenticate with User
  4. Obtain the Access token.

  1. Register a Twitter Application

            Log on to https://apps.twitter.com/ and go for Create new App and fill the necessary details. We can provide Website & Callback URL as our Visualforce page URL.

       

        After successful registration, we will get a consumer key & a consumer secret.

       


   2. Obtain the Request Token from Twitter


         To get the request token, we have to make a POST request to the Twitter API. For every API request, Twitter needs an Authorization header (API documentation). The header includes the following parameters
  • oauth_consumer_key -  The consumer key from Twitter app
  • oauth_nonce - An unique string to identify the request
  • oauth_signature -  A value which is generated by all of the request parameters and two secret values through a signing algorithm.
  • oauth_signature_method -  The signature method to generate oauth_signature. eg: HMAC-SHA1
  • oauth_timestamp - The number of seconds since the Unix epoch at the point the request is generated
  • oauth_version - Always be 1.0 for any request.
  • oauth_token - if available from Twitter (optional).

The API request as per the API documentation:

    Request Method: POST
    End point: https://api.twitter.com/oauth/request_token
    Parameters: oauth_callback = "the Visulaforce page URL"
    Header: Authorization header generated with the above values.

Salesforce settings

         Before going to callouts, we have to add the Twitter API URL (https://api.twitter.com) in remote site settings.  And we have to declare some constants in apex class.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
    final String oauthVersion = '1.0';
    final String oauthConsumerKey = '<YOUR KEY>';
    final String oauthConsumerSecret = '<YOUR SECRET>';
    final String baseUrl = 'https://api.twitter.com';
    final String oauthSignatureMethod = 'HMAC-SHA1';
    final String oauth_callback = '<YOUR VF PAGE URL>';
    
    String oauthTimestamp;
    String oauthNonce;
    String oauthToken;
    String oauthTokenSecret;
    String accessToken;
    String accessTokenSecret;

2.1 Generating oauth_timestamp in apex code


1
2
3
4
private void getTimeStamp(){
        DateTime dateTimeNow = dateTime.now();
        this.oauthTimestamp = ''+(dateTimeNow.getTime()/1000);
    }


2.2 Generating oauth_nonce in apex code



 1
 2
 3
 4
 5
 6
 7
 8
 9
10
private  void generateNounce() {
        final String chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz';
        String randStr = '';
        
        while (randStr.length() < 32) {
           Integer idx = Math.mod(Math.abs(Crypto.getRandomInteger()), chars.length());
           randStr += chars.substring(idx, idx+1);
        }
        this.oauthNonce =  EncodingUtil.base64Encode(Blob.valueOf(randStr)).remove('=');
    }


2.3  Generating oauth_signature  in apex code


             As per the API documentation, we have to collect all the parameters used in the request along with the oauth parameters and encrypt them by a signing algorithm. Also we have to create a signing key from consumer secret. We can use the Apex Crypto class for HMAC-SHA1 encoding.

 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
private String generateSignature(String httpMethod, String baseUrl, Map<String, String> params){
              
           String encodedString = '';
           Map<String, String> encodeParams = new Map<String, String>();       
           encodeParams.putAll(params);
           encodeParams.put('oauth_nonce', this.oauthNonce);
           encodeParams.put('oauth_signature_method', this.oauthSignatureMethod);
           encodeParams.put('oauth_timestamp', this.oauthTimestamp);
           encodeParams.put('oauth_consumer_key', this.oauthConsumerKey);
           encodeParams.put('oauth_version', this.oauthVersion);
           
           List<String> keyList = New List<String>();
           keyList.addAll(encodeParams.keySet());
           keyList.sort();
           
           for(String key: keyList){
               encodedString +=  EncodingUtil.urlEncode(key,'UTF-8') + '=' + EncodingUtil.urlEncode(encodeParams.get(key),'UTF-8') + '&';
           }
           encodedString = encodedString.removeEnd('&');
            
           String baseString = httpMethod.toUpperCase() + '&' + EncodingUtil.urlEncode(baseUrl,'UTF-8') + '&' + EncodingUtil.urlEncode(encodedString,'UTF-8');
           String signingKey = EncodingUtil.urlEncode(this.oauthConsumerSecret,'UTF-8') + '&';
           if(params.containsKey('oauth_token') && String.isNotBlank(this.oauthTokenSecret)){
               signingKey += EncodingUtil.urlEncode(this.oauthTokenSecret,'UTF-8');
           }   
           
           Blob data = Crypto.generateMac('hmacSHA1', Blob.valueOf(baseString), Blob.valueOf(signingKey));
           String signature =  EncodingUtil.base64Encode(data);
           return signature;
}  


2.4 Generating the authorization header in apex code



 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
private String generateAuthHeader(Map<String, String> params){
           
           Map<String, String> authParams = new Map<String, String>();
           authParams.putAll(params);
           authParams.put('oauth_consumer_key', this.oauthConsumerKey);
           authParams.put('oauth_signature_method', this.oauthSignatureMethod);
           authParams.put('oauth_timestamp', this.oauthTimestamp);
           authParams.put('oauth_nonce', this.oauthNonce);
           authParams .put('oauth_version', this.oauthVersion);
           
           List<String> keyList = New List<String>();
           keyList.addAll(authParams .keySet());
           keyList.sort();
           String OathString = '';    
           for(String key: keyList){
              OathString += EncodingUtil.urlEncode(key,'UTF-8') + '=' + '"' + EncodingUtil.urlEncode(authParams.get(key),'UTF-8') + '"' + ', '; 
           }
           OathString = 'OAuth ' + OathString.removeEnd(', ');
           return  OathString ;
    
} 

2.4 Apex callout to get the Request Token


      For this request we don't have the oauth_token & oauth_token_secret. We can use rest of the parameters for generating signature and the header.
           
 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
public void getAuthToken(){
    
         String requestUrl = this.baseUrl + '/oauth/request_token';
         String requestMethod = 'POST';
         
         this.getTimeStamp();
         this.generateNounce();
         
         Map<String, String> params = new Map<String, String>();
         params.put('oauth_callback', this.oauth_callback);         
         String authSignature = this.generateSignature(requestMethod, requestUrl, params);
         
         params = new Map<String, String>();
         params .put('oauth_callback',this.oauth_callback);
         params .put('oauth_signature', authSignature);
          
          HttpRequest request = new HttpRequest();
          request.setHeader('Authorization', this.generateAuthHeader(params));
          request.setMethod(requestMethod);
          request.setEndpoint(requestUrl);
          HttpResponse response = new HttpResponse();
          this.oauthToken = '';
          Http  http = new Http();
          try{
              response = http.send(request);
              String responseBody = response.getBody();
              this.oauthToken = responseBody.substringBefore('&').substringAfter('=');
              this.oauthTokenSecret = detail.substringAfter('&').substringBetween('=','&');
              ApexPages.currentPage().setCookies(new Cookie[]{new Cookie('TSecret', oauthTokenSecret, null, -1, false)});
          }catch(Exception e){
              system.debug(e.getMessage());
          }
          
          return null;
    }  

We will get a responds with oauth_token & oauth_token_secret as below:

oauth_token=Z6eEdO8MOmk394WozF5oKyuAv855l4Mlqo7hhlSLik&oauth_token_secret=Kd75W4OQfb2oJTV0vzGzeXftVAwgMnEK9MumzYcM&oauth_callback_confirmed=true

          After getting oauth_token & oauth_token_secret we have to go for user authentication.  After user authentication we need this oauth_token_secret for next request. So we need to store the oauth_token_secret  temporally. Here we can save this in a cookie.


  3. Authenticate with User


    As per the documentation, we have to call the authentication URL in browser with the oauth_ token obtained from previous step as a parameter. 


1
2
3
4
5
6
public PageReference authenticate(){
          
         String requestUrl = baseUrl + '/oauth/authenticate?oauth_token=' + this.oauthtoken + '&force_login=true';         
         return  new PageReference(requestUrl).setRedirect(true);
        
     }


This will redirect to the Twitter login page. After successful user login, Twitter redirects to our Visualforce page  with oauth_token & oauth_verifier as URL parameters.

4. Obtain the Access Token


After getting the oauth_varifier,  we have to obtain the access_token by another POST request.

The API request as per the API documentation:

    Request Method: POST
    End point: https://api.twitter.com/oauth/access_token
    Parameters: oauth_verifier  = The value we collected on previous step.
    Header: Authorization header

            To generate the header, follow the same steps for  Obtain the request token described above. So we have to generate new time_stamp, new nonce, new signature with new parameters and  new signing key as a combination of consumer_secret and oauth_token_secret (already saved in cookies). And we have to include the oauth_token for both signature and header.

The corresponding apex code:


 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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public void  getAccesToken(){
 
       this.oauthtoken = ApexPages.currentPage().getparameters().get('oauth_token');
       
       if(String.isNotBlank(this.oauthtoken)){
           
            String twitterId;
            Cookie counter = ApexPages.currentPage().getCookies().get('TSecret');
            if(counter != null) {
                this.oauthTokenSecret = counter.getValue();
                ApexPages.currentPage().setCookies(new Cookie[]{new Cookie('TSecret', '', null, -1, false)});
            }
    
           String requestUrl = this.baseUrl + '/oauth/access_token';
           String httpMethod = 'POST';        
           String oauthVerifier = ApexPages.currentPage().getparameters().get('oauth_verifier');
           
           this.getTimeStamb();
           this.generateNounce();
           
           Map<String, String> params = new Map<String, String>();
           params.put('oauth_token', this.oauthToken);
           params.put('oauth_verifier', oauthVerifier);           
           String authSignature = this.generateSignature(httpMethod, requestUrl, params);
           
           params = new Map<String, String>();
           params.put('oauth_token',this.oauthtoken);
           params.put('oauth_signature',authSignature);
           
           HttpRequest request = new HttpRequest();
           HttpResponse response = new HttpResponse();
           Http  http = new Http();
           request.setEndPoint(requestUrl);
           request.setMethod(httpMethod);
           request.setHeader('Authorization', this.generateAuthHeader(params));
           request.setBody('oauth_verifier='+EncodingUtil.urlEncode(oauthVerifier, 'UTF-8'));
           try{
              response = http.send(request);
              String responseBody = response.getBody();
              this.oauthToken = responseBody.substringBetween('oauth_token=', '&');
              this.oauthTokenSecret = responseBody.substringBetween('oauth_token_secret=', '&');
              twitterId = responseBody.substringBetween('user_id=', '&');
              
              detail = twitterId;
          }catch(Exception e){
              system.debug(e.getMessage());
          }
          
      }
       
}

  This will return the access_token, access_token_secret and the user details.

oauth_token=6253282-eWudHldSbIaelX7swmsiHImEL4KinwaGloHANdrY&oauth_token_secret=2EEfA6BG3ly3sR3RjE0IBSnlQu4ZrUzPiYKmrkVU&user_id=6253282&screen_name=twitterapi

Using this token we can interact with the Twitter API to post or get tweets.

References:


Tuesday, November 24, 2015

What is Saleforce.com ?


             Salesforce is an on-demand software which is hosted in cloud servers.  Salesforce allows us to create & implement our own applications in a secure cloud platform. Hence we don't worry about the hardware, operating systems, databases, maintenance cost, etc..  We just need a web browser and an internet connection.

           The subscription model of Salesforce provide us a software instance, where we can play our own games. Just a like the hostel rooms, we will be allotted to one room with basic furniture & interior amenities. There we can arrange our own interior and we can create extra facilities. And also if we need more space, we can add more rooms as required.

           The Salesforce has a built-in CRM (Customer Relationship Management) software, which is delivered entirely over internet. This CRM has the flexibility to customize as per our business rules. Inn-addition to the CRM, Salesforce provides a platform to create our own applications. It also provides a app store to exchange our custom Salesforce applications.


What Salesforce provide us?
  • A customizable CRM on cloud platform.
  • Set up & create Pont & Click applications over cloud.
  • A set of tools for business automation.
  • A secure platform to organize and store the data.
  • Customizable analytical reports & charts.
  • Well defined security model.
  • Customizable Email service.
  • Extended functionalities by customization.
  • Inbound & outbound integration with other systems.
  • A Platform to develop, test and deploy applications.
  • Platform to develop mobile friendly applications. 
NB: This for my future reference only
 


              




Salesforce: Test Class For Apex Email Service


This is the test class for the functionality Apex Email Service to create case from email, in Previous Article 


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
@isTest
private class Test_CustomEmailCase {
 static testMethod void testEmailToCase() {
        
        Test.startTest();
   // Create a new email and envelope object
            Messaging.InboundEmail testEmail = new Messaging.InboundEmail();
            Messaging.InboundEnvelope envelope = new Messaging.InboundEnvelope();        
            testEmail.subject = 'The email subject';
            testEmail.plainTextBody = 'The email body';
            testEmail.fromAddress = 'test.testing@sometest.com';
   
   // Send the email
   CustomEmailCase TestEmailCase = new CustomEmailCase();
   // Invock the handler function
            CustomEmailCase.handleInboundEmail(testEmail, envelope);
   
   system.assertEquals([SELECT Type FROM Case LIMIT 1].Type, 'New');
  Test.stopTest();
    }
}
NB: This is for my future reference.

Friday, March 20, 2015

Salesforce: Apex Email Service To Create Cases

Sometimes we have to create case form custom email formats. Sales force provides a Apex interface to process inbound emails.

1. Create Apex Class

      Create an Apex class to parse the email content and create case as per your business logic.

 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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
global class CustomEmailCase implements Messaging.InboundEmailHandler {

     global Messaging.InboundEmailResult handleInboundEmail(Messaging.InboundEmail email, Messaging.InboundEnvelope envelope) {
        Messaging.InboundEmailResult result = new Messaging.InboundEmailresult();
           //sendTestMail('plain'+email.plainTextBody);
           //sendTestMail('html'+email.htmlBody);
         // if (email.subject == 'subject1' || email.subject == 'Email Subject 2') {
          //sendTestMail(email.plainTextBody);
              /* Email Format - Plaint text body
                       NAME:       Riyas basheer
     PHONE:          0398489724
     EMAIL:          riyas@google.co.in
              */ 
              
              String pName = this.parseEmailContent(email.plainTextBody, 'NAME: ');
              String pEmail = this.parseEmailContent(email.plainTextBody, 'EMAIL: ');
              String pPhone  = this.parseEmailContent(email.plainTextBody, 'PHONE: ');
    
              // Using the email address from above, query the database for a matching Contact.
              if (pEmail.length() > 0) {
                Contact [] contactArray ;
                Case emailCase = new Case();
                for(Contact caseContact:[SELECT Id, Email, AccountId FROM Contact WHERE Email =: pEmail LIMIT 1]){
                
                   emailCase.ContactId = caseContact.Id;
                   emailCase.AccountId = caseContact.AccountId;
                }
               emailCase.Status = 'New';
               emailCase.Origin = 'the orgin';
               emailCase.Priority = 'Medium';
               emailCase.Type = 'my type';
               emailCase.Subject = ' '+email.subject;
               emailCase.p_Name__c = pName;
               emailCase.p_Email__c = pEmail;
               emailCase.p_Phone__c = pPhone ;
               for(Group g : [SELECT Id FROM Group WHERE name = 'myTeam']){
                    emailCase.OwnerId = g.id;
               }
               try {
                 insert emailCase;
               } catch (System.DmlException e)
                   {System.debug('ERROR: Not able to create Case: ' + e);
                 }
                
              }else if (pEmail == null || pEmail.length() == 0){
                System.debug('No email address was found.');
              }
       // }

        return result;

      } 
      
      
      private String parseEmailContent(String emailText, String dataLabal){
        emailText += '\n';
        String dataValue = '';
        Integer  labalIdx = emailText.indexOf(dataLabal);
        if(labalIdx >= 0){
            dataValue = emailText.substring(labalIdx + dataLabal.length(), emailText.indexOf('\n', labalIdx + dataLabal.length()));
            if(String.isNotBlank(dataValue)){
             dataValue = dataValue.trim();
            }
        }
        return dataValue;
      }
      
      private void sendTestMail(String body ){
          List<Messaging.SingleEmailMessage> mails = new List<Messaging.SingleEmailMessage>();
              Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
              List<String> sendTo = new List<String>();
              sendTo.add('riyas.salesforce@gmail.com');
              mail.setToAddresses(sendTo);
              mail.setReplyTo('riyas.salesforce@gmail.com');
              mail.setSenderDisplayName('DebugMail');
              mail.setSubject('Debug Mail');
              mail.setHtmlBody(body);
              mails.add(mail);
          Messaging.sendEmail(mails);
        }
        
} 

    Here I am parsing the email body and the crating the case and accoiating it with contact and account if the email is found in the system.

2. Setup Email Service

Go to Setup => Develop => Email Services => New Email Services ans select your apex class


 Save the changes and click on New Email Address button 


3. Setup Email Service Address

Fill the details and Save 


You get a email address like below



  By sending email to this address creates the case.


Extras 

One major concern is the debugging of apex class. It will not appear in debugg logs. So I have to created a custom apex email function "private void sendTestMail()" to send the details to my Emails.  

NB: This is for my future reference.