Logaholic.de

Avatar

web development

How to add transparent SoapHeader authentication as decorator to any existing service class

This should not be limited to it, but I’d like to show how I implemented this inside the Zend Framework and its Zend_Soap_* components.

SoapHeader authentication is a widely used method to secure access to soap Webservices where the credentials are in the request header.

Activating this type of authentication is for me down to a change from this:

$soap = new Zend_Soap_Server($uri.'&wsdl', $serverOptions);
$soap->setClass('My_Service_'.$serviceName);

to this:

$soap = new Zend_Soap_Server($uri.'&wsdl', $serverOptions);
$soap->setClass('My_Soap_Decorator_Secure', 'My_Service_'.$serviceName);

This is the basic decorator code:

/**
 * This class decorates Soap service classes, provides and enforces authentication via soap header 'authenticate'
 *
 * @author Karsten Deubert <karsten@deubert.net>
 */
class My_Soap_Decorator_Secure
{
    /**
     * @var bool
     */
    protected $_authenticationHeaderPresent = false;

    /**
     * @var mixed
     */
    protected $_authenticatedUser = null;

    /**
     * @var mixed
     */
    protected $_serviceClass = null;

    public function __construct($class)
    {
        if (!class_exists($class))
        {
            throw new Exception('invalid class: '.$class);
        }
        $this->_serviceClass = new $class();
    }

    /**
     * @param mixed $data
     * @return void
     */
    public function authenticate($data)
    {
        $this->_authenticationHeaderPresent = true;

        // authentication code which checks if credentials are valid

        $this->_authenticatedUser = $yourAuthenticatedUser;
    }

    public function __call($name, $arguments)
    {
        if (!$this->isAuthenticationHeaderPresent() || is_null($this->_authenticatedUser))
        {
            throw new Exception('authentication failed');
        }
        if (!is_callable(array($this->_serviceClass, $name)))
        {
            throw new Exception('invalid service class method');
        }

        return call_user_func_array(array($this->_serviceClass, $name), $arguments);
    }
}

The usual soap request with authentication header should now look like this:

$authData = new stdClass();
$authData->user = 'foo';
$authData->secret = 'bar';

$authHeader = new SoapHeader($namespace, 'authenticate', $authData);

$soapClient = new SoapClient('http://foo.bar/asdf?wsdl',
    array(
        'cache_wsdl' => 0,
        'soap_version' => SOAP_1_1
    )
);
$soapClient->__setSoapHeaders(array($authHeader));
$soapClient->fooMethod();

With this header the soap server will first execute the authenticate method from the decorator, then (if successful) pass the method call via magic __call to the inner service class and its fooMethod() in this example.

Voila, transparent SoapHeader authentication separated from your service classes ;)

What I haven’t researched fully yet is if there is a way to specify the authenticate header in the WSDL – every comment appreciated.

How to fail ant builds with xbuild

While setting up a new mono (C# with xbuild) project with ant as build tool I discovered that xbuild always exits with return code 0.

Exit code 0 should always stand for “process finished, everything went fine” – ant consequently concludes that every xbuild run is a success… which is not always true.

Since I need that information to control wether to perform further steps in the project (auto-deployment to test system) or not, I am now scanning the xbuild output for “Build FAILED.” like this in my ant build.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project name="foobar" default="build" basedir=".">
    <target name="xbuild">
        <exec executable="xbuild" failonerror="true" dir="${basedir}" vmlauncher="false" outputproperty="xbuild.output" />
        <echo message="${xbuild.output}" />
    </target>

    <target name="failbuild-on-error">
        <fail message="Build Failed">
            <condition>
                <contains string="${xbuild.output}" substring="Build FAILED." />
            </condition>
        </fail>
    </target>

    <target name="build" depends="xbuild,failbuild-on-error"/>
</project>

,