From fba2a8ab0f5ad36664752311c18f9e94631043a8 Mon Sep 17 00:00:00 2001 From: Ryan P Kilby Date: Wed, 15 Nov 2017 19:55:29 -0500 Subject: [PATCH] Re-raise/wrap auth attribute errors --- rest_framework/request.py | 32 ++++++++++++++++++++++++++++---- tests/test_request.py | 6 +++--- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/rest_framework/request.py b/rest_framework/request.py index 944691039..8540eeeee 100644 --- a/rest_framework/request.py +++ b/rest_framework/request.py @@ -10,6 +10,9 @@ The wrapped request then offers a richer API, in particular : """ from __future__ import unicode_literals +import sys +from contextlib import contextmanager + from django.conf import settings from django.http import QueryDict from django.http.multipartparser import parse_header @@ -59,6 +62,24 @@ class override_method(object): self.view.action = self.action +class WrappedAttributeError(Exception): + pass + + +@contextmanager +def wrap_attributeerrors(): + """ + Used to re-raise AttributeErrors caught during authentication, preventing + these errors from otherwise being handled by the attribute access protocol. + """ + try: + yield + except AttributeError: + info = sys.exc_info() + exc = WrappedAttributeError(str(info[1])) + six.reraise(type(exc), exc, info[2]) + + class Empty(object): """ Placeholder for unset attributes. @@ -191,7 +212,8 @@ class Request(object): by the authentication classes provided to the request. """ if not hasattr(self, '_user'): - self._authenticate() + with wrap_attributeerrors(): + self._authenticate() return self._user @user.setter @@ -214,7 +236,8 @@ class Request(object): request, such as an authentication token. """ if not hasattr(self, '_auth'): - self._authenticate() + with wrap_attributeerrors(): + self._authenticate() return self._auth @auth.setter @@ -233,7 +256,8 @@ class Request(object): to authenticate the request, or `None`. """ if not hasattr(self, '_authenticator'): - self._authenticate() + with wrap_attributeerrors(): + self._authenticate() return self._authenticator def _load_data_and_files(self): @@ -316,7 +340,7 @@ class Request(object): try: parsed = parser.parse(stream, media_type, self.parser_context) - except: + except Exception: # If we get an exception during parsing, fill in empty data and # re-raise. Ensures we don't simply repeat the error when # attempting to render the browsable renderer response, or when diff --git a/tests/test_request.py b/tests/test_request.py index cd1c873f4..66bb70929 100644 --- a/tests/test_request.py +++ b/tests/test_request.py @@ -19,7 +19,7 @@ from django.utils import six from rest_framework import status from rest_framework.authentication import SessionAuthentication from rest_framework.parsers import BaseParser, FormParser, MultiPartParser -from rest_framework.request import Request +from rest_framework.request import Request, WrappedAttributeError from rest_framework.response import Response from rest_framework.test import APIClient, APIRequestFactory from rest_framework.views import APIView @@ -227,10 +227,10 @@ class TestUserSetter(TestCase): # The DRF request object does not have a user and should run authenticators expected = r"no attribute 'MISSPELLED_NAME_THAT_DOESNT_EXIST'" - with pytest.raises(AttributeError, match=expected): + with pytest.raises(WrappedAttributeError, match=expected): request.user - with pytest.raises(AttributeError, match=expected): + with pytest.raises(WrappedAttributeError, match=expected): login(request, self.user)