Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions src/Symfony/Bundle/FrameworkBundle/KernelBrowser.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -19,6 +21,7 @@
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.
Expand Down Expand Up @@ -104,6 +107,31 @@ public function enableReboot()
$this->reboot = true;
}

/**
* @param UserInterface $user
*/
public function loginUser($user, string $firewallContext = 'main'): self
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing the typehint allowed me to have a bit nicer error message when Security is not installed.

{
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)));
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if I need to use the get_debug_type() function here already, or if #35945 has to be merged first.

}

$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}
*
Expand Down
37 changes: 37 additions & 0 deletions src/Symfony/Bundle/FrameworkBundle/Test/TestBrowserToken.php
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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().'!');
}
}
Original file line number Diff line number Diff line change
@@ -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());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no assertion on the roles, I'd suggest to add some, to make sure roles are passed to the user and token, WDYT?

}

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());
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about adding some tests to check for users that are not in the provider?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It has to be in the user provider (as Symfony reloads the user upon every requests and if the user changed (i.e. is no longer available), the user is logged out). Do you mean adding a test that the request is unsuccesfull?

}
Original file line number Diff line number Diff line change
@@ -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(),
];
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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 }
1 change: 1 addition & 0 deletions src/Symfony/Bundle/FrameworkBundle/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down