Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

[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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged

Conversation

@chalasr
Copy link
Member

QA
Branch?2.8
Bug fix?yes
New feature?n/a
BC breaks?no
Deprecations?no
Tests pass?yes
Fixed tickets#26775
LicenseMIT
Doc PRn/a


$user =$authToken->getUser();

if (!$userinstanceof UserInterface) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

What if the user isAnon.? It will fail

Copy link
Contributor

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.

Copy link
Contributor

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 inNULL

Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Yea, butloadUserByUsername() will throw a UsernameNotFoundException. Should we throw a specific exception before?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

It will also handlenull:https://3v4l.org/Xrt8u

Copy link
MemberAuthor

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

jvasseur and someniatko reacted with thumbs up emoji
Copy link
Contributor

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 doreturn null; and it won't fail.

Copy link
Member

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)

someniatko reacted with thumbs up emoji
Copy link
Contributor

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".

$user =$this->userProvider->loadUserByUsername($username);
if (!$userinstanceof UserInterface) {
thrownewAuthenticationServiceException('The user provider must return a UserInterface object.');
}

Copy link
MemberAuthor

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

@nicolas-grekas
Copy link
Member

nicolas-grekas commentedApr 4, 2018
edited
Loading

wrong target branch? (fixed)

chalasr reacted with thumbs up emoji

@nicolas-grekasnicolas-grekas changed the base branch frommaster to2.8April 4, 2018 12:02
@chalasrchalasrforce-pushed thesimple-auth-user-checks branch 3 times, most recently fromf40ce0f to21f6216CompareApril 4, 2018 12:59
@chalasrchalasrforce-pushed thesimple-auth-user-checks branch from21f6216 toc318306CompareApril 4, 2018 13:01
$user =$this->userProvider->loadUserByUsername($user);

