Skip to content

Commit a7a6f8a

Browse files
Amochalasr
authored andcommitted
[Security] Add Guard authenticator <supports> method
This method will be called before starting an authentication against a guard authhenticator. The authentication will be tried only if the supports method returned <true> This improves understanding of code, increase consistency and removes responsability for <getCredentials> method To decide if the current request should be supported or not.
1 parent 2abe788 commit a7a6f8a

11 files changed

+435
-47
lines changed

UPGRADE-3.4.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,9 @@ SecurityBundle
302302
* Using voters that do not implement the `VoterInterface`is now deprecated in
303303
the `AccessDecisionManager` and this functionality will be removed in 4.0.
304304

305+
* Using guard authenticator that implement the `GuardAuthenticatorInterface` is now
306+
deprecated, this will be removed in 4.0. `AuthenticatorInterface` must be used now.
307+
305308
* `FirewallContext::getListeners()` now returns `\Traversable|array`
306309

307310
* `InitAclCommand::__construct()` now takes an instance of

UPGRADE-4.0.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -659,6 +659,9 @@ Security
659659
* The `RoleInterface` has been removed. Extend the `Symfony\Component\Security\Core\Role\Role`
660660
class instead.
661661

662+
* The `GuardAuthenticatorInterface` has been removed. Implement
663+
`Symfony\Component\Security\Guard\AuthenticatorInterface` class instead.
664+
662665
* The `LogoutUrlGenerator::registerListener()` method expects a 6th `string $context = null` argument.
663666

664667
* The `AccessDecisionManager::setVoters()` method has been removed. Pass the

src/Symfony/Component/Security/Guard/AbstractGuardAuthenticator.php

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Component\Security\Guard;
1313

14+
use Symfony\Component\HttpFoundation\Request;
1415
use Symfony\Component\Security\Core\User\UserInterface;
1516
use Symfony\Component\Security\Guard\Token\PostAuthenticationGuardToken;
1617

