Skip to content
This repository was archived by the owner on May 31, 2024. It is now read-only.

Commit e7d6919

Browse files
committed
Initial commit (but after some polished work) of the new Guard authentication system
1 parent 85fb36d commit e7d6919

10 files changed

+1124
-0
lines changed

Guard/AbstractGuardAuthenticator.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
namespace Symfony\Component\Security\Guard;
4+
5+
use Symfony\Component\Security\Core\User\UserInterface;
6+
use Symfony\Component\Security\Guard\Token\GenericGuardToken;
7+
8+
/**
9+
* An optional base class that creates a GenericGuardToken for you
10+
*
11+
* @author Ryan Weaver <[email protected]>
12+
*/
13+
abstract class AbstractGuardAuthenticator implements GuardAuthenticatorInterface
14+
{
15+
/**
16+
* Shortcut to create a GenericGuardToken for you, if you don't really
17+
* care about which authenticated token you're using
18+
*
19+
* @param UserInterface $user
20+
* @param string $providerKey
21+
* @return GenericGuardToken
22+
*/
23+
public function createAuthenticatedToken(UserInterface $user, $providerKey)
24+
{
25+
return new GenericGuardToken(
26+
$user,
27+
$providerKey,
28+
$user->getRoles()
29+
);
30+
}
31+
}
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
<?php
2+
3+
namespace Symfony\Component\Security\Guard\Firewall;
4+
5+
use Symfony\Component\HttpFoundation\Request;
6+
use Symfony\Component\HttpFoundation\Response;
7+
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
8+
use Symfony\Component\Security\Guard\GuardAuthenticatorHandler;
9+
use Symfony\Component\Security\Guard\Token\NonAuthenticatedGuardToken;
10+
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
11+
use Symfony\Component\Security\Guard\GuardAuthenticatorInterface;
12+
use Psr\Log\LoggerInterface;
13+
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
14+
use Symfony\Component\Security\Core\Exception\AuthenticationException;
15+
use Symfony\Component\Security\Http\Firewall\ListenerInterface;
16+
use Symfony\Component\Security\Http\RememberMe\RememberMeServicesInterface;
17+
18+
/**
19+
* Authentication listener for the "guard" system
20+
*
21+
* @author Ryan Weaver <[email protected]>
22+
*/
23+
class GuardAuthenticationListener implements ListenerInterface
24+
{
25+
private $guardHandler;
26+
private $authenticationManager;
27+
private $providerKey;
28+
private $guardAuthenticators;
29+
private $logger;
30+
private $rememberMeServices;
31+
32+
/**
33+
* @param GuardAuthenticatorHandler $guardHandler The Guard handler
34+
* @param AuthenticationManagerInterface $authenticationManager An AuthenticationManagerInterface instance
35+
* @param string $providerKey The provider (i.e. firewall) key
36+
* @param GuardAuthenticatorInterface[] $guardAuthenticators The authenticators, with keys that match what's passed to GuardAuthenticationProvider
37+
* @param LoggerInterface $logger A LoggerInterface instance
38+
*/
39+
public function __construct(GuardAuthenticatorHandler $guardHandler, AuthenticationManagerInterface $authenticationManager, $providerKey, $guardAuthenticators, LoggerInterface $logger = null)
40+
{
41+
if (empty($providerKey)) {
42+
throw new \InvalidArgumentException('$providerKey must not be empty.');
43+
}
44+
45+
$this->guardHandler = $guardHandler;
46+
$this->authenticationManager = $authenticationManager;
47+
$this->providerKey = $providerKey;
48+
$this->guardAuthenticators = $guardAuthenticators;
49+
$this->logger = $logger;
50+
}
51+
52+
/**
53+
* Iterates over each authenticator to see if each wants to authenticate the request
54+
*
55+
* @param GetResponseEvent $event
56+
*/
57+
public function handle(GetResponseEvent $event)
58+
{
59+
if (null !== $this->logger) {
60+
$this->logger->info('Checking for guard authentication credentials', array('firewall_key' => $this->providerKey, 'authenticators' => count($this->guardAuthenticators)));
61+
}
62+
63+
foreach ($this->guardAuthenticators as $key => $guardAuthenticator) {
64+
// get a key that's unique to *this* guard authenticator
65+
// this MUST be the same as GuardAuthenticationProvider
66+
$uniqueGuardKey = $this->providerKey.'_'.$key;
67+
68+
$this->executeGuardAuthenticator($uniqueGuardKey, $guardAuthenticator, $event);
69+
}
70+
}
71+
72+
private function executeGuardAuthenticator($uniqueGuardKey, GuardAuthenticatorInterface $guardAuthenticator, GetResponseEvent $event)
73+
{
74+
$request = $event->getRequest();
75+
try {
76+
if (null !== $this->logger) {
77+
$this->logger->info('Calling getCredentialsFromRequest on guard configurator', array('firewall_key' => $this->providerKey, 'authenticator' => get_class($guardAuthenticator)));
78+
}
79+
80+
// allow the authenticator to fetch authentication info from the request
81+
$credentials = $guardAuthenticator->getCredentialsFromRequest($request);
82+
83+
// allow null to be returned to skip authentication
84+
if (null === $credentials) {
85+
return;
86+
}
87+
88+
// create a token with the unique key, so that the provider knows which authenticator to use
89+
$token = new NonAuthenticatedGuardToken($credentials, $uniqueGuardKey);
90+
91+
if (null !== $this->logger) {
92+
$this->logger->info('Passing guard token information to the GuardAuthenticationProvider', array('firewall_key' => $this->providerKey, 'authenticator' => get_class($guardAuthenticator)));
93+
}
94+
// pass the token into the AuthenticationManager system
95+
// this indirectly calls GuardAuthenticationProvider::authenticate()
96+
$token = $this->authenticationManager->authenticate($token);
97+
98+
if (null !== $this->logger) {
99+
$this->logger->info('Guard authentication successful!', array('token' => $token, 'authenticator' => get_class($guardAuthenticator)));
100+
}
101+
102+
// sets the token on the token storage, etc
103+
$this->guardHandler->authenticateWithToken($token, $request);
104+
} catch (AuthenticationException $e) {
105+
// oh no! Authentication failed!
106+
107+
if (null !== $this->logger) {
108+
$this->logger->info('Guard authentication failed.', array('exception' => $e, 'authenticator' => get_class($guardAuthenticator)));
109+
}
110+
111+
$response = $this->guardHandler->handleAuthenticationFailure($e, $request, $guardAuthenticator);
112+
113+
if ($response instanceof Response) {
114+
$event->setResponse($response);
115+
}
116+
117+
return;
118+
}
119+
120+
// success!
121+
$response = $this->guardHandler->handleAuthenticationSuccess($token, $request, $guardAuthenticator, $this->providerKey);
122+
if ($response instanceof Response) {
123+
if (null !== $this->logger) {
124+
$this->logger->info('Guard authenticator set success response', array('response' => $response, 'authenticator' => get_class($guardAuthenticator)));
125+
}
126+
127+
$event->setResponse($response);
128+
} else {
129+
if (null !== $this->logger) {
130+
$this->logger->info('Guard authenticator set no success response: request continues', array('authenticator' => get_class($guardAuthenticator)));
131+
}
132+
}
133+
134+
// attempt to trigger the remember me functionality
135+
$this->triggerRememberMe($guardAuthenticator, $request, $token, $response);
136+
}
137+
138+
/**
139+
* Should be called if this listener will support remember me.
140+
*
141+
* @param RememberMeServicesInterface $rememberMeServices
142+
*/
143+
public function setRememberMeServices(RememberMeServicesInterface $rememberMeServices)
144+
{
145+
$this->rememberMeServices = $rememberMeServices;
146+
}
147+
148+
/**
149+
* Checks to see if remember me is supported in the authenticator and
150+
* on the firewall. If it is, the RememberMeServicesInterface is notified
151+
*
152+
* @param GuardAuthenticatorInterface $guardAuthenticator
153+
* @param Request $request
154+
* @param TokenInterface $token
155+
* @param Response $response
156+
*/
157+
private function triggerRememberMe(GuardAuthenticatorInterface $guardAuthenticator, Request $request, TokenInterface $token, Response $response = null)
158+
{
159+
if (!$guardAuthenticator->supportsRememberMe()) {
160+
return;
161+
}
162+
163+
if (null === $this->rememberMeServices) {
164+
if (null !== $this->logger) {
165+
$this->logger->info('Remember me skipped: it is not configured for the firewall', array('authenticator' => get_class($guardAuthenticator)));
166+
}
167+
168+
return;
169+
}
170+
171+
if (!$response instanceof Response) {
172+
throw new \LogicException(sprintf(
173+
'%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.',
174+
get_class($guardAuthenticator)
175+
));
176+
}
177+
178+
$this->rememberMeServices->loginSuccess($request, $response, $token);
179+
}
180+
}

Guard/GuardAuthenticatorHandler.php

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
<?php
2+
3+
namespace Symfony\Component\Security\Guard;
4+
5+
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
6+
use Symfony\Component\HttpFoundation\Request;
7+
use Symfony\Component\HttpFoundation\Response;
8+
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
9+
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
10+
use Symfony\Component\Security\Core\Exception\AuthenticationException;
11+
use Symfony\Component\Security\Core\User\UserInterface;
12+
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
13+
use Symfony\Component\Security\Http\SecurityEvents;
14+
15+
/**
16+
* A utility class that does much of the *work* during the guard authentication process
17+
*
18+
* By having the logic here instead of the listener, more of the process
19+
* can be called directly (e.g. for manual authentication) or overridden.
20+
*
21+
* @author Ryan Weaver <[email protected]>
22+
*/
23+
class GuardAuthenticatorHandler
24+
{
25+
private $tokenStorage;
26+
27+
private $dispatcher;
28+
29+
public function __construct(TokenStorageInterface $tokenStorage, EventDispatcherInterface $eventDispatcher = null)
30+
{
31+
$this->tokenStorage = $tokenStorage;
32+
$this->dispatcher = $eventDispatcher;
33+
}
34+
35+
/**
36+
* Authenticates the given token in the system
37+
*
38+
* @param TokenInterface $token
39+
* @param Request $request
40+
*/
41+
public function authenticateWithToken(TokenInterface $token, Request $request)
42+
{
43+
$this->tokenStorage->setToken($token);
44+
45+
if (null !== $this->dispatcher) {
46+
$loginEvent = new InteractiveLoginEvent($request, $token);
47+
$this->dispatcher->dispatch(SecurityEvents::INTERACTIVE_LOGIN, $loginEvent);
48+
}
49+
}
50+
51+
/**
52+
* Returns the "on success" response for the given GuardAuthenticator
53+
*
54+
* @param TokenInterface $token
55+
* @param Request $request
56+
* @param GuardAuthenticatorInterface $guardAuthenticator
57+
* @param string $providerKey The provider (i.e. firewall) key
58+
* @return null|Response
59+
*/
60+
public function handleAuthenticationSuccess(TokenInterface $token, Request $request, GuardAuthenticatorInterface $guardAuthenticator, $providerKey)
61+
{
62+
$response = $guardAuthenticator->onAuthenticationSuccess($request, $token, $providerKey);
63+
64+
// check that it's a Response or null
65+
if ($response instanceof Response || null === $response) {
66+
return $response;
67+
}
68+
69+
throw new \UnexpectedValueException(sprintf(
70+
'The %s::onAuthenticationSuccess method must return null or a Response object. You returned %s',
71+
get_class($guardAuthenticator),
72+
is_object($response) ? get_class($response) : gettype($response)
73+
));
74+
}
75+
76+
/**
77+
* Convenience method for authenticating the user and returning the
78+
* Response *if any* for success
79+
*
80+
* @param UserInterface $user
81+
* @param Request $request
82+
* @param GuardAuthenticatorInterface $authenticator
83+
* @param string $providerKey The provider (i.e. firewall) key
84+
* @return Response|null
85+
*/
86+
public function authenticateUserAndHandleSuccess(UserInterface $user, Request $request, GuardAuthenticatorInterface $authenticator, $providerKey)
87+
{
88+
// create an authenticated token for the User
89+
$token = $authenticator->createAuthenticatedToken($user, $providerKey);
90+
// authenticate this in the system
91+
$this->authenticateWithToken($token, $request);
92+
93+
// return the success metric
94+
return $this->handleAuthenticationSuccess($token, $request, $authenticator, $providerKey);
95+
}
96+
97+
/**
98+
* Handles an authentication failure and returns the Response for the
99+
* GuardAuthenticator
100+
*
101+
* @param AuthenticationException $authenticationException
102+
* @param Request $request
103+
* @param GuardAuthenticatorInterface $guardAuthenticator
104+
* @return null|Response
105+
*/
106+
public function handleAuthenticationFailure(AuthenticationException $authenticationException, Request $request, GuardAuthenticatorInterface $guardAuthenticator)
107+
{
108+
$this->tokenStorage->setToken(null);
109+
110+
$response = $guardAuthenticator->onAuthenticationFailure($request, $authenticationException);
111+
if ($response instanceof Response || null === $response) {
112+
// returning null is ok, it means they want the request to continue
113+
return $response;
114+
}
115+
116+
throw new \UnexpectedValueException(sprintf(
117+
'The %s::onAuthenticationFailure method must return null or a Response object. You returned %s',
118+
get_class($guardAuthenticator),
119+
is_object($response) ? get_class($response) : gettype($response)
120+
));
121+
}
122+
}

0 commit comments

Comments
 (0)