mardi 31 décembre 2019

Symfony Test - null token with Guard Authentication

I have a Guard Authentication in my Symfony Web Application. I would like to perform some unit tests. I'm unable to simulate an authentification in my test. The token stays null when calling $tokenStorage->getToken().

Note:

  • The login authentification is working under dev and prod environnement.
  • I saw quite a lot of related topics without success and the doc as well.
  • Symfony version: 3.4.

Reproduce: you can reproduce the error from this repo (symfony project).

Code sample:

After manually creating an User, the login function used in tests:

private function logIn($firewallName = 'main'){
   // dummy call to bypass the hasPreviousSession check
   $crawler = $this->client->request('GET', '/');
   $session = $this->client->getContainer()->get('session');

   /** @var User $user */
   $user = $this->entityManager->getRepository(User::class)
       ->findOneBy(['email' => 'user1@example.com']);

   // you may need to use a different token class depending on your application.
   // for example, when using Guard authentication you must instantiate PostAuthenticationGuardToken
   $token = new PostAuthenticationGuardToken($user, $firewallName, [new Role('ROLE_CLIENT')]);
        self::$kernel->getContainer()->get('security.token_storage')->setToken($token);

   $session->set('_security_'.$firewallName, serialize($token));
   $session->save();

   $cookie = new Cookie($session->getName(), $session->getId());
   $this->client->getCookieJar()->set($cookie);
}

The User call from tokenStorage (from service function):

class ExampleValidator extends ConstraintValidator{
    protected $requestStack;
    protected $em;
    protected $user_id;

    public function __construct(RequestStack $request,
                                EntityManager $em,
                                TokenStorage $tokenStorage){
        $this->requestStack = $request;
        $this->em = $em;

        /** @var User $user */
        // Token is always null
        $user = $tokenStorage->getToken()->getUser();
        $this->user_id = $user != "anon." ? $user->getId() : null;
    }

    /**
     * @param $value
     * @param Constraint $constraint
     */
    public function validate($value, Constraint $constraint)
    {
        // validation rules ...
    }
}

LoginFormAuthenticator.php

<?php

namespace AppBundle\Security;


use AppBundle\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
use Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator;
use Symfony\Component\Security\Http\Util\TargetPathTrait;

class LoginFormAuthenticator extends AbstractFormLoginAuthenticator{
    use TargetPathTrait;

    private $entityManager;
    private $urlGenerator;
    private $csrfTokenManager;
    private $passwordEncoder;
    private $loginAttemptRepository;


    public function __construct(EntityManagerInterface $entityManager,
                                UrlGeneratorInterface $urlGenerator,
                                CsrfTokenManagerInterface $csrfTokenManager,
                                UserPasswordEncoderInterface $passwordEncoder){
        $this->entityManager = $entityManager;
        $this->urlGenerator = $urlGenerator;
        $this->csrfTokenManager = $csrfTokenManager;
        $this->passwordEncoder = $passwordEncoder;
    }

    /**
     * @param Request $request
     * @return bool
     */
    public function supports(Request $request){
        return $request->getPathInfo() == '/login_check' &&
            $request->isMethod('POST') &&
            $request->request->get('_password') !== null;
    }


    /**
     * @param Request $request
     * @return array|mixed|void|null
     */
    public function getCredentials(Request $request){
        $isLoginSubmit = $request->getPathInfo() == '/login_check' &&
            $request->isMethod('POST') &&
            $request->request->get('_password') !== null;
        $isCaptcha = $request->request->get('captcha_set');

        if ($isCaptcha == 1 && $request->request->get('_password') !== null) {
            $secret = ...;
            if($_POST['g-recaptcha-response'] !== null){
                // Paramètre renvoyé par le recaptcha
                $response = $_POST['g-recaptcha-response'];
                $remoteip = $_SERVER['REMOTE_ADDR'];

                $api_url = "https://www.google.com/recaptcha/api/siteverify?secret="
                    . $secret
                    . "&response=" . $response
                    . "&remoteip=" . $remoteip ;

                $decode = json_decode(file_get_contents($api_url), true);

                if ($decode['success'] == true) {
                    $username = $request->request->get('_username');
                    $password = $request->request->get('_password');
                    $csrfToken = $request->request->get('_csrf_token');

                    if (false === $this->csrfTokenManager->isTokenValid(new CsrfToken('authenticate', $csrfToken))) {
                        throw new InvalidCsrfTokenException('Invalid CSRF token.');
                    }

                    $request->getSession()->set(
                        Security::LAST_USERNAME,
                        $username
                    );

                    return [
                        'username' => $username,
                        'password' => $password,
                    ];
                }
                else{
                    throw new CustomUserMessageAuthenticationException('Captcha invalids.');
                }
            }
            else{
                throw new CustomUserMessageAuthenticationException('Captcha invalids.');
            }
        }
        else {
            if (!$isLoginSubmit) {
                // skip authentication
                return;
            }

            $username = $request->request->get('_username');
            $password = $request->request->get('_password');
            $csrfToken = $request->request->get('_csrf_token');

            if (false === $this->csrfTokenManager->isTokenValid(new CsrfToken('authenticate', $csrfToken))) {
                throw new InvalidCsrfTokenException('Invalid CSRF token.');
            }

            $request->getSession()->set(
                Security::LAST_USERNAME,
                $username
            );

            return [
                'username' => $username,
                'password' => $password,
            ];
        }
    }

    /**
     * @param mixed $credentials
     * @param UserProviderInterface $userProvider
     * @return User|object|UserInterface|null
     */
    public function getUser($credentials, UserProviderInterface $userProvider){
        $username = $credentials["username"];
        $user = $this->entityManager->getRepository(User::class)
            ->findOneBy(['username' => $username]);
        return $user;
    }


    /**
     * @param mixed $credentials
     * @param UserInterface $user
     * @return bool
     */
    public function checkCredentials($credentials, UserInterface $user){
        $password = $credentials["password"];
        $rep = false;
        if ($this->passwordEncoder->isPasswordValid($user, $password)){
            $rep = true;
        }
        return $rep;
    }

    /**
     * @param Request $request
     * @param TokenInterface $token
     * @param string $providerKey
     * @return RedirectResponse
     */
    public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey){
        $targetPath = null;
        if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) {
            return new RedirectResponse($targetPath);
        }
        return new RedirectResponse($this->urlGenerator->generate('map'));
    }

    /**
     * @return string
     */
    protected function getLoginUrl(){
        return $this->urlGenerator->generate('fos_user_security_login');
    }
}

Aucun commentaire:

Enregistrer un commentaire