Skip to content

[WIP] [Security] Fix #2172 User switching is not available for pre-authenticated users (oldest open bug!) #19059

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

Closed
wants to merge 8 commits into from

Conversation

pasdeloup
Copy link
Contributor

@pasdeloup pasdeloup commented Jun 15, 2016

Q A
Branch? 2.7
Bug fix? yes
New feature? no
BC breaks? maybe
Deprecations? no
Tests pass? no (!)
Fixed tickets #2172
License MIT
Doc PR no

TODO

  • gather feedback for my changes
  • fix the test according to feedback

For my first PR, I tried to fix the Symfony's oldest still open bug (2011!)
New tests have been added to reproduce the bug and are now passing.

To be reviewed carefully as it deals with security.

From my understanding, the problem comes from the AbstractPreAuthenticatedListener that is not stateless: whenever a token is passed, it has to preauthenticate again.
Problem, even if another type of token is passed (like a UsernamePasswordToken), it still tries to authenticate it and as it failed it creates a new one with the current user.
This behaviour doesn't work when switching user because the new artificial user token is automatically replaced by the current preauthenticated token.

So the patch bypass this check if (and only if) a token is already there (given by context), it is already correctly authenticated, and it is of another class than PreAuthenticatedToken (to limit risks of BC break).

Problem: this new behaviour breaks (by design) the following test:
Symfony\Component\Security\Http\Tests\Firewall\AbstractPreAuthenticatedListenerTest::testHandleWhenAuthenticationFailsWithDifferentToken

To be discussed: is this new behaviour correct (it may fix other bugs), or is there potential security issues? Should it be considered as a BC break?

$token = $this->tokenStorage->getToken();
if(null !== $token && false === ($token instanceof PreAuthenticatedToken) && $token->isAuthenticated()) {
if (null !== $this->logger) {
$this->logger->debug('An authenticated token is already provided, bypass PreAuthenticatedListener.', array('token' => (string) $this->tokenStorage->getToken()));
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd personally prefer to see the to string method being called here in case of refactoring one day

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You mean in the debug log?
I kept the same syntax than following logs.
But I realized I should use the $token variable in the log, don't need to recall the getter.


public static function setUpBeforeClass()
{
parent::deleteTmpDir('StandardFormLogin');
Copy link
Contributor

Choose a reason for hiding this comment

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

Why parent:: and not self:: or static::?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's the way all the tests have already been wrotten, I just kept the same syntax.
In this context, it's the same, and maybe the more obvious to understand.

Copy link
Contributor

Choose a reason for hiding this comment

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

If it's convention.. i guess it's okay then.

From my personal POV i think it's just weird... why bypass the current class layer?

@pasdeloup
Copy link
Contributor Author

Any feedback about this idea of bypassing the PreAuthenticatedListener check in case there is already an authenticated token provided in the context?

@@ -54,6 +54,15 @@ public function __construct(TokenStorageInterface $tokenStorage, AuthenticationM
*/
final public function handle(GetResponseEvent $event)
{
$token = $this->tokenStorage->getToken();
if(null !== $token && !$token instanceof PreAuthenticatedToken && $token->isAuthenticated()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Missing space if (null !== ... instead of if(null !==

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oups, I missed this one. Thanks.
Done!

@nicolas-grekas
Copy link
Member

I don't know the Security component enough to have an opinion here.
Yet, I think you should investigate the test failure, it might help understanding any implications (and make tests green :) )

@stof
Copy link
Member

stof commented Jun 15, 2016

This PR breaks BC by changing the behavior of pre-authenticated listeners when they find an existing token. So adding this in 2.7 is just a no-go. and regarding your question about the change being correct, I don't think it is. It can introduce other bugs.

Regarding the user-switching, the issue is that pre-authenticated listeners are meant to be used for stateless auth methods where the client sends the credentials again with each request and our user switching system works only for stateful authentication where the client authenticates once and then the auth is kept in the session (allowing to impersonate someone and have this info stored for subsequent requests).

If your project uses a stateless auth method, an impersonation feature would probably have to be implemented in a stateless way too (where the client remembers the fact that it wants to impersonate someone instead of the server remembering it).
This could be done for instance by sending a header X-Impersonate: nicolas-grekas in the request alongside your credentials. The impersonation would then happen when this header is here (and without redirecting after handling the impersonation of course, as we talk about a stateless feature where we impersonate for the current request).
The current system cannot work for stateless auth, and hacking other auth methods to make them behave in a stateful way would actually break them.

so I'm voting 👎 here

@nicolas-grekas
Copy link
Member

@stof any clue to help @pasdeloup fix the linked issue? Or do you consider it invalid?

@pasdeloup
Copy link
Contributor Author

So it would mean the issue itself should be closed?

It has been opened almost 5 years ago by developers using "non-stateless authentication with x509", so I suppose they mean they use a session so there must be a way to do something and the feature seems interesting.

However, I personnaly don't need it (for now), I just took the oldest issue and tried to understand and fix it, so if you say the issue itself is irrelevant, I won't spend more time on it and will pick up something else.

@pasdeloup
Copy link
Contributor Author

@stof @nicolas-grekas so what do we do? I close the PR and you close the corresponding issue or I try to work on a workaround without BC Break?

@fabpot
Copy link
Member

fabpot commented Jun 22, 2016

@pasdeloup I would add a note in the docs saying that impersonification is not supported for pre-auth and close both this PR and the issue.

@pasdeloup
Copy link
Contributor Author

pasdeloup commented Jun 22, 2016

Ok, I just submitted the PR #6673 for documentation.

@pasdeloup pasdeloup closed this Jun 22, 2016
@nicolas-grekas
Copy link
Member

Thanks for your help @pasdeloup. This wasn't merged but it's been definitely helpful. Waitinf for your next PR now :-)

@lisachenko
Copy link

It's 2017 year and looks like we still need this feature :)

Let me describe our case why it's needed. We have a lot of internal SF2 services that provide API and UI for our internal users (not clients). All users use x509 certificates with eToken devices to securely access our system with given privileges.

But sometime we experience a situation when there is an issue with our service and this issue is reproducing only for one single user. We can't just go and check it locally because we are international company with many offices across the world.

To reproduce this issue we currently have two options: remote connect via terminal like TeamViewer/RAdmin (it can be painfully slow connection speed for example for Nigeria) or we need to request same rights for our developer on production server to reproduce an issue which is not good, because production environment contains sensitive information that should not be available for all of our developers.

This is why I'm bumping this old PR. In my opinion, user switching could be a nice feature for stateless environment too, this can be either cookie (yes, for stateless authentication!) or a GET-parameter in the URL like ?_switch=Some.Username. Of course, this feature should be protected by separate privilege or role or even one-time tokens for developers.

Without user switching we are doing dirty hack: in app.php file we check for presence of special parameter that should contain certificate and override global variables like SSL_CLIENT_I_DN, SSL_CLIENT_S_DN to temporary have impersonation enabled. I don't like that raw ugly solution, so maybe we could bring this PR back to Security component?

Best regards, Alexander

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.

9 participants