Fork me on GitHub

Thursday, November 29, 2012

Symfony on non-standard ports

As Symfony (2.0) doc is not very clear on this subject, I spent some time to help a colleague on the following question : How to make HTTPS port different from 443 ?

Of course it's possible, and this is a simple configuration option in app/config.yml :

framework:

   ...

    # router configuration
    router:
        ...
        http_port:            81
        https_port:           1443

    ...

Official doc shows it in the Reference chapter, but not in table of indexes... Let's see http://symfony.com/doc/2.0/reference/configuration/framework.html#full-default-configuration

Tuesday, October 16, 2012

How to inhibit Soap Security headers "mustUnderstand" attribute in a Symfony2 controller

I never managed to make native PHP SoapServer class to handle "understand" soap:Security header as expected with mustUnderstand attribute. But some WS tools like SoapUI automatically set it to "true" or "1" and I needed to make a work around to avoid Soap Fault : "SOAP-ENV:MustUnderstand / Header not understood"

Here is a controller action that inhib mustUnderstand attribute :

   public function ordersFollowUpAction()
   {
      $request = $this->getRequest();

      //This deletes mustUnderstand attribute from raw request content
      $decodedRequest = preg_replace('/ ([-\w]+\:)?(mustUnderstand=")(1|true)(")/', '', $request->getContent());

      $server = new \SoapServer(__DIR__ . '/../Resources/public/wsdl/MyWsdl.wsdl');

      $server->setObject($this->get('my_webservice'));

      $response = new Response();
      $response->headers->set('Content-Type', 'text/xml; charset=UTF-8');

      try
      {
         ob_start();
         //This explicitely passes clear request content to the service handler
         $server->handle($decodedRequest);
         $response->setContent(@ob_get_clean());
      }
      catch (\Exception $e)
      {
         $this->get('logger')->err($e->getMessage());
         $response->setContent('An error occured, please contact our support service.');
      }

      return $response;
   }

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
      );
   }

}

Monday, August 6, 2012

How to deal with asynchronous request with Symfony2 and other PHP frameworks

As well as most of PHP frameworks, Symfony2 manages user session by locking it during request treatment. It avoids concurrent access to session file but makes requests to be queued until session lock is freed...

This behaviour is really well explained in this post from Gareth McCumskey.

As Gareth describes his solution, we need to manually free session lock after all session modifications happen in the request treatment. Here is a pattern for controllers that handle AJAX calls (solution provided by Matt on stackoverflow :

<php

// ...

myControllerAction()
{
    //Actions that modify session
    // ...
 
    //We free session lock as no more session modifications will happen in next actions !
    $session = $this->get('session');
    $session->save();
    session_write_close();
 
    //Actions that don't modify session
    // ...
 
    $this->return('...');
}

Here is the result for an action that process a 10 seconds sleep() :
Before :
After :

Tuesday, June 26, 2012

I need help on groups validation !

I handle a multi-tab form that is validated by one group per form-tab. When I get errors while binding request, I'd like to know the first tab (ie validation group) that is not valid.

Is it possible with Symfony 2.0, and how ?

Please feel free to post your answer on StackOverflow, I will then publish it here. Thanks in advance !