Skip to content

Commit 650b906

Browse files
committed
Initial commit (but after some polished work) of the new Guard authentication system
1 parent a35afd0 commit 650b906

File tree

4 files changed

+335
-0
lines changed

4 files changed

+335
-0
lines changed
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
<?php
2+
3+
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory;
4+
5+
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
6+
use Symfony\Component\DependencyInjection\ContainerBuilder;
7+
use Symfony\Component\DependencyInjection\DefinitionDecorator;
8+
use Symfony\Component\DependencyInjection\Reference;
9+
10+
/**
11+
* Configures the "guard" authentication provider key under a firewall
12+
*
13+
* @author Ryan Weaver <[email protected]>
14+
*/
15+
class GuardAuthenticationFactory implements SecurityFactoryInterface
16+
{
17+
public function getPosition()
18+
{
19+
return 'pre_auth';
20+
}
21+
22+
public function getKey()
23+
{
24+
return 'guard';
25+
}
26+
27+
public function addConfiguration(NodeDefinition $node)
28+
{
29+
$node
30+
->fixXmlConfig('authenticator')
31+
->children()
32+
->scalarNode('provider')
33+
->info('A key from the "providers" section of your security config, in case your user provider is different than the firewall')
34+
->end()
35+
->scalarNode('entry_point')
36+
->info('A service id (of one of your authenticators) whose start() method should be called when an anonymous user hits a page that requires authentication')
37+
->defaultValue(null)
38+
->end()
39+
->arrayNode('authenticators')
40+
->info('An array of service ids for all of your "authenticators"')
41+
->requiresAtLeastOneElement()
42+
->prototype('scalar')->end()
43+
->end()
44+
->end()
45+
;
46+
}
47+
48+
public function create(ContainerBuilder $container, $id, $config, $userProvider, $defaultEntryPoint)
49+
{
50+
$authenticatorIds = $config['authenticators'];
51+
$authenticatorReferences = array();
52+
foreach ($authenticatorIds as $authenticatorId) {
53+
$authenticatorReferences[] = new Reference($authenticatorId);
54+
}
55+
56+
// configure the GuardAuthenticationFactory to have the dynamic constructor arguments
57+
$providerId = 'security.authentication.provider.guard.'.$id;
58+
$container
59+
->setDefinition($providerId, new DefinitionDecorator('security.authentication.provider.guard'))
60+
->replaceArgument(0, $authenticatorReferences)
61+
->replaceArgument(1, new Reference($userProvider))
62+
->replaceArgument(2, $id)
63+
;
64+
65+
// listener
66+
$listenerId = 'security.authentication.listener.guard.'.$id;
67+
$listener = $container->setDefinition($listenerId, new DefinitionDecorator('security.authentication.listener.guard'));
68+
$listener->replaceArgument(2, $id);
69+
$listener->replaceArgument(3, $authenticatorReferences);
70+
71+
// determine the entryPointId to use
72+
$entryPointId = $this->determineEntryPoint($defaultEntryPoint, $config);
73+
74+
// this is always injected - then the listener decides if it should be used
75+
$container
76+
->getDefinition($listenerId)
77+
->addTag('security.remember_me_aware', array('id' => $id, 'provider' => $userProvider));
78+
79+
return array($providerId, $listenerId, $entryPointId);
80+
}
81+
82+
private function determineEntryPoint($defaultEntryPointId, array $config)
83+
{
84+
if ($defaultEntryPointId) {
85+
// explode if they've configured the entry_point, but there is already one
86+
if ($config['entry_point']) {
87+
throw new \LogicException(sprintf(
88+
'The guard authentication provider cannot use the "%s" entry_point because another entry point is already configured by another provider! Either remove the other provider or move the entry_point configuration as a root key under your firewall',
89+
$config['entry_point']
90+
));
91+
}
92+
93+
return $defaultEntryPointId;
94+
}
95+
96+
if ($config['entry_point']) {
97+
// if it's configured explicitly, use it!
98+
return $config['entry_point'];
99+
}
100+
101+
$authenticatorIds = $config['authenticators'];
102+
if (count($authenticatorIds) == 1) {
103+
// if there is only one authenticator, use that as the entry point
104+
return array_shift($authenticatorIds);
105+
}
106+
107+
// we have multiple entry points - we must ask them to configure one
108+
throw new \LogicException(sprintf(
109+
'Because you have multiple guard configurators, you need to set the "guard.entry_point" key to one of you configurators (%s)',
110+
implode(', ', $authenticatorIds)
111+
));
112+
}
113+
}

