A solution to make csrf_exempt work for views

Summary of changes
===

1. The ```Request``` class has a new attribute ```csrf_exempt``` (private attribute - ```_csrf_exempt```, property - ```csrf_exempt```)

2. A view class can define ```csrf_exempt = True``` if it needs to avoid CSRF checks. The default is ```False``` and it is passed to the ```Request``` class object creation in ```initialize_request``` which is stored as the attribute defined above

3. ```SessionAuthenticaton``` class' ```authenticate``` method performs ```enforce_csrf``` only if ```request.csrf_exempt``` is ```False```

What it effects?
===

Only ```SessionAuthentication``` where the default behavior is intact apart from the case where a user adds ```csrf_exempt = True``` in their view class, in which case the csrf checks are bypassed

How to use?
===

Add the following to your view class:

```csrf_exempt = True```

Why this change?
===

Because ```SessionAuthentication``` is not aware of ```csrf_exempt``` when set through method decorators on a view class or its methods using a form like ```@method_decorator(csrf_exempt)``` or ```@method_decorator(csrf_exempt, name='dispatch')``` and hence CSRF checks cannot be bypassed even if these method decorators are in place.
This commit is contained in:
Reetesh Ranjan 2016-07-24 22:58:19 +05:30
parent e407dc7f01
commit 1bdb872bac
3 changed files with 17 additions and 4 deletions

View File

@ -125,7 +125,8 @@ class SessionAuthentication(BaseAuthentication):
if not user or not user.is_active:
return None
self.enforce_csrf(request)
if not request.csrf_exempt:
self.enforce_csrf(request)
# CSRF passed with authenticated user
return (user, None)

View File

@ -81,7 +81,8 @@ def clone_request(request, method):
parsers=request.parsers,
authenticators=request.authenticators,
negotiator=request.negotiator,
parser_context=request.parser_context)
parser_context=request.parser_context,
csrf_exempt=request.csrf_exempt)
ret._data = request._data
ret._files = request._files
ret._full_data = request._full_data
@ -132,7 +133,7 @@ class Request(object):
"""
def __init__(self, request, parsers=None, authenticators=None,
negotiator=None, parser_context=None):
negotiator=None, parser_context=None, csrf_exempt=False):
self._request = request
self.parsers = parsers or ()
self.authenticators = authenticators or ()
@ -143,6 +144,7 @@ class Request(object):
self._full_data = Empty
self._content_type = Empty
self._stream = Empty
self._csrf_exempt = csrf_exempt
if self.parser_context is None:
self.parser_context = {}
@ -237,6 +239,13 @@ class Request(object):
self._authenticate()
return self._authenticator
@property
def csrf_exempt(self):
"""
Return the _csrf_exempt attribute
"""
return self._csrf_exempt
def _load_data_and_files(self):
"""
Parses the request content into `self.data`.

View File

@ -358,12 +358,15 @@ class APIView(View):
"""
parser_context = self.get_parser_context(request)
csrf_exempt = getattr(self, 'csrf_exempt', False)
return Request(
request,
parsers=self.get_parsers(),
authenticators=self.get_authenticators(),
negotiator=self.get_content_negotiator(),
parser_context=parser_context
parser_context=parser_context,
csrf_exempt=csrf_exempt
)
def initial(self, request, *args, **kwargs):