1

I'm trying to create a custom connection where I should use web service. So I read the tutorial on security and this one on custom provider. Now I'm trying to create my own login form with 3 fields : Email, password and number. After validation I understood that my /login_check pass in the function loadUserByUsername($username), but this function took in argument just $username and doesn't take my fields email and number. To execute my web service I need to get my 3 args. How can I customize my login form?

The goal is: When users submit the login form I want to send a web service with login form args. If I get my response without error I want to connect my user loaded by web service to symfony2 toolbar else I want to display an error message.

You can see my code here :

Security.yml :

security:
    encoders:
        MonApp\MonBundle\Security\User\WebserviceUser: sha512
        #Symfony\Component\Security\Core\User\User: plaintext

    # http://symfony.com/doc/current/book/security.html#hierarchical-roles
    role_hierarchy:
        ROLE_ADMIN:       ROLE_USER
        ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]

    # http://symfony.com/doc/current/book/security.html#where-do-users-come-    from-user-providers
    providers:
        #in_memory:
             #memory:
                #users:
                    #ryan:  { password: ryanpass, roles: 'ROLE_USER' }
                    #admin: { password: kitten, roles: 'ROLE_ADMIN' }
        webservice:
            id: webservice_user_provider

    # the main part of the security, where you can set up firewalls
    # for specific sections of your app
    firewalls:
        # disables authentication for assets and the profiler, adapt it according to your needs
        dev:
            pattern:  ^/(_(profiler|wdt)|css|images|js)/
            security: false

        area_secured:
            pattern:    ^/
            anonymous:  ~
            form_login:
                login_path:  /login
                check_path:  /login_check
                default_target_path: /test
            logout:
                path:   /logout
                target: /

    # with these settings you can restrict or allow access for different parts
    # of your application based on roles, ip, host or methods
    access_control:
        - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/, roles: ROLE_AUTHENTICATED }

WebserviceUser.php :

<?php

namespace MonApp\MonBundle\Security\User;

use Symfony\Component\Security\Core\User\UserInterface;

class WebserviceUser implements UserInterface
{
    private $email;
    private $password;
    private $num;
    private $salt;
    private $roles;

    public function __construct($email, $password, $num, $salt, array $roles)
    {
        $this->email = $email;
        $this->password = $password;
        $this->num = $num;
        $this->salt = $salt;
        $this->roles = $roles;
    }

    public function getUsername()
    {
        return '';
    }

    public function getEmail()
    {
        return $this->email;
    }

    public function getPassword()
    {
        return $this->password;
    }

    public function getNum()
    {
        return $this->num;
    }    

    public function getSalt()
    {
        return $this->salt;
    }

    public function getRoles()
    {
        return $this->roles;
    }

    public function eraseCredentials()
    {}

    public function isEqualTo(UserInterface $user)
    {
        if (!$user instanceof WebserviceUser) {
            return false;
        }

        if ($this->email !== $user->getEmail()) {
            return false;
        }

        if ($this->password !== $user->getPassword()) {
            return false;
        }

        if ($this->num !== $user->getNum()) {
            return false;
        }

        if ($this->getSalt() !== $user->getSalt()) {
            return false;
        }

        return true;
    }
}

WebserviceUserProvider.php

<?php

namespace MonApp\MonBundle\Security\User;

use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;

use MonApp\MonBundle\Security\User\WebserviceUser;

class WebserviceUserProvider implements UserProviderInterface
{
    public function loadUserByUsername($username)
    {
        //print_r($username);
        //die();
        // effectuez un appel à votre service web ici

        return new WebserviceUser('email', 'password', '45555', 'salt', array('ROLE_USER'));
        //throw new UsernameNotFoundException(sprintf('Username "%s" does not exist.', $username));
    }

    public function refreshUser(UserInterface $user)
    {
        if (!$user instanceof WebserviceUser) {
            throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_class($user)));
        }

        print_r($user);
        die();

        return $this->loadUserByUsername($user->getUsername());
    }

    public function supportsClass($class)
    {
        return $class === 'MonApp\MonBundle\Security\User\WebserviceUser';
    }
}

service.yml

parameters:
    webservice_user_provider.class:  MonApp\MonBundle\Security\User\WebserviceUserProvider

services:
    webservice_user_provider:
        class: "%webservice_user_provider.class%"

I won't put all the code, but my login action, template and routing are exactly the same than security link. But my user new WebserviceUser('email', 'password', '45555', 'salt', array('ROLE_USER')) isn't connected to the toolbar. So I think I forgot something...

Do I need to use a Listener, UserToken and Factory to do that ?

