Thursday, July 5, 2012

WSO2 ESB - Error Codes

Following is the list of error codes used in WSO2 ESB and a brief description about them.

Code Description
101000 Receiver IO error sending
101001 Receiver IO error receiving
101500 Sender IO error sending
101501 Sender IO error receiving
101503 Connection failed
101504 Connection timed out
101505 Connection closed
101506 HTTP protocol violation
101507 Connect cancel
101508 Connect timeout
101509 Send abort

Error Handling in WSO2 ESB

When a Proxy Service or a Sequence which mediate messages, is considered, error handling would be a key concern. Depending on the error type, we might need to send a fault message, custom response etc.

WSO2 ESB makes our life easier by allowing us to specify a custom fault sequence for error handling. i.e. It gives the flexibility to use mediator and do various tasks even when an error occurred.

Following is a sample sequence which logs the error details and sends a custom message to the client when an error occurs. Further, if you need to send a SOAP Fault, then Fault mediator can be used instead of Payload Factory mediator.
<sequence name="myErrorSequence">
    <header name="To" action="remove"/>
    <property name="RESPONSE" value="true"/>
    <property name="NO_ENTITY_BODY" scope="axis2" action="remove"/>

    <log level="custom">
        <property name="error-message" expression="get-property('ERROR_MESSAGE')"/>
        <property name="error-code" expression="get-property('ERROR_CODE')"/>
        <property name="error-detail" expression="get-property('ERROR_DETAIL')"/>
        <property name="error-exception" expression="get-property('ERROR_EXCEPTION')"/>
    </log>

    <payloadFactory>
        <format>
            <ns:MyResponse xmlns:ns="http://services.samples">
                <ns:Error/>
            </ns:MyResponse>
        </format>
    </payloadFactory>
    <send/>
</sequence> 

As response might be sending even without going to the outSequence, we need to remove the To header and specify the other two properties named RESPONSE and NO_ENTITY_BODY.

You can also get the details of the error occurred using properties named ERROR_MESSAGE, ERROR_CODE, ERROR_DETAIL and ERROR_EXCEPTION.

After you added the above sequence into the configuration, you can refer it as below from a Proxy or a Sequence.

For a Sequence :
<sequence name="mySequence" onerror="myErrorSequence">
.......
</sequence>
For a Proxy Service :
<proxy name="MyProxy" xmlns="http://ws.apache.org/ns/synapse">
    <target faultSequence="myErrorSequence">
    ......
    </target>
</proxy>

Wednesday, July 4, 2012

Enable Wire Level Logs in WSO2 ESB

When you work with WSO2 ESB, you might need to monitor the wire level data which go through the server. Then, you can simply do that by adding the following entry into the log4j.properties file which can be found at lib/log4j.properties

log4j.category.org.apache.synapse.transport.nhttp.wire=DEBUG

Fixing REST URLs in Send Mediator of WSO2 ESB

There might be situations, that you would need to do REST calls using the send mediator of WSO2 ESB. Then, you might have noticed the endpoint url that you specified in the endpoint configuration, gets suffixed by a url fragment.

This happens when ever you do a REST call using the send mediator. In order to get rid of it, please specify the following property before the send mediator.
<property name="REST_URL_POSTFIX" scope="axis2" action="remove"/>

Sending a Dummy Response from WSO2 ESB

When you play with WSO2 ESB, you might need to send some dummy data as the response of a proxy service or an REST API. In order to do that you will need to specify To, RESPONSE and NO_ENTITY_BODY properties in the inSequence. If the sequence doesn't get called via an HTTP GET, then the property NO_ENTITY_BODY can be removed from the sequence.

Then, the sequence will directly send the response to the client even without going through the outSequence. Using either Enrich or Payload Factory mediator, you can create the response massage manually.

Following is a sample inSequence.
<inSequence>
    <header name="To" action="remove"/>
    <property name="RESPONSE" value="true"/>
    <property name="NO_ENTITY_BODY" scope="axis2" action="remove"/>
    <payloadFactory>
        <format>
            <ns:getQuoteResponse xmlns:ns="http://services.samples">
                <ns:return xmlns:ax21="http://services.samples/xsd">
                    <ax21:change>4.212070096051944</ax21:change>
                    <ax21:earnings>-9.567415587431361</ax21:earnings>
                    <ax21:high>-148.1740146577308</ax21:high>
                    <ax21:symbol>IBM</ax21:symbol>
                </ns:return>
            </ns:getQuoteResponse>
        </format>
    </payloadFactory>
    <send/>
