RESTful Web Service Endpoint Scripting

Documentation home

 

Endpoint Event Scripts 1

Working with Endpoint Scripts 1

Endpoint JavaScript API 2

Error handling. 3

On Error Event 4

Implementing Security. 5

Basic Authentication Example. 6

Custom Authentication. 7

 

See Also: Publishing RESTful Web Services Overview, RESTful Services, Server Admin Web Service Administration, Publishing Web Service Documentation, RESTful Web Service Tutorials

Endpoint Event Scripts

 

A RESTful Web Service contains a collection of endpoints. Endpoint script(s) are added to each endpoint and are executed when a request for the endpoint is processed. Note that all RESTful web service scripts should be created in JavaScript, as the FPL language does not have any way to address the web service request or response data.

 

Any script statements that operate on web pages or controls are not supported in an Endpoint Script – as a RESTful Web Service has no pages.

Working with Endpoint Scripts

 

An Endpoint Script can be created in the tree: New > Script.

 

         

 

Endpoint JavaScript API

 

All of the RESTful Web Service specific functions are can be accessed using the JavaScript variable form.rest.

 

The table below shows the methods that can be used to access request data:

 

Function

Returns

Description

form.rest.getMethod()

String

Returns the HTTP method used to call the endpoint. e.g GET, POST

form.rest.getEndpointPath()

String

Returns the endpoint path invoked that is received by the system e.g /customers/1

form.rest.getPathParameter(name)

String

Returns the incoming path parameter value by name or returns null if the variable does not exist.

form.rest.getPathParameters()

Map<String, String>

Returns a Map of key, value String pairs for all the incoming path parameters.

form.rest.getRequestBody()

String

Returns the request body or null if no body is set on the request.

form.rest.getRequestContentType()

String

Returns the Content-Type HTTP header from the incoming request. e.g application/json. Returns null if the Content-Type header is not set.

form.rest.getRequestHeader(name)

String

Returns the HTTP header value by name or returns null if the header does not exist.

form.rest.getRequestHeaders()

Map<String, String>

Returns a Map of key, value String pairs for all the incoming HTTP headers.

form.rest.getRequestParameter(name)

String

Returns the incoming query or post parameter value by name or returns null if the variable does not exist.

form.rest.getRequestParameters()

Map<String, String>

Returns a Map of key, value String pairs for all the incoming query/post parameters.

 

 

The table below shows the methods that can be used to set information on the response:

 

Function

Parameter

Description

form.rest.setResponseBody(body)

body - String

Sets the response body.

form.rest.setResponseContentType(type)

type - String

Sets the Content-Type HTTP header for the response. E.g application/json

form.rest.setResponseHeader(name, value)

name – String

value - String

 

Adds a response HTTP header value by name.

form.rest.setResponseStatus(status)

status - Number

Set the response status code. The default is 200 - HTTP OK.

 

Error handling

 

The default HTTP status code for the response is 200 (Status OK). Error handling should be handled within the script and the appropriate HTTP status should be set on the response before exiting the script. Unhandled errors are returned as status code 500 (server error) and the detail error message is returned in the response body.

 

An example script with error handling might be as follows:

 

importPackage(com.ebasetech.xi.api);

importPackage(com.ebasetech.xi.services);

 

//get customer id from the request URI

fields.customer_id.value = form.rest.getPathParameter("customerId");

 

//fetch customer

resources.customers.fetch();

 

if(system.variables.$FETCH_COUNT > 0)

{

      var customer = {};

     

      customer.id = fields.customer_id.value;

      customer.firstName = fields.first_name.value;

      customer.lastName = fields.last_name.value;

      customer.dob = fields.dob.value;

      customer.sex = fields.sex.value;;

      customer.hasChildren = fields.children.value;

 

      form.rest.setResponseContentType("application/json");

      form.rest.setResponseBody(JSON.stringify(customer));

}

else

{

      //return 404 if customer not found

      form.rest.setResponseStatus(404);

      form.rest.setResponseBody("Customer not found with customer id: " + fields.customer_id.value);

}

 

See Internal Errors for more information regarding generic error response status codes.

On Error Event

If an error is uncaught when executing an endpoint event, then the ‘On Error’ event is invoked, if configured.

 

An on error event may be as follows:

 

importPackage(com.ebasetech.xi.api);

importPackage(com.ebasetech.xi.services);

 

//get customer id from the request URI

fields.customer_id.value = form.rest.getPathParameter("customerId");

 

//fetch customer

resources.customers.fetch();

 

if(system.variables.$FETCH_COUNT > 0)

