Showing posts with label RESTful. Show all posts
Showing posts with label RESTful. Show all posts

Wednesday, November 25, 2015

Implementing no CAPTCHA reCAPTCHA in PeopleSoft

This post is based on a great discussion concerning security on the OTN forums.

The requirement was to solve the problem of bots/hacker websites hijacking users to their malicious locations (masquerading the legitimate PeopleSoft site), scraping the credentials from the users and then posting the data into the PeopleSoft signin page in a typical phishing attack scenario.

If you read through the discussion, you will notice some very valid questions, concerns and suggestions provided by Greg Kelly from Oracle and others around how to ensure that such phishing emails and attacks can be prevented.

Outside of that discussion, traditionally CAPTCHA has been used in several websites to prevent bot attacks. Now Google's reCAPTCHA project is being used more commonly since its inception in 2009. The latest version (no CAPTCHA reCAPTCHA) has significant user experience improvements. Additionally, the reCAPTCHA API version 2.0 is now a RESTful API which makes server side validation a lot easier. Another great incentive to use Google's no CAPTCHA reCAPTCHA is the fact that it is FREE!

Before going down the path of implementing reCAPTCHA, please consider accessibility and user experience implications and test accordingly. Click here for an article that talks about accessibility features of reCAPTCHA.

Here are the steps to implement Google reCAPTCHA API version 2.0 in PeopleSoft.

Note: I am currently using a HCM 9.2 PUM Image 12 - PeopleTools 8.54.08.
Reference Document: Developer's Guide

Step 1: Sign up for an API key pair

Please click here to sign up on the Goolge reCAPTCHA website. Once you sign up for an API key pair to your site, you will be provided with two keys - secret key and site key.

Step 2: Client side integration

In this case, let us assume that we want to add reCAPTCHA to the PeopleSoft signin page. So the signin page will be our client side content where we would add the reCAPTCHA code.

Here are the instructions from the Google reCAPTCHA documentation:


Let us add the above code snippet to the PeopleSoft signin page (signin.html).

Note: We can locate the signin.html file in the following directory on the web server.
<PIA_HOME>/webserv/<DOMAIN>/applications/peoplesoft/PORTAL.war/WEB-INF/psftdocs/<site_name>/

Add the following script inclusion code just before </HEAD> as shown:
<!-- CSK reCAPTCHA - Start -->
<script src='https://www.google.com/recaptcha/api.js'></script>
<!-- CSK reCAPTCHA - End -->


 Add the following div (reCAPTCHA) in an appropriate location on the content page. I chose to add it right before the submit button which could be referenced by <input name="Submit" type="submit" title="<%=137%>" class="ps-button" value="<%=137%>">.

<!-- CSK reCAPTCHA - Start -->
    <div class="g-recaptcha" data-sitekey="<ENTER_YOUR_SITE_KEY_HERE>" align="center"></div>
    <br/>
<!-- CSK reCAPTCHA - End -->


Note: If you are on PeopleTools 8.54 then you might also want to perform the same customizations to signin_fmode.html file as well. This is due to a known issue that will be fixed in PeopleTools 8.55. Refer: E-FLUID - Fluid HomePage Sign Off Link Calls fmode=1 in URL, Skips Custom signout.html Page (Doc ID 2001761.1).

Please bounce and clear the cache on your web server domains. Let us now see the reCAPTCHA plugin in action on the client side.






We are not done yet! Once verified, the reCAPTCHA integration would additionally add a string with the name "g-recaptcha-response" to the payload when the user submits (form post). This string would be a key that is generated specifically for our site. The string needs to be validated on the server side which we will explore in the next section.

Step 3: Server side integration/validation

This is the step where we verify the string provided by reCAPTCHA for validity by making a REST API call. Instructions from Google reCAPTCHA documentation.


For more details: Refer API documentation.

In this case, since we are trying to validate the reCAPTCHA response string provided by the PeopleSoft signin page, the best place to add the validation logic would be in the SignOn PeopleCode.

Let us now add a new row/entry in the sequence of Signon PeopleCode events as shown in the following screenshot. I am referencing a custom function name which I will explain shortly.


Record: CSK_RECAPTCHA
Field Name: FUNCLIB
Event Name: FieldFormula
Function Name: CSK_RECAPTCHA_CHECK

Note: After making any changes to Signon PeopleCode we must bounce the app servers for them to take effect.

Now let us see how to write some custom code to validate the reCAPTCHA response string. For reference, I pasted the entire peoplecode at the very end of this post.

Click on image to zoom in!


Note: In this case, I am using Apache Commons HttpClient Java Libraries to make the REST API call. Please refer to my previous blog posts that have more details on this topic (Part I and Part II).

Details on custom function validate_reCAPTCHA_JSON is provided below. For the purposes of JSON parsing, I am using a server side scripting technique described by Jim Marion in his blog post

Click on image to zoom in!


Message Catalog Entry that contains the JSON Parsing Script:


Script for reference:

var result = (function() {

     var json = JSON.parse(json_string);

     if (json.success) {
          return "true";
     } else {
          return "false";
     }

}());


This completes our server side integration/validation. Now we are relatively free of bot attacks! :)

Note: The same feature can be applied to any content page within the PeopleSoft application as well. If not easier, the effort should be the same as what we just went through in this post.

PeopleCode Functions for reference:

Function validate_reCAPTCHA_JSON(&json As string) Returns boolean;
  
   Local string &success;
  
   /* Instantiate ScriptEngine Manager and Engine objects */
   Local JavaObject &manager = CreateJavaObject("javax.script.ScriptEngineManager");
   Local JavaObject &engine = &manager.getEngineByName("JavaScript");
  
   /* Pass JSON string */
   &engine.put("json_string", &json);
   /* Get JSON Parse JavaScript from Message Catalog */
   Local string &jsFunction = MsgGetExplainText(26000, 1, "NONE");
  
   If &jsFunction <> "NONE" Then
      &engine.eval(&jsFunction);
      &success = &engine.get("result").toString();
   End-If;
  
   If Upper(&success) = "TRUE" Then
      Return True;
   Else
      Return False;
   End-If;
  
End-Function;

Function CSK_RECAPTCHA_CHECK()
  
   /* Get reCaptcha parameter from the Request */
   Local string &reCAPTCHA = %Request.GetParameter("g-recaptcha-response");
  
   If &reCAPTCHA = "" Then
      /* Fail Authentication */
      SetAuthenticationResult( False, %SignonUserId, "", False);
   Else
      /* Do further validation if required. Refer: https://developers.google.com/recaptcha/docs/verify */
      /* Post user's reCAPTCHA response and server's private key to API: https://www.google.com/recaptcha/api/siteverify */
      try
        
         /* Using Apache HttpClient for REST - Post Method */
         Local JavaObject &jHttp, &jMethod, &filePart, &partArray, &mPartReqEntity;
        
         /* Initialize HttpClient and set parameters */
         &jHttp = CreateJavaObject("org.apache.commons.httpclient.HttpClient");
         &jHttp.getHttpConnectionManager().getParams().setConnectionTimeout(20000);
        
         /* Initialize PostMethod */
         /* !!!!!! Avoid hardcoding !!!!!!!! Replace below URL with a URL definition that is configurable. */
         Local string &sURL = "https://www.google.com/recaptcha/api/siteverify";
         &jMethod = CreateJavaObject("org.apache.commons.httpclient.methods.PostMethod", &sURL);
         &jMethod.setFollowRedirects( False);
        
         /* Add Multi-Part Message - Start */
         /* Create String Part - secret */
         &secretPart = CreateJavaObject("org.apache.commons.httpclient.methods.multipart.StringPart", "secret", "<ENTER_YOUR_SECRET_KEY_HERE>");
         /* Create String Part - response */
         &responsePart = CreateJavaObject("org.apache.commons.httpclient.methods.multipart.StringPart", "response", &reCAPTCHA);
         /* Add Parts to Part Array */
         &partArray = CreateJavaObject("org.apache.commons.httpclient.methods.multipart.Part[]", &secretPart, &responsePart);
         /* Create Multi-Part Request Entity */
         &mPartReqEntity = CreateJavaObject("org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity", &partArray, &jMethod.getParams());
         /* Add Multi-Part Request Entity to the Post Method */
         &jMethod.setRequestEntity(&mPartReqEntity);
         /* Add Multi-Part Message - End */
        
         /* Invoke PostMethod */
         Local integer &return = &jHttp.executeMethod(&jMethod);
         Local string &responseBody = &jMethod.getResponseBodyAsString();
        
         If (validate_reCAPTCHA_JSON(&responseBody)) Then
            /* reCaptcha success: allow user */
            SetAuthenticationResult( True, %SignonUserId, "", False);
         Else
            /* reCaptcha failure: deny */
            SetAuthenticationResult( False, %SignonUserId, "", False);
         End-If;
        
      catch Exception &ex
         /* Unknown Exception */
         SetAuthenticationResult( False, %SignonUserId, "", False);
      end-try;
   End-If;
End-Function;

 

Wednesday, October 29, 2014

Apache Commons HttpClient for REST in PeopleCode - Part II - More Examples

This is a follow up to my previous post where we discussed how to use Apache Commons HttpClient for REST in PeopleSoft. Here are some more examples.

GetMethod - Adding Cookie (Header) to the Request:

Local JavaObject &jHttp, &jMethod;

/* Initialize HttpClient and set parameters */
&jHttp = CreateJavaObject("org.apache.commons.httpclient.HttpClient");
&jHttp.getHttpConnectionManager().getParams().setConnectionTimeout(20000);

/* Initialize GetMethod */
&sURL = "https://www.test.com/name.v1/get?emplid=12345";
&jMethod = CreateJavaObject("org.apache.commons.httpclient.methods.GetMethod", &sURL);
&jMethod.setFollowRedirects( False);

/* Adding Cookie to the Request */
&jMethod.setRequestHeader("Cookie", "JSESSIONID=64497D7D587637EBF17E128881C04016";
/* Adding Cookie to the Request */

/* Invoke GetMethod */
&return = &jHttp.executeMethod(&jMethod);
&responseStatus = &jMethod.getStatusLine().getStatusCode();

MessageBox(0, "", 0, 0, "Response Status: " | &responseStatus); 
MessageBox(0, "", 0, 0, "Response Message: " | &jMethod.getResponseBodyAsString());

&jMethod.releaseConnection();

PostMethod - Plain Text Content:

Local JavaObject &jHttp, jMethod;

/* Initialize HttpClient and set parameters */

&jHttp = CreateJavaObject("org.apache.commons.httpclient.HttpClient");
&jHttp.getHttpConnectionManager().getParams().setConnectionTimeout(20000);

/* Initialize PostMethod and Set Request Details */

Local string &sURL = "https://cas.test.com/cas/v1/tickets";
&jMethod = CreateJavaObject("org.apache.commons.httpclient.methods.PostMethod", &sURL);
&jMethod.setFollowRedirects( False);
&jMethod.setRequestHeader("Content-Type", "text/plain");
&jMethod.setRequestBody("username=testuser&password=testpassword");

/* Invoke PostMethod */

&jHttp.executeMethod(&jMethod);

Local integer &responseStatus = &jMethod.getStatusLine().getStatusCode();

Local string &responseBody = &jMethod.getResponseBodyAsString();

MessageBox(0, "", 0, 0, "&responseBody " | &responseBody);

MessageBox(0, "", 0, 0, "&responseStatus " | &responseStatus);

&jMethod.releaseConnection();

PostMethod - Multi-Part Message:

In this example, we will be posting a file on the App Server file system as a multi-part message.

Local JavaObject &jHttp,
&jMethod, &filePart, &partArray, &mPartReqEntity;

/* Initialize HttpClient and set parameters */

&jHttp = CreateJavaObject("org.apache.commons.httpclient.HttpClient");
&jHttp.getHttpConnectionManager().getParams().setConnectionTimeout(20000);

/* Initialize PostMethod */

Local string &sURL = "https://doc.mgmt.com/upload/v1/filename=test.pdf";
&jMethod = CreateJavaObject("org.apache.commons.httpclient.methods.PostMethod", &sURL);
&jMethod.setFollowRedirects( False);

/* Add Multi-Part Message */


/* Create File Object from App Server */

Local JavaObject &file = CreateJavaObject("java.io.File", "/tmp/test.pdf");

/* Create File Part */
&filePart = CreateJavaObject("org.apache.commons.httpclient.methods.multipart.FilePart", "test.pdf", &file);
/* Add File Part to Part Array */
&partArray = CreateJavaObject("org.apache.commons.httpclient.methods.multipart.Part[]", &filePart);
/* Create Multi-Part Request Entity */
&mPartReqEntity = CreateJavaObject("org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity", &partArray, &jMethod.getParams());
/* Add Multi-Part Request Entity to the Post Method */
&jMethod.setRequestEntity(&mPartReqEntity);

/* Add Multi-Part Message */

/* Invoke PostMethod */
Local integer &return = &jHttp.executeMethod(&jMethod);
Local integer &responseStatus = &jMethod.getStatusLine().getStatusCode();
Local string &responseBody = &jMethod.getResponseBodyAsString();

MessageBox(0, "", 0, 0, "&responseBody " | &responseBody);
MessageBox(0, "", 0, 0, "&responseStatus " | &responseStatus);

&jMethod.releaseConnection();


DeleteMethod:

Local JavaObject &jHttp, jMethod;

/* Initialize HttpClient and set parameters */
&jHttp = CreateJavaObject("org.apache.commons.httpclient.HttpClient");
&jHttp.getHttpConnectionManager().getParams().setConnectionTimeout(20000);

/* Initialize DeleteMethod */
Local string &sURL = "https://doc.mgmt.com/delete/v1/filename=test.pdf";
Local JavaObject &jMethod = CreateJavaObject("org.apache.commons.httpclient.methods.DeleteMethod", &sURL);
&jMethod.setFollowRedirects( False);

/* Invoke DeleteMethod */

Local integer &return = &jHttp.executeMethod(&jMethod);
Local integer &responseStatus = &jMethod.getStatusLine().getStatusCode();
Local string &responseBody = &jMethod.getResponseBodyAsString();

MessageBox(0, "", 0, 0, "&responseBody " | &responseBody);
MessageBox(0, "", 0, 0, "&responseStatus " | &responseStatus);

&jMethod.releaseConnection();

Tuesday, October 28, 2014

Apache Commons HttpClient for REST in PeopleCode - Part I - GetMethod and Basic Authentication Examples

Recently, I used Apache Commons HttpClient for making REST calls in PeopleCode for a project (interacting with a RESTful API of a Document Management System).

I found that using PeopleSoft Integration Broker for REST had the following limitations for my project:
1. Cookies (intended to be part of response messages) were getting lost and not coming through.
2. Dealing with, reading and processing response messages containing raw binary data (document).

Looking for workarounds, I then started exploring Apache Commons HttpClient after reading Chris Malek's blog and some posts in Jim Marion's blog.

The advantage of using Java is that it gives us a lot of flexibility to interact directly at the http layer. The disadvantage is that we would be bypassing Integration Broker (Gateway) which means we need to consider and implement functionality like logging, error handling and other built-in IB features such as authentication, etc.

There is also a distinction between the IB approach versus the Java approach.

IB Approach has the following flow:
App Server -> Web Server (IB Gateway) -> Third Party System

Java Approach has the following flow:
App Server -> Third Party System

I mention the above to point out that if there are any firewalls to be opened or if there are certificates/keys to be loaded then it needs to be done at the App Server level (if we are going with the Java Approach).

Stating the obvious, but another point to note is that this Java Approach is only an option for outbound REST calls.

That said, I would like to share my experiments and implementation experiences using Apache Commons HttpClient with some examples.

In this post, let us explore how to invoke the Get Method and use Basic Authentication. There are two options that I found to use Basic Authentication (Note: Based on OTN discussion).

Option 1 - Adding Authorization Header:

Local JavaObject &jHttp, &jMethod;

/* Initialize HttpClient and set parameters */
&jHttp = CreateJavaObject("org.apache.commons.httpclient.HttpClient");
&jHttp.getHttpConnectionManager().getParams().setConnectionTimeout(20000);

/* Initialize GetMethod */
&sURL = "https://www.test.com/name.v1/get?emplid=12345";
&jMethod = CreateJavaObject("org.apache.commons.httpclient.methods.GetMethod", &sURL);
&jMethod.setFollowRedirects( False);

/* Basic Auth - Adding Authorization Header */
/* &base64EncodeString = username:password encoded in base64 */
/* Note: In Production it is advised to store the base64 encoded string in a configuration table rather than hardcoding */
&base64EncodeString = "ABCD12345TESTBASE64";
&jMethod.setRequestHeader("Authorization", "Basic " | &base64EncodeString);
/* Basic Auth - Adding Authorization Header */

/* Invoke GetMethod */
&return = &jHttp.executeMethod(&jMethod);
&responseStatus = &jMethod.getStatusLine().getStatusCode();

MessageBox(0, "", 0, 0, "Response Status: " | &responseStatus); 
MessageBox(0, "", 0, 0, "Response Message: " | &jMethod.getResponseBodyAsString());

&jMethod.releaseConnection();

Option 2 - Using UsernamePasswordCredentials Class:


Local JavaObject &jHttp, &jMethod, &jCred, &jAuth;

/* Initialize HttpClient and set parameters */
&jHttp = CreateJavaObject("org.apache.commons.httpclient.HttpClient");
&jHttp.getHttpConnectionManager().getParams().setConnectionTimeout(20000);

/* Initialize GetMethod */
&sURL = "https://www.test.com/name.v1/get?emplid=12345";
&jMethod = CreateJavaObject("org.apache.commons.httpclient.methods.GetMethod", &sURL);
&jMethod.setFollowRedirects( False);

/* Basic Auth - Using UsernamePasswordCredentials Class */
&jCred = CreateJavaObject("org.apache.commons.httpclient.UsernamePasswordCredentials", "userid", "password");
&jAuth = CreateJavaObject("org.apache.commons.httpclient.auth.AuthScope", "www.test.com", 443);
/* Note: Use 443 if your host is using https or 80 if your host is using http */

&jHttp.getState().setCredentials(&jAuth, &jCred);
&jMethod.setDoAuthentication( True);
/* Basic Auth - Using UsernamePasswordCredentials Class */

/* Invoke GetMethod */
&return = &jHttp.executeMethod(&jMethod);
&responseStatus = &jMethod.getStatusLine().getStatusCode();

MessageBox(0, "", 0, 0, "Response Status: " | &responseStatus); 
MessageBox(0, "", 0, 0, "Response Message: " | &jMethod.getResponseBodyAsString());

&jMethod.releaseConnection();

Note: Here are the jars that I downloaded and placed on my app server classpath. An app server bounce/restart is required for the new jars to take effect.
- commons-httpclient-3.1.zip
- commons-codec-1.6.zip
- commons-logging-1.1.3.zip
- commons-io-2.4.zip (optional)

Few more examples of "Apache Commons HttpClient for REST in PeopleCode" to follow in Part II.