feature #35997 [DX][Testing] Added a loginUser() method to test protected resources (javiereguiluz, wouterj)

This PR was merged into the 5.1-dev branch.

Discussion
----------

[DX][Testing] Added a loginUser() method to test protected resources

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| Deprecations? | no
| Tickets       | Fix #26839
| License       | MIT
| Doc PR        | tbd

This finishes https://github.com/symfony/symfony/pull/32850 original description:

> I know this won't work for 100% of our users ... but the goal is to make life easier to *most* of them. Thanks!

A custom `ConcreteToken` test-object is created as suggested by @linaori, to not bind this token to any specific implementation (as other implementations aren't fully compatible with eachother).

Commits
-------

2980a680d4 Added special test token and implemented 'real' functional tests
f516829d99 [DX][Testing] Added a loginUser() method to test protected resources
This commit is contained in:
Fabien Potencier 2020-03-11 08:47:25 +01:00
commit a51a0c5bd5
8 changed files with 217 additions and 0 deletions

View File

@ -11,6 +11,8 @@
namespace Symfony\Bundle\FrameworkBundle;
use Symfony\Bundle\FrameworkBundle\Test\TestBrowserToken;
use Symfony\Component\BrowserKit\Cookie;
use Symfony\Component\BrowserKit\CookieJar;
use Symfony\Component\BrowserKit\History;
use Symfony\Component\DependencyInjection\ContainerInterface;
@ -19,6 +21,7 @@ use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\HttpKernelBrowser;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\HttpKernel\Profiler\Profile as HttpProfile;
use Symfony\Component\Security\Core\User\UserInterface;
/**
* Simulates a browser and makes requests to a Kernel object.
@ -104,6 +107,31 @@ class KernelBrowser extends HttpKernelBrowser
$this->reboot = true;
}
/**
* @param UserInterface $user
*/
public function loginUser($user, string $firewallContext = 'main'): self
{
if (!interface_exists(UserInterface::class)) {
throw new \LogicException(sprintf('"%s" requires symfony/security-core to be installed.', __METHOD__));
}
if (!$user instanceof UserInterface) {
throw new \LogicException(sprintf('The first argument of "%s" must be instance of "%s", "%s" provided.', __METHOD__, UserInterface::class, \is_object($user) ? \get_class($user) : \gettype($user)));
}
$token = new TestBrowserToken($user->getRoles(), $user);
$token->setAuthenticated(true);
$session = $this->getContainer()->get('session');
$session->set('_security_'.$firewallContext, serialize($token));
$session->save();
$cookie = new Cookie($session->getName(), $session->getId());
$this->getCookieJar()->set($cookie);
return $this;
}
/**
* {@inheritdoc}
*

View File

@ -0,0 +1,37 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\Test;
use Symfony\Component\Security\Core\Authentication\Token\AbstractToken;
use Symfony\Component\Security\Core\User\UserInterface;
/**
* A very limited token that is used to login in tests using the KernelBrowser.
*
* @author Wouter de Jong <wouter@wouterj.nl>
*/
class TestBrowserToken extends AbstractToken
{
public function __construct(array $roles = [], UserInterface $user = null)
{
parent::__construct($roles);
if (null !== $user) {
$this->setUser($user);
}
}
public function getCredentials()
{
return null;
}
}

View File

@ -0,0 +1,26 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Controller;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
use Symfony\Component\HttpFoundation\Response;
class SecurityController implements ContainerAwareInterface
{
use ContainerAwareTrait;
public function profileAction()
{
return new Response('Welcome '.$this->container->get('security.token_storage')->getToken()->getUsername().'!');
}
}

View File

@ -0,0 +1,72 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\Tests\Functional;
use Symfony\Component\Security\Core\User\User;
class SecurityTest extends AbstractWebTestCase
{
/**
* @dataProvider getUsers
*/
public function testLoginUser(string $username, array $roles, ?string $firewallContext)
{
$user = new User($username, 'the-password', $roles);
$client = $this->createClient(['test_case' => 'Security', 'root_config' => 'config.yml']);
if (null === $firewallContext) {
$client->loginUser($user);
} else {
$client->loginUser($user, $firewallContext);
}
$client->request('GET', '/'.($firewallContext ?? 'main').'/user_profile');
$this->assertEquals('Welcome '.$username.'!', $client->getResponse()->getContent());
}
public function getUsers()
{
yield ['the-username', ['ROLE_FOO'], null];
yield ['the-username', ['ROLE_FOO'], 'main'];
yield ['other-username', ['ROLE_FOO'], 'custom'];
yield ['the-username', ['ROLE_FOO'], null];
yield ['no-role-username', [], null];
}
public function testLoginUserMultipleRequests()
{
$user = new User('the-username', 'the-password', ['ROLE_FOO']);
$client = $this->createClient(['test_case' => 'Security', 'root_config' => 'config.yml']);
$client->loginUser($user);
$client->request('GET', '/main/user_profile');
$this->assertEquals('Welcome the-username!', $client->getResponse()->getContent());
$client->request('GET', '/main/user_profile');
$this->assertEquals('Welcome the-username!', $client->getResponse()->getContent());
}
public function testLoginInBetweenRequests()
{
$user = new User('the-username', 'the-password', ['ROLE_FOO']);
$client = $this->createClient(['test_case' => 'Security', 'root_config' => 'config.yml']);
$client->request('GET', '/main/user_profile');
$this->assertTrue($client->getResponse()->isRedirect('http://localhost/login'));
$client->loginUser($user);
$client->request('GET', '/main/user_profile');
$this->assertEquals('Welcome the-username!', $client->getResponse()->getContent());
}
}

View File

@ -0,0 +1,20 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
use Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\TestBundle;
use Symfony\Bundle\SecurityBundle\SecurityBundle;
return [
new FrameworkBundle(),
new SecurityBundle(),
new TestBundle(),
];

View File

@ -0,0 +1,26 @@
imports:
- { resource: ./../config/default.yml }
security:
providers:
main:
memory:
users:
the-username: { password: the-password, roles: ['ROLE_FOO'] }
no-role-username: { password: the-password, roles: [] }
custom:
memory:
users:
other-username: { password: the-password, roles: ['ROLE_FOO'] }
firewalls:
main:
pattern: ^/main
form_login:
check_path: /main/login/check
provider: main
custom:
pattern: ^/custom
form_login:
check_path: /custom/login/check
provider: custom

View File

@ -0,0 +1,7 @@
security_profile:
path: /main/user_profile
defaults: { _controller: Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Controller\SecurityController::profileAction }
security_custom_profile:
path: /custom/user_profile
defaults: { _controller: Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Controller\SecurityController::profileAction }

View File

@ -47,6 +47,7 @@
"symfony/messenger": "^4.4|^5.0",
"symfony/mime": "^4.4|^5.0",
"symfony/process": "^4.4|^5.0",
"symfony/security-bundle": "^5.1",
"symfony/security-csrf": "^4.4|^5.0",
"symfony/security-http": "^4.4|^5.0",
"symfony/serializer": "^4.4|^5.0",