@@ -19,8 +20,24 @@
1920
*
2021
* @author Ryan Weaver <[email protected]>
2122
*/
22-
abstract class AbstractGuardAuthenticator implements GuardAuthenticatorInterface
23+
abstract class AbstractGuardAuthenticator implements AuthenticatorInterface
2324
{
25+
/**
26+
* Default implementation of the AuthenticatorInterface::supports method
27+
* As we still have the deprecated GuardAuthenticatorInterface, this method must be implemented here
28+
* Once GuardAuthenticatorInterface will be removed, this method should be removed too.
29+
*
30+
* @param Request $request
31+
*
32+
* @return bool
33+
*/
34+
public function supports(Request $request)
35+
{
36+
@trigger_error('The Symfony\Component\Security\Guard\AbstractGuardAuthenticator::supports default implementation is used. This is provided for backward compatibility on GuardAuthenticationInterface that is deprecated since version 3.1 and will be removed in 4.0. Provide your own implementation of the supports method instead.', E_USER_DEPRECATED);
37+
38+
return true;
39+
}
40+
2441
/**
2542
* Shortcut to create a PostAuthenticationGuardToken for you, if you don't really
2643
* care about which authenticated token you're using.
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Security\Guard;
13+
14+
use Symfony\Component\HttpFoundation\Request;
15+
use Symfony\Component\HttpFoundation\Response;
16+
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
17+
use Symfony\Component\Security\Core\Exception\AuthenticationException;
18+
use Symfony\Component\Security\Core\User\UserInterface;
19+
use Symfony\Component\Security\Core\User\UserProviderInterface;
20+
use Symfony\Component\Security\Guard\Token\GuardTokenInterface;
21+
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;
22+
23+
/**
24+
* The interface for all "guard" authenticators.
25+
*
26+
* The methods on this interface are called throughout the guard authentication
27+
* process to give you the power to control most parts of the process from
28+
* one location.
29+
*
30+
* @author Ryan Weaver <[email protected]>
31+
* @author Amaury Leroux de Lens <[email protected]>
32+
*/
33+
interface AuthenticatorInterface extends AuthenticationEntryPointInterface
34+
{
35+
/**
36+
* Does the authenticator support the given Request ?
37+
*
38+
* If this returns true, authentication will continue (e.g. getCredentials() will be called).
39+
* If false, this authenticator is done. The next (if any) authenticators will be called and
40+
* may authenticate the user, or leave the user as anonymous.
41+
*
42+
* @param Request $request
43+
*
44+
* @return bool
45+
*/
46+
public function supports(Request $request);
47+
48+
/**
49+
* Get the authentication credentials from the request and return them
50+
* as any type (e.g. an associate array). If you return null, authentication
51+
* will be skipped.
52+
*
53+
* Whatever value you return here will be passed to getUser() and checkCredentials()
54+
*
55+
* For example, for a form login, you might:
56+
*
57+
* return array(
58+
* 'username' => $request->request->get('_username'),
59+
* 'password' => $request->request->get('_password'),
60+
* );
61+
*
62+
* Or for an API token that's on a header, you might use:
63+
*
64+
* return array('api_key' => $request->headers->get('X-API-TOKEN'));
65+
*
66+
* @param Request $request
67+
*
68+
* @return mixed|null
69+
*/
70+
public function getCredentials(Request $request);
71+
72+
/**
73+
* Return a UserInterface object based on the credentials.
74+
*
75+
* The *credentials* are the return value from getCredentials()
76+
*
77+
* You may throw an AuthenticationException if you wish. If you return
78+
* null, then a UsernameNotFoundException is thrown for you.
79+
*
80+
* @param mixed $credentials
81+
* @param UserProviderInterface $userProvider
82+
*
83+
* @throws AuthenticationException
84+
*
85+
* @return UserInterface|null
86+
*/
87+
public function getUser($credentials, UserProviderInterface $userProvider);
88+
89+
/**
90+
* Returns true if the credentials are valid.
91+
*
92+
* If any value other than true is returned, authentication will
93+
* fail. You may also throw an AuthenticationException if you wish
94+
* to cause authentication to fail.
95+
*
96+
* The *credentials* are the return value from getCredentials()
97+
*
98+
* @param mixed $credentials
99+
* @param UserInterface $user
100+
*
101+
* @return bool
102+
*
103+
* @throws AuthenticationException
104+
*/
105+
public function checkCredentials($credentials, UserInterface $user);
106+
107+
/**
108+
* Creates an authenticated token for the given user.
109+
*
110+
* If you don't care about which token class is used or don't really
111+
* understand what a "token" is, you can skip this method by extending
112+
* the AbstractGuardAuthenticator class from your authenticator.
113+
*
114+
* @see AbstractGuardAuthenticator
115+
*
116+
* @param UserInterface $user
117+
* @param string $providerKey The provider (i.e. firewall) key
118+
*
119+
* @return GuardTokenInterface
120+
*/
121+
public function createAuthenticatedToken(UserInterface $user, $providerKey);
122+
123+
/**
124+
* Called when authentication executed, but failed (e.g. wrong username password).
125+
*
126+
* This should return the Response sent back to the user, like a
127+
* RedirectResponse to the login page or a 403 response.
128+
*
129+
* If you return null, the request will continue, but the user will
130+
* not be authenticated. This is probably not what you want to do.
131+
*
132+
* @param Request $request
133+
* @param AuthenticationException $exception
134+
*
135+
* @return Response|null
136+
*/
137+
public function onAuthenticationFailure(Request $request, AuthenticationException $exception);
138+
139+
/**
140+
* Called when authentication executed and was successful!
141+
*
142+
* This should return the Response sent back to the user, like a
143+
* RedirectResponse to the last page they visited.
144+
*
145+
* If you return null, the current request will continue, and the user
146+
* will be authenticated. This makes sense, for example, with an API.
147+
*
148+
* @param Request $request
149+
* @param TokenInterface $token
150+
* @param string $providerKey The provider (i.e. firewall) key
151+
*
152+
* @return Response|null
153+
*/
154+
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey);
155+
156+
/**
157+
* Does this method support remember me cookies?
158+
*
159+
* Remember me cookie will be set if *all* of the following are met:
160+
* A) This method returns true
161+
* B) The remember_me key under your firewall is configured
162+
* C) The "remember me" functionality is activated. This is usually
163+
* done by having a _remember_me checkbox in your form, but
164+
* can be configured by the "always_remember_me" and "remember_me_parameter"
165+
* parameters under the "remember_me" firewall key
166+
*
167+
* @return bool
168+
*/
169+
public function supportsRememberMe();
170+
}