{

      var customer = {};

     

      customer.id = fields.customer_id.value;

      customer.firstName = fields.first_name.value;

      customer.lastName = fields.last_name.value;

      customer.dob = fields.dob.value;

      customer.sex = fields.sex.value;;

      customer.hasChildren = fields.children.value;

 

      form.rest.setResponseContentType("application/json");

      form.rest.setResponseBody(JSON.stringify(customer));

}

else

{

      //return 404 if customer not found

      form.rest.setResponseStatus(404);

      form.rest.setResponseBody("Customer not found with customer id: " + fields.customer_id.value);

}

 

Implementing Security

 

A RESTful Web Service does not support authentication as default. Security is typically required on each endpoint implementation requirements. This normally depends on whether the data is sensitive or not. Usually public information does not require any security where as sensitive information, for example, customer details should have some form of authentication implementation.

 

Other considerations might be that the system supports user roles and permissions. These also have specific requirements based on the endpoint implementation.

 

When implementing security the server should return the correct HTTP status:

 

HTTP Status

HTTP Status Description

Description

200

OK

User is validated and the request has been successfully executed.

401

UNAUTHORIZED

The request requires authentication. The response MUST include the response header – WWW-Authenticate containing a challenge applicable to the requested resource, for example -  WWW-Authenticate: Basic realm=”myrealm”

 

If the request contains the required Authorization credentials then a 401 indicates that the authorization has failed for those credentials, for example, incorrect username or password.

403

FORBIDDEN

The server has understood the request but is refusing to fulfill it, for example, a user does not have sufficient permissions to access a resource. This is a permanent failure and does not contain a challenge.

 

The JavaScript API contains a method to help with Base64 encoding that is used in HTTP Basic authentication:

 

Method Name

Description

form.rest.readBasicAuthenticationCredentials()

Extracts the username and password from the request Authorization HTTP header and returns a UserCredential object that contains the username and password in clear text.

 

Example:

 

var credentials = form.rest.readBasicAuthenticationCredentials();

if(credentials)

{

  //authenticate user

}

 

When implementing any custom authentication, it is not recommended to add username and passwords to the URI parameters as they are always visible on the request. Username and passwords should be added to the request by using POST parameters or as HTTP headers.

 

It does not matter which authentication method is used, always use HTTPS to secure the client and server communication.

 

Basic Authentication Example

 

Basic authentication is the simplest type of authentication. The username and password are concatenated together and then base64 encoded and added to the request header:

 

Authorization: Basic QWxhZGRpbjpPcGVuU2VzYW1l

 

The following example reads the Basic Authorization request header and tries to authenticate the user against a database. If the request does not contain the required Basic authentication credentials then return a 401 (UNAUTHORIZED). If the authorization fails then return a HTTP response of 403 (FORBIDDEN). If the credentials are authorized, process the request. The Basic authentication is authorized in the endpoint script before returning the response:

 

//read authentication

var cred = form.rest.readBasicAuthenticationCredentials();

 

fields.customer_id.value = form.rest.getPathParameter("customerId");

fields.firstName.value = form.rest.getRequestParameter("firstName");

fields.lastName.value = form.rest.getRequestParameter("lastName");

 

var authenticated = false;

if(cred)

{

            //authenticate user

       if(validateUser(cred.getUsername(), cred.getPassword()))

       {

                  //update the customer

                  resources.customers.update();

       }

       else

       {

                  //just reject the user

                  form.rest.setResponseStatus(403);

       }

}

else

{

      log("No credentials");

      form.rest.setResponseHeader("WWW-Authenticate", "Basic realm=\"User Realm\"");

      form.rest.setResponseStatus(401);

}

 

function validateUser(username, password)

{

       var auth = false;

       var stmt = "select password from users where name = '" + username + "'";

       services.database.executeSelectStatement("SAMPLES", stmt,

   function (columnData)

   {

       auth = password == columnData.PASSWORD;

      return true;      // continue

   });

   return auth;

}

 

 

Custom Authentication

 

Custom Authentication allows you to implement your own authentication. This could be username and password based authentication or token based authentication. Any requests containing security are recommended to be transferred SSL (https). If the request is not transferred using SSL it is recommended that the security credentials are encrypted.

 

The following example shows a token based authentication. Token based authentication is a two phased authentication procedure. See below for the authentication flow:

 

  1. The client sends its credentials (username and password) to the Authentication Endpoint to call an endpoint to authenticate the user.
  2. The server authenticates the user credentials and generates a token with an expiration date.
  3. The server stores the previously generated token in some storage with user identifier, such as a database or a map in memory.
  4. The server sends the generated token back to the client.
  5. For every Resource Request, the client sends the token to the server.
  6. The server, in each request, extracts the token from the incoming request, looks up the user identifier with the token to obtain the user information to do the authentication/authorization.
  7. If the token has expired return a 401 (UNAUTHORIZED) response status.

 

