From 330aa7f7292c9c103d3b7c4d77f7e7f3c9e0b489 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Sun, 17 May 2015 14:21:37 -0400 Subject: [PATCH 01/24] Improving phpdoc on AuthenticationEntryPointInterface so people that implement this understand it --- .../AuthenticationEntryPointInterface.php | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Component/Security/Http/EntryPoint/AuthenticationEntryPointInterface.php b/src/Symfony/Component/Security/Http/EntryPoint/AuthenticationEntryPointInterface.php index 0d7595d40720e..2aa7a03e9c75c 100644 --- a/src/Symfony/Component/Security/Http/EntryPoint/AuthenticationEntryPointInterface.php +++ b/src/Symfony/Component/Security/Http/EntryPoint/AuthenticationEntryPointInterface.php @@ -16,15 +16,25 @@ 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 */ 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 From 05af97c7f7cb6464e539c038c129003f2d0462ce Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Sun, 17 May 2015 14:39:26 -0400 Subject: [PATCH 02/24] Initial commit (but after some polished work) of the new Guard authentication system --- .../Factory/GuardAuthenticationFactory.php | 113 +++++++++ .../DependencyInjection/SecurityExtension.php | 1 + .../SecurityBundle/Resources/config/guard.xml | 40 ++++ .../GuardAuthenticationFactoryTest.php | 181 ++++++++++++++ .../Guard/AbstractGuardAuthenticator.php | 31 +++ .../Firewall/GuardAuthenticationListener.php | 180 ++++++++++++++ .../Guard/GuardAuthenticatorHandler.php | 122 ++++++++++ .../Guard/GuardAuthenticatorInterface.php | 119 ++++++++++ .../Provider/GuardAuthenticationProvider.php | 106 +++++++++ .../GuardAuthenticationListenerTest.php | 222 ++++++++++++++++++ .../Tests/GuardAuthenticatorHandlerTest.php | 99 ++++++++ .../GuardAuthenticationProviderTest.php | 93 ++++++++ .../Guard/Token/GenericGuardToken.php | 96 ++++++++ .../Token/NonAuthenticatedGuardToken.php | 56 +++++ 14 files changed, 1459 insertions(+) create mode 100644 src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/GuardAuthenticationFactory.php create mode 100644 src/Symfony/Bundle/SecurityBundle/Resources/config/guard.xml create mode 100644 src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Security/Factory/GuardAuthenticationFactoryTest.php create mode 100644 src/Symfony/Component/Security/Guard/AbstractGuardAuthenticator.php create mode 100644 src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php create mode 100644 src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php create mode 100644 src/Symfony/Component/Security/Guard/GuardAuthenticatorInterface.php create mode 100644 src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php create mode 100644 src/Symfony/Component/Security/Guard/Tests/Firewall/GuardAuthenticationListenerTest.php create mode 100644 src/Symfony/Component/Security/Guard/Tests/GuardAuthenticatorHandlerTest.php create mode 100644 src/Symfony/Component/Security/Guard/Tests/Provider/GuardAuthenticationProviderTest.php create mode 100644 src/Symfony/Component/Security/Guard/Token/GenericGuardToken.php create mode 100644 src/Symfony/Component/Security/Guard/Token/NonAuthenticatedGuardToken.php diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/GuardAuthenticationFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/GuardAuthenticationFactory.php new file mode 100644 index 0000000000000..e721ee77fee2e --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/GuardAuthenticationFactory.php @@ -0,0 +1,113 @@ + + */ +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) + )); + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php index e82e3ca8779bc..6db34e4d28101 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php @@ -65,6 +65,7 @@ public function load(array $configs, ContainerBuilder $container) $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'); diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/guard.xml b/src/Symfony/Bundle/SecurityBundle/Resources/config/guard.xml new file mode 100644 index 0000000000000..0524cf2b95b4b --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/guard.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Security/Factory/GuardAuthenticationFactoryTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Security/Factory/GuardAuthenticationFactoryTest.php new file mode 100644 index 0000000000000..1f107879db634 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Security/Factory/GuardAuthenticationFactoryTest.php @@ -0,0 +1,181 @@ + + * + * 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); + } +} diff --git a/src/Symfony/Component/Security/Guard/AbstractGuardAuthenticator.php b/src/Symfony/Component/Security/Guard/AbstractGuardAuthenticator.php new file mode 100644 index 0000000000000..ebd09bb73f20e --- /dev/null +++ b/src/Symfony/Component/Security/Guard/AbstractGuardAuthenticator.php @@ -0,0 +1,31 @@ + + */ +abstract class AbstractGuardAuthenticator implements GuardAuthenticatorInterface +{ + /** + * Shortcut to create a GenericGuardToken for you, if you don't really + * care about which authenticated token you're using + * + * @param UserInterface $user + * @param string $providerKey + * @return GenericGuardToken + */ + public function createAuthenticatedToken(UserInterface $user, $providerKey) + { + return new GenericGuardToken( + $user, + $providerKey, + $user->getRoles() + ); + } +} \ No newline at end of file diff --git a/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php b/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php new file mode 100644 index 0000000000000..a53478a2b7c6a --- /dev/null +++ b/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php @@ -0,0 +1,180 @@ + + */ +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, $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 getCredentialsFromRequest on guard configurator', array('firewall_key' => $this->providerKey, 'authenticator' => get_class($guardAuthenticator))); + } + + // allow the authenticator to fetch authentication info from the request + $credentials = $guardAuthenticator->getCredentialsFromRequest($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 NonAuthenticatedGuardToken($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); + + 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 (!$guardAuthenticator->supportsRememberMe()) { + return; + } + + 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 (!$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); + } +} \ No newline at end of file diff --git a/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php b/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php new file mode 100644 index 0000000000000..e574613f7a577 --- /dev/null +++ b/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php @@ -0,0 +1,122 @@ + + */ +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 + * @return null|Response + */ + public function handleAuthenticationFailure(AuthenticationException $authenticationException, Request $request, GuardAuthenticatorInterface $guardAuthenticator) + { + $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) + )); + } +} \ No newline at end of file diff --git a/src/Symfony/Component/Security/Guard/GuardAuthenticatorInterface.php b/src/Symfony/Component/Security/Guard/GuardAuthenticatorInterface.php new file mode 100644 index 0000000000000..dba8d09e224e4 --- /dev/null +++ b/src/Symfony/Component/Security/Guard/GuardAuthenticatorInterface.php @@ -0,0 +1,119 @@ + + */ +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 authenticate() + * + * 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 getCredentialsFromRequest(Request $request); + + /** + * Return a UserInterface object based on the credentials OR throw + * an AuthenticationException + * + * The *credentials* are the return value from getCredentialsFromRequest() + * + * @param mixed $credentials + * @param UserProviderInterface $userProvider + * @throws AuthenticationException + * @return UserInterface + */ + public function authenticate($credentials, UserProviderInterface $userProvider); + + /** + * 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 TokenInterface + */ + 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(); +} diff --git a/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php b/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php new file mode 100644 index 0000000000000..740f4c47c22e4 --- /dev/null +++ b/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php @@ -0,0 +1,106 @@ + + */ +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 NonAuthenticatedGuardToken $token + * @return TokenInterface + */ + public function authenticate(TokenInterface $token) + { + if (!$token instanceof NonAuthenticatedGuardToken) { + throw new \InvalidArgumentException('GuardAuthenticationProvider only supports NonAuthenticatedGuardToken'); + } + + // 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); + } + } + + throw new \LogicException(sprintf( + 'The correct GuardAuthenticator could not be found for unique key "%s". The listener and provider should be passed the same list of authenticators!?', + $token->getGuardProviderKey() + )); + } + + private function authenticateViaGuard(GuardAuthenticatorInterface $guardAuthenticator, NonAuthenticatedGuardToken $token) + { + // get the user from the GuardAuthenticator + $user = $guardAuthenticator->authenticate($token->getCredentials(), $this->userProvider); + + if (!$user instanceof UserInterface) { + throw new \UnexpectedValueException(sprintf( + 'The %s::authenticate method must return a UserInterface. You returned %s', + get_class($guardAuthenticator), + is_object($user) ? get_class($user) : gettype($user) + )); + } + + // check the AdvancedUserInterface methods! + $this->userChecker->checkPreAuth($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 NonAuthenticatedGuardToken; + } +} diff --git a/src/Symfony/Component/Security/Guard/Tests/Firewall/GuardAuthenticationListenerTest.php b/src/Symfony/Component/Security/Guard/Tests/Firewall/GuardAuthenticationListenerTest.php new file mode 100644 index 0000000000000..b8939a93ddd37 --- /dev/null +++ b/src/Symfony/Component/Security/Guard/Tests/Firewall/GuardAuthenticationListenerTest.php @@ -0,0 +1,222 @@ + + * + * 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\NonAuthenticatedGuardToken; +use Symfony\Component\Security\Core\Exception\AuthenticationException; + +/** + * @author Ryan Weaver + */ +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('getCredentialsFromRequest') + ->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 NonAuthenticatedGuardToken($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('getCredentialsFromRequest') + ->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('getCredentialsFromRequest') + ->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); + + $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('getCredentialsFromRequest') + ->will($this->returnValue(null)); + $authenticatorB + ->expects($this->once()) + ->method('getCredentialsFromRequest') + ->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; + } +} diff --git a/src/Symfony/Component/Security/Guard/Tests/GuardAuthenticatorHandlerTest.php b/src/Symfony/Component/Security/Guard/Tests/GuardAuthenticatorHandlerTest.php new file mode 100644 index 0000000000000..6b27e209bb0b1 --- /dev/null +++ b/src/Symfony/Component/Security/Guard/Tests/GuardAuthenticatorHandlerTest.php @@ -0,0 +1,99 @@ + + * + * 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; + +/** + * @author Ryan Weaver + */ +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() + { + $this->tokenStorage->expects($this->once()) + ->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); + $this->assertSame($response, $actualResponse); + } + + 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; + } +} diff --git a/src/Symfony/Component/Security/Guard/Tests/Provider/GuardAuthenticationProviderTest.php b/src/Symfony/Component/Security/Guard/Tests/Provider/GuardAuthenticationProviderTest.php new file mode 100644 index 0000000000000..0ef68186594bd --- /dev/null +++ b/src/Symfony/Component/Security/Guard/Tests/Provider/GuardAuthenticationProviderTest.php @@ -0,0 +1,93 @@ + + * + * 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; + +/** + * @author Ryan Weaver + */ +class GuardAuthenticationProviderTest extends \PHPUnit_Framework_TestCase +{ + private $userProvider; + private $userChecker; + private $nonAuthedToken; + + 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->nonAuthedToken->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->nonAuthedToken->expects($this->once()) + ->method('getCredentials') + ->will($this->returnValue($enteredCredentials)); + + // authenticators A and C are never called + $authenticatorA->expects($this->never()) + ->method('authenticate'); + $authenticatorC->expects($this->never()) + ->method('authenticate'); + + $mockedUser = $this->getMock('Symfony\Component\Security\Core\User\UserInterface'); + $authenticatorB->expects($this->once()) + ->method('authenticate') + ->with($enteredCredentials, $this->userProvider) + ->will($this->returnValue($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->nonAuthedToken); + $this->assertSame($authedToken, $actualAuthedToken); + } + + protected function setUp() + { + $this->userProvider = $this->getMock('Symfony\Component\Security\Core\User\UserProviderInterface'); + $this->userChecker = $this->getMock('Symfony\Component\Security\Core\User\UserCheckerInterface'); + $this->nonAuthedToken = $this->getMockBuilder('Symfony\Component\Security\Guard\Token\NonAuthenticatedGuardToken') + ->disableOriginalConstructor() + ->getMock(); + } + + protected function tearDown() + { + $this->userProvider = null; + $this->userChecker = null; + $this->nonAuthedToken = null; + } +} diff --git a/src/Symfony/Component/Security/Guard/Token/GenericGuardToken.php b/src/Symfony/Component/Security/Guard/Token/GenericGuardToken.php new file mode 100644 index 0000000000000..869d0710c8cf7 --- /dev/null +++ b/src/Symfony/Component/Security/Guard/Token/GenericGuardToken.php @@ -0,0 +1,96 @@ + + */ +class GenericGuardToken extends AbstractToken +{ + 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); + } + + /** + * {@inheritdoc} + */ + public function setAuthenticated($isAuthenticated) + { + if ($isAuthenticated) { + throw new \LogicException('Cannot set this token to trusted after instantiation.'); + } + + parent::setAuthenticated(false); + } + + /** + * 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); + } +} diff --git a/src/Symfony/Component/Security/Guard/Token/NonAuthenticatedGuardToken.php b/src/Symfony/Component/Security/Guard/Token/NonAuthenticatedGuardToken.php new file mode 100644 index 0000000000000..28e21e0dee97d --- /dev/null +++ b/src/Symfony/Component/Security/Guard/Token/NonAuthenticatedGuardToken.php @@ -0,0 +1,56 @@ + + */ +class NonAuthenticatedGuardToken extends AbstractToken +{ + 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 \Exception('The NonAuthenticatedGuardToken is *always* not authenticated'); + } +} From a0bceb43c9127912675fe4d72d3bae121281df3e Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Sun, 17 May 2015 17:10:29 -0400 Subject: [PATCH 03/24] adding Guard tests --- src/Symfony/Component/Security/phpunit.xml.dist | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Symfony/Component/Security/phpunit.xml.dist b/src/Symfony/Component/Security/phpunit.xml.dist index 7747b371c1713..c0dbb2d29933a 100644 --- a/src/Symfony/Component/Security/phpunit.xml.dist +++ b/src/Symfony/Component/Security/phpunit.xml.dist @@ -15,6 +15,7 @@ ./Acl/Tests/ ./Core/Tests/ ./Http/Tests/ + ./Guard/Tests/ From 873ed284d26b937efe4ead5c7b5abce13fea9d09 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Sun, 17 May 2015 17:27:01 -0400 Subject: [PATCH 04/24] Renaming the tokens to be clear they are "post" and "pre" auth - also adding an interface The reason is that the GuardAuthenticationProvider *must* respond to *all* tokens created by the system - both "pre auth" and "post auth" tokens. The reason is that if a "post auth" token becomes not authenticated (e.g. because the user changes between requests), then it may be passed to the provider system. If no providers respond (which was the case before this commit), then AuthenticationProviderManager throws an exception. The next commit will properly handle these "post auth" + "no-longer-authenticated" tokens, which should cause a log out. --- .../Security/Guard/AbstractGuardAuthenticator.php | 10 +++++----- .../Firewall/GuardAuthenticationListener.php | 4 ++-- .../Provider/GuardAuthenticationProvider.php | 15 +++++++++------ .../Firewall/GuardAuthenticationListenerTest.php | 4 ++-- .../Provider/GuardAuthenticationProviderTest.php | 12 ++++++------ .../Security/Guard/Token/GuardTokenInterface.php | 15 +++++++++++++++ ...Token.php => PostAuthenticationGuardToken.php} | 11 ++++------- ...dToken.php => PreAuthenticationGuardToken.php} | 4 ++-- 8 files changed, 45 insertions(+), 30 deletions(-) create mode 100644 src/Symfony/Component/Security/Guard/Token/GuardTokenInterface.php rename src/Symfony/Component/Security/Guard/Token/{GenericGuardToken.php => PostAuthenticationGuardToken.php} (87%) rename src/Symfony/Component/Security/Guard/Token/{NonAuthenticatedGuardToken.php => PreAuthenticationGuardToken.php} (88%) diff --git a/src/Symfony/Component/Security/Guard/AbstractGuardAuthenticator.php b/src/Symfony/Component/Security/Guard/AbstractGuardAuthenticator.php index ebd09bb73f20e..647cb0250cd62 100644 --- a/src/Symfony/Component/Security/Guard/AbstractGuardAuthenticator.php +++ b/src/Symfony/Component/Security/Guard/AbstractGuardAuthenticator.php @@ -3,26 +3,26 @@ namespace Symfony\Component\Security\Guard; use Symfony\Component\Security\Core\User\UserInterface; -use Symfony\Component\Security\Guard\Token\GenericGuardToken; +use Symfony\Component\Security\Guard\Token\PostAuthenticationGuardToken; /** - * An optional base class that creates a GenericGuardToken for you + * An optional base class that creates a PostAuthenticationGuardToken for you * * @author Ryan Weaver */ abstract class AbstractGuardAuthenticator implements GuardAuthenticatorInterface { /** - * Shortcut to create a GenericGuardToken for you, if you don't really + * 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 GenericGuardToken + * @return PostAuthenticationGuardToken */ public function createAuthenticatedToken(UserInterface $user, $providerKey) { - return new GenericGuardToken( + return new PostAuthenticationGuardToken( $user, $providerKey, $user->getRoles() diff --git a/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php b/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php index a53478a2b7c6a..6a4f09cfbdedd 100644 --- a/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php +++ b/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php @@ -6,7 +6,7 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\GetResponseEvent; use Symfony\Component\Security\Guard\GuardAuthenticatorHandler; -use Symfony\Component\Security\Guard\Token\NonAuthenticatedGuardToken; +use Symfony\Component\Security\Guard\Token\PreAuthenticationGuardToken; use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; use Symfony\Component\Security\Guard\GuardAuthenticatorInterface; use Psr\Log\LoggerInterface; @@ -86,7 +86,7 @@ private function executeGuardAuthenticator($uniqueGuardKey, GuardAuthenticatorIn } // create a token with the unique key, so that the provider knows which authenticator to use - $token = new NonAuthenticatedGuardToken($credentials, $uniqueGuardKey); + $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))); diff --git a/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php b/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php index 740f4c47c22e4..150943dffaa48 100644 --- a/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php +++ b/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php @@ -3,15 +3,18 @@ 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\AuthenticationException; use Symfony\Component\Security\Guard\GuardAuthenticatorInterface; -use Symfony\Component\Security\Guard\Token\NonAuthenticatedGuardToken; +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; /** - * Responsible for accepting the NonAuthenticatedGuardToken and calling + * Responsible for accepting the PreAuthenticationGuardToken and calling * the correct authenticator to retrieve the authenticated token * * @author Ryan Weaver @@ -43,12 +46,12 @@ public function __construct(array $guardAuthenticators, UserProviderInterface $u /** * Finds the correct authenticator for the token and calls it * - * @param NonAuthenticatedGuardToken $token + * @param GuardTokenInterface $token * @return TokenInterface */ public function authenticate(TokenInterface $token) { - if (!$token instanceof NonAuthenticatedGuardToken) { + if (!$this->supports($token)) { throw new \InvalidArgumentException('GuardAuthenticationProvider only supports NonAuthenticatedGuardToken'); } @@ -69,7 +72,7 @@ public function authenticate(TokenInterface $token) )); } - private function authenticateViaGuard(GuardAuthenticatorInterface $guardAuthenticator, NonAuthenticatedGuardToken $token) + private function authenticateViaGuard(GuardAuthenticatorInterface $guardAuthenticator, PreAuthenticationGuardToken $token) { // get the user from the GuardAuthenticator $user = $guardAuthenticator->authenticate($token->getCredentials(), $this->userProvider); @@ -101,6 +104,6 @@ private function authenticateViaGuard(GuardAuthenticatorInterface $guardAuthenti public function supports(TokenInterface $token) { - return $token instanceof NonAuthenticatedGuardToken; + return $token instanceof GuardTokenInterface; } } diff --git a/src/Symfony/Component/Security/Guard/Tests/Firewall/GuardAuthenticationListenerTest.php b/src/Symfony/Component/Security/Guard/Tests/Firewall/GuardAuthenticationListenerTest.php index b8939a93ddd37..2de60c1100938 100644 --- a/src/Symfony/Component/Security/Guard/Tests/Firewall/GuardAuthenticationListenerTest.php +++ b/src/Symfony/Component/Security/Guard/Tests/Firewall/GuardAuthenticationListenerTest.php @@ -14,7 +14,7 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Security\Guard\Firewall\GuardAuthenticationListener; -use Symfony\Component\Security\Guard\Token\NonAuthenticatedGuardToken; +use Symfony\Component\Security\Guard\Token\PreAuthenticationGuardToken; use Symfony\Component\Security\Core\Exception\AuthenticationException; /** @@ -44,7 +44,7 @@ public function testHandleSuccess() // a clone of the token that should be created internally $uniqueGuardKey = 'my_firewall_0'; - $nonAuthedToken = new NonAuthenticatedGuardToken($credentials, $uniqueGuardKey); + $nonAuthedToken = new PreAuthenticationGuardToken($credentials, $uniqueGuardKey); $this->authenticationManager ->expects($this->once()) diff --git a/src/Symfony/Component/Security/Guard/Tests/Provider/GuardAuthenticationProviderTest.php b/src/Symfony/Component/Security/Guard/Tests/Provider/GuardAuthenticationProviderTest.php index 0ef68186594bd..7df3ecb9d66a0 100644 --- a/src/Symfony/Component/Security/Guard/Tests/Provider/GuardAuthenticationProviderTest.php +++ b/src/Symfony/Component/Security/Guard/Tests/Provider/GuardAuthenticationProviderTest.php @@ -20,7 +20,7 @@ class GuardAuthenticationProviderTest extends \PHPUnit_Framework_TestCase { private $userProvider; private $userChecker; - private $nonAuthedToken; + private $preAuthenticationToken; public function testAuthenticate() { @@ -32,7 +32,7 @@ public function testAuthenticate() $authenticators = array($authenticatorA, $authenticatorB, $authenticatorC); // called 2 times - for authenticator A and B (stops on B because of match) - $this->nonAuthedToken->expects($this->exactly(2)) + $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')); @@ -41,7 +41,7 @@ public function testAuthenticate() 'username' => '_weaverryan_test_user', 'password' => 'guard_auth_ftw', ); - $this->nonAuthedToken->expects($this->once()) + $this->preAuthenticationToken->expects($this->once()) ->method('getCredentials') ->will($this->returnValue($enteredCredentials)); @@ -71,7 +71,7 @@ public function testAuthenticate() ->with($mockedUser); $provider = new GuardAuthenticationProvider($authenticators, $this->userProvider, $providerKey, $this->userChecker); - $actualAuthedToken = $provider->authenticate($this->nonAuthedToken); + $actualAuthedToken = $provider->authenticate($this->preAuthenticationToken); $this->assertSame($authedToken, $actualAuthedToken); } @@ -79,7 +79,7 @@ protected function setUp() { $this->userProvider = $this->getMock('Symfony\Component\Security\Core\User\UserProviderInterface'); $this->userChecker = $this->getMock('Symfony\Component\Security\Core\User\UserCheckerInterface'); - $this->nonAuthedToken = $this->getMockBuilder('Symfony\Component\Security\Guard\Token\NonAuthenticatedGuardToken') + $this->preAuthenticationToken = $this->getMockBuilder('Symfony\Component\Security\Guard\Token\PreAuthenticationGuardToken') ->disableOriginalConstructor() ->getMock(); } @@ -88,6 +88,6 @@ protected function tearDown() { $this->userProvider = null; $this->userChecker = null; - $this->nonAuthedToken = null; + $this->preAuthenticationToken = null; } } diff --git a/src/Symfony/Component/Security/Guard/Token/GuardTokenInterface.php b/src/Symfony/Component/Security/Guard/Token/GuardTokenInterface.php new file mode 100644 index 0000000000000..f4afb70a5df73 --- /dev/null +++ b/src/Symfony/Component/Security/Guard/Token/GuardTokenInterface.php @@ -0,0 +1,15 @@ + + */ +interface GuardTokenInterface +{ +} diff --git a/src/Symfony/Component/Security/Guard/Token/GenericGuardToken.php b/src/Symfony/Component/Security/Guard/Token/PostAuthenticationGuardToken.php similarity index 87% rename from src/Symfony/Component/Security/Guard/Token/GenericGuardToken.php rename to src/Symfony/Component/Security/Guard/Token/PostAuthenticationGuardToken.php index 869d0710c8cf7..5b7e20e3b7b2c 100644 --- a/src/Symfony/Component/Security/Guard/Token/GenericGuardToken.php +++ b/src/Symfony/Component/Security/Guard/Token/PostAuthenticationGuardToken.php @@ -7,17 +7,14 @@ use Symfony\Component\Security\Core\User\UserInterface; /** - * A generic token used by the AbstractGuardAuthenticator + * Used as an "authenticated" token, though it could be set to not-authenticated later. * - * This is meant to be used as an "authenticated" token, though it - * could be set to not-authenticated later. - * - * You're free to use this (it's simple) or use any other token for - * your authenticated token + * If you're using Guard authentication, you *must* use a class that implements + * GuardTokenInterface as your authenticated token (like this class). * * @author Ryan Weaver */ -class GenericGuardToken extends AbstractToken +class PostAuthenticationGuardToken extends AbstractToken implements GuardTokenInterface { private $providerKey; diff --git a/src/Symfony/Component/Security/Guard/Token/NonAuthenticatedGuardToken.php b/src/Symfony/Component/Security/Guard/Token/PreAuthenticationGuardToken.php similarity index 88% rename from src/Symfony/Component/Security/Guard/Token/NonAuthenticatedGuardToken.php rename to src/Symfony/Component/Security/Guard/Token/PreAuthenticationGuardToken.php index 28e21e0dee97d..1bfe6395b92fd 100644 --- a/src/Symfony/Component/Security/Guard/Token/NonAuthenticatedGuardToken.php +++ b/src/Symfony/Component/Security/Guard/Token/PreAuthenticationGuardToken.php @@ -13,7 +13,7 @@ * * @author Ryan Weaver */ -class NonAuthenticatedGuardToken extends AbstractToken +class PreAuthenticationGuardToken extends AbstractToken implements GuardTokenInterface { private $credentials; private $guardProviderKey; @@ -51,6 +51,6 @@ public function getCredentials() public function setAuthenticated($authenticated) { - throw new \Exception('The NonAuthenticatedGuardToken is *always* not authenticated'); + throw new \Exception('The PreAuthenticationGuardToken is *always* not authenticated'); } } From 180e2c78783428b463f59539e86ea2b1ffa26081 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Sun, 17 May 2015 17:29:55 -0400 Subject: [PATCH 05/24] Properly handles "post auth" tokens that have become not authenticated Here is the flow: A) You login using guard and are given a PostAuthGuardToken B) Your user changes between requests - AbstractToken::setUser() and hasUserChanged() - which results in the Token becoming "not authenticated" C) Something calls out to the security system, which then passes the no-longer-authed token back into the AuthenticationProviderManager D) Because the PostauthGuardToken implements GuardTokenInterface, the provider responds to it. But, seeing that this is a no-longer-authed PostAuthGuardToken, it returns an AnonymousToken, which triggers logout --- .../Provider/GuardAuthenticationProvider.php | 13 +++++++++++++ .../GuardAuthenticationProviderTest.php | 18 ++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php b/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php index 150943dffaa48..aa9a78dde8f01 100644 --- a/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php +++ b/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php @@ -55,6 +55,19 @@ public function authenticate(TokenInterface $token) throw new \InvalidArgumentException('GuardAuthenticationProvider only supports NonAuthenticatedGuardToken'); } + 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. + */ + + return new AnonymousToken($this->providerKey, 'anon.'); + } + // 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 diff --git a/src/Symfony/Component/Security/Guard/Tests/Provider/GuardAuthenticationProviderTest.php b/src/Symfony/Component/Security/Guard/Tests/Provider/GuardAuthenticationProviderTest.php index 7df3ecb9d66a0..99e9b5d2065f1 100644 --- a/src/Symfony/Component/Security/Guard/Tests/Provider/GuardAuthenticationProviderTest.php +++ b/src/Symfony/Component/Security/Guard/Tests/Provider/GuardAuthenticationProviderTest.php @@ -11,7 +11,9 @@ namespace Symfony\Component\Security\Guard\Tests\Provider; +use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken; use Symfony\Component\Security\Guard\Provider\GuardAuthenticationProvider; +use Symfony\Component\Security\Guard\Token\PostAuthenticationGuardToken; /** * @author Ryan Weaver @@ -75,6 +77,22 @@ public function testAuthenticate() $this->assertSame($authedToken, $actualAuthedToken); } + 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); + // this should return the anonymous user + $this->assertEquals(new AnonymousToken($providerKey, 'anon.'), $actualToken); + } + protected function setUp() { $this->userProvider = $this->getMock('Symfony\Component\Security\Core\User\UserProviderInterface'); From 6c180c78daefb713c6acba9bf0a0508696417499 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Sun, 17 May 2015 17:35:08 -0400 Subject: [PATCH 06/24] Adding an edge case - this should not happen anyways --- .../Security/Guard/GuardAuthenticatorInterface.php | 3 ++- .../Guard/Provider/GuardAuthenticationProvider.php | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/Security/Guard/GuardAuthenticatorInterface.php b/src/Symfony/Component/Security/Guard/GuardAuthenticatorInterface.php index dba8d09e224e4..f6405e9f30b60 100644 --- a/src/Symfony/Component/Security/Guard/GuardAuthenticatorInterface.php +++ b/src/Symfony/Component/Security/Guard/GuardAuthenticatorInterface.php @@ -8,6 +8,7 @@ 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; /** @@ -67,7 +68,7 @@ public function authenticate($credentials, UserProviderInterface $userProvider); * @see AbstractGuardAuthenticator * @param UserInterface $user * @param string $providerKey The provider (i.e. firewall) key - * @return TokenInterface + * @return GuardTokenInterface */ public function createAuthenticatedToken(UserInterface $user, $providerKey); diff --git a/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php b/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php index aa9a78dde8f01..524d7fe021d80 100644 --- a/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php +++ b/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php @@ -65,6 +65,13 @@ public function authenticate(TokenInterface $token) * we will return an AnonymousToken to accomplish that. */ + // this should never happen - but technically, the token is + // authenticated... so it could jsut be returned + if ($token->isAuthenticated()) { + return $token; + } + + // cause the logout - the token is not authenticated return new AnonymousToken($this->providerKey, 'anon.'); } From c73c32e674e6b45bd0910970f9bd7e17dd22064e Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Sun, 17 May 2015 18:16:06 -0400 Subject: [PATCH 07/24] Thanks fabbot! --- .../Factory/GuardAuthenticationFactory.php | 2 +- .../GuardAuthenticationFactoryTest.php | 13 ++++---- .../Guard/AbstractGuardAuthenticator.php | 7 +++-- .../Firewall/GuardAuthenticationListener.php | 22 ++++++------- .../Guard/GuardAuthenticatorHandler.php | 31 ++++++++++--------- .../Guard/GuardAuthenticatorInterface.php | 25 +++++++++------ .../Provider/GuardAuthenticationProvider.php | 18 +++++------ .../Guard/Token/GuardTokenInterface.php | 2 +- .../Token/PreAuthenticationGuardToken.php | 2 +- .../AuthenticationEntryPointInterface.php | 2 +- 10 files changed, 67 insertions(+), 57 deletions(-) diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/GuardAuthenticationFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/GuardAuthenticationFactory.php index e721ee77fee2e..f490a4827c9e6 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/GuardAuthenticationFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/GuardAuthenticationFactory.php @@ -8,7 +8,7 @@ use Symfony\Component\DependencyInjection\Reference; /** - * Configures the "guard" authentication provider key under a firewall + * Configures the "guard" authentication provider key under a firewall. * * @author Ryan Weaver */ diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Security/Factory/GuardAuthenticationFactoryTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Security/Factory/GuardAuthenticationFactoryTest.php index 1f107879db634..cfbc37859b97a 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Security/Factory/GuardAuthenticationFactoryTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/Security/Factory/GuardAuthenticationFactoryTest.php @@ -59,13 +59,13 @@ public function getValidConfigurationTests() array( 'authenticators' => array('authenticator1', 'authenticator2'), 'provider' => 'some_provider', - 'entry_point' => 'the_entry_point' + 'entry_point' => 'the_entry_point', ), array( 'authenticators' => array('authenticator1', 'authenticator2'), 'provider' => 'some_provider', - 'entry_point' => 'the_entry_point' - ) + 'entry_point' => 'the_entry_point', + ), ); // testing xml config fix: authenticator -> authenticators @@ -76,7 +76,7 @@ public function getValidConfigurationTests() array( 'authenticators' => array('authenticator1', 'authenticator2'), 'entry_point' => null, - ) + ), ); return $tests; @@ -88,7 +88,7 @@ public function getInvalidConfigurationTests() // testing not empty $tests[] = array( - array('authenticators' => array()) + array('authenticators' => array()), ); return $tests; @@ -108,7 +108,7 @@ public function testBasicCreate() $this->assertEquals(array( 'index_0' => array(new Reference('authenticator123')), 'index_1' => new Reference('my_user_provider'), - 'index_2' => 'my_firewall' + 'index_2' => 'my_firewall', ), $providerDefinition->getArguments()); $listenerDefinition = $container->getDefinition('security.authentication.listener.guard.my_firewall'); @@ -153,7 +153,6 @@ public function testMultipleAuthenticatorsRequiresEntryPoint() $this->executeCreate($config, null); } - public function testCreateWithEntryPoint() { // any existing default entry point is used diff --git a/src/Symfony/Component/Security/Guard/AbstractGuardAuthenticator.php b/src/Symfony/Component/Security/Guard/AbstractGuardAuthenticator.php index 647cb0250cd62..95a398bb4f656 100644 --- a/src/Symfony/Component/Security/Guard/AbstractGuardAuthenticator.php +++ b/src/Symfony/Component/Security/Guard/AbstractGuardAuthenticator.php @@ -6,7 +6,7 @@ use Symfony\Component\Security\Guard\Token\PostAuthenticationGuardToken; /** - * An optional base class that creates a PostAuthenticationGuardToken for you + * An optional base class that creates a PostAuthenticationGuardToken for you. * * @author Ryan Weaver */ @@ -14,10 +14,11 @@ 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 + * care about which authenticated token you're using. * * @param UserInterface $user - * @param string $providerKey + * @param string $providerKey + * * @return PostAuthenticationGuardToken */ public function createAuthenticatedToken(UserInterface $user, $providerKey) diff --git a/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php b/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php index 6a4f09cfbdedd..5c6de607a02f2 100644 --- a/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php +++ b/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php @@ -16,7 +16,7 @@ use Symfony\Component\Security\Http\RememberMe\RememberMeServicesInterface; /** - * Authentication listener for the "guard" system + * Authentication listener for the "guard" system. * * @author Ryan Weaver */ @@ -30,11 +30,11 @@ class GuardAuthenticationListener implements ListenerInterface 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 + * @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, $guardAuthenticators, LoggerInterface $logger = null) { @@ -50,7 +50,7 @@ public function __construct(GuardAuthenticatorHandler $guardHandler, Authenticat } /** - * Iterates over each authenticator to see if each wants to authenticate the request + * Iterates over each authenticator to see if each wants to authenticate the request. * * @param GetResponseEvent $event */ @@ -147,12 +147,12 @@ public function setRememberMeServices(RememberMeServicesInterface $rememberMeSer /** * Checks to see if remember me is supported in the authenticator and - * on the firewall. If it is, the RememberMeServicesInterface is notified + * on the firewall. If it is, the RememberMeServicesInterface is notified. * * @param GuardAuthenticatorInterface $guardAuthenticator - * @param Request $request - * @param TokenInterface $token - * @param Response $response + * @param Request $request + * @param TokenInterface $token + * @param Response $response */ private function triggerRememberMe(GuardAuthenticatorInterface $guardAuthenticator, Request $request, TokenInterface $token, Response $response = null) { diff --git a/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php b/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php index e574613f7a577..5be7eb5564c5a 100644 --- a/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php +++ b/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php @@ -13,7 +13,7 @@ use Symfony\Component\Security\Http\SecurityEvents; /** - * A utility class that does much of the *work* during the guard authentication process + * 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. @@ -33,10 +33,10 @@ public function __construct(TokenStorageInterface $tokenStorage, EventDispatcher } /** - * Authenticates the given token in the system + * Authenticates the given token in the system. * * @param TokenInterface $token - * @param Request $request + * @param Request $request */ public function authenticateWithToken(TokenInterface $token, Request $request) { @@ -49,12 +49,13 @@ public function authenticateWithToken(TokenInterface $token, Request $request) } /** - * Returns the "on success" response for the given GuardAuthenticator + * Returns the "on success" response for the given GuardAuthenticator. * - * @param TokenInterface $token - * @param Request $request + * @param TokenInterface $token + * @param Request $request * @param GuardAuthenticatorInterface $guardAuthenticator - * @param string $providerKey The provider (i.e. firewall) key + * @param string $providerKey The provider (i.e. firewall) key + * * @return null|Response */ public function handleAuthenticationSuccess(TokenInterface $token, Request $request, GuardAuthenticatorInterface $guardAuthenticator, $providerKey) @@ -75,12 +76,13 @@ public function handleAuthenticationSuccess(TokenInterface $token, Request $requ /** * Convenience method for authenticating the user and returning the - * Response *if any* for success + * Response *if any* for success. * - * @param UserInterface $user - * @param Request $request + * @param UserInterface $user + * @param Request $request * @param GuardAuthenticatorInterface $authenticator - * @param string $providerKey The provider (i.e. firewall) key + * @param string $providerKey The provider (i.e. firewall) key + * * @return Response|null */ public function authenticateUserAndHandleSuccess(UserInterface $user, Request $request, GuardAuthenticatorInterface $authenticator, $providerKey) @@ -96,11 +98,12 @@ public function authenticateUserAndHandleSuccess(UserInterface $user, Request $r /** * Handles an authentication failure and returns the Response for the - * GuardAuthenticator + * GuardAuthenticator. * - * @param AuthenticationException $authenticationException - * @param Request $request + * @param AuthenticationException $authenticationException + * @param Request $request * @param GuardAuthenticatorInterface $guardAuthenticator + * * @return null|Response */ public function handleAuthenticationFailure(AuthenticationException $authenticationException, Request $request, GuardAuthenticatorInterface $guardAuthenticator) diff --git a/src/Symfony/Component/Security/Guard/GuardAuthenticatorInterface.php b/src/Symfony/Component/Security/Guard/GuardAuthenticatorInterface.php index f6405e9f30b60..0b4306c9c0a89 100644 --- a/src/Symfony/Component/Security/Guard/GuardAuthenticatorInterface.php +++ b/src/Symfony/Component/Security/Guard/GuardAuthenticatorInterface.php @@ -12,7 +12,7 @@ use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface; /** - * The interface for all "guard" authenticators + * 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 @@ -41,39 +41,44 @@ interface GuardAuthenticatorInterface extends AuthenticationEntryPointInterface * return array('api_key' => $request->headers->get('X-API-TOKEN')); * * @param Request $request + * * @return mixed|null */ public function getCredentialsFromRequest(Request $request); /** * Return a UserInterface object based on the credentials OR throw - * an AuthenticationException + * an AuthenticationException. * * The *credentials* are the return value from getCredentialsFromRequest() * - * @param mixed $credentials + * @param mixed $credentials * @param UserProviderInterface $userProvider + * * @throws AuthenticationException + * * @return UserInterface */ public function authenticate($credentials, UserProviderInterface $userProvider); /** - * Create an authenticated token for the given 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 + * @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) + * 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. @@ -81,8 +86,9 @@ public function createAuthenticatedToken(UserInterface $user, $providerKey); * 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 Request $request * @param AuthenticationException $exception + * * @return Response|null */ public function onAuthenticationFailure(Request $request, AuthenticationException $exception); @@ -96,9 +102,10 @@ public function onAuthenticationFailure(Request $request, AuthenticationExceptio * 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 Request $request * @param TokenInterface $token - * @param string $providerKey The provider (i.e. firewall) key + * @param string $providerKey The provider (i.e. firewall) key + * * @return Response|null */ public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey); diff --git a/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php b/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php index 524d7fe021d80..c21af4c49c2a9 100644 --- a/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php +++ b/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php @@ -4,7 +4,6 @@ use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface; use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken; -use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Guard\GuardAuthenticatorInterface; use Symfony\Component\Security\Guard\Token\GuardTokenInterface; use Symfony\Component\Security\Guard\Token\PreAuthenticationGuardToken; @@ -15,7 +14,7 @@ /** * Responsible for accepting the PreAuthenticationGuardToken and calling - * the correct authenticator to retrieve the authenticated token + * the correct authenticator to retrieve the authenticated token. * * @author Ryan Weaver */ @@ -30,10 +29,10 @@ class GuardAuthenticationProvider implements AuthenticationProviderInterface 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 + * @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) { @@ -44,9 +43,10 @@ public function __construct(array $guardAuthenticators, UserProviderInterface $u } /** - * Finds the correct authenticator for the token and calls it + * Finds the correct authenticator for the token and calls it. * * @param GuardTokenInterface $token + * * @return TokenInterface */ public function authenticate(TokenInterface $token) @@ -66,7 +66,7 @@ public function authenticate(TokenInterface $token) */ // this should never happen - but technically, the token is - // authenticated... so it could jsut be returned + // authenticated... so it could just be returned if ($token->isAuthenticated()) { return $token; } @@ -106,7 +106,7 @@ private function authenticateViaGuard(GuardAuthenticatorInterface $guardAuthenti } // check the AdvancedUserInterface methods! - $this->userChecker->checkPreAuth($user);; + $this->userChecker->checkPreAuth($user); $this->userChecker->checkPostAuth($user); // turn the UserInterface into a TokenInterface diff --git a/src/Symfony/Component/Security/Guard/Token/GuardTokenInterface.php b/src/Symfony/Component/Security/Guard/Token/GuardTokenInterface.php index f4afb70a5df73..de3d08601b600 100644 --- a/src/Symfony/Component/Security/Guard/Token/GuardTokenInterface.php +++ b/src/Symfony/Component/Security/Guard/Token/GuardTokenInterface.php @@ -3,7 +3,7 @@ namespace Symfony\Component\Security\Guard\Token; /** - * An empty interface that both guard tokens implement + * An empty interface that both guard tokens implement. * * This interface is used by the GuardAuthenticationProvider to know * that a token belongs to its system. diff --git a/src/Symfony/Component/Security/Guard/Token/PreAuthenticationGuardToken.php b/src/Symfony/Component/Security/Guard/Token/PreAuthenticationGuardToken.php index 1bfe6395b92fd..8950f93533ca6 100644 --- a/src/Symfony/Component/Security/Guard/Token/PreAuthenticationGuardToken.php +++ b/src/Symfony/Component/Security/Guard/Token/PreAuthenticationGuardToken.php @@ -5,7 +5,7 @@ use Symfony\Component\Security\Core\Authentication\Token\AbstractToken; /** - * The token used by the guard auth system before authentication + * 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 diff --git a/src/Symfony/Component/Security/Http/EntryPoint/AuthenticationEntryPointInterface.php b/src/Symfony/Component/Security/Http/EntryPoint/AuthenticationEntryPointInterface.php index 2aa7a03e9c75c..df777f688cdff 100644 --- a/src/Symfony/Component/Security/Http/EntryPoint/AuthenticationEntryPointInterface.php +++ b/src/Symfony/Component/Security/Http/EntryPoint/AuthenticationEntryPointInterface.php @@ -24,7 +24,7 @@ interface AuthenticationEntryPointInterface { /** - * Returns a response that directs the user to authenticate + * 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 From eb158cbdb3521220794fad066ad73d5ceecbeddc Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Mon, 18 May 2015 08:48:41 -0400 Subject: [PATCH 08/24] Updating interface method per suggestion - makes sense to me, Request is redundant --- .../Guard/Firewall/GuardAuthenticationListener.php | 4 ++-- .../Security/Guard/GuardAuthenticatorInterface.php | 4 ++-- .../Tests/Firewall/GuardAuthenticationListenerTest.php | 10 +++++----- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php b/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php index 5c6de607a02f2..9e2a8868f2679 100644 --- a/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php +++ b/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php @@ -74,11 +74,11 @@ private function executeGuardAuthenticator($uniqueGuardKey, GuardAuthenticatorIn $request = $event->getRequest(); try { if (null !== $this->logger) { - $this->logger->info('Calling getCredentialsFromRequest on guard configurator', array('firewall_key' => $this->providerKey, 'authenticator' => get_class($guardAuthenticator))); + $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->getCredentialsFromRequest($request); + $credentials = $guardAuthenticator->getCredentials($request); // allow null to be returned to skip authentication if (null === $credentials) { diff --git a/src/Symfony/Component/Security/Guard/GuardAuthenticatorInterface.php b/src/Symfony/Component/Security/Guard/GuardAuthenticatorInterface.php index 0b4306c9c0a89..4b1e4073b9067 100644 --- a/src/Symfony/Component/Security/Guard/GuardAuthenticatorInterface.php +++ b/src/Symfony/Component/Security/Guard/GuardAuthenticatorInterface.php @@ -44,13 +44,13 @@ interface GuardAuthenticatorInterface extends AuthenticationEntryPointInterface * * @return mixed|null */ - public function getCredentialsFromRequest(Request $request); + public function getCredentials(Request $request); /** * Return a UserInterface object based on the credentials OR throw * an AuthenticationException. * - * The *credentials* are the return value from getCredentialsFromRequest() + * The *credentials* are the return value from getCredentials() * * @param mixed $credentials * @param UserProviderInterface $userProvider diff --git a/src/Symfony/Component/Security/Guard/Tests/Firewall/GuardAuthenticationListenerTest.php b/src/Symfony/Component/Security/Guard/Tests/Firewall/GuardAuthenticationListenerTest.php index 2de60c1100938..ab7829223edfc 100644 --- a/src/Symfony/Component/Security/Guard/Tests/Firewall/GuardAuthenticationListenerTest.php +++ b/src/Symfony/Component/Security/Guard/Tests/Firewall/GuardAuthenticationListenerTest.php @@ -38,7 +38,7 @@ public function testHandleSuccess() $credentials = array('username' => 'weaverryan', 'password' => 'all_your_base'); $authenticator ->expects($this->once()) - ->method('getCredentialsFromRequest') + ->method('getCredentials') ->with($this->equalTo($this->request)) ->will($this->returnValue($credentials)); @@ -87,7 +87,7 @@ public function testHandleSuccessWithRememberMe() $authenticator ->expects($this->once()) - ->method('getCredentialsFromRequest') + ->method('getCredentials') ->with($this->equalTo($this->request)) ->will($this->returnValue(array('username' => 'anything_not_empty'))); @@ -130,7 +130,7 @@ public function testHandleCatchesAuthenticationException() $authException = new AuthenticationException('Get outta here crazy user with a bad password!'); $authenticator ->expects($this->once()) - ->method('getCredentialsFromRequest') + ->method('getCredentials') ->will($this->throwException($authException)); // this is not called @@ -162,11 +162,11 @@ public function testReturnNullToSkipAuth() $authenticatorA ->expects($this->once()) - ->method('getCredentialsFromRequest') + ->method('getCredentials') ->will($this->returnValue(null)); $authenticatorB ->expects($this->once()) - ->method('getCredentialsFromRequest') + ->method('getCredentials') ->will($this->returnValue(null)); // this is not called From d6937218be82386d42ad6a135de68fb774757e01 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Mon, 18 May 2015 08:51:00 -0400 Subject: [PATCH 09/24] Adding periods at the end of exceptions, and changing one class name to LogicException thanks to @iltar --- .../Guard/Firewall/GuardAuthenticationListener.php | 2 +- .../Security/Guard/GuardAuthenticatorHandler.php | 4 ++-- .../Guard/Provider/GuardAuthenticationProvider.php | 8 ++++---- .../Security/Guard/Token/PreAuthenticationGuardToken.php | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php b/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php index 9e2a8868f2679..d3be9abbe9455 100644 --- a/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php +++ b/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php @@ -162,7 +162,7 @@ private function triggerRememberMe(GuardAuthenticatorInterface $guardAuthenticat 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))); + $this->logger->info('Remember me skipped: it is not configured for the firewall.', array('authenticator' => get_class($guardAuthenticator))); } return; diff --git a/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php b/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php index 5be7eb5564c5a..465b45a957fa0 100644 --- a/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php +++ b/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php @@ -68,7 +68,7 @@ public function handleAuthenticationSuccess(TokenInterface $token, Request $requ } throw new \UnexpectedValueException(sprintf( - 'The %s::onAuthenticationSuccess method must return null or a Response object. You returned %s', + '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) )); @@ -117,7 +117,7 @@ public function handleAuthenticationFailure(AuthenticationException $authenticat } throw new \UnexpectedValueException(sprintf( - 'The %s::onAuthenticationFailure method must return null or a Response object. You returned %s', + '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) )); diff --git a/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php b/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php index c21af4c49c2a9..4fefee3b895c6 100644 --- a/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php +++ b/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php @@ -52,7 +52,7 @@ public function __construct(array $guardAuthenticators, UserProviderInterface $u public function authenticate(TokenInterface $token) { if (!$this->supports($token)) { - throw new \InvalidArgumentException('GuardAuthenticationProvider only supports NonAuthenticatedGuardToken'); + throw new \InvalidArgumentException('GuardAuthenticationProvider only supports GuardTokenInterface.'); } if (!$token instanceof PreAuthenticationGuardToken) { @@ -87,7 +87,7 @@ public function authenticate(TokenInterface $token) } throw new \LogicException(sprintf( - 'The correct GuardAuthenticator could not be found for unique key "%s". The listener and provider should be passed the same list of authenticators!?', + 'The correct GuardAuthenticator could not be found for unique key "%s". The listener and provider should be passed the same list of authenticators.', $token->getGuardProviderKey() )); } @@ -99,7 +99,7 @@ private function authenticateViaGuard(GuardAuthenticatorInterface $guardAuthenti if (!$user instanceof UserInterface) { throw new \UnexpectedValueException(sprintf( - 'The %s::authenticate method must return a UserInterface. You returned %s', + 'The %s::authenticate method must return a UserInterface. You returned %s.', get_class($guardAuthenticator), is_object($user) ? get_class($user) : gettype($user) )); @@ -113,7 +113,7 @@ private function authenticateViaGuard(GuardAuthenticatorInterface $guardAuthenti $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', + 'The %s::createAuthenticatedToken method must return a TokenInterface. You returned %s.', get_class($guardAuthenticator), is_object($authenticatedToken) ? get_class($authenticatedToken) : gettype($authenticatedToken) )); diff --git a/src/Symfony/Component/Security/Guard/Token/PreAuthenticationGuardToken.php b/src/Symfony/Component/Security/Guard/Token/PreAuthenticationGuardToken.php index 8950f93533ca6..cc52ec2b8acbd 100644 --- a/src/Symfony/Component/Security/Guard/Token/PreAuthenticationGuardToken.php +++ b/src/Symfony/Component/Security/Guard/Token/PreAuthenticationGuardToken.php @@ -51,6 +51,6 @@ public function getCredentials() public function setAuthenticated($authenticated) { - throw new \Exception('The PreAuthenticationGuardToken is *always* not authenticated'); + throw new \LogicException('The PreAuthenticationGuardToken is *always* not authenticated.'); } } From 6edb9e1b06ade62511798bf3d9cfa7de4424fcab Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Mon, 18 May 2015 08:52:38 -0400 Subject: [PATCH 10/24] Tweaking docblock on interface thanks to @iltar --- .../Component/Security/Guard/Token/GuardTokenInterface.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Component/Security/Guard/Token/GuardTokenInterface.php b/src/Symfony/Component/Security/Guard/Token/GuardTokenInterface.php index de3d08601b600..8ad57b3b2ab1a 100644 --- a/src/Symfony/Component/Security/Guard/Token/GuardTokenInterface.php +++ b/src/Symfony/Component/Security/Guard/Token/GuardTokenInterface.php @@ -3,10 +3,11 @@ namespace Symfony\Component\Security\Guard\Token; /** - * An empty interface that both guard tokens implement. + * A marker interface that both guard tokens implement. * - * This interface is used by the GuardAuthenticationProvider to know - * that a token belongs to its system. + * Any tokens passed to GuardAuthenticationProvider (i.e. any tokens that + * are handled by the guard auth system) must implement this + * interface. * * @author Ryan Weaver */ From ffdbc665347c935be76c537021a86fbfa77e658b Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Mon, 18 May 2015 09:14:21 -0400 Subject: [PATCH 11/24] Splitting the getting of the user and checking credentials into two steps This looks like a subjective change (one more method, but the method implementations are simpler), but it wasn't. The problem was that the UserChecker checkPreAuth should happen *after* we get the user, but *before* the credentials are checked, and that wasn't possible before this change. Now it is. --- .../Guard/GuardAuthenticatorInterface.php | 24 +++++++++++++++---- .../Provider/GuardAuthenticationProvider.php | 17 ++++++++++--- .../GuardAuthenticationProviderTest.php | 12 ++++++---- 3 files changed, 41 insertions(+), 12 deletions(-) diff --git a/src/Symfony/Component/Security/Guard/GuardAuthenticatorInterface.php b/src/Symfony/Component/Security/Guard/GuardAuthenticatorInterface.php index 4b1e4073b9067..a7273714099e7 100644 --- a/src/Symfony/Component/Security/Guard/GuardAuthenticatorInterface.php +++ b/src/Symfony/Component/Security/Guard/GuardAuthenticatorInterface.php @@ -27,7 +27,7 @@ interface GuardAuthenticatorInterface extends AuthenticationEntryPointInterface * 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 authenticate() + * Whatever value you return here will be passed to getUser() and checkCredentials() * * For example, for a form login, you might: * @@ -47,19 +47,33 @@ interface GuardAuthenticatorInterface extends AuthenticationEntryPointInterface public function getCredentials(Request $request); /** - * Return a UserInterface object based on the credentials OR throw - * an AuthenticationException. + * 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 + * @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 + * @return void */ - public function authenticate($credentials, UserProviderInterface $userProvider); + public function checkCredentials($credentials, UserInterface $user); /** * Create an authenticated token for the given user. diff --git a/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php b/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php index 4fefee3b895c6..eb9ef31e43797 100644 --- a/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php +++ b/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php @@ -4,6 +4,7 @@ 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; @@ -95,18 +96,28 @@ public function authenticate(TokenInterface $token) private function authenticateViaGuard(GuardAuthenticatorInterface $guardAuthenticator, PreAuthenticationGuardToken $token) { // get the user from the GuardAuthenticator - $user = $guardAuthenticator->authenticate($token->getCredentials(), $this->userProvider); + $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::authenticate method must return a UserInterface. You returned %s.', + 'The %s::getUser method must return a UserInterface. You returned %s.', get_class($guardAuthenticator), is_object($user) ? get_class($user) : gettype($user) )); } - // check the AdvancedUserInterface methods! + // check the preAuth UserChecker $this->userChecker->checkPreAuth($user); + // check the credentials + $guardAuthenticator->checkCredentials($token->getCredentials(), $user); + // check the postAuth UserChecker $this->userChecker->checkPostAuth($user); // turn the UserInterface into a TokenInterface diff --git a/src/Symfony/Component/Security/Guard/Tests/Provider/GuardAuthenticationProviderTest.php b/src/Symfony/Component/Security/Guard/Tests/Provider/GuardAuthenticationProviderTest.php index 99e9b5d2065f1..24c946d0e1647 100644 --- a/src/Symfony/Component/Security/Guard/Tests/Provider/GuardAuthenticationProviderTest.php +++ b/src/Symfony/Component/Security/Guard/Tests/Provider/GuardAuthenticationProviderTest.php @@ -43,21 +43,25 @@ public function testAuthenticate() 'username' => '_weaverryan_test_user', 'password' => 'guard_auth_ftw', ); - $this->preAuthenticationToken->expects($this->once()) + $this->preAuthenticationToken->expects($this->atLeastOnce()) ->method('getCredentials') ->will($this->returnValue($enteredCredentials)); // authenticators A and C are never called $authenticatorA->expects($this->never()) - ->method('authenticate'); + ->method('getUser'); $authenticatorC->expects($this->never()) - ->method('authenticate'); + ->method('getUser'); $mockedUser = $this->getMock('Symfony\Component\Security\Core\User\UserInterface'); $authenticatorB->expects($this->once()) - ->method('authenticate') + ->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') From 7de05be3f6396e8893c7204f1dfdbec905063dfa Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Mon, 18 May 2015 09:41:05 -0400 Subject: [PATCH 12/24] A few more changes thanks to @iltar --- .../Guard/Firewall/GuardAuthenticationListener.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php b/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php index d3be9abbe9455..fc5706d5716ab 100644 --- a/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php +++ b/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php @@ -36,7 +36,7 @@ class GuardAuthenticationListener implements ListenerInterface * @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, $guardAuthenticators, LoggerInterface $logger = null) + public function __construct(GuardAuthenticatorHandler $guardHandler, AuthenticationManagerInterface $authenticationManager, $providerKey, array $guardAuthenticators, LoggerInterface $logger = null) { if (empty($providerKey)) { throw new \InvalidArgumentException('$providerKey must not be empty.'); @@ -57,7 +57,7 @@ public function __construct(GuardAuthenticatorHandler $guardHandler, Authenticat 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))); + $this->logger->info('Checking for guard authentication credentials.', array('firewall_key' => $this->providerKey, 'authenticators' => count($this->guardAuthenticators))); } foreach ($this->guardAuthenticators as $key => $guardAuthenticator) { @@ -121,13 +121,13 @@ private function executeGuardAuthenticator($uniqueGuardKey, GuardAuthenticatorIn $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))); + $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))); + $this->logger->info('Guard authenticator set no success response: request continues.', array('authenticator' => get_class($guardAuthenticator))); } } From 7a94994e8e5116ccf935aa25d44dfe9c67e9b63c Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Mon, 18 May 2015 09:45:17 -0400 Subject: [PATCH 13/24] Thanks again fabbot! --- .../Security/Guard/GuardAuthenticatorInterface.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/Security/Guard/GuardAuthenticatorInterface.php b/src/Symfony/Component/Security/Guard/GuardAuthenticatorInterface.php index a7273714099e7..e55f178e9d374 100644 --- a/src/Symfony/Component/Security/Guard/GuardAuthenticatorInterface.php +++ b/src/Symfony/Component/Security/Guard/GuardAuthenticatorInterface.php @@ -47,7 +47,7 @@ interface GuardAuthenticatorInterface extends AuthenticationEntryPointInterface public function getCredentials(Request $request); /** - * Return a UserInterface object based on the credentials + * Return a UserInterface object based on the credentials. * * The *credentials* are the return value from getCredentials() * @@ -64,14 +64,14 @@ public function getCredentials(Request $request); public function getUser($credentials, UserProviderInterface $userProvider); /** - * Throw an AuthenticationException if the credentials are invalid + * Throw an AuthenticationException if the credentials are invalid. * * The *credentials* are the return value from getCredentials() * - * @param mixed $credentials + * @param mixed $credentials * @param UserInterface $user + * * @throws AuthenticationException - * @return void */ public function checkCredentials($credentials, UserInterface $user); From 81432f90446cea79704ba9b38b0c0e484804fc82 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Mon, 25 May 2015 19:51:12 -0400 Subject: [PATCH 14/24] Adding missing factory registration --- src/Symfony/Bundle/SecurityBundle/SecurityBundle.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php b/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php index 72f7b68de959d..f2dcab1061d1f 100644 --- a/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php +++ b/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php @@ -23,6 +23,7 @@ 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 @@ public function build(ContainerBuilder $container) $extension->addSecurityListenerFactory(new RemoteUserFactory()); $extension->addSecurityListenerFactory(new SimplePreAuthenticationFactory()); $extension->addSecurityListenerFactory(new SimpleFormFactory()); + $extension->addSecurityListenerFactory(new GuardAuthenticationFactory()); $extension->addUserProviderFactory(new InMemoryFactory()); $container->addCompilerPass(new AddSecurityVotersPass()); From 293c8a1775af3d78fabc10139578de5642bc2ef1 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Sun, 20 Sep 2015 19:37:42 -0400 Subject: [PATCH 15/24] meaningless author and license changes --- .../Security/Factory/GuardAuthenticationFactory.php | 11 ++++++++++- .../Security/Guard/AbstractGuardAuthenticator.php | 13 +++++++++++-- .../Guard/Firewall/GuardAuthenticationListener.php | 13 +++++++++++-- .../Security/Guard/GuardAuthenticatorHandler.php | 13 +++++++++++-- .../Security/Guard/GuardAuthenticatorInterface.php | 11 ++++++++++- .../Guard/Provider/GuardAuthenticationProvider.php | 11 ++++++++++- .../Security/Guard/Token/GuardTokenInterface.php | 11 ++++++++++- .../Guard/Token/PostAuthenticationGuardToken.php | 11 ++++++++++- .../Guard/Token/PreAuthenticationGuardToken.php | 11 ++++++++++- 9 files changed, 93 insertions(+), 12 deletions(-) diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/GuardAuthenticationFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/GuardAuthenticationFactory.php index f490a4827c9e6..0d58c39b058e0 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/GuardAuthenticationFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/GuardAuthenticationFactory.php @@ -1,5 +1,14 @@ + * + * 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; @@ -10,7 +19,7 @@ /** * Configures the "guard" authentication provider key under a firewall. * - * @author Ryan Weaver + * @author Ryan Weaver */ class GuardAuthenticationFactory implements SecurityFactoryInterface { diff --git a/src/Symfony/Component/Security/Guard/AbstractGuardAuthenticator.php b/src/Symfony/Component/Security/Guard/AbstractGuardAuthenticator.php index 95a398bb4f656..609d772e194b4 100644 --- a/src/Symfony/Component/Security/Guard/AbstractGuardAuthenticator.php +++ b/src/Symfony/Component/Security/Guard/AbstractGuardAuthenticator.php @@ -1,5 +1,14 @@ + * + * 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; @@ -8,7 +17,7 @@ /** * An optional base class that creates a PostAuthenticationGuardToken for you. * - * @author Ryan Weaver + * @author Ryan Weaver */ abstract class AbstractGuardAuthenticator implements GuardAuthenticatorInterface { @@ -29,4 +38,4 @@ public function createAuthenticatedToken(UserInterface $user, $providerKey) $user->getRoles() ); } -} \ No newline at end of file +} diff --git a/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php b/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php index fc5706d5716ab..38ca0a06657bc 100644 --- a/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php +++ b/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php @@ -1,5 +1,14 @@ + * + * 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; @@ -18,7 +27,7 @@ /** * Authentication listener for the "guard" system. * - * @author Ryan Weaver + * @author Ryan Weaver */ class GuardAuthenticationListener implements ListenerInterface { @@ -177,4 +186,4 @@ private function triggerRememberMe(GuardAuthenticatorInterface $guardAuthenticat $this->rememberMeServices->loginSuccess($request, $response, $token); } -} \ No newline at end of file +} diff --git a/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php b/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php index 465b45a957fa0..c588d688b7fb1 100644 --- a/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php +++ b/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php @@ -1,5 +1,14 @@ + * + * 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; @@ -18,7 +27,7 @@ * 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 + * @author Ryan Weaver */ class GuardAuthenticatorHandler { @@ -122,4 +131,4 @@ public function handleAuthenticationFailure(AuthenticationException $authenticat is_object($response) ? get_class($response) : gettype($response) )); } -} \ No newline at end of file +} diff --git a/src/Symfony/Component/Security/Guard/GuardAuthenticatorInterface.php b/src/Symfony/Component/Security/Guard/GuardAuthenticatorInterface.php index e55f178e9d374..2db313cc2bd6e 100644 --- a/src/Symfony/Component/Security/Guard/GuardAuthenticatorInterface.php +++ b/src/Symfony/Component/Security/Guard/GuardAuthenticatorInterface.php @@ -1,5 +1,14 @@ + * + * 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; @@ -18,7 +27,7 @@ * process to give you the power to control most parts of the process from * one location. * - * @author Ryan Weaver + * @author Ryan Weaver */ interface GuardAuthenticatorInterface extends AuthenticationEntryPointInterface { diff --git a/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php b/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php index eb9ef31e43797..7610f490cfa0c 100644 --- a/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php +++ b/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php @@ -1,5 +1,14 @@ + * + * 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; @@ -17,7 +26,7 @@ * Responsible for accepting the PreAuthenticationGuardToken and calling * the correct authenticator to retrieve the authenticated token. * - * @author Ryan Weaver + * @author Ryan Weaver */ class GuardAuthenticationProvider implements AuthenticationProviderInterface { diff --git a/src/Symfony/Component/Security/Guard/Token/GuardTokenInterface.php b/src/Symfony/Component/Security/Guard/Token/GuardTokenInterface.php index 8ad57b3b2ab1a..f0db250dd38f4 100644 --- a/src/Symfony/Component/Security/Guard/Token/GuardTokenInterface.php +++ b/src/Symfony/Component/Security/Guard/Token/GuardTokenInterface.php @@ -1,5 +1,14 @@ + * + * 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; /** @@ -9,7 +18,7 @@ * are handled by the guard auth system) must implement this * interface. * - * @author Ryan Weaver + * @author Ryan Weaver */ interface GuardTokenInterface { diff --git a/src/Symfony/Component/Security/Guard/Token/PostAuthenticationGuardToken.php b/src/Symfony/Component/Security/Guard/Token/PostAuthenticationGuardToken.php index 5b7e20e3b7b2c..9657f8a524345 100644 --- a/src/Symfony/Component/Security/Guard/Token/PostAuthenticationGuardToken.php +++ b/src/Symfony/Component/Security/Guard/Token/PostAuthenticationGuardToken.php @@ -1,5 +1,14 @@ + * + * 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; @@ -12,7 +21,7 @@ * If you're using Guard authentication, you *must* use a class that implements * GuardTokenInterface as your authenticated token (like this class). * - * @author Ryan Weaver + * @author Ryan Weaver n@gmail.com> */ class PostAuthenticationGuardToken extends AbstractToken implements GuardTokenInterface { diff --git a/src/Symfony/Component/Security/Guard/Token/PreAuthenticationGuardToken.php b/src/Symfony/Component/Security/Guard/Token/PreAuthenticationGuardToken.php index cc52ec2b8acbd..b694541bde22d 100644 --- a/src/Symfony/Component/Security/Guard/Token/PreAuthenticationGuardToken.php +++ b/src/Symfony/Component/Security/Guard/Token/PreAuthenticationGuardToken.php @@ -1,5 +1,14 @@ + * + * 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; @@ -11,7 +20,7 @@ * immediately by the GuardAuthenticationProvider. If authentication is * successful, a different authenticated token is returned * - * @author Ryan Weaver + * @author Ryan Weaver */ class PreAuthenticationGuardToken extends AbstractToken implements GuardTokenInterface { From 05017615048a99f0ade9ce2e465ecf1c2b50d2cb Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Sun, 20 Sep 2015 19:37:51 -0400 Subject: [PATCH 16/24] Allowing for other authenticators to be checked If you have 2 firewalls, 2 GuardAuthenticationProviders are still created, so we need to be able to run through both of them. --- .../Security/Guard/Provider/GuardAuthenticationProvider.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php b/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php index 7610f490cfa0c..18e46cf8e286a 100644 --- a/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php +++ b/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php @@ -96,10 +96,8 @@ public function authenticate(TokenInterface $token) } } - throw new \LogicException(sprintf( - 'The correct GuardAuthenticator could not be found for unique key "%s". The listener and provider should be passed the same list of authenticators.', - $token->getGuardProviderKey() - )); + // 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) From 31f9caef000baa133515fe379f4cc203ab2067e7 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Sun, 20 Sep 2015 19:38:23 -0400 Subject: [PATCH 17/24] Adding a base class to assist with form login authentication --- .../AbstractFormLoginAuthenticator.php | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 src/Symfony/Component/Security/Guard/Authenticator/AbstractFormLoginAuthenticator.php diff --git a/src/Symfony/Component/Security/Guard/Authenticator/AbstractFormLoginAuthenticator.php b/src/Symfony/Component/Security/Guard/Authenticator/AbstractFormLoginAuthenticator.php new file mode 100644 index 0000000000000..c972ed6f15612 --- /dev/null +++ b/src/Symfony/Component/Security/Guard/Authenticator/AbstractFormLoginAuthenticator.php @@ -0,0 +1,104 @@ + + * + * 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\Encoder\UserPasswordEncoderInterface; +use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Symfony\Component\Security\Core\Exception\BadCredentialsException; +use Symfony\Component\Security\Core\Security; +use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Security\Core\User\UserProviderInterface; + +/** + * A base class to make form login authentication easier! + * + * @author Ryan Weaver + */ +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); + } +} From c9d94309131096a4423ee157daf23de440acee7d Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Sun, 20 Sep 2015 19:43:59 -0400 Subject: [PATCH 18/24] Adding logging on this step and switching the order - not for any huge reason --- .../Guard/Firewall/GuardAuthenticationListener.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php b/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php index 38ca0a06657bc..26d5852be49ac 100644 --- a/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php +++ b/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php @@ -165,13 +165,17 @@ public function setRememberMeServices(RememberMeServicesInterface $rememberMeSer */ private function triggerRememberMe(GuardAuthenticatorInterface $guardAuthenticator, Request $request, TokenInterface $token, Response $response = null) { - if (!$guardAuthenticator->supportsRememberMe()) { + 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 (null === $this->rememberMeServices) { + if (!$guardAuthenticator->supportsRememberMe()) { if (null !== $this->logger) { - $this->logger->info('Remember me skipped: it is not configured for the firewall.', array('authenticator' => get_class($guardAuthenticator))); + $this->logger->info('Remember me skipped: your authenticator does not support it.', array('authenticator' => get_class($guardAuthenticator))); } return; From 396a1622dc69b53c552659a4839e4f22834976a8 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Sun, 20 Sep 2015 19:51:09 -0400 Subject: [PATCH 19/24] Tweaks thanks to Wouter --- .../Guard/Provider/GuardAuthenticationProvider.php | 7 ++----- .../Security/Guard/Token/PreAuthenticationGuardToken.php | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php b/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php index 18e46cf8e286a..646eea9b59a6f 100644 --- a/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php +++ b/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php @@ -114,24 +114,21 @@ private function authenticateViaGuard(GuardAuthenticatorInterface $guardAuthenti if (!$user instanceof UserInterface) { throw new \UnexpectedValueException(sprintf( - 'The %s::getUser method must return a UserInterface. You returned %s.', + 'The %s::getUser() method must return a UserInterface. You returned %s.', get_class($guardAuthenticator), is_object($user) ? get_class($user) : gettype($user) )); } - // check the preAuth UserChecker $this->userChecker->checkPreAuth($user); - // check the credentials $guardAuthenticator->checkCredentials($token->getCredentials(), $user); - // check the postAuth UserChecker $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.', + 'The %s::createAuthenticatedToken() method must return a TokenInterface. You returned %s.', get_class($guardAuthenticator), is_object($authenticatedToken) ? get_class($authenticatedToken) : gettype($authenticatedToken) )); diff --git a/src/Symfony/Component/Security/Guard/Token/PreAuthenticationGuardToken.php b/src/Symfony/Component/Security/Guard/Token/PreAuthenticationGuardToken.php index b694541bde22d..abbe985c9e0b2 100644 --- a/src/Symfony/Component/Security/Guard/Token/PreAuthenticationGuardToken.php +++ b/src/Symfony/Component/Security/Guard/Token/PreAuthenticationGuardToken.php @@ -60,6 +60,6 @@ public function getCredentials() public function setAuthenticated($authenticated) { - throw new \LogicException('The PreAuthenticationGuardToken is *always* not authenticated.'); + throw new \LogicException('The PreAuthenticationGuardToken is *never* authenticated.'); } } From 302235e6e59319c14ce0f066398218f5d5f8accd Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Sun, 20 Sep 2015 20:11:34 -0400 Subject: [PATCH 20/24] Fixing a bug where having an authentication failure would log you out. This solution is a copy of what AbstractAuthenticationListener does. Scenario: 1) Login 2) Go back to the log in page 3) Put in a bad user/pass You *should* still be logged in after a failed attempt. This commit gives that behavior. --- .../Firewall/GuardAuthenticationListener.php | 2 +- .../Guard/GuardAuthenticatorHandler.php | 9 +++- .../GuardAuthenticationListenerTest.php | 2 +- .../Tests/GuardAuthenticatorHandlerTest.php | 50 +++++++++++++++++-- 4 files changed, 55 insertions(+), 8 deletions(-) diff --git a/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php b/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php index 26d5852be49ac..6140be0980f09 100644 --- a/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php +++ b/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php @@ -117,7 +117,7 @@ private function executeGuardAuthenticator($uniqueGuardKey, GuardAuthenticatorIn $this->logger->info('Guard authentication failed.', array('exception' => $e, 'authenticator' => get_class($guardAuthenticator))); } - $response = $this->guardHandler->handleAuthenticationFailure($e, $request, $guardAuthenticator); + $response = $this->guardHandler->handleAuthenticationFailure($e, $request, $guardAuthenticator, $this->providerKey); if ($response instanceof Response) { $event->setResponse($response); diff --git a/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php b/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php index c588d688b7fb1..5c6451ea7c7cf 100644 --- a/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php +++ b/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php @@ -18,6 +18,7 @@ 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; @@ -112,12 +113,16 @@ public function authenticateUserAndHandleSuccess(UserInterface $user, Request $r * @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) + public function handleAuthenticationFailure(AuthenticationException $authenticationException, Request $request, GuardAuthenticatorInterface $guardAuthenticator, $providerKey) { - $this->tokenStorage->setToken(null); + $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) { diff --git a/src/Symfony/Component/Security/Guard/Tests/Firewall/GuardAuthenticationListenerTest.php b/src/Symfony/Component/Security/Guard/Tests/Firewall/GuardAuthenticationListenerTest.php index ab7829223edfc..8fab3991f98a4 100644 --- a/src/Symfony/Component/Security/Guard/Tests/Firewall/GuardAuthenticationListenerTest.php +++ b/src/Symfony/Component/Security/Guard/Tests/Firewall/GuardAuthenticationListenerTest.php @@ -141,7 +141,7 @@ public function testHandleCatchesAuthenticationException() $this->guardAuthenticatorHandler ->expects($this->once()) ->method('handleAuthenticationFailure') - ->with($authException, $this->request, $authenticator); + ->with($authException, $this->request, $authenticator, $providerKey); $listener = new GuardAuthenticationListener( $this->guardAuthenticatorHandler, diff --git a/src/Symfony/Component/Security/Guard/Tests/GuardAuthenticatorHandlerTest.php b/src/Symfony/Component/Security/Guard/Tests/GuardAuthenticatorHandlerTest.php index 6b27e209bb0b1..6f36702df1a3f 100644 --- a/src/Symfony/Component/Security/Guard/Tests/GuardAuthenticatorHandlerTest.php +++ b/src/Symfony/Component/Security/Guard/Tests/GuardAuthenticatorHandlerTest.php @@ -18,9 +18,6 @@ use Symfony\Component\Security\Http\Event\InteractiveLoginEvent; use Symfony\Component\Security\Http\SecurityEvents; -/** - * @author Ryan Weaver - */ class GuardAuthenticatorHandlerTest extends \PHPUnit_Framework_TestCase { private $tokenStorage; @@ -63,7 +60,41 @@ public function testHandleAuthenticationSuccess() 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!'); @@ -75,10 +106,21 @@ public function testHandleAuthenticationFailure() ->will($this->returnValue($response)); $handler = new GuardAuthenticatorHandler($this->tokenStorage, $this->dispatcher); - $actualResponse = $handler->handleAuthenticationFailure($authException, $this->request, $this->guardAuthenticator); + $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'); From dd485f4c13fdf58b28a5f9c74f30736eb23af0b8 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Sun, 20 Sep 2015 20:41:52 -0400 Subject: [PATCH 21/24] Adding a new exception and throwing it when the User changes This is quite technical. As you can see in the provider, the method is called sometimes when the User changes, and so the token becomes de-authenticated (e.g. someone else changes the password between requests). In practice, the user should be unauthenticated. Using the anonymous token did this, but throwing an AccountStatusException seems like a better idea. It needs to be an AccountStatusException because the ExceptionListener from the Firewall looks for exceptions of this class and logs the user out when they are found (because this is their purpose). --- .../AuthenticationExpiredException.php | 31 +++++++++++++++++++ .../Provider/GuardAuthenticationProvider.php | 5 +-- .../GuardAuthenticationProviderTest.php | 5 +-- 3 files changed, 37 insertions(+), 4 deletions(-) create mode 100644 src/Symfony/Component/Security/Core/Exception/AuthenticationExpiredException.php diff --git a/src/Symfony/Component/Security/Core/Exception/AuthenticationExpiredException.php b/src/Symfony/Component/Security/Core/Exception/AuthenticationExpiredException.php new file mode 100644 index 0000000000000..caf2e6cc38601 --- /dev/null +++ b/src/Symfony/Component/Security/Core/Exception/AuthenticationExpiredException.php @@ -0,0 +1,31 @@ + + * + * 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 + */ +class AuthenticationExpiredException extends AccountStatusException +{ + /** + * {@inheritdoc} + */ + public function getMessageKey() + { + return 'Authentication expired because your account information has changed.'; + } +} diff --git a/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php b/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php index 646eea9b59a6f..2a58085baeec2 100644 --- a/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php +++ b/src/Symfony/Component/Security/Guard/Provider/GuardAuthenticationProvider.php @@ -21,6 +21,7 @@ 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 @@ -81,8 +82,8 @@ public function authenticate(TokenInterface $token) return $token; } - // cause the logout - the token is not authenticated - return new AnonymousToken($this->providerKey, 'anon.'); + // this AccountStatusException causes the user to be logged out + throw new AuthenticationExpiredException(); } // find the *one* GuardAuthenticator that this token originated from diff --git a/src/Symfony/Component/Security/Guard/Tests/Provider/GuardAuthenticationProviderTest.php b/src/Symfony/Component/Security/Guard/Tests/Provider/GuardAuthenticationProviderTest.php index 24c946d0e1647..3bc002b2698e3 100644 --- a/src/Symfony/Component/Security/Guard/Tests/Provider/GuardAuthenticationProviderTest.php +++ b/src/Symfony/Component/Security/Guard/Tests/Provider/GuardAuthenticationProviderTest.php @@ -81,6 +81,9 @@ public function testAuthenticate() $this->assertSame($authedToken, $actualAuthedToken); } + /** + * @expectedException \Symfony\Component\Security\Core\Exception\AuthenticationExpiredException + */ public function testGuardWithNoLongerAuthenticatedTriggersLogout() { $providerKey = 'my_firewall_abc'; @@ -93,8 +96,6 @@ public function testGuardWithNoLongerAuthenticatedTriggersLogout() $provider = new GuardAuthenticationProvider(array(), $this->userProvider, $providerKey, $this->userChecker); $actualToken = $provider->authenticate($token); - // this should return the anonymous user - $this->assertEquals(new AnonymousToken($providerKey, 'anon.'), $actualToken); } protected function setUp() From e353833baf0c91c6502c91152ad2523dca62c5f4 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Sun, 20 Sep 2015 20:45:52 -0400 Subject: [PATCH 22/24] fabbot --- .../AbstractFormLoginAuthenticator.php | 23 +++++++++---------- .../Guard/GuardAuthenticatorHandler.php | 2 +- .../GuardAuthenticationProviderTest.php | 1 - 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/Symfony/Component/Security/Guard/Authenticator/AbstractFormLoginAuthenticator.php b/src/Symfony/Component/Security/Guard/Authenticator/AbstractFormLoginAuthenticator.php index c972ed6f15612..b3c6bd73a0a9b 100644 --- a/src/Symfony/Component/Security/Guard/Authenticator/AbstractFormLoginAuthenticator.php +++ b/src/Symfony/Component/Security/Guard/Authenticator/AbstractFormLoginAuthenticator.php @@ -15,12 +15,8 @@ use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; -use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface; use Symfony\Component\Security\Core\Exception\AuthenticationException; -use Symfony\Component\Security\Core\Exception\BadCredentialsException; use Symfony\Component\Security\Core\Security; -use Symfony\Component\Security\Core\User\UserInterface; -use Symfony\Component\Security\Core\User\UserProviderInterface; /** * A base class to make form login authentication easier! @@ -30,7 +26,7 @@ abstract class AbstractFormLoginAuthenticator extends AbstractGuardAuthenticator { /** - * Return the URL to the login page + * Return the URL to the login page. * * @return string */ @@ -40,17 +36,18 @@ 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) + * 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 + * Override to change what happens after a bad username/password is submitted. * - * @param Request $request + * @param Request $request * @param AuthenticationException $exception + * * @return RedirectResponse */ public function onAuthenticationFailure(Request $request, AuthenticationException $exception) @@ -62,11 +59,12 @@ public function onAuthenticationFailure(Request $request, AuthenticationExceptio } /** - * Override to change what happens after successful authentication + * Override to change what happens after successful authentication. * - * @param Request $request + * @param Request $request * @param TokenInterface $token - * @param string $providerKey + * @param string $providerKey + * * @return RedirectResponse */ public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey) @@ -91,8 +89,9 @@ public function supportsRememberMe() * Override to control what happens when the user hits a secure page * but isn't logged in yet. * - * @param Request $request + * @param Request $request * @param AuthenticationException|null $authException + * * @return RedirectResponse */ public function start(Request $request, AuthenticationException $authException = null) diff --git a/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php b/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php index 5c6451ea7c7cf..5e1351dcc25ee 100644 --- a/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php +++ b/src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php @@ -113,7 +113,7 @@ public function authenticateUserAndHandleSuccess(UserInterface $user, Request $r * @param AuthenticationException $authenticationException * @param Request $request * @param GuardAuthenticatorInterface $guardAuthenticator - * @param string $providerKey The key of the firewall + * @param string $providerKey The key of the firewall * * @return null|Response */ diff --git a/src/Symfony/Component/Security/Guard/Tests/Provider/GuardAuthenticationProviderTest.php b/src/Symfony/Component/Security/Guard/Tests/Provider/GuardAuthenticationProviderTest.php index 3bc002b2698e3..33c00e5073125 100644 --- a/src/Symfony/Component/Security/Guard/Tests/Provider/GuardAuthenticationProviderTest.php +++ b/src/Symfony/Component/Security/Guard/Tests/Provider/GuardAuthenticationProviderTest.php @@ -11,7 +11,6 @@ namespace Symfony\Component\Security\Guard\Tests\Provider; -use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken; use Symfony\Component\Security\Guard\Provider\GuardAuthenticationProvider; use Symfony\Component\Security\Guard\Token\PostAuthenticationGuardToken; From d763134e1a4c3651bd61564dabc130f477af8077 Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Tue, 22 Sep 2015 19:45:03 -0400 Subject: [PATCH 23/24] Removing unnecessary override --- .../Guard/Token/PostAuthenticationGuardToken.php | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/Symfony/Component/Security/Guard/Token/PostAuthenticationGuardToken.php b/src/Symfony/Component/Security/Guard/Token/PostAuthenticationGuardToken.php index 9657f8a524345..36c40cab9579d 100644 --- a/src/Symfony/Component/Security/Guard/Token/PostAuthenticationGuardToken.php +++ b/src/Symfony/Component/Security/Guard/Token/PostAuthenticationGuardToken.php @@ -50,18 +50,6 @@ public function __construct(UserInterface $user, $providerKey, array $roles) parent::setAuthenticated(true); } - /** - * {@inheritdoc} - */ - public function setAuthenticated($isAuthenticated) - { - if ($isAuthenticated) { - throw new \LogicException('Cannot set this token to trusted after instantiation.'); - } - - parent::setAuthenticated(false); - } - /** * This is meant to be only an authenticated token, where credentials * have already been used and are thus cleared. From a01ed35cfcbc97aed0fadcbbd0ae61fd92a4cacc Mon Sep 17 00:00:00 2001 From: Ryan Weaver Date: Wed, 23 Sep 2015 21:55:58 -0400 Subject: [PATCH 24/24] Adding the necessary files so that Guard can be its own installable component --- .../Component/Security/Guard/.gitignore | 3 ++ src/Symfony/Component/Security/Guard/LICENSE | 19 ++++++++++ .../Component/Security/Guard/README.md | 22 ++++++++++++ .../Component/Security/Guard/composer.json | 36 +++++++++++++++++++ .../Component/Security/Guard/phpunit.xml.dist | 33 +++++++++++++++++ 5 files changed, 113 insertions(+) create mode 100644 src/Symfony/Component/Security/Guard/.gitignore create mode 100644 src/Symfony/Component/Security/Guard/LICENSE create mode 100644 src/Symfony/Component/Security/Guard/README.md create mode 100644 src/Symfony/Component/Security/Guard/composer.json create mode 100644 src/Symfony/Component/Security/Guard/phpunit.xml.dist diff --git a/src/Symfony/Component/Security/Guard/.gitignore b/src/Symfony/Component/Security/Guard/.gitignore new file mode 100644 index 0000000000000..c49a5d8df5c65 --- /dev/null +++ b/src/Symfony/Component/Security/Guard/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/src/Symfony/Component/Security/Guard/LICENSE b/src/Symfony/Component/Security/Guard/LICENSE new file mode 100644 index 0000000000000..43028bc600f26 --- /dev/null +++ b/src/Symfony/Component/Security/Guard/LICENSE @@ -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. diff --git a/src/Symfony/Component/Security/Guard/README.md b/src/Symfony/Component/Security/Guard/README.md new file mode 100644 index 0000000000000..845340f9232e6 --- /dev/null +++ b/src/Symfony/Component/Security/Guard/README.md @@ -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 diff --git a/src/Symfony/Component/Security/Guard/composer.json b/src/Symfony/Component/Security/Guard/composer.json new file mode 100644 index 0000000000000..1e0dc8ceddcbc --- /dev/null +++ b/src/Symfony/Component/Security/Guard/composer.json @@ -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" + } + } +} diff --git a/src/Symfony/Component/Security/Guard/phpunit.xml.dist b/src/Symfony/Component/Security/Guard/phpunit.xml.dist new file mode 100644 index 0000000000000..093628b332e80 --- /dev/null +++ b/src/Symfony/Component/Security/Guard/phpunit.xml.dist @@ -0,0 +1,33 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./vendor + ./Tests + + + +