src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use Symfony\Component\Security\Guard\Token\PreAuthenticationGuardToken;
1919
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
2020
use Symfony\Component\Security\Guard\GuardAuthenticatorInterface;
21+
use Symfony\Component\Security\Guard\AuthenticatorInterface;
2122
use Psr\Log\LoggerInterface;
2223
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
2324
use Symfony\Component\Security\Core\Exception\AuthenticationException;
@@ -28,6 +29,7 @@
2829
* Authentication listener for the "guard" system.
2930
*
3031
* @author Ryan Weaver <[email protected]>
32+
* @author Amaury Leroux de Lens <[email protected]>
3133
*/
3234
class GuardAuthenticationListener implements ListenerInterface
3335
{
@@ -39,11 +41,11 @@ class GuardAuthenticationListener implements ListenerInterface
3941
private $rememberMeServices;
4042

4143
/**
42-
* @param GuardAuthenticatorHandler $guardHandler The Guard handler
43-
* @param AuthenticationManagerInterface $authenticationManager An AuthenticationManagerInterface instance
44-
* @param string $providerKey The provider (i.e. firewall) key
45-
* @param iterable|GuardAuthenticatorInterface[] $guardAuthenticators The authenticators, with keys that match what's passed to GuardAuthenticationProvider
46-
* @param LoggerInterface $logger A LoggerInterface instance
44+
* @param GuardAuthenticatorHandler $guardHandler The Guard handler
45+
* @param AuthenticationManagerInterface $authenticationManager An AuthenticationManagerInterface instance
46+
* @param string $providerKey The provider (i.e. firewall) key
47+
* @param iterable|AuthenticatorInterface[] $guardAuthenticators The authenticators, with keys that match what's passed to GuardAuthenticationProvider
48+
* @param LoggerInterface $logger A LoggerInterface instance
4749
*/
4850
public function __construct(GuardAuthenticatorHandler $guardHandler, AuthenticationManagerInterface $authenticationManager, $providerKey, $guardAuthenticators, LoggerInterface $logger = null)
4951
{
@@ -92,20 +94,46 @@ public function handle(GetResponseEvent $event)
9294
}
9395
}
9496