6
  • You are using the Anonymous firewall, maybe you should create a WebServiceFirewall class with it's own factory. Commented Apr 15, 2015 at 8:38
  • Thanks Med, do you have a link where i can implement my own firewall ? i put anonymous: ~ because i saw that in the cookbook, the tutorial about security. Commented Apr 15, 2015 at 8:52
  • If you are still stuck post again here, i will help you symfony.com/fr/doc/current/cookbook/security/… Commented Apr 15, 2015 at 8:53
  • Ok I followed all the tutorial but, which login form i should use ? it's the same than this link : symfony.com/doc/current/book/security.html ? For the user provider, should I use my custom user provider ? Because when i executed the code and delete anonymous: ~ I got a redirect error to /login. This is really hard.. Thanks Commented Apr 15, 2015 at 9:28
  • Can you join chat ? I created a room named Med, chat.stackoverflow.com/rooms/75270/med Commented Apr 15, 2015 at 9:34

1 Answer 1

0

Ok boy, prepare for a long answer.

I assume that you have a folder named Security placed in /MonApp/MonBundle

First you need a custom Token placed in Security/Token/WebServiceToken

<?php 
namespace MonApp\MonBundle\Security\Token;


use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;

class WebServiceToken implements TokenInterface
{
protected $attributes;
protected $authenticated;
protected $user;

public function __construct($attributes)
{
    $this->setAttributes($attributes);
    $this->authenticated = false;
    $this->user = null;
}


/**
 * {@inheritdoc}
 */
public function serialize()
{
    return serialize(
            array(
                    is_object($this->user) ? clone $this->user : $this->user,
                    $this->authenticated,
                    $this->attributes
            )
    );
}

/**
 * {@inheritdoc}
 */
public function unserialize($serialized)
{
    list($this->user, $this->authenticated, $this->attributes) = unserialize($serialized);
}


public function __toString()
{
    $result = '';

    foreach($this->attributes as $name => $value)
    {
        $result .= "$name: $value ";
    }

    return "Token($result)";
}

/**
 * Returns the user roles.
 *
 * @return RoleInterface[] An array of RoleInterface instances.
*/
public function getRoles()
{
    return $this->user->getRoles();
}

public function getUser()
{
    return $this->user;
}

public function setUser($user)
{
    $this->user = $user;
}

public function getUsername()
{
    return $this->user->getUsername();
}

public function isAuthenticated()
{
    return $this->authenticated;
}

public function setAuthenticated($isAuthenticated)
{
    $this->authenticated = $isAuthenticated;
}

public function eraseCredentials()
{
    ;
}

public function getAttributes()
{
    return $this->attributes;
}

public function setAttributes(array $attributes)
{
    $this->attributes = $attributes;
}

public function hasAttribute($name)
{
    return array_key_exists($name, $this->attributes);
}

public function getAttribute($name)
{
    if (!array_key_exists($name, $this->attributes)) {
        throw new \InvalidArgumentException(sprintf('This token has no "%s" attribute.', $name));
    }

    return $this->attributes[$name];
}

public function setAttribute($name, $value)
{
    $this->attributes[$name] = $value;
}

public function getCredentials()
{
    return null;
}
}

Then you need a Firewall in Security/Authentication/WebServiceAuthenticationListener

<?php 

namespace MonApp\MonBundle\Security\Authentication;

use MonApp\MonBundle\Security\Token\WebServiceToken;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\Security\Core\SecurityContextInterface;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\Firewall\ListenerInterface;

class WebServiceAuthenticationListener implements ListenerInterface
{

protected $securityContext;
protected $authentificationManager;
protected $logger;

/**
 * @param SecurityContextInterface $securityContext
 * @param AuthenticationManagerInterface $authenticationManager
 * @param LoggerInterface $logger
 */
public function __construct(SecurityContextInterface $securityContext, AuthenticationManagerInterface $authenticationManager, LoggerInterface $logger = null)
{
    $this->securityContext       = $securityContext;
    $this->authenticationManager = $authenticationManager;
    $this->logger                = $logger;
}


/**
 * {@inheritdoc}
 * @see \Symfony\Component\Security\Http\Firewall\ListenerInterface::handle()
 */
final public function handle(GetResponseEvent $event)
{
    $request = $event->getRequest();

     /**
     * Fill $attributes with the data you want to set in the user
     */
    $attributes = array();
    $token =  new WebServiceToken($attributes);


    try {
        if (null !== $this->logger ) {
            $this->logger->debug(sprintf('Vérification du contexte de sécurité pour le token: %s', $token));
        }

        $token = $this->authenticationManager->authenticate($token);

        if (null !== $this->logger) {
            $this->logger->info(sprintf('Authentification réussie: %s', $token));
        }

        // Token authentifié
        $this->securityContext->setToken($token);
    }
    catch (AuthenticationException $failed) {
        throw $failed;
    }
}
}