</inSequence>

Friday, December 2, 2011

Invoking Secured Web Services with WSO2 Mashup Server

WSO2 Mashup Server is based on WSO2 Carbon Platform which is then built on top of Apache Axis2 engine. Apache Rampart is the security module of Axis2. It secures SOAP messages according to specifications in the WS-Security stack. When you create a complex mashup which invokes many enterprise level web services, then WS* security will be a key concern.

Using WSRequest hostobject of the Mashup Server, you can easily access web services secured with many security scenarios. You can either dynamically pick the policy from the service wsdl or set it manually.

In both cases, you can set all the rampart related configurations using a simple json object notation. In this post, I have described how you can use WSO2 Mashup Server to invoke secured services from a mashup.

All the samples described here can be deployed on WSO2 Mashup Server 2.3.3 or above which can be downloaded from here.

Structure of the Keystores

Lets consider an encryption scenario as below.

Request Flow

When, client invokes the service, it encrypt the payload using client’s private key. Then at the server, it should have client’s public key certificate in order to decrypt the payload.

Response Flow

When server sends back the response to the client, server uses its private key to encrypt the response. Then the client who invoked the secured service should have the public key certificate of the server to decrypt the response.

Likewise, depending on the security policies being used between client and server, it will need to have client’s public key certificate in at the server and server’s public key certificate at the client. But for several other scenarios, a trusted third party certificate might be used.

Anyway, we are going to have the following structure in our client and server keystore as it will be used to test all the scenarios available via Mashup Server’s management console.

client.jks (password - client)

alias key
client client's private key (password - client)
server server's public key

 

server.jks (password - server)

alias key
server server's private key (password - server)
client client's public key

Generating keystores

Please follow this post and create client.jks and server.jks with the structure
described above.

Creating a sample mashup to be used as the service

In this tutorial, I will use a simple mashup as the secured service. i.e A mashup which takes a string name as the input and responds a greeting for that name, will be created and deployed in WSO2 Mashup Server. Once we deployed any mashup in WSO2 Mashup Server, it will behave as any other Axis2 service. Then, we can easily do most of the administrative tasks we want using management console.

So, please create a mashup with the content below.
this.serviceName = "helloService";
    function sayHello(name) {
    return "Hello " + name "!";
}
Creating mashup client

Introduction to WSRequest Hostobject

The WSRequest object is similar to the XMLHTTPRequest object. Its usage typically involves specifying the endpoint address and setting up options on how to frame the message, invoking the operation with a specific XML payload, and then checking and extracting information from the result.

It can also be considered as the Javascript wrapper for Axis2 ServiceClient which allows to do web service invocation. So, in WSO2 Mashup Server, the Web Service client is WSRequest. You can invoke services either synchronously or asynchronous, send custom HTTP/SOAP headers and even secure services invocation from a mashup with the help of WSRequest Hostobject.

Following is the Javascript interface of the WSRequest Hostobject. For more information please refer this.
{
    property EventListener onreadystatechange;
    property unsigned short readyState;
    function void open ( object options | String httpMethod, String url [, boolean async [, String username[, String password]]]);
    function void send ( XML payload | XMLString payload ); // To be used when the open method of WSRequest is used
    function void openWSDL ( String wsdlURL, ?boolean async, [Object options,[QName serviceName, [String endpointName]]]);
    function void send ( String operationName | QName operationName, XML payload | XMLString payload ); //To be used when the openWSDL method of WSRequest is used
    readonly property String responseText;
    readonly property Document responseXML;
    readonly property XML responseE4X;
    readonly property WebServiceError error;
}
Invoking the sample Service

Here I will describe how you can use WSRequest Hostobject to invoke a web service. You can find more information about service invocation using WSRequest Hostobject at Mashup Server’s documentation pages. But it would be better to write the client mashup to work with a normal service before extending it to access a secured service.