Create an authentication endpoint:

 

Authentication Endpoint

 

 

  1. Create a RESTful Web Service
  2. Add an endpoint and name it authenticate user.
  3. Click the button on the consumes field and select application/x-www-form-urlencoded.
  4. Deselect GET HTTP Method
  5. Select POST HTTP Method
  6. Create an endpoint script called authenticateUser and implement the user authentication and token generation.

 

importPackage(com.ebasetech.xi.api);

importPackage(com.ebasetech.xi.services);

 

 

var username = form.rest.getRequestParameter("username");

var password = form.rest.getRequestParameter("password");

 

// Authenticate the user using the credentials provided

if(authenticate(username, password))

{

 

  // Issue a token for the user

  var tokenObj = issueToken(username);

 

   //persist token

   persistToken(tokenObj);

 

  // Return the token on the response

  form.rest.setResponseBody(JSON.stringify(tokenObj));

}

else

{

   form.rest.setResponseStatus(403);

}  

 

/**

 * Authenticate against a database, LDAP, file or whatever

 * @return false if the credentials fail otherwise true

 */

function authenticate(username, password) {

    if(username == "demouser" && password == "demopwd")

         return true;

    else

         return false;

}

 

/**

 *  Issue a token (can be a random String persisted to a database or a JWT token)

 *  The issued token must be associated to a user

 *  @return the issued token

 */

function issueToken(username) {

  

    var d = new Date();

    d.setDate(d.getDate() + 1); //add a day

    var random = new java.security.SecureRandom();

    var token = new java.math.BigInteger(130, random).toString(32);

    var tokenObj = {};

    tokenObj.token = token;

    tokenObj.expires = d.getTime();

    return tokenObj;

}

 

/**

 * Add the issued token to the database

 */

function persistToken(tokenObj)

{

   fields.token.value = tokenObj.token;

   fields.expires.value = tokenObj.expires;

   fields.username.value = username;

   resources.login.update();

}

 

  1. Add the script to the endpoint event.

 

Resource Request

 

 

 

  1. Open the RESTful Web Service created in the Authentication Endpoint section.
  2. Create a new endpoint for your resource requirements
  3. Enter the endpoint URI including any parameters required.
  4. Select the HTTP method
  5. Create an Endpoint Script to adding code to authenticate the validate token generated in the Authenticate Endpoint script and return the requested resource information.

 

importPackage(com.ebasetech.xi.api);

importPackage(com.ebasetech.xi.services);

 

try

{

  

   var accessToken = form.rest.getRequestHeader("Authentication");

  

   //validate token

   if(validateToken(accessToken))

   {

  

               //get customer id from the request URI

               fields.customer_id.value = form.rest.getPathParameter("customerId");

              

               //fetch customer

               resources.customers.fetch();

        

               if(system.variables.$FETCH_COUNT > 0)

               {

                     var customer = {};

                    

                     customer.id = fields.customer_id.value;

                     customer.firstName = fields.first_name.value;

                     customer.lastName = fields.last_name.value;

                     customer.dob = fields.dob.value;

                     customer.sex = fields.sex.value;

                     customer.hasChildren = fields.children.value;

              

                     form.rest.setResponseContentType("application/json");

                     form.rest.setResponseBody(JSON.stringify(customer));

               }

               else

               {

                     //return 404 if customer not found

                     form.rest.setResponseStatus(404);

                     form.rest.setResponseBody("Customer not found with customer id: " + fields.customer_id.value);

               }

   }

   else

   {

               //return not authorized

               log("Authorization failure");

               form.rest.setResponseHeader("WWW-Authenticate", "Bearer realm=\"User Realm\"");

               form.rest.setResponseStatus(401);

   }

 

}

catch(e)

{

   //return generic error

   form.rest.setResponseStatus(500);

   form.rest.setResponseBody("Error finding customer details for customer: " + e);

}

 

function validateToken(accessToken)

{

    

   if(accessToken || accessToken.indexOf("Bearer ") != 0)

         return false;

 

   var token = accessToken.substring("Bearer".length).trim();

  

   var stmt = "select expiry from auth_tokens where access_token = '" + token + "'";

   services.database.executeSelectStatement("SAMPLES", stmt,

   function (columnData)

   {

               var verifyDate = new Date();

               auth = verifyDate.getTime() <= columnData.expiry;

         return true;      // continue

   });

 

   return auth;

    

}

 

  1. Add the script to the endpoint event.