Then you need an Authentication provider in Security/Authentication/WebServiceAuthenticationProvider

<?php

namespace MonApp\MonBundle\Security\Authentication;

use Symfony\Component\Security\Core\Exception\AuthenticationException;

use MonApp\MonBundle\Security\User\WebServiceUser;
use MonApp\MonBundle\Security\User\WebServiceUserProvider;
use MonApp\MonBundle\Security\Token\WebServiceToken;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface;

class WebServiceAuthenticationProvider implements AuthenticationProviderInterface
{

protected $provider;

public function __construct(WebServiceUserProvider $provider)
{
    $this->provider = $provider;
}

 public function authenticate(TokenInterface $token)
 {
    if (!$this->supports($token)) {
        return new AuthenticationException('Token non supporté');
    }


    $user = $this->provider->createUser($token->getAttributes());
    $token->setUser($user);

    /**
     * CALL TO THE WEB SERVICE HERE
     */
    $myCallisASuccess = true;
    if($myCallisASuccess) {
        $token->setAuthenticated(true);
    }

    return $token;
 }


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

Now the factory ... Security/Factory/WebServiceFactory

<?php 

namespace MonApp\MonBundle\Security\Factory;

use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\DefinitionDecorator;
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SecurityFactoryInterface;



class WebServiceFactory implements SecurityFactoryInterface
{
/**
 * {@inheritdoc}
 * @see \Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SecurityFactoryInterface::create()
 */
public function create(ContainerBuilder $container, $id, $config, $userProvider, $defaultEntryPoint)
{
    $providerId = 'security.authentication.provider.web_service'.$id;
    $container->setDefinition($providerId, new DefinitionDecorator('web_service.security.authentication.provider'));

    $listenerId = 'security.authentication.listener.web_service.'.$id;
    $container->setDefinition($listenerId, new DefinitionDecorator('web_service.security.authentication.listener'));

    return array($providerId, $listenerId, $defaultEntryPoint);
}

/**
 * {@inheritdoc}
 * @see \Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SecurityFactoryInterface::getPosition()
 */
public function getPosition()
{
    return 'pre_auth';
}


/**
 * {@inheritdoc}
 * @see \Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SecurityFactoryInterface::getKey()
 */
public function getKey()
{
    return 'web_service';
}



}

You have to edit WebServiceUserProvider by adding this function

    public function createUser(array $attributes)
{
    $email = $attributes['email'];
    $password = $attributes['password'];
    $num = $attributes['num'];
    $salt = $attributes['salt'];

    $user = new WebServiceUser($email, $password, $num, $salt);

    return $user;
}

And remove $roles from you WebServiceUSer class:

public function __construct($email, $password, $num, $salt)
{
    $this->email = $email;
    $this->password = $password;
    $this->num = $num;
    $this->salt = $salt;
    $this->roles = array();
}

Ok, now you have all you security classes done. Let's configure this....

In the class MonBundle

<?php 

namespace MonApp\Bundle\MonBundle;

use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use MonApp\Bundle\MonBundle\Security\Factory\WebServiceFactory;


class MonBundle extends Bundle
{
/**
 * {@inheritdoc}
 * @see \Symfony\Component\HttpKernel\Bundle\Bundle::build()
 */
public function build(ContainerBuilder $container)
{
    parent::build($container);

    // Ajout de la clef 'web_service' à l'extension security
    $extension = $container->getExtension('security');
    $extension->addSecurityListenerFactory(new WebServiceFactory());

}
}

In the config of MonBundle

services:
    web_service.security.user.provider:
        class:  MonApp\Bundle\MonBundle\Security\User\WebServiceUserProvider

web_service.security.authentication.listener:
    class:  MonApp\Bundle\MonBundle\Security\Authentication\WebServiceAuthenticationListener
    arguments: ['@security.context', '@web_service.security.authentication.provider','@?logger']

web_service.security.authentication.provider:
    class:  MonApp\Bundle\MonBundle\Security\Authentication\WebServiceAuthenticationProvider
    arguments: ['@web_service.security.user.provider']

And last, in your app config:

security:
    area_secured:
        pattern:    ^/
        web_service:  ~
        form_login:
            login_path:  /login
            check_path:  /login_check
            default_target_path: /test
        logout:
            path:   /logout
            target: /
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.