feature #10698 [Security] Added a REMOTE_USER based listener to security firewalls (Maxime Douailin)
This PR was squashed before being merged into the 2.6-dev branch (closes #10698).
Discussion
----------
[Security] Added a REMOTE_USER based listener to security firewalls
| Q | A
| ------------- | ---
| Bug fix? | no
| New feature? | yes
| BC breaks? | no
| Deprecations? | no
| Tests pass? | yes
| Fixed tickets | /
| License | MIT
| Doc PR | symfony/symfony-docs#3912
TODO
- [x] submit changes to the documentation
I've seen myself implementing a few times a REMOTE_USER based authentication listener, as a large part of security modules for Apache (Kerberos, CAS, and more) are providing the username via an environment variable.
So I thought this could benefit the whole community if directly included in the framework. It is very similar to the X509AuthenticationListener, and basing the RemoteUserAuthenticationListener on the AbstractPreAuthenticatedListener is relevant and very convenient.
Using the X509AuthenticationListener could be possible, but it is confusing to use it directly when your authentication is not certificate based.
Please let me know if I need to update anything.
Regards
Commits
-------
a2872f2
[Security] Added a REMOTE_USER based listener to security firewalls
This commit is contained in:
commit
0050b8d458
@ -0,0 +1,65 @@
|
||||
<?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\SecurityBundle\DependencyInjection\Security\Factory;
|
||||
|
||||
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
|
||||
|
||||
use Symfony\Component\DependencyInjection\DefinitionDecorator;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
/**
|
||||
* RemoteUserFactory creates services for REMOTE_USER based authentication.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Maxime Douailin <maxime.douailin@gmail.com>
|
||||
*/
|
||||
class RemoteUserFactory implements SecurityFactoryInterface
|
||||
{
|
||||
public function create(ContainerBuilder $container, $id, $config, $userProvider, $defaultEntryPoint)
|
||||
{
|
||||
$providerId = 'security.authentication.provider.pre_authenticated.'.$id;
|
||||
$container
|
||||
->setDefinition($providerId, new DefinitionDecorator('security.authentication.provider.pre_authenticated'))
|
||||
->replaceArgument(0, new Reference($userProvider))
|
||||
->addArgument($id)
|
||||
;
|
||||
|
||||
$listenerId = 'security.authentication.listener.remote_user.'.$id;
|
||||
$listener = $container->setDefinition($listenerId, new DefinitionDecorator('security.authentication.listener.remote_user'));
|
||||
$listener->replaceArgument(2, $id);
|
||||
$listener->replaceArgument(3, $config['user']);
|
||||
|
||||
return array($providerId, $listenerId, $defaultEntryPoint);
|
||||
}
|
||||
|
||||
public function getPosition()
|
||||
{
|
||||
return 'pre_auth';
|
||||
}
|
||||
|
||||
public function getKey()
|
||||
{
|
||||
return 'remote-user';
|
||||
}
|
||||
|
||||
public function addConfiguration(NodeDefinition $node)
|
||||
{
|
||||
$node
|
||||
->children()
|
||||
->scalarNode('provider')->end()
|
||||
->scalarNode('user')->defaultValue('REMOTE_USER')->end()
|
||||
->end()
|
||||
;
|
||||
}
|
||||
}
|
@ -24,6 +24,8 @@
|
||||
|
||||
<parameter key="security.authentication.listener.x509.class">Symfony\Component\Security\Http\Firewall\X509AuthenticationListener</parameter>
|
||||
|
||||
<parameter key="security.authentication.listener.remote_user.class">Symfony\Component\Security\Http\Firewall\RemoteUserAuthenticationListener</parameter>
|
||||
|
||||
<parameter key="security.authentication.listener.anonymous.class">Symfony\Component\Security\Http\Firewall\AnonymousAuthenticationListener</parameter>
|
||||
|
||||
<parameter key="security.authentication.switchuser_listener.class">Symfony\Component\Security\Http\Firewall\SwitchUserListener</parameter>
|
||||
@ -173,6 +175,16 @@
|
||||
<argument type="service" id="event_dispatcher" on-invalid="null"/>
|
||||
</service>
|
||||
|
||||
<service id="security.authentication.listener.remote_user" class="%security.authentication.listener.remote_user.class%" public="false" abstract="true">
|
||||
<tag name="monolog.logger" channel="security" />
|
||||
<argument type="service" id="security.context" />
|
||||
<argument type="service" id="security.authentication.manager" />
|
||||
<argument /> <!-- Provider-shared Key -->
|
||||
<argument /> <!-- REMOTE_USER server env var -->
|
||||
<argument type="service" id="logger" on-invalid="null" />
|
||||
<argument type="service" id="event_dispatcher" on-invalid="null"/>
|
||||
</service>
|
||||
|
||||
<service id="security.authentication.listener.basic" class="%security.authentication.listener.basic.class%" public="false" abstract="true">
|
||||
<tag name="monolog.logger" channel="security" />
|
||||
<argument type="service" id="security.context" />
|
||||
|
@ -19,6 +19,7 @@ use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\HttpBasic
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\HttpDigestFactory;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\RememberMeFactory;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\X509Factory;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\RemoteUserFactory;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SimplePreAuthenticationFactory;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SimpleFormFactory;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider\InMemoryFactory;
|
||||
@ -40,6 +41,7 @@ class SecurityBundle extends Bundle
|
||||
$extension->addSecurityListenerFactory(new HttpDigestFactory());
|
||||
$extension->addSecurityListenerFactory(new RememberMeFactory());
|
||||
$extension->addSecurityListenerFactory(new X509Factory());
|
||||
$extension->addSecurityListenerFactory(new RemoteUserFactory());
|
||||
$extension->addSecurityListenerFactory(new SimplePreAuthenticationFactory());
|
||||
$extension->addSecurityListenerFactory(new SimpleFormFactory());
|
||||
|
||||
|
@ -78,6 +78,7 @@ abstract class CompleteConfigurationTest extends \PHPUnit_Framework_TestCase
|
||||
'security.channel_listener',
|
||||
'security.logout_listener.secure',
|
||||
'security.authentication.listener.x509.secure',
|
||||
'security.authentication.listener.remote_user.secure',
|
||||
'security.authentication.listener.form.secure',
|
||||
'security.authentication.listener.basic.secure',
|
||||
'security.authentication.listener.digest.secure',
|
||||
|
@ -69,6 +69,7 @@ $container->loadFromExtension('security', array(
|
||||
'anonymous' => true,
|
||||
'switch_user' => true,
|
||||
'x509' => true,
|
||||
'remote_user' => true,
|
||||
'logout' => true,
|
||||
'remember_me' => array('key' => 'TheKey'),
|
||||
),
|
||||
|
@ -54,6 +54,7 @@
|
||||
<anonymous />
|
||||
<switch-user />
|
||||
<x509 />
|
||||
<remote-user />
|
||||
<logout />
|
||||
<remember-me key="TheyKey"/>
|
||||
</firewall>
|
||||
|
@ -52,6 +52,7 @@ security:
|
||||
anonymous: true
|
||||
switch_user: true
|
||||
x509: true
|
||||
remote_user: true
|
||||
logout: true
|
||||
remember_me:
|
||||
key: TheKey
|
||||
|
@ -0,0 +1,49 @@
|
||||
<?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\Component\Security\Http\Firewall;
|
||||
|
||||
use Symfony\Component\Security\Core\SecurityContextInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
|
||||
/**
|
||||
* REMOTE_USER authentication listener.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Maxime Douailin <maxime.douailin@gmail.com>
|
||||
*/
|
||||
class RemoteUserAuthenticationListener extends AbstractPreAuthenticatedListener
|
||||
{
|
||||
private $userKey;
|
||||
|
||||
public function __construct(SecurityContextInterface $securityContext, AuthenticationManagerInterface $authenticationManager, $providerKey, $userKey = 'REMOTE_USER', LoggerInterface $logger = null, EventDispatcherInterface $dispatcher = null)
|
||||
{
|
||||
parent::__construct($securityContext, $authenticationManager, $providerKey, $logger, $dispatcher);
|
||||
|
||||
$this->userKey = $userKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getPreAuthenticatedData(Request $request)
|
||||
{
|
||||
if (!$request->server->has($this->userKey)) {
|
||||
throw new BadCredentialsException(sprintf('User key was not found: %s', $this->userKey));
|
||||
}
|
||||
|
||||
return array($request->server->get($this->userKey), null);
|
||||
}
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
<?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\Component\Security\Http\Tests\Firewall;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Security\Http\Firewall\RemoteUserAuthenticationListener;
|
||||
|
||||
class RemoteUserAuthenticationListenerTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testGetPreAuthenticatedData()
|
||||
{
|
||||
$serverVars = array(
|
||||
'REMOTE_USER' => 'TheUser'
|
||||
);
|
||||
|
||||
$request = new Request(array(), array(), array(), array(), array(), $serverVars);
|
||||
|
||||
$context = $this->getMock('Symfony\Component\Security\Core\SecurityContextInterface');
|
||||
|
||||
$authenticationManager = $this->getMock('Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface');
|
||||
|
||||
$listener = new RemoteUserAuthenticationListener(
|
||||
$context,
|
||||
$authenticationManager,
|
||||
'TheProviderKey'
|
||||
);
|
||||
|
||||
$method = new \ReflectionMethod($listener, 'getPreAuthenticatedData');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$result = $method->invokeArgs($listener, array($request));
|
||||
$this->assertSame($result, array('TheUser', null));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Security\Core\Exception\BadCredentialsException
|
||||
*/
|
||||
public function testGetPreAuthenticatedDataNoUser()
|
||||
{
|
||||
$request = new Request(array(), array(), array(), array(), array(), array());
|
||||
|
||||
$context = $this->getMock('Symfony\Component\Security\Core\SecurityContextInterface');
|
||||
|
||||
$authenticationManager = $this->getMock('Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface');
|
||||
|
||||
$listener = new RemoteUserAuthenticationListener(
|
||||
$context,
|
||||
$authenticationManager,
|
||||
'TheProviderKey'
|
||||
);
|
||||
|
||||
$method = new \ReflectionMethod($listener, 'getPreAuthenticatedData');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$result = $method->invokeArgs($listener, array($request));
|
||||
}
|
||||
|
||||
public function testGetPreAuthenticatedDataWithDifferentKeys()
|
||||
{
|
||||
$userCredentials = array('TheUser', null);
|
||||
|
||||
$request = new Request(array(), array(), array(), array(), array(), array(
|
||||
'TheUserKey' => 'TheUser'
|
||||
));
|
||||
$context = $this->getMock('Symfony\Component\Security\Core\SecurityContextInterface');
|
||||
|
||||
$authenticationManager = $this->getMock('Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface');
|
||||
|
||||
$listener = new RemoteUserAuthenticationListener(
|
||||
$context,
|
||||
$authenticationManager,
|
||||
'TheProviderKey',
|
||||
'TheUserKey'
|
||||
);
|
||||
|
||||
$method = new \ReflectionMethod($listener, 'getPreAuthenticatedData');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$result = $method->invokeArgs($listener, array($request));
|
||||
$this->assertSame($result, $userCredentials);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user