From f516829d996b56cccac16e7ab43e3cb75c617835 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Thu, 1 Aug 2019 11:02:55 +0200 Subject: [PATCH 1/2] [DX][Testing] Added a loginUser() method to test protected resources --- .../Bundle/FrameworkBundle/KernelBrowser.php | 16 ++++++ .../Tests/Functional/SecurityTest.php | 57 +++++++++++++++++++ .../Tests/Functional/app/Security/bundles.php | 20 +++++++ .../Tests/Functional/app/Security/config.yml | 2 + .../Tests/Functional/app/Security/routing.yml | 2 + .../Bundle/FrameworkBundle/composer.json | 1 + 6 files changed, 98 insertions(+) create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/Functional/SecurityTest.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Security/bundles.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Security/config.yml create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Security/routing.yml diff --git a/src/Symfony/Bundle/FrameworkBundle/KernelBrowser.php b/src/Symfony/Bundle/FrameworkBundle/KernelBrowser.php index 38d2f06f2e..24f17e230d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/KernelBrowser.php +++ b/src/Symfony/Bundle/FrameworkBundle/KernelBrowser.php @@ -11,6 +11,7 @@ namespace Symfony\Bundle\FrameworkBundle; +use Symfony\Component\BrowserKit\Cookie; use Symfony\Component\BrowserKit\CookieJar; use Symfony\Component\BrowserKit\History; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -19,6 +20,8 @@ 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\Authentication\Token\UsernamePasswordToken; +use Symfony\Component\Security\Core\User\UserInterface; /** * Simulates a browser and makes requests to a Kernel object. @@ -203,4 +206,17 @@ EOF; return $code.$this->getHandleScript(); } + + public function loginUser(UserInterface $user, string $firewallContext = 'main'): self + { + $token = new UsernamePasswordToken($user, null, $firewallContext, $user->getRoles()); + $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; + } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/SecurityTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/SecurityTest.php new file mode 100644 index 0000000000..2e986f3070 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/SecurityTest.php @@ -0,0 +1,57 @@ + + * + * 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\HttpFoundation\Session\SessionInterface; +use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; +use Symfony\Component\Security\Core\User\User; + +class SecurityTest extends AbstractWebTestCase +{ + /** + * @dataProvider getUsers + */ + public function testLoginUser(string $username, ?string $password, array $roles, ?string $firewallContext, string $expectedProviderKey) + { + $user = new User($username, $password, $roles); + $client = $this->createClient(['test_case' => 'Security', 'root_config' => 'config.yml']); + + if (null === $firewallContext) { + $client->loginUser($user); + } else { + $client->loginUser($user, $firewallContext); + } + + /** @var SessionInterface $session */ + $session = $client->getContainer()->get('session'); + /** @var UsernamePasswordToken $userToken */ + $userToken = unserialize($session->get('_security_'.$expectedProviderKey)); + + $this->assertSame('_security_'.$expectedProviderKey, array_keys($session->all())[0]); + $this->assertSame($expectedProviderKey, $userToken->getProviderKey()); + $this->assertSame($username, $userToken->getUsername()); + $this->assertSame($password, $userToken->getUser()->getPassword()); + $this->assertSame($roles, $userToken->getUser()->getRoles()); + + $this->assertNotNull($client->getCookieJar()->get('MOCKSESSID')); + } + + public function getUsers() + { + yield ['the-username', 'the-password', ['ROLE_FOO'], null, 'main']; + yield ['the-username', 'the-password', ['ROLE_FOO'], 'main', 'main']; + yield ['the-username', 'the-password', ['ROLE_FOO'], 'custom_firewall_context', 'custom_firewall_context']; + + yield ['the-username', null, ['ROLE_FOO'], null, 'main']; + yield ['the-username', 'the-password', [], null, 'main']; + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Security/bundles.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Security/bundles.php new file mode 100644 index 0000000000..bd57eef389 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Security/bundles.php @@ -0,0 +1,20 @@ + + * + * 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(), +]; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Security/config.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Security/config.yml new file mode 100644 index 0000000000..65dd6c7fa9 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Security/config.yml @@ -0,0 +1,2 @@ +imports: + - { resource: ./../config/default.yml } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Security/routing.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Security/routing.yml new file mode 100644 index 0000000000..d4b77c3f70 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Security/routing.yml @@ -0,0 +1,2 @@ +_sessiontest_bundle: + resource: '@TestBundle/Resources/config/routing.yml' diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index eddb25a372..b2b96f3052 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -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": "^4.0|^5.0", "symfony/security-csrf": "^4.4|^5.0", "symfony/security-http": "^4.4|^5.0", "symfony/serializer": "^4.4|^5.0", From 2980a680d4731135655336ad9e437824e06fccda Mon Sep 17 00:00:00 2001 From: Wouter de Jong Date: Sat, 7 Mar 2020 19:27:18 +0100 Subject: [PATCH 2/2] Added special test token and implemented 'real' functional tests --- .../Bundle/FrameworkBundle/KernelBrowser.php | 40 ++++++++----- .../FrameworkBundle/Test/TestBrowserToken.php | 37 ++++++++++++ .../Controller/SecurityController.php | 26 +++++++++ .../Tests/Functional/SecurityTest.php | 57 ++++++++++++------- .../Tests/Functional/app/Security/config.yml | 24 ++++++++ .../Tests/Functional/app/Security/routing.yml | 9 ++- .../Bundle/FrameworkBundle/composer.json | 2 +- 7 files changed, 157 insertions(+), 38 deletions(-) create mode 100644 src/Symfony/Bundle/FrameworkBundle/Test/TestBrowserToken.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Controller/SecurityController.php diff --git a/src/Symfony/Bundle/FrameworkBundle/KernelBrowser.php b/src/Symfony/Bundle/FrameworkBundle/KernelBrowser.php index 24f17e230d..9d83925757 100644 --- a/src/Symfony/Bundle/FrameworkBundle/KernelBrowser.php +++ b/src/Symfony/Bundle/FrameworkBundle/KernelBrowser.php @@ -11,6 +11,7 @@ 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; @@ -20,7 +21,6 @@ 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\Authentication\Token\UsernamePasswordToken; use Symfony\Component\Security\Core\User\UserInterface; /** @@ -107,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} * @@ -206,17 +231,4 @@ EOF; return $code.$this->getHandleScript(); } - - public function loginUser(UserInterface $user, string $firewallContext = 'main'): self - { - $token = new UsernamePasswordToken($user, null, $firewallContext, $user->getRoles()); - $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; - } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Test/TestBrowserToken.php b/src/Symfony/Bundle/FrameworkBundle/Test/TestBrowserToken.php new file mode 100644 index 0000000000..08f7b107d0 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Test/TestBrowserToken.php @@ -0,0 +1,37 @@ + + * + * 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 + */ +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; + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Controller/SecurityController.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Controller/SecurityController.php new file mode 100644 index 0000000000..6bf27e1ca2 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/Bundle/TestBundle/Controller/SecurityController.php @@ -0,0 +1,26 @@ + + * + * 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().'!'); + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/SecurityTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/SecurityTest.php index 2e986f3070..be2999ec1c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/SecurityTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/SecurityTest.php @@ -11,8 +11,6 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Functional; -use Symfony\Component\HttpFoundation\Session\SessionInterface; -use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use Symfony\Component\Security\Core\User\User; class SecurityTest extends AbstractWebTestCase @@ -20,9 +18,9 @@ class SecurityTest extends AbstractWebTestCase /** * @dataProvider getUsers */ - public function testLoginUser(string $username, ?string $password, array $roles, ?string $firewallContext, string $expectedProviderKey) + public function testLoginUser(string $username, array $roles, ?string $firewallContext) { - $user = new User($username, $password, $roles); + $user = new User($username, 'the-password', $roles); $client = $this->createClient(['test_case' => 'Security', 'root_config' => 'config.yml']); if (null === $firewallContext) { @@ -31,27 +29,44 @@ class SecurityTest extends AbstractWebTestCase $client->loginUser($user, $firewallContext); } - /** @var SessionInterface $session */ - $session = $client->getContainer()->get('session'); - /** @var UsernamePasswordToken $userToken */ - $userToken = unserialize($session->get('_security_'.$expectedProviderKey)); - - $this->assertSame('_security_'.$expectedProviderKey, array_keys($session->all())[0]); - $this->assertSame($expectedProviderKey, $userToken->getProviderKey()); - $this->assertSame($username, $userToken->getUsername()); - $this->assertSame($password, $userToken->getUser()->getPassword()); - $this->assertSame($roles, $userToken->getUser()->getRoles()); - - $this->assertNotNull($client->getCookieJar()->get('MOCKSESSID')); + $client->request('GET', '/'.($firewallContext ?? 'main').'/user_profile'); + $this->assertEquals('Welcome '.$username.'!', $client->getResponse()->getContent()); } public function getUsers() { - yield ['the-username', 'the-password', ['ROLE_FOO'], null, 'main']; - yield ['the-username', 'the-password', ['ROLE_FOO'], 'main', 'main']; - yield ['the-username', 'the-password', ['ROLE_FOO'], 'custom_firewall_context', 'custom_firewall_context']; + yield ['the-username', ['ROLE_FOO'], null]; + yield ['the-username', ['ROLE_FOO'], 'main']; + yield ['other-username', ['ROLE_FOO'], 'custom']; - yield ['the-username', null, ['ROLE_FOO'], null, 'main']; - yield ['the-username', 'the-password', [], null, 'main']; + 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()); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Security/config.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Security/config.yml index 65dd6c7fa9..686d7ad982 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Security/config.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Security/config.yml @@ -1,2 +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 diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Security/routing.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Security/routing.yml index d4b77c3f70..e894da532b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Security/routing.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Security/routing.yml @@ -1,2 +1,7 @@ -_sessiontest_bundle: - resource: '@TestBundle/Resources/config/routing.yml' +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 } diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index b2b96f3052..ea7b119c12 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -47,7 +47,7 @@ "symfony/messenger": "^4.4|^5.0", "symfony/mime": "^4.4|^5.0", "symfony/process": "^4.4|^5.0", - "symfony/security-bundle": "^4.0|^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",