Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork9.7k
[Security][SecurityBundle] Enhance automatic logout url generation#20516
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
Uh oh!
There was an error while loading.Please reload this page.
Conversation
chalasr commentedNov 14, 2016
What about doing it in |
ogizanagi commentedNov 14, 2016 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
@chalasr : I'd like to. But the (we can provide an implementation in the |
ogizanagi commentedNov 14, 2016 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
Note that providing an implementation in the |
ogizanagi commentedNov 14, 2016 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
Let's get feedbacks about an implementation in the |
| useSymfony\Component\Security\Http\FirewallMapInterface; | ||
| useSymfony\Component\Security\Http\Logout\LogoutUrlGeneratorasBaseLogoutUrlGenerator; | ||
| class LogoutUrlGeneratorextends BaseLogoutUrlGenerator |
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 provide an interface and use decoration instead? IMHO it's not especially better here, but WDYT?
| { | ||
| parent::__construct($requestStack,$router,$tokenStorage); | ||
| $this->requestStack =$requestStack; |
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 could open the visibility instead if preferred, but I doubt it is.
| publicfunctiongetLogoutPath($key =null) | ||
| { | ||
| returnparent::getLogoutPath($key ===null ?$key =$this->extractKeyFromFirewallConfig() :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.
Should benull === $key ? $this->extractKeyFromFirewallConfig() : $key right?
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.
Absolutely... Good catch 😅
| publicfunctiongetLogoutPath($key =null) | ||
| { | ||
| returnparent::getLogoutPath($key !==null ?$key :$this->extractKeyFromFirewallConfig()); |
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.
Yoda :p
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.
Weak my attention is this evening. :)
chalasr commentedNov 14, 2016
Imho it is worth it 👍 |
ro0NL commentedNov 14, 2016 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
Shouldnt we technically still check What about |
ogizanagi commentedNov 14, 2016 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
That would made the
Why? 😕 |
chalasr commentedNov 14, 2016 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
Since the parent methods are called, the token storagewill be used if About the |
ro0NL commentedNov 14, 2016 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
Yeah.. but in case of null we pass Opposed to try {returnparent::getLogoutPath($key);}catch (\InvalidArgumentException$e) {if (null ===$key &&null !==$defaultKey =$this->extractKeyFromFirewallConfig()) {returnparent::getLogoutPath($defaultKey); }throw$e;} |
ogizanagi commentedNov 14, 2016
Why? |
ro0NL commentedNov 14, 2016
Again not sure :) just imagining one could actually be authenticated against a different firewall (ie. identified by its current token) other then the current firewall (ie. identified by request?) |
ogizanagi commentedNov 14, 2016
Correction: |
ogizanagi commentedNov 14, 2016 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
Then I'm not confident about this PR anymore. Even by changing the code by: try {returnparent::getLogoutPath($key);}catch (\InvalidArgumentException$e) {if (null ===$key &&null !==$defaultKey =$this->extractKeyFromFirewallConfig()) {returnparent::getLogoutPath($defaultKey); }throw$e;} as you've suggested@ro0NL. The issue is that it's probably wrong to suggest to logout using the wrong logout url, even in last resort. 😕 (and you may still not find the logout listener) |
ro0NL commentedNov 14, 2016 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
You should return a key only if |
ogizanagi commentedNov 14, 2016 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
The originalLogoutUrlGenerator will already throw an exception if the logout listener does not exists for this firewall. That's not what I meant by:
I meant you may still be behind a firewall on which is not defined the Anyway, I think I'm closing this one, because it brings more issues than it solves. It's not even an issue I encountered, but I thought it could be a good usage of the new Thank you for your time. :) |
ro0NL commentedNov 15, 2016 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
@ogizanagi im really considering this a DX improvement.. and we were almost there, right? What's wrong with the exception in case you're passing the key from firewall config and it has no listeners? In user-land if someone calls this, and there is absolutely no available url it already crashes anyway. The only difference would be (in case of null) we get Instead of If we add a |
ogizanagi commentedNov 15, 2016 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
Nothing wrong with the exception. What would appear to be "wrong" to me is to generate the logout url with the wrong firewall and logout listener (not the one which generated the token). I mean...it's not fundamentally wrong: AFAIK, given two firewalls sharing a same context, with only one defining the logout listener, the logout url is actually available for both firewalls and will invalidate the token, doesn't matter which firewall created it. But is this PR really worth it, considering it won't give you 100% chances to get the logout url (as the logout listener might be on another firewall than the current one)? (you don't have 100% chances currently neither of course, but the implementation is sufficient to me) I'm reopening this in order to get more feedbacks about it, but I don't expect much actually 😕
Still not convinced about this because it'll make the |
chalasr commentedNov 15, 2016
Sure, but still more chances than if kept as is so, for what it costs, I would still say yes. Since the token's provider key would be the better alternative if provided (right?), shouldn't it be indeed checked at first? |
| /** | ||
| * @author Maxime Steinhausser <maxime.steinhausser@gmail.com> | ||
| */ | ||
| class LogoutUrlGeneratorextends BaseLogoutUrlGenerator |
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.
Couldn't this be final?
ogizanagiNov 15, 2016 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
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.
Could be. Or could be removed entirely in favor of a request listener injecting the current firewall key in the originalUrlLogoutListener as suggested by@ro0NL with theLogoutUrlGenerator::setDefaultProviderKey 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.
Imho decorating it is fine.
ogizanagi commentedNov 15, 2016 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
It won't give more chances, but is still more accurate yes. I agree with that and@ro0NL's suggestion about checking it first, and then fallback to the current firewall key if no logout listener is found. |
chalasr commentedNov 19, 2016
Status: needs work :) |
ogizanagi commentedNov 19, 2016 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
I've updated the PR by adding a new commit as a POC for using firewall contexts and a listener setting the current firewall name + context per request (no more When not providing the firewall key:
The behavior remains unchanged when providing explicitly the firewall key. No fallback.
|
ro0NL left a comment
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.
👍 for this approach.
| } | ||
| if (null ===$key) { | ||
| if (null ===$key &&null ===$this->currentFirewall) { |
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 about moving this check togetListener?
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 prefer not: I'd rather keep the$key argument mandatory for callinggetListener()
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 it's not :) we still passnull if a current firewall is present.
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.
@ro0NL : Yup. Just saw that and was doing the change 😅
| } | ||
| /** | ||
| * @param string $key The firewall key |
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.
string|null, we can infer$tryFromCurrent by doingnull === $key here as well.
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 we can't in case the$key was extracted from the token. It'll not be null, but we still like to guess if the listener is not 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.
I've refactored everything in order to hide the whole listener extraction logic insidegetListener so this argument isn't necessary anymore.
| */ | ||
| privatefunctiongetListener($key,$tryFromCurrent =false) | ||
| { | ||
| if (array_key_exists($key,$this->listeners)) { |
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.
Can beisset
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.
Could be. That's only legacy code unchanged.
| // Fetch from injected current firewall information, if possible | ||
| list($key,$context) =$this->currentFirewall; | ||
| if (array_key_exists($key,$this->listeners)) { |
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,isset.
| return$this->doGenerateLogoutUrl($referenceType,$logoutPath,$csrfTokenId,$csrfParameter,$csrfTokenManager); | ||
| } | ||
| privatefunctiondoGenerateLogoutUrl($referenceType,$logoutPath,$csrfTokenId,$csrfParameter,CsrfTokenManagerInterface$csrfTokenManager =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.
Really needed?
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.
Not really needed, but helps to unclutter up previous method.
| } | ||
| if (null !==$context) { | ||
| return$this->getListenerForContext($context); |
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 can write outgetListenerForContext here.
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.
👍
| { | ||
| $guess =$key ===null; | ||
| // Fetch the current provider key from token, if possible |
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.
Lets try to avoid$guess.
null === $key && ..
| $key =$token->getProviderKey(); | ||
| } | ||
| } | ||
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.
Can (then) be removed.
| if (null ===$key &&null ===$this->currentFirewall) { | ||
| thrownew \InvalidArgumentException('Unable to find the current firewall LogoutListener, please provide the provider key manually.'); | ||
| } | ||
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.
Can (then) be removed.
| if (isset($this->listeners[$key])) { | ||
| return$this->listeners[$key]; | ||
| } | ||
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 (null === $key && null !== $this->currentFirewall) {
| * | ||
| * @return string The logout URL | ||
| * | ||
| * @throws \InvalidArgumentException if no LogoutListener is registered for the key or the key could not be found automatically. |
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.
string
| } | ||
| /** | ||
| * @param string $key The current firewall key |
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.
string|null
| * @param string $csrfTokenId The ID of the CSRF token | ||
| * @param string $csrfParameter The CSRF token parameter name | ||
| * @param CsrfTokenManagerInterface $csrfTokenManager A CsrfTokenManagerInterface instance | ||
| * @param string $context The listener context |
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.
string|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.
Should (then) be
if (null ===$key) {// throw Unable to find the current firewall LogoutListener, please provide the provider key manually.}else {// throw No LogoutListener found for firewall key "%s".}
ogizanagi commentedNov 20, 2016 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
@ro0NL : I thinkb29b004?w=1 should cover your expectations for your last review (However it's hard to follow small comments for such changes while ensuring it does not change any behavior (tests will of course be required if we want to merge this). Could you provide a full diff next time instead?). Anyway, thank you for your review. ;) |
ro0NL commentedNov 20, 2016
Ship it. |
| publicfunctiononKernelFinishRequest(FinishRequestEvent$event) | ||
| { | ||
| if ($event->isMasterRequest()) { | ||
| $this->logoutUrlGenerator->setCurrentFirewall(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.
This setsarray(null, null) though, letsallow it and unset$currentFirewall in that case (yes, ignoring$context if so.. perhaps throw ).
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.
Simplified by0c5628c?w=1
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.
0bb5dd7 was good? Now$currentFirewall can be null, an array, an array with nulls. Which isnt checked for accordingly (seelist and the upcomingisset (where$key can be null again ^^).
ogizanagiNov 20, 2016 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
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.
IMHO, it doesn't matter much how the current firewall informations are stored internally (in this case, it aims to simplify the code. The multiple possible values aren't an issue, as we're expecting to use it withlist), andisset($this->listeners[$key]) wouldn't be an issue even with$key as 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.
Ok,list($a, $b) = null doesnt crashes :) Fair enough. I'd prefer the previous commit as it was explicit.
Just saying we run code that's not actually needed. And we implicitly allowsetCurrentFirewall(null, 'context') which im not sure is intended / should be allowed.
fabpot commentedMar 1, 2017
@ogizanagi What's the status of this PR? |
ogizanagi commentedMar 1, 2017 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
@fabpot: Squashed, rebased on master and tested back on the symfony demo using the patch mentioned in#20516 (comment). Tests are green on my side. No more changes planned on my side. It actually enhances the situation for cases mentioned in the PR description and should always find and use the proper logout listener across contexts (as soon as there is one of course). So I think it's ready for the final review and validating the strategy used. :) |
| if (__CLASS__ !==get_class($this)) { | ||
| $r =new \ReflectionMethod($this,__FUNCTION__); | ||
| if (__CLASS__ !==$r->getDeclaringClass()->getName()) { | ||
| @trigger_error(sprintf('Method %s() will have a sixth `$context = null` argument in version 4.0. Not defining it is deprecated since 3.3.',get_class($this),__FUNCTION__),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.
Method %s::%s() ...
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 even use__METHOD__ instead. (671694d#diff-98543741515f0201292fdfa41ea3c412R97)
| // Fetch from injected current firewall information, if possible | ||
| list($key,$context) =$this->currentFirewall; | ||
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.
Perhaps addnull !== $key to be explicit.
fabpot commentedMar 22, 2017
Thank you@ogizanagi. |
…l generation (ogizanagi)This PR was merged into the 3.3-dev branch.Discussion----------[Security][SecurityBundle] Enhance automatic logout url generation| Q | A| ------------- | ---| Branch? | master| Bug fix? | no| New feature? | yes| BC breaks? | no| Deprecations? | yes| Tests pass? | yes| Fixed tickets | N/A| License | MIT| Doc PR | N/AThis should help whenever:- [the token does not implement the `getProviderKey` method](https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Security/Http/Logout/LogoutUrlGenerator.php#L89-L99)- you've got multiple firewalls sharing a same context but a logout listener only define on one of them.##### Behavior:> When not providing the firewall key:>>- Try to find the key from the token (unless it's an anonymous token)>- If found, try to get the listener from the key. If the listener is found, stop there.>- Try from the injected firewall key. If the listener is found, stop there.>- Try from the injected firewall context. If the listener is found, stop there.>>The behavior remains unchanged when providing explicitly the firewall key. No fallback.Commits-------5b7fe85 [Security][SecurityBundle] Enhance automatic logout url generation
This PR was merged into the 3.3-dev branch.Discussion----------Fix deprecation message| Q | A| ------------- | ---| Branch? | master| Bug fix? | yes| New feature? | no| BC breaks? | no| Deprecations? | no| Tests pass? | yes| Fixed tickets |#20516 (comment)| License | MIT| Doc PR | N/AMy bad, it seems I've never pushed the fix for#20516 (comment) :/Commits-------57427cc Fix deprecation message
Uh oh!
There was an error while loading.Please reload this page.
This should help whenever:
getProviderKeymethodBehavior: