aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSébastien Piquemal2012-02-23 22:47:45 +0200
committerSébastien Piquemal2012-02-23 22:47:45 +0200
commitafd490238a38c5445013f030547b1019f484f0bc (patch)
treed53ea67eae108c7c1d45aa52bb3f209155926349
parent9da1ae81dc9a056db94ea07f35478ed003fea598 (diff)
downloaddjango-rest-framework-afd490238a38c5445013f030547b1019f484f0bc.tar.bz2
authentication refactor : request.user + tests pass
-rw-r--r--djangorestframework/authentication.py5
-rw-r--r--djangorestframework/mixins.py63
-rw-r--r--djangorestframework/request.py41
-rw-r--r--djangorestframework/tests/authentication.py2
-rw-r--r--djangorestframework/tests/throttling.py8
-rw-r--r--djangorestframework/views.py14
6 files changed, 79 insertions, 54 deletions
diff --git a/djangorestframework/authentication.py b/djangorestframework/authentication.py
index e326c15a..00a61e3d 100644
--- a/djangorestframework/authentication.py
+++ b/djangorestframework/authentication.py
@@ -88,13 +88,14 @@ class UserLoggedInAuthentication(BaseAuthentication):
Otherwise returns :const:`None`.
"""
request.DATA # Make sure our generic parsing runs first
+ user = getattr(request.request, 'user', None)
- if getattr(request, 'user', None) and request.user.is_active:
+ if user and user.is_active:
# Enforce CSRF validation for session based authentication.
resp = CsrfViewMiddleware().process_view(request, None, (), {})
if resp is None: # csrf passed
- return request.user
+ return user
return None
diff --git a/djangorestframework/mixins.py b/djangorestframework/mixins.py
index 51c859cd..398ed28a 100644
--- a/djangorestframework/mixins.py
+++ b/djangorestframework/mixins.py
@@ -3,7 +3,6 @@ The :mod:`mixins` module provides a set of reusable `mixin`
classes that can be added to a `View`.
"""
-from django.contrib.auth.models import AnonymousUser
from django.core.paginator import Paginator
from django.db.models.fields.related import ForeignKey
from urlobject import URLObject
@@ -19,7 +18,7 @@ __all__ = (
# Base behavior mixins
'RequestMixin',
'ResponseMixin',
- 'AuthMixin',
+ 'PermissionsMixin',
'ResourceMixin',
# Reverse URL lookup behavior
'InstanceMixin',
@@ -45,6 +44,13 @@ class RequestMixin(object):
Should be a tuple/list of classes as described in the :mod:`parsers` module.
"""
+ authentication_classes = ()
+ """
+ The set of authentication types that this view can handle.
+
+ Should be a tuple/list of classes as described in the :mod:`authentication` module.
+ """
+
request_class = Request
"""
The class to use as a wrapper for the original request object.
@@ -56,6 +62,12 @@ class RequestMixin(object):
"""
return [p(self) for p in self.parser_classes]
+ def get_authentications(self):
+ """
+ Instantiates and returns the list of authentications the request will use.
+ """
+ return [a(self) for a in self.authentication_classes]
+
def create_request(self, request):
"""
Creates and returns an instance of :class:`request.Request`.
@@ -63,7 +75,9 @@ class RequestMixin(object):
parsers set on the view.
"""
parsers = self.get_parsers()
- return self.request_class(request, parsers=parsers)
+ authentications = self.get_authentications()
+ return self.request_class(request, parsers=parsers,
+ authentications=authentications)
@property
def _parsed_media_types(self):
@@ -134,57 +148,32 @@ class ResponseMixin(object):
return [renderer.format for renderer in self.get_renderers()]
-########## Auth Mixin ##########
+########## Permissions Mixin ##########
-class AuthMixin(object):
+class PermissionsMixin(object):
"""
- Simple :class:`mixin` class to add authentication and permission checking to a :class:`View` class.
+ Simple :class:`mixin` class to add permission checking to a :class:`View` class.
"""
- authentication = ()
- """
- The set of authentication types that this view can handle.
-
- Should be a tuple/list of classes as described in the :mod:`authentication` module.
- """
-
- permissions = ()
+ permissions_classes = ()
"""
The set of permissions that will be enforced on this view.
Should be a tuple/list of classes as described in the :mod:`permissions` module.
"""
- @property
- def user(self):
- """
- Returns the :obj:`user` for the current request, as determined by the set of
- :class:`authentication` classes applied to the :class:`View`.
- """
- if not hasattr(self, '_user'):
- self._user = self._authenticate()
- return self._user
-
- def _authenticate(self):
+ def get_permissions(self):
"""
- Attempt to authenticate the request using each authentication class in turn.
- Returns a ``User`` object, which may be ``AnonymousUser``.
+ Instantiates and returns the list of permissions that this view requires.
"""
- for authentication_cls in self.authentication:
- authentication = authentication_cls(self)
- user = authentication.authenticate(self.request)
- if user:
- return user
- return AnonymousUser()
+ return [p(self) for p in self.permissions_classes]
# TODO: wrap this behavior around dispatch()
- def _check_permissions(self):
+ def check_permissions(self, user):
"""
Check user permissions and either raise an ``ImmediateResponse`` or return.
"""
- user = self.user
- for permission_cls in self.permissions:
- permission = permission_cls(self)
+ for permission in self.get_permissions():
permission.check_permission(user)
diff --git a/djangorestframework/request.py b/djangorestframework/request.py
index e8f2b8c3..964231ba 100644
--- a/djangorestframework/request.py
+++ b/djangorestframework/request.py
@@ -8,14 +8,15 @@ The wrapped request then offers a richer API, in particular :
- full support of PUT method, including support for file uploads
- form overloading of HTTP method, content type and content
"""
+from StringIO import StringIO
+
+from django.contrib.auth.models import AnonymousUser
from djangorestframework.response import ImmediateResponse
from djangorestframework import status
from djangorestframework.utils.mediatypes import is_form_media_type
from djangorestframework.utils import as_tuple
-from StringIO import StringIO
-
__all__ = ('Request',)
@@ -27,6 +28,7 @@ class Request(object):
Kwargs:
- request(HttpRequest). The original request instance.
- parsers(list/tuple). The parsers to use for parsing the request content.
+ - authentications(list/tuple). The authentications used to try authenticating the request's user.
"""
_USE_FORM_OVERLOADING = True
@@ -34,10 +36,12 @@ class Request(object):
_CONTENTTYPE_PARAM = '_content_type'
_CONTENT_PARAM = '_content'
- def __init__(self, request=None, parsers=None):
+ def __init__(self, request, parsers=None, authentications=None):
self.request = request
if parsers is not None:
self.parsers = parsers
+ if authentications is not None:
+ self.authentications = authentications
@property
def method(self):
@@ -87,6 +91,16 @@ class Request(object):
self._load_data_and_files()
return self._files
+ @property
+ def user(self):
+ """
+ Returns the :obj:`user` for the current request, authenticated
+ with the set of :class:`authentication` instances applied to the :class:`Request`.
+ """
+ if not hasattr(self, '_user'):
+ self._user = self._authenticate()
+ return self._user
+
def _load_data_and_files(self):
"""
Parses the request content into self.DATA and self.FILES.
@@ -192,6 +206,27 @@ class Request(object):
parsers = property(_get_parsers, _set_parsers)
+ def _authenticate(self):
+ """
+ Attempt to authenticate the request using each authentication instance in turn.
+ Returns a ``User`` object, which may be ``AnonymousUser``.
+ """
+ for authentication in self.authentications:
+ user = authentication.authenticate(self)
+ if user:
+ return user
+ return AnonymousUser()
+
+ def _get_authentications(self):
+ if hasattr(self, '_authentications'):
+ return self._authentications
+ return ()
+
+ def _set_authentications(self, value):
+ self._authentications = value
+
+ authentications = property(_get_authentications, _set_authentications)
+
def __getattr__(self, name):
"""
When an attribute is not present on the calling instance, try to get it
diff --git a/djangorestframework/tests/authentication.py b/djangorestframework/tests/authentication.py
index 25410b04..5debc79a 100644
--- a/djangorestframework/tests/authentication.py
+++ b/djangorestframework/tests/authentication.py
@@ -12,7 +12,7 @@ import base64
class MockView(View):
- permissions = (permissions.IsAuthenticated,)
+ permissions_classes = (permissions.IsAuthenticated,)
def post(self, request):
return HttpResponse({'a': 1, 'b': 2, 'c': 3})
diff --git a/djangorestframework/tests/throttling.py b/djangorestframework/tests/throttling.py
index 393c3ec8..73a4c02b 100644
--- a/djangorestframework/tests/throttling.py
+++ b/djangorestframework/tests/throttling.py
@@ -13,17 +13,17 @@ from djangorestframework.resources import FormResource
from djangorestframework.response import Response
class MockView(View):
- permissions = ( PerUserThrottling, )
+ permissions_classes = ( PerUserThrottling, )
throttle = '3/sec'
def get(self, request):
return Response('foo')
class MockView_PerViewThrottling(MockView):
- permissions = ( PerViewThrottling, )
+ permissions_classes = ( PerViewThrottling, )
class MockView_PerResourceThrottling(MockView):
- permissions = ( PerResourceThrottling, )
+ permissions_classes = ( PerResourceThrottling, )
resource = FormResource
class MockView_MinuteThrottling(MockView):
@@ -54,7 +54,7 @@ class ThrottlingTests(TestCase):
"""
Explicitly set the timer, overriding time.time()
"""
- view.permissions[0].timer = lambda self: value
+ view.permissions_classes[0].timer = lambda self: value
def test_request_throttling_expires(self):
"""
diff --git a/djangorestframework/views.py b/djangorestframework/views.py
index 6bfc4192..509d1471 100644
--- a/djangorestframework/views.py
+++ b/djangorestframework/views.py
@@ -69,7 +69,7 @@ _resource_classes = (
)
-class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
+class View(ResourceMixin, RequestMixin, ResponseMixin, PermissionsMixin, DjangoView):
"""
Handles incoming requests and maps them to REST operations.
Performs request deserialization, response serialization, authentication and input validation.
@@ -91,13 +91,13 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
List of parser classes the resource can parse the request with.
"""
- authentication = (authentication.UserLoggedInAuthentication,
+ authentication_classes = (authentication.UserLoggedInAuthentication,
authentication.BasicAuthentication)
"""
List of all authenticating methods to attempt.
"""
- permissions = (permissions.FullAnonAccess,)
+ permissions_classes = (permissions.FullAnonAccess,)
"""
List of all permissions that must be checked.
"""
@@ -206,15 +206,15 @@ class View(ResourceMixin, RequestMixin, ResponseMixin, AuthMixin, DjangoView):
# all other authentication is CSRF exempt.
@csrf_exempt
def dispatch(self, request, *args, **kwargs):
- self.request = self.create_request(request)
+ self.request = request = self.create_request(request)
self.args = args
self.kwargs = kwargs
try:
self.initial(request, *args, **kwargs)
-
- # Authenticate and check request has the relevant permissions
- self._check_permissions()
+
+ # check that user has the relevant permissions
+ self.check_permissions(request.user)
# Get the appropriate handler method
if request.method.lower() in self.http_method_names: