Skip to content

[Security] make TokenInterface::getUser() nullable to tell about unauthenticated tokens #42650

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

Merged
merged 1 commit into from
Aug 20, 2021

Conversation

nicolas-grekas
Copy link
Member

Q A
Branch? 5.4
Bug fix? no
New feature? no
Deprecations? no
Tickets -
License MIT
Doc PR -

As discussed in #42644
I think this might work well.

Copy link
Member

@chalasr chalasr left a comment

Choose a reason for hiding this comment

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

So it seems we have no other choice than to keep the concept of unauthenticated tokens in order to workaround the API incompatibility we have between the authentication layer and the authorization one.
I think we can work with that. Thank you

@ro0NL
Copy link
Contributor

ro0NL commented Aug 19, 2021

What about keeping isAuthenticated + a throwing getUser?

(i tend to prefer a random UnauthenticatedTokenException, rather than a random null value)

@nicolas-grekas
Copy link
Member Author

nicolas-grekas commented Aug 19, 2021

(i tend to prefer a random UnauthenticatedTokenException, rather than a random null value)

We should then make that UnauthenticatedTokenException part of the abstraction, and there could be specific implementation bugs (eg when isAuthenticated returns true while getUser throws).

I think I prefer making getUser nullable personally.

@chalasr
Copy link
Member

chalasr commented Aug 19, 2021

Also we come from getUser(): string|Stringable|UserInterface where string can mean anon.. Going with UserInterface|null looks like a smaller gap

@wouterj
Copy link
Member

wouterj commented Aug 19, 2021

Thanks! I surely prefer a nullable user over reintroducing isAuthenticated()/throwing an exception.

Nullable return types are not optimal. However, in this case 99% of the apps I see already do something like assert($user instanceof AppUser); or /** @var AppUser $user */, as the UserInterface contract is often not the useful contract for applications. This means that nullable isn't that bad as they already check for nullability.

Btw, I'm personally not sure if adding a new UnauthenticatedVoterInterface::votePublicAccess(array $attributes, mixed $subject) and removing NullToken is better than this one. But I don't have the time to try this out myself, so I'm ok with this PR if others agree.

@ro0NL
Copy link
Contributor

ro0NL commented Aug 19, 2021

when isAuthenticated returns true while getUser throws

it be a contract violation. I proposed it because of less deprecation hassle actually 😅 but yes, it's a try/catch shortcut sure.

going from a non-nulllable to nullable is a bigger gap IMHO.

However, in this case 99% of the apps I see already do something like assert($user instanceof AppUser);

hence my proposal.

@ro0NL
Copy link
Contributor

ro0NL commented Aug 19, 2021

and there could be specific implementation bugs
it's a try/catch shortcut sure

actually, if isAuthenticated() is public API specific tokens can do a more sophisticated check. Where isAuthenticated is leveraged in getUser().

(this can be achieved in both styles btw, but i still tend to prefer explicit isAuthenticated/UnauthenticatedTokenException somewhat :/)

even without isAuthenticated, a voter doing the try/catch is still 100% explicit.

@nicolas-grekas
Copy link
Member Author

A try/catch is not a nice API to me. nullability works just great. I tend to prefer APIs that don't open traps for implementation bugs with to-enforce correlations between their methods. isAuthenticated should not be used in getUser because they are different views of the same state - and not just correlated states.

votePublicAccess(array $attributes, mixed $subject)

that would require knowing about both interfaces to cover the domain, and that would require another interface for AccessDecisionManagerInterface::decide(). nullability packs everything together in a simple to discover API.

@ro0NL
Copy link
Contributor

ro0NL commented Aug 20, 2021

sorry, im not convinced (yet). Still i believe as a consumer i'd prefer

tend to prefer a random UnauthenticatedTokenException, rather than a random null value

But we'll be null checking in 99% of authentcated cases 👍

https://symfony.com/doc/current/security.html#retrieving-the-user-object null check is missing here btw.

I think if Security/Controller::getUser can convey authenticated yes/no thru nullability, im not sure the tokenstorage needs to.

Nevertheless a nullable Security/Controller::getUser is also pesky :)

From the tokenstorage layer i think TokenNotFoundException + new UnauthenticatedTokenException fits yes.

@ro0NL
Copy link
Contributor

ro0NL commented Aug 20, 2021

basically what im saying is, if you want a boolean value there's a access decision manager for it. But 99% the check is encapsulated IMHO.

@nicolas-grekas
Copy link
Member Author

nicolas-grekas commented Aug 20, 2021

symfony.com/doc/current/security.html#retrieving-the-user-object null check is missing here btw.

not needed because of the call to denyAccessUnlessGranted

I think if Security/Controller::getUser can convey authenticated yes/no thru nullability, im not sure the tokenstorage needs to.

Can you be more explicit about that please? Do you mean to always return a NullToken instead of throwing?

Nevertheless a nullable Security/Controller::getUser is also pesky :)

yep, we already have this nullable API elsewhere

From the tokenstorage layer i think TokenNotFoundException + new UnauthenticatedTokenException fits yes.

That's technically possible, I'm just not convinced it's better.
You tell about missing nullability checks, but with your proposal phpstorm will complain about missing try/catch :)

Another opinion or argument to help decide?

@ro0NL
Copy link
Contributor

ro0NL commented Aug 20, 2021

but with your proposal phpstorm will complain about missing try/catch

it's about runtime. Do we want to halt, or let a null value bubble unexpectedly.

btw im 100% aligned with

this might only be the expression of my ignorance of this subsystem :)

Here we put such wisdom on real tiles :) (https://en.wikipedia.org/wiki/Tegelspreuken)

/**
* Returns the current security token.
*
* @return TokenInterface|null
*/
public function getToken();

could we infer unauthenticated state here already? (edit: right, this will trigger our voter + NullToken issue)

what if we go back to an UnauthenticatedToken for internal usage (mostly)?

@wouterj
Copy link
Member

wouterj commented Aug 20, 2021

Thank you Nicolas!

@nicolas-grekas nicolas-grekas deleted the sec-user-null branch August 20, 2021 09:12
@ro0NL ro0NL mentioned this pull request Aug 21, 2021
nicolas-grekas added a commit that referenced this pull request Aug 25, 2021
…) (chalasr)

This PR was merged into the 5.4 branch.

Discussion
----------

[Security] Fix AuthenticationTrustResolver::isAnonymous()

| Q             | A
| ------------- | ---
| Branch?       | 5.4
| Bug fix?      | yes
| New feature?  | no
| Deprecations? | no
| Tickets       | #42726
| License       | MIT
| Doc PR        | -

This method wasn't checking if a token is null nor `$token->isAuthenticated()` until #42650.
Reverting that behavior change fixes tests on both 5.3 and 5.4

Commits
-------

83da786 [Security] Fix AuthenticationTrustResolver::isAnonymous()
This was referenced Nov 5, 2021
fabpot added a commit that referenced this pull request Mar 12, 2022
…lasr)

This PR was merged into the 5.4 branch.

Discussion
----------

[Security] Fix return value of `NullToken::getUser()`

| Q             | A
| ------------- | ---
| Branch?       | 5.4
| Bug fix?      | yes
| New feature?  | no
| Deprecations? | no
| Tickets       | #44909
| License       | MIT
| Doc PR        | -

We went back & forth on this one but according to the history, we've just forgot to do it in #42650.
Note: it's already `null` on 6.0+

Commits
-------

d892a51 Fix return value of `NullToken::getUser()`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants