From 4b691c402707775c3048a90531024f3bc5be6f91 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 20 Sep 2012 13:06:27 +0100 Subject: Change package name: djangorestframework -> rest_framework --- rest_framework/authentication.py | 132 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 rest_framework/authentication.py (limited to 'rest_framework/authentication.py') diff --git a/rest_framework/authentication.py b/rest_framework/authentication.py new file mode 100644 index 00000000..fd597397 --- /dev/null +++ b/rest_framework/authentication.py @@ -0,0 +1,132 @@ +""" +The :mod:`authentication` module provides a set of pluggable authentication classes. + +Authentication behavior is provided by mixing the :class:`mixins.RequestMixin` class into a :class:`View` class. +""" + +from django.contrib.auth import authenticate +from rest_framework.compat import CsrfViewMiddleware +from rest_framework.authtoken.models import Token +import base64 + + +class BaseAuthentication(object): + """ + All authentication classes should extend BaseAuthentication. + """ + + def authenticate(self, request): + """ + Authenticate the :obj:`request` and return a :obj:`User` or :const:`None`. [*]_ + + .. [*] The authentication context *will* typically be a :obj:`User`, + but it need not be. It can be any user-like object so long as the + permissions classes (see the :mod:`permissions` module) on the view can + handle the object and use it to determine if the request has the required + permissions or not. + + This can be an important distinction if you're implementing some token + based authentication mechanism, where the authentication context + may be more involved than simply mapping to a :obj:`User`. + """ + return None + + +class BasicAuthentication(BaseAuthentication): + """ + Base class for HTTP Basic authentication. + Subclasses should implement `.authenticate_credentials()`. + """ + + def authenticate(self, request): + """ + Returns a `User` if a correct username and password have been supplied + using HTTP Basic authentication. Otherwise returns `None`. + """ + from django.utils.encoding import smart_unicode, DjangoUnicodeDecodeError + + if 'HTTP_AUTHORIZATION' in request.META: + auth = request.META['HTTP_AUTHORIZATION'].split() + if len(auth) == 2 and auth[0].lower() == "basic": + try: + auth_parts = base64.b64decode(auth[1]).partition(':') + except TypeError: + return None + + try: + userid, password = smart_unicode(auth_parts[0]), smart_unicode(auth_parts[2]) + except DjangoUnicodeDecodeError: + return None + + return self.authenticate_credentials(userid, password) + + def authenticate_credentials(self, userid, password): + """ + Given the Basic authentication userid and password, authenticate + and return a user instance. + """ + raise NotImplementedError('.authenticate_credentials() must be overridden') + + +class UserBasicAuthentication(BasicAuthentication): + def authenticate_credentials(self, userid, password): + """ + Authenticate the userid and password against username and password. + """ + user = authenticate(username=userid, password=password) + if user is not None and user.is_active: + return (user, None) + + +class SessionAuthentication(BaseAuthentication): + """ + Use Django's session framework for authentication. + """ + + def authenticate(self, request): + """ + Returns a :obj:`User` if the request session currently has a logged in user. + Otherwise returns :const:`None`. + """ + user = getattr(request._request, 'user', None) + + 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 (user, None) + + +class TokenAuthentication(BaseAuthentication): + """ + Simple token based authentication. + + Clients should authenticate by passing the token key in the "Authorization" + HTTP header, prepended with the string "Token ". For example: + + Authorization: Token 401f7ac837da42b97f613d789819ff93537bee6a + """ + + model = Token + """ + A custom token model may be used, but must have the following properties. + + * key -- The string identifying the token + * user -- The user to which the token belongs + """ + + def authenticate(self, request): + auth = request.META.get('HTTP_AUTHORIZATION', '').split() + + if len(auth) == 2 and auth[0].lower() == "token": + key = auth[1] + try: + token = self.model.objects.get(key=key) + except self.model.DoesNotExist: + return None + + if token.user.is_active and not getattr(token, 'revoked', False): + return (token.user, token) + +# TODO: OAuthAuthentication -- cgit v1.2.3 From 5c17a60176d91b8ef8fa729096fd57481de7a4ea Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 9 Oct 2012 09:57:31 +0100 Subject: Tweak authtoken --- rest_framework/authentication.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'rest_framework/authentication.py') diff --git a/rest_framework/authentication.py b/rest_framework/authentication.py index fd597397..f8954428 100644 --- a/rest_framework/authentication.py +++ b/rest_framework/authentication.py @@ -126,7 +126,7 @@ class TokenAuthentication(BaseAuthentication): except self.model.DoesNotExist: return None - if token.user.is_active and not getattr(token, 'revoked', False): + if token.user.is_active: return (token.user, token) # TODO: OAuthAuthentication -- cgit v1.2.3 From 221ecd21828c11a800c00a6ec52e93587b7e2a3b Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 10 Oct 2012 16:36:25 +0100 Subject: Fix session auth --- rest_framework/authentication.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'rest_framework/authentication.py') diff --git a/rest_framework/authentication.py b/rest_framework/authentication.py index f8954428..ee5bd2f2 100644 --- a/rest_framework/authentication.py +++ b/rest_framework/authentication.py @@ -88,11 +88,14 @@ class SessionAuthentication(BaseAuthentication): Returns a :obj:`User` if the request session currently has a logged in user. Otherwise returns :const:`None`. """ - user = getattr(request._request, 'user', None) + + # Get the underlying HttpRequest object + http_request = request._request + user = getattr(http_request, 'user', None) if user and user.is_active: # Enforce CSRF validation for session based authentication. - resp = CsrfViewMiddleware().process_view(request, None, (), {}) + resp = CsrfViewMiddleware().process_view(http_request, None, (), {}) if resp is None: # csrf passed return (user, None) -- cgit v1.2.3 From 9c1fba3483b7e81da0744464dcf23a5f12711de2 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 15 Oct 2012 13:27:50 +0100 Subject: Tweak parsers to take parser_context --- rest_framework/authentication.py | 40 +++++++++------------------------------- 1 file changed, 9 insertions(+), 31 deletions(-) (limited to 'rest_framework/authentication.py') diff --git a/rest_framework/authentication.py b/rest_framework/authentication.py index ee5bd2f2..d7624708 100644 --- a/rest_framework/authentication.py +++ b/rest_framework/authentication.py @@ -1,10 +1,9 @@ """ -The :mod:`authentication` module provides a set of pluggable authentication classes. - -Authentication behavior is provided by mixing the :class:`mixins.RequestMixin` class into a :class:`View` class. +Provides a set of pluggable authentication policies. """ from django.contrib.auth import authenticate +from django.utils.encoding import smart_unicode, DjangoUnicodeDecodeError from rest_framework.compat import CsrfViewMiddleware from rest_framework.authtoken.models import Token import base64 @@ -17,25 +16,14 @@ class BaseAuthentication(object): def authenticate(self, request): """ - Authenticate the :obj:`request` and return a :obj:`User` or :const:`None`. [*]_ - - .. [*] The authentication context *will* typically be a :obj:`User`, - but it need not be. It can be any user-like object so long as the - permissions classes (see the :mod:`permissions` module) on the view can - handle the object and use it to determine if the request has the required - permissions or not. - - This can be an important distinction if you're implementing some token - based authentication mechanism, where the authentication context - may be more involved than simply mapping to a :obj:`User`. + Authenticate the request and return a two-tuple of (user, token). """ - return None + raise NotImplementedError(".authenticate() must be overridden.") class BasicAuthentication(BaseAuthentication): """ - Base class for HTTP Basic authentication. - Subclasses should implement `.authenticate_credentials()`. + HTTP Basic authentication against username/password. """ def authenticate(self, request): @@ -43,8 +31,6 @@ class BasicAuthentication(BaseAuthentication): Returns a `User` if a correct username and password have been supplied using HTTP Basic authentication. Otherwise returns `None`. """ - from django.utils.encoding import smart_unicode, DjangoUnicodeDecodeError - if 'HTTP_AUTHORIZATION' in request.META: auth = request.META['HTTP_AUTHORIZATION'].split() if len(auth) == 2 and auth[0].lower() == "basic": @@ -54,21 +40,13 @@ class BasicAuthentication(BaseAuthentication): return None try: - userid, password = smart_unicode(auth_parts[0]), smart_unicode(auth_parts[2]) + userid = smart_unicode(auth_parts[0]) + password = smart_unicode(auth_parts[2]) except DjangoUnicodeDecodeError: return None return self.authenticate_credentials(userid, password) - def authenticate_credentials(self, userid, password): - """ - Given the Basic authentication userid and password, authenticate - and return a user instance. - """ - raise NotImplementedError('.authenticate_credentials() must be overridden') - - -class UserBasicAuthentication(BasicAuthentication): def authenticate_credentials(self, userid, password): """ Authenticate the userid and password against username and password. @@ -85,8 +63,8 @@ class SessionAuthentication(BaseAuthentication): def authenticate(self, request): """ - Returns a :obj:`User` if the request session currently has a logged in user. - Otherwise returns :const:`None`. + Returns a `User` if the request session currently has a logged in user. + Otherwise returns `None`. """ # Get the underlying HttpRequest object -- cgit v1.2.3 From 3c8f01b985396c9bfe802f0d1e25bbb59ea2a1a9 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 15 Oct 2012 14:03:36 +0100 Subject: Explicit CSRF failure message. Fixes #60. --- rest_framework/authentication.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) (limited to 'rest_framework/authentication.py') diff --git a/rest_framework/authentication.py b/rest_framework/authentication.py index d7624708..30c78ebc 100644 --- a/rest_framework/authentication.py +++ b/rest_framework/authentication.py @@ -4,6 +4,7 @@ Provides a set of pluggable authentication policies. from django.contrib.auth import authenticate from django.utils.encoding import smart_unicode, DjangoUnicodeDecodeError +from rest_framework import exceptions from rest_framework.compat import CsrfViewMiddleware from rest_framework.authtoken.models import Token import base64 @@ -71,12 +72,23 @@ class SessionAuthentication(BaseAuthentication): http_request = request._request user = getattr(http_request, 'user', None) - if user and user.is_active: - # Enforce CSRF validation for session based authentication. - resp = CsrfViewMiddleware().process_view(http_request, None, (), {}) + # Unauthenticated, CSRF validation not required + if not user or not user.is_active: + return - if resp is None: # csrf passed - return (user, None) + # Enforce CSRF validation for session based authentication. + class CSRFCheck(CsrfViewMiddleware): + def _reject(self, request, reason): + # Return the failure reason instead of an HttpResponse + return reason + + reason = CSRFCheck().process_view(http_request, None, (), {}) + if reason: + # CSRF failed, bail with explicit error message + raise exceptions.PermissionDenied('CSRF Failed: %s' % reason) + + # CSRF passed with authenticated user + return (user, None) class TokenAuthentication(BaseAuthentication): -- cgit v1.2.3