diff --git a/src/Symfony/Bundle/FrameworkBundle/KernelBrowser.php b/src/Symfony/Bundle/FrameworkBundle/KernelBrowser.php index 38d2f06f2e..9d83925757 100644 --- a/src/Symfony/Bundle/FrameworkBundle/KernelBrowser.php +++ b/src/Symfony/Bundle/FrameworkBundle/KernelBrowser.php @@ -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} * 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 new file mode 100644 index 0000000000..be2999ec1c --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/SecurityTest.php @@ -0,0 +1,72 @@ + + * + * 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()); + } +} 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..686d7ad982 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Security/config.yml @@ -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 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..e894da532b --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Functional/app/Security/routing.yml @@ -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 } diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index eddb25a372..ea7b119c12 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": "^5.1", "symfony/security-csrf": "^4.4|^5.0", "symfony/security-http": "^4.4|^5.0", "symfony/serializer": "^4.4|^5.0",