Symfony FOSRestBundle + WSSE + FOSUserBundle

917 views Asked by At

I'm having trouble setting up a wsse security on my Symfony API. I'm following the tutorial Configure WSSE on Symfony with FOSRestBundle based on How to Create a custom Authentication Provider

The issues are :

  • How do I manage to use FOSUserBundle with WSSE (especially salt) ? I don't want the front to retrieve the salt of each user that sends a request.

  • I want to make the registration route anonymously reachable and each time I try to HTTP Request without sending the x-wsse i'm getting an error :

    "message":"A Token was not found in the SecurityContext.",
    "class":"Symfony\\Component\\Security\\Core\\Exception\\AuthenticationCredentialsNotFoundException"
    

app/config/security.yml

security:
    encoders:
        FOS\UserBundle\Model\UserInterface: sha512

    role_hierarchy:
        ROLE_ADMIN:       ROLE_USER
        ROLE_SUPER_ADMIN: ROLE_ADMIN

    providers:
        fos_userbundle:
            id: fos_user.user_provider.username_email
    firewalls:
        wsse_secured:
            pattern:   ^/
            stateless:    true
            wsse: true
            anonymous : false
        main:
            pattern: ^/
            form_login:
                provider: fos_userbundle
                csrf_provider: form.csrf_provider
            logout:       true
            anonymous:    true
    access_control:
        # I tried using - { path: ^/users/$, roles: IS_AUTHENTICATED_ANONYMOUSLY, methods: [POST] }
        - { path: ^/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/register, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/admin/, role: ROLE_ADMIN }

WsseProvider

class WsseProvider implements AuthenticationProviderInterface
{
    private $userProvider;
    private $cacheDir;

    public function __construct(UserProviderInterface $userProvider, $cacheDir)
    {
        $this->userProvider = $userProvider;
        $this->cacheDir     = $cacheDir;
    }

    public function authenticate(TokenInterface $token){
        $user = $this->userProvider->loadUserByUsername($token->getUsername());
        if(!$user){
            throw new AuthenticationException("Bad credentials... Did you forgot your username ?");
        }
        if ($user && $this->validateDigest($token->digest, $token->nonce, $token->created, $user->getPassword())) {
        $authenticatedToken = new WsseUserToken($user->getRoles());
        $authenticatedToken->setUser($user);
        return $authenticatedToken;
        }
    }

    protected function validateDigest($digest, $nonce, $created, $secret){

        // Check created time is not in the future
        if (strtotime($created) > time()) {
        throw new AuthenticationException("Back to the future...");
        }

        // Expire timestamp after 5 minutes
        if (time() - strtotime($created) > 300) {
            throw new AuthenticationException("Too late for this timestamp... Watch your watch.");
        }

        // Validate nonce is unique within 5 minutes
        if (file_exists($this->cacheDir.'/'.$nonce) && file_get_contents($this->cacheDir.'/'.$nonce) + 300 > time()) {
            throw new NonceExpiredException('Previously used nonce detected');
        }

        // If cache directory does not exist we create it
        if (!is_dir($this->cacheDir)) {
            mkdir($this->cacheDir, 0777, true);
        }

        file_put_contents($this->cacheDir.'/'.$nonce, time());

       // Validate Secret
        $expected = base64_encode(sha1(base64_decode($nonce).$created.$secret, true));

        if($digest !== $expected){
            throw new AuthenticationException("Bad credentials ! Digest is not as expected.");
        }

        return true;
    }

    public function supports(TokenInterface $token){
        return $token instanceof WsseUserToken;
    }
}

WsseUserToken

class WsseUserToken extends AbstractToken
{
    public $created;
    public $digest;
    public $nonce;

    public function __construct(array $roles = array())
    {
        parent::__construct($roles);

        $this->setAuthenticated(count($roles) > 0);
    }

    public function getCredentials()
    {
        return '';
    }
}

WsseListener

class WsseListener implements ListenerInterface
{
    protected $securityContext;
    protected $authenticationManager;
    protected $logger;

    public function __construct(SecurityContextInterface $securityContext,
        AuthenticationManagerInterface $authenticationManager
    )
    {
        $this->securityContext = $securityContext;
        $this->authenticationManager = $authenticationManager;
    }

    public function handle(GetResponseEvent $event)
    {
        $request = $event->getRequest();

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

        $token = new WsseUserToken();
        $token->setUser($matches[1]);
        $token->digest   = $matches[2];
        $token->nonce    = $matches[3];
        $token->created  = $matches[4];
        try {
            $authToken = $this->authenticationManager->authenticate($token);
            $this->securityContext->setToken($authToken);
            return;
        } catch (AuthenticationException $failed) {
            $failedMessage = 'WSSE Login failed for '.$token->getUsername().'. Why ? '.$failed->getMessage();

            // Deny authentication with a '403 Forbidden' HTTP response
            $response = new Response();
            $response->setStatusCode(403);
            $response->setContent($failedMessage);
            $event->setResponse($response);
            return;

        }
        // By default deny authorization
        $response = new Response();
        $response->setStatusCode(403);
        $event->setResponse($response);
    }
}

Any help greatly appreciated this is so killing me...

0

There are 0 answers