Following code fragment will invoke the above deployed HelloService and return the response. In the following client, the payload has been hard coded for the simplicity.
function invokeService(){
    var client = new WSRequest();
    var options = new Array();
    options["useSOAP"] = 1.2;
    options["action"] = "urn:sayHello";

    var payload =
    <sayHello>
        <name>Ruchira</name>
    </sayHello>;

    var result;

    try {
        client.open(options,"http://localhost:9763/services/admin/helloService", false);
        client.send(payload);
        result = client.responseE4X;
    } catch (e) {
        system.log(e.toString(),"error");
        return e.toString();
    }
    return result;
}
Create a file named helloClient.js in repository/deployment/server/jsservice/admin directory and add the above code fragment. Once you invoke the "Tryit" page of the newly created service, you should get the "Hello Ruchira!" greeting from the service.

Once you succeed with the above step, you can move to the next step which secures the helloService and modifies helloClient to invoke the secured service.

Secure Services

Secured Web services can be categorized into following forms depending on the method they used.

HTTP Basic Auth

Your service might have been secured using HTTP Basic Authentication. In that case, you can simply pass the username and password into the open() method of WSRequest Hostobject and invoke the service.

WS-Security

In most of the Enterprise level use-cases, HTTP Basic Authentication might not be a good solution, then WS-Security comes into the picture. Using WS-Security, you can have a greater flexibility with your service security. So, most of the today’s services are based on WS-Security.

Likewise, a web service can be secured either with HTTP Basic Authentication or using WS-Security. WSRequest hostobject can be used to invoke services secured with any of the above methods.

Securing the mashup service
  • Uploading Keystore into WSO2 Mashup Server
    1. Go to Configure > Keystores
    2. Go through "Add New Key store" wizard and add the server.jks created above
  • Apply a security scenario using Management Console
    1. Go to Main > Manage > Web Services > List
    2. Select the above created service i.e. "helloService" and click on "Unsecured" link on the service listing
    3. Go through the security engaging wizard and engage one of the scenarios listed there
  • View the WSDL
    1. If you view the WSDL 1.0 or 2.0 of the service now, you will be able to see the newly applied policy in
      the WSDL.
Invoking secured service

WSRequest Hostobject offers two methods to deal with security. If you know the policy being used in the service, then you can use open() method and set the policy manually. If you don’t, then you can use openWSDL() method which will then extract the policy definition from the given WSDL and invoke the service. But performance wise, open() method is more better than openWSDL() as openWSDL method always read the WSDL before invoking the service.

open() method

Method signature is as bellow.
function void open ( object options | String httpMethod, String url [, boolean async [, String username[, String password]]])
When you use this method to invoke a secured service, then you have two options as below.
  1. Specify policy definition with rampart configuration as an E4X XML object in the options object using "policy" property. You can also use ${resources.dir} to specify the path of the resources folder in keystore paths. i.e. Assume your keystore named client.jks is in *.resources folder, then you can use ${resources.dir}/client.jks as the keystore path in your rampart configuration.
  2. Specify policy definition using "policy" property and rampart configuration as a JSON object using "rampart" property. The structure of the rampart configuration JSON has been described under the section named "Rampart
    Configuration" below. Here, you don’t need to specify rampart configuration in policy definition.
In both cases, you can also keep the policy definition as a separate XML file within your resources folder and set it to "policy" property by creating an XML object with the help of File Hostobject.

If you are invoking an HTTP Basic Authentication enabled service, then just passing the username and password as method parameters would be sufficient and "policy" and "rampart" properties won’t need to be specified.
var options = new Object();
…..
options["policy"] =
<Policy>.....</Policy>;
options["rampart"] = { ….. };
…..
wsRequest.open(options, serviceUrl, false);
Please refer WSRequest documentation at [2] for more information about other input parameters and properties of the options object.

openWSDL() method
function void openWSDL ( String wsdlURL, ?boolean async, [Object options,[QName serviceName, [String endpointName]]]);
When openWSDL() method is used, you need to specify the URL of the secured service WSDL. Then it will read the WSDL and find the attached policy definitions from it. In this case, you only need to specify the "rampart" property in your options object.

Specifying "policy" property too, will override the policy definition extracted from the WSDL.

Rampart Configuration

