-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[Security] Add Guard authenticator <supports> method #16835
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Note: as being not familiar with the recent deprecation mechanisms, i'm not sure i did it well in |
About tests, i get, i'm not sure how is this wrong or ok.
|
if (method_exists($guardAuthenticator, 'supports')) { | ||
if (true !== $guardAuthenticator->supports($request)) { | ||
return; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You add an interface but you check if the method exists here. Why not simply check if the interface is in use?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That was because i did not handled it this way originally. In fact now it would make more sense to check the interface implementation.
by the way @weaverryan , that's what i was talking with you this morning. |
I'm not sure we need this. It would probably have to duplicate some of the work done in getCredentials in many time. @weaverryan what do you think ? |
@Amo tests are failing because you are using the deprecated way in a non-legacy test. |
It's "clearer" in some ways because supports() can return a clean true/false value and then getCredentials() can cause an AuthException if it returns null, which would make it more consistent with getUser and checkCredentials. I discussed this at the conference - I'm mixed on it - the only negative I see is exactly what Stof mentions, but that is a practical concern. For an API token auth, I'll either have to duplicate code that gets/checks for the API token value in supports() and getUser() or just always return true from supports, which defeats the purpose. @Amo Can you convince us that this will be a better user experience? Thx! |
The idea is to separate the responsabilities of
and doing so, improve the readability, support of variations and testability of an authentication method.
As @stof said, it could end-up in duplicating the same checks in Let's take an example : Access to // ...
/**
* Ensure the given request provides an API token in the headers
*
* @param Request $request
*
* @return null|mixed
*/
public function supports(Request $request)
{
if (false === $request->headers->has('X-API-Token')) {
return false;
}
return true;
}
/**
* Extract the credentials from the request.
*
* @param Request $request
*
* @return string
*/
public function getCredentials(Request $request)
{
return $request->headers->get('X-API-Token'));
} If additional credentials retrieval methods are required (qs, payload ...) it is likely that the This separation
Where
And each dedicated authenticator provides the dedicated logic to retrieve credentials from header, query string or payload |
How can i avoid it ? googling about Symfony 3 and deprecation conventions didn't give any results. |
@Amo I think your arguments are quite good. So, let's see if we can complete this PR :). Forgetting about BC, how would you want the interface to look in 4.0? You added a new |
@Amo about the tests, the GuardAuthenticationListenerTest is mocking the GuardAuthenticatorInterface, which then triggers your deprecation warning. This mock should be changed to |
ok thanks, i'm on it |
Tests are still running. |
Should i fixup and |
I notice the Using a null logger instead of a PSR logger Move code out of the try catch which is unrelated
Out of all available methods in the authenticator, only Make After this the code will still be quite massive but it will at least be easier to read and understand what it does. |
@weaverryan What's the status of this one? |
@weaverryan I'd like to move this one forward. Can you have a look at it? Thanks. |
} | ||
|
||
// otherwise something went wrong and the authentication must fail | ||
throw new \InvalidArgumentException('Guard authenticator must provide user credentials'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A RuntimeException (or even better its child UnexpectedValueException) would be better. This is not an invalid argument here, but an invalid return value
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And with the message:
Did you forget to return some credentials from Foo\Bar\ClassName::getCredentials()? Credentials can be anything: an array, string or object.
Updated. |
* | ||
* @return mixed|null | ||
*/ | ||
public function getCredentials(Request $request); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Amo What do you think about actually removing getCredentials()
now? And instead changing getUser()
to be getUser(Request $request, UserProviderInterface $userProvider);
Now that getCredentials() only has the responsibility of returning the credentials, it seems simpler just to ask "give me a user" instead of breaking things into these 2 pieces.
Btw, if you agree, we will need to rename getUser() to something like fetchUser()
so we can add it to the new interface.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It could be simpler, but it would add much more complexity in the fetchUser
method.
This "merge" of the two responsabilities will make the fetchUser
method a bit more complex to unit test.
Aditionnaly, the UserProviderInterface
is known by the GuardAuthenticationProvider
and here we are in the GuardAuthenticationListener
so it needs many changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's just a question of "DX", which is subjective:
Before:
public function getCredentials(Request $request)
{
return $request->headers->get('Authorization');
}
public function getUser($credentials, UserProviderInterface $userProvider)
{
$authorizationHeader = $credentials;
// ...
}
After
public function fetchUser(Request $request, UserProviderInterface $userProvider)
{
$authorizationHeader = $request->headers->get('Authorization');
// ...
}
@javiereguiluz @iltar Do you guys have a preference?
@Amo This new method would be called from GuardAuthenticationProvider - we would set the entire request in the PreAuthenticationGuardToken
so that we could pass it to the new method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not 100% sure about the "fetch" part, it deviates from the currently popular find and get (and makes me feel like talking to my dog). DX wise I'd go for the second solution, however the first option gives you the ability to make a chain credential provider in symfony. That might be overkill and people can still implement a similar pattern in their fetchUser
. If getCredentials
only purpose is to be passed to getUser
, they should be merged imo.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then how about we name it findUser()
? I agree with you on the chaining - there may be a use for this, but I was also thinking: implement it yourself.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Personally I'd lean more towards getUser()
, this because it's rather confusing with the Repositories otherwise. By default those start with find
and it might confuse developers when those names start getting mixed up. Ideally I'd throw an exception in a get*
method if unable to get whatever was expected.
How would that work out if the could would always throw an exception if the user could not be found?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@iltar The problem with getUser()
is that we already have a method named that - and this would cause a signature change. So we need a new method name so we can keep the old one for BC. So, it's not idea. Perhaps getUserFromRequest()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's starting to sound more like a builder pattern;
$guard->buildUser(new UserFromRequestBuilder($request), $userProvider);
// potential to decouple from the request
$guard->buildUser(new UserFromConsoleBuilder($input, $output), $userProvider);
Anyway, getUserFromRequest()
sounds decent enough, what will happen to getUser()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can re-use the same method name. With get_func_args()
, it's possible to detect which version the user is using, like we've done in the past elsewhere in the framework. I'd go with the current getUser()
method name.
Thanks for the quick update @Amo - and sorry for the extra comments - I had to pause in the middle of reviewing :). Btw, when I talked about Guard at the last conference, someone else suggested this exact change afterwards. |
I've been updating the code according to the discussion about |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
UPGRADE-3.4.md
Outdated
@@ -294,7 +294,10 @@ Security | |||
|
|||
* Deprecated the HTTP digest authentication: `NonceExpiredException`, | |||
`DigestAuthenticationListener` and `DigestAuthenticationEntryPoint` will be | |||
removed in 4.0. Use another authentication system like `http_basic` instead. | |||
removed in 4.0. Use another authentication system like `http_basic` instead. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
extra space at end of line (same on the line below)
// abort the execution of the authenticator if it doesn't support the request. | ||
if ($guardAuthenticator instanceof GuardAuthenticatorInterface) { | ||
// deprecated since version 3.4, to be removed in 4.0 | ||
@trigger_error(sprintf('The %s interface is deprecated since version 3.4 and will be removed in 4.0. Implement %s instead.', GuardAuthenticatorInterface::class, AuthenticatorInterface::class), E_USER_DEPRECATED); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this runtime deprecation should be removed: it has already been trigger by DebugClassLoader
} | ||
|
||
// otherwise something went wrong and the authentication must fail | ||
throw new \UnexpectedValueException(sprintf( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should be on one line
* | ||
* @return null|Response | ||
*/ | ||
public function handleAuthenticationSuccess(TokenInterface $token, Request $request, GuardAuthenticatorInterface $guardAuthenticator, $providerKey) | ||
public function handleAuthenticationSuccess(TokenInterface $token, Request $request, AuthenticatorInterface $guardAuthenticator, $providerKey) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that's a BC break unfortunately (same for all public interfaces)
*/ | ||
interface GuardAuthenticatorInterface extends AuthenticationEntryPointInterface | ||
interface GuardAuthenticatorInterface extends AuthenticatorInterface |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should be done the other way around, to still allow passing legacy type-hints.
this would mean removing the runtime notice above, but DebugClassLoader will take care or triggering it anyway
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AuthenticatorInterface extends GuardAuthenticatorInterface
@@ -142,6 +142,6 @@ private function authenticateViaGuard(GuardAuthenticatorInterface $guardAuthenti | |||
|
|||
public function supports(TokenInterface $token) | |||
{ | |||
return $token instanceof GuardTokenInterface; | |||
return $token instanceof TokenInterface; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why this change?
@@ -56,7 +56,7 @@ public function __construct($guardAuthenticators, UserProviderInterface $userPro | |||
/** | |||
* Finds the correct authenticator for the token and calls it. | |||
* | |||
* @param GuardTokenInterface $token | |||
* @param TokenInterface|GuardTokenInterface $token |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I reverted this change as it should have been TokenInterface
from day 1. I'm going to fix it on 2.7
@nicolas-grekas comments addressed, thanks |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(but there are more FQCN strings that could be replaced by ...::class
)
Remaining FQCNs fixed |
*/ | ||
private function triggerRememberMe(GuardAuthenticatorInterface $guardAuthenticator, Request $request, TokenInterface $token, Response $response = null) | ||
private function triggerRememberMe($guardAuthenticator, Request $request, TokenInterface $token, Response $response = null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO we should be consistent in removing the type hint (like here) or not (like in GuardAuthenticatorHandler
below). Keeping the type hint might be a good idea to catch all places where we need to adjust them when removing code in 4.0.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 I re-added the typehint in this class so that it breaks if we forget to change them when removing the deprecated interface.
Thank you @Amo. |
…Amo, chalasr) This PR was merged into the 3.4 branch. Discussion ---------- [Security] Add Guard authenticator <supports> method This method will be called before starting an authentication against a guard authenticator. 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. | Q | A | | --- | --- | | Bug fix? | no | | New feature? | yes | | BC breaks? | yes | | Deprecations? | yes | | Tests pass? | yes | | Fixed tickets | none | | License | MIT | | Doc PR | | Commits ------- 78eecba Fix BC layer a7a6f8a [Security] Add Guard authenticator <supports> method
Thank you ! Now i'm a proud contributor of Symfony |
@Amo Thanks for this and congrats for your first PR on github! You did it right, in the right repo :) |
This PR was merged into the 4.0-dev branch. Discussion ---------- [Security] Remove GuardAuthenticatorInterface | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | no | BC breaks? | yes | Deprecations? | no | Tests pass? | yes | Fixed tickets | n/a | License | MIT | Doc PR | symfony/symfony-docs#8485 Removes BC layers for #16835. Commits ------- 3408152 [Security][Guard] Remove GuardAuthenticatorInterface
I am getting what I think is a false positive deprecation notice in 3.4:
I have added |
{ | ||
/** | ||
* {@inheritdoc} | ||
* | ||
* @deprecated since version 3.4, to be removed in 4.0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I remove this line, the false positive deprecation notice is removed. Should I open a PR?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please do. This method must be implemented by concrete subclasses and it is unlikely to be called directly.
…ardAuthenticator::supports() (kbond) This PR was merged into the 3.4 branch. Discussion ---------- [Security] remove invalid deprecation notice on AbstractGuardAuthenticator::supports() | Q | A | ------------- | --- | Branch? | 3.4 | Bug fix? | yes | New feature? | no | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #16835 (comment) | License | MIT | Doc PR | n/a This deprecation flag causes a false positive. Commits ------- 5fb44e7 [Guard] remove invalid deprecation notice
This method will be called before starting an authentication against a guard authenticator.
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.