-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[Security] Load the user before pre/post auth checks when needed #26788
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
chalasr
commented
Apr 4, 2018
Q | A |
---|---|
Branch? | 2.8 |
Bug fix? | yes |
New feature? | n/a |
BC breaks? | no |
Deprecations? | no |
Tests pass? | yes |
Fixed tickets | #26775 |
License | MIT |
Doc PR | n/a |
@@ -45,6 +46,11 @@ public function authenticate(TokenInterface $token) | |||
} | |||
|
|||
$user = $authToken->getUser(); | |||
|
|||
if (!$user instanceof UserInterface) { |
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.
What if the user is Anon.
? It will fail
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.
No it will not. instanceof
handles strings.
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 will call this: $user = $this->userProvider->loadUserByUsername('Anon.');
, which will fail to retrieve the user and result in 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.
Yea, but loadUserByUsername()
will throw a UsernameNotFoundException. Should we throw a specific exception before?
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 will also handle null
: https://3v4l.org/Xrt8u
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.
* @return UserInterface
does enforce it to me, it doesn't allow 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.
Except that people can just do return null;
and it won't fail.
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.
well, if they don't respect the contract of the interface, they enter unknown land. Things might break badly at any time (and the typehint error on the next call is such a failure), and the failure can even change in any Symfony patch release. We don't guarantee any backward compatibility for code not respecting the interface contract (and yes, any new API added in Symfony 4.1+ will use return types to enforce it better)
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.
While I fully agree, sadly (especially older implementations) are often based of "reverse engineered" authenticators, due to lack of better (SF2.0~2.6). Thus, I can't fully blame developers for making this "mistake".
symfony/src/Symfony/Component/Security/Core/Authentication/Provider/DaoAuthenticationProvider.php
Lines 74 to 78 in 9f1c017
$user = $this->userProvider->loadUserByUsername($username); | |
if (!$user instanceof UserInterface) { | |
throw new AuthenticationServiceException('The user provider must return a UserInterface object.'); | |
} |
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.
same check added for consistency
|
f40ce0f
to
21f6216
Compare
21f6216
to
c318306
Compare
$user = $this->userProvider->loadUserByUsername($user); | ||
|
||
if (!$user instanceof UserInterface) { | ||
throw new AuthenticationServiceException('The user provider must return a UserInterface object.'); |
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 we also call forget this comment 😉setToken
for this one?
Thank you @chalasr. |
…needed (chalasr) This PR was merged into the 2.8 branch. Discussion ---------- [Security] Load the user before pre/post auth checks when needed | Q | A | ------------- | --- | Branch? | 2.8 | Bug fix? | yes | New feature? | n/a | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #26775 | License | MIT | Doc PR | n/a Commits ------- c318306 [Security] Load the user before pre/post auth checks when needed
Unfortunately, this implementation is broken as well.
The only viable solution is to only run the UserChecker if |
Right, casting to string beforehand should be enough to fix that.
No, it must return an instance of UserInterface. I'll submit a patch tomorrow, thanks for reporting. |
That will not solve the problem, at least not for us (Contao CMS). For BC reasons, our current LTS version (Contao 4.4) is using a custom Authenticator registered in the firewall config. Our legacy user classes do not implement the It's all rather complex, but worked perfectly fine until now. Our legacy user class is stored in the token, and thanks to the Now, casting the user object to a string would not work, because the object's username would not be suitable for our user provider. I also think the whole approach is flawed. The code is essentially calling the user provider again to get a user. It did not get one in the first place, why would it get one if you call it again? Even worse, we're cancelling the authentication if the user provider does not return a |
And this is one of the reasons why the User object should be restricted as data object and be limited to the security internal behavior, never be exposed to the outside. While I agree that the current approach is not really ideal, I think that the I'm curious though, why a custom authenticator to eventually return an anonymous user? I personally think that you should not be altering the |
I totally agree Still, I think there are valid use cases for not having a Regardless of examples, I think my last paragraph was still valid. It does not make sense to try to load a user again from the user provider. The authenticator is responsible for that, and if there is no |
I agree, and I'm 100% sure that we can make Symfony never expose the |
Point is the contract says
That means we would have a different behavior when passing a username (or object with At this stage I suggest to open a new issue to discuss about what can/should be done. |
I guess we could. It would be wonky as we're implementing |
Unfortunately, there are more issues with the implementation. Our |