if (!$userinstanceof UserInterface) {
thrownewAuthenticationServiceException('The user provider must return a UserInterface object.');
Copy link
Contributor

@dmaicherdmaicherApr 4, 2018
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

should we also callsetToken for this one? forget this comment 😉

@nicolas-grekas
Copy link
Member

Thank you@chalasr.

@nicolas-grekasnicolas-grekas merged commitc318306 intosymfony:2.8Apr 4, 2018
nicolas-grekas added a commit that referenced this pull requestApr 4, 2018
…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/aCommits-------c318306 [Security] Load the user before pre/post auth checks when needed
@chalasrchalasr deleted the simple-auth-user-checks branchApril 4, 2018 13:34
This was referencedApr 6, 2018
@aschempp
Copy link
Contributor

Unfortunately, this implementation is broken as well.

  • If$user is notUserInterface, the new implementation calls theUserProviderwith the$user, which might be *an object implementing a__toString` method*.
  • TheUserProvider only expects a string to be the argument, so an object would be wrong.
  • Also, the implementation enforces the return value of the user provider to be aUserInterface. That again is incorrect, as the return value still could bean object implementing a__toString method.

The only viable solution is to only run the UserChecker if$user isinstanceof UserInterface.

fritzmg and leofeyer reacted with thumbs up emoji

@chalasr
Copy link
MemberAuthor

The UserProvider only expects a string to be the argument, so an object would be wrong.

Right, casting to string beforehand should be enough to fix that.

Also, the implementation enforces the return value of the user provider to be a UserInterface. That again is incorrect, as the return value still could be an object implementing a __toString method.

No, it mustreturn an instance of UserInterface.

I'll submit a patch tomorrow, thanks for reporting.

@aschempp
Copy link
Contributor

That will not solve the problem, at least not for us (Contao CMS).

For BC reasons, our current LTS version (Contao 4.4) is usinga custom Authenticator registeredin the firewall config. Our legacy user classes do not implement theUserInterface. We alsodon't actually load the user by username, but byfirewall scope. Based on the firewall name,users are loaded from legacy classes.

It's all rather complex, but worked perfectly fine until now. Our legacy user class is stored in the token, and thanks to the__toString method the actual username is shown in the profiler etc.


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 provideragain 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 aUserInterface on the second attempt, but that case is absolutely valid! There does not need to be aUserInterface, the user can always just be a string and be fully authenticated.

fritzmg reacted with thumbs up emoji

@linaori
Copy link
Contributor

the user can always just be a string and be fully authenticated.

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 theSimple* variants are not really the way to go anymore. I know it doesn't fix your problem, but Guard has better boundries and restricts far more, leading to less edge-cases and bugs.

I'm curious though, why a custom authenticator to eventually return an anonymous user? I personally think that you should not be altering theAnonymous behavior that Symfony has. So far I've seen all issues that occur with this change, related to anonymous users and strings in custom authenticators.

chalasr reacted with thumbs up emoji

@aschempp
Copy link
Contributor

I totally agreeGuardwould have been the way. We implemented this in Symfony 2.6 or 2.7, so there was no Guard. Since Contao 4.5 (latest) we switched to full Symfony authentication (which iskinda complex as well due to all the legacy stuff), but we're not using Simple Authentication anymore and our users actually do implementUserInterface now.

Still, I think there are valid use cases for not having aUserInterface. I did some work withShibboleth a while ago, where the actual authentication was done through the Apache server (mod_shibboleth). So you simply have a server variable that gives you a username. And for some cases that might just be enough to have aIS_FULLY_AUTHENTICATED user.

Regardless of examples, I think my last paragraph was still valid. It does not make sense to try to load a useragain from the user provider. The authenticator is responsible for that, and if there is noUserInterface then we just can't use theUserChecker.

@linaori
Copy link
Contributor

Still, I think there are valid use cases for not having a UserInterface.

I agree, and I'm 100% sure that we can make Symfonynever expose theUserInterface, by storing only authentication (identification) information in the token, rather than the user object. It would simplify a lot.

@chalasr
Copy link
MemberAuthor

Point is the contract saysUserInterface, you're on your own as soon as you return something else (see#26788 (comment)). That is consistent with the rest of the codebase which has the same kind of checks everywhere else (e.g. for guard authenticator'sgetUser() return value).
Considering that our BC promise doesn't cover its use case, can't Contao just make its user classes implement UserInterface?

The authenticator is responsible for that, and if there is no UserInterface then we just can't use the UserChecker.

That means we would have a different behavior when passing a username (or object with__toString() returning the username), not sure it's desirable.

At this stage I suggest to open a new issue to discuss about what can/should be done.

@aschempp
Copy link
Contributor

Considering that our BC promise doesn't cover its use case, can't Contao just make its user classes implement UserInterface?

I guess we could. It would be wonky as we're implementingUserInterface so the user can be passed toUserChecker which does nothing because the user is notAdvancedUserInterface. But yet, it would work… 😂

@linaori
Copy link
Contributor

@aschempp That should be solved if this RFC would be implemented:#26348

Add a NullUserChecker, which features 0 checks. In 5.0 this would become the default UserChecker in the configuration.

@aschempp
Copy link
Contributor

Unfortunately, there are more issues with the implementation. OurSimplePreAuthenticatorInterface implementation returns an anonymous token if there is no user logged in. This again will result in an exception because is now enforcing aUserInterface 😞

fritzmg and bedrandogan reacted with thumbs up emoji

@chalasr
Copy link
MemberAuthor

@aschempp see#26871

Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment

Reviewers

@stofstofstof left review comments

@nicolas-grekasnicolas-grekasnicolas-grekas approved these changes

+3 more reviewers

@dmaicherdmaicherdmaicher left review comments

@leofeyerleofeyerleofeyer approved these changes

@linaorilinaorilinaori left review comments

Reviewers whose approvals may not affect merge requirements

Assignees

No one assigned

Projects

None yet

Milestone

2.8

Development

Successfully merging this pull request may close these issues.

8 participants

@chalasr@nicolas-grekas@aschempp@linaori@stof@dmaicher@leofeyer@carsonbot

[8]ページ先頭

©2009-2025 Movatter.jp