From 1fe2ed64d1c83d4fac115733dedf7e2b5cb8e2d3 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Mon, 19 Nov 2012 18:01:13 +0100 Subject: [PATCH] [Security] Add SimpleForm authentication --- .../Security/Factory/SimpleFormFactory.php | 93 ++++++++++++++ .../Resources/config/security_listeners.xml | 15 +++ .../Bundle/SecurityBundle/SecurityBundle.php | 2 + .../Provider/SimpleAuthenticationProvider.php | 56 +++++++++ .../SimpleAuthenticatorInterface.php | 30 +++++ .../SimpleFormAuthenticatorInterface.php | 25 ++++ .../SimpleFormAuthenticationListener.php | 116 ++++++++++++++++++ 7 files changed, 337 insertions(+) create mode 100644 src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/SimpleFormFactory.php create mode 100644 src/Symfony/Component/Security/Core/Authentication/Provider/SimpleAuthenticationProvider.php create mode 100644 src/Symfony/Component/Security/Core/Authentication/SimpleAuthenticatorInterface.php create mode 100644 src/Symfony/Component/Security/Core/Authentication/SimpleFormAuthenticatorInterface.php create mode 100644 src/Symfony/Component/Security/Http/Firewall/SimpleFormAuthenticationListener.php diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/SimpleFormFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/SimpleFormFactory.php new file mode 100644 index 0000000000..1acbde25a1 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/SimpleFormFactory.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\Bundle\SecurityBundle\DependencyInjection\Security\Factory; + +use Symfony\Component\Config\Definition\Builder\NodeDefinition; +use Symfony\Component\DependencyInjection\DefinitionDecorator; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +/** + * @author Jordi Boggiano + */ +class SimpleFormFactory extends FormLoginFactory +{ + public function __construct() + { + parent::__construct(); + + $this->addOption('authenticator', null); + } + + public function getKey() + { + return 'simple-form'; + } + + public function addConfiguration(NodeDefinition $node) + { + parent::addConfiguration($node); + + $node->children() + ->scalarNode('authenticator')->cannotBeEmpty()->end() + ->end(); + } + + protected function getListenerId() + { + return 'security.authentication.listener.simple_form'; + } + + protected function createAuthProvider(ContainerBuilder $container, $id, $config, $userProviderId) + { + $provider = 'security.authentication.provider.simple_form.'.$id; + $container + ->setDefinition($provider, new DefinitionDecorator('security.authentication.provider.simple')) + ->replaceArgument(0, new Reference($config['authenticator'])) + ->replaceArgument(1, new Reference($userProviderId)) + ->replaceArgument(2, $id) + ; + + return $provider; + } + + protected function createListener($container, $id, $config, $userProvider) + { + $listenerId = parent::createListener($container, $id, $config, $userProvider); + + if (!isset($config['csrf_provider'])) { + $container + ->getDefinition($listenerId) + ->addArgument(null) + ; + } + $container + ->getDefinition($listenerId) + ->addArgument(new Reference($config['authenticator'])) + ; + + return $listenerId; + } + + protected function createEntryPoint($container, $id, $config, $defaultEntryPoint) + { + $entryPointId = 'security.authentication.form_entry_point.'.$id; + $container + ->setDefinition($entryPointId, new DefinitionDecorator('security.authentication.form_entry_point')) + ->addArgument(new Reference('security.http_utils')) + ->addArgument($config['login_path']) + ->addArgument($config['use_forward']) + ; + + return $entryPointId; + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/security_listeners.xml b/src/Symfony/Bundle/SecurityBundle/Resources/config/security_listeners.xml index 6148337520..88c70b383d 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/security_listeners.xml +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/security_listeners.xml @@ -12,6 +12,8 @@ Symfony\Component\Security\Http\EntryPoint\FormAuthenticationEntryPoint Symfony\Component\Security\Http\Firewall\UsernamePasswordFormAuthenticationListener + Symfony\Component\Security\Http\Firewall\SimpleFormAuthenticationListener + Symfony\Component\Security\Http\Firewall\BasicAuthenticationListener Symfony\Component\Security\Http\EntryPoint\BasicAuthenticationEntryPoint @@ -35,6 +37,7 @@ Symfony\Component\Security\Http\Firewall\ContextListener Symfony\Component\Security\Core\Authentication\Provider\DaoAuthenticationProvider + Symfony\Component\Security\Core\Authentication\Provider\SimpleAuthenticationProvider Symfony\Component\Security\Core\Authentication\Provider\PreAuthenticatedAuthenticationProvider Symfony\Component\Security\Core\Authentication\Provider\AnonymousAuthenticationProvider @@ -133,6 +136,12 @@ abstract="true"> + + + @@ -170,6 +179,12 @@ %security.authentication.hide_user_not_found% + + + + + + diff --git a/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php b/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php index 7d810fde38..2796c232ad 100644 --- a/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php +++ b/src/Symfony/Bundle/SecurityBundle/SecurityBundle.php @@ -19,6 +19,7 @@ use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\HttpBasic use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\HttpDigestFactory; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\RememberMeFactory; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\X509Factory; +use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SimpleFormFactory; use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider\InMemoryFactory; /** @@ -38,6 +39,7 @@ class SecurityBundle extends Bundle $extension->addSecurityListenerFactory(new HttpDigestFactory()); $extension->addSecurityListenerFactory(new RememberMeFactory()); $extension->addSecurityListenerFactory(new X509Factory()); + $extension->addSecurityListenerFactory(new SimpleFormFactory()); $extension->addUserProviderFactory(new InMemoryFactory()); $container->addCompilerPass(new AddSecurityVotersPass()); diff --git a/src/Symfony/Component/Security/Core/Authentication/Provider/SimpleAuthenticationProvider.php b/src/Symfony/Component/Security/Core/Authentication/Provider/SimpleAuthenticationProvider.php new file mode 100644 index 0000000000..72b82cba35 --- /dev/null +++ b/src/Symfony/Component/Security/Core/Authentication/Provider/SimpleAuthenticationProvider.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Authentication\Provider; + +use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface; +use Symfony\Component\Security\Core\User\UserProviderInterface; +use Symfony\Component\Security\Core\User\UserCheckerInterface; +use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; +use Symfony\Component\Security\Core\Exception\AuthenticationServiceException; +use Symfony\Component\Security\Core\Exception\BadCredentialsException; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authentication\SimpleAuthenticatorInterface; +use Symfony\Component\Security\Core\Exception\AuthenticationException; + +/** + * @author Jordi Boggiano + */ +class SimpleAuthenticationProvider implements AuthenticationProviderInterface +{ + private $simpleAuthenticator; + private $userProvider; + private $providerKey; + + public function __construct(SimpleAuthenticatorInterface $simpleAuthenticator, UserProviderInterface $userProvider, $providerKey) + { + $this->simpleAuthenticator = $simpleAuthenticator; + $this->userProvider = $userProvider; + $this->providerKey = $providerKey; + } + + public function authenticate(TokenInterface $token) + { + $authToken = $this->simpleAuthenticator->authenticate($token, $this->userProvider, $this->providerKey); + + if ($authToken instanceof TokenInterface) { + return $authToken; + } + + throw new AuthenticationException('Simple authenticator failed to return an authenticated token.'); + } + + public function supports(TokenInterface $token) + { + return $this->simpleAuthenticator->supports($token, $this->providerKey); + } +} diff --git a/src/Symfony/Component/Security/Core/Authentication/SimpleAuthenticatorInterface.php b/src/Symfony/Component/Security/Core/Authentication/SimpleAuthenticatorInterface.php new file mode 100644 index 0000000000..81f761fd9f --- /dev/null +++ b/src/Symfony/Component/Security/Core/Authentication/SimpleAuthenticatorInterface.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Authentication; + +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Security\Core\User\UserProviderInterface; + +/** + * @author Jordi Boggiano + */ +interface SimpleAuthenticatorInterface +{ + public function authenticate(TokenInterface $token, UserProviderInterface $userProvider, $providerKey); + + public function supports(TokenInterface $token, $providerKey); + + public function handleAuthenticationFailure(GetResponseEvent $event, AuthenticationException $exception); +} diff --git a/src/Symfony/Component/Security/Core/Authentication/SimpleFormAuthenticatorInterface.php b/src/Symfony/Component/Security/Core/Authentication/SimpleFormAuthenticatorInterface.php new file mode 100644 index 0000000000..79fdb1cb3c --- /dev/null +++ b/src/Symfony/Component/Security/Core/Authentication/SimpleFormAuthenticatorInterface.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Authentication; + +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpFoundation\Request; + +/** + * @author Jordi Boggiano + */ +interface SimpleFormAuthenticatorInterface extends SimpleAuthenticatorInterface +{ + public function createToken(Request $request, $username, $password, $providerKey); +} diff --git a/src/Symfony/Component/Security/Http/Firewall/SimpleFormAuthenticationListener.php b/src/Symfony/Component/Security/Http/Firewall/SimpleFormAuthenticationListener.php new file mode 100644 index 0000000000..f096c2f2c3 --- /dev/null +++ b/src/Symfony/Component/Security/Http/Firewall/SimpleFormAuthenticationListener.php @@ -0,0 +1,116 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Firewall; + +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\Log\LoggerInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface; +use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface; +use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; +use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; +use Symfony\Component\Security\Core\Authentication\SimpleFormAuthenticatorInterface; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Symfony\Component\Security\Core\Exception\SessionUnavailableException; +use Symfony\Component\Security\Core\SecurityContextInterface; +use Symfony\Component\Security\Http\Event\InteractiveLoginEvent; +use Symfony\Component\Security\Http\HttpUtils; +use Symfony\Component\Security\Http\RememberMe\RememberMeServicesInterface; +use Symfony\Component\Security\Http\SecurityEvents; +use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface; + +/** + * @author Jordi Boggiano + */ +class SimpleFormAuthenticationListener extends AbstractAuthenticationListener +{ + private $simpleAuthenticator; + private $csrfProvider; + + /** + * Constructor. + * + * @param SecurityContextInterface $securityContext A SecurityContext instance + * @param AuthenticationManagerInterface $authenticationManager An AuthenticationManagerInterface instance + * @param SessionAuthenticationStrategyInterface $sessionStrategy + * @param HttpUtils $httpUtils An HttpUtilsInterface instance + * @param string $providerKey + * @param AuthenticationSuccessHandlerInterface $successHandler + * @param AuthenticationFailureHandlerInterface $failureHandler + * @param array $options An array of options for the processing of a + * successful, or failed authentication attempt + * @param LoggerInterface $logger A LoggerInterface instance + * @param EventDispatcherInterface $dispatcher An EventDispatcherInterface instance + * @param SimpleFormAuthenticatorInterface $simpleAuthenticator A SimpleFormAuthenticatorInterface instance + * @param CsrfProviderInterface $csrfProvider A CsrfProviderInterface instance + */ + public function __construct(SecurityContextInterface $securityContext, AuthenticationManagerInterface $authenticationManager, SessionAuthenticationStrategyInterface $sessionStrategy, HttpUtils $httpUtils, $providerKey, AuthenticationSuccessHandlerInterface $successHandler, AuthenticationFailureHandlerInterface $failureHandler, array $options = array(), LoggerInterface $logger = null, EventDispatcherInterface $dispatcher = null, CsrfProviderInterface $csrfProvider = null, SimpleFormAuthenticatorInterface $simpleAuthenticator = null) + { + if (!$simpleAuthenticator) { + throw new \InvalidArgumentException('Missing simple authenticator'); + } + + $this->simpleAuthenticator = $simpleAuthenticator; + $this->csrfProvider = $csrfProvider; + + $options = array_merge(array( + 'username_parameter' => '_username', + 'password_parameter' => '_password', + 'csrf_parameter' => '_csrf_token', + 'intention' => 'authenticate', + 'post_only' => true, + ), $options); + parent::__construct($securityContext, $authenticationManager, $sessionStrategy, $httpUtils, $providerKey, $successHandler, $failureHandler, $options, $logger, $dispatcher); + } + + /** + * {@inheritdoc} + */ + protected function requiresAuthentication(Request $request) + { + if ($this->options['post_only'] && !$request->isMethod('POST')) { + return false; + } + + return parent::requiresAuthentication($request); + } + + /** + * {@inheritdoc} + */ + protected function attemptAuthentication(Request $request) + { + if (null !== $this->csrfProvider) { + $csrfToken = $request->get($this->options['csrf_parameter'], null, true); + + if (false === $this->csrfProvider->isCsrfTokenValid($this->options['intention'], $csrfToken)) { + throw new InvalidCsrfTokenException('Invalid CSRF token.'); + } + } + + if ($this->options['post_only']) { + $username = trim($request->request->get($this->options['username_parameter'], null, true)); + $password = $request->request->get($this->options['password_parameter'], null, true); + } else { + $username = trim($request->get($this->options['username_parameter'], null, true)); + $password = $request->get($this->options['password_parameter'], null, true); + } + + $request->getSession()->set(SecurityContextInterface::LAST_USERNAME, $username); + + $token = $this->simpleAuthenticator->createToken($request, $username, $password, $this->providerKey); + + return $this->authenticationManager->authenticate($token); + } +}