-
Notifications
You must be signed in to change notification settings - Fork 888
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
Decouple CSRF protection from the session machinery #2854
Conversation
f1fe60c
to
e4f3399
Compare
pyramid/csrf.py
Outdated
csrf = registry.queryUtility(ICSRF) | ||
if csrf is not None: | ||
event['get_csrf_token'] = partial(csrf.get_csrf_token, request) | ||
except AttributeError: |
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.
Is this except needed since request.registry
is not always available? It might mask an error if csrf
does not implement get_csrf_token
(not likely, but still).
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 also catches the case when request is None or some weird mock object, like in tests. I've moved the except
handler only to cover request.registry and the rest in in an else
handler.
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 utility promises to implement ICSRF, which guarantees that it has a get_csrf_token
method. We don't need to defend against borked implementations.
pyramid/config/security.py
Outdated
@@ -218,9 +227,14 @@ def register(): | |||
intr['header'] = header | |||
intr['safe_methods'] = as_sorted_tuple(safe_methods) | |||
intr['callback'] = callback | |||
|
|||
from pyramid.csrf import csrf_token_template_global | |||
from pyramid.events import BeforeRender |
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 move these imports to the top?
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.
Done.
e4f3399
to
3066e55
Compare
pyramid/csrf.py
Outdated
) | ||
from pyramid.interfaces import ICSRF | ||
from pyramid.settings import aslist | ||
from zope.interface import implementer |
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.
Third-party imports should be prior to "local" imports.
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.
Agreed, moving.
pyramid/csrf.py
Outdated
csrf = registry.queryUtility(ICSRF) | ||
if csrf is not None: | ||
event['get_csrf_token'] = partial(csrf.get_csrf_token, request) | ||
except AttributeError: |
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 utility promises to implement ICSRF, which guarantees that it has a get_csrf_token
method. We don't need to defend against borked implementations.
docs/narr/security.rst
Outdated
|
||
To use CSRF tokens, you must first enable a :term:`session factory` as | ||
described in :ref:`using_the_default_session_factory` or | ||
:ref:`using_alternate_session_factories`. |
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.
There should be language here which points up the fact that enabling sessions is a requirement of the default ICSRF
implementation, rather than a general requirement.
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 believe @jcerjak is looking at rewording this, is that the case?
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 fixed it in the code docstrings, but missed it here, fixing.
docs/narr/security.rst
Outdated
token = get_csrf_token(request) | ||
|
||
The ``get_csrf_token()`` method accepts a single argument, of the request. It | ||
returns a CSRF *token* string. If ``get_csrf_token()`` or ``new_csrf_token()`` |
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.
accepts a single argument, of the request.
The "of" should not be there.
docs/narr/security.rst
Outdated
|
||
.. code-block:: javascript | ||
|
||
var csrfToken = ${get_csrf_token()}; |
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.
My JS-fu may be weak, but ISTM that the token value needs to be wrapped in quotes 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.
Agreed, the same is true of the current documentation, I didn't pick up on it when I changed request.session.get_csrf_token
to get_csrf_token
.
docs/narr/security.rst
Outdated
from pyramid.csrf import get_csrf_token | ||
token = new_csrf_token() | ||
|
||
Note, it is not possible to force a new CSRF token from a template. If you want |
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.
Use .. note::
.
pyramid/csrf.py
Outdated
registry = request.registry | ||
csrf = registry.queryUtility(ICSRF) | ||
if csrf is not None: | ||
return csrf.get_csrf_token(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.
Shouldn't this helper raise if no utility is registered?
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'm not sure, I think you can make arguments either way. If you've written third part code that's intended to be reusable it means you have to special case when users haven't got CSRF enabled.
Either way, we should document the behavior.
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 third-party code registers a view with require_csrf
, AFAIK there is no way straightforward way to disable CSRF protection.
As discussed with @MatthewWilkes , if we want to support this use case, we could add a CSRF option for disabling the protection. Or a user could register a dummy ICSRF implementation, which just returns an empty string. But in any case we should raise an error if no ICSRF utility is registered.
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 third-party code registers a view with require_csrf, AFAIK there is no way straightforward way to disable CSRF protection.
This is intentional... if someone defines a view that explicitly requires csrf then it will be inaccessible until someone registers a csrf policy for the app. Turning off csrf protection for those views is a non-goal and can already be done just by re-defining it after config.commit()
to override the old view.
I'd expect this code to raise if there is no policy. Simply using getUtility
should be enough.
pyramid/csrf.py
Outdated
registry = request.registry | ||
csrf = registry.queryUtility(ICSRF) | ||
if csrf is not None: | ||
return csrf.new_csrf_token(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.
Shouldn't this helper raise if no utility is registered?
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.
Yes, it should raise. Using getUtility
should be enough.
pyramid/csrf.py
Outdated
if supplied_token == "" and header is not None: | ||
supplied_token = request.headers.get(header, "") | ||
|
||
impl = request.registry.getUtility(ICSRF) |
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.
Shouldn't this helper raise if no utility is registered?
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.
As this one is getUtility
it will raise a ComponentLookupError
if it's missing, were you thinking of something a bit more descriptive?
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.
Ah, my eyes read that as queryUtility()
like the ones above.
c29afe9
to
052aa49
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.
This is on the right track, great work so far!
Could we consider adding an alternate implementation that does not store the CSRF token in the session to prove the viability of this approach? After all, there's no point to this feature if it doesn't change that status quo.
Maybe just a simple CookieCSRF is enough? AFAIK it does not need to be signed, but @bertjwregeer will be happy to correct my ignorance if I'm wrong.
Finally I'd like to think of a better name for the interface than ICSRF
such as ICSRFTokenStorage
or something. It's a bit weird because the interface itself doesn't control the rest of the default options. Similarly naming it impl
and implementation
in the code is a bit too generic for me versus storage
or policy
or something. I'm not quite sure yet.
docs/whatsnew-1.8.rst
Outdated
@@ -0,0 +1,40 @@ | |||
What's New in Pyramid 1.8 |
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.
Please don't add a whatsnew document. We will do it prior to release. Your changes are good I just think it's easier to create that file from scratch just before a major release.
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.
Got it, thanks!
pyramid/csrf.py
Outdated
registry = request.registry | ||
csrf = registry.queryUtility(ICSRF) | ||
if csrf is not None: | ||
return csrf.get_csrf_token(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.
If third-party code registers a view with require_csrf, AFAIK there is no way straightforward way to disable CSRF protection.
This is intentional... if someone defines a view that explicitly requires csrf then it will be inaccessible until someone registers a csrf policy for the app. Turning off csrf protection for those views is a non-goal and can already be done just by re-defining it after config.commit()
to override the old view.
I'd expect this code to raise if there is no policy. Simply using getUtility
should be enough.
pyramid/csrf.py
Outdated
registry = request.registry | ||
csrf = registry.queryUtility(ICSRF) | ||
if csrf is not None: | ||
return csrf.new_csrf_token(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.
Yes, it should raise. Using getUtility
should be enough.
pyramid/csrf.py
Outdated
registry = request.registry | ||
csrf = registry.queryUtility(ICSRF) | ||
if csrf is not None: | ||
return csrf.get_csrf_token(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.
Should these be functions or should we add them to the request as methods, similar to request.has_permission
, request.session.get_csrf_token
etc? request.get_csrf_token()
? request.csrf.get_token()
?
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 that new_csrf_token
and get_csrf_token
should be available in the same place, but I'm a little nervous about having new_csrf_token
available in templates, personally. Maybe I'm worrying over nothing, but it feels to me like something that should only be called in Python code.
The CSRF cookie doesn't need to be signed. |
pyramid/csrf.py
Outdated
# should never appear in an URL as doing so is a security issue. We also | ||
# explicitly check for request.POST here as we do not support sending form | ||
# encoded data over anything but a request.POST. | ||
if token is not None: |
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 would recommend swapping the check for the token in the headers/body. request.POST
is more expensive than checking request.headers
.
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 unchanged from the previous implementation, but that makes sense. Switching.
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.
Thanks @MatthewWilkes!
@mmerickel I've pondered a bit about the change to be specifically about storage, and think that it might be worthwhile to have some of the checking in the CSRF implementation too. Specifically, currently it assumes that constant-time string comparison is sufficient, but an implementation might want to do funky stuff like scope tokens (as mentioned in the original ticket). If we have a hook there then we can allow this to be supported without having to also supply a |
5f9dd69
to
197a609
Compare
@mmerickel @jcerjak and I worked on this more today and incorporated changes based on your feedback. We have added the checking function I referenced in my previous comment, as I think it gives us a bit more flexibility. |
197a609
to
028132b
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.
I don't think I like ICSRFPolicy
because it's too similar to the current default_csrf_options
which I almost named and defined as a policy when I was doing that work, so I think we should avoid that word.
How would scope tokens be done with this new API? I think it's important to at least consider and I don't have a good answer for it right now.
How does this feature influence user apps right now? Does the session factory automatically register a csrf implementation somehow? Or does pyramid's configurator just always enable the SessionCSRF by default, similar to default renderers?
pyramid/csrf.py
Outdated
return | ||
else: | ||
csrf = registry.getUtility(ICSRFPolicy) | ||
event['get_csrf_token'] = partial(csrf.get_csrf_token, 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.
This is code from my own apps that I'll at least paste here incase you like it better.
def includeme(config):
# other session setup usually
# ...
config.add_subscriber(renderer_globals, 'pyramid.interfaces.IBeforeRender')
class CSRFToken(object):
def __init__(self, request):
self.request = request
def __json__(self, request):
return self.request.session.get_csrf_token()
def __html__(self):
return self.request.session.get_csrf_token()
def renderer_globals(event):
request = event['request']
event['csrf_token'] = CSRFToken(request) # allows {{ csrf_token }}
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 like this idea!
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 this feels like a little too much magic personally. I can certainly see the simplicity benefits, but the current approach has a couple of minor benefits too:
- A function call feels more expensive, which considering setting a CSRF cookie can cause pages to not be trivially cachable may theoretically prevent people adding it 'just in case'
- It is closer to the existing idiom, users would change
request.session.get_csrf_token()
toget_csrf_token()
, which may make upgrades easier.
For what it's worth, I don't think either of those two arguments are sufficient to make it the only feasible option, I just thought I'd share my thinking.
I don't think I want to integrate this change at this stage, maybe it would be worth another PR? What do you think @jcerjak?
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 like the simplicity, but maybe it's better to be more explicit here. Don't have a strong opinion about it, but I think it would be better if it's a separate PR.
Will reply to the rest tomorrow, but if you can give us steer on naming that would help. Do you prefer ICSRFStorage? |
Yeah sorry, I know naming is the biggest derailment. Let's go with |
@mmerickel With regards to scoped tokens, this isn't included in core (and I personally don't think it should be), but it's nice and easy to implement in userspace with these changes. The implementation would be something like: from functools import wraps
import hmac
from zope.interface import implementer
from pyramid.csrf import ICSRFStoragePolicy, SessionCSRF
from pyramid.compat import bytes_
def csrf_scope(scope):
def csrf_scope_decorator(func):
@wraps(func)
def with_scope(request, *args, **kwargs):
request.csrf_scope = scope
return func(request, *args, **kwargs)
return with_scope
return csrf_scope_decorator
@implementer(ICSRFStoragePolicy)
class ScopedSessionCSRF(SessionCSRF):
def _get_scope(self, request):
return getattr(request, 'csrf_scope', '')
def _scope_token(self, scope, base_token):
return hmac.new(bytes_(base_token, 'ascii'), scope).hexdigest()
def new_csrf_token(self, request):
base_token = super(ScopedSessionCSRF, self).new_csrf_token(request)
return self._scope_token(self._get_scope(request), base_token)
def get_csrf_token(self, request):
base_token = super(ScopedSessionCSRF, self).get_csrf_token(request)
return self._scope_token(self._get_scope(request), base_token) and would be used like: @view_config(route_name='home', renderer='templates/mytemplate.pt')
@csrf_scope('foobar')
def my_view(request):
return {'project': 'hacking'} |
@mmerickel WRT user facing changes, that's a good point. I've added a fallback to the That means that if the user explicitly marks a view as being CSRF checked without calling |
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'm thinking pyramid should just register a SessionCSRFStoragePolicy
by default in Configurator.__init__
via calling config.set_csrf_storage_policy(SessionCSRFStoragePolicy())
. in order to keep bw-compat. This would alleviate the need for the fallback policy creation in each method (check_csrf_token
and new_csrf_token
). If someone accesses these without a session it'll just raise which is fine.
docs/narr/security.rst
Outdated
from pyramid.config import Configurator | ||
|
||
config = Configurator() | ||
config.set_default_csrf_options(implementation=MyCustomCSRFPolicy()) |
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'd prefer if this were a separate method for config.set_csrf_storage_policy
as CSRF is entirely unusable without it and it would be good if it could be replaced without changing the other options.
pyramid/csrf.py
Outdated
|
||
|
||
@implementer(ICSRFStoragePolicy) | ||
class SessionCSRF(object): |
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.
SessionCSRFStoragePolicy
pyramid/csrf.py
Outdated
) | ||
|
||
@implementer(ICSRFStoragePolicy) | ||
class CookieCSRF(object): |
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.
CookieCSRFStoragePolicy
pyramid/config/security.py
Outdated
@@ -218,9 +231,12 @@ def register(): | |||
intr['header'] = header | |||
intr['safe_methods'] = as_sorted_tuple(safe_methods) | |||
intr['callback'] = callback | |||
|
|||
self.add_subscriber(csrf_token_template_global, [BeforeRender]) |
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.
Currently Pyramid has no precedent for automatically registering a BeforeRender
subscriber and I think we should keep it that way. If you really want to inject it we could a) do it in the current renderer bindings (pyramid_jinja2, pyramid_mako, pyramid_chameleon) or b) do it in the pyramid.renderer.RendererHelper
as a system
value.
pyramid/csrf.py
Outdated
self.secure = secure | ||
self.httponly = httponly | ||
self.domain = domain | ||
self.path = path |
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 implementation should be using the webob.cookies.CookieProfile
from webob in order to set the cookies on potentially multiple domains similar to how SignedCookieSessionFactory
and AuthTktAuthenticationPolicy
operate.
…from the session machinery. Adds configuration of this to the csrf_options configurator commands. Make the default implementation a fallback to the old one. Documentation patches for new best practices given updates CSRF implementation.
… review regarding naming of variables and code cleanup.
…configure a session and set explicit checks would see an exception
… implemenations based on feedback, split CSRF implementation and option configuration and make the csrf token function exposed as a system default rather than a renderer event.
a3e3f70
to
7c0f098
Compare
@mmerickel I've looked at this again today, as I now have a client who wants CSRF protection without sessions. I've pushed changes to address your comments, let me know when you've had a chance to take a look? |
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.
👍 liking this a lot :-D
pyramid/csrf.py
Outdated
considered valid. It must be passed in either the request body or | ||
a header. | ||
|
||
.. versionchanged:: 1.8a1 |
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 are going to need to change these to Pyramid 1.9
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.
@MatthewWilkes this is really great stuff! I think one more round with very minor tweaks and we're good to go.
@@ -320,179 +319,3 @@ flash storage. | |||
.. index:: |
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 these index directives need to be moved.
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.
👍
pyramid/config/security.py
Outdated
def register(): | ||
self.registry.registerUtility(policy, ICSRFStoragePolicy) | ||
|
||
self.action(ICSRFStoragePolicy, register, order=PHASE1_CONFIG) |
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'm pretty sure the PHASE1_CONFIG
is unnecessary here. It's used in set_default_csrf_options
because it needs to be configured prior to add_view
running the csrf_view
view deriver. I think the storage policy is only used at request-time though so can be left at the default config phase.
If you think it should be PHASE1_CONFIG
then it should be added to http://docs.pylonsproject.org/projects/pyramid/en/master/narr/extconfig.html#ordering-actions
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 had added it to extconfig, but you're right. Removing it from here and extconfig.
pyramid/csrf.py
Outdated
# This means explicit validation has been asked for without configuring | ||
# the CSRF implementation. Fall back to SessionCSRFStoragePolicy as that is the | ||
# default | ||
policy = SessionCSRFStoragePolicy() |
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 probably unnecessary now. Just use getUtility
instead an exception if there's no policy. Thoughts?
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.
Yep, thanks. I was missing a call in the testing setup that this was hiding. Fixed.
- Renamed `SessionCSRFStoragePolicy` to `LegacySessionCSRFStoragePolicy` for the version that uses the legacy `ISession.get_csrf_token` and `ISession.new_csrf_token` apis and set that as the default. - Added new `SessionCSRFStoragePolicy` that stores data in the session similar to how the `SessionAuthenticationPolicy` works. - `CookieCSRFStoragePolicy` did not properly return the newly generated token from `get_csrf_token` after calling `new_csrf_token`. It needed to cache the new value since the response callback does not affect the current request. - `CookieCSRFStoragePolicy` was not forwarding the `domain` value to the `CookieProfile` causing that setting to be ignored. - Removed `check_csrf_token` from the `ICSRFStoragePolicy` interface to simplify implementations of storage policies. - Added an introspectable item for the configured storage policy so that it appears on the debugtoolbar. - Added a change note on `ISession` that it no longer required the csrf methods.
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 went through this tonight and found several minor issues.
-
SessionCSRFStoragePolicy
should really just use the session as a storage mechanism. I addedLegacySessionCSRFStoragePolicy
for the version that uses the legacyISession.get_csrf_token
andISession.new_csrf_token
apis and set that as the default. I also updatedSessionCSRFStoragePolicy
to just store data in the session similar to how theSessionAuthenticationPolicy
works. -
CookieCSRFStoragePolicy
did not properly return the newly generated token fromget_csrf_token
after callingnew_csrf_token
. It needs to cache the new value since the response callback does not affect the current request. -
CookieCSRFStoragePolicy
was not forwarding thedomain
value to theCookieProfile
causing that setting to be ignored. -
I removed
check_csrf_token
from theICSRFStoragePolicy
interface because I can't see the point to making it pluggable. On top of that it makes implementation of storage policies more difficult. -
Added an introspectable item for the configured storage policy so that it appears on the debugtoolbar.
-
Added a change note on
ISession
that it no longer required the csrf methods. -
Restored (but deprecated) shims in
pyramid.session
forcheck_csrf_origin
andcheck_csrf_token
.
- Renamed `SessionCSRFStoragePolicy` to `LegacySessionCSRFStoragePolicy` for the version that uses the legacy `ISession.get_csrf_token` and `ISession.new_csrf_token` apis and set that as the default. - Added new `SessionCSRFStoragePolicy` that stores data in the session similar to how the `SessionAuthenticationPolicy` works. - `CookieCSRFStoragePolicy` did not properly return the newly generated token from `get_csrf_token` after calling `new_csrf_token`. It needed to cache the new value since the response callback does not affect the current request. - `CookieCSRFStoragePolicy` was not forwarding the `domain` value to the `CookieProfile` causing that setting to be ignored. - Removed `check_csrf_token` from the `ICSRFStoragePolicy` interface to simplify implementations of storage policies. - Added an introspectable item for the configured storage policy so that it appears on the debugtoolbar. - Added a change note on `ISession` that it no longer required the csrf methods.
- Renamed `SessionCSRFStoragePolicy` to `LegacySessionCSRFStoragePolicy` for the version that uses the legacy `ISession.get_csrf_token` and `ISession.new_csrf_token` apis and set that as the default. - Added new `SessionCSRFStoragePolicy` that stores data in the session similar to how the `SessionAuthenticationPolicy` works. - `CookieCSRFStoragePolicy` did not properly return the newly generated token from `get_csrf_token` after calling `new_csrf_token`. It needed to cache the new value since the response callback does not affect the current request. - `CookieCSRFStoragePolicy` was not forwarding the `domain` value to the `CookieProfile` causing that setting to be ignored. - Removed `check_csrf_token` from the `ICSRFStoragePolicy` interface to simplify implementations of storage policies. - Added an introspectable item for the configured storage policy so that it appears on the debugtoolbar. - Added a change note on `ISession` that it no longer required the csrf methods. - Leave deprecated shims in ``pyramid.session`` for ``check_csrf_origin`` and ``check_csrf_token``.
typo is fixed (function is removed entirely) in #3019
This PR will closed and show as merged automatically when #3019 is merged. |
@mmerickel Please don't remove the check_csrf_token method, it is very useful to have it pluggable as that allows for keyring based or fully-stateless CSRF policies. For example, consider this CSRF policy:
For non-security experts reading this, I'm not claiming that the above is a good CSRF implementation, however I do think that it demonstrates that there is a value in allowing the CSRF implementation to decide what's valid and what isn't. If you're concerned about boilerplate (although, personally, I think it's minimal), maybe we could make a mixin available? |
@MatthewWilkes |
This is true, good point. |
@MatthewWilkes thank you very much for the alternative example. I definitely had not considered this stateless use case. It appears that the main downside of supporting that use case without a pluggable EDIT: I thought about it a bit longer and I can't actually provide this |
Thanks for coming to that decision, it occured to me this morning that Bert's workaround has a fatal flaw. Any submission that includes a CSRF check would cause get_card_token to return the supplied token, making it very easy to hit the short timeout. Imagine a form that takes 3 minutes to fill in, once you've submitted that the resultant page render will use a token with only 2 minutes of validity, so a multipage form would quickly fail. As to being the most overengineered in the business, I have one word for you, Plone. |
Decouple CSRF protection from the session machinery (replaced #2854)
In #1573 @dstufft makes a number of feature requests about the CSRF implementation of Pyramid, most of which were added in 1.7.
This patch attempts to move the CSRF generation logic out of the session, so new CSRF implementations that do not depend on sessions can be added. It currently does not add any such implementations, the only one shipped is the default which falls back to an implementation of CSRF on the session.
It also introduces new utility functions for
get_csrf_token(request)
andnew_csrf_token(request)
in Python, as well as aget_csrf_token()
in the template global scope, and repoints documentation to use these rather than explicitly calling the session machinery.This was written at the DragonSprint by me and @jcerjak, a new contributor to Pyramid.