summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Core/Authorization/Voter/AbstractVoter.php46
-rw-r--r--Core/Exception/AuthenticationExpiredException.php31
-rw-r--r--Guard/.gitignore3
-rw-r--r--Guard/AbstractGuardAuthenticator.php41
-rw-r--r--Guard/Authenticator/AbstractFormLoginAuthenticator.php103
-rw-r--r--Guard/Firewall/GuardAuthenticationListener.php193
-rw-r--r--Guard/GuardAuthenticatorHandler.php139
-rw-r--r--Guard/GuardAuthenticatorInterface.php150
-rw-r--r--Guard/LICENSE19
-rw-r--r--Guard/Provider/GuardAuthenticationProvider.php145
-rw-r--r--Guard/README.md22
-rw-r--r--Guard/Tests/Firewall/GuardAuthenticationListenerTest.php222
-rw-r--r--Guard/Tests/GuardAuthenticatorHandlerTest.php141
-rw-r--r--Guard/Tests/Provider/GuardAuthenticationProviderTest.php115
-rw-r--r--Guard/Token/GuardTokenInterface.php25
-rw-r--r--Guard/Token/PostAuthenticationGuardToken.php90
-rw-r--r--Guard/Token/PreAuthenticationGuardToken.php65
-rw-r--r--Guard/composer.json36
-rw-r--r--Guard/phpunit.xml.dist33
-rw-r--r--Http/EntryPoint/AuthenticationEntryPointInterface.php16
-rw-r--r--Tests/Core/Authentication/Voter/AbstractVoterTest.php32
-rw-r--r--phpunit.xml.dist1
22 files changed, 1661 insertions, 7 deletions
diff --git a/Core/Authorization/Voter/AbstractVoter.php b/Core/Authorization/Voter/AbstractVoter.php
index efa1562..6bbea36 100644
--- a/Core/Authorization/Voter/AbstractVoter.php
+++ b/Core/Authorization/Voter/AbstractVoter.php
@@ -65,6 +65,12 @@ abstract class AbstractVoter implements VoterInterface
// abstain vote by default in case none of the attributes are supported
$vote = self::ACCESS_ABSTAIN;
+ $reflector = new \ReflectionMethod($this, 'voteOnAttribute');
+ $isNewOverwritten = $reflector->getDeclaringClass()->getName() !== 'Symfony\Component\Security\Core\Authorization\Voter\AbstractVoter';
+ if (!$isNewOverwritten) {
+ @trigger_error(sprintf("The AbstractVoter::isGranted method is deprecated since 2.8 and won't be called anymore in 3.0. Override voteOnAttribute() instead.", $reflector->class), E_USER_DEPRECATED);
+ }
+
foreach ($attributes as $attribute) {
if (!$this->supportsAttribute($attribute)) {
continue;
@@ -73,9 +79,16 @@ abstract class AbstractVoter implements VoterInterface
// as soon as at least one attribute is supported, default is to deny access
$vote = self::ACCESS_DENIED;
- if ($this->isGranted($attribute, $object, $token->getUser())) {
- // grant access as soon as at least one voter returns a positive response
- return self::ACCESS_GRANTED;
+ if ($isNewOverwritten) {
+ if ($this->voteOnAttribute($attribute, $object, $token)) {
+ // grant access as soon as at least one voter returns a positive response
+ return self::ACCESS_GRANTED;
+ }
+ } else {
+ if ($this->isGranted($attribute, $object, $token->getUser())) {
+ // grant access as soon as at least one voter returns a positive response
+ return self::ACCESS_GRANTED;
+ }
}
}
@@ -107,7 +120,32 @@ abstract class AbstractVoter implements VoterInterface
* @param object $object
* @param UserInterface|string $user
*
+ * @deprecated This method will be removed in 3.0 - override voteOnAttribute instead.
+ *
* @return bool
*/
- abstract protected function isGranted($attribute, $object, $user = null);
+ protected function isGranted($attribute, $object, $user = null)
+ {
+ return false;
+ }
+
+ /**
+ * Perform a single access check operation on a given attribute, object and (optionally) user
+ * It is safe to assume that $attribute and $object's class pass supportsAttribute/supportsClass
+ * $user can be one of the following:
+ * a UserInterface object (fully authenticated user)
+ * a string (anonymously authenticated user).
+ *
+ * This method will become abstract in 3.0.
+ *
+ * @param string $attribute
+ * @param object $object
+ * @param TokenInterface $token
+ *
+ * @return bool
+ */
+ protected function voteOnAttribute($attribute, $object, TokenInterface $token)
+ {
+ return false;
+ }
}
diff --git a/Core/Exception/AuthenticationExpiredException.php b/Core/Exception/AuthenticationExpiredException.php
new file mode 100644
index 0000000..caf2e6c
--- /dev/null
+++ b/Core/Exception/AuthenticationExpiredException.php
@@ -0,0 +1,31 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Security\Core\Exception;
+
+/**
+ * AuthenticationServiceException is thrown when an authenticated token becomes un-authentcated between requests.
+ *
+ * In practice, this is due to the User changing between requests (e.g. password changes),
+ * causes the token to become un-authenticated.
+ *
+ * @author Ryan Weaver <ryan@knpuniversity.com>
+ */
+class AuthenticationExpiredException extends AccountStatusException
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function getMessageKey()
+ {
+ return 'Authentication expired because your account information has changed.';
+ }
+}
diff --git a/Guard/.gitignore b/Guard/.gitignore
new file mode 100644
index 0000000..c49a5d8
--- /dev/null
+++ b/Guard/.gitignore
@@ -0,0 +1,3 @@
+vendor/
+composer.lock
+phpunit.xml
diff --git a/Guard/AbstractGuardAuthenticator.php b/Guard/AbstractGuardAuthenticator.php
new file mode 100644
index 0000000..609d772
--- /dev/null
+++ b/Guard/AbstractGuardAuthenticator.php
@@ -0,0 +1,41 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Security\Guard;
+
+use Symfony\Component\Security\Core\User\UserInterface;
+use Symfony\Component\Security\Guard\Token\PostAuthenticationGuardToken;
+
+/**
+ * An optional base class that creates a PostAuthenticationGuardToken for you.
+ *
+ * @author Ryan Weaver <ryan@knpuniversity.com>
+ */
+abstract class AbstractGuardAuthenticator implements GuardAuthenticatorInterface
+{
+ /**
+ * Shortcut to create a PostAuthenticationGuardToken for you, if you don't really
+ * care about which authenticated token you're using.
+ *
+ * @param UserInterface $user
+ * @param string $providerKey
+ *
+ * @return PostAuthenticationGuardToken
+ */
+ public function createAuthenticatedToken(UserInterface $user, $providerKey)
+ {
+ return new PostAuthenticationGuardToken(
+ $user,
+ $providerKey,
+ $user->getRoles()
+ );
+ }
+}
diff --git a/Guard/Authenticator/AbstractFormLoginAuthenticator.php b/Guard/Authenticator/AbstractFormLoginAuthenticator.php
new file mode 100644
index 0000000..b3c6bd7
--- /dev/null
+++ b/Guard/Authenticator/AbstractFormLoginAuthenticator.php
@@ -0,0 +1,103 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Security\Guard\Authenticator;
+
+use Symfony\Component\Security\Guard\AbstractGuardAuthenticator;
+use Symfony\Component\HttpFoundation\RedirectResponse;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
+use Symfony\Component\Security\Core\Exception\AuthenticationException;
+use Symfony\Component\Security\Core\Security;
+
+/**
+ * A base class to make form login authentication easier!
+ *
+ * @author Ryan Weaver <ryan@knpuniversity.com>
+ */
+abstract class AbstractFormLoginAuthenticator extends AbstractGuardAuthenticator
+{
+ /**
+ * Return the URL to the login page.
+ *
+ * @return string
+ */
+ abstract protected function getLoginUrl();
+
+ /**
+ * The user will be redirected to the secure page they originally tried
+ * to access. But if no such page exists (i.e. the user went to the
+ * login page directly), this returns the URL the user should be redirected
+ * to after logging in successfully (e.g. your homepage).
+ *
+ * @return string
+ */
+ abstract protected function getDefaultSuccessRedirectUrl();
+
+ /**
+ * Override to change what happens after a bad username/password is submitted.
+ *
+ * @param Request $request
+ * @param AuthenticationException $exception
+ *
+ * @return RedirectResponse
+ */
+ public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
+ {
+ $request->getSession()->set(Security::AUTHENTICATION_ERROR, $exception);
+ $url = $this->getLoginUrl();
+
+ return new RedirectResponse($url);
+ }
+
+ /**
+ * Override to change what happens after successful authentication.
+ *
+ * @param Request $request
+ * @param TokenInterface $token
+ * @param string $providerKey
+ *
+ * @return RedirectResponse
+ */
+ public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
+ {
+ // if the user hit a secure page and start() was called, this was
+ // the URL they were on, and probably where you want to redirect to
+ $targetPath = $request->getSession()->get('_security.'.$providerKey.'.target_path');
+
+ if (!$targetPath) {
+ $targetPath = $this->getDefaultSuccessRedirectUrl();
+ }
+
+ return new RedirectResponse($targetPath);
+ }
+
+ public function supportsRememberMe()
+ {
+ return true;
+ }
+
+ /**
+ * Override to control what happens when the user hits a secure page
+ * but isn't logged in yet.
+ *
+ * @param Request $request
+ * @param AuthenticationException|null $authException
+ *
+ * @return RedirectResponse
+ */
+ public function start(Request $request, AuthenticationException $authException = null)
+ {
+ $url = $this->getLoginUrl();
+
+ return new RedirectResponse($url);
+ }
+}
diff --git a/Guard/Firewall/GuardAuthenticationListener.php b/Guard/Firewall/GuardAuthenticationListener.php
new file mode 100644
index 0000000..6140be0
--- /dev/null
+++ b/Guard/Firewall/GuardAuthenticationListener.php
@@ -0,0 +1,193 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Security\Guard\Firewall;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\Event\GetResponseEvent;
+use Symfony\Component\Security\Guard\GuardAuthenticatorHandler;
+use Symfony\Component\Security\Guard\Token\PreAuthenticationGuardToken;
+use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
+use Symfony\Component\Security\Guard\GuardAuthenticatorInterface;
+use Psr\Log\LoggerInterface;
+use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
+use Symfony\Component\Security\Core\Exception\AuthenticationException;
+use Symfony\Component\Security\Http\Firewall\ListenerInterface;
+use Symfony\Component\Security\Http\RememberMe\RememberMeServicesInterface;
+
+/**
+ * Authentication listener for the "guard" system.
+ *
+ * @author Ryan Weaver <ryan@knpuniversity.com>
+ */
+class GuardAuthenticationListener implements ListenerInterface
+{
+ private $guardHandler;
+ private $authenticationManager;
+ private $providerKey;
+ private $guardAuthenticators;
+ private $logger;
+ private $rememberMeServices;
+
+ /**
+ * @param GuardAuthenticatorHandler $guardHandler The Guard handler
+ * @param AuthenticationManagerInterface $authenticationManager An AuthenticationManagerInterface instance
+ * @param string $providerKey The provider (i.e. firewall) key
+ * @param GuardAuthenticatorInterface[] $guardAuthenticators The authenticators, with keys that match what's passed to GuardAuthenticationProvider
+ * @param LoggerInterface $logger A LoggerInterface instance
+ */
+ public function __construct(GuardAuthenticatorHandler $guardHandler, AuthenticationManagerInterface $authenticationManager, $providerKey, array $guardAuthenticators, LoggerInterface $logger = null)
+ {
+ if (empty($providerKey)) {
+ throw new \InvalidArgumentException('$providerKey must not be empty.');
+ }
+
+ $this->guardHandler = $guardHandler;
+ $this->authenticationManager = $authenticationManager;
+ $this->providerKey = $providerKey;
+ $this->guardAuthenticators = $guardAuthenticators;
+ $this->logger = $logger;
+ }
+
+ /**
+ * Iterates over each authenticator to see if each wants to authenticate the request.
+ *
+ * @param GetResponseEvent $event
+ */
+ public function handle(GetResponseEvent $event)
+ {
+ if (null !== $this->logger) {
+ $this->logger->info('Checking for guard authentication credentials.', array('firewall_key' => $this->providerKey, 'authenticators' => count($this->guardAuthenticators)));
+ }
+
+ foreach ($this->guardAuthenticators as $key => $guardAuthenticator) {
+ // get a key that's unique to *this* guard authenticator
+ // this MUST be the same as GuardAuthenticationProvider
+ $uniqueGuardKey = $this->providerKey.'_'.$key;
+
+ $this->executeGuardAuthenticator($uniqueGuardKey, $guardAuthenticator, $event);
+ }
+ }
+
+ private function executeGuardAuthenticator($uniqueGuardKey, GuardAuthenticatorInterface $guardAuthenticator, GetResponseEvent $event)
+ {
+ $request = $event->getRequest();
+ try {
+ if (null !== $this->logger) {
+ $this->logger->info('Calling getCredentials on guard configurator.', array('firewall_key' => $this->providerKey, 'authenticator' => get_class($guardAuthenticator)));
+ }
+
+ // allow the authenticator to fetch authentication info from the request
+ $credentials = $guardAuthenticator->getCredentials($request);
+
+ // allow null to be returned to skip authentication
+ if (null === $credentials) {
+ return;
+ }
+
+ // create a token with the unique key, so that the provider knows which authenticator to use
+ $token = new PreAuthenticationGuardToken($credentials, $uniqueGuardKey);
+
+ if (null !== $this->logger) {
+ $this->logger->info('Passing guard token information to the GuardAuthenticationProvider', array('firewall_key' => $this->providerKey, 'authenticator' => get_class($guardAuthenticator)));
+ }
+ // pass the token into the AuthenticationManager system
+ // this indirectly calls GuardAuthenticationProvider::authenticate()
+ $token = $this->authenticationManager->authenticate($token);
+
+ if (null !== $this->logger) {
+ $this->logger->info('Guard authentication successful!', array('token' => $token, 'authenticator' => get_class($guardAuthenticator)));
+ }
+
+ // sets the token on the token storage, etc
+ $this->guardHandler->authenticateWithToken($token, $request);
+ } catch (AuthenticationException $e) {
+ // oh no! Authentication failed!
+
+ if (null !== $this->logger) {
+ $this->logger->info('Guard authentication failed.', array('exception' => $e, 'authenticator' => get_class($guardAuthenticator)));
+ }
+
+ $response = $this->guardHandler->handleAuthenticationFailure($e, $request, $guardAuthenticator, $this->providerKey);
+
+ if ($response instanceof Response) {
+ $event->setResponse($response);
+ }
+
+ return;
+ }
+
+ // success!
+ $response = $this->guardHandler->handleAuthenticationSuccess($token, $request, $guardAuthenticator, $this->providerKey);
+ if ($response instanceof Response) {
+ if (null !== $this->logger) {
+ $this->logger->info('Guard authenticator set success response.', array('response' => $response, 'authenticator' => get_class($guardAuthenticator)));
+ }
+
+ $event->setResponse($response);
+ } else {
+ if (null !== $this->logger) {
+ $this->logger->info('Guard authenticator set no success response: request continues.', array('authenticator' => get_class($guardAuthenticator)));
+ }
+ }
+
+ // attempt to trigger the remember me functionality
+ $this->triggerRememberMe($guardAuthenticator, $request, $token, $response);
+ }
+
+ /**
+ * Should be called if this listener will support remember me.
+ *
+ * @param RememberMeServicesInterface $rememberMeServices
+ */
+ public function setRememberMeServices(RememberMeServicesInterface $rememberMeServices)
+ {
+ $this->rememberMeServices = $rememberMeServices;
+ }
+
+ /**
+ * Checks to see if remember me is supported in the authenticator and
+ * on the firewall. If it is, the RememberMeServicesInterface is notified.
+ *
+ * @param GuardAuthenticatorInterface $guardAuthenticator
+ * @param Request $request
+ * @param TokenInterface $token
+ * @param Response $response
+ */
+ private function triggerRememberMe(GuardAuthenticatorInterface $guardAuthenticator, Request $request, TokenInterface $token, Response $response = null)
+ {
+ if (null === $this->rememberMeServices) {
+ if (null !== $this->logger) {
+ $this->logger->info('Remember me skipped: it is not configured for the firewall.', array('authenticator' => get_class($guardAuthenticator)));
+ }
+
+ return;
+ }
+
+ if (!$guardAuthenticator->supportsRememberMe()) {
+ if (null !== $this->logger) {
+ $this->logger->info('Remember me skipped: your authenticator does not support it.', array('authenticator' => get_class($guardAuthenticator)));
+ }
+
+ return;
+ }
+
+ if (!$response instanceof Response) {
+ throw new \LogicException(sprintf(
+ '%s::onAuthenticationSuccess *must* return a Response if you want to use the remember me functionality. Return a Response, or set remember_me to false under the guard configuration.',
+ get_class($guardAuthenticator)
+ ));
+ }
+
+ $this->rememberMeServices->loginSuccess($request, $response, $token);
+ }
+}
diff --git a/Guard/GuardAuthenticatorHandler.php b/Guard/GuardAuthenticatorHandler.php
new file mode 100644
index 0000000..5e1351d
--- /dev/null
+++ b/Guard/GuardAuthenticatorHandler.php
@@ -0,0 +1,139 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Security\Guard;
+
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
+use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
+use Symfony\Component\Security\Core\Exception\AuthenticationException;
+use Symfony\Component\Security\Core\User\UserInterface;
+use Symfony\Component\Security\Guard\Token\PostAuthenticationGuardToken;
+use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
+use Symfony\Component\Security\Http\SecurityEvents;
+
+/**
+ * A utility class that does much of the *work* during the guard authentication process.
+ *
+ * By having the logic here instead of the listener, more of the process
+ * can be called directly (e.g. for manual authentication) or overridden.
+ *
+ * @author Ryan Weaver <ryan@knpuniversity.com>
+ */
+class GuardAuthenticatorHandler
+{
+ private $tokenStorage;
+
+ private $dispatcher;
+
+ public function __construct(TokenStorageInterface $tokenStorage, EventDispatcherInterface $eventDispatcher = null)
+ {
+ $this->tokenStorage = $tokenStorage;
+ $this->dispatcher = $eventDispatcher;
+ }
+
+ /**
+ * Authenticates the given token in the system.
+ *
+ * @param TokenInterface $token
+ * @param Request $request
+ */
+ public function authenticateWithToken(TokenInterface $token, Request $request)
+ {
+ $this->tokenStorage->setToken($token);
+
+ if (null !== $this->dispatcher) {
+ $loginEvent = new InteractiveLoginEvent($request, $token);
+ $this->dispatcher->dispatch(SecurityEvents::INTERACTIVE_LOGIN, $loginEvent);
+ }
+ }
+
+ /**
+ * Returns the "on success" response for the given GuardAuthenticator.
+ *
+ * @param TokenInterface $token
+ * @param Request $request
+ * @param GuardAuthenticatorInterface $guardAuthenticator
+ * @param string $providerKey The provider (i.e. firewall) key
+ *
+ * @return null|Response
+ */
+ public function handleAuthenticationSuccess(TokenInterface $token, Request $request, GuardAuthenticatorInterface $guardAuthenticator, $providerKey)
+ {
+ $response = $guardAuthenticator->onAuthenticationSuccess($request, $token, $providerKey);
+
+ // check that it's a Response or null
+ if ($response instanceof Response || null === $response) {
+ return $response;
+ }
+
+ throw new \UnexpectedValueException(sprintf(
+ 'The %s::onAuthenticationSuccess method must return null or a Response object. You returned %s.',
+ get_class($guardAuthenticator),
+ is_object($response) ? get_class($response) : gettype($response)
+ ));
+ }
+
+ /**
+ * Convenience method for authenticating the user and returning the
+ * Response *if any* for success.
+ *
+ * @param UserInterface $user
+ * @param Request $request
+ * @param GuardAuthenticatorInterface $authenticator
+ * @param string $providerKey The provider (i.e. firewall) key
+ *
+ * @return Response|null
+ */
+ public function authenticateUserAndHandleSuccess(UserInterface $user, Request $request, GuardAuthenticatorInterface $authenticator, $providerKey)
+ {
+ // create an authenticated token for the User
+ $token = $authenticator->createAuthenticatedToken($user, $providerKey);
+ // authenticate this in the system
+ $this->authenticateWithToken($token, $request);
+
+ // return the success metric
+ return $this->handleAuthenticationSuccess($token, $request, $authenticator, $providerKey);
+ }
+
+ /**
+ * Handles an authentication failure and returns the Response for the
+ * GuardAuthenticator.
+ *
+ * @param AuthenticationException $authenticationException
+ * @param Request $request
+ * @param GuardAuthenticatorInterface $guardAuthenticator
+ * @param string $providerKey The key of the firewall
+ *
+ * @return null|Response
+ */
+ public function handleAuthenticationFailure(AuthenticationException $authenticationException, Request $request, GuardAuthenticatorInterface $guardAuthenticator, $providerKey)
+ {
+ $token = $this->tokenStorage->getToken();
+ if ($token instanceof PostAuthenticationGuardToken && $providerKey === $token->getProviderKey()) {
+ $this->tokenStorage->setToken(null);
+ }
+
+ $response = $guardAuthenticator->onAuthenticationFailure($request, $authenticationException);
+ if ($response instanceof Response || null === $response) {
+ // returning null is ok, it means they want the request to continue
+ return $response;
+ }
+
+ throw new \UnexpectedValueException(sprintf(
+ 'The %s::onAuthenticationFailure method must return null or a Response object. You returned %s.',
+ get_class($guardAuthenticator),
+ is_object($response) ? get_class($response) : gettype($response)
+ ));
+ }
+}
diff --git a/Guard/GuardAuthenticatorInterface.php b/Guard/GuardAuthenticatorInterface.php
new file mode 100644
index 0000000..2db313c
--- /dev/null
+++ b/Guard/GuardAuthenticatorInterface.php
@@ -0,0 +1,150 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Security\Guard;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
+use Symfony\Component\Security\Core\Exception\AuthenticationException;
+use Symfony\Component\Security\Core\User\UserInterface;
+use Symfony\Component\Security\Core\User\UserProviderInterface;
+use Symfony\Component\Security\Guard\Token\GuardTokenInterface;
+use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;
+
+/**
+ * The interface for all "guard" authenticators.
+ *
+ * The methods on this interface are called throughout the guard authentication
+ * process to give you the power to control most parts of the process from
+ * one location.
+ *
+ * @author Ryan Weaver <ryan@knpuniversity.com>
+ */
+interface GuardAuthenticatorInterface extends AuthenticationEntryPointInterface
+{
+ /**
+ * Get the authentication credentials from the request and return them
+ * as any type (e.g. an associate array). If you return null, authentication
+ * will be skipped.
+ *
+ * Whatever value you return here will be passed to getUser() and checkCredentials()
+ *
+ * For example, for a form login, you might:
+ *
+ * return array(
+ * 'username' => $request->request->get('_username'),
+ * 'password' => $request->request->get('_password'),
+ * );
+ *
+ * Or for an API token that's on a header, you might use:
+ *
+ * return array('api_key' => $request->headers->get('X-API-TOKEN'));
+ *
+ * @param Request $request
+ *
+ * @return mixed|null
+ */
+ public function getCredentials(Request $request);
+
+ /**
+ * Return a UserInterface object based on the credentials.
+ *
+ * The *credentials* are the return value from getCredentials()
+ *
+ * You may throw an AuthenticationException if you wish. If you return
+ * null, then a UsernameNotFoundException is thrown for you.
+ *
+ * @param mixed $credentials
+ * @param UserProviderInterface $userProvider
+ *
+ * @throws AuthenticationException
+ *
+ * @return UserInterface|null
+ */
+ public function getUser($credentials, UserProviderInterface $userProvider);
+
+ /**
+ * Throw an AuthenticationException if the credentials are invalid.
+ *
+ * The *credentials* are the return value from getCredentials()
+ *
+ * @param mixed $credentials
+ * @param UserInterface $user
+ *
+ * @throws AuthenticationException
+ */
+ public function checkCredentials($credentials, UserInterface $user);
+
+ /**
+ * Create an authenticated token for the given user.
+ *
+ * If you don't care about which token class is used or don't really
+ * understand what a "token" is, you can skip this method by extending
+ * the AbstractGuardAuthenticator class from your authenticator.
+ *
+ * @see AbstractGuardAuthenticator
+ *
+ * @param UserInterface $user
+ * @param string $providerKey The provider (i.e. firewall) key
+ *
+ * @return GuardTokenInterface
+ */
+ public function createAuthenticatedToken(UserInterface $user, $providerKey);
+
+ /**
+ * Called when authentication executed, but failed (e.g. wrong username password).
+ *
+ * This should return the Response sent back to the user, like a
+ * RedirectResponse to the login page or a 403 response.
+ *
+ * If you return null, the request will continue, but the user will
+ * not be authenticated. This is probably not what you want to do.
+ *
+ * @param Request $request
+ * @param AuthenticationException $exception
+ *
+ * @return Response|null
+ */
+ public function onAuthenticationFailure(Request $request, AuthenticationException $exception);
+
+ /**
+ * Called when authentication executed and was successful!
+ *
+ * This should return the Response sent back to the user, like a
+ * RedirectResponse to the last page they visited.
+ *
+ * If you return null, the current request will continue, and the user
+ * will be authenticated. This makes sense, for example, with an API.
+ *
+ * @param Request $request
+ * @param TokenInterface $token
+ * @param string $providerKey The provider (i.e. firewall) key
+ *
+ * @return Response|null
+ */
+ public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey);
+
+ /**
+ * Does this method support remember me cookies?
+ *
+ * Remember me cookie will be set if *all* of the following are met:
+ * A) This method returns true
+ * B) The remember_me key under your firewall is configured
+ * C) The "remember me" functionality is activated. This is usually
+ * done by having a _remember_me checkbox in your form, but
+ * can be configured by the "always_remember_me" and "remember_me_parameter"
+ * parameters under the "remember_me" firewall key
+ *
+ * @return bool
+ */
+ public function supportsRememberMe();
+}
diff --git a/Guard/LICENSE b/Guard/LICENSE
new file mode 100644
index 0000000..43028bc
--- /dev/null
+++ b/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/Guard/Provider/GuardAuthenticationProvider.php b/Guard/Provider/GuardAuthenticationProvider.php
new file mode 100644
index 0000000..2a58085
--- /dev/null
+++ b/Guard/Provider/GuardAuthenticationProvider.php
@@ -0,0 +1,145 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Security\Guard\Provider;
+
+use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface;
+use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken;
+use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
+use Symfony\Component\Security\Guard\GuardAuthenticatorInterface;
+use Symfony\Component\Security\Guard\Token\GuardTokenInterface;
+use Symfony\Component\Security\Guard\Token\PreAuthenticationGuardToken;
+use Symfony\Component\Security\Core\User\UserCheckerInterface;
+use Symfony\Component\Security\Core\User\UserInterface;
+use Symfony\Component\Security\Core\User\UserProviderInterface;
+use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
+use Symfony\Component\Security\Core\Exception\AuthenticationExpiredException;
+
+/**
+ * Responsible for accepting the PreAuthenticationGuardToken and calling
+ * the correct authenticator to retrieve the authenticated token.
+ *
+ * @author Ryan Weaver <ryan@knpuniversity.com>
+ */
+class GuardAuthenticationProvider implements AuthenticationProviderInterface
+{
+ /**
+ * @var GuardAuthenticatorInterface[]
+ */
+ private $guardAuthenticators;
+ private $userProvider;
+ private $providerKey;
+ private $userChecker;
+
+ /**
+ * @param GuardAuthenticatorInterface[] $guardAuthenticators The authenticators, with keys that match what's passed to GuardAuthenticationListener
+ * @param UserProviderInterface $userProvider The user provider
+ * @param string $providerKey The provider (i.e. firewall) key
+ * @param UserCheckerInterface $userChecker
+ */
+ public function __construct(array $guardAuthenticators, UserProviderInterface $userProvider, $providerKey, UserCheckerInterface $userChecker)
+ {
+ $this->guardAuthenticators = $guardAuthenticators;
+ $this->userProvider = $userProvider;
+ $this->providerKey = $providerKey;
+ $this->userChecker = $userChecker;
+ }
+
+ /**
+ * Finds the correct authenticator for the token and calls it.
+ *
+ * @param GuardTokenInterface $token
+ *
+ * @return TokenInterface
+ */
+ public function authenticate(TokenInterface $token)
+ {
+ if (!$this->supports($token)) {
+ throw new \InvalidArgumentException('GuardAuthenticationProvider only supports GuardTokenInterface.');
+ }
+
+ if (!$token instanceof PreAuthenticationGuardToken) {
+ /*
+ * The listener *only* passes PreAuthenticationGuardToken instances.
+ * This means that an authenticated token (e.g. PostAuthenticationGuardToken)
+ * is being passed here, which happens if that token becomes
+ * "not authenticated" (e.g. happens if the user changes between
+ * requests). In this case, the user should be logged out, so
+ * we will return an AnonymousToken to accomplish that.
+ */
+
+ // this should never happen - but technically, the token is
+ // authenticated... so it could just be returned
+ if ($token->isAuthenticated()) {
+ return $token;
+ }
+
+ // this AccountStatusException causes the user to be logged out
+ throw new AuthenticationExpiredException();
+ }
+
+ // find the *one* GuardAuthenticator that this token originated from
+ foreach ($this->guardAuthenticators as $key => $guardAuthenticator) {
+ // get a key that's unique to *this* guard authenticator
+ // this MUST be the same as GuardAuthenticationListener
+ $uniqueGuardKey = $this->providerKey.'_'.$key;
+
+ if ($uniqueGuardKey == $token->getGuardProviderKey()) {
+ return $this->authenticateViaGuard($guardAuthenticator, $token);
+ }
+ }
+
+ // no matching authenticator found - but there will be multiple GuardAuthenticationProvider
+ // instances that will be checked if you have multiple firewalls.
+ }
+
+ private function authenticateViaGuard(GuardAuthenticatorInterface $guardAuthenticator, PreAuthenticationGuardToken $token)
+ {
+ // get the user from the GuardAuthenticator
+ $user = $guardAuthenticator->getUser($token->getCredentials(), $this->userProvider);
+
+ if (null === $user) {
+ throw new UsernameNotFoundException(sprintf(
+ 'Null returned from %s::getUser()',
+ get_class($guardAuthenticator)
+ ));
+ }
+
+ if (!$user instanceof UserInterface) {
+ throw new \UnexpectedValueException(sprintf(
+ 'The %s::getUser() method must return a UserInterface. You returned %s.',
+ get_class($guardAuthenticator),
+ is_object($user) ? get_class($user) : gettype($user)
+ ));
+ }
+
+ $this->userChecker->checkPreAuth($user);
+ $guardAuthenticator->checkCredentials($token->getCredentials(), $user);
+ $this->userChecker->checkPostAuth($user);
+
+ // turn the UserInterface into a TokenInterface
+ $authenticatedToken = $guardAuthenticator->createAuthenticatedToken($user, $this->providerKey);
+ if (!$authenticatedToken instanceof TokenInterface) {
+ throw new \UnexpectedValueException(sprintf(
+ 'The %s::createAuthenticatedToken() method must return a TokenInterface. You returned %s.',
+ get_class($guardAuthenticator),
+ is_object($authenticatedToken) ? get_class($authenticatedToken) : gettype($authenticatedToken)
+ ));
+ }
+
+ return $authenticatedToken;
+ }
+
+ public function supports(TokenInterface $token)
+ {
+ return $token instanceof GuardTokenInterface;
+ }
+}
diff --git a/Guard/README.md b/Guard/README.md
new file mode 100644
index 0000000..845340f
--- /dev/null
+++ b/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/Guard/Tests/Firewall/GuardAuthenticationListenerTest.php b/Guard/Tests/Firewall/GuardAuthenticationListenerTest.php
new file mode 100644
index 0000000..8fab399
--- /dev/null
+++ b/Guard/Tests/Firewall/GuardAuthenticationListenerTest.php
@@ -0,0 +1,222 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Security\Guard\Tests\Firewall;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\Security\Guard\Firewall\GuardAuthenticationListener;
+use Symfony\Component\Security\Guard\Token\PreAuthenticationGuardToken;
+use Symfony\Component\Security\Core\Exception\AuthenticationException;
+
+/**
+ * @author Ryan Weaver <weaverryan@gmail.com>
+ */
+class GuardAuthenticationListenerTest extends \PHPUnit_Framework_TestCase
+{
+ private $authenticationManager;
+ private $guardAuthenticatorHandler;
+ private $event;
+ private $logger;
+ private $request;
+ private $rememberMeServices;
+
+ public function testHandleSuccess()
+ {
+ $authenticator = $this->getMock('Symfony\Component\Security\Guard\GuardAuthenticatorInterface');
+ $authenticateToken = $this->getMock('Symfony\Component\Security\Core\Authentication\Token\TokenInterface');
+ $providerKey = 'my_firewall';
+
+ $credentials = array('username' => 'weaverryan', 'password' => 'all_your_base');
+ $authenticator
+ ->expects($this->once())
+ ->method('getCredentials')
+ ->with($this->equalTo($this->request))
+ ->will($this->returnValue($credentials));
+
+ // a clone of the token that should be created internally
+ $uniqueGuardKey = 'my_firewall_0';
+ $nonAuthedToken = new PreAuthenticationGuardToken($credentials, $uniqueGuardKey);
+
+ $this->authenticationManager
+ ->expects($this->once())
+ ->method('authenticate')
+ ->with($this->equalTo($nonAuthedToken))
+ ->will($this->returnValue($authenticateToken));
+
+ $this->guardAuthenticatorHandler
+ ->expects($this->once())
+ ->method('authenticateWithToken')
+ ->with($authenticateToken, $this->request);
+
+ $this->guardAuthenticatorHandler
+ ->expects($this->once())
+ ->method('handleAuthenticationSuccess')
+ ->with($authenticateToken, $this->request, $authenticator, $providerKey);
+
+ $listener = new GuardAuthenticationListener(
+ $this->guardAuthenticatorHandler,
+ $this->authenticationManager,
+ $providerKey,
+ array($authenticator),
+ $this->logger
+ );
+
+ $listener->setRememberMeServices($this->rememberMeServices);
+ // should never be called - our handleAuthenticationSuccess() does not return a Response
+ $this->rememberMeServices
+ ->expects($this->never())
+ ->method('loginSuccess');
+
+ $listener->handle($this->event);
+ }
+
+ public function testHandleSuccessWithRememberMe()
+ {
+ $authenticator = $this->getMock('Symfony\Component\Security\Guard\GuardAuthenticatorInterface');
+ $authenticateToken = $this->getMock('Symfony\Component\Security\Core\Authentication\Token\TokenInterface');
+ $providerKey = 'my_firewall_with_rememberme';
+
+ $authenticator
+ ->expects($this->once())
+ ->method('getCredentials')
+ ->with($this->equalTo($this->request))
+ ->will($this->returnValue(array('username' => 'anything_not_empty')));
+
+ $this->authenticationManager
+ ->expects($this->once())
+ ->method('authenticate')
+ ->will($this->returnValue($authenticateToken));
+
+ $successResponse = new Response('Success!');
+ $this->guardAuthenticatorHandler
+ ->expects($this->once())
+ ->method('handleAuthenticationSuccess')
+ ->will($this->returnValue($successResponse));
+
+ $listener = new GuardAuthenticationListener(
+ $this->guardAuthenticatorHandler,
+ $this->authenticationManager,
+ $providerKey,
+ array($authenticator),
+ $this->logger
+ );
+
+ $listener->setRememberMeServices($this->rememberMeServices);
+ $authenticator->expects($this->once())
+ ->method('supportsRememberMe')
+ ->will($this->returnValue(true));
+ // should be called - we do have a success Response
+ $this->rememberMeServices
+ ->expects($this->once())
+ ->method('loginSuccess');
+
+ $listener->handle($this->event);
+ }
+
+ public function testHandleCatchesAuthenticationException()
+ {
+ $authenticator = $this->getMock('Symfony\Component\Security\Guard\GuardAuthenticatorInterface');
+ $providerKey = 'my_firewall2';
+
+ $authException = new AuthenticationException('Get outta here crazy user with a bad password!');
+ $authenticator
+ ->expects($this->once())
+ ->method('getCredentials')
+ ->will($this->throwException($authException));
+
+ // this is not called
+ $this->authenticationManager
+ ->expects($this->never())
+ ->method('authenticate');
+
+ $this->guardAuthenticatorHandler
+ ->expects($this->once())
+ ->method('handleAuthenticationFailure')
+ ->with($authException, $this->request, $authenticator, $providerKey);
+
+ $listener = new GuardAuthenticationListener(
+ $this->guardAuthenticatorHandler,
+ $this->authenticationManager,
+ $providerKey,
+ array($authenticator),
+ $this->logger
+ );
+
+ $listener->handle($this->event);
+ }
+
+ public function testReturnNullToSkipAuth()
+ {
+ $authenticatorA = $this->getMock('Symfony\Component\Security\Guard\GuardAuthenticatorInterface');
+ $authenticatorB = $this->getMock('Symfony\Component\Security\Guard\GuardAuthenticatorInterface');
+ $providerKey = 'my_firewall3';
+
+ $authenticatorA
+ ->expects($this->once())
+ ->method('getCredentials')
+ ->will($this->returnValue(null));
+ $authenticatorB
+ ->expects($this->once())
+ ->method('getCredentials')
+ ->will($this->returnValue(null));
+
+ // this is not called
+ $this->authenticationManager
+ ->expects($this->never())
+ ->method('authenticate');
+
+ $this->guardAuthenticatorHandler
+ ->expects($this->never())
+ ->method('handleAuthenticationSuccess');
+
+ $listener = new GuardAuthenticationListener(
+ $this->guardAuthenticatorHandler,
+ $this->authenticationManager,
+ $providerKey,
+ array($authenticatorA, $authenticatorB),
+ $this->logger
+ );
+
+ $listener->handle($this->event);
+ }
+
+ protected function setUp()
+ {
+ $this->authenticationManager = $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\AuthenticationProviderManager')
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->guardAuthenticatorHandler = $this->getMockBuilder('Symfony\Component\Security\Guard\GuardAuthenticatorHandler')
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->request = new Request(array(), array(), array(), array(), array(), array());
+
+ $this->event = $this->getMock('Symfony\Component\HttpKernel\Event\GetResponseEvent', array(), array(), '', false);
+ $this->event
+ ->expects($this->any())
+ ->method('getRequest')
+ ->will($this->returnValue($this->request));
+
+ $this->logger = $this->getMock('Psr\Log\LoggerInterface');
+ $this->rememberMeServices = $this->getMock('Symfony\Component\Security\Http\RememberMe\RememberMeServicesInterface');
+ }
+
+ protected function tearDown()
+ {
+ $this->authenticationManager = null;
+ $this->guardAuthenticatorHandler = null;
+ $this->event = null;
+ $this->logger = null;
+ $this->request = null;
+ }
+}
diff --git a/Guard/Tests/GuardAuthenticatorHandlerTest.php b/Guard/Tests/GuardAuthenticatorHandlerTest.php
new file mode 100644
index 0000000..6f36702
--- /dev/null
+++ b/Guard/Tests/GuardAuthenticatorHandlerTest.php
@@ -0,0 +1,141 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Security\Guard\Tests;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\Security\Guard\GuardAuthenticatorHandler;
+use Symfony\Component\Security\Core\Exception\AuthenticationException;
+use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
+use Symfony\Component\Security\Http\SecurityEvents;
+
+class GuardAuthenticatorHandlerTest extends \PHPUnit_Framework_TestCase
+{
+ private $tokenStorage;
+ private $dispatcher;
+ private $token;
+ private $request;
+ private $guardAuthenticator;
+
+ public function testAuthenticateWithToken()
+ {
+ $this->tokenStorage->expects($this->once())
+ ->method('setToken')
+ ->with($this->token);
+
+ $loginEvent = new InteractiveLoginEvent($this->request, $this->token);
+
+ $this->dispatcher
+ ->expects($this->once())
+ ->method('dispatch')
+ ->with($this->equalTo(SecurityEvents::INTERACTIVE_LOGIN), $this->equalTo($loginEvent))
+ ;
+
+ $handler = new GuardAuthenticatorHandler($this->tokenStorage, $this->dispatcher);
+ $handler->authenticateWithToken($this->token, $this->request);
+ }
+
+ public function testHandleAuthenticationSuccess()
+ {
+ $providerKey = 'my_handleable_firewall';
+ $response = new Response('Guard all the things!');
+ $this->guardAuthenticator->expects($this->once())
+ ->method('onAuthenticationSuccess')
+ ->with($this->request, $this->token, $providerKey)
+ ->will($this->returnValue($response));
+
+ $handler = new GuardAuthenticatorHandler($this->tokenStorage, $this->dispatcher);
+ $actualResponse = $handler->handleAuthenticationSuccess($this->token, $this->request, $this->guardAuthenticator, $providerKey);
+ $this->assertSame($response, $actualResponse);
+ }
+
+ public function testHandleAuthenticationFailure()
+ {
+ // setToken() not called - getToken() will return null, so there's nothing to clear
+ $this->tokenStorage->expects($this->never())
+ ->method('setToken')
+ ->with(null);
+ $authException = new AuthenticationException('Bad password!');
+
+ $response = new Response('Try again, but with the right password!');
+ $this->guardAuthenticator->expects($this->once())
+ ->method('onAuthenticationFailure')
+ ->with($this->request, $authException)
+ ->will($this->returnValue($response));
+
+ $handler = new GuardAuthenticatorHandler($this->tokenStorage, $this->dispatcher);
+ $actualResponse = $handler->handleAuthenticationFailure($authException, $this->request, $this->guardAuthenticator, 'firewall_provider_key');
+ $this->assertSame($response, $actualResponse);
+ }
+
+ /**
+ * @dataProvider getTokenClearingTests
+ */
+ public function testHandleAuthenticationClearsToken($tokenClass, $tokenProviderKey, $actualProviderKey, $shouldTokenBeCleared)
+ {
+ $token = $this->getMockBuilder($tokenClass)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $token->expects($this->any())
+ ->method('getProviderKey')
+ ->will($this->returnValue($tokenProviderKey));
+
+ // make the $token be the current token
+ $this->tokenStorage->expects($this->once())
+ ->method('getToken')
+ ->will($this->returnValue($token));
+
+ $this->tokenStorage->expects($shouldTokenBeCleared ? $this->once() : $this->never())
+ ->method('setToken')
+ ->with(null);
+ $authException = new AuthenticationException('Bad password!');
+
+ $response = new Response('Try again, but with the right password!');
+ $this->guardAuthenticator->expects($this->once())
+ ->method('onAuthenticationFailure')
+ ->with($this->request, $authException)
+ ->will($this->returnValue($response));
+
+ $handler = new GuardAuthenticatorHandler($this->tokenStorage, $this->dispatcher);
+ $actualResponse = $handler->handleAuthenticationFailure($authException, $this->request, $this->guardAuthenticator, $actualProviderKey);
+ $this->assertSame($response, $actualResponse);
+ }
+
+ public function getTokenClearingTests()
+ {
+ $tests = array();
+ // correct token class and matching firewall => clear the token
+ $tests[] = array('Symfony\Component\Security\Guard\Token\PostAuthenticationGuardToken', 'the_firewall_key', 'the_firewall_key', true);
+ $tests[] = array('Symfony\Component\Security\Guard\Token\PostAuthenticationGuardToken', 'the_firewall_key', 'different_key', false);
+ $tests[] = array('Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken', 'the_firewall_key', 'the_firewall_key', false);
+
+ return $tests;
+ }
+
+ protected function setUp()
+ {
+ $this->tokenStorage = $this->getMock('Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface');
+ $this->dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
+ $this->token = $this->getMock('Symfony\Component\Security\Core\Authentication\Token\TokenInterface');
+ $this->request = new Request(array(), array(), array(), array(), array(), array());
+ $this->guardAuthenticator = $this->getMock('Symfony\Component\Security\Guard\GuardAuthenticatorInterface');
+ }
+
+ protected function tearDown()
+ {
+ $this->tokenStorage = null;
+ $this->dispatcher = null;
+ $this->token = null;
+ $this->request = null;
+ $this->guardAuthenticator = null;
+ }
+}
diff --git a/Guard/Tests/Provider/GuardAuthenticationProviderTest.php b/Guard/Tests/Provider/GuardAuthenticationProviderTest.php
new file mode 100644
index 0000000..33c00e5
--- /dev/null
+++ b/Guard/Tests/Provider/GuardAuthenticationProviderTest.php
@@ -0,0 +1,115 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Security\Guard\Tests\Provider;
+
+use Symfony\Component\Security\Guard\Provider\GuardAuthenticationProvider;
+use Symfony\Component\Security\Guard\Token\PostAuthenticationGuardToken;
+
+/**
+ * @author Ryan Weaver <weaverryan@gmail.com>
+ */
+class GuardAuthenticationProviderTest extends \PHPUnit_Framework_TestCase
+{
+ private $userProvider;
+ private $userChecker;
+ private $preAuthenticationToken;
+
+ public function testAuthenticate()
+ {
+ $providerKey = 'my_cool_firewall';
+
+ $authenticatorA = $this->getMock('Symfony\Component\Security\Guard\GuardAuthenticatorInterface');
+ $authenticatorB = $this->getMock('Symfony\Component\Security\Guard\GuardAuthenticatorInterface');
+ $authenticatorC = $this->getMock('Symfony\Component\Security\Guard\GuardAuthenticatorInterface');
+ $authenticators = array($authenticatorA, $authenticatorB, $authenticatorC);
+
+ // called 2 times - for authenticator A and B (stops on B because of match)
+ $this->preAuthenticationToken->expects($this->exactly(2))
+ ->method('getGuardProviderKey')
+ // it will return the "1" index, which will match authenticatorB
+ ->will($this->returnValue('my_cool_firewall_1'));
+
+ $enteredCredentials = array(
+ 'username' => '_weaverryan_test_user',
+ 'password' => 'guard_auth_ftw',
+ );
+ $this->preAuthenticationToken->expects($this->atLeastOnce())
+ ->method('getCredentials')
+ ->will($this->returnValue($enteredCredentials));
+
+ // authenticators A and C are never called
+ $authenticatorA->expects($this->never())
+ ->method('getUser');
+ $authenticatorC->expects($this->never())
+ ->method('getUser');
+
+ $mockedUser = $this->getMock('Symfony\Component\Security\Core\User\UserInterface');
+ $authenticatorB->expects($this->once())
+ ->method('getUser')
+ ->with($enteredCredentials, $this->userProvider)
+ ->will($this->returnValue($mockedUser));
+ // checkCredentials is called
+ $authenticatorB->expects($this->once())
+ ->method('checkCredentials')
+ ->with($enteredCredentials, $mockedUser);
+ $authedToken = $this->getMock('Symfony\Component\Security\Core\Authentication\Token\TokenInterface');
+ $authenticatorB->expects($this->once())
+ ->method('createAuthenticatedToken')
+ ->with($mockedUser, $providerKey)
+ ->will($this->returnValue($authedToken));
+
+ // user checker should be called
+ $this->userChecker->expects($this->once())
+ ->method('checkPreAuth')
+ ->with($mockedUser);
+ $this->userChecker->expects($this->once())
+ ->method('checkPostAuth')
+ ->with($mockedUser);
+
+ $provider = new GuardAuthenticationProvider($authenticators, $this->userProvider, $providerKey, $this->userChecker);
+ $actualAuthedToken = $provider->authenticate($this->preAuthenticationToken);
+ $this->assertSame($authedToken, $actualAuthedToken);
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Security\Core\Exception\AuthenticationExpiredException
+ */
+ public function testGuardWithNoLongerAuthenticatedTriggersLogout()
+ {
+ $providerKey = 'my_firewall_abc';
+
+ // create a token and mark it as NOT authenticated anymore
+ // this mimics what would happen if a user "changed" between request
+ $mockedUser = $this->getMock('Symfony\Component\Security\Core\User\UserInterface');
+ $token = new PostAuthenticationGuardToken($mockedUser, $providerKey, array('ROLE_USER'));
+ $token->setAuthenticated(false);
+
+ $provider = new GuardAuthenticationProvider(array(), $this->userProvider, $providerKey, $this->userChecker);
+ $actualToken = $provider->authenticate($token);
+ }
+
+ protected function setUp()
+ {
+ $this->userProvider = $this->getMock('Symfony\Component\Security\Core\User\UserProviderInterface');
+ $this->userChecker = $this->getMock('Symfony\Component\Security\Core\User\UserCheckerInterface');
+ $this->preAuthenticationToken = $this->getMockBuilder('Symfony\Component\Security\Guard\Token\PreAuthenticationGuardToken')
+ ->disableOriginalConstructor()
+ ->getMock();
+ }
+
+ protected function tearDown()
+ {
+ $this->userProvider = null;
+ $this->userChecker = null;
+ $this->preAuthenticationToken = null;
+ }
+}
diff --git a/Guard/Token/GuardTokenInterface.php b/Guard/Token/GuardTokenInterface.php
new file mode 100644
index 0000000..f0db250
--- /dev/null
+++ b/Guard/Token/GuardTokenInterface.php
@@ -0,0 +1,25 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Security\Guard\Token;
+
+/**
+ * A marker interface that both guard tokens implement.
+ *
+ * Any tokens passed to GuardAuthenticationProvider (i.e. any tokens that
+ * are handled by the guard auth system) must implement this
+ * interface.
+ *
+ * @author Ryan Weaver <ryan@knpuniversity.com>
+ */
+interface GuardTokenInterface
+{
+}
diff --git a/Guard/Token/PostAuthenticationGuardToken.php b/Guard/Token/PostAuthenticationGuardToken.php
new file mode 100644
index 0000000..36c40ca
--- /dev/null
+++ b/Guard/Token/PostAuthenticationGuardToken.php
@@ -0,0 +1,90 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Security\Guard\Token;
+
+use Symfony\Component\Security\Core\Authentication\Token\AbstractToken;
+use Symfony\Component\Security\Core\Role\RoleInterface;
+use Symfony\Component\Security\Core\User\UserInterface;
+
+/**
+ * Used as an "authenticated" token, though it could be set to not-authenticated later.
+ *
+ * If you're using Guard authentication, you *must* use a class that implements
+ * GuardTokenInterface as your authenticated token (like this class).
+ *
+ * @author Ryan Weaver <ryan@knpuniversity.com>n@gmail.com>
+ */
+class PostAuthenticationGuardToken extends AbstractToken implements GuardTokenInterface
+{
+ private $providerKey;
+
+ /**
+ * @param UserInterface $user The user!
+ * @param string $providerKey The provider (firewall) key
+ * @param RoleInterface[]|string[] $roles An array of roles
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function __construct(UserInterface $user, $providerKey, array $roles)
+ {
+ parent::__construct($roles);
+
+ if (empty($providerKey)) {
+ throw new \InvalidArgumentException('$providerKey (i.e. firewall key) must not be empty.');
+ }
+
+ $this->setUser($user);
+ $this->providerKey = $providerKey;
+
+ // this token is meant to be used after authentication success, so it is always authenticated
+ // you could set it as non authenticated later if you need to
+ parent::setAuthenticated(true);
+ }
+
+ /**
+ * This is meant to be only an authenticated token, where credentials
+ * have already been used and are thus cleared.
+ *
+ * {@inheritdoc}
+ */
+ public function getCredentials()
+ {
+ return array();
+ }
+
+ /**
+ * Returns the provider (firewall) key.
+ *
+ * @return string
+ */
+ public function getProviderKey()
+ {
+ return $this->providerKey;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function serialize()
+ {
+ return serialize(array($this->providerKey, parent::serialize()));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function unserialize($serialized)
+ {
+ list($this->providerKey, $parentStr) = unserialize($serialized);
+ parent::unserialize($parentStr);
+ }
+}
diff --git a/Guard/Token/PreAuthenticationGuardToken.php b/Guard/Token/PreAuthenticationGuardToken.php
new file mode 100644
index 0000000..abbe985
--- /dev/null
+++ b/Guard/Token/PreAuthenticationGuardToken.php
@@ -0,0 +1,65 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Security\Guard\Token;
+
+use Symfony\Component\Security\Core\Authentication\Token\AbstractToken;
+
+/**
+ * The token used by the guard auth system before authentication.
+ *
+ * The GuardAuthenticationListener creates this, which is then consumed
+ * immediately by the GuardAuthenticationProvider. If authentication is
+ * successful, a different authenticated token is returned
+ *
+ * @author Ryan Weaver <ryan@knpuniversity.com>
+ */
+class PreAuthenticationGuardToken extends AbstractToken implements GuardTokenInterface
+{
+ private $credentials;
+ private $guardProviderKey;
+
+ /**
+ * @param mixed $credentials
+ * @param string $guardProviderKey Unique key that bind this token to a specific GuardAuthenticatorInterface
+ */
+ public function __construct($credentials, $guardProviderKey)
+ {
+ $this->credentials = $credentials;
+ $this->guardProviderKey = $guardProviderKey;
+
+ parent::__construct(array());
+
+ // never authenticated
+ parent::setAuthenticated(false);
+ }
+
+ public function getGuardProviderKey()
+ {
+ return $this->guardProviderKey;
+ }
+
+ /**
+ * Returns the user credentials, which might be an array of anything you
+ * wanted to put in there (e.g. username, password, favoriteColor).
+ *
+ * @return mixed The user credentials
+ */
+ public function getCredentials()
+ {
+ return $this->credentials;
+ }
+
+ public function setAuthenticated($authenticated)
+ {
+ throw new \LogicException('The PreAuthenticationGuardToken is *never* authenticated.');
+ }
+}
diff --git a/Guard/composer.json b/Guard/composer.json
new file mode 100644
index 0000000..1e0dc8c
--- /dev/null
+++ b/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/Guard/phpunit.xml.dist b/Guard/phpunit.xml.dist
new file mode 100644
index 0000000..093628b
--- /dev/null
+++ b/Guard/phpunit.xml.dist
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<phpunit backupGlobals="false"
+ backupStaticAttributes="false"
+ colors="true"
+ convertErrorsToExceptions="true"
+ convertNoticesToExceptions="true"
+ convertWarningsToExceptions="true"
+ processIsolation="false"
+ stopOnFailure="false"
+ syntaxCheck="false"
+ bootstrap="vendor/autoload.php"
+>
+ <php>
+ <ini name="error_reporting" value="-1" />
+ </php>
+
+ <testsuites>
+ <testsuite name="Symfony Security Component Guard Suite">
+ <directory>./Tests/</directory>
+ </testsuite>
+ </testsuites>
+
+ <filter>
+ <whitelist>
+ <directory>./</directory>
+ <exclude>
+ <directory>./vendor</directory>
+ <directory>./Tests</directory>
+ </exclude>
+ </whitelist>
+ </filter>
+</phpunit>
diff --git a/Http/EntryPoint/AuthenticationEntryPointInterface.php b/Http/EntryPoint/AuthenticationEntryPointInterface.php
index 0d7595d..df777f6 100644
--- a/Http/EntryPoint/AuthenticationEntryPointInterface.php
+++ b/Http/EntryPoint/AuthenticationEntryPointInterface.php
@@ -16,15 +16,25 @@ use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
- * AuthenticationEntryPointInterface is the interface used to start the
- * authentication scheme.
+ * Implement this interface for any classes that will be called to "start"
+ * the authentication process (see method for more details).
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface AuthenticationEntryPointInterface
{
/**
- * Starts the authentication scheme.
+ * Returns a response that directs the user to authenticate.
+ *
+ * This is called when an anonymous request accesses a resource that
+ * requires authentication. The job of this method is to return some
+ * response that "helps" the user start into the authentication process.
+ *
+ * Examples:
+ * A) For a form login, you might redirect to the login page
+ * return new Response('/login');
+ * B) For an API token authentication system, you return a 401 response
+ * return new Response('Auth header required', 401);
*
* @param Request $request The request that resulted in an AuthenticationException
* @param AuthenticationException $authException The exception that started the authentication process
diff --git a/Tests/Core/Authentication/Voter/AbstractVoterTest.php b/Tests/Core/Authentication/Voter/AbstractVoterTest.php
index af7b82f..ecf82fb 100644
--- a/Tests/Core/Authentication/Voter/AbstractVoterTest.php
+++ b/Tests/Core/Authentication/Voter/AbstractVoterTest.php
@@ -11,6 +11,7 @@
namespace Symfony\Component\Security\Tests\Core\Authentication\Voter;
+use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\AbstractVoter;
/**
@@ -46,6 +47,17 @@ class AbstractVoterTest extends \PHPUnit_Framework_TestCase
$this->assertEquals($expectedVote, $this->voter->vote($this->token, $object, $attributes), $message);
}
+ /**
+ * @dataProvider getData
+ * @group legacy
+ */
+ public function testVoteUsingDeprecatedIsGranted($expectedVote, $object, $attributes, $message)
+ {
+ $voter = new DeprecatedVoterFixture();
+
+ $this->assertEquals($expectedVote, $voter->vote($this->token, $object, $attributes), $message);
+ }
+
public function getData()
{
return array(
@@ -75,6 +87,26 @@ class VoterFixture extends AbstractVoter
return array('foo', 'bar', 'baz');
}
+ protected function voteOnAttribute($attribute, $object, TokenInterface $token)
+ {
+ return $attribute === 'foo';
+ }
+}
+
+class DeprecatedVoterFixture extends AbstractVoter
+{
+ protected function getSupportedClasses()
+ {
+ return array(
+ 'Symfony\Component\Security\Tests\Core\Authentication\Voter\ObjectFixture',
+ );
+ }
+
+ protected function getSupportedAttributes()
+ {
+ return array('foo', 'bar', 'baz');
+ }
+
protected function isGranted($attribute, $object, $user = null)
{
return $attribute === 'foo';
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 7747b37..c0dbb2d 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -15,6 +15,7 @@
<directory>./Acl/Tests/</directory>
<directory>./Core/Tests/</directory>
<directory>./Http/Tests/</directory>
+ <directory>./Guard/Tests/</directory>
</testsuite>
</testsuites>