Merge branch '2.8'
* 2.8: (29 commits) Updating AbstractVoter so that the method receives the TokenInterface Adding the necessary files so that Guard can be its own installable component Fix syntax in a test Normalize the way we check versions Avoid errors when generating the logout URL when there is no firewall key Removing unnecessary override fabbot Adding a new exception and throwing it when the User changes Fixing a bug where having an authentication failure would log you out. Tweaks thanks to Wouter Adding logging on this step and switching the order - not for any huge reason Adding a base class to assist with form login authentication Allowing for other authenticators to be checked meaningless author and license changes Adding missing factory registration Thanks again fabbot! A few more changes thanks to @iltar Splitting the getting of the user and checking credentials into two steps Tweaking docblock on interface thanks to @iltar Adding periods at the end of exceptions, and changing one class name to LogicException thanks to @iltar ... Conflicts: UPGRADE-2.8.md src/Symfony/Bridge/Twig/Tests/Node/DumpNodeTest.php src/Symfony/Bundle/FrameworkBundle/Command/ServerCommand.php src/Symfony/Component/Validator/Tests/Constraints/AbstractComparisonValidatorTestCase.php src/Symfony/Component/Validator/Tests/Constraints/IdenticalToValidatorTest.php src/Symfony/Component/Validator/Tests/Constraints/RangeValidatorTest.php
This commit is contained in:
commit
9db5d017d8
@ -17,6 +17,7 @@ use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
|
||||
use Symfony\Component\Security\Core\Role\RoleInterface;
|
||||
use Symfony\Component\Security\Http\Logout\LogoutUrlGenerator;
|
||||
|
||||
/**
|
||||
* SecurityDataCollector.
|
||||
@ -27,17 +28,20 @@ class SecurityDataCollector extends DataCollector
|
||||
{
|
||||
private $tokenStorage;
|
||||
private $roleHierarchy;
|
||||
private $logoutUrlGenerator;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param TokenStorageInterface|null $tokenStorage
|
||||
* @param RoleHierarchyInterface|null $roleHierarchy
|
||||
* @param LogoutUrlGenerator|null $logoutUrlGenerator
|
||||
*/
|
||||
public function __construct(TokenStorageInterface $tokenStorage = null, RoleHierarchyInterface $roleHierarchy = null)
|
||||
public function __construct(TokenStorageInterface $tokenStorage = null, RoleHierarchyInterface $roleHierarchy = null, LogoutUrlGenerator $logoutUrlGenerator = null)
|
||||
{
|
||||
$this->tokenStorage = $tokenStorage;
|
||||
$this->roleHierarchy = $roleHierarchy;
|
||||
$this->logoutUrlGenerator = $logoutUrlGenerator;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -50,7 +54,7 @@ class SecurityDataCollector extends DataCollector
|
||||
'enabled' => false,
|
||||
'authenticated' => false,
|
||||
'token_class' => null,
|
||||
'provider_key' => null,
|
||||
'logout_url' => null,
|
||||
'user' => '',
|
||||
'roles' => array(),
|
||||
'inherited_roles' => array(),
|
||||
@ -61,7 +65,7 @@ class SecurityDataCollector extends DataCollector
|
||||
'enabled' => true,
|
||||
'authenticated' => false,
|
||||
'token_class' => null,
|
||||
'provider_key' => null,
|
||||
'logout_url' => null,
|
||||
'user' => '',
|
||||
'roles' => array(),
|
||||
'inherited_roles' => array(),
|
||||
@ -70,6 +74,7 @@ class SecurityDataCollector extends DataCollector
|
||||
} else {
|
||||
$inheritedRoles = array();
|
||||
$assignedRoles = $token->getRoles();
|
||||
|
||||
if (null !== $this->roleHierarchy) {
|
||||
$allRoles = $this->roleHierarchy->getReachableRoles($assignedRoles);
|
||||
foreach ($allRoles as $role) {
|
||||
@ -78,11 +83,21 @@ class SecurityDataCollector extends DataCollector
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$logoutUrl = null;
|
||||
try {
|
||||
if (null !== $this->logoutUrlGenerator) {
|
||||
$logoutUrl = $this->logoutUrlGenerator->getLogoutPath();
|
||||
}
|
||||
} catch(\Exception $e) {
|
||||
// fail silently when the logout URL cannot be generated
|
||||
}
|
||||
|
||||
$this->data = array(
|
||||
'enabled' => true,
|
||||
'authenticated' => $token->isAuthenticated(),
|
||||
'token_class' => get_class($token),
|
||||
'provider_key' => method_exists($token, 'getProviderKey') ? $token->getProviderKey() : null,
|
||||
'logout_url' => $logoutUrl,
|
||||
'user' => $token->getUsername(),
|
||||
'roles' => array_map(function (RoleInterface $role) { return $role->getRole();}, $assignedRoles),
|
||||
'inherited_roles' => array_map(function (RoleInterface $role) { return $role->getRole(); }, $inheritedRoles),
|
||||
@ -167,9 +182,9 @@ class SecurityDataCollector extends DataCollector
|
||||
*
|
||||
* @return string The provider key
|
||||
*/
|
||||
public function getProviderKey()
|
||||
public function getLogoutUrl()
|
||||
{
|
||||
return $this->data['provider_key'];
|
||||
return $this->data['logout_url'];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,122 @@
|
||||
<?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\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\DefinitionDecorator;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
/**
|
||||
* Configures the "guard" authentication provider key under a firewall.
|
||||
*
|
||||
* @author Ryan Weaver <ryan@knpuniversity.com>
|
||||
*/
|
||||
class GuardAuthenticationFactory implements SecurityFactoryInterface
|
||||
{
|
||||
public function getPosition()
|
||||
{
|
||||
return 'pre_auth';
|
||||
}
|
||||
|
||||
public function getKey()
|
||||
{
|
||||
return 'guard';
|
||||
}
|
||||
|
||||
public function addConfiguration(NodeDefinition $node)
|
||||
{
|
||||
$node
|
||||
->fixXmlConfig('authenticator')
|
||||
->children()
|
||||
->scalarNode('provider')
|
||||
->info('A key from the "providers" section of your security config, in case your user provider is different than the firewall')
|
||||
->end()
|
||||
->scalarNode('entry_point')
|
||||
->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')
|
||||
->defaultValue(null)
|
||||
->end()
|
||||
->arrayNode('authenticators')
|
||||
->info('An array of service ids for all of your "authenticators"')
|
||||
->requiresAtLeastOneElement()
|
||||
->prototype('scalar')->end()
|
||||
->end()
|
||||
->end()
|
||||
;
|
||||
}
|
||||
|
||||
public function create(ContainerBuilder $container, $id, $config, $userProvider, $defaultEntryPoint)
|
||||
{
|
||||
$authenticatorIds = $config['authenticators'];
|
||||
$authenticatorReferences = array();
|
||||
foreach ($authenticatorIds as $authenticatorId) {
|
||||
$authenticatorReferences[] = new Reference($authenticatorId);
|
||||
}
|
||||
|
||||
// configure the GuardAuthenticationFactory to have the dynamic constructor arguments
|
||||
$providerId = 'security.authentication.provider.guard.'.$id;
|
||||
$container
|
||||
->setDefinition($providerId, new DefinitionDecorator('security.authentication.provider.guard'))
|
||||
->replaceArgument(0, $authenticatorReferences)
|
||||
->replaceArgument(1, new Reference($userProvider))
|
||||
->replaceArgument(2, $id)
|
||||
;
|
||||
|
||||
// listener
|
||||
$listenerId = 'security.authentication.listener.guard.'.$id;
|
||||
$listener = $container->setDefinition($listenerId, new DefinitionDecorator('security.authentication.listener.guard'));
|
||||
$listener->replaceArgument(2, $id);
|
||||
$listener->replaceArgument(3, $authenticatorReferences);
|
||||
|
||||
// determine the entryPointId to use
|
||||
$entryPointId = $this->determineEntryPoint($defaultEntryPoint, $config);
|
||||
|
||||
// this is always injected - then the listener decides if it should be used
|
||||
$container
|
||||
->getDefinition($listenerId)
|
||||
->addTag('security.remember_me_aware', array('id' => $id, 'provider' => $userProvider));
|
||||
|
||||
return array($providerId, $listenerId, $entryPointId);
|
||||
}
|
||||
|
||||
private function determineEntryPoint($defaultEntryPointId, array $config)
|
||||
{
|
||||
if ($defaultEntryPointId) {
|
||||
// explode if they've configured the entry_point, but there is already one
|
||||
if ($config['entry_point']) {
|
||||
throw new \LogicException(sprintf(
|
||||
'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',
|
||||
$config['entry_point']
|
||||
));
|
||||
}
|
||||
|
||||
return $defaultEntryPointId;
|
||||
}
|
||||
|
||||
if ($config['entry_point']) {
|
||||
// if it's configured explicitly, use it!
|
||||
return $config['entry_point'];
|
||||
}
|
||||
|
||||
$authenticatorIds = $config['authenticators'];
|
||||
if (count($authenticatorIds) == 1) {
|
||||
// if there is only one authenticator, use that as the entry point
|
||||
return array_shift($authenticatorIds);
|
||||
}
|
||||
|
||||
// we have multiple entry points - we must ask them to configure one
|
||||
throw new \LogicException(sprintf(
|
||||
'Because you have multiple guard configurators, you need to set the "guard.entry_point" key to one of you configurators (%s)',
|
||||
implode(', ', $authenticatorIds)
|
||||
));
|
||||
}
|
||||
}
|
@ -65,6 +65,7 @@ class SecurityExtension extends Extension
|
||||
$loader->load('templating_php.xml');
|
||||
$loader->load('templating_twig.xml');
|
||||
$loader->load('collectors.xml');
|
||||
$loader->load('guard.xml');
|
||||
|
||||
if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) {
|
||||
$container->removeDefinition('security.expression_language');
|
||||
|
@ -9,6 +9,7 @@
|
||||
<tag name="data_collector" template="@Security/Collector/security.html.twig" id="security" />
|
||||
<argument type="service" id="security.token_storage" on-invalid="ignore" />
|
||||
<argument type="service" id="security.role_hierarchy" />
|
||||
<argument type="service" id="security.logout_url_generator" />
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
||||
|
40
src/Symfony/Bundle/SecurityBundle/Resources/config/guard.xml
Normal file
40
src/Symfony/Bundle/SecurityBundle/Resources/config/guard.xml
Normal file
@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" ?>
|
||||
|
||||
<container xmlns="http://symfony.com/schema/dic/services"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
|
||||
|
||||
<services>
|
||||
<service id="security.authentication.guard_handler"
|
||||
class="Symfony\Component\Security\Guard\GuardAuthenticatorHandler"
|
||||
>
|
||||
<argument type="service" id="security.token_storage" />
|
||||
<argument type="service" id="event_dispatcher" on-invalid="null" />
|
||||
</service>
|
||||
|
||||
<!-- See GuardAuthenticationFactory -->
|
||||
<service id="security.authentication.provider.guard"
|
||||
class="Symfony\Component\Security\Guard\Provider\GuardAuthenticationProvider"
|
||||
abstract="true"
|
||||
public="false"
|
||||
>
|
||||
<argument /> <!-- Simple Authenticator -->
|
||||
<argument /> <!-- User Provider -->
|
||||
<argument /> <!-- Provider-shared Key -->
|
||||
<argument type="service" id="security.user_checker" />
|
||||
</service>
|
||||
|
||||
<service id="security.authentication.listener.guard"
|
||||
class="Symfony\Component\Security\Guard\Firewall\GuardAuthenticationListener"
|
||||
public="false"
|
||||
abstract="true"
|
||||
>
|
||||
<tag name="monolog.logger" channel="security" />
|
||||
<argument type="service" id="security.authentication.guard_handler" />
|
||||
<argument type="service" id="security.authentication.manager" />
|
||||
<argument /> <!-- Provider-shared Key -->
|
||||
<argument /> <!-- Authenticator -->
|
||||
<argument type="service" id="logger" on-invalid="null" />
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
@ -33,10 +33,10 @@
|
||||
<span>{{ collector.tokenClass|abbr_class }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if collector.providerKey %}
|
||||
{% if collector.logoutUrl %}
|
||||
<div class="sf-toolbar-info-piece">
|
||||
<b>Actions</b>
|
||||
<span><a href="{{ logout_path(collector.providerKey) }}">Logout</a></span>
|
||||
<span><a href="{{ collector.logoutUrl }}">Logout</a></span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% elseif collector.enabled %}
|
||||
|
@ -23,6 +23,7 @@ use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\RemoteUse
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SimplePreAuthenticationFactory;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SimpleFormFactory;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider\InMemoryFactory;
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\GuardAuthenticationFactory;
|
||||
|
||||
/**
|
||||
* Bundle.
|
||||
@ -44,6 +45,7 @@ class SecurityBundle extends Bundle
|
||||
$extension->addSecurityListenerFactory(new RemoteUserFactory());
|
||||
$extension->addSecurityListenerFactory(new SimplePreAuthenticationFactory());
|
||||
$extension->addSecurityListenerFactory(new SimpleFormFactory());
|
||||
$extension->addSecurityListenerFactory(new GuardAuthenticationFactory());
|
||||
|
||||
$extension->addUserProviderFactory(new InMemoryFactory());
|
||||
$container->addCompilerPass(new AddSecurityVotersPass());
|
||||
|
@ -0,0 +1,180 @@
|
||||
<?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\Tests\DependencyInjection\Security\Factory;
|
||||
|
||||
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\GuardAuthenticationFactory;
|
||||
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
class GuardAuthenticationFactoryTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider getValidConfigurationTests
|
||||
*/
|
||||
public function testAddValidConfiguration(array $inputConfig, array $expectedConfig)
|
||||
{
|
||||
$factory = new GuardAuthenticationFactory();
|
||||
$nodeDefinition = new ArrayNodeDefinition('guard');
|
||||
$factory->addConfiguration($nodeDefinition);
|
||||
|
||||
$node = $nodeDefinition->getNode();
|
||||
$normalizedConfig = $node->normalize($inputConfig);
|
||||
$finalizedConfig = $node->finalize($normalizedConfig);
|
||||
|
||||
$this->assertEquals($expectedConfig, $finalizedConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Config\Definition\Exception\InvalidConfigurationException
|
||||
* @dataProvider getInvalidConfigurationTests
|
||||
*/
|
||||
public function testAddInvalidConfiguration(array $inputConfig)
|
||||
{
|
||||
$factory = new GuardAuthenticationFactory();
|
||||
$nodeDefinition = new ArrayNodeDefinition('guard');
|
||||
$factory->addConfiguration($nodeDefinition);
|
||||
|
||||
$node = $nodeDefinition->getNode();
|
||||
$normalizedConfig = $node->normalize($inputConfig);
|
||||
// will validate and throw an exception on invalid
|
||||
$node->finalize($normalizedConfig);
|
||||
}
|
||||
|
||||
public function getValidConfigurationTests()
|
||||
{
|
||||
$tests = array();
|
||||
|
||||
// completely basic
|
||||
$tests[] = array(
|
||||
array(
|
||||
'authenticators' => array('authenticator1', 'authenticator2'),
|
||||
'provider' => 'some_provider',
|
||||
'entry_point' => 'the_entry_point',
|
||||
),
|
||||
array(
|
||||
'authenticators' => array('authenticator1', 'authenticator2'),
|
||||
'provider' => 'some_provider',
|
||||
'entry_point' => 'the_entry_point',
|
||||
),
|
||||
);
|
||||
|
||||
// testing xml config fix: authenticator -> authenticators
|
||||
$tests[] = array(
|
||||
array(
|
||||
'authenticator' => array('authenticator1', 'authenticator2'),
|
||||
),
|
||||
array(
|
||||
'authenticators' => array('authenticator1', 'authenticator2'),
|
||||
'entry_point' => null,
|
||||
),
|
||||
);
|
||||
|
||||
return $tests;
|
||||
}
|
||||
|
||||
public function getInvalidConfigurationTests()
|
||||
{
|
||||
$tests = array();
|
||||
|
||||
// testing not empty
|
||||
$tests[] = array(
|
||||
array('authenticators' => array()),
|
||||
);
|
||||
|
||||
return $tests;
|
||||
}
|
||||
|
||||
public function testBasicCreate()
|
||||
{
|
||||
// simple configuration
|
||||
$config = array(
|
||||
'authenticators' => array('authenticator123'),
|
||||
'entry_point' => null,
|
||||
);
|
||||
list($container, $entryPointId) = $this->executeCreate($config, null);
|
||||
$this->assertEquals('authenticator123', $entryPointId);
|
||||
|
||||
$providerDefinition = $container->getDefinition('security.authentication.provider.guard.my_firewall');
|
||||
$this->assertEquals(array(
|
||||
'index_0' => array(new Reference('authenticator123')),
|
||||
'index_1' => new Reference('my_user_provider'),
|
||||
'index_2' => 'my_firewall',
|
||||
), $providerDefinition->getArguments());
|
||||
|
||||
$listenerDefinition = $container->getDefinition('security.authentication.listener.guard.my_firewall');
|
||||
$this->assertEquals('my_firewall', $listenerDefinition->getArgument(2));
|
||||
$this->assertEquals(array(new Reference('authenticator123')), $listenerDefinition->getArgument(3));
|
||||
}
|
||||
|
||||
public function testExistingDefaultEntryPointUsed()
|
||||
{
|
||||
// any existing default entry point is used
|
||||
$config = array(
|
||||
'authenticators' => array('authenticator123'),
|
||||
'entry_point' => null,
|
||||
);
|
||||
list($container, $entryPointId) = $this->executeCreate($config, 'some_default_entry_point');
|
||||
$this->assertEquals('some_default_entry_point', $entryPointId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \LogicException
|
||||
*/
|
||||
public function testCannotOverrideDefaultEntryPoint()
|
||||
{
|
||||
// any existing default entry point is used
|
||||
$config = array(
|
||||
'authenticators' => array('authenticator123'),
|
||||
'entry_point' => 'authenticator123',
|
||||
);
|
||||
$this->executeCreate($config, 'some_default_entry_point');
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \LogicException
|
||||
*/
|
||||
public function testMultipleAuthenticatorsRequiresEntryPoint()
|
||||
{
|
||||
// any existing default entry point is used
|
||||
$config = array(
|
||||
'authenticators' => array('authenticator123', 'authenticatorABC'),
|
||||
'entry_point' => null,
|
||||
);
|
||||
$this->executeCreate($config, null);
|
||||
}
|
||||
|
||||
public function testCreateWithEntryPoint()
|
||||
{
|
||||
// any existing default entry point is used
|
||||
$config = array(
|
||||
'authenticators' => array('authenticator123', 'authenticatorABC'),
|
||||
'entry_point' => 'authenticatorABC',
|
||||
);
|
||||
list($container, $entryPointId) = $this->executeCreate($config, null);
|
||||
$this->assertEquals('authenticatorABC', $entryPointId);
|
||||
}
|
||||
|
||||
private function executeCreate(array $config, $defaultEntryPointId)
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
$container->register('security.authentication.provider.guard');
|
||||
$container->register('security.authentication.listener.guard');
|
||||
$id = 'my_firewall';
|
||||
$userProviderId = 'my_user_provider';
|
||||
|
||||
$factory = new GuardAuthenticationFactory();
|
||||
list($providerId, $listenerId, $entryPointId) = $factory->create($container, $id, $config, $userProviderId, $defaultEntryPointId);
|
||||
|
||||
return array($container, $entryPointId);
|
||||
}
|
||||
}
|
@ -76,9 +76,11 @@ class FormFactory implements FormFactoryInterface
|
||||
} elseif ($type instanceof FormTypeInterface) {
|
||||
// BC
|
||||
$typeName = $type->getName();
|
||||
} else {
|
||||
} elseif (is_string($type)) {
|
||||
// BC
|
||||
$typeName = $type;
|
||||
} else {
|
||||
throw new UnexpectedTypeException($type, 'string, Symfony\Component\Form\ResolvedFormTypeInterface or Symfony\Component\Form\FormTypeInterface');
|
||||
}
|
||||
|
||||
if (null === $name) {
|
||||
|
@ -160,6 +160,15 @@ class FormFactoryTest extends \PHPUnit_Framework_TestCase
|
||||
$this->factory->createNamedBuilder('name', new \stdClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Form\Exception\UnexpectedTypeException
|
||||
* @expectedExceptionMessage Expected argument of type "string, Symfony\Component\Form\ResolvedFormTypeInterface or Symfony\Component\Form\FormTypeInterface", "stdClass" given
|
||||
*/
|
||||
public function testCreateThrowsUnderstandableException()
|
||||
{
|
||||
$this->factory->create(new \stdClass());
|
||||
}
|
||||
|
||||
public function testCreateUsesTypeNameIfTypeGivenAsString()
|
||||
{
|
||||
$options = array('a' => '1', 'b' => '2');
|
||||
|
@ -65,6 +65,12 @@ abstract class AbstractVoter implements VoterInterface
|
||||
// abstain vote by default in case none of the attributes are supported
|
||||
$vote = self::ACCESS_ABSTAIN;
|
||||
|
||||
$reflector = new \ReflectionMethod($this, 'voteOnAttribute');
|
||||
$isNewOverwritten = $reflector->getDeclaringClass()->getName() !== 'Symfony\Component\Security\Core\Authorization\Voter\AbstractVoter';
|
||||
if (!$isNewOverwritten) {
|
||||
@trigger_error(sprintf("The AbstractVoter::isGranted method is deprecated since 2.8 and won't be called anymore in 3.0. Override voteOnAttribute() instead.", $reflector->class), E_USER_DEPRECATED);
|
||||
}
|
||||
|
||||
foreach ($attributes as $attribute) {
|
||||
if (!$this->supportsAttribute($attribute)) {
|
||||
continue;
|
||||
@ -73,9 +79,16 @@ abstract class AbstractVoter implements VoterInterface
|
||||
// as soon as at least one attribute is supported, default is to deny access
|
||||
$vote = self::ACCESS_DENIED;
|
||||
|
||||
if ($this->isGranted($attribute, $object, $token->getUser())) {
|
||||
// grant access as soon as at least one voter returns a positive response
|
||||
return self::ACCESS_GRANTED;
|
||||
if ($isNewOverwritten) {
|
||||
if ($this->voteOnAttribute($attribute, $object, $token)) {
|
||||
// grant access as soon as at least one voter returns a positive response
|
||||
return self::ACCESS_GRANTED;
|
||||
}
|
||||
} else {
|
||||
if ($this->isGranted($attribute, $object, $token->getUser())) {
|
||||
// grant access as soon as at least one voter returns a positive response
|
||||
return self::ACCESS_GRANTED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -107,7 +120,32 @@ abstract class AbstractVoter implements VoterInterface
|
||||
* @param object $object
|
||||
* @param UserInterface|string $user
|
||||
*
|
||||
* @deprecated This method will be removed in 3.0 - override voteOnAttribute instead.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
abstract protected function isGranted($attribute, $object, $user = null);
|
||||
protected function isGranted($attribute, $object, $user = null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a single access check operation on a given attribute, object and (optionally) user
|
||||
* It is safe to assume that $attribute and $object's class pass supportsAttribute/supportsClass
|
||||
* $user can be one of the following:
|
||||
* a UserInterface object (fully authenticated user)
|
||||
* a string (anonymously authenticated user).
|
||||
*
|
||||
* This method will become abstract in 3.0.
|
||||
*
|
||||
* @param string $attribute
|
||||
* @param object $object
|
||||
* @param TokenInterface $token
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function voteOnAttribute($attribute, $object, TokenInterface $token)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,31 @@
|
||||
<?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\Core\Exception;
|
||||
|
||||
/**
|
||||
* AuthenticationServiceException is thrown when an authenticated token becomes un-authentcated between requests.
|
||||
*
|
||||
* In practice, this is due to the User changing between requests (e.g. password changes),
|
||||
* causes the token to become un-authenticated.
|
||||
*
|
||||
* @author Ryan Weaver <ryan@knpuniversity.com>
|
||||
*/
|
||||
class AuthenticationExpiredException extends AccountStatusException
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getMessageKey()
|
||||
{
|
||||
return 'Authentication expired because your account information has changed.';
|
||||
}
|
||||
}
|
3
src/Symfony/Component/Security/Guard/.gitignore
vendored
Normal file
3
src/Symfony/Component/Security/Guard/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
vendor/
|
||||
composer.lock
|
||||
phpunit.xml
|
@ -0,0 +1,41 @@
|
||||
<?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\Guard;
|
||||
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Security\Guard\Token\PostAuthenticationGuardToken;
|
||||
|
||||
/**
|
||||
* An optional base class that creates a PostAuthenticationGuardToken for you.
|
||||
*
|
||||
* @author Ryan Weaver <ryan@knpuniversity.com>
|
||||
*/
|
||||
abstract class AbstractGuardAuthenticator implements GuardAuthenticatorInterface
|
||||
{
|
||||
/**
|
||||
* Shortcut to create a PostAuthenticationGuardToken for you, if you don't really
|
||||
* care about which authenticated token you're using.
|
||||
*
|
||||
* @param UserInterface $user
|
||||
* @param string $providerKey
|
||||
*
|
||||
* @return PostAuthenticationGuardToken
|
||||
*/
|
||||
public function createAuthenticatedToken(UserInterface $user, $providerKey)
|
||||
{
|
||||
return new PostAuthenticationGuardToken(
|
||||
$user,
|
||||
$providerKey,
|
||||
$user->getRoles()
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
<?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\Guard\Authenticator;
|
||||
|
||||
use Symfony\Component\Security\Guard\AbstractGuardAuthenticator;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
|
||||
/**
|
||||
* A base class to make form login authentication easier!
|
||||
*
|
||||
* @author Ryan Weaver <ryan@knpuniversity.com>
|
||||
*/
|
||||
abstract class AbstractFormLoginAuthenticator extends AbstractGuardAuthenticator
|
||||
{
|
||||
/**
|
||||
* Return the URL to the login page.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function getLoginUrl();
|
||||
|
||||
/**
|
||||
* The user will be redirected to the secure page they originally tried
|
||||
* to access. But if no such page exists (i.e. the user went to the
|
||||
* login page directly), this returns the URL the user should be redirected
|
||||
* to after logging in successfully (e.g. your homepage).
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function getDefaultSuccessRedirectUrl();
|
||||
|
||||
/**
|
||||
* Override to change what happens after a bad username/password is submitted.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param AuthenticationException $exception
|
||||
*
|
||||
* @return RedirectResponse
|
||||
*/
|
||||
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
|
||||
{
|
||||
$request->getSession()->set(Security::AUTHENTICATION_ERROR, $exception);
|
||||
$url = $this->getLoginUrl();
|
||||
|
||||
return new RedirectResponse($url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override to change what happens after successful authentication.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param TokenInterface $token
|
||||
* @param string $providerKey
|
||||
*
|
||||
* @return RedirectResponse
|
||||
*/
|
||||
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
|
||||
{
|
||||
// if the user hit a secure page and start() was called, this was
|
||||
// the URL they were on, and probably where you want to redirect to
|
||||
$targetPath = $request->getSession()->get('_security.'.$providerKey.'.target_path');
|
||||
|
||||
if (!$targetPath) {
|
||||
$targetPath = $this->getDefaultSuccessRedirectUrl();
|
||||
}
|
||||
|
||||
return new RedirectResponse($targetPath);
|
||||
}
|
||||
|
||||
public function supportsRememberMe()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override to control what happens when the user hits a secure page
|
||||
* but isn't logged in yet.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param AuthenticationException|null $authException
|
||||
*
|
||||
* @return RedirectResponse
|
||||
*/
|
||||
public function start(Request $request, AuthenticationException $authException = null)
|
||||
{
|
||||
$url = $this->getLoginUrl();
|
||||
|
||||
return new RedirectResponse($url);
|
||||
}
|
||||
}
|
@ -0,0 +1,193 @@
|
||||
<?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\Guard\Firewall;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
|
||||
use Symfony\Component\Security\Guard\GuardAuthenticatorHandler;
|
||||
use Symfony\Component\Security\Guard\Token\PreAuthenticationGuardToken;
|
||||
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
|
||||
use Symfony\Component\Security\Guard\GuardAuthenticatorInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||
use Symfony\Component\Security\Http\Firewall\ListenerInterface;
|
||||
use Symfony\Component\Security\Http\RememberMe\RememberMeServicesInterface;
|
||||
|
||||
/**
|
||||
* Authentication listener for the "guard" system.
|
||||
*
|
||||
* @author Ryan Weaver <ryan@knpuniversity.com>
|
||||
*/
|
||||
class GuardAuthenticationListener implements ListenerInterface
|
||||
{
|
||||
private $guardHandler;
|
||||
private $authenticationManager;
|
||||
private $providerKey;
|
||||
private $guardAuthenticators;
|
||||
private $logger;
|
||||
private $rememberMeServices;
|
||||
|
||||
/**
|
||||
* @param GuardAuthenticatorHandler $guardHandler The Guard handler
|
||||
* @param AuthenticationManagerInterface $authenticationManager An AuthenticationManagerInterface instance
|
||||
* @param string $providerKey The provider (i.e. firewall) key
|
||||
* @param GuardAuthenticatorInterface[] $guardAuthenticators The authenticators, with keys that match what's passed to GuardAuthenticationProvider
|
||||
* @param LoggerInterface $logger A LoggerInterface instance
|
||||
*/
|
||||
public function __construct(GuardAuthenticatorHandler $guardHandler, AuthenticationManagerInterface $authenticationManager, $providerKey, array $guardAuthenticators, LoggerInterface $logger = null)
|
||||
{
|
||||
if (empty($providerKey)) {
|
||||
throw new \InvalidArgumentException('$providerKey must not be empty.');
|
||||
}
|
||||
|
||||
$this->guardHandler = $guardHandler;
|
||||
$this->authenticationManager = $authenticationManager;
|
||||
$this->providerKey = $providerKey;
|
||||
$this->guardAuthenticators = $guardAuthenticators;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates over each authenticator to see if each wants to authenticate the request.
|
||||
*
|
||||
* @param GetResponseEvent $event
|
||||
*/
|
||||
public function handle(GetResponseEvent $event)
|
||||
{
|
||||
if (null !== $this->logger) {
|
||||
$this->logger->info('Checking for guard authentication credentials.', array('firewall_key' => $this->providerKey, 'authenticators' => count($this->guardAuthenticators)));
|
||||
}
|
||||
|
||||
foreach ($this->guardAuthenticators as $key => $guardAuthenticator) {
|
||||
// get a key that's unique to *this* guard authenticator
|
||||
// this MUST be the same as GuardAuthenticationProvider
|
||||
$uniqueGuardKey = $this->providerKey.'_'.$key;
|
||||
|
||||
$this->executeGuardAuthenticator($uniqueGuardKey, $guardAuthenticator, $event);
|
||||
}
|
||||
}
|
||||
|
||||
private function executeGuardAuthenticator($uniqueGuardKey, GuardAuthenticatorInterface $guardAuthenticator, GetResponseEvent $event)
|
||||
{
|
||||
$request = $event->getRequest();
|
||||
try {
|
||||
if (null !== $this->logger) {
|
||||
$this->logger->info('Calling getCredentials on guard configurator.', array('firewall_key' => $this->providerKey, 'authenticator' => get_class($guardAuthenticator)));
|
||||
}
|
||||
|
||||
// allow the authenticator to fetch authentication info from the request
|
||||
$credentials = $guardAuthenticator->getCredentials($request);
|
||||
|
||||
// allow null to be returned to skip authentication
|
||||
if (null === $credentials) {
|
||||
return;
|
||||
}
|
||||
|
||||
// create a token with the unique key, so that the provider knows which authenticator to use
|
||||
$token = new PreAuthenticationGuardToken($credentials, $uniqueGuardKey);
|
||||
|
||||
if (null !== $this->logger) {
|
||||
$this->logger->info('Passing guard token information to the GuardAuthenticationProvider', array('firewall_key' => $this->providerKey, 'authenticator' => get_class($guardAuthenticator)));
|
||||
}
|
||||
// pass the token into the AuthenticationManager system
|
||||
// this indirectly calls GuardAuthenticationProvider::authenticate()
|
||||
$token = $this->authenticationManager->authenticate($token);
|
||||
|
||||
if (null !== $this->logger) {
|
||||
$this->logger->info('Guard authentication successful!', array('token' => $token, 'authenticator' => get_class($guardAuthenticator)));
|
||||
}
|
||||
|
||||
// sets the token on the token storage, etc
|
||||
$this->guardHandler->authenticateWithToken($token, $request);
|
||||
} catch (AuthenticationException $e) {
|
||||
// oh no! Authentication failed!
|
||||
|
||||
if (null !== $this->logger) {
|
||||
$this->logger->info('Guard authentication failed.', array('exception' => $e, 'authenticator' => get_class($guardAuthenticator)));
|
||||
}
|
||||
|
||||
$response = $this->guardHandler->handleAuthenticationFailure($e, $request, $guardAuthenticator, $this->providerKey);
|
||||
|
||||
if ($response instanceof Response) {
|
||||
$event->setResponse($response);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// success!
|
||||
$response = $this->guardHandler->handleAuthenticationSuccess($token, $request, $guardAuthenticator, $this->providerKey);
|
||||
if ($response instanceof Response) {
|
||||
if (null !== $this->logger) {
|
||||
$this->logger->info('Guard authenticator set success response.', array('response' => $response, 'authenticator' => get_class($guardAuthenticator)));
|
||||
}
|
||||
|
||||
$event->setResponse($response);
|
||||
} else {
|
||||
if (null !== $this->logger) {
|
||||
$this->logger->info('Guard authenticator set no success response: request continues.', array('authenticator' => get_class($guardAuthenticator)));
|
||||
}
|
||||
}
|
||||
|
||||
// attempt to trigger the remember me functionality
|
||||
$this->triggerRememberMe($guardAuthenticator, $request, $token, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be called if this listener will support remember me.
|
||||
*
|
||||
* @param RememberMeServicesInterface $rememberMeServices
|
||||
*/
|
||||
public function setRememberMeServices(RememberMeServicesInterface $rememberMeServices)
|
||||
{
|
||||
$this->rememberMeServices = $rememberMeServices;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if remember me is supported in the authenticator and
|
||||
* on the firewall. If it is, the RememberMeServicesInterface is notified.
|
||||
*
|
||||
* @param GuardAuthenticatorInterface $guardAuthenticator
|
||||
* @param Request $request
|
||||
* @param TokenInterface $token
|
||||
* @param Response $response
|
||||
*/
|
||||
private function triggerRememberMe(GuardAuthenticatorInterface $guardAuthenticator, Request $request, TokenInterface $token, Response $response = null)
|
||||
{
|
||||
if (null === $this->rememberMeServices) {
|
||||
if (null !== $this->logger) {
|
||||
$this->logger->info('Remember me skipped: it is not configured for the firewall.', array('authenticator' => get_class($guardAuthenticator)));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$guardAuthenticator->supportsRememberMe()) {
|
||||
if (null !== $this->logger) {
|
||||
$this->logger->info('Remember me skipped: your authenticator does not support it.', array('authenticator' => get_class($guardAuthenticator)));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$response instanceof Response) {
|
||||
throw new \LogicException(sprintf(
|
||||
'%s::onAuthenticationSuccess *must* return a Response if you want to use the remember me functionality. Return a Response, or set remember_me to false under the guard configuration.',
|
||||
get_class($guardAuthenticator)
|
||||
));
|
||||
}
|
||||
|
||||
$this->rememberMeServices->loginSuccess($request, $response, $token);
|
||||
}
|
||||
}
|
@ -0,0 +1,139 @@
|
||||
<?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\Guard;
|
||||
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Security\Guard\Token\PostAuthenticationGuardToken;
|
||||
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
|
||||
use Symfony\Component\Security\Http\SecurityEvents;
|
||||
|
||||
/**
|
||||
* A utility class that does much of the *work* during the guard authentication process.
|
||||
*
|
||||
* By having the logic here instead of the listener, more of the process
|
||||
* can be called directly (e.g. for manual authentication) or overridden.
|
||||
*
|
||||
* @author Ryan Weaver <ryan@knpuniversity.com>
|
||||
*/
|
||||
class GuardAuthenticatorHandler
|
||||
{
|
||||
private $tokenStorage;
|
||||
|
||||
private $dispatcher;
|
||||
|
||||
public function __construct(TokenStorageInterface $tokenStorage, EventDispatcherInterface $eventDispatcher = null)
|
||||
{
|
||||
$this->tokenStorage = $tokenStorage;
|
||||
$this->dispatcher = $eventDispatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticates the given token in the system.
|
||||
*
|
||||
* @param TokenInterface $token
|
||||
* @param Request $request
|
||||
*/
|
||||
public function authenticateWithToken(TokenInterface $token, Request $request)
|
||||
{
|
||||
$this->tokenStorage->setToken($token);
|
||||
|
||||
if (null !== $this->dispatcher) {
|
||||
$loginEvent = new InteractiveLoginEvent($request, $token);
|
||||
$this->dispatcher->dispatch(SecurityEvents::INTERACTIVE_LOGIN, $loginEvent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the "on success" response for the given GuardAuthenticator.
|
||||
*
|
||||
* @param TokenInterface $token
|
||||
* @param Request $request
|
||||
* @param GuardAuthenticatorInterface $guardAuthenticator
|
||||
* @param string $providerKey The provider (i.e. firewall) key
|
||||
*
|
||||
* @return null|Response
|
||||
*/
|
||||
public function handleAuthenticationSuccess(TokenInterface $token, Request $request, GuardAuthenticatorInterface $guardAuthenticator, $providerKey)
|
||||
{
|
||||
$response = $guardAuthenticator->onAuthenticationSuccess($request, $token, $providerKey);
|
||||
|
||||
// check that it's a Response or null
|
||||
if ($response instanceof Response || null === $response) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
throw new \UnexpectedValueException(sprintf(
|
||||
'The %s::onAuthenticationSuccess method must return null or a Response object. You returned %s.',
|
||||
get_class($guardAuthenticator),
|
||||
is_object($response) ? get_class($response) : gettype($response)
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method for authenticating the user and returning the
|
||||
* Response *if any* for success.
|
||||
*
|
||||
* @param UserInterface $user
|
||||
* @param Request $request
|
||||
* @param GuardAuthenticatorInterface $authenticator
|
||||
* @param string $providerKey The provider (i.e. firewall) key
|
||||
*
|
||||
* @return Response|null
|
||||
*/
|
||||
public function authenticateUserAndHandleSuccess(UserInterface $user, Request $request, GuardAuthenticatorInterface $authenticator, $providerKey)
|
||||
{
|
||||
// create an authenticated token for the User
|
||||
$token = $authenticator->createAuthenticatedToken($user, $providerKey);
|
||||
// authenticate this in the system
|
||||
$this->authenticateWithToken($token, $request);
|
||||
|
||||
// return the success metric
|
||||
return $this->handleAuthenticationSuccess($token, $request, $authenticator, $providerKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles an authentication failure and returns the Response for the
|
||||
* GuardAuthenticator.
|
||||
*
|
||||
* @param AuthenticationException $authenticationException
|
||||
* @param Request $request
|
||||
* @param GuardAuthenticatorInterface $guardAuthenticator
|
||||
* @param string $providerKey The key of the firewall
|
||||
*
|
||||
* @return null|Response
|
||||
*/
|
||||
public function handleAuthenticationFailure(AuthenticationException $authenticationException, Request $request, GuardAuthenticatorInterface $guardAuthenticator, $providerKey)
|
||||
{
|
||||
$token = $this->tokenStorage->getToken();
|
||||
if ($token instanceof PostAuthenticationGuardToken && $providerKey === $token->getProviderKey()) {
|
||||
$this->tokenStorage->setToken(null);
|
||||
}
|
||||
|
||||
$response = $guardAuthenticator->onAuthenticationFailure($request, $authenticationException);
|
||||
if ($response instanceof Response || null === $response) {
|
||||
// returning null is ok, it means they want the request to continue
|
||||
return $response;
|
||||
}
|
||||
|
||||
throw new \UnexpectedValueException(sprintf(
|
||||
'The %s::onAuthenticationFailure method must return null or a Response object. You returned %s.',
|
||||
get_class($guardAuthenticator),
|
||||
is_object($response) ? get_class($response) : gettype($response)
|
||||
));
|
||||
}
|
||||
}
|
@ -0,0 +1,150 @@
|
||||
<?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\Guard;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Security\Core\User\UserProviderInterface;
|
||||
use Symfony\Component\Security\Guard\Token\GuardTokenInterface;
|
||||
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;
|
||||
|
||||
/**
|
||||
* The interface for all "guard" authenticators.
|
||||
*
|
||||
* The methods on this interface are called throughout the guard authentication
|
||||
* process to give you the power to control most parts of the process from
|
||||
* one location.
|
||||
*
|
||||
* @author Ryan Weaver <ryan@knpuniversity.com>
|
||||
*/
|
||||
interface GuardAuthenticatorInterface extends AuthenticationEntryPointInterface
|
||||
{
|
||||
/**
|
||||
* Get the authentication credentials from the request and return them
|
||||
* as any type (e.g. an associate array). If you return null, authentication
|
||||
* will be skipped.
|
||||
*
|
||||
* Whatever value you return here will be passed to getUser() and checkCredentials()
|
||||
*
|
||||
* For example, for a form login, you might:
|
||||
*
|
||||
* return array(
|
||||
* 'username' => $request->request->get('_username'),
|
||||
* 'password' => $request->request->get('_password'),
|
||||
* );
|
||||
*
|
||||
* Or for an API token that's on a header, you might use:
|
||||
*
|
||||
* return array('api_key' => $request->headers->get('X-API-TOKEN'));
|
||||
*
|
||||
* @param Request $request
|
||||
*
|
||||
* @return mixed|null
|
||||
*/
|
||||
public function getCredentials(Request $request);
|
||||
|
||||
/**
|
||||
* Return a UserInterface object based on the credentials.
|
||||
*
|
||||
* The *credentials* are the return value from getCredentials()
|
||||
*
|
||||
* You may throw an AuthenticationException if you wish. If you return
|
||||
* null, then a UsernameNotFoundException is thrown for you.
|
||||
*
|
||||
* @param mixed $credentials
|
||||
* @param UserProviderInterface $userProvider
|
||||
*
|
||||
* @throws AuthenticationException
|
||||
*
|
||||
* @return UserInterface|null
|
||||
*/
|
||||
public function getUser($credentials, UserProviderInterface $userProvider);
|
||||
|
||||
/**
|
||||
* Throw an AuthenticationException if the credentials are invalid.
|
||||
*
|
||||
* The *credentials* are the return value from getCredentials()
|
||||
*
|
||||
* @param mixed $credentials
|
||||
* @param UserInterface $user
|
||||
*
|
||||
* @throws AuthenticationException
|
||||
*/
|
||||
public function checkCredentials($credentials, UserInterface $user);
|
||||
|
||||
/**
|
||||
* Create an authenticated token for the given user.
|
||||
*
|
||||
* If you don't care about which token class is used or don't really
|
||||
* understand what a "token" is, you can skip this method by extending
|
||||
* the AbstractGuardAuthenticator class from your authenticator.
|
||||
*
|
||||
* @see AbstractGuardAuthenticator
|
||||
*
|
||||
* @param UserInterface $user
|
||||
* @param string $providerKey The provider (i.e. firewall) key
|
||||
*
|
||||
* @return GuardTokenInterface
|
||||
*/
|
||||
public function createAuthenticatedToken(UserInterface $user, $providerKey);
|
||||
|
||||
/**
|
||||
* Called when authentication executed, but failed (e.g. wrong username password).
|
||||
*
|
||||
* This should return the Response sent back to the user, like a
|
||||
* RedirectResponse to the login page or a 403 response.
|
||||
*
|
||||
* If you return null, the request will continue, but the user will
|
||||
* not be authenticated. This is probably not what you want to do.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param AuthenticationException $exception
|
||||
*
|
||||
* @return Response|null
|
||||
*/
|
||||
public function onAuthenticationFailure(Request $request, AuthenticationException $exception);
|
||||
|
||||
/**
|
||||
* Called when authentication executed and was successful!
|
||||
*
|
||||
* This should return the Response sent back to the user, like a
|
||||
* RedirectResponse to the last page they visited.
|
||||
*
|
||||
* If you return null, the current request will continue, and the user
|
||||
* will be authenticated. This makes sense, for example, with an API.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param TokenInterface $token
|
||||
* @param string $providerKey The provider (i.e. firewall) key
|
||||
*
|
||||
* @return Response|null
|
||||
*/
|
||||
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey);
|
||||
|
||||
/**
|
||||
* Does this method support remember me cookies?
|
||||
*
|
||||
* Remember me cookie will be set if *all* of the following are met:
|
||||
* A) This method returns true
|
||||
* B) The remember_me key under your firewall is configured
|
||||
* C) The "remember me" functionality is activated. This is usually
|
||||
* done by having a _remember_me checkbox in your form, but
|
||||
* can be configured by the "always_remember_me" and "remember_me_parameter"
|
||||
* parameters under the "remember_me" firewall key
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function supportsRememberMe();
|
||||
}
|
19
src/Symfony/Component/Security/Guard/LICENSE
Normal file
19
src/Symfony/Component/Security/Guard/LICENSE
Normal file
@ -0,0 +1,19 @@
|
||||
Copyright (c) 2004-2015 Fabien Potencier
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
@ -0,0 +1,145 @@
|
||||
<?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\Guard\Provider;
|
||||
|
||||
use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken;
|
||||
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
|
||||
use Symfony\Component\Security\Guard\GuardAuthenticatorInterface;
|
||||
use Symfony\Component\Security\Guard\Token\GuardTokenInterface;
|
||||
use Symfony\Component\Security\Guard\Token\PreAuthenticationGuardToken;
|
||||
use Symfony\Component\Security\Core\User\UserCheckerInterface;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
use Symfony\Component\Security\Core\User\UserProviderInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\Exception\AuthenticationExpiredException;
|
||||
|
||||
/**
|
||||
* Responsible for accepting the PreAuthenticationGuardToken and calling
|
||||
* the correct authenticator to retrieve the authenticated token.
|
||||
*
|
||||
* @author Ryan Weaver <ryan@knpuniversity.com>
|
||||
*/
|
||||
class GuardAuthenticationProvider implements AuthenticationProviderInterface
|
||||
{
|
||||
/**
|
||||
* @var GuardAuthenticatorInterface[]
|
||||
*/
|
||||
private $guardAuthenticators;
|
||||
private $userProvider;
|
||||
private $providerKey;
|
||||
private $userChecker;
|
||||
|
||||
/**
|
||||
* @param GuardAuthenticatorInterface[] $guardAuthenticators The authenticators, with keys that match what's passed to GuardAuthenticationListener
|
||||
* @param UserProviderInterface $userProvider The user provider
|
||||
* @param string $providerKey The provider (i.e. firewall) key
|
||||
* @param UserCheckerInterface $userChecker
|
||||
*/
|
||||
public function __construct(array $guardAuthenticators, UserProviderInterface $userProvider, $providerKey, UserCheckerInterface $userChecker)
|
||||
{
|
||||
$this->guardAuthenticators = $guardAuthenticators;
|
||||
$this->userProvider = $userProvider;
|
||||
$this->providerKey = $providerKey;
|
||||
$this->userChecker = $userChecker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the correct authenticator for the token and calls it.
|
||||
*
|
||||
* @param GuardTokenInterface $token
|
||||
*
|
||||
* @return TokenInterface
|
||||
*/
|
||||
public function authenticate(TokenInterface $token)
|
||||
{
|
||||
if (!$this->supports($token)) {
|
||||
throw new \InvalidArgumentException('GuardAuthenticationProvider only supports GuardTokenInterface.');
|
||||
}
|
||||
|
||||
if (!$token instanceof PreAuthenticationGuardToken) {
|
||||
/*
|
||||
* The listener *only* passes PreAuthenticationGuardToken instances.
|
||||
* This means that an authenticated token (e.g. PostAuthenticationGuardToken)
|
||||
* is being passed here, which happens if that token becomes
|
||||
* "not authenticated" (e.g. happens if the user changes between
|
||||
* requests). In this case, the user should be logged out, so
|
||||
* we will return an AnonymousToken to accomplish that.
|
||||
*/
|
||||
|
||||
// this should never happen - but technically, the token is
|
||||
// authenticated... so it could just be returned
|
||||
if ($token->isAuthenticated()) {
|
||||
return $token;
|
||||
}
|
||||
|
||||
// this AccountStatusException causes the user to be logged out
|
||||
throw new AuthenticationExpiredException();
|
||||
}
|
||||
|
||||
// find the *one* GuardAuthenticator that this token originated from
|
||||
foreach ($this->guardAuthenticators as $key => $guardAuthenticator) {
|
||||
// get a key that's unique to *this* guard authenticator
|
||||
// this MUST be the same as GuardAuthenticationListener
|
||||
$uniqueGuardKey = $this->providerKey.'_'.$key;
|
||||
|
||||
if ($uniqueGuardKey == $token->getGuardProviderKey()) {
|
||||
return $this->authenticateViaGuard($guardAuthenticator, $token);
|
||||
}
|
||||
}
|
||||
|
||||
// no matching authenticator found - but there will be multiple GuardAuthenticationProvider
|
||||
// instances that will be checked if you have multiple firewalls.
|
||||
}
|
||||
|
||||
private function authenticateViaGuard(GuardAuthenticatorInterface $guardAuthenticator, PreAuthenticationGuardToken $token)
|
||||
{
|
||||
// get the user from the GuardAuthenticator
|
||||
$user = $guardAuthenticator->getUser($token->getCredentials(), $this->userProvider);
|
||||
|
||||
if (null === $user) {
|
||||
throw new UsernameNotFoundException(sprintf(
|
||||
'Null returned from %s::getUser()',
|
||||
get_class($guardAuthenticator)
|
||||
));
|
||||
}
|
||||
|
||||
if (!$user instanceof UserInterface) {
|
||||
throw new \UnexpectedValueException(sprintf(
|
||||
'The %s::getUser() method must return a UserInterface. You returned %s.',
|
||||
get_class($guardAuthenticator),
|
||||
is_object($user) ? get_class($user) : gettype($user)
|
||||
));
|
||||
}
|
||||
|
||||
$this->userChecker->checkPreAuth($user);
|
||||
$guardAuthenticator->checkCredentials($token->getCredentials(), $user);
|
||||
$this->userChecker->checkPostAuth($user);
|
||||
|
||||
// turn the UserInterface into a TokenInterface
|
||||
$authenticatedToken = $guardAuthenticator->createAuthenticatedToken($user, $this->providerKey);
|
||||
if (!$authenticatedToken instanceof TokenInterface) {
|
||||
throw new \UnexpectedValueException(sprintf(
|
||||
'The %s::createAuthenticatedToken() method must return a TokenInterface. You returned %s.',
|
||||
get_class($guardAuthenticator),
|
||||
is_object($authenticatedToken) ? get_class($authenticatedToken) : gettype($authenticatedToken)
|
||||
));
|
||||
}
|
||||
|
||||
return $authenticatedToken;
|
||||
}
|
||||
|
||||
public function supports(TokenInterface $token)
|
||||
{
|
||||
return $token instanceof GuardTokenInterface;
|
||||
}
|
||||
}
|
22
src/Symfony/Component/Security/Guard/README.md
Normal file
22
src/Symfony/Component/Security/Guard/README.md
Normal file
@ -0,0 +1,22 @@
|
||||
Security Component - Guard
|
||||
==========================
|
||||
|
||||
The Guard component brings many layers of authentication together, making
|
||||
it much easier to create complex authentication systems where you have
|
||||
total control.
|
||||
|
||||
Resources
|
||||
---------
|
||||
|
||||
Documentation:
|
||||
|
||||
https://symfony.com/doc/2.8/book/security.html
|
||||
|
||||
Tests
|
||||
-----
|
||||
|
||||
You can run the unit tests with the following command:
|
||||
|
||||
$ cd path/to/Symfony/Component/Security/Guard/
|
||||
$ composer.phar install --dev
|
||||
$ phpunit
|
@ -0,0 +1,222 @@
|
||||
<?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\Guard\Tests\Firewall;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Security\Guard\Firewall\GuardAuthenticationListener;
|
||||
use Symfony\Component\Security\Guard\Token\PreAuthenticationGuardToken;
|
||||
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||
|
||||
/**
|
||||
* @author Ryan Weaver <weaverryan@gmail.com>
|
||||
*/
|
||||
class GuardAuthenticationListenerTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
private $authenticationManager;
|
||||
private $guardAuthenticatorHandler;
|
||||
private $event;
|
||||
private $logger;
|
||||
private $request;
|
||||
private $rememberMeServices;
|
||||
|
||||
public function testHandleSuccess()
|
||||
{
|
||||
$authenticator = $this->getMock('Symfony\Component\Security\Guard\GuardAuthenticatorInterface');
|
||||
$authenticateToken = $this->getMock('Symfony\Component\Security\Core\Authentication\Token\TokenInterface');
|
||||
$providerKey = 'my_firewall';
|
||||
|
||||
$credentials = array('username' => 'weaverryan', 'password' => 'all_your_base');
|
||||
$authenticator
|
||||
->expects($this->once())
|
||||
->method('getCredentials')
|
||||
->with($this->equalTo($this->request))
|
||||
->will($this->returnValue($credentials));
|
||||
|
||||
// a clone of the token that should be created internally
|
||||
$uniqueGuardKey = 'my_firewall_0';
|
||||
$nonAuthedToken = new PreAuthenticationGuardToken($credentials, $uniqueGuardKey);
|
||||
|
||||
$this->authenticationManager
|
||||
->expects($this->once())
|
||||
->method('authenticate')
|
||||
->with($this->equalTo($nonAuthedToken))
|
||||
->will($this->returnValue($authenticateToken));
|
||||
|
||||
$this->guardAuthenticatorHandler
|
||||
->expects($this->once())
|
||||
->method('authenticateWithToken')
|
||||
->with($authenticateToken, $this->request);
|
||||
|
||||
$this->guardAuthenticatorHandler
|
||||
->expects($this->once())
|
||||
->method('handleAuthenticationSuccess')
|
||||
->with($authenticateToken, $this->request, $authenticator, $providerKey);
|
||||
|
||||
$listener = new GuardAuthenticationListener(
|
||||
$this->guardAuthenticatorHandler,
|
||||
$this->authenticationManager,
|
||||
$providerKey,
|
||||
array($authenticator),
|
||||
$this->logger
|
||||
);
|
||||
|
||||
$listener->setRememberMeServices($this->rememberMeServices);
|
||||
// should never be called - our handleAuthenticationSuccess() does not return a Response
|
||||
$this->rememberMeServices
|
||||
->expects($this->never())
|
||||
->method('loginSuccess');
|
||||
|
||||
$listener->handle($this->event);
|
||||
}
|
||||
|
||||
public function testHandleSuccessWithRememberMe()
|
||||
{
|
||||
$authenticator = $this->getMock('Symfony\Component\Security\Guard\GuardAuthenticatorInterface');
|
||||
$authenticateToken = $this->getMock('Symfony\Component\Security\Core\Authentication\Token\TokenInterface');
|
||||
$providerKey = 'my_firewall_with_rememberme';
|
||||
|
||||
$authenticator
|
||||
->expects($this->once())
|
||||
->method('getCredentials')
|
||||
->with($this->equalTo($this->request))
|
||||
->will($this->returnValue(array('username' => 'anything_not_empty')));
|
||||
|
||||
$this->authenticationManager
|
||||
->expects($this->once())
|
||||
->method('authenticate')
|
||||
->will($this->returnValue($authenticateToken));
|
||||
|
||||
$successResponse = new Response('Success!');
|
||||
$this->guardAuthenticatorHandler
|
||||
->expects($this->once())
|
||||
->method('handleAuthenticationSuccess')
|
||||
->will($this->returnValue($successResponse));
|
||||
|
||||
$listener = new GuardAuthenticationListener(
|
||||
$this->guardAuthenticatorHandler,
|
||||
$this->authenticationManager,
|
||||
$providerKey,
|
||||
array($authenticator),
|
||||
$this->logger
|
||||
);
|
||||
|
||||
$listener->setRememberMeServices($this->rememberMeServices);
|
||||
$authenticator->expects($this->once())
|
||||
->method('supportsRememberMe')
|
||||
->will($this->returnValue(true));
|
||||
// should be called - we do have a success Response
|
||||
$this->rememberMeServices
|
||||
->expects($this->once())
|
||||
->method('loginSuccess');
|
||||
|
||||
$listener->handle($this->event);
|
||||
}
|
||||
|
||||
public function testHandleCatchesAuthenticationException()
|
||||
{
|
||||
$authenticator = $this->getMock('Symfony\Component\Security\Guard\GuardAuthenticatorInterface');
|
||||
$providerKey = 'my_firewall2';
|
||||
|
||||
$authException = new AuthenticationException('Get outta here crazy user with a bad password!');
|
||||
$authenticator
|
||||
->expects($this->once())
|
||||
->method('getCredentials')
|
||||
->will($this->throwException($authException));
|
||||
|
||||
// this is not called
|
||||
$this->authenticationManager
|
||||
->expects($this->never())
|
||||
->method('authenticate');
|
||||
|
||||
$this->guardAuthenticatorHandler
|
||||
->expects($this->once())
|
||||
->method('handleAuthenticationFailure')
|
||||
->with($authException, $this->request, $authenticator, $providerKey);
|
||||
|
||||
$listener = new GuardAuthenticationListener(
|
||||
$this->guardAuthenticatorHandler,
|
||||
$this->authenticationManager,
|
||||
$providerKey,
|
||||
array($authenticator),
|
||||
$this->logger
|
||||
);
|
||||
|
||||
$listener->handle($this->event);
|
||||
}
|
||||
|
||||
public function testReturnNullToSkipAuth()
|
||||
{
|
||||
$authenticatorA = $this->getMock('Symfony\Component\Security\Guard\GuardAuthenticatorInterface');
|
||||
$authenticatorB = $this->getMock('Symfony\Component\Security\Guard\GuardAuthenticatorInterface');
|
||||
$providerKey = 'my_firewall3';
|
||||
|
||||
$authenticatorA
|
||||
->expects($this->once())
|
||||
->method('getCredentials')
|
||||
->will($this->returnValue(null));
|
||||
$authenticatorB
|
||||
->expects($this->once())
|
||||
->method('getCredentials')
|
||||
->will($this->returnValue(null));
|
||||
|
||||
// this is not called
|
||||
$this->authenticationManager
|
||||
->expects($this->never())
|
||||
->method('authenticate');
|
||||
|
||||
$this->guardAuthenticatorHandler
|
||||
->expects($this->never())
|
||||
->method('handleAuthenticationSuccess');
|
||||
|
||||
$listener = new GuardAuthenticationListener(
|
||||
$this->guardAuthenticatorHandler,
|
||||
$this->authenticationManager,
|
||||
$providerKey,
|
||||
array($authenticatorA, $authenticatorB),
|
||||
$this->logger
|
||||
);
|
||||
|
||||
$listener->handle($this->event);
|
||||
}
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
$this->authenticationManager = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\AuthenticationProviderManager')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
$this->guardAuthenticatorHandler = $this->getMockBuilder('Symfony\Component\Security\Guard\GuardAuthenticatorHandler')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
$this->request = new Request(array(), array(), array(), array(), array(), array());
|
||||
|
||||
$this->event = $this->getMock('Symfony\Component\HttpKernel\Event\GetResponseEvent', array(), array(), '', false);
|
||||
$this->event
|
||||
->expects($this->any())
|
||||
->method('getRequest')
|
||||
->will($this->returnValue($this->request));
|
||||
|
||||
$this->logger = $this->getMock('Psr\Log\LoggerInterface');
|
||||
$this->rememberMeServices = $this->getMock('Symfony\Component\Security\Http\RememberMe\RememberMeServicesInterface');
|
||||
}
|
||||
|
||||
protected function tearDown()
|
||||
{
|
||||
$this->authenticationManager = null;
|
||||
$this->guardAuthenticatorHandler = null;
|
||||
$this->event = null;
|
||||
$this->logger = null;
|
||||
$this->request = null;
|
||||
}
|
||||
}
|
@ -0,0 +1,141 @@
|
||||
<?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\Guard\Tests;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Security\Guard\GuardAuthenticatorHandler;
|
||||
use Symfony\Component\Security\Core\Exception\AuthenticationException;
|
||||
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
|
||||
use Symfony\Component\Security\Http\SecurityEvents;
|
||||
|
||||
class GuardAuthenticatorHandlerTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
private $tokenStorage;
|
||||
private $dispatcher;
|
||||
private $token;
|
||||
private $request;
|
||||
private $guardAuthenticator;
|
||||
|
||||
public function testAuthenticateWithToken()
|
||||
{
|
||||
$this->tokenStorage->expects($this->once())
|
||||
->method('setToken')
|
||||
->with($this->token);
|
||||
|
||||
$loginEvent = new InteractiveLoginEvent($this->request, $this->token);
|
||||
|
||||
$this->dispatcher
|
||||
->expects($this->once())
|
||||
->method('dispatch')
|
||||
->with($this->equalTo(SecurityEvents::INTERACTIVE_LOGIN), $this->equalTo($loginEvent))
|
||||
;
|
||||
|
||||
$handler = new GuardAuthenticatorHandler($this->tokenStorage, $this->dispatcher);
|
||||
$handler->authenticateWithToken($this->token, $this->request);
|
||||
}
|
||||
|
||||
public function testHandleAuthenticationSuccess()
|
||||
{
|
||||
$providerKey = 'my_handleable_firewall';
|
||||
$response = new Response('Guard all the things!');
|
||||
$this->guardAuthenticator->expects($this->once())
|
||||
->method('onAuthenticationSuccess')
|
||||
->with($this->request, $this->token, $providerKey)
|
||||
->will($this->returnValue($response));
|
||||
|
||||
$handler = new GuardAuthenticatorHandler($this->tokenStorage, $this->dispatcher);
|
||||
$actualResponse = $handler->handleAuthenticationSuccess($this->token, $this->request, $this->guardAuthenticator, $providerKey);
|
||||
$this->assertSame($response, $actualResponse);
|
||||
}
|
||||
|
||||
public function testHandleAuthenticationFailure()
|
||||
{
|
||||
// setToken() not called - getToken() will return null, so there's nothing to clear
|
||||
$this->tokenStorage->expects($this->never())
|
||||
->method('setToken')
|
||||
->with(null);
|
||||
$authException = new AuthenticationException('Bad password!');
|
||||
|
||||
$response = new Response('Try again, but with the right password!');
|
||||
$this->guardAuthenticator->expects($this->once())
|
||||
->method('onAuthenticationFailure')
|
||||
->with($this->request, $authException)
|
||||
->will($this->returnValue($response));
|
||||
|
||||
$handler = new GuardAuthenticatorHandler($this->tokenStorage, $this->dispatcher);
|
||||
$actualResponse = $handler->handleAuthenticationFailure($authException, $this->request, $this->guardAuthenticator, 'firewall_provider_key');
|
||||
$this->assertSame($response, $actualResponse);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getTokenClearingTests
|
||||
*/
|
||||
public function testHandleAuthenticationClearsToken($tokenClass, $tokenProviderKey, $actualProviderKey, $shouldTokenBeCleared)
|
||||
{
|
||||
$token = $this->getMockBuilder($tokenClass)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$token->expects($this->any())
|
||||
->method('getProviderKey')
|
||||
->will($this->returnValue($tokenProviderKey));
|
||||
|
||||
// make the $token be the current token
|
||||
$this->tokenStorage->expects($this->once())
|
||||
->method('getToken')
|
||||
->will($this->returnValue($token));
|
||||
|
||||
$this->tokenStorage->expects($shouldTokenBeCleared ? $this->once() : $this->never())
|
||||
->method('setToken')
|
||||
->with(null);
|
||||
$authException = new AuthenticationException('Bad password!');
|
||||
|
||||
$response = new Response('Try again, but with the right password!');
|
||||
$this->guardAuthenticator->expects($this->once())
|
||||
->method('onAuthenticationFailure')
|
||||
->with($this->request, $authException)
|
||||
->will($this->returnValue($response));
|
||||
|
||||
$handler = new GuardAuthenticatorHandler($this->tokenStorage, $this->dispatcher);
|
||||
$actualResponse = $handler->handleAuthenticationFailure($authException, $this->request, $this->guardAuthenticator, $actualProviderKey);
|
||||
$this->assertSame($response, $actualResponse);
|
||||
}
|
||||
|
||||
public function getTokenClearingTests()
|
||||
{
|
||||
$tests = array();
|
||||
// correct token class and matching firewall => clear the token
|
||||
$tests[] = array('Symfony\Component\Security\Guard\Token\PostAuthenticationGuardToken', 'the_firewall_key', 'the_firewall_key', true);
|
||||
$tests[] = array('Symfony\Component\Security\Guard\Token\PostAuthenticationGuardToken', 'the_firewall_key', 'different_key', false);
|
||||
$tests[] = array('Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken', 'the_firewall_key', 'the_firewall_key', false);
|
||||
|
||||
return $tests;
|
||||
}
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
$this->tokenStorage = $this->getMock('Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface');
|
||||
$this->dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
|
||||
$this->token = $this->getMock('Symfony\Component\Security\Core\Authentication\Token\TokenInterface');
|
||||
$this->request = new Request(array(), array(), array(), array(), array(), array());
|
||||
$this->guardAuthenticator = $this->getMock('Symfony\Component\Security\Guard\GuardAuthenticatorInterface');
|
||||
}
|
||||
|
||||
protected function tearDown()
|
||||
{
|
||||
$this->tokenStorage = null;
|
||||
$this->dispatcher = null;
|
||||
$this->token = null;
|
||||
$this->request = null;
|
||||
$this->guardAuthenticator = null;
|
||||
}
|
||||
}
|
@ -0,0 +1,115 @@
|
||||
<?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\Guard\Tests\Provider;
|
||||
|
||||
use Symfony\Component\Security\Guard\Provider\GuardAuthenticationProvider;
|
||||
use Symfony\Component\Security\Guard\Token\PostAuthenticationGuardToken;
|
||||
|
||||
/**
|
||||
* @author Ryan Weaver <weaverryan@gmail.com>
|
||||
*/
|
||||
class GuardAuthenticationProviderTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
private $userProvider;
|
||||
private $userChecker;
|
||||
private $preAuthenticationToken;
|
||||
|
||||
public function testAuthenticate()
|
||||
{
|
||||
$providerKey = 'my_cool_firewall';
|
||||
|
||||
$authenticatorA = $this->getMock('Symfony\Component\Security\Guard\GuardAuthenticatorInterface');
|
||||
$authenticatorB = $this->getMock('Symfony\Component\Security\Guard\GuardAuthenticatorInterface');
|
||||
$authenticatorC = $this->getMock('Symfony\Component\Security\Guard\GuardAuthenticatorInterface');
|
||||
$authenticators = array($authenticatorA, $authenticatorB, $authenticatorC);
|
||||
|
||||
// called 2 times - for authenticator A and B (stops on B because of match)
|
||||
$this->preAuthenticationToken->expects($this->exactly(2))
|
||||
->method('getGuardProviderKey')
|
||||
// it will return the "1" index, which will match authenticatorB
|
||||
->will($this->returnValue('my_cool_firewall_1'));
|
||||
|
||||
$enteredCredentials = array(
|
||||
'username' => '_weaverryan_test_user',
|
||||
'password' => 'guard_auth_ftw',
|
||||
);
|
||||
$this->preAuthenticationToken->expects($this->atLeastOnce())
|
||||
->method('getCredentials')
|
||||
->will($this->returnValue($enteredCredentials));
|
||||
|
||||
// authenticators A and C are never called
|
||||
$authenticatorA->expects($this->never())
|
||||
->method('getUser');
|
||||
$authenticatorC->expects($this->never())
|
||||
->method('getUser');
|
||||
|
||||
$mockedUser = $this->getMock('Symfony\Component\Security\Core\User\UserInterface');
|
||||
$authenticatorB->expects($this->once())
|
||||
->method('getUser')
|
||||
->with($enteredCredentials, $this->userProvider)
|
||||
->will($this->returnValue($mockedUser));
|
||||
// checkCredentials is called
|
||||
$authenticatorB->expects($this->once())
|
||||
->method('checkCredentials')
|
||||
->with($enteredCredentials, $mockedUser);
|
||||
$authedToken = $this->getMock('Symfony\Component\Security\Core\Authentication\Token\TokenInterface');
|
||||
$authenticatorB->expects($this->once())
|
||||
->method('createAuthenticatedToken')
|
||||
->with($mockedUser, $providerKey)
|
||||
->will($this->returnValue($authedToken));
|
||||
|
||||
// user checker should be called
|
||||
$this->userChecker->expects($this->once())
|
||||
->method('checkPreAuth')
|
||||
->with($mockedUser);
|
||||
$this->userChecker->expects($this->once())
|
||||
->method('checkPostAuth')
|
||||
->with($mockedUser);
|
||||
|
||||
$provider = new GuardAuthenticationProvider($authenticators, $this->userProvider, $providerKey, $this->userChecker);
|
||||
$actualAuthedToken = $provider->authenticate($this->preAuthenticationToken);
|
||||
$this->assertSame($authedToken, $actualAuthedToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\Security\Core\Exception\AuthenticationExpiredException
|
||||
*/
|
||||
public function testGuardWithNoLongerAuthenticatedTriggersLogout()
|
||||
{
|
||||
$providerKey = 'my_firewall_abc';
|
||||
|
||||
// create a token and mark it as NOT authenticated anymore
|
||||
// this mimics what would happen if a user "changed" between request
|
||||
$mockedUser = $this->getMock('Symfony\Component\Security\Core\User\UserInterface');
|
||||
$token = new PostAuthenticationGuardToken($mockedUser, $providerKey, array('ROLE_USER'));
|
||||
$token->setAuthenticated(false);
|
||||
|
||||
$provider = new GuardAuthenticationProvider(array(), $this->userProvider, $providerKey, $this->userChecker);
|
||||
$actualToken = $provider->authenticate($token);
|
||||
}
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
$this->userProvider = $this->getMock('Symfony\Component\Security\Core\User\UserProviderInterface');
|
||||
$this->userChecker = $this->getMock('Symfony\Component\Security\Core\User\UserCheckerInterface');
|
||||
$this->preAuthenticationToken = $this->getMockBuilder('Symfony\Component\Security\Guard\Token\PreAuthenticationGuardToken')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
}
|
||||
|
||||
protected function tearDown()
|
||||
{
|
||||
$this->userProvider = null;
|
||||
$this->userChecker = null;
|
||||
$this->preAuthenticationToken = null;
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
<?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\Guard\Token;
|
||||
|
||||
/**
|
||||
* A marker interface that both guard tokens implement.
|
||||
*
|
||||
* Any tokens passed to GuardAuthenticationProvider (i.e. any tokens that
|
||||
* are handled by the guard auth system) must implement this
|
||||
* interface.
|
||||
*
|
||||
* @author Ryan Weaver <ryan@knpuniversity.com>
|
||||
*/
|
||||
interface GuardTokenInterface
|
||||
{
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
<?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\Guard\Token;
|
||||
|
||||
use Symfony\Component\Security\Core\Authentication\Token\AbstractToken;
|
||||
use Symfony\Component\Security\Core\Role\RoleInterface;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
|
||||
/**
|
||||
* Used as an "authenticated" token, though it could be set to not-authenticated later.
|
||||
*
|
||||
* If you're using Guard authentication, you *must* use a class that implements
|
||||
* GuardTokenInterface as your authenticated token (like this class).
|
||||
*
|
||||
* @author Ryan Weaver <ryan@knpuniversity.com>n@gmail.com>
|
||||
*/
|
||||
class PostAuthenticationGuardToken extends AbstractToken implements GuardTokenInterface
|
||||
{
|
||||
private $providerKey;
|
||||
|
||||
/**
|
||||
* @param UserInterface $user The user!
|
||||
* @param string $providerKey The provider (firewall) key
|
||||
* @param RoleInterface[]|string[] $roles An array of roles
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function __construct(UserInterface $user, $providerKey, array $roles)
|
||||
{
|
||||
parent::__construct($roles);
|
||||
|
||||
if (empty($providerKey)) {
|
||||
throw new \InvalidArgumentException('$providerKey (i.e. firewall key) must not be empty.');
|
||||
}
|
||||
|
||||
$this->setUser($user);
|
||||
$this->providerKey = $providerKey;
|
||||
|
||||
// this token is meant to be used after authentication success, so it is always authenticated
|
||||
// you could set it as non authenticated later if you need to
|
||||
parent::setAuthenticated(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is meant to be only an authenticated token, where credentials
|
||||
* have already been used and are thus cleared.
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCredentials()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the provider (firewall) key.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getProviderKey()
|
||||
{
|
||||
return $this->providerKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function serialize()
|
||||
{
|
||||
return serialize(array($this->providerKey, parent::serialize()));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function unserialize($serialized)
|
||||
{
|
||||
list($this->providerKey, $parentStr) = unserialize($serialized);
|
||||
parent::unserialize($parentStr);
|
||||
}
|
||||
}
|
@ -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\Component\Security\Guard\Token;
|
||||
|
||||
use Symfony\Component\Security\Core\Authentication\Token\AbstractToken;
|
||||
|
||||
/**
|
||||
* The token used by the guard auth system before authentication.
|
||||
*
|
||||
* The GuardAuthenticationListener creates this, which is then consumed
|
||||
* immediately by the GuardAuthenticationProvider. If authentication is
|
||||
* successful, a different authenticated token is returned
|
||||
*
|
||||
* @author Ryan Weaver <ryan@knpuniversity.com>
|
||||
*/
|
||||
class PreAuthenticationGuardToken extends AbstractToken implements GuardTokenInterface
|
||||
{
|
||||
private $credentials;
|
||||
private $guardProviderKey;
|
||||
|
||||
/**
|
||||
* @param mixed $credentials
|
||||
* @param string $guardProviderKey Unique key that bind this token to a specific GuardAuthenticatorInterface
|
||||
*/
|
||||
public function __construct($credentials, $guardProviderKey)
|
||||
{
|
||||
$this->credentials = $credentials;
|
||||
$this->guardProviderKey = $guardProviderKey;
|
||||
|
||||
parent::__construct(array());
|
||||
|
||||
// never authenticated
|
||||
parent::setAuthenticated(false);
|
||||
}
|
||||
|
||||
public function getGuardProviderKey()
|
||||
{
|
||||
return $this->guardProviderKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the user credentials, which might be an array of anything you
|
||||
* wanted to put in there (e.g. username, password, favoriteColor).
|
||||
*
|
||||
* @return mixed The user credentials
|
||||
*/
|
||||
public function getCredentials()
|
||||
{
|
||||
return $this->credentials;
|
||||
}
|
||||
|
||||
public function setAuthenticated($authenticated)
|
||||
{
|
||||
throw new \LogicException('The PreAuthenticationGuardToken is *never* authenticated.');
|
||||
}
|
||||
}
|
36
src/Symfony/Component/Security/Guard/composer.json
Normal file
36
src/Symfony/Component/Security/Guard/composer.json
Normal file
@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "symfony/security-guard",
|
||||
"type": "library",
|
||||
"description": "Symfony Security Component - Guard",
|
||||
"keywords": [],
|
||||
"homepage": "https://symfony.com",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Fabien Potencier",
|
||||
"email": "fabien@symfony.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.3.9",
|
||||
"symfony/security-core": "~2.8|~3.0.0",
|
||||
"symfony/security-http": "~2.8|~3.0.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/phpunit-bridge": "~2.8|~3.0.0",
|
||||
"psr/log": "~1.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": { "Symfony\\Component\\Security\\Guard\\": "" }
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.8-dev"
|
||||
}
|
||||
}
|
||||
}
|
33
src/Symfony/Component/Security/Guard/phpunit.xml.dist
Normal file
33
src/Symfony/Component/Security/Guard/phpunit.xml.dist
Normal file
@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<phpunit backupGlobals="false"
|
||||
backupStaticAttributes="false"
|
||||
colors="true"
|
||||
convertErrorsToExceptions="true"
|
||||
convertNoticesToExceptions="true"
|
||||
convertWarningsToExceptions="true"
|
||||
processIsolation="false"
|
||||
stopOnFailure="false"
|
||||
syntaxCheck="false"
|
||||
bootstrap="vendor/autoload.php"
|
||||
>
|
||||
<php>
|
||||
<ini name="error_reporting" value="-1" />
|
||||
</php>
|
||||
|
||||
<testsuites>
|
||||
<testsuite name="Symfony Security Component Guard Suite">
|
||||
<directory>./Tests/</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
<filter>
|
||||
<whitelist>
|
||||
<directory>./</directory>
|
||||
<exclude>
|
||||
<directory>./vendor</directory>
|
||||
<directory>./Tests</directory>
|
||||
</exclude>
|
||||
</whitelist>
|
||||
</filter>
|
||||
</phpunit>
|
@ -16,15 +16,25 @@ use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* AuthenticationEntryPointInterface is the interface used to start the
|
||||
* authentication scheme.
|
||||
* Implement this interface for any classes that will be called to "start"
|
||||
* the authentication process (see method for more details).
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
interface AuthenticationEntryPointInterface
|
||||
{
|
||||
/**
|
||||
* Starts the authentication scheme.
|
||||
* Returns a response that directs the user to authenticate.
|
||||
*
|
||||
* This is called when an anonymous request accesses a resource that
|
||||
* requires authentication. The job of this method is to return some
|
||||
* response that "helps" the user start into the authentication process.
|
||||
*
|
||||
* Examples:
|
||||
* A) For a form login, you might redirect to the login page
|
||||
* return new Response('/login');
|
||||
* B) For an API token authentication system, you return a 401 response
|
||||
* return new Response('Auth header required', 401);
|
||||
*
|
||||
* @param Request $request The request that resulted in an AuthenticationException
|
||||
* @param AuthenticationException $authException The exception that started the authentication process
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace Symfony\Component\Security\Tests\Core\Authentication\Voter;
|
||||
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\Authorization\Voter\AbstractVoter;
|
||||
|
||||
/**
|
||||
@ -46,6 +47,17 @@ class AbstractVoterTest extends \PHPUnit_Framework_TestCase
|
||||
$this->assertEquals($expectedVote, $this->voter->vote($this->token, $object, $attributes), $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getData
|
||||
* @group legacy
|
||||
*/
|
||||
public function testVoteUsingDeprecatedIsGranted($expectedVote, $object, $attributes, $message)
|
||||
{
|
||||
$voter = new DeprecatedVoterFixture();
|
||||
|
||||
$this->assertEquals($expectedVote, $voter->vote($this->token, $object, $attributes), $message);
|
||||
}
|
||||
|
||||
public function getData()
|
||||
{
|
||||
return array(
|
||||
@ -75,6 +87,26 @@ class VoterFixture extends AbstractVoter
|
||||
return array('foo', 'bar', 'baz');
|
||||
}
|
||||
|
||||
protected function voteOnAttribute($attribute, $object, TokenInterface $token)
|
||||
{
|
||||
return $attribute === 'foo';
|
||||
}
|
||||
}
|
||||
|
||||
class DeprecatedVoterFixture extends AbstractVoter
|
||||
{
|
||||
protected function getSupportedClasses()
|
||||
{
|
||||
return array(
|
||||
'Symfony\Component\Security\Tests\Core\Authentication\Voter\ObjectFixture',
|
||||
);
|
||||
}
|
||||
|
||||
protected function getSupportedAttributes()
|
||||
{
|
||||
return array('foo', 'bar', 'baz');
|
||||
}
|
||||
|
||||
protected function isGranted($attribute, $object, $user = null)
|
||||
{
|
||||
return $attribute === 'foo';
|
||||
|
@ -15,6 +15,7 @@
|
||||
<directory>./Acl/Tests/</directory>
|
||||
<directory>./Core/Tests/</directory>
|
||||
<directory>./Http/Tests/</directory>
|
||||
<directory>./Guard/Tests/</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
|
Reference in New Issue
Block a user