From 1aed9c1604be3db8f3f2d2de748eb6f7c574637a Mon Sep 17 00:00:00 2001 From: swistakm Date: Mon, 25 Feb 2013 16:58:16 +0100 Subject: add OAuthAuthentication class --- rest_framework/authentication.py | 119 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 118 insertions(+), 1 deletion(-) (limited to 'rest_framework/authentication.py') diff --git a/rest_framework/authentication.py b/rest_framework/authentication.py index 14b2136b..2bd0767e 100644 --- a/rest_framework/authentication.py +++ b/rest_framework/authentication.py @@ -9,6 +9,18 @@ from rest_framework.compat import CsrfViewMiddleware from rest_framework.authtoken.models import Token import base64 +from django.core.exceptions import ImproperlyConfigured +try: + import oauth2 +except ImportError: + oauth2 = None + +try: + import oauth_provider + from oauth_provider.store import store +except ImportError: + oauth_provider = None + class BaseAuthentication(object): """ @@ -155,4 +167,109 @@ class TokenAuthentication(BaseAuthentication): return 'Token' -# TODO: OAuthAuthentication +class OAuthAuthentication(BaseAuthentication): + """rest_framework OAuth authentication backend using + django-oath-plus""" + www_authenticate_realm = 'api' + require_active = True + + def __init__(self, **kwargs): + super(OAuthAuthentication, self).__init__(**kwargs) + + if oauth2 is None: + raise ImproperlyConfigured("The 'python-oauth2' package could not be imported. It is required for use with the 'OAuthAuthentication' class.") + + if oauth_provider is None: + raise ImproperlyConfigured("The 'django-oauth-plus' package could not be imported. It is required for use with the 'OAuthAuthentication' class.") + + + def authenticate(self, request): + """ + :returns: two-tuple of (user, auth) if authentication succeeds, or None otherwise. + """ + from oauth_provider.store import store + if self.is_valid_request(request): + oauth_request = oauth_provider.utils.get_oauth_request(request) + + if not self.check_nonce(request, oauth_request): + raise exceptions.AuthenticationFailed("Nonce check failed") + + try: + consumer = store.get_consumer(request, oauth_request, + oauth_request.get_parameter('oauth_consumer_key')) + except oauth_provider.store.InvalidConsumerError, e: + raise exceptions.AuthenticationFailed(e) + + if consumer.status != oauth_provider.consts.ACCEPTED: + raise exceptions.AuthenticationFailed('Invalid consumer key status: %s' % consumer.get_status_display()) + + try: + token = store.get_access_token(request, oauth_request, + consumer, oauth_request.get_parameter('oauth_token')) + + except oauth_provider.store.InvalidTokenError: + raise exceptions.AuthenticationFailed( + 'Invalid access token: %s' % oauth_request.get_parameter('oauth_token')) + + try: + self.validate_token(request, consumer, token) + except oauth2.Error, e: + print "got e" + raise exceptions.AuthenticationFailed(e.message) + + if not self.check_active(token.user): + raise exceptions.AuthenticationFailed('User not active: %s' % token.user.username) + + if consumer and token: + request.user = token.user + return (request.user, None) + + raise exceptions.AuthenticationFailed( + 'You are not allowed to access this resource.') + + return None + + def authenticate_header(self, request): + return 'OAuth realm="%s"' % self.www_authenticate_realm + + def is_in(self, params): + """ + Checks to ensure that all the OAuth parameter names are in the + provided ``params``. + """ + from oauth_provider.consts import OAUTH_PARAMETERS_NAMES + + for param_name in OAUTH_PARAMETERS_NAMES: + if param_name not in params: + return False + + return True + + def is_valid_request(self, request): + """ + Checks whether the required parameters are either in the HTTP + ``Authorization`` header sent by some clients (the preferred method + according to OAuth spec) or fall back to ``GET/POST``. + """ + auth_params = request.META.get("HTTP_AUTHORIZATION", []) + return self.is_in(auth_params) or self.is_in(request.REQUEST) + + def validate_token(self, request, consumer, token): + oauth_server, oauth_request = oauth_provider.utils.initialize_server_request(request) + return oauth_server.verify_request(oauth_request, consumer, token) + + def check_active(self, user): + """ + Ensures the user has an active account. + + Optimized for the ``django.contrib.auth.models.User`` case. + """ + if not self.require_active: + # Ignore & move on. + return True + + return user.is_active + + def check_nonce(self, request, oauth_request): + """Checks nonce of request""" + return store.check_nonce(request, oauth_request, oauth_request['oauth_nonce']) -- cgit v1.2.3 From 59a6f5f463472656518cb0680b9da5f22a724882 Mon Sep 17 00:00:00 2001 From: swistakm Date: Tue, 26 Feb 2013 11:22:21 +0100 Subject: Move oauth2 and django-oauth-plus imports to compat and fix some minor issues - alias oauth2 as oauth - remove rouge print - remove docstring markups - OAuthAuthentication.authenticate() now returns (user, token) two-tuple on success - don't set request.user because it's already set --- rest_framework/authentication.py | 31 ++++++++++--------------------- 1 file changed, 10 insertions(+), 21 deletions(-) (limited to 'rest_framework/authentication.py') diff --git a/rest_framework/authentication.py b/rest_framework/authentication.py index 2bd0767e..b507c5e1 100644 --- a/rest_framework/authentication.py +++ b/rest_framework/authentication.py @@ -4,23 +4,14 @@ Provides a set of pluggable authentication policies. from __future__ import unicode_literals from django.contrib.auth import authenticate from django.utils.encoding import DjangoUnicodeDecodeError +from django.core.exceptions import ImproperlyConfigured from rest_framework import exceptions, HTTP_HEADER_ENCODING from rest_framework.compat import CsrfViewMiddleware +from rest_framework.compat import oauth +from rest_framework.compat import oauth_provider from rest_framework.authtoken.models import Token import base64 -from django.core.exceptions import ImproperlyConfigured -try: - import oauth2 -except ImportError: - oauth2 = None - -try: - import oauth_provider - from oauth_provider.store import store -except ImportError: - oauth_provider = None - class BaseAuthentication(object): """ @@ -169,15 +160,15 @@ class TokenAuthentication(BaseAuthentication): class OAuthAuthentication(BaseAuthentication): """rest_framework OAuth authentication backend using - django-oath-plus""" + django-oath-plus and oauth2""" www_authenticate_realm = 'api' require_active = True def __init__(self, **kwargs): super(OAuthAuthentication, self).__init__(**kwargs) - if oauth2 is None: - raise ImproperlyConfigured("The 'python-oauth2' package could not be imported. It is required for use with the 'OAuthAuthentication' class.") + if oauth is None: + raise ImproperlyConfigured("The 'oauth2' package could not be imported. It is required for use with the 'OAuthAuthentication' class.") if oauth_provider is None: raise ImproperlyConfigured("The 'django-oauth-plus' package could not be imported. It is required for use with the 'OAuthAuthentication' class.") @@ -185,7 +176,7 @@ class OAuthAuthentication(BaseAuthentication): def authenticate(self, request): """ - :returns: two-tuple of (user, auth) if authentication succeeds, or None otherwise. + Returns two-tuple of (user, auth token) if authentication succeeds, or None otherwise. """ from oauth_provider.store import store if self.is_valid_request(request): @@ -213,16 +204,14 @@ class OAuthAuthentication(BaseAuthentication): try: self.validate_token(request, consumer, token) - except oauth2.Error, e: - print "got e" + except oauth.Error, e: raise exceptions.AuthenticationFailed(e.message) if not self.check_active(token.user): raise exceptions.AuthenticationFailed('User not active: %s' % token.user.username) if consumer and token: - request.user = token.user - return (request.user, None) + return (token.user, token) raise exceptions.AuthenticationFailed( 'You are not allowed to access this resource.') @@ -272,4 +261,4 @@ class OAuthAuthentication(BaseAuthentication): def check_nonce(self, request, oauth_request): """Checks nonce of request""" - return store.check_nonce(request, oauth_request, oauth_request['oauth_nonce']) + return oauth_provider.store.store.check_nonce(request, oauth_request, oauth_request['oauth_nonce']) -- cgit v1.2.3 From da9d7fb8ec19f289d9d2777738a45007c41a1289 Mon Sep 17 00:00:00 2001 From: Pierre Dulac Date: Fri, 1 Mar 2013 02:08:58 +0100 Subject: Add the OAuth2Authentication class --- rest_framework/authentication.py | 85 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 84 insertions(+), 1 deletion(-) (limited to 'rest_framework/authentication.py') diff --git a/rest_framework/authentication.py b/rest_framework/authentication.py index 14b2136b..c20d9cb5 100644 --- a/rest_framework/authentication.py +++ b/rest_framework/authentication.py @@ -6,6 +6,7 @@ from django.contrib.auth import authenticate from django.utils.encoding import DjangoUnicodeDecodeError from rest_framework import exceptions, HTTP_HEADER_ENCODING from rest_framework.compat import CsrfViewMiddleware +from rest_framework.compat import oauth2_provider, oauth2 from rest_framework.authtoken.models import Token import base64 @@ -155,4 +156,86 @@ class TokenAuthentication(BaseAuthentication): return 'Token' -# TODO: OAuthAuthentication +class OAuth2Authentication(BaseAuthentication): + """ + OAuth 2 authentication backend using `django-oauth2-provider` + """ + require_active = True + + def __init__(self, **kwargs): + super(OAuth2Authentication, self).__init__(**kwargs) + if oauth2_provider is None: + raise ImproperlyConfigured("The 'django-oauth2-provider' package could not be imported. It is required for use with the 'OAuth2Authentication' class.") + + def authenticate(self, request): + """ + The Bearer type is the only finalized type + + Read the spec for more details + http://tools.ietf.org/html/rfc6749#section-7.1 + """ + auth = request.META.get('HTTP_AUTHORIZATION', '').split() + print auth + if not auth or auth[0].lower() != "bearer": + return None + + if len(auth) != 2: + raise exceptions.AuthenticationFailed('Invalid token header') + + return self.authenticate_credentials(request, auth[1]) + + def authenticate_credentials(self, request, access_token): + """ + :returns: two-tuple of (user, auth) if authentication succeeds, or None otherwise. + """ + + # authenticate the client + oauth2_client_form = oauth2.forms.ClientAuthForm(request.REQUEST) + if not oauth2_client_form.is_valid(): + raise exceptions.AuthenticationFailed("Client could not be validated") + client = oauth2_client_form.cleaned_data.get('client') + + # retrieve the `oauth2.models.OAuth2AccessToken` instance from the access_token + auth_backend = oauth2.backends.AccessTokenBackend() + token = auth_backend.authenticate(access_token, client) + if token is None: + raise exceptions.AuthenticationFailed("Invalid token") # does not exist or is expired + + # TODO check scope + # try: + # self.validate_token(request, consumer, token) + # except oauth2.Error, e: + # print "got e" + # raise exceptions.AuthenticationFailed(e.message) + + if not self.check_active(token.user): + raise exceptions.AuthenticationFailed('User not active: %s' % token.user.username) + + if client and token: + request.user = token.user + return (request.user, None) + + raise exceptions.AuthenticationFailed( + 'You are not allowed to access this resource.') + + return None + + def authenticate_header(self, request): + """ + Bearer is the only finalized type currently + + Check details on the `OAuth2Authentication.authenticate` method + """ + return 'Bearer' + + def check_active(self, user): + """ + Ensures the user has an active account. + + Optimized for the ``django.contrib.auth.models.User`` case. + """ + if not self.require_active: + # Ignore & move on. + return True + + return user.is_active -- cgit v1.2.3 From 9d5c3060386cc8deb4ee55eda022f0a134e897c0 Mon Sep 17 00:00:00 2001 From: Pierre Dulac Date: Fri, 1 Mar 2013 11:53:30 +0100 Subject: Improve the `django-oauth2-provider` import block to avoid naming collision with `oauth2` used for OAuth 1 --- rest_framework/authentication.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'rest_framework/authentication.py') diff --git a/rest_framework/authentication.py b/rest_framework/authentication.py index c20d9cb5..c94af405 100644 --- a/rest_framework/authentication.py +++ b/rest_framework/authentication.py @@ -6,7 +6,7 @@ from django.contrib.auth import authenticate from django.utils.encoding import DjangoUnicodeDecodeError from rest_framework import exceptions, HTTP_HEADER_ENCODING from rest_framework.compat import CsrfViewMiddleware -from rest_framework.compat import oauth2_provider, oauth2 +from rest_framework.compat import oauth2_provider from rest_framework.authtoken.models import Token import base64 @@ -190,13 +190,13 @@ class OAuth2Authentication(BaseAuthentication): """ # authenticate the client - oauth2_client_form = oauth2.forms.ClientAuthForm(request.REQUEST) + oauth2_client_form = oauth2_provider.forms.ClientAuthForm(request.REQUEST) if not oauth2_client_form.is_valid(): raise exceptions.AuthenticationFailed("Client could not be validated") client = oauth2_client_form.cleaned_data.get('client') - # retrieve the `oauth2.models.OAuth2AccessToken` instance from the access_token - auth_backend = oauth2.backends.AccessTokenBackend() + # retrieve the `oauth2_provider.models.OAuth2AccessToken` instance from the access_token + auth_backend = oauth2_provider.backends.AccessTokenBackend() token = auth_backend.authenticate(access_token, client) if token is None: raise exceptions.AuthenticationFailed("Invalid token") # does not exist or is expired @@ -204,7 +204,7 @@ class OAuth2Authentication(BaseAuthentication): # TODO check scope # try: # self.validate_token(request, consumer, token) - # except oauth2.Error, e: + # except oauth2_provider.Error, e: # print "got e" # raise exceptions.AuthenticationFailed(e.message) -- cgit v1.2.3 From d4c2267187128c60927931c685a5c41a95c300bd Mon Sep 17 00:00:00 2001 From: Pierre Dulac Date: Fri, 1 Mar 2013 12:08:28 +0100 Subject: Clean up some print and comments --- rest_framework/authentication.py | 6 ------ 1 file changed, 6 deletions(-) (limited to 'rest_framework/authentication.py') diff --git a/rest_framework/authentication.py b/rest_framework/authentication.py index c94af405..c74078fc 100644 --- a/rest_framework/authentication.py +++ b/rest_framework/authentication.py @@ -175,7 +175,6 @@ class OAuth2Authentication(BaseAuthentication): http://tools.ietf.org/html/rfc6749#section-7.1 """ auth = request.META.get('HTTP_AUTHORIZATION', '').split() - print auth if not auth or auth[0].lower() != "bearer": return None @@ -202,11 +201,6 @@ class OAuth2Authentication(BaseAuthentication): raise exceptions.AuthenticationFailed("Invalid token") # does not exist or is expired # TODO check scope - # try: - # self.validate_token(request, consumer, token) - # except oauth2_provider.Error, e: - # print "got e" - # raise exceptions.AuthenticationFailed(e.message) if not self.check_active(token.user): raise exceptions.AuthenticationFailed('User not active: %s' % token.user.username) -- cgit v1.2.3 From c449dd4f4d8c9602c826e906870a87c13d6689de Mon Sep 17 00:00:00 2001 From: Pierre Dulac Date: Sat, 2 Mar 2013 20:17:14 +0100 Subject: Properly fail to wrong Authorization token type --- rest_framework/authentication.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'rest_framework/authentication.py') diff --git a/rest_framework/authentication.py b/rest_framework/authentication.py index c74078fc..d4ba7967 100644 --- a/rest_framework/authentication.py +++ b/rest_framework/authentication.py @@ -176,7 +176,7 @@ class OAuth2Authentication(BaseAuthentication): """ auth = request.META.get('HTTP_AUTHORIZATION', '').split() if not auth or auth[0].lower() != "bearer": - return None + raise exceptions.AuthenticationFailed('Invalid Authorization token type') if len(auth) != 2: raise exceptions.AuthenticationFailed('Invalid token header') @@ -212,8 +212,6 @@ class OAuth2Authentication(BaseAuthentication): raise exceptions.AuthenticationFailed( 'You are not allowed to access this resource.') - return None - def authenticate_header(self, request): """ Bearer is the only finalized type currently -- cgit v1.2.3 From 44930f30915298cda8c1474ed9ec4415258c3e6f Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 7 Mar 2013 09:15:05 +0000 Subject: Fix Py3k syntax errors --- rest_framework/authentication.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'rest_framework/authentication.py') diff --git a/rest_framework/authentication.py b/rest_framework/authentication.py index 24a8e336..460c1e53 100644 --- a/rest_framework/authentication.py +++ b/rest_framework/authentication.py @@ -185,7 +185,7 @@ class OAuthAuthentication(BaseAuthentication): try: consumer_key = oauth_request.get_parameter('oauth_consumer_key') consumer = oauth_provider_store.get_consumer(request, oauth_request, consumer_key) - except oauth_provider_store.InvalidConsumerError, err: + except oauth_provider_store.InvalidConsumerError as err: raise exceptions.AuthenticationFailed(err) if consumer.status != oauth_provider.consts.ACCEPTED: @@ -201,8 +201,8 @@ class OAuthAuthentication(BaseAuthentication): try: self.validate_token(request, consumer, token) - except oauth.Error, e: - raise exceptions.AuthenticationFailed(e.message) + except oauth.Error as err: + raise exceptions.AuthenticationFailed(err.message) user = token.user -- cgit v1.2.3 From 1d62594fa9ed87545a312681f999bbfa0237491b Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 7 Mar 2013 15:44:36 +0000 Subject: Clean ups. --- rest_framework/authentication.py | 63 ++++++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 28 deletions(-) (limited to 'rest_framework/authentication.py') diff --git a/rest_framework/authentication.py b/rest_framework/authentication.py index 460c1e53..8ee3a900 100644 --- a/rest_framework/authentication.py +++ b/rest_framework/authentication.py @@ -157,6 +157,7 @@ class OAuthAuthentication(BaseAuthentication): OAuth 1.0a authentication backend using `django-oauth-plus` and `oauth2`. Note: The `oauth2` package actually provides oauth1.0a support. Urg. + We import it from the `compat` module as `oauth`. """ www_authenticate_realm = 'api' @@ -164,23 +165,42 @@ class OAuthAuthentication(BaseAuthentication): super(OAuthAuthentication, self).__init__(**kwargs) if oauth is None: - raise ImproperlyConfigured("The 'oauth2' package could not be imported. It is required for use with the 'OAuthAuthentication' class.") + raise ImproperlyConfigured( + "The 'oauth2' package could not be imported." + "It is required for use with the 'OAuthAuthentication' class.") if oauth_provider is None: - raise ImproperlyConfigured("The 'django-oauth-plus' package could not be imported. It is required for use with the 'OAuthAuthentication' class.") + raise ImproperlyConfigured( + "The 'django-oauth-plus' package could not be imported." + "It is required for use with the 'OAuthAuthentication' class.") def authenticate(self, request): """ Returns two-tuple of (user, token) if authentication succeeds, or None otherwise. """ - if not self.is_valid_request(request): + try: + oauth_request = oauth_provider.utils.get_oauth_request(request) + except oauth.Error as err: + raise exceptions.AuthenticationFailed(err.message) + + oauth_params = oauth_provider.consts.OAUTH_PARAMETERS_NAMES + + found = any(param for param in oauth_params if param in oauth_request) + missing = list(param for param in oauth_params if param not in oauth_request) + + if not found: + # OAuth authentication was not attempted. return None - oauth_request = oauth_provider.utils.get_oauth_request(request) + if missing: + # OAuth was attempted but missing parameters. + msg = 'Missing parameters: %s' % (', '.join(missing)) + raise exceptions.AuthenticationFailed(msg) if not self.check_nonce(request, oauth_request): - raise exceptions.AuthenticationFailed("Nonce check failed") + msg = 'Nonce check failed' + raise exceptions.AuthenticationFailed(msg) try: consumer_key = oauth_request.get_parameter('oauth_consumer_key') @@ -207,40 +227,27 @@ class OAuthAuthentication(BaseAuthentication): user = token.user if not user.is_active: - raise exceptions.AuthenticationFailed('User inactive or deleted: %s' % user.username) + msg = 'User inactive or deleted: %s' % user.username + raise exceptions.AuthenticationFailed(msg) return (token.user, token) def authenticate_header(self, request): - return 'OAuth realm="%s"' % self.www_authenticate_realm - - def is_in(self, params): """ - Checks to ensure that all the OAuth parameter names are in the - provided ``params``. + If permission is denied, return a '401 Unauthorized' response, + with an appropraite 'WWW-Authenticate' header. """ - for param_name in oauth_provider.consts.OAUTH_PARAMETERS_NAMES: - if param_name not in params: - return False - - return True + return 'OAuth realm="%s"' % self.www_authenticate_realm - def is_valid_request(self, request): + def validate_token(self, request, consumer, token): """ - Checks whether the required parameters are either in the HTTP - `Authorization` header sent by some clients. - (The preferred method according to OAuth spec.) - Or fall back to `GET/POST`. + Check the token and raise an `oauth.Error` exception if invalid. """ - auth_params = request.META.get('HTTP_AUTHORIZATION', []) - return self.is_in(auth_params) or self.is_in(request.REQUEST) - - def validate_token(self, request, consumer, token): oauth_server, oauth_request = oauth_provider.utils.initialize_server_request(request) - return oauth_server.verify_request(oauth_request, consumer, token) + oauth_server.verify_request(oauth_request, consumer, token) def check_nonce(self, request, oauth_request): """ - Checks nonce of request. + Checks nonce of request, and return True if valid. """ - return oauth_provider.store.store.check_nonce(request, oauth_request, oauth_request['oauth_nonce']) + return oauth_provider_store.check_nonce(request, oauth_request, oauth_request['oauth_nonce']) -- cgit v1.2.3 From 650d8e6a8ecd092e1bdd7269097044563f4ea449 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 8 Mar 2013 20:23:11 +0000 Subject: More bits of cleanup --- rest_framework/authentication.py | 125 +++++++++++++++++++++------------------ 1 file changed, 66 insertions(+), 59 deletions(-) (limited to 'rest_framework/authentication.py') diff --git a/rest_framework/authentication.py b/rest_framework/authentication.py index 4d6e3375..3000de3a 100644 --- a/rest_framework/authentication.py +++ b/rest_framework/authentication.py @@ -12,6 +12,19 @@ from rest_framework.authtoken.models import Token import base64 +def get_authorization_header(request): + """ + Return request's 'Authorization:' header, as a bytestring. + + Hide some test client ickyness where the header can be unicode. + """ + auth = request.META.get('HTTP_AUTHORIZATION', b'') + if type(auth) == type(''): + # Work around django test client oddness + auth = auth.encode(HTTP_HEADER_ENCODING) + return auth + + class BaseAuthentication(object): """ All authentication classes should extend BaseAuthentication. @@ -43,22 +56,22 @@ class BasicAuthentication(BaseAuthentication): Returns a `User` if a correct username and password have been supplied using HTTP Basic authentication. Otherwise returns `None`. """ - auth = request.META.get('HTTP_AUTHORIZATION', b'') - if type(auth) == type(''): - # Work around django test client oddness - auth = auth.encode(HTTP_HEADER_ENCODING) - auth = auth.split() + auth = get_authorization_header(request).split() if not auth or auth[0].lower() != b'basic': return None - if len(auth) != 2: - raise exceptions.AuthenticationFailed('Invalid basic header') + if len(auth) == 1: + msg = 'Invalid basic header. No credentials provided.' + if len(auth) > 2: + msg = 'Invalid basic header. Credentials string should not contain spaces.' + raise exceptions.AuthenticationFailed(msg) try: auth_parts = base64.b64decode(auth[1]).decode(HTTP_HEADER_ENCODING).partition(':') except (TypeError, UnicodeDecodeError): - raise exceptions.AuthenticationFailed('Invalid basic header') + msg = 'Invalid basic header. Credentials not correctly base64 encoded' + raise exceptions.AuthenticationFailed(msg) userid, password = auth_parts[0], auth_parts[2] return self.authenticate_credentials(userid, password) @@ -68,9 +81,9 @@ class BasicAuthentication(BaseAuthentication): 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) - raise exceptions.AuthenticationFailed('Invalid username/password') + if user is None or not user.is_active: + raise exceptions.AuthenticationFailed('Invalid username/password') + return (user, None) def authenticate_header(self, request): return 'Basic realm="%s"' % self.www_authenticate_realm @@ -129,13 +142,16 @@ class TokenAuthentication(BaseAuthentication): """ def authenticate(self, request): - auth = request.META.get('HTTP_AUTHORIZATION', '').split() + auth = get_authorization_header(request).split() if not auth or auth[0].lower() != "token": return None - if len(auth) != 2: - raise exceptions.AuthenticationFailed('Invalid token header') + if len(auth) == 1: + msg = 'Invalid token header. No credentials provided.' + if len(auth) > 2: + msg = 'Invalid token header. Token string should not contain spaces.' + raise exceptions.AuthenticationFailed(msg) return self.authenticate_credentials(auth[1]) @@ -145,9 +161,10 @@ class TokenAuthentication(BaseAuthentication): except self.model.DoesNotExist: raise exceptions.AuthenticationFailed('Invalid token') - if token.user.is_active: - return (token.user, token) - raise exceptions.AuthenticationFailed('User inactive or deleted') + if not token.user.is_active: + raise exceptions.AuthenticationFailed('User inactive or deleted') + + return (token.user, token) def authenticate_header(self, request): return 'Token' @@ -162,8 +179,8 @@ class OAuthAuthentication(BaseAuthentication): """ www_authenticate_realm = 'api' - def __init__(self, **kwargs): - super(OAuthAuthentication, self).__init__(**kwargs) + def __init__(self, *args, **kwargs): + super(OAuthAuthentication, self).__init__(*args, **kwargs) if oauth is None: raise ImproperlyConfigured( @@ -258,57 +275,59 @@ class OAuth2Authentication(BaseAuthentication): """ OAuth 2 authentication backend using `django-oauth2-provider` """ - require_active = True + www_authenticate_realm = 'api' + + def __init__(self, *args, **kwargs): + super(OAuth2Authentication, self).__init__(*args, **kwargs) - def __init__(self, **kwargs): - super(OAuth2Authentication, self).__init__(**kwargs) if oauth2_provider is None: - raise ImproperlyConfigured("The 'django-oauth2-provider' package could not be imported. It is required for use with the 'OAuth2Authentication' class.") + raise ImproperlyConfigured( + "The 'django-oauth2-provider' package could not be imported. " + "It is required for use with the 'OAuth2Authentication' class.") def authenticate(self, request): """ - The Bearer type is the only finalized type - - Read the spec for more details - http://tools.ietf.org/html/rfc6749#section-7.1 + Returns two-tuple of (user, token) if authentication succeeds, + or None otherwise. """ - auth = request.META.get('HTTP_AUTHORIZATION', '').split() - if not auth or auth[0].lower() != "bearer": - raise exceptions.AuthenticationFailed('Invalid Authorization token type') - if len(auth) != 2: - raise exceptions.AuthenticationFailed('Invalid token header') + auth = get_authorization_header(request).split() + + if not auth or auth[0].lower() != 'bearer': + return None + + if len(auth) == 1: + msg = 'Invalid bearer header. No credentials provided.' + if len(auth) > 2: + msg = 'Invalid bearer header. Token string should not contain spaces.' + raise exceptions.AuthenticationFailed(msg) return self.authenticate_credentials(request, auth[1]) def authenticate_credentials(self, request, access_token): """ - :returns: two-tuple of (user, auth) if authentication succeeds, or None otherwise. + Authenticate the request, given the access token. """ - # authenticate the client + # Authenticate the client oauth2_client_form = oauth2_provider_forms.ClientAuthForm(request.REQUEST) if not oauth2_client_form.is_valid(): - raise exceptions.AuthenticationFailed("Client could not be validated") + raise exceptions.AuthenticationFailed('Client could not be validated') client = oauth2_client_form.cleaned_data.get('client') - # retrieve the `oauth2_provider.models.OAuth2AccessToken` instance from the access_token + # Retrieve the `OAuth2AccessToken` instance from the access_token auth_backend = oauth2_provider_backends.AccessTokenBackend() token = auth_backend.authenticate(access_token, client) if token is None: - raise exceptions.AuthenticationFailed("Invalid token") # does not exist or is expired - - # TODO check scope + raise exceptions.AuthenticationFailed('Invalid token') - if not self.check_active(token.user): - raise exceptions.AuthenticationFailed('User not active: %s' % token.user.username) + user = token.user - if client and token: - request.user = token.user - return (request.user, None) + if not user.is_active: + msg = 'User inactive or deleted: %s' % user.username + raise exceptions.AuthenticationFailed(msg) - raise exceptions.AuthenticationFailed( - 'You are not allowed to access this resource.') + return (token.user, token) def authenticate_header(self, request): """ @@ -316,16 +335,4 @@ class OAuth2Authentication(BaseAuthentication): Check details on the `OAuth2Authentication.authenticate` method """ - return 'Bearer' - - def check_active(self, user): - """ - Ensures the user has an active account. - - Optimized for the ``django.contrib.auth.models.User`` case. - """ - if not self.require_active: - # Ignore & move on. - return True - - return user.is_active + return 'Bearer realm="%s"' % self.www_authenticate_realm -- cgit v1.2.3 From 2596c12a21003d230beb101aa93ddf83a1995305 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 8 Mar 2013 22:56:24 +0000 Subject: Fixes for auth header checking. --- rest_framework/authentication.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) (limited to 'rest_framework/authentication.py') diff --git a/rest_framework/authentication.py b/rest_framework/authentication.py index 3000de3a..b4b73699 100644 --- a/rest_framework/authentication.py +++ b/rest_framework/authentication.py @@ -63,7 +63,8 @@ class BasicAuthentication(BaseAuthentication): if len(auth) == 1: msg = 'Invalid basic header. No credentials provided.' - if len(auth) > 2: + raise exceptions.AuthenticationFailed(msg) + elif len(auth) > 2: msg = 'Invalid basic header. Credentials string should not contain spaces.' raise exceptions.AuthenticationFailed(msg) @@ -144,12 +145,13 @@ class TokenAuthentication(BaseAuthentication): def authenticate(self, request): auth = get_authorization_header(request).split() - if not auth or auth[0].lower() != "token": + if not auth or auth[0].lower() != b'token': return None if len(auth) == 1: msg = 'Invalid token header. No credentials provided.' - if len(auth) > 2: + raise exceptions.AuthenticationFailed(msg) + elif len(auth) > 2: msg = 'Invalid token header. Token string should not contain spaces.' raise exceptions.AuthenticationFailed(msg) @@ -293,12 +295,13 @@ class OAuth2Authentication(BaseAuthentication): auth = get_authorization_header(request).split() - if not auth or auth[0].lower() != 'bearer': + if not auth or auth[0].lower() != b'bearer': return None if len(auth) == 1: msg = 'Invalid bearer header. No credentials provided.' - if len(auth) > 2: + raise exceptions.AuthenticationFailed(msg) + elif len(auth) > 2: msg = 'Invalid bearer header. Token string should not contain spaces.' raise exceptions.AuthenticationFailed(msg) -- cgit v1.2.3 From 4055129662023a13c40ccea108a990b818d244cd Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 22 Mar 2013 21:31:32 +0000 Subject: If oauth is not attempted don't throw an error. Fixes #748. --- rest_framework/authentication.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'rest_framework/authentication.py') diff --git a/rest_framework/authentication.py b/rest_framework/authentication.py index b4b73699..8f4ec536 100644 --- a/rest_framework/authentication.py +++ b/rest_framework/authentication.py @@ -204,6 +204,9 @@ class OAuthAuthentication(BaseAuthentication): except oauth.Error as err: raise exceptions.AuthenticationFailed(err.message) + if not oauth_request: + return None + oauth_params = oauth_provider.consts.OAUTH_PARAMETERS_NAMES found = any(param for param in oauth_params if param in oauth_request) -- cgit v1.2.3 From f1b8fee4f1e0ea2503d4e0453bdc3049edaa2598 Mon Sep 17 00:00:00 2001 From: Fernando Rocha Date: Wed, 27 Mar 2013 14:05:46 -0300 Subject: client credentials should be optional (fix #759) client credentials should only be required on token request Signed-off-by: Fernando Rocha --- rest_framework/authentication.py | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) (limited to 'rest_framework/authentication.py') diff --git a/rest_framework/authentication.py b/rest_framework/authentication.py index 8f4ec536..f4626a2e 100644 --- a/rest_framework/authentication.py +++ b/rest_framework/authentication.py @@ -2,14 +2,16 @@ Provides a set of pluggable authentication policies. """ from __future__ import unicode_literals +import base64 +from datetime import datetime + from django.contrib.auth import authenticate from django.core.exceptions import ImproperlyConfigured from rest_framework import exceptions, HTTP_HEADER_ENCODING from rest_framework.compat import CsrfViewMiddleware from rest_framework.compat import oauth, oauth_provider, oauth_provider_store -from rest_framework.compat import oauth2_provider, oauth2_provider_forms, oauth2_provider_backends +from rest_framework.compat import oauth2_provider, oauth2_provider_forms from rest_framework.authtoken.models import Token -import base64 def get_authorization_header(request): @@ -314,22 +316,24 @@ class OAuth2Authentication(BaseAuthentication): """ Authenticate the request, given the access token. """ + client = None # Authenticate the client - oauth2_client_form = oauth2_provider_forms.ClientAuthForm(request.REQUEST) - if not oauth2_client_form.is_valid(): - raise exceptions.AuthenticationFailed('Client could not be validated') - client = oauth2_client_form.cleaned_data.get('client') - - # Retrieve the `OAuth2AccessToken` instance from the access_token - auth_backend = oauth2_provider_backends.AccessTokenBackend() - token = auth_backend.authenticate(access_token, client) - if token is None: - raise exceptions.AuthenticationFailed('Invalid token') + if 'client_id' in request.REQUEST: + oauth2_client_form = oauth2_provider_forms.ClientAuthForm(request.REQUEST) + if not oauth2_client_form.is_valid(): + raise exceptions.AuthenticationFailed('Client could not be validated') + client = oauth2_client_form.cleaned_data.get('client') - user = token.user + try: + token = oauth2_provider.models.AccessToken.objects.select_related('user') + if client is not None: + token = token.filter(client=client) + token = token.get(token=access_token, expires__gt=datetime.now()) + except oauth2_provider.models.AccessToken.DoesNotExist: + raise exceptions.AuthenticationFailed('Invalid token') - if not user.is_active: + if not token.user.is_active: msg = 'User inactive or deleted: %s' % user.username raise exceptions.AuthenticationFailed(msg) -- cgit v1.2.3 From b2cea84fae4f721e8eb6432b3d1bab1309e21a00 Mon Sep 17 00:00:00 2001 From: Fernando Rocha Date: Wed, 27 Mar 2013 19:00:36 -0300 Subject: Complete remove of client checks from oauth2 Signed-off-by: Fernando Rocha --- rest_framework/authentication.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) (limited to 'rest_framework/authentication.py') diff --git a/rest_framework/authentication.py b/rest_framework/authentication.py index f4626a2e..145d4295 100644 --- a/rest_framework/authentication.py +++ b/rest_framework/authentication.py @@ -316,19 +316,11 @@ class OAuth2Authentication(BaseAuthentication): """ Authenticate the request, given the access token. """ - client = None - - # Authenticate the client - if 'client_id' in request.REQUEST: - oauth2_client_form = oauth2_provider_forms.ClientAuthForm(request.REQUEST) - if not oauth2_client_form.is_valid(): - raise exceptions.AuthenticationFailed('Client could not be validated') - client = oauth2_client_form.cleaned_data.get('client') try: token = oauth2_provider.models.AccessToken.objects.select_related('user') - if client is not None: - token = token.filter(client=client) + # TODO: Change to timezone aware datetime when oauth2_provider add + # support to it. token = token.get(token=access_token, expires__gt=datetime.now()) except oauth2_provider.models.AccessToken.DoesNotExist: raise exceptions.AuthenticationFailed('Invalid token') -- cgit v1.2.3 From 74fbd5ccc5b2aa2f0aab25ead5ffa36024079fcf Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 3 Apr 2013 09:20:36 +0100 Subject: Fix bug with inactive user accessing OAuth --- rest_framework/authentication.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'rest_framework/authentication.py') diff --git a/rest_framework/authentication.py b/rest_framework/authentication.py index 145d4295..3e7e89e8 100644 --- a/rest_framework/authentication.py +++ b/rest_framework/authentication.py @@ -10,7 +10,7 @@ from django.core.exceptions import ImproperlyConfigured from rest_framework import exceptions, HTTP_HEADER_ENCODING from rest_framework.compat import CsrfViewMiddleware from rest_framework.compat import oauth, oauth_provider, oauth_provider_store -from rest_framework.compat import oauth2_provider, oauth2_provider_forms +from rest_framework.compat import oauth2_provider from rest_framework.authtoken.models import Token @@ -325,11 +325,13 @@ class OAuth2Authentication(BaseAuthentication): except oauth2_provider.models.AccessToken.DoesNotExist: raise exceptions.AuthenticationFailed('Invalid token') - if not token.user.is_active: + user = token.user + + if not user.is_active: msg = 'User inactive or deleted: %s' % user.username raise exceptions.AuthenticationFailed(msg) - return (token.user, token) + return (user, token) def authenticate_header(self, request): """ -- cgit v1.2.3 From 80d28de03477a8dab3832707ca4489c4b2e78e5d Mon Sep 17 00:00:00 2001 From: Atle Frenvik Sveen Date: Wed, 3 Apr 2013 13:10:41 +0200 Subject: Fix the fact that InvalidConsumerError and InvalidTokenError wasn't imported correctly from oauth_provider --- rest_framework/authentication.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'rest_framework/authentication.py') diff --git a/rest_framework/authentication.py b/rest_framework/authentication.py index 3e7e89e8..1eebb5b9 100644 --- a/rest_framework/authentication.py +++ b/rest_framework/authentication.py @@ -230,7 +230,7 @@ class OAuthAuthentication(BaseAuthentication): try: consumer_key = oauth_request.get_parameter('oauth_consumer_key') consumer = oauth_provider_store.get_consumer(request, oauth_request, consumer_key) - except oauth_provider_store.InvalidConsumerError as err: + except oauth_provider.store.InvalidConsumerError as err: raise exceptions.AuthenticationFailed(err) if consumer.status != oauth_provider.consts.ACCEPTED: @@ -240,7 +240,7 @@ class OAuthAuthentication(BaseAuthentication): try: token_param = oauth_request.get_parameter('oauth_token') token = oauth_provider_store.get_access_token(request, oauth_request, consumer, token_param) - except oauth_provider_store.InvalidTokenError: + except oauth_provider.store.InvalidTokenError: msg = 'Invalid access token: %s' % oauth_request.get_parameter('oauth_token') raise exceptions.AuthenticationFailed(msg) -- cgit v1.2.3 From 95abe6e8445f59f9e52609b0c54d9276830dbfd3 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 25 Apr 2013 12:47:34 +0100 Subject: Cleanup docstrings --- 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 1eebb5b9..9caca788 100644 --- a/rest_framework/authentication.py +++ b/rest_framework/authentication.py @@ -1,5 +1,5 @@ """ -Provides a set of pluggable authentication policies. +Provides various authentication policies. """ from __future__ import unicode_literals import base64 -- cgit v1.2.3