diff options
| author | Sébastien Piquemal | 2012-02-23 22:47:45 +0200 | 
|---|---|---|
| committer | Sébastien Piquemal | 2012-02-23 22:47:45 +0200 | 
| commit | afd490238a38c5445013f030547b1019f484f0bc (patch) | |
| tree | d53ea67eae108c7c1d45aa52bb3f209155926349 | |
| parent | 9da1ae81dc9a056db94ea07f35478ed003fea598 (diff) | |
| download | django-rest-framework-afd490238a38c5445013f030547b1019f484f0bc.tar.bz2 | |
authentication refactor : request.user + tests pass
| -rw-r--r-- | djangorestframework/authentication.py | 5 | ||||
| -rw-r--r-- | djangorestframework/mixins.py | 63 | ||||
| -rw-r--r-- | djangorestframework/request.py | 41 | ||||
| -rw-r--r-- | djangorestframework/tests/authentication.py | 2 | ||||
| -rw-r--r-- | djangorestframework/tests/throttling.py | 8 | ||||
| -rw-r--r-- | djangorestframework/views.py | 14 | 
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:  | 