DependencyInjection/SecurityExtension.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ public function load(array $configs, ContainerBuilder $container)
6565
$loader->load('templating_php.xml');
6666
$loader->load('templating_twig.xml');
6767
$loader->load('collectors.xml');
68+
$loader->load('guard.xml');
6869

6970
if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) {
7071
$container->removeDefinition('security.expression_language');

Resources/config/guard.xml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?xml version="1.0" ?>
2+
3+
<container xmlns="http://symfony.com/schema/dic/services"
4+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5+
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
6+
7+
<services>
8+
<service id="security.authentication.guard_handler"
9+
class="Symfony\Component\Security\Guard\GuardAuthenticatorHandler"
10+
>
11+
<argument type="service" id="security.token_storage" />
12+
<argument type="service" id="event_dispatcher" on-invalid="null" />
13+
</service>
14+
15+
<!-- See GuardAuthenticationFactory -->
16+
<service id="security.authentication.provider.guard"
17+
class="Symfony\Component\Security\Guard\Provider\GuardAuthenticationProvider"
18+
abstract="true"
19+
public="false"
20+
>
21+
<argument /> <!-- Simple Authenticator -->
22+
<argument /> <!-- User Provider -->
23+
<argument /> <!-- Provider-shared Key -->
24+
<argument type="service" id="security.user_checker" />
25+
</service>
26+
27+
<service id="security.authentication.listener.guard"
28+
class="Symfony\Component\Security\Guard\Firewall\GuardAuthenticationListener"
29+
public="false"
30+
abstract="true"
31+
>
32+
<tag name="monolog.logger" channel="security" />
33+
<argument type="service" id="security.authentication.guard_handler" />
34+
<argument type="service" id="security.authentication.manager" />
35+
<argument /> <!-- Provider-shared Key -->
36+
<argument /> <!-- Authenticator -->
37+
<argument type="service" id="logger" on-invalid="null" />
38+
</service>
39+
</services>
40+
</container>
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bundle\SecurityBundle\Tests\DependencyInjection\Security\Factory;
13+
14+
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\GuardAuthenticationFactory;
15+
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
16+
use Symfony\Component\DependencyInjection\ContainerBuilder;
17+
use Symfony\Component\DependencyInjection\Reference;
18+
19+
class GuardAuthenticationFactoryTest extends \PHPUnit_Framework_TestCase
20+
{
21+
/**
22+
* @dataProvider getValidConfigurationTests
23+
*/
24+
public function testAddValidConfiguration(array $inputConfig, array $expectedConfig)
25+
{
26+
$factory = new GuardAuthenticationFactory();
27+
$nodeDefinition = new ArrayNodeDefinition('guard');
28+
$factory->addConfiguration($nodeDefinition);
29+
30+
$node = $nodeDefinition->getNode();
31+
$normalizedConfig = $node->normalize($inputConfig);
32+
$finalizedConfig = $node->finalize($normalizedConfig);
33+
34+
$this->assertEquals($expectedConfig, $finalizedConfig);
35+
}
36+
37+
/**
38+
* @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
39+
* @dataProvider getInvalidConfigurationTests
40+
*/
41+
public function testAddInvalidConfiguration(array $inputConfig)
42+
{
43+
$factory = new GuardAuthenticationFactory();
44+
$nodeDefinition = new ArrayNodeDefinition('guard');
45+
$factory->addConfiguration($nodeDefinition);
46+
47+
$node = $nodeDefinition->getNode();
48+
$normalizedConfig = $node->normalize($inputConfig);
49+
// will validate and throw an exception on invalid
50+
$node->finalize($normalizedConfig);
51+
}
52+
53+
public function getValidConfigurationTests()
54+
{
55+
$tests = array();
56+
57+
// completely basic
58+
$tests[] = array(
59+
array(
60+
'authenticators' => array('authenticator1', 'authenticator2'),
61+
'provider' => 'some_provider',
62+
'entry_point' => 'the_entry_point'
63+
),
64+
array(
65+
'authenticators' => array('authenticator1', 'authenticator2'),
66+
'provider' => 'some_provider',
67+
'entry_point' => 'the_entry_point'
68+
)
69+
);
70+
71+
// testing xml config fix: authenticator -> authenticators
72+
$tests[] = array(
73+
array(
74+
'authenticator' => array('authenticator1', 'authenticator2'),
75+
),
76+
array(
77+
'authenticators' => array('authenticator1', 'authenticator2'),
78+
'entry_point' => null,
79+
)
80+
);
81+
82+
return $tests;
83+
}
84+
85+
public function getInvalidConfigurationTests()
86+
{
87+
$tests = array();
88+
89+
// testing not empty
90+
$tests[] = array(
91+
array('authenticators' => array())
92+
);
93+
94+
return $tests;
95+
}
96+
97+
public function testBasicCreate()
98+
{
99+
// simple configuration
100+
$config = array(
101+
'authenticators' => array('authenticator123'),
102+
'entry_point' => null,
103+
);
104+
list($container, $entryPointId) = $this->executeCreate($config, null);
105+
$this->assertEquals('authenticator123', $entryPointId);
106+
107+
$providerDefinition = $container->getDefinition('security.authentication.provider.guard.my_firewall');
108+
$this->assertEquals(array(
109+
'index_0' => array(new Reference('authenticator123')),
110+
'index_1' => new Reference('my_user_provider'),
111+
'index_2' => 'my_firewall'
112+
), $providerDefinition->getArguments());
113+
114+
$listenerDefinition = $container->getDefinition('security.authentication.listener.guard.my_firewall');
115+
$this->assertEquals('my_firewall', $listenerDefinition->getArgument(2));
116+
$this->assertEquals(array(new Reference('authenticator123')), $listenerDefinition->getArgument(3));
117+
}
118+
119+
public function testExistingDefaultEntryPointUsed()
120+
{
121+
// any existing default entry point is used
122+
$config = array(
123+
'authenticators' => array('authenticator123'),
124+
'entry_point' => null,
125+
);
126+
list($container, $entryPointId) = $this->executeCreate($config, 'some_default_entry_point');
127+
$this->assertEquals('some_default_entry_point', $entryPointId);
128+
}
129+
130+
/**
131+
* @expectedException \LogicException
132+
*/
133+
public function testCannotOverrideDefaultEntryPoint()
134+
{
135+
// any existing default entry point is used
136+
$config = array(
137+
'authenticators' => array('authenticator123'),
138+
'entry_point' => 'authenticator123',
139+
);
140+
$this->executeCreate($config, 'some_default_entry_point');
141+
}
142+
143+
/**
144+
* @expectedException \LogicException
145+
*/
146+
public function testMultipleAuthenticatorsRequiresEntryPoint()
147+
{
148+
// any existing default entry point is used
149+
$config = array(
150+
'authenticators' => array('authenticator123', 'authenticatorABC'),
151+
'entry_point' => null,
152+
);
153+
$this->executeCreate($config, null);
154+
}
155+
156+
157+
public function testCreateWithEntryPoint()
158+
{
159+
// any existing default entry point is used
160+
$config = array(
161+
'authenticators' => array('authenticator123', 'authenticatorABC'),
162+
'entry_point' => 'authenticatorABC',
163+
);
164+
list($container, $entryPointId) = $this->executeCreate($config, null);
165+
$this->assertEquals('authenticatorABC', $entryPointId);
166+
}
167+
168+
private function executeCreate(array $config, $defaultEntryPointId)
169+
{
170+
$container = new ContainerBuilder();
171+
$container->register('security.authentication.provider.guard');
172+
$container->register('security.authentication.listener.guard');
173+
$id = 'my_firewall';
174+
$userProviderId = 'my_user_provider';
175+
176+
$factory = new GuardAuthenticationFactory();
177+
list($providerId, $listenerId, $entryPointId) = $factory->create($container, $id, $config, $userProviderId, $defaultEntryPointId);
178+
179+
return array($container, $entryPointId);
180+
}
181+
}

0 commit comments

Comments
 (0)