Skip to content
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

API: Fix redirect issues #22285

Merged
merged 6 commits into from
Mar 11, 2020
Merged

API: Fix redirect issues #22285

merged 6 commits into from
Mar 11, 2020

Conversation

papagian
Copy link
Contributor

@papagian papagian commented Feb 18, 2020

What this PR does / why we need it:

The 6.6.1 has the following issues:

  • In this modification I had made the wrong assumption that the appSubUrl does not include the leading slash. As a result the redirect validation is not correct. In addition to this, validation should not consider correct paths like /<appSubUrl>blahblah.
  • Frontend prepends the subpath to redirects in addition to the backend (/grafana ends up to /grafana/grafana)

This fix addresses the above issues by removing the subpath from redirects in the backend.
However it does not work optimally when redirects occur in the backend before reaching the frontend (like in the OAuth flow) because the session cookie path is set to appSubUrl/ and requests without the appSubUrl can not read it.

This fixes the above issue by removing the trailing slash from the cookie path.

The reason for this is that addresses the causes of the problem instead of the symptoms.

This PR:

Which issue(s) this PR fixes:

Fixes #22227
Fixes #22461

Special notes for your reviewer:

I have tested it with the following scenarios:

@papagian papagian requested review from briangann, a team, marefr, torkelo and mckn and removed request for a team February 18, 2020 23:42
ctx.Redirect(redirectTo)
return
if err := hs.validateRedirectTo(redirectTo); err == nil {
middleware.DeleteCookie(ctx.Resp, "redirect_to", hs.cookieOptionsFromCfg)
Copy link
Contributor

Choose a reason for hiding this comment

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

happen to understand this enough to add a comment explaining why the cookie is deleted?

Copy link
Contributor

Choose a reason for hiding this comment

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

the cookie is no longer needed since it held the redirect_to location. this code path is going to send the browser to the new location, and should not retain the redirect_to cookie (could end up in a redirect loop, but mainly it is no longer needed).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

exactly; the cookie is deleted because it's has fulfilled its purpose.

@kylebrandt
Copy link
Contributor

Don't understand this enough to approve it, but an exemplar pull request message :-)

briangann
briangann previously approved these changes Feb 19, 2020
Copy link
Contributor

@briangann briangann left a comment

Choose a reason for hiding this comment

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

looks great!

pkg/api/login.go Show resolved Hide resolved
ctx.Redirect(redirectTo)
return
if err := hs.validateRedirectTo(redirectTo); err == nil {
middleware.DeleteCookie(ctx.Resp, "redirect_to", hs.cookieOptionsFromCfg)
Copy link
Contributor

Choose a reason for hiding this comment

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

the cookie is no longer needed since it held the redirect_to location. this code path is going to send the browser to the new location, and should not retain the redirect_to cookie (could end up in a redirect loop, but mainly it is no longer needed).

@consideRatio
Copy link
Contributor

I have not fully understood the details here, but I think it makes sense to use the logic from #22265 alongside this PR that allows cookies to be passed along to /grafana/ and /grafana paths and not only the former. My example for why I think so is the following.

  • Assume I have a grafana_session cookie already, but without Server: Don't include trailing slash in cookie path when hosting Grafana in a sub path #22265's logic, it's path value remains at /grafana/ as I understand it, because I don't think this PR influence the grafana_session cookie's path value.
  • Assume I visit mydomain.org/grafana, then my grafana_session cookie isn't passed along because I didn't include a trailing / in my host header, so I get redirected to /login instead of directly visiting Grafana's root path even though I'm already logged in.

Thanks for working on this!

@papagian
Copy link
Contributor Author

I have not fully understood the details here, but I think it makes sense to use the logic from #22265 alongside this PR that allows cookies to be passed along to /grafana/ and /grafana paths and not only the former. My example for why I think so is the following.

In that case, this validation returns error and the redirect_to cookie is ignored and delete it and the logged in user is navigated to the home page.

  • Assume I visit mydomain.org/grafana, then my grafana_session cookie isn't passed along because I didn't include a trailing / in my host header, so I get redirected to /login instead of directly visiting Grafana's root path even though I'm already logged in.

This is true.

Thanks for working on this!

@consideRatio
Your concerns are fair and I want also to test your modifications on top of these. I'm only a bit reluctant changing the cookie path from what is used to be until today because we had faced a number of issues related to cookie handling and there can be some cases we can't think of right now.

It's also a bit concerning that two different people have difficulty understanding the modifications in this PR therefore I would love some feedback from anybody with better insight than me.

@papagian
Copy link
Contributor Author

  • Assume I visit mydomain.org/grafana, then my grafana_session cookie isn't passed along because I didn't include a trailing / in my host header, so I get redirected to /login instead of directly visiting Grafana's root path even though I'm already logged in.

This is true.

I have pushed a modification for fixing this.

@consideRatio
Copy link
Contributor

consideRatio commented Feb 19, 2020

  • Assume I visit mydomain.org/grafana, then my grafana_session cookie isn't passed along because I didn't include a trailing / in my host header, so I get redirected to /login instead of directly visiting Grafana's root path even though I'm already logged in.

This is true.

I have pushed a modification for fixing this.

The fix will still make /grafana?and=query-params redirect to /grafana/login, but from /grafana/login, it will realize that the redirect_to cookie specifies that it should go to /grafana instead of /grafana/, so it will say "no! this is invalid" and then assume the user wants to go to /grafana/ where the authentication cookie is actually passed along.

It makes a visit to /grafana a bit less invalid, but it still disrupts the user by bounces that truncates potential query parameters.

Your concerns are fair and I want also to test your modifications on top of these. I'm only a bit reluctant changing the cookie path from what is used to be until today because we had faced a number of issues related to cookie handling and there can be some cases we can't think of right now.

This is the key discussion in my mind. Cookie handling is indeed a delicate and complicated matter. I have never really felt confident about them, but after the references you found I've started to feel that I understand them somewhat. That makes me want to tackle the question if the change suggested in #22265 actually would cause issues head on. If #22265 isn't going to cause issues, it would actually solve the issue I described above with the workaround in 4e52076 and remove the need to bounce the user to /home/login once just to get redirected again where the cookie is passed.

Copy link
Member

@marefr marefr left a comment

Choose a reason for hiding this comment

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

Sorry being late to the party. I probably lack a lot of context, but added some comments/questions.

@@ -47,7 +47,11 @@ func notAuthorized(c *m.ReqContext) {
return
}

WriteCookie(c.Resp, "redirect_to", url.QueryEscape(c.Req.RequestURI), 0, newCookieOptions)
redirectTo := c.Req.RequestURI
if setting.AppSubUrl != "" && !strings.HasPrefix(redirectTo, setting.AppSubUrl) {
Copy link
Member

Choose a reason for hiding this comment

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

Why doesn't c.Req.RequestURI include AppSubUrl? Would it help to use c.Req.URL instead to skip writing the appSubUrl in cookie?

c.Req.Request.URI: /grafana/d/5SdHCadmz/panel-tests-graph?orgId=1
c.Req.Request.URL.String(): /d/5SdHCadmz/panel-tests-graph?orgId=1

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Why doesn't c.Req.RequestURI include AppSubUrl?

As far as I understand, the user can request whatever URL.
For instance, the user can request http://localhost:3000 even though the subpath is /grafana.

In addition to this, we always prepended the subpath before this change which was introduced for fixing this problematic situation.

Probably now that we have validation for the redirects (and if it's working correctly) we no more need to prepend it
because invalid redirect paths are ignored.
But I think it's safer to keep it here, since it appends it only if it's missing.

@@ -104,9 +104,17 @@ export class LoginCtrl extends PureComponent<Props, State> {
const params = this.props.routeParams;
// Use window.location.href to force page reload
if (params.redirect && params.redirect[0] === '/') {
window.location.href = config.appSubUrl + params.redirect;
if (config.appSubUrl !== '' && !params.redirect.startsWith(config.appSubUrl)) {
Copy link
Member

Choose a reason for hiding this comment

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

Do we need frontend logic at all for redirect, couldn't the backend make sure to always return the correct url? Wait why are we using redirect query param here - thought we always had redirect in cookie?

papagian and others added 3 commits March 10, 2020 17:03
* Fix redirect validation

* Fix tests

* Add removed redirect validation comment

* Chore: Add test for parse of app url and app sub url

Co-authored-by: Marcus Efraimsson <marcus.efraimsson@gmail.com>
* Prepand subpath in redirect URL only if it's missing

* Change frontend to prepend the subpath only if needed
@marefr marefr requested review from briangann and removed request for a team March 10, 2020 15:42
papagian and others added 2 commits March 10, 2020 17:45
* Fix login view in case of invalid redirect

If the user is authenticated and
redirect_to cookie points to an invalid path,
the user should be navigated to the home page
instead of rendering the login page with error.

* Fix  TestLoginViewRedirect
Copy link
Member

@marefr marefr left a comment

Choose a reason for hiding this comment

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

A: Logout scenario with anonymous auth disabled:

  1. Login
  2. Browse dashboard X
  3. Clear cookies
  4. Refresh browser. Should be redirected to login page
  5. Login
  6. Should be redirected to dashboard X

B: Anonymous scenario:

  1. Browse dashboard X
  2. Click on login in sidemenu and redirect to login page
  3. Login
  4. Should be redirected to dashboard X

Root url default without sub path using Grafana auth:
A: Works as expected
B: Works as expected

Root url default without sub path using OAuth:
A: Works as expected
B: No, redirected to home page

Root url http://localhost:3000/grafana with serve_from_sub_path using Grafana auth:
A: Works as expected
B: Works as expected

Root url http://localhost:3000/grafana with serve_from_sub_path using OAuth:
A: Works as expected
B: No, redirected to home page

Root url http://grafana.myproxy.com:10080/grafana without serve_from_sub_path using nginx and Grafana auth:
A: Works as expected
B: Works as expected

Root url http://grafana.myproxy.com:10080/grafana without serve_from_sub_path using nginx and OAuth:
A: Works as expected
B: No, redirected to home page

Also confirmed this resolves #22227

@papagian Not sure if B scenario using OAuth is a new or existing regression? We could create a new issue if there's no existing one and fix that for 6.7 stable maybe?

@papagian
Copy link
Contributor Author

@papagian Not sure if B scenario using OAuth is a new or existing regression? We could create a new issue if there's no existing one and fix that for 6.7 stable maybe?

I was able to reproduce it in 6.5.2 and 6.6.2 and therefore I have filed a new issue

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
add to changelog type/bug type/epic Issue made of smaller issues
Projects
None yet
5 participants