adiddigi
Tera Guru

Earlier last year, I blogged about a way to mimic oAuth using HTTP Client. But it is of no use because the package HTTPClient is retired from Calgary. Also, we aren't using the Service Now features to do the same. Henceforth, I came up with a another way to do oAuth authentication for GotoMeeting.

Again this might be a repetition of a post that I made long back, but don't seem to find that post. Also this isn't packaged well as the main goal of this post isn't re-usability, but to go over some details of oAuth.

Google, Twitter, Github, GotoMeeting and many others will follow a slightly different way to do a oAuth 2.0 authentication. I will slowly start adding more methods to this script soon for other websites too.

Philosophy of this Script Include:

1. Never use any Package code, unless absolutely necessary. Always use any Service Now OOB functionality if available.

That's the only rule this Script Include and it's corresponding code will abide by.

Before I start with the code, I will explain oAuth 2.0   for GotoMeeting in a nutshell:

1. From service now, redirect the user to the Citrix site, where the user will authenticate the third party( here it's service now)

2. Once the user authenticates, Citrix can redirect back to a Service Now URL where in it appends something called [code] code[/code]. We need to exchange this `code` to a Authorization code, with which you will do all your   other calls.

3. Store the Authorization code   somewhere, so that you can make subsequent calls until it expires.

Before you do any of this, you need to register yourself on the Citrix Developer site for a Developer Key, that you will use so that Citrix identifies your application. Also create an App.

Let's first prepare the URL that you need to point the user to, the below piece of code will do that :

_gotoCall:function(){

              //Some common variables.

              var developer_key = 'dce44dd4d7c3d5153a9342e81b7df08c';

              var URL = 'https://api.citrixonline.com/oauth/authorize?client_id='+developer_key;

              //Goto meeting's oAuth can be summarised this way.

              //1. Point the user's browser to the link upon which you will receive a code.

              //2. The code should be exchanged to a Access token

              //Check if the user already has a access token for GOTOMeeting

              var grOAuth   = new GlideRecord("u_oauth_token_info");

              grOAuth.addQuery("u_user",gs.getUserID());

              grOAuth.addQuery("state","valid");

              grOAuth.query();

              //We already have a User and his payload. Hence we don't do the calls again, until the access is

              //revoked. This will not be handles in this Script Include. As we only do oAuth.

              if(grOAuth.next()){

                      //if there is already an access token return JSON object which can be used for furthur calls.

     

                      return grOAuth.u_payload;

              }

              else{

                      //return the URL - and point it to browser.

                      //responsibilty of the code to check if it has a `https://` and decide if it's a JSON object

                      //or URL

                      //A redirect URL need to be specified like this : https://api.citrixonline.com/oauth/authorize?client_id={api_key}&redirect_uri={redirect}

     

                      var redirect_uri = 'https://<instancename>.service-now.com/oAuth_callback.do?';

                      redirect_uri = redirect_uri+"&sysparm_client=goto_meeting"+"&sysparm_userID="+this.user_id;

                      var finalURL = URL+'&redirect_uri='+redirect_uri;

                      if(this.enable_log) this.log("The URL being sent to Citrix "+finalURL);

                              return finalURL;

              }

      }

If there is already a Valid key for the user, then just return the payload which Contains the Authorization code. Else return the URL to which you need to point the code.

The user will need to login into that URL, Login and approve your application. Once he does that, GOTOmeeting calls a URL on Service Now.

You can use a UI page as a |redirect_url| but you need to make it a Public Page.

My redirect URL is something like this :   https://instanceName.service-now.com/oAuth_callback.do&sysparm_client=goto_meeting

Now, the reason why we are setting |sysparm_client| is because, we can extend the UI page by setting that parameter. Say you are going to do a Google oAuth integration, then you will just need to change |sysparm_client| to |google|.

And the code in the UI page is something like this( You can do a LOT here, I'm making it simple)

UI Page Name : oAuth_callback

[code]

<?xml version="1.0" encoding="utf-8" ?>

<j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide" xmlns:j2="null" xmlns:g2="null">

<g:evaluate>

var URL = RP.getReferringURL();

var code = URL.split('code')[1].replace('=','');

var gr = new GlideRecord("u_oauth_token_info");

gr.initialize();

gr.u_user = gs.getUserID();

gr.u_code = code;

gr.insert();

</g:evaluate>

You have been successfully authorized. You can now close this window.

</j:jelly>

[/code]

From the UI page, we glide the table, and update the record for the user with the code we received from Citrix.

The last step: Exchange |code| for |Authorization code|

gotoExchange:function(){

              var developer_key = 'dce44dd4d7c3d5153a9342e81b7df08c';

              var grOAuth   = new GlideRecord("u_oauth_token_info");

              grOAuth.addQuery("u_user",gs.getUserID());

              grOAuth.addQuery("state","valid");

              grOAuth.query();

              //We already have a User and his payload. Hence we don't do the calls again, until the access is

              //revoked. This will not be handles in this Script Include. As we only do oAuth.

              if(grOAuth.next()){

     

                      var r = new RESTMessage('GoTo Meeting', 'get');

     

     

                      r.setStringParameter('code',grOAuth.u_code);

                      r.setStringParameter('client_id',developer_key);

                      var response = r.execute();

                      gs.log(response.getBody());

                      if(response.haveError()){

                              grOAuth.u_payload   = response.getBody();

             

                      }

                      else{

             

                              var payload = response.getBody();

                             

                              grOAuth.u_payload = payload;

                              var expiryTime = new JSON().decode(payload)['expires_in'];//stores the expiry for this token in seconds.

                             

                              var hours = (0.000277778 * expiryTime)/24;

                             

                              var days = -1*hours;

                             

                              grOAuth.setValue('u_expiry_date',gs.daysAgo(days));

             

             

             

                      }

                      grOAuth.update();

     

     

              }

      }

As you can see, I am using something called |new RESTMessage()| you need to create a RESTMessage before hand and point it to this URL |https://api.citrixonline.com/oauth/access_token?grant_type=authorization_code&code=${code}&client_id...|

There you go. Once you execute the above script, you will get the JSON payload in the below format :

{

"access_token":"1234567890",

"expires_in":"30758399",

"refresh_token":"7ae3a10234234161914ec65b8db6650c",

"organizer_key":"2000000000003345",

"account_key":"200000000000002211",

"account_type":"corporate"

}

You can use the |access_token| to make any subsequent calls.

The below is the complete Script Include :

Name : OAuth

var OAuth = Class.create();

OAuth.prototype = {

      /* Constructor. It handles all the oAuth 2.0 calls*/

      initialize:function(/*Implementation client name*/ impl_name,/*enable log*/ enable_log){

              this.props = new Packages.java.util.Properties();//for storing properties.

              this.impl_name = impl_name;

              this.user_id = gs.getUserID();

              this.enable_log = enable_log;

      },

      /** Execute function, that handles all the oAuth 2.0 calls*/

      execute:function(){

              if(this.impl_name == 'goto_meeting'){

     

                      return this._gotoCall();

              }

      },

      _gotoCall:function(){

              //Some common variables.

              var developer_key = 'dce44dd4d7c3d5157a9342e81b7df08c'; //your developer key

              var URL = 'https://api.citrixonline.com/oauth/authorize?client_id='+developer_key;

              //Goto meeting's oAuth can be summarised this way.

              //1. Point the user's browser to the link upon which you will receive a code.

              //2. The code should be exchanged to a Access token

              //Check if the user already has a access token for GOTOMeeting

              var grOAuth   = new GlideRecord("u_oauth_token_info");

              grOAuth.addQuery("u_user",gs.getUserID());

              grOAuth.addQuery("state","valid");

              grOAuth.query();

              //We already have a User and his payload. Hence we don't do the calls again, until the access is

              //revoked. This will not be handles in this Script Include. As we only do oAuth.

              if(grOAuth.next()){

                      //if there is already an access token return JSON object which can be used for furthur calls.

     

                      return grOAuth.u_payload;

              }

              else{

                      //return the URL - and point it to browser.

                      //responsibilty of the code to check if it has a `https://` and decide if it's a JSON object

                      //or URL

                      //A redirect URL need to be specified like this : https://api.citrixonline.com/oauth/authorize?client_id={api_key}&redirect_uri={redirect}

     

                      var redirect_uri = 'https://infypov.service-now.com/oAuth_callback.do?';

                      redirect_uri = redirect_uri+"&sysparm_client=goto_meeting"+"&sysparm_userID="+this.user_id;

                      var finalURL = URL+'&redirect_uri='+redirect_uri;

                      if(this.enable_log) this.log("The URL being sent to Citrix "+finalURL);

                              return finalURL;

              }

      },

      gotoExchange:function(){

              var developer_key = 'dce44dd4d7c3d5153a9342e81b7df08c';

              var grOAuth   = new GlideRecord("u_oauth_token_info");

              grOAuth.addQuery("u_user",gs.getUserID());

              grOAuth.addQuery("state","valid");

              grOAuth.query();

              //We already have a User and his payload. Hence we don't do the calls again, until the access is

              //revoked. This will not be handles in this Script Include. As we only do oAuth.

              if(grOAuth.next()){

     

                      var r = new RESTMessage('GoTo Meeting', 'get');

     

     

                      r.setStringParameter('code',grOAuth.u_code);

                      r.setStringParameter('client_id',developer_key);

                      var response = r.execute();

                      gs.log(response.getBody());

                      if(response.haveError()){

                              grOAuth.u_payload   = response.getBody();

             

                      }

                      else{

             

                              var payload = response.getBody();

                     

                              grOAuth.u_payload = payload;

                              var expiryTime = new JSON().decode(payload)['expires_in'];//stores the expiry for this token in seconds.

                     

                              var hours = (0.000277778 * expiryTime)/24;

                     

                              var days = -1*hours;

                     

                              grOAuth.setValue('u_expiry_date',gs.daysAgo(days));

             

             

             

                      }

                      grOAuth.update();

     

     

              }

      },

      log:function(value){

              gs.log("Logging from oAuth 2.0" + value);

      }

};

Usage:

First call,

var payload = new OAuth('goto_meeting',true).execute();

if(payload.indexOf('https://') > -1){ // we have a URL

// redirect the user.

}

else{

//use the payload.

|payload| gives the JSON object, which you can process and exteact the |authorization code|

}

If the user is redirected, then an entry will be made in the intermediate table. Once that is done, run this:

new OAuth().gotoExchange(); // This should give you the Payload which has |Authorization code|

Another oAuth library ( for twitter ) by Andrew Venables is here : ServiceNow Share

Thanks

To jimmy.yuan for being a Dronacharya to an Ekalavya.