mirror of
https://github.com/encode/django-rest-framework.git
synced 2024-11-22 09:36:49 +03:00
authentication refactor : request.user + tests pass
This commit is contained in:
parent
9da1ae81dc
commit
afd490238a
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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):
|
||||
def get_permissions(self):
|
||||
"""
|
||||
Returns the :obj:`user` for the current request, as determined by the set of
|
||||
:class:`authentication` classes applied to the :class:`View`.
|
||||
Instantiates and returns the list of permissions that this view requires.
|
||||
"""
|
||||
if not hasattr(self, '_user'):
|
||||
self._user = self._authenticate()
|
||||
return self._user
|
||||
|
||||
def _authenticate(self):
|
||||
"""
|
||||
Attempt to authenticate the request using each authentication class in turn.
|
||||
Returns a ``User`` object, which may be ``AnonymousUser``.
|
||||
"""
|
||||
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)
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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})
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in New Issue
Block a user