Following is the format of the rampart configuration JSON. Dummy values and comments have been added to explain it more precisely.

{ 
    user : "admin", //username for to use UT and other scenarios
    userPassword : "admin", //in UT, this is the user password
    keyPassword : "client", //private key password
    userCertAlias : "client",
    stsAlias : "sts",
    encryptionUser : "server",
    timestampTTL : "10000",
    timestampMaxSkew : "10",
    timestampPrecisionInMilliseconds : "100",
    signatureCrypto : {
        type : "jks", //keystore type
        file : "client.jks", //keystore file relative to resource folder
        password : "client", //keystore password
        enableCryptoCaching : true,
        cacheRefreshInterval : 3000
    },
    encryptionCrypto : {
        type : "jks", //keystore type
        file : "client.jks", //keystore file relative to resource folder
        password : "client", //keystore password
        enableCryptoCaching : true,
        cacheRefreshInterval : 3000
    },
    decryptionCrypto : {
        type : "jks", //keystore type
        file : "client.jks", //keystore file relative to resource folder
        password : "client" //keystore password
    },
    stsCrypto : {
        type : "jks", //keystore type
        file : "client.jks", //keystore file relative to resource folder
        password : "client" //keystore password
    },
    kerberosConfig : {
        /**
        "key" : "value" properties, you can also use ${resources.dir}
        in property values
        */
        "client.principal.name" : "client",
        "client.principal.password" : "client",
        "service.principal.name" : "service",
        "java.security.auth.login.config" : "jassconfig",
        "javax.security.auth.useSubjectCredsOnly" : "true",
        "kdc.des.aes.factor" : "4",
        "java.security.krb5.conf" : "/home/ruchira/wso2/conf"
    }
} 
Secure Client using Open() method
(you need to set the correct scenario xml there in the line number 6)
function invokeSecureServiceOpen() {
    /**
    * Reads the policy.xml from resources folder and creates
    * E4X XML object from it
    */
    var policyFile = new File("scenarios/scenario2-policy.xml");
    policyFile.openForReading();
    var policy = new XML(policyFile.readAll());

    var client = new WSRequest();
    var options = new Array();
    options["useSOAP"] = 1.2;
    options["action"] = "urn:sayHello";
    options["policy"] = policy;
    options["rampart"] = {
        user : "admin", //username for to use UT and other scenarios
        userPassword : "admin", //in UT, this is the user password
        keyPassword : "client", //private key password
        userCertAlias : "client",
        encryptionUser : "server",
        signatureCrypto : {
            type : "jks", //keystore type
            file : "client.jks", //keystore file relative to resource folder
            password : "client", //keystore password
            enableCryptoCaching : true,
            cacheRefreshInterval : 3000
        },
        encryptionCrypto : {
            type : "jks", //keystore type
            file : "client.jks", //keystore file relative to resource folder
            password : "client", //keystore password
            enableCryptoCaching : true,
            cacheRefreshInterval : 3000
        },
        decryptionCrypto : {
            type : "jks", //keystore type
            file : "client.jks", //keystore file relative to resource folder
            password : "client" //keystore password
        },
        stsCrypto : {
            type : "jks", //keystore type
            file : "client.jks", //keystore file relative to resource folder
            password : "client" //keystore password
        }
    };

    var payload =
    <sayHello>
        <name>Ruchira</name>
    </sayHello>;

    var result;

    try {
        client.open(options,"http://localhost:9763/services/admin/helloService", false);
        client.send(payload);
        result = version.responseE4X;
    } catch (e) {
        system.log(e.toString(),"error");
        return e.toString();
    }
    return result;
}
Secure Client using openWSDL() method
function invokeSecureServiceOpenWSDL() {
    var client = new WSRequest();
    var options = new Array();
    options["useSOAP"] = 1.2;
    options["action"] = "urn:sayHello";
    options["rampart"] = {
    user : "admin", //username for to use UT and other scenarios
    userPassword : "admin", //in UT, this is the user password, else private key
    keyPassword : "client", //private key password
    userCertAlias : "client",
    encryptionUser : "server",
    signatureCrypto : {
            type : "jks", //keystore type
            file : "client.jks", //keystore file relative to resource folder
            password : "client", //keystore password
            enableCryptoCaching : true,
            cacheRefreshInterval : 3000
        },
        encryptionCrypto : {
            type : "jks", //keystore type
            file : "client.jks", //keystore file relative to resource folder
            password : "client", //keystore password
            enableCryptoCaching : true,
            cacheRefreshInterval : 3000
        },
        decryptionCrypto : {
            type : "jks", //keystore type
            file : "client.jks", //keystore file relative to resource folder
            password : "client" //keystore password
        },
        stsCrypto : {
            type : "jks", //keystore type
            file : "client.jks", //keystore file relative to resource folder
            password : "client" //keystore password
        }
    };

    var payload =
    <sayHello>
        <name>Ruchira</name>
    </sayHello>;

    var result;

    try {
        var service = new QName("http://services.mashup.wso2.org/helloService","helloService");
        request.openWSDL("http://localhost:9763/services/admin/helloService?wsdl", false, options, service, "SOAP12Endpoint");
        request.send("sayHello", payload);
        result = version.responseE4X;
    } catch (e) {
        system.log(e.toString(),"error");
        return e.toString();
    }
    return result;
}
Testing the service

After successfully created the two operations with open and openWSDL methods, now you can invoke the client service using it’s tryit page which will then invoke the secured "helloService".

Thursday, June 16, 2011

XML to JSON and JSON to XML Conversion in Javascript

When we deal with today's highly ajax based web application developments, dealing with XMLs within your page might be a little bit tedious task. As all of us know, JavaScript doesn't have a proper way to deal with XMLs(although recent specs supports E4X, it hasn't implemented in all browsers). If we can convert the XML into a JSON format, then it will be a great relief for web developers.

Although there are many XML to JSON notations available, all of them tends to ignore xml namespaces, but Badgerfish. So, I thought of writing a JavaScript code which does the XML to JSON conversion and wise-versa. If you want to get the Javascript code for XML to JSON, then go though my blog post XML to BadgerFish Conversion in Javascript. In this post, I will give you the function which converts JSON back to it's original XML.

Using these xml2bf and bf2xml functions, you will get the most flexibility with XML in your web developments.
function bf2xml(json) {
    if (typeof json !== "object") return null;
    var cloneNS = function(ns) {
        var nns = {};
        for (var n in ns) {
            if (ns.hasOwnProperty(n)) {
                nns[n] = ns[n];
            }
        }
        return nns;
    };
    var processLeaf = function(lname, child, ns) {
        var body = "";
        if (child instanceof Array) {
            for (var i = 0; i < child.length; i++) {
                body += processLeaf(lname, child[i], cloneNS(ns));
            }
            return body;
        } else if (typeof child === "object") {
            var el = "<" + lname;
            var attributes = "";
            var text = "";
            if (child["@xmlns"]) {
                var xmlns = child["@xmlns"];
                for (var prefix in xmlns) {
                    if (xmlns.hasOwnProperty(prefix)) {
                        if (prefix === "$") {
                            if (ns[prefix] !== xmlns[prefix]) {
                                attributes += " " + "xmlns=\"" + xmlns[prefix] + "\"";
                                ns[prefix] = xmlns[prefix];
                            }
                        } else if (!ns[prefix] || (ns[prefix] !== xmlns[prefix])) {
                            attributes += " xmlns:" + prefix + "=\"" + xmlns[prefix] + "\"";
                            ns[prefix] = xmlns[prefix];
                        }
                    }
                }
            }
            for (var key in child) {
                if (child.hasOwnProperty(key) && key !== "@xmlns") {
                    var obj = child[key];
                    if (key === "$") {
                        text += obj;
                    } else if (key.indexOf("@") === 0) {
                        attributes += " " + key.substring(1) + "=\"" + obj + "\"";
                    } else {
                        body += processLeaf(key, obj, cloneNS(ns));
                    }
                }
            }
            body = text + body;
            return (body !== "") ? el + attributes + ">" + body + "</" + lname + ">" : el + attributes + "/>"
        }
    };
    for (var lname in json) {
        if (json.hasOwnProperty(lname) && lname.indexOf("@") == -1) {
            return processLeaf(lname, json[lname], {});
        }
    }
    return null;
}