95-
private function executeGuardAuthenticator($uniqueGuardKey, GuardAuthenticatorInterface $guardAuthenticator, GetResponseEvent $event)
97+
private function executeGuardAuthenticator($uniqueGuardKey, AuthenticatorInterface $guardAuthenticator, GetResponseEvent $event)
9698
{
9799
$request = $event->getRequest();
98100
try {
99101
if (null !== $this->logger) {
100102
$this->logger->debug('Calling getCredentials() on guard configurator.', array('firewall_key' => $this->providerKey, 'authenticator' => get_class($guardAuthenticator)));
101103
}
102104

105+
// abort the execution of the authenticator if it doesn't support the request.
106+
if ($guardAuthenticator instanceof GuardAuthenticatorInterface) {
107+
// it's a GuardAuthenticatorInterface
108+
// we support the previous behaviour to avoid BC break.
109+
$credentialsCanBeNull = true;
110+
@trigger_error('The Symfony\Component\Security\Guard\GuardAuthenticatorInterface interface is deprecated since version 3.1 and will be removed in 4.0. Use Symfony\Component\Security\Guard\Authenticator\GuardAuthenticatorInterface instead.', E_USER_DEPRECATED);
111+
} else {
112+
if (true !== $guardAuthenticator->supports($request)) {
113+
return;
114+
}
115+
// as there was a support for given request,
116+
// authenticator is expected to give not-null credentials.
117+
$credentialsCanBeNull = false;
118+
}
119+
103120
// allow the authenticator to fetch authentication info from the request
104121
$credentials = $guardAuthenticator->getCredentials($request);
105122

106-
// allow null to be returned to skip authentication
107123
if (null === $credentials) {
108-
return;
124+
// if GuardAuthenticatorInterface is used
125+
// allow null to skip authentication.
126+
if ($credentialsCanBeNull) {
127+
return;
128+
}
129+
130+
// otherwise something went wrong and the authentication must fail
131+
throw new \UnexpectedValueException(sprintf(
132+
'You must return some credentials from %s:getCredentials().
133+
To skip authentication, return false from %s::supports().',
134+
get_class($guardAuthenticator),
135+
get_class($guardAuthenticator)
136+
));
109137
}
110138

111139
// create a token with the unique key, so that the provider knows which authenticator to use
@@ -172,12 +200,12 @@ public function setRememberMeServices(RememberMeServicesInterface $rememberMeSer
172200
* Checks to see if remember me is supported in the authenticator and
173201
* on the firewall. If it is, the RememberMeServicesInterface is notified.
174202
*
175-
* @param GuardAuthenticatorInterface $guardAuthenticator
176-
* @param Request $request
177-
* @param TokenInterface $token
178-
* @param Response $response
203+
* @param AuthenticatorInterface $guardAuthenticator
204+
* @param Request $request
205+
* @param TokenInterface $token
206+
* @param Response $response
179207
*/
180-
private function triggerRememberMe(GuardAuthenticatorInterface $guardAuthenticator, Request $request, TokenInterface $token, Response $response = null)
208+
private function triggerRememberMe(AuthenticatorInterface $guardAuthenticator, Request $request, TokenInterface $token, Response $response = null)
181209
{
182210
if (null === $this->rememberMeServices) {
183211
if (null !== $this->logger) {

src/Symfony/Component/Security/Guard/GuardAuthenticatorHandler.php

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -61,14 +61,14 @@ public function authenticateWithToken(TokenInterface $token, Request $request)
6161
/**
6262
* Returns the "on success" response for the given GuardAuthenticator.
6363
*
64-
* @param TokenInterface $token
65-
* @param Request $request
66-
* @param GuardAuthenticatorInterface $guardAuthenticator
67-
* @param string $providerKey The provider (i.e. firewall) key
64+
* @param TokenInterface $token
65+
* @param Request $request
66+
* @param AuthenticatorInterface $guardAuthenticator
67+
* @param string $providerKey The provider (i.e. firewall) key
6868
*
6969
* @return null|Response
7070
*/
71-
public function handleAuthenticationSuccess(TokenInterface $token, Request $request, GuardAuthenticatorInterface $guardAuthenticator, $providerKey)
71+
public function handleAuthenticationSuccess(TokenInterface $token, Request $request, AuthenticatorInterface $guardAuthenticator, $providerKey)
7272
{
7373
$response = $guardAuthenticator->onAuthenticationSuccess($request, $token, $providerKey);
7474

@@ -88,14 +88,14 @@ public function handleAuthenticationSuccess(TokenInterface $token, Request $requ
8888
* Convenience method for authenticating the user and returning the
8989
* Response *if any* for success.
9090
*
91-
* @param UserInterface $user
92-
* @param Request $request
93-
* @param GuardAuthenticatorInterface $authenticator
94-
* @param string $providerKey The provider (i.e. firewall) key
91+
* @param UserInterface $user
92+
* @param Request $request
93+
* @param AuthenticatorInterface $authenticator
94+
* @param string $providerKey The provider (i.e. firewall) key
9595
*
9696
* @return Response|null
9797
*/
98-
public function authenticateUserAndHandleSuccess(UserInterface $user, Request $request, GuardAuthenticatorInterface $authenticator, $providerKey)
98+
public function authenticateUserAndHandleSuccess(UserInterface $user, Request $request, AuthenticatorInterface $authenticator, $providerKey)
9999
{
100100
// create an authenticated token for the User
101101
$token = $authenticator->createAuthenticatedToken($user, $providerKey);
@@ -110,14 +110,14 @@ public function authenticateUserAndHandleSuccess(UserInterface $user, Request $r
110110
* Handles an authentication failure and returns the Response for the
111111
* GuardAuthenticator.
112112
*
113-
* @param AuthenticationException $authenticationException
114-
* @param Request $request
115-
* @param GuardAuthenticatorInterface $guardAuthenticator
116-
* @param string $providerKey The key of the firewall
113+
* @param AuthenticationException $authenticationException
114+
* @param Request $request
115+
* @param AuthenticatorInterface $guardAuthenticator
116+
* @param string $providerKey The key of the firewall
117117
*
118118
* @return null|Response
119119
*/
120-
public function handleAuthenticationFailure(AuthenticationException $authenticationException, Request $request, GuardAuthenticatorInterface $guardAuthenticator, $providerKey)
120+
public function handleAuthenticationFailure(AuthenticationException $authenticationException, Request $request, AuthenticatorInterface $guardAuthenticator, $providerKey)
121121
{
122122
$token = $this->tokenStorage->getToken();
123123
if ($token instanceof PostAuthenticationGuardToken && $providerKey === $token->getProviderKey()) {

0 commit comments

Comments
 (0)