From f1b8fee4f1e0ea2503d4e0453bdc3049edaa2598 Mon Sep 17 00:00:00 2001 From: Fernando Rocha Date: Wed, 27 Mar 2013 14:05:46 -0300 Subject: [PATCH 1/4] 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 | 30 +++++++++++++++----------- rest_framework/tests/authentication.py | 12 +++++++++++ 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/rest_framework/authentication.py b/rest_framework/authentication.py index 8f4ec536e..f4626a2e3 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') + 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') - # 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: + 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') - user = token.user - - if not user.is_active: + if not token.user.is_active: msg = 'User inactive or deleted: %s' % user.username raise exceptions.AuthenticationFailed(msg) diff --git a/rest_framework/tests/authentication.py b/rest_framework/tests/authentication.py index b663ca48f..375b19bd2 100644 --- a/rest_framework/tests/authentication.py +++ b/rest_framework/tests/authentication.py @@ -516,6 +516,18 @@ class OAuth2Tests(TestCase): response = self.csrf_client.get('/oauth2-test/', params, HTTP_AUTHORIZATION=auth) self.assertEqual(response.status_code, 200) + @unittest.skipUnless(oauth2_provider, 'django-oauth2-provider not installed') + def test_get_form_passing_auth_without_client_params(self): + """ + Ensure GETing form over OAuth without client credentials + + Regression test for issue #759: + https://github.com/tomchristie/django-rest-framework/issues/759 + """ + auth = self._create_authorization_header() + response = self.csrf_client.get('/oauth2-test/', HTTP_AUTHORIZATION=auth) + self.assertEqual(response.status_code, 200) + @unittest.skipUnless(oauth2_provider, 'django-oauth2-provider not installed') def test_post_form_passing_auth(self): """Ensure POSTing form over OAuth with correct credentials passes and does not require CSRF""" From b2cea84fae4f721e8eb6432b3d1bab1309e21a00 Mon Sep 17 00:00:00 2001 From: Fernando Rocha Date: Wed, 27 Mar 2013 19:00:36 -0300 Subject: [PATCH 2/4] Complete remove of client checks from oauth2 Signed-off-by: Fernando Rocha --- docs/api-guide/authentication.md | 2 +- rest_framework/authentication.py | 12 ++---------- rest_framework/tests/authentication.py | 9 --------- 3 files changed, 3 insertions(+), 20 deletions(-) diff --git a/docs/api-guide/authentication.md b/docs/api-guide/authentication.md index 541c65756..f1dd6f5fe 100644 --- a/docs/api-guide/authentication.md +++ b/docs/api-guide/authentication.md @@ -294,7 +294,7 @@ The only thing needed to make the `OAuth2Authentication` class work is to insert The command line to test the authentication looks like: - curl -H "Authorization: Bearer " http://localhost:8000/api/?client_id=YOUR_CLIENT_ID\&client_secret=YOUR_CLIENT_SECRET + curl -H "Authorization: Bearer " http://localhost:8000/api/ --- diff --git a/rest_framework/authentication.py b/rest_framework/authentication.py index f4626a2e3..145d42954 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') diff --git a/rest_framework/tests/authentication.py b/rest_framework/tests/authentication.py index 375b19bd2..629db4226 100644 --- a/rest_framework/tests/authentication.py +++ b/rest_framework/tests/authentication.py @@ -499,15 +499,6 @@ class OAuth2Tests(TestCase): response = self.csrf_client.get('/oauth2-test/', params, HTTP_AUTHORIZATION=auth) self.assertEqual(response.status_code, 401) - @unittest.skipUnless(oauth2_provider, 'django-oauth2-provider not installed') - def test_get_form_with_wrong_client_data_failing_auth(self): - """Ensure GETing form over OAuth with incorrect client credentials fails""" - auth = self._create_authorization_header() - params = self._client_credentials_params() - params['client_id'] += 'a' - response = self.csrf_client.get('/oauth2-test/', params, HTTP_AUTHORIZATION=auth) - self.assertEqual(response.status_code, 401) - @unittest.skipUnless(oauth2_provider, 'django-oauth2-provider not installed') def test_get_form_passing_auth(self): """Ensure GETing form over OAuth with correct client credentials succeed""" From 8ec60a22e1c14792b7021ff9b4e940e16528788a Mon Sep 17 00:00:00 2001 From: Pierre Dulac Date: Thu, 28 Mar 2013 00:57:23 +0100 Subject: [PATCH 3/4] Remove client credentials from all OAuth 2 tests --- rest_framework/tests/authentication.py | 45 ++++++-------------------- 1 file changed, 10 insertions(+), 35 deletions(-) diff --git a/rest_framework/tests/authentication.py b/rest_framework/tests/authentication.py index 629db4226..8e6d3e51a 100644 --- a/rest_framework/tests/authentication.py +++ b/rest_framework/tests/authentication.py @@ -466,17 +466,13 @@ class OAuth2Tests(TestCase): def _create_authorization_header(self, token=None): return "Bearer {0}".format(token or self.access_token.token) - def _client_credentials_params(self): - return {'client_id': self.CLIENT_ID, 'client_secret': self.CLIENT_SECRET} - @unittest.skipUnless(oauth2_provider, 'django-oauth2-provider not installed') def test_get_form_with_wrong_authorization_header_token_type_failing(self): """Ensure that a wrong token type lead to the correct HTTP error status code""" auth = "Wrong token-type-obsviously" response = self.csrf_client.get('/oauth2-test/', {}, HTTP_AUTHORIZATION=auth) self.assertEqual(response.status_code, 401) - params = self._client_credentials_params() - response = self.csrf_client.get('/oauth2-test/', params, HTTP_AUTHORIZATION=auth) + response = self.csrf_client.get('/oauth2-test/', HTTP_AUTHORIZATION=auth) self.assertEqual(response.status_code, 401) @unittest.skipUnless(oauth2_provider, 'django-oauth2-provider not installed') @@ -485,8 +481,7 @@ class OAuth2Tests(TestCase): auth = "Bearer wrong token format" response = self.csrf_client.get('/oauth2-test/', {}, HTTP_AUTHORIZATION=auth) self.assertEqual(response.status_code, 401) - params = self._client_credentials_params() - response = self.csrf_client.get('/oauth2-test/', params, HTTP_AUTHORIZATION=auth) + response = self.csrf_client.get('/oauth2-test/', HTTP_AUTHORIZATION=auth) self.assertEqual(response.status_code, 401) @unittest.skipUnless(oauth2_provider, 'django-oauth2-provider not installed') @@ -495,27 +490,13 @@ class OAuth2Tests(TestCase): auth = "Bearer wrong-token" response = self.csrf_client.get('/oauth2-test/', {}, HTTP_AUTHORIZATION=auth) self.assertEqual(response.status_code, 401) - params = self._client_credentials_params() - response = self.csrf_client.get('/oauth2-test/', params, HTTP_AUTHORIZATION=auth) + response = self.csrf_client.get('/oauth2-test/', HTTP_AUTHORIZATION=auth) self.assertEqual(response.status_code, 401) @unittest.skipUnless(oauth2_provider, 'django-oauth2-provider not installed') def test_get_form_passing_auth(self): """Ensure GETing form over OAuth with correct client credentials succeed""" auth = self._create_authorization_header() - params = self._client_credentials_params() - response = self.csrf_client.get('/oauth2-test/', params, HTTP_AUTHORIZATION=auth) - self.assertEqual(response.status_code, 200) - - @unittest.skipUnless(oauth2_provider, 'django-oauth2-provider not installed') - def test_get_form_passing_auth_without_client_params(self): - """ - Ensure GETing form over OAuth without client credentials - - Regression test for issue #759: - https://github.com/tomchristie/django-rest-framework/issues/759 - """ - auth = self._create_authorization_header() response = self.csrf_client.get('/oauth2-test/', HTTP_AUTHORIZATION=auth) self.assertEqual(response.status_code, 200) @@ -523,8 +504,7 @@ class OAuth2Tests(TestCase): def test_post_form_passing_auth(self): """Ensure POSTing form over OAuth with correct credentials passes and does not require CSRF""" auth = self._create_authorization_header() - params = self._client_credentials_params() - response = self.csrf_client.post('/oauth2-test/', params, HTTP_AUTHORIZATION=auth) + response = self.csrf_client.post('/oauth2-test/', HTTP_AUTHORIZATION=auth) self.assertEqual(response.status_code, 200) @unittest.skipUnless(oauth2_provider, 'django-oauth2-provider not installed') @@ -532,16 +512,14 @@ class OAuth2Tests(TestCase): """Ensure POSTing when there is no OAuth access token in db fails""" self.access_token.delete() auth = self._create_authorization_header() - params = self._client_credentials_params() - response = self.csrf_client.post('/oauth2-test/', params, HTTP_AUTHORIZATION=auth) + response = self.csrf_client.post('/oauth2-test/', HTTP_AUTHORIZATION=auth) self.assertIn(response.status_code, (status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN)) @unittest.skipUnless(oauth2_provider, 'django-oauth2-provider not installed') def test_post_form_with_refresh_token_failing_auth(self): """Ensure POSTing with refresh token instead of access token fails""" auth = self._create_authorization_header(token=self.refresh_token.token) - params = self._client_credentials_params() - response = self.csrf_client.post('/oauth2-test/', params, HTTP_AUTHORIZATION=auth) + response = self.csrf_client.post('/oauth2-test/', HTTP_AUTHORIZATION=auth) self.assertIn(response.status_code, (status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN)) @unittest.skipUnless(oauth2_provider, 'django-oauth2-provider not installed') @@ -550,8 +528,7 @@ class OAuth2Tests(TestCase): self.access_token.expires = datetime.datetime.now() - datetime.timedelta(seconds=10) # 10 seconds late self.access_token.save() auth = self._create_authorization_header() - params = self._client_credentials_params() - response = self.csrf_client.post('/oauth2-test/', params, HTTP_AUTHORIZATION=auth) + response = self.csrf_client.post('/oauth2-test/', HTTP_AUTHORIZATION=auth) self.assertIn(response.status_code, (status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN)) self.assertIn('Invalid token', response.content) @@ -562,10 +539,9 @@ class OAuth2Tests(TestCase): read_only_access_token.scope = oauth2_provider_scope.SCOPE_NAME_DICT['read'] read_only_access_token.save() auth = self._create_authorization_header(token=read_only_access_token.token) - params = self._client_credentials_params() - response = self.csrf_client.get('/oauth2-with-scope-test/', params, HTTP_AUTHORIZATION=auth) + response = self.csrf_client.get('/oauth2-with-scope-test/', HTTP_AUTHORIZATION=auth) self.assertEqual(response.status_code, 200) - response = self.csrf_client.post('/oauth2-with-scope-test/', params, HTTP_AUTHORIZATION=auth) + response = self.csrf_client.post('/oauth2-with-scope-test/', HTTP_AUTHORIZATION=auth) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) @unittest.skipUnless(oauth2_provider, 'django-oauth2-provider not installed') @@ -575,6 +551,5 @@ class OAuth2Tests(TestCase): read_write_access_token.scope = oauth2_provider_scope.SCOPE_NAME_DICT['write'] read_write_access_token.save() auth = self._create_authorization_header(token=read_write_access_token.token) - params = self._client_credentials_params() - response = self.csrf_client.post('/oauth2-with-scope-test/', params, HTTP_AUTHORIZATION=auth) + response = self.csrf_client.post('/oauth2-with-scope-test/', HTTP_AUTHORIZATION=auth) self.assertEqual(response.status_code, 200) From fa61b2b2f10bf07e3cb87ca947ce7f0ca51a2ede Mon Sep 17 00:00:00 2001 From: Pierre Dulac Date: Thu, 28 Mar 2013 01:05:51 +0100 Subject: [PATCH 4/4] Remove oauth2-provider backends reference from compat.py --- rest_framework/compat.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 7b2ef7384..c3e423e89 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -445,14 +445,12 @@ except ImportError: # OAuth 2 support is optional try: import provider.oauth2 as oauth2_provider - from provider.oauth2 import backends as oauth2_provider_backends from provider.oauth2 import models as oauth2_provider_models from provider.oauth2 import forms as oauth2_provider_forms from provider import scope as oauth2_provider_scope from provider import constants as oauth2_constants except ImportError: oauth2_provider = None - oauth2_provider_backends = None oauth2_provider_models = None oauth2_provider_forms = None oauth2_provider_scope = None