From 1bdb872bac5345202e2f58728d0e7fad70dfd7ed Mon Sep 17 00:00:00 2001 From: Reetesh Ranjan Date: Sun, 24 Jul 2016 22:58:19 +0530 Subject: [PATCH] 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. --- rest_framework/authentication.py | 3 ++- rest_framework/request.py | 13 +++++++++++-- rest_framework/views.py | 5 ++++- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/rest_framework/authentication.py b/rest_framework/authentication.py index cb9608a3c..8e8a4ec3f 100644 --- a/rest_framework/authentication.py +++ b/rest_framework/authentication.py @@ -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) diff --git a/rest_framework/request.py b/rest_framework/request.py index aafafcb32..8e9435f6d 100644 --- a/rest_framework/request.py +++ b/rest_framework/request.py @@ -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`. diff --git a/rest_framework/views.py b/rest_framework/views.py index 41d108e53..9a1db447a 100644 --- a/rest_framework/views.py +++ b/rest_framework/views.py @@ -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):