-
Notifications
You must be signed in to change notification settings - Fork 439
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
Enable CSRF protection globally by default #9334
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,6 +30,8 @@ def create_app(_global_config, **settings): # pragma: no cover | |
|
||
|
||
def includeme(config): # pragma: no cover | ||
config.set_default_csrf_options(require_csrf=True) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is what we're doing instead of always subclassing a |
||
|
||
config.scan("h.subscribers") | ||
|
||
config.add_tween("h.tweens.conditional_http_tween_factory", under=EXCVIEW) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,10 +5,13 @@ | |
form templates in preference to the defaults. | ||
""" | ||
|
||
from functools import partial | ||
|
||
import deform | ||
import pyramid_jinja2 | ||
from markupsafe import Markup | ||
from pyramid import httpexceptions | ||
from pyramid.csrf import get_csrf_token | ||
from pyramid.path import AssetResolver | ||
|
||
from h import i18n | ||
|
@@ -46,6 +49,10 @@ def __call__(self, template_name, **kwargs): | |
context = self._system.copy() | ||
context.update(kwargs) | ||
|
||
context.setdefault( | ||
"get_csrf_token", partial(get_csrf_token, context["request"]) | ||
) | ||
Comment on lines
+52
to
+54
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Adding Pyramid's TODO: There are lots of templates throughout the codebase that do their own |
||
|
||
return Markup(template.render(context)) | ||
|
||
|
||
|
@@ -75,7 +82,7 @@ def create_form(request, *args, **kwargs): | |
default) will use the renderer configured in the :py:mod:`h.form` module. | ||
""" | ||
env = request.registry[ENVIRONMENT_KEY] | ||
renderer = Jinja2Renderer(env, {"feature": request.feature}) | ||
renderer = Jinja2Renderer(env, {"feature": request.feature, "request": request}) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Passing |
||
kwargs.setdefault("renderer", renderer) | ||
|
||
return deform.Form(*args, **kwargs) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,7 @@ | |
class="form {{ field.css_class or '' }} | ||
{%- if field.use_inline_editing %} js-form {% endif %}"> | ||
<input type="hidden" name="__formid__" value="{{ field.formid }}" /> | ||
<input type="hidden" name="csrf_token" value="{{ get_csrf_token() }}"> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The base Deform template for all forms now includes the CSRF token so this doesn't have to be done by each individual template. Forms are rendered by having Deform serialize Colander templates to HTML using these custom Deform templates. When all our Colander templates were subclasses of a |
||
|
||
<div class="form__backdrop" data-ref="formBackdrop"></div> | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -104,6 +104,7 @@ def post(self): | |
request_param="response_mode=web_message", | ||
is_authenticated=True, | ||
renderer="h:templates/oauth/authorize_web_message.html.jinja2", | ||
require_csrf=False, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FIXME: This is needed to get some functests passing. This line should be removed and the failing functests should be fixed to use a CSRF token. (This reveals a bug in the code: this view wasn't requiring CSRF when it should have been.) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you add a comment here to indicate that this is being added only for the tests, and should not be required in actual usage. |
||
) | ||
def post_web_message(self): | ||
""" | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,10 +35,7 @@ def test_accessible_by_staff(self, app, url, accessible): | |
|
||
assert res.status_code == 200 if accessible else 404 | ||
|
||
GROUP_PAGES = ( | ||
("POST", "/admin/groups/delete/{pubid}", 302), | ||
("GET", "/admin/groups/{pubid}", 200), | ||
) | ||
Comment on lines
-38
to
-41
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FIXME: The |
||
GROUP_PAGES = (("GET", "/admin/groups/{pubid}", 200),) | ||
|
||
@pytest.mark.usefixtures("with_logged_in_admin") | ||
@pytest.mark.parametrize("method,url_template,success_code", GROUP_PAGES) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,7 +3,6 @@ | |
|
||
import colander | ||
import pytest | ||
from pyramid.exceptions import BadCSRFToken | ||
|
||
from h.accounts import schemas | ||
from h.services.user_password import UserPasswordService | ||
|
@@ -156,9 +155,7 @@ def test_it_validates_with_valid_payload( | |
|
||
result = schema.deserialize(valid_params) | ||
|
||
assert result == dict( | ||
valid_params, privacy_accepted=True, comms_opt_in=None, csrf_token=None | ||
) | ||
Comment on lines
-159
to
-161
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The test was expecting the Colander schema to include |
||
assert result == dict(valid_params, privacy_accepted=True, comms_opt_in=None) | ||
|
||
@pytest.fixture | ||
def valid_params(self): | ||
|
@@ -194,18 +191,6 @@ def test_it_is_valid_if_email_same_as_users_existing_email( | |
|
||
schema.deserialize({"email": user.email, "password": "flibble"}) | ||
|
||
def test_it_is_invalid_if_csrf_token_missing(self, pyramid_request, schema): | ||
del pyramid_request.headers["X-CSRF-Token"] | ||
|
||
with pytest.raises(BadCSRFToken): | ||
schema.deserialize({"email": "[email protected]", "password": "flibble"}) | ||
|
||
def test_it_is_invalid_if_csrf_token_wrong(self, pyramid_request, schema): | ||
pyramid_request.headers["X-CSRF-Token"] = "WRONG" | ||
|
||
with pytest.raises(BadCSRFToken): | ||
schema.deserialize({"email": "[email protected]", "password": "flibble"}) | ||
Comment on lines
-197
to
-207
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The Colander schemas no longer do CSRF verification. Several unittests like this can now be removed. (In most cases developers had forgotten to add CSRF test cases for their schemas anyway.) |
||
|
||
def test_it_is_invalid_if_password_wrong(self, schema, user_password_service): | ||
user_password_service.check_password.return_value = False | ||
|
||
|
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're no longer inheriting from a
CSRFSchema
superclass with avalidate()
method, so several of thesesuper()
calls had to be removed. It'll no longer be possible to accidentally disable CSRF protection by forgetting to callsuper()
here.