Tuesday, October 16, 2012

How to read Soap WSSE headers

While following Symfony2 WSSE implementation tutorial, I was annoyed by the suggested WSSE headers interpretation. It only presents HTTP headers, not Soap WSSE headers...

Here is an extract of Symfony2 tutorial, handling HTTP headers :

        $wsseRegex = '/UsernameToken Username="([^"]+)", PasswordDigest="([^"]+)", Nonce="([^"]+)", Created="([^"]+)"/';
        if (!$request->headers->has('x-wsse') || 1 !== preg_match($wsseRegex, $request->headers->get('x-wsse'), $matches)) {
            return;
        }

Obviously, it doesn't fit Soap headers that look like that :


  
   2012-10-10T12:00:36.099Z
   2012-10-10T12:05:36.099Z
  
  
   username@email.com
   E2wz/+mTabxWO37Hk6UeqSJF8+o=
   sAg8czv5GmW7rUKgqhm1Qw==
   2012-10-10T12:00:36.099Z
  
 

To read these headers, I use SimpleXML after having formatted the raw request content :

<?php

namespace AlterPHP\SoapBundle\Tools;

use Symfony\Component\HttpFoundation\Request;

/**
 * Description of WsseHeadersDecoder
 *
 * @author pece
 */
class WsseHeadersDecoder
{

   public function getHeaders(Request $request)
   {
      //HTTP headers (as described here : http://symfony.com/doc/2.0/cookbook/security/custom_authentication_provider.html#the-listener
      if ($request->headers->has('x-wsse'))
      {
         $wsseRegex = '/UsernameToken Username="([^"]+)", PasswordDigest="([^"]+)", Nonce="([^"]+)", Created="([^"]+)"/';
         if (1 !== preg_match($wsseRegex, $request->headers->get('x-wsse'), $matches))
         {
            return false;
         }
         else
         {
            $username = $matches[1];
            $passwordDigest = $matches[2];
            $nonce = $matches[3];
            $created = $matches[4];
         }
      }
      //Classic SOAP headers
      else
      {
         //Clear XML namespace prefixes to handle with SimpleXML
         $decodedRequest = preg_replace("/(<\/?)([-\w]+):([^>]*>)/", "$1$3", $request->getContent());
         $xmlRequest = simplexml_load_string($decodedRequest);

         if (
            !isset($xmlRequest->Header->Security->UsernameToken->Username)
            || !isset($xmlRequest->Header->Security->UsernameToken->Password)
            || !isset($xmlRequest->Header->Security->UsernameToken->Nonce)
            || !isset($xmlRequest->Header->Security->UsernameToken->Created)
         )
         {
            return false;
         }
         else
         {
            $username = (string) $xmlRequest->Header->Security->UsernameToken->Username;
            $passwordDigest = (string) $xmlRequest->Header->Security->UsernameToken->Password;
            $nonce = (string) $xmlRequest->Header->Security->UsernameToken->Nonce;
            $created = (string) $xmlRequest->Header->Security->UsernameToken->Created;
         }
      }

      return array (
              'username' => $username, 'passwordDigest' => $passwordDigest, 'nonce' => $nonce, 'created' => $created
      );
   }

}

2 comments :

Anonymous said...

This is way too convoluted. In modern PHP you only need a method with the name of the header to process implemented in the class that server the WSSE protected SOAP service.

Basic code:

public function Security( $header ){
$this->Authenticated = true;
$this->Username = $header->UsernameToken->Username;
$this->Password = $header->UsernameToken->Password;
}

Done

AlterPHP said...

I would have liked it was so simple ;) But Soap headers are not well decoded in PHP that's why I test content before assuming requester is authenticated ($this->Authenticated = true; => seems useless ?). And I needed to deal with namespace prefixes too...

Post a Comment

Comments are moderated before being published.