Merge branch '3.4'
* 3.4: [FrameworkBundle] Register a NullLogger from test kernels [SecurityBundle] Deprecate auto picking the first provider [Security] Add user impersonation support for stateless authentication
This commit is contained in:
commit
bc4a69225f
@ -317,6 +317,13 @@ SecurityBundle
|
||||
* Deprecated the HTTP digest authentication: `HttpDigestFactory` will be removed in 4.0.
|
||||
Use another authentication system like `http_basic` instead.
|
||||
|
||||
* Deprecated setting the `switch_user.stateless` option to false when the firewall is `stateless`.
|
||||
Setting it to false will have no effect in 4.0.
|
||||
|
||||
* Not configuring explicitly the provider on a firewall is ambiguous when there is more than one registered provider.
|
||||
Using the first configured provider is deprecated since 3.4 and will throw an exception on 4.0.
|
||||
Explicitly configure the provider to use on your firewalls.
|
||||
|
||||
Translation
|
||||
-----------
|
||||
|
||||
|
@ -694,6 +694,12 @@ SecurityBundle
|
||||
* Removed the HTTP digest authentication system. The `HttpDigestFactory` class
|
||||
has been removed. Use another authentication system like `http_basic` instead.
|
||||
|
||||
* The `switch_user.stateless` option is now always true if the firewall is stateless.
|
||||
|
||||
* Not configuring explicitly the provider on a firewall is ambiguous when there is more than one registered provider.
|
||||
The first configured provider is not used anymore and an exception is thrown instead.
|
||||
Explicitly configure the provider to use on your firewalls.
|
||||
|
||||
Serializer
|
||||
----------
|
||||
|
||||
|
@ -11,8 +11,10 @@
|
||||
|
||||
namespace Symfony\Bundle\FrameworkBundle\Tests\Command\CacheClearCommand\Fixture;
|
||||
|
||||
use Psr\Log\NullLogger;
|
||||
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
|
||||
use Symfony\Component\Config\Loader\LoaderInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\HttpKernel\Kernel;
|
||||
|
||||
class TestAppKernel extends Kernel
|
||||
@ -33,4 +35,9 @@ class TestAppKernel extends Kernel
|
||||
{
|
||||
$loader->load(__DIR__.DIRECTORY_SEPARATOR.'config.yml');
|
||||
}
|
||||
|
||||
protected function build(ContainerBuilder $container)
|
||||
{
|
||||
$container->register('logger', NullLogger::class);
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,9 @@
|
||||
|
||||
namespace Symfony\Bundle\FrameworkBundle\Tests\Functional\app;
|
||||
|
||||
use Psr\Log\NullLogger;
|
||||
use Symfony\Component\Config\Loader\LoaderInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\Filesystem\Filesystem;
|
||||
use Symfony\Component\HttpKernel\Kernel;
|
||||
|
||||
@ -72,6 +74,11 @@ class AppKernel extends Kernel
|
||||
$loader->load($this->rootConfig);
|
||||
}
|
||||
|
||||
protected function build(ContainerBuilder $container)
|
||||
{
|
||||
$container->register('logger', NullLogger::class);
|
||||
}
|
||||
|
||||
public function serialize()
|
||||
{
|
||||
return serialize(array($this->varDir, $this->testCase, $this->rootConfig, $this->getEnvironment(), $this->isDebug()));
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace Symfony\Bundle\FrameworkBundle\Tests\Kernel;
|
||||
|
||||
use Psr\Log\NullLogger;
|
||||
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
|
||||
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
|
||||
use Symfony\Component\Config\Loader\LoaderInterface;
|
||||
@ -77,6 +78,7 @@ class ConcreteMicroKernel extends Kernel implements EventSubscriberInterface
|
||||
|
||||
protected function configureContainer(ContainerBuilder $c, LoaderInterface $loader)
|
||||
{
|
||||
$c->register('logger', NullLogger::class);
|
||||
$c->loadFromExtension('framework', array(
|
||||
'secret' => '$ecret',
|
||||
));
|
||||
|
@ -30,6 +30,8 @@ CHANGELOG
|
||||
* deprecated command `acl:set` along with `SetAclCommand` class
|
||||
* deprecated command `init:acl` along with `InitAclCommand` class
|
||||
* Added support for the new Argon2i password encoder
|
||||
* added `stateless` option to the `switch_user` listener
|
||||
* deprecated auto picking the first registered provider when no configured provider on a firewall and ambiguous
|
||||
|
||||
3.3.0
|
||||
-----
|
||||
|
@ -254,6 +254,7 @@ class MainConfiguration implements ConfigurationInterface
|
||||
->scalarNode('provider')->end()
|
||||
->scalarNode('parameter')->defaultValue('_switch_user')->end()
|
||||
->scalarNode('role')->defaultValue('ROLE_ALLOWED_TO_SWITCH')->end()
|
||||
->booleanNode('stateless')->defaultValue(false)->end()
|
||||
->end()
|
||||
->end()
|
||||
;
|
||||
|
@ -262,6 +262,10 @@ class SecurityExtension extends Extension
|
||||
$defaultProvider = $providerIds[$normalizedName];
|
||||
} else {
|
||||
$defaultProvider = reset($providerIds);
|
||||
|
||||
if (count($providerIds) > 1) {
|
||||
@trigger_error(sprintf('Firewall "%s" has no "provider" set but multiple providers exist. Using the first configured provider (%s) is deprecated since 3.4 and will throw an exception in 4.0, set the "provider" key on the firewall instead.', $id, key($providerIds)), E_USER_DEPRECATED);
|
||||
}
|
||||
}
|
||||
|
||||
$config->replaceArgument(5, $defaultProvider);
|
||||
@ -359,7 +363,7 @@ class SecurityExtension extends Extension
|
||||
// Switch user listener
|
||||
if (isset($firewall['switch_user'])) {
|
||||
$listenerKeys[] = 'switch_user';
|
||||
$listeners[] = new Reference($this->createSwitchUserListener($container, $id, $firewall['switch_user'], $defaultProvider));
|
||||
$listeners[] = new Reference($this->createSwitchUserListener($container, $id, $firewall['switch_user'], $defaultProvider, $firewall['stateless']));
|
||||
}
|
||||
|
||||
// Access listener
|
||||
@ -602,10 +606,15 @@ class SecurityExtension extends Extension
|
||||
return $exceptionListenerId;
|
||||
}
|
||||
|
||||
private function createSwitchUserListener($container, $id, $config, $defaultProvider)
|
||||
private function createSwitchUserListener($container, $id, $config, $defaultProvider, $stateless)
|
||||
{
|
||||
$userProvider = isset($config['provider']) ? $this->getUserProviderId($config['provider']) : $defaultProvider;
|
||||
|
||||
// in 4.0, ignore the `switch_user.stateless` key if $stateless is `true`
|
||||
if ($stateless && false === $config['stateless']) {
|
||||
@trigger_error(sprintf('Firewall "%s" is configured as "stateless" but the "switch_user.stateless" key is set to false. Both should have the same value, the firewall\'s "stateless" value will be used as default value for the "switch_user.stateless" key in 4.0.', $id), E_USER_DEPRECATED);
|
||||
}
|
||||
|
||||
$switchUserListenerId = 'security.authentication.switchuser_listener.'.$id;
|
||||
$listener = $container->setDefinition($switchUserListenerId, new ChildDefinition('security.authentication.switchuser_listener'));
|
||||
$listener->replaceArgument(1, new Reference($userProvider));
|
||||
@ -613,6 +622,7 @@ class SecurityExtension extends Extension
|
||||
$listener->replaceArgument(3, $id);
|
||||
$listener->replaceArgument(6, $config['parameter']);
|
||||
$listener->replaceArgument(7, $config['role']);
|
||||
$listener->replaceArgument(9, $config['stateless']);
|
||||
|
||||
return $switchUserListenerId;
|
||||
}
|
||||
|
@ -230,6 +230,7 @@
|
||||
<argument>_switch_user</argument>
|
||||
<argument>ROLE_ALLOWED_TO_SWITCH</argument>
|
||||
<argument type="service" id="event_dispatcher" on-invalid="null"/>
|
||||
<argument>false</argument> <!-- Stateless -->
|
||||
</service>
|
||||
|
||||
<service id="security.access_listener" class="Symfony\Component\Security\Http\Firewall\AccessListener">
|
||||
|
@ -116,6 +116,7 @@ abstract class CompleteConfigurationTest extends TestCase
|
||||
array(
|
||||
'parameter' => '_switch_user',
|
||||
'role' => 'ROLE_ALLOWED_TO_SWITCH',
|
||||
'stateless' => true,
|
||||
),
|
||||
),
|
||||
array(
|
||||
|
@ -60,12 +60,13 @@ $container->loadFromExtension('security', array(
|
||||
),
|
||||
|
||||
'firewalls' => array(
|
||||
'simple' => array('pattern' => '/login', 'security' => false),
|
||||
'simple' => array('provider' => 'default', 'pattern' => '/login', 'security' => false),
|
||||
'secure' => array('stateless' => true,
|
||||
'provider' => 'default',
|
||||
'http_basic' => true,
|
||||
'form_login' => true,
|
||||
'anonymous' => true,
|
||||
'switch_user' => true,
|
||||
'switch_user' => array('stateless' => true),
|
||||
'x509' => true,
|
||||
'remote_user' => true,
|
||||
'logout' => true,
|
||||
@ -74,6 +75,7 @@ $container->loadFromExtension('security', array(
|
||||
'logout_on_user_change' => true,
|
||||
),
|
||||
'host' => array(
|
||||
'provider' => 'default',
|
||||
'pattern' => '/test',
|
||||
'host' => 'foo\\.example\\.org',
|
||||
'methods' => array('GET', 'POST'),
|
||||
@ -82,6 +84,7 @@ $container->loadFromExtension('security', array(
|
||||
'logout_on_user_change' => true,
|
||||
),
|
||||
'with_user_checker' => array(
|
||||
'provider' => 'default',
|
||||
'user_checker' => 'app.user_checker',
|
||||
'anonymous' => true,
|
||||
'http_basic' => true,
|
||||
|
@ -17,7 +17,7 @@ $container->loadFromExtension('security', array(
|
||||
'http_basic' => true,
|
||||
'form_login' => true,
|
||||
'anonymous' => true,
|
||||
'switch_user' => true,
|
||||
'switch_user' => array('stateless' => true),
|
||||
'x509' => true,
|
||||
'remote_user' => true,
|
||||
'logout' => true,
|
||||
|
@ -43,13 +43,13 @@
|
||||
<chain providers="service, basic" />
|
||||
</provider>
|
||||
|
||||
<firewall name="simple" pattern="/login" security="false" />
|
||||
<firewall name="simple" pattern="/login" security="false" provider="default" />
|
||||
|
||||
<firewall name="secure" stateless="true">
|
||||
<firewall name="secure" stateless="true" provider="default">
|
||||
<http-basic />
|
||||
<form-login />
|
||||
<anonymous />
|
||||
<switch-user />
|
||||
<switch-user stateless="true" />
|
||||
<x509 />
|
||||
<remote-user />
|
||||
<user-checker />
|
||||
@ -57,12 +57,12 @@
|
||||
<remember-me secret="TheSecret"/>
|
||||
</firewall>
|
||||
|
||||
<firewall name="host" pattern="/test" host="foo\.example\.org" methods="GET,POST" logout-on-user-change="true">
|
||||
<firewall name="host" pattern="/test" host="foo\.example\.org" methods="GET,POST" logout-on-user-change="true" provider="default">
|
||||
<anonymous />
|
||||
<http-basic />
|
||||
</firewall>
|
||||
|
||||
<firewall name="with_user_checker" logout-on-user-change="true">
|
||||
<firewall name="with_user_checker" logout-on-user-change="true" provider="default">
|
||||
<anonymous />
|
||||
<http-basic />
|
||||
<user-checker>app.user_checker</user-checker>
|
||||
|
@ -17,7 +17,7 @@
|
||||
<http-basic />
|
||||
<form-login />
|
||||
<anonymous />
|
||||
<switch-user />
|
||||
<switch-user stateless="true" />
|
||||
<x509 />
|
||||
<remote-user />
|
||||
<user-checker />
|
||||
|
@ -43,11 +43,13 @@ security:
|
||||
firewalls:
|
||||
simple: { pattern: /login, security: false }
|
||||
secure:
|
||||
provider: default
|
||||
stateless: true
|
||||
http_basic: true
|
||||
form_login: true
|
||||
anonymous: true
|
||||
switch_user: true
|
||||
switch_user:
|
||||
stateless: true
|
||||
x509: true
|
||||
remote_user: true
|
||||
logout: true
|
||||
@ -56,6 +58,7 @@ security:
|
||||
user_checker: ~
|
||||
|
||||
host:
|
||||
provider: default
|
||||
pattern: /test
|
||||
host: foo\.example\.org
|
||||
methods: [GET,POST]
|
||||
@ -64,6 +67,7 @@ security:
|
||||
logout_on_user_change: true
|
||||
|
||||
with_user_checker:
|
||||
provider: default
|
||||
anonymous: ~
|
||||
http_basic: ~
|
||||
user_checker: app.user_checker
|
||||
|
@ -12,7 +12,8 @@ security:
|
||||
http_basic: true
|
||||
form_login: true
|
||||
anonymous: true
|
||||
switch_user: true
|
||||
switch_user:
|
||||
stateless: true
|
||||
x509: true
|
||||
remote_user: true
|
||||
logout: true
|
||||
|
@ -148,6 +148,57 @@ class SecurityExtensionTest extends TestCase
|
||||
$container->compile();
|
||||
}
|
||||
|
||||
/**
|
||||
* @group legacy
|
||||
* @expectedDeprecation Firewall "some_firewall" is configured as "stateless" but the "switch_user.stateless" key is set to false. Both should have the same value, the firewall's "stateless" value will be used as default value for the "switch_user.stateless" key in 4.0.
|
||||
*/
|
||||
public function testSwitchUserNotStatelessOnStatelessFirewall()
|
||||
{
|
||||
$container = $this->getRawContainer();
|
||||
|
||||
$container->loadFromExtension('security', array(
|
||||
'providers' => array(
|
||||
'default' => array('id' => 'foo'),
|
||||
),
|
||||
|
||||
'firewalls' => array(
|
||||
'some_firewall' => array(
|
||||
'stateless' => true,
|
||||
'http_basic' => null,
|
||||
'switch_user' => array('stateless' => false),
|
||||
'logout_on_user_change' => true,
|
||||
),
|
||||
),
|
||||
));
|
||||
|
||||
$container->compile();
|
||||
}
|
||||
|
||||
/**
|
||||
* @group legacy
|
||||
* @expectedDeprecation Firewall "default" has no "provider" set but multiple providers exist. Using the first configured provider (first) is deprecated since 3.4 and will throw an exception in 4.0, set the "provider" key on the firewall instead.
|
||||
*/
|
||||
public function testDeprecationForAmbiguousProvider()
|
||||
{
|
||||
$container = $this->getRawContainer();
|
||||
|
||||
$container->loadFromExtension('security', array(
|
||||
'providers' => array(
|
||||
'first' => array('id' => 'foo'),
|
||||
'second' => array('id' => 'bar'),
|
||||
),
|
||||
|
||||
'firewalls' => array(
|
||||
'default' => array(
|
||||
'http_basic' => null,
|
||||
'logout_on_user_change' => true,
|
||||
),
|
||||
),
|
||||
));
|
||||
|
||||
$container->compile();
|
||||
}
|
||||
|
||||
protected function getRawContainer()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace Symfony\Bundle\SecurityBundle\Tests\Functional;
|
||||
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\Security\Http\Firewall\SwitchUserListener;
|
||||
|
||||
class SwitchUserTest extends WebTestCase
|
||||
@ -50,6 +51,18 @@ class SwitchUserTest extends WebTestCase
|
||||
$this->assertEquals('user_can_switch', $client->getProfile()->getCollector('security')->getUser());
|
||||
}
|
||||
|
||||
public function testSwitchUserStateless()
|
||||
{
|
||||
$client = $this->createClient(array('test_case' => 'JsonLogin', 'root_config' => 'switchuser_stateless.yml'));
|
||||
$client->request('POST', '/chk', array('_switch_user' => 'dunglas'), array(), array('CONTENT_TYPE' => 'application/json'), '{"user": {"login": "user_can_switch", "password": "test"}}');
|
||||
$response = $client->getResponse();
|
||||
|
||||
$this->assertInstanceOf(JsonResponse::class, $response);
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertSame(array('message' => 'Welcome @dunglas!'), json_decode($response->getContent(), true));
|
||||
$this->assertSame('dunglas', $client->getProfile()->getCollector('security')->getUser());
|
||||
}
|
||||
|
||||
public function getTestParameters()
|
||||
{
|
||||
return array(
|
||||
|
@ -0,0 +1,13 @@
|
||||
imports:
|
||||
- { resource: ./config.yml }
|
||||
|
||||
security:
|
||||
providers:
|
||||
in_memory:
|
||||
memory:
|
||||
users:
|
||||
user_can_switch: { password: test, roles: [ROLE_USER, ROLE_ALLOWED_TO_SWITCH] }
|
||||
firewalls:
|
||||
main:
|
||||
switch_user:
|
||||
stateless: true
|
@ -49,8 +49,9 @@ class SwitchUserListener implements ListenerInterface
|
||||
private $role;
|
||||
private $logger;
|
||||
private $dispatcher;
|
||||
private $stateless;
|
||||
|
||||
public function __construct(TokenStorageInterface $tokenStorage, UserProviderInterface $provider, UserCheckerInterface $userChecker, $providerKey, AccessDecisionManagerInterface $accessDecisionManager, LoggerInterface $logger = null, $usernameParameter = '_switch_user', $role = 'ROLE_ALLOWED_TO_SWITCH', EventDispatcherInterface $dispatcher = null)
|
||||
public function __construct(TokenStorageInterface $tokenStorage, UserProviderInterface $provider, UserCheckerInterface $userChecker, $providerKey, AccessDecisionManagerInterface $accessDecisionManager, LoggerInterface $logger = null, $usernameParameter = '_switch_user', $role = 'ROLE_ALLOWED_TO_SWITCH', EventDispatcherInterface $dispatcher = null, $stateless = false)
|
||||
{
|
||||
if (empty($providerKey)) {
|
||||
throw new \InvalidArgumentException('$providerKey must not be empty.');
|
||||
@ -65,6 +66,7 @@ class SwitchUserListener implements ListenerInterface
|
||||
$this->role = $role;
|
||||
$this->logger = $logger;
|
||||
$this->dispatcher = $dispatcher;
|
||||
$this->stateless = $stateless;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -92,13 +94,14 @@ class SwitchUserListener implements ListenerInterface
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->stateless) {
|
||||
$request->query->remove($this->usernameParameter);
|
||||
$request->server->set('QUERY_STRING', http_build_query($request->query->all()));
|
||||
|
||||
$response = new RedirectResponse($request->getUri(), 302);
|
||||
|
||||
$event->setResponse($response);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to switch to another user.
|
||||
|
@ -266,4 +266,29 @@ class SwitchUserListenerTest extends TestCase
|
||||
|
||||
$this->assertSame($replacedToken, $this->tokenStorage->getToken());
|
||||
}
|
||||
|
||||
public function testSwitchUserStateless()
|
||||
{
|
||||
$token = new UsernamePasswordToken('username', '', 'key', array('ROLE_FOO'));
|
||||
$user = new User('username', 'password', array());
|
||||
|
||||
$this->tokenStorage->setToken($token);
|
||||
$this->request->query->set('_switch_user', 'kuba');
|
||||
|
||||
$this->accessDecisionManager->expects($this->once())
|
||||
->method('decide')->with($token, array('ROLE_ALLOWED_TO_SWITCH'))
|
||||
->will($this->returnValue(true));
|
||||
|
||||
$this->userProvider->expects($this->once())
|
||||
->method('loadUserByUsername')->with('kuba')
|
||||
->will($this->returnValue($user));
|
||||
$this->userChecker->expects($this->once())
|
||||
->method('checkPostAuth')->with($user);
|
||||
|
||||
$listener = new SwitchUserListener($this->tokenStorage, $this->userProvider, $this->userChecker, 'provider123', $this->accessDecisionManager, null, '_switch_user', 'ROLE_ALLOWED_TO_SWITCH', null, true);
|
||||
$listener->handle($this->event);
|
||||
|
||||
$this->assertInstanceOf('Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken', $this->tokenStorage->getToken());
|
||||
$this->assertFalse($this->event->hasResponse());
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user