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
Fixed #35328 - Improved debug messaging behind proxies. #18014
base: main
Are you sure you want to change the base?
Conversation
0df060c
to
95759af
Compare
REASON_BAD_ORIGIN = "Origin checking failed - %s does not match any trusted origins." | ||
REASON_BAD_ORIGIN = ( | ||
"Origin checking failed - %s does not match any trusted origins to server %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.
This ended up requiring a number of test changes. I think that this doesn't run afoul of our backward compatibility policy because I don't think that this is a documented property, but this seems like a likely place for a reviewer to have concerns.
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.
Yeah, it’s fine to change human-readable error messages because user code shouldn’t generally rely on them. Django’s tests for this code naturally need to check the messages though.
REASON_BAD_ORIGIN | ||
% ( | ||
request.META["HTTP_ORIGIN"], | ||
self._host_origin(request) or "<disallowed host>", |
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.
It would probably be even better to handle this with a dedicated reason, but I didn't want to delay submitting since even with the messaging like this since even as it is it seems like an improvement over the status quo.
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 think I agree this should have it's own reason
@@ -53,7 +79,7 @@ <h2>Help</h2> | |||
<li>Your browser is accepting cookies.</li> | |||
|
|||
<li>The view function passes a <code>request</code> to the template’s <a | |||
href="https://docs.djangoproject.com/en/dev/topics/templates/#django.template.backends.base.Template.render"><code>render</code></a> | |||
href="https://docs.djangoproject.com/en/{{ docs_version }}/topics/templates/#django.template.backends.base.Template.render"><code>render</code></a> |
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 is unrelated to my changes, except that I'm in here and noticed that it wasn't using the same version format for these doc urls.
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 can be a separate commit, merged independently 👍
Let's pull this out and keep the changeset really tight to the problem being solved
a8dadcf
to
dbbf194
Compare
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.
Thank you Ryan ⭐ I have initial comments
@@ -61,6 +65,16 @@ def csrf_failure(request, reason="", template_name=CSRF_FAILURE_TEMPLATE_NAME): | |||
"re-enable them, at least for this site, or for “same-origin” " | |||
"requests." | |||
), | |||
"bad_origin": reason.split(" - ")[0] == REASON_BAD_ORIGIN.split(" - ")[0], |
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.
"bad_origin": reason.split(" - ")[0] == REASON_BAD_ORIGIN.split(" - ")[0], | |
"bad_origin": reason.startswith("Origin checking failed"), |
Or maybe
REASON_BAD_ORIGIN_PREFIX = "Origin checking failed"
REASON_BAD_ORIGIN = f"{REASON_BAD_ORIGIN_PREFIX} - %s does not match any trusted origins to server %s."
and
"bad_origin": reason.startswith(REASON_BAD_ORIGIN_PREFIX),
not sure 🤔
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.
Just hardcode the string:
"bad_origin": reason.split(" - ")[0] == REASON_BAD_ORIGIN.split(" - ")[0], | |
"bad_origin": reason.startswith("Origin checking failed"), |
It’s easier to read and we have tests that will ensure it’s accurate.
REASON_BAD_ORIGIN | ||
% ( | ||
request.META["HTTP_ORIGIN"], | ||
self._host_origin(request) or "<disallowed host>", |
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 think I agree this should have it's own reason
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.
Thank you for your work here @ryanhiebert ! I have some comments with small improvements.
REASON_BAD_ORIGIN = "Origin checking failed - %s does not match any trusted origins." | ||
REASON_BAD_ORIGIN = ( | ||
"Origin checking failed - %s does not match any trusted origins to server %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.
Yeah, it’s fine to change human-readable error messages because user code shouldn’t generally rely on them. Django’s tests for this code naturally need to check the messages though.
REASON_BAD_ORIGIN = ( | ||
"Origin checking failed - %s does not match any trusted origins to server %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.
The new to server https://example.com
suffix is a bit inaccurate, since https://example.com
is technically an origin, not a server. How about instead:
REASON_BAD_ORIGIN = ( | |
"Origin checking failed - %s does not match any trusted origins to server %s." | |
) | |
REASON_BAD_ORIGIN = ( | |
"Origin checking failed - %r does not match %r or any other trusted origins." | |
) |
Also switching to %r
to quote the strings.
@@ -61,6 +65,16 @@ def csrf_failure(request, reason="", template_name=CSRF_FAILURE_TEMPLATE_NAME): | |||
"re-enable them, at least for this site, or for “same-origin” " | |||
"requests." | |||
), | |||
"bad_origin": reason.split(" - ")[0] == REASON_BAD_ORIGIN.split(" - ")[0], |
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.
Just hardcode the string:
"bad_origin": reason.split(" - ")[0] == REASON_BAD_ORIGIN.split(" - ")[0], | |
"bad_origin": reason.startswith("Origin checking failed"), |
It’s easier to read and we have tests that will ensure it’s accurate.
msg = REASON_BAD_ORIGIN % ( | ||
req.META["HTTP_ORIGIN"], | ||
"http://" + req.META["HTTP_HOST"], | ||
) |
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 these tests would be better with hardcoded strings - easier to read and debug, and avoiding the chance of incorrect logic.
https://abseil.io/resources/swe-book/html/ch12.html#donapostrophet_put_logic_in_tests
<code>{{x_forwarded_proto}}</code></li> | ||
<li><strong><code>X-Forwarded-Host</code></strong>: | ||
<code>{{x_forwarded_host}}</code></li> |
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.
<code>{{x_forwarded_proto}}</code></li> | |
<li><strong><code>X-Forwarded-Host</code></strong>: | |
<code>{{x_forwarded_host}}</code></li> | |
<code>{{ x_forwarded_proto }}</code></li> | |
<li><strong><code>X-Forwarded-Host</code></strong>: | |
<code>{{ x_forwarded_host }}</code></li> |
</pre></p> | ||
{% else %} | ||
If the expected server origin looks correct, | ||
you may wish to add the origin to the CSRF_TRUSTED_ORIGINS setting. |
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.
you may wish to add the origin to the CSRF_TRUSTED_ORIGINS setting. | |
you may wish to add the origin to the <code>CSRF_TRUSTED_ORIGINS</code> | |
setting. |
@@ -436,7 +444,12 @@ def process_view(self, request, callback, callback_args, callback_kwargs): | |||
if "HTTP_ORIGIN" in request.META: | |||
if not self._origin_verified(request): |
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 we restructure the code to avoid calling _host_origin
twice (within _origin_verified()
and then on line 451). So here could look like:
if not self._origin_verified(request): | |
host_origin = self._host_origin(request) | |
if not self._origin_verified(request, host_origin): |
And the error message could use host_origin or "<disallowed host>"
.
Trac ticket number
ticket-35328
Branch description
When in debug mode, we can make some better error messaging when the
Origin
header checking fails. In common scenarios, we can notice that they are likely missing headers from proxies. In any case, we can remove the verbose messaging around CSRF Tokens, which are not the way they have failed and are unhelpful to solving the error they are viewing.Checklist
main
branch.