From 1062d71f8be929f1f7e6910a8d573ac643082bae Mon Sep 17 00:00:00 2001 From: swistakm Date: Mon, 25 Feb 2013 16:56:14 +0100 Subject: add tests for OAuth authentication --- rest_framework/tests/authentication.py | 145 ++++++++++++++++++++++++++++++++- 1 file changed, 142 insertions(+), 3 deletions(-) (limited to 'rest_framework') diff --git a/rest_framework/tests/authentication.py b/rest_framework/tests/authentication.py index c9df1733..88b6fd16 100644 --- a/rest_framework/tests/authentication.py +++ b/rest_framework/tests/authentication.py @@ -2,15 +2,19 @@ from __future__ import unicode_literals from django.contrib.auth.models import User from django.http import HttpResponse from django.test import Client, TestCase -from rest_framework import HTTP_HEADER_ENCODING +import time +from rest_framework import HTTP_HEADER_ENCODING, status from rest_framework import permissions from rest_framework.authtoken.models import Token -from rest_framework.authentication import TokenAuthentication, BasicAuthentication, SessionAuthentication +from rest_framework.authentication import TokenAuthentication, BasicAuthentication, SessionAuthentication, OAuthAuthentication from rest_framework.compat import patterns from rest_framework.views import APIView import json import base64 - +from oauth_provider.models import Consumer, Resource +from oauth_provider.models import Token as OAuthToken +from oauth_provider import consts as oauth_consts +import oauth2 as oauth class MockView(APIView): permission_classes = (permissions.IsAuthenticated,) @@ -21,11 +25,15 @@ class MockView(APIView): def put(self, request): return HttpResponse({'a': 1, 'b': 2, 'c': 3}) + def get(self, request): + return HttpResponse({'a': 1, 'b': 2, 'c': 3}) + urlpatterns = patterns('', (r'^session/$', MockView.as_view(authentication_classes=[SessionAuthentication])), (r'^basic/$', MockView.as_view(authentication_classes=[BasicAuthentication])), (r'^token/$', MockView.as_view(authentication_classes=[TokenAuthentication])), (r'^auth-token/$', 'rest_framework.authtoken.views.obtain_auth_token'), + (r'^oauth/$', MockView.as_view(authentication_classes=[OAuthAuthentication])) ) @@ -186,3 +194,134 @@ class TokenAuthTests(TestCase): {'username': self.username, 'password': self.password}) self.assertEqual(response.status_code, 200) self.assertEqual(json.loads(response.content.decode('ascii'))['token'], self.key) + +class OAuthTests(TestCase): + """OAuth 1.0a authentication""" + urls = 'rest_framework.tests.authentication' + + def setUp(self): + self.csrf_client = Client(enforce_csrf_checks=True) + self.username = 'john' + self.email = 'lennon@thebeatles.com' + self.password = 'password' + self.user = User.objects.create_user(self.username, self.email, self.password) + + self.CONSUMER_KEY = 'consumer_key' + self.CONSUMER_SECRET = 'consumer_secret' + self.TOKEN_KEY = "token_key" + self.TOKEN_SECRET = "token_secret" + + self.consumer = Consumer.objects.create(key=self.CONSUMER_KEY, secret=self.CONSUMER_SECRET, + name='example', user=self.user, status=oauth_consts.ACCEPTED) + + + self.resource = Resource.objects.create(name="resource name", url="api/") + self.token = OAuthToken.objects.create(user=self.user, consumer=self.consumer, resource=self.resource, + token_type=OAuthToken.ACCESS, key=self.TOKEN_KEY, secret=self.TOKEN_SECRET, is_approved=True + ) + + + def _create_authorization_header(self): + params = { + 'oauth_version': "1.0", + 'oauth_nonce': oauth.generate_nonce(), + 'oauth_timestamp': int(time.time()), + 'oauth_token': self.token.key, + 'oauth_consumer_key': self.consumer.key + } + + req = oauth.Request(method="GET", url="http://example.com", parameters=params) + + signature_method = oauth.SignatureMethod_PLAINTEXT() + req.sign_request(signature_method, self.consumer, self.token) + + return req.to_header()["Authorization"] + + def _create_authorization_url_parameters(self): + params = { + 'oauth_version': "1.0", + 'oauth_nonce': oauth.generate_nonce(), + 'oauth_timestamp': int(time.time()), + 'oauth_token': self.token.key, + 'oauth_consumer_key': self.consumer.key + } + + req = oauth.Request(method="GET", url="http://example.com", parameters=params) + + signature_method = oauth.SignatureMethod_PLAINTEXT() + req.sign_request(signature_method, self.consumer, self.token) + return dict(req) + + def test_post_form_passing_oauth(self): + """Ensure POSTing form over OAuth with correct credentials passes and does not require CSRF""" + auth = self._create_authorization_header() + response = self.csrf_client.post('/oauth/', {'example': 'example'}, HTTP_AUTHORIZATION=auth) + self.assertEqual(response.status_code, 200) + + def test_post_form_repeated_nonce_failing_oauth(self): + """Ensure POSTing form over OAuth with repeated auth (same nonces and timestamp) credentials fails""" + auth = self._create_authorization_header() + response = self.csrf_client.post('/oauth/', {'example': 'example'}, HTTP_AUTHORIZATION=auth) + self.assertEqual(response.status_code, 200) + + # simulate reply attack auth header containes already used (nonce, timestamp) pair + response = self.csrf_client.post('/oauth/', {'example': 'example'}, HTTP_AUTHORIZATION=auth) + self.assertIn(response.status_code, (status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN)) + + def test_post_form_token_removed_failing_oauth(self): + """Ensure POSTing when there is no OAuth access token in db fails""" + self.token.delete() + auth = self._create_authorization_header() + response = self.csrf_client.post('/oauth/', {'example': 'example'}, HTTP_AUTHORIZATION=auth) + self.assertIn(response.status_code, (status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN)) + + def test_post_form_consumer_status_not_accepted_failing_oauth(self): + """Ensure POSTing when consumer status is anything other than ACCEPTED fails""" + for consumer_status in (oauth_consts.CANCELED, oauth_consts.PENDING, oauth_consts.REJECTED): + self.consumer.status = consumer_status + self.consumer.save() + + auth = self._create_authorization_header() + response = self.csrf_client.post('/oauth/', {'example': 'example'}, HTTP_AUTHORIZATION=auth) + self.assertIn(response.status_code, (status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN)) + + def test_post_form_with_request_token_failing_oauth(self): + """Ensure POSTing with unauthorized request token instead of access token fails""" + self.token.token_type = OAuthToken.REQUEST + self.token.save() + + auth = self._create_authorization_header() + response = self.csrf_client.post('/oauth/', {'example': 'example'}, HTTP_AUTHORIZATION=auth) + self.assertIn(response.status_code, (status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN)) + + def test_post_form_with_urlencoded_parameters(self): + """Ensure POSTing with x-www-form-urlencoded auth parameters passes""" + params = self._create_authorization_url_parameters() + response = self.csrf_client.post('/oauth/', params) + self.assertEqual(response.status_code, 200) + + def test_get_form_with_url_parameters(self): + """Ensure GETing with auth in url parameters passes""" + params = self._create_authorization_url_parameters() + response = self.csrf_client.get('/oauth/', params) + self.assertEqual(response.status_code, 200) + + def test_post_hmac_sha1_signature_passes(self): + """Ensure POSTing using HMAC_SHA1 signature method passes""" + params = { + 'oauth_version': "1.0", + 'oauth_nonce': oauth.generate_nonce(), + 'oauth_timestamp': int(time.time()), + 'oauth_token': self.token.key, + 'oauth_consumer_key': self.consumer.key + } + + req = oauth.Request(method="POST", url="http://testserver/oauth/", parameters=params) + + signature_method = oauth.SignatureMethod_HMAC_SHA1() + req.sign_request(signature_method, self.consumer, self.token) + auth = req.to_header()["Authorization"] + + response = self.csrf_client.post('/oauth/', HTTP_AUTHORIZATION=auth) + self.assertEqual(response.status_code, 200) + -- cgit v1.2.3 From ced22db7cfe83d658283257bd898e7e11d125ad4 Mon Sep 17 00:00:00 2001 From: swistakm Date: Mon, 25 Feb 2013 16:57:45 +0100 Subject: add django-oauth-plus & oauth2 to installed apps in runtests settings.py --- rest_framework/runtests/settings.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'rest_framework') diff --git a/rest_framework/runtests/settings.py b/rest_framework/runtests/settings.py index 03bfc216..683669ce 100644 --- a/rest_framework/runtests/settings.py +++ b/rest_framework/runtests/settings.py @@ -97,7 +97,9 @@ INSTALLED_APPS = ( # 'django.contrib.admindocs', 'rest_framework', 'rest_framework.authtoken', - 'rest_framework.tests' + 'rest_framework.tests', + 'oauth_provider', + ) STATIC_URL = '/static/' -- cgit v1.2.3 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') 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 ++++++++++--------------------- rest_framework/compat.py | 12 ++++++++++++ 2 files changed, 22 insertions(+), 21 deletions(-) (limited to 'rest_framework') 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']) diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 07fdddce..e4bad0cb 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -426,3 +426,15 @@ try: import defusedxml.ElementTree as etree except ImportError: etree = None + +# OAuth is optional +try: + import oauth2 as oauth +except ImportError: + oauth = None + +# OAuth is optional +try: + import oauth_provider +except ImportError: + oauth_provider = None \ No newline at end of file -- cgit v1.2.3 From d84c2cf2752467c835200a54601f77ad1cee6b38 Mon Sep 17 00:00:00 2001 From: swistakm Date: Tue, 26 Feb 2013 11:25:27 +0100 Subject: OAuth tests now are skipped unless django-oauth-plus and oauth2 are installed. --- rest_framework/tests/authentication.py | 37 +++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) (limited to 'rest_framework') diff --git a/rest_framework/tests/authentication.py b/rest_framework/tests/authentication.py index 88b6fd16..d1c978fc 100644 --- a/rest_framework/tests/authentication.py +++ b/rest_framework/tests/authentication.py @@ -9,12 +9,11 @@ from rest_framework.authtoken.models import Token from rest_framework.authentication import TokenAuthentication, BasicAuthentication, SessionAuthentication, OAuthAuthentication from rest_framework.compat import patterns from rest_framework.views import APIView +from rest_framework.compat import oauth +from rest_framework.compat import oauth_provider import json import base64 -from oauth_provider.models import Consumer, Resource -from oauth_provider.models import Token as OAuthToken -from oauth_provider import consts as oauth_consts -import oauth2 as oauth +import unittest class MockView(APIView): permission_classes = (permissions.IsAuthenticated,) @@ -200,6 +199,14 @@ class OAuthTests(TestCase): urls = 'rest_framework.tests.authentication' def setUp(self): + # these imports are here because oauth is optional and hiding them in try..except block or compat + # could obscure problems if something breaks + from oauth_provider.models import Consumer, Resource + from oauth_provider.models import Token as OAuthToken + from oauth_provider import consts + + self.consts = consts + self.csrf_client = Client(enforce_csrf_checks=True) self.username = 'john' self.email = 'lennon@thebeatles.com' @@ -212,7 +219,7 @@ class OAuthTests(TestCase): self.TOKEN_SECRET = "token_secret" self.consumer = Consumer.objects.create(key=self.CONSUMER_KEY, secret=self.CONSUMER_SECRET, - name='example', user=self.user, status=oauth_consts.ACCEPTED) + name='example', user=self.user, status=self.consts.ACCEPTED) self.resource = Resource.objects.create(name="resource name", url="api/") @@ -252,12 +259,16 @@ class OAuthTests(TestCase): req.sign_request(signature_method, self.consumer, self.token) return dict(req) + @unittest.skipUnless(oauth_provider, 'django-oauth-plus not installed') + @unittest.skipUnless(oauth, 'oauth2 not installed') def test_post_form_passing_oauth(self): """Ensure POSTing form over OAuth with correct credentials passes and does not require CSRF""" auth = self._create_authorization_header() response = self.csrf_client.post('/oauth/', {'example': 'example'}, HTTP_AUTHORIZATION=auth) self.assertEqual(response.status_code, 200) + @unittest.skipUnless(oauth_provider, 'django-oauth-plus not installed') + @unittest.skipUnless(oauth, 'oauth2 not installed') def test_post_form_repeated_nonce_failing_oauth(self): """Ensure POSTing form over OAuth with repeated auth (same nonces and timestamp) credentials fails""" auth = self._create_authorization_header() @@ -268,6 +279,8 @@ class OAuthTests(TestCase): response = self.csrf_client.post('/oauth/', {'example': 'example'}, HTTP_AUTHORIZATION=auth) self.assertIn(response.status_code, (status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN)) + @unittest.skipUnless(oauth_provider, 'django-oauth-plus not installed') + @unittest.skipUnless(oauth, 'oauth2 not installed') def test_post_form_token_removed_failing_oauth(self): """Ensure POSTing when there is no OAuth access token in db fails""" self.token.delete() @@ -275,9 +288,11 @@ class OAuthTests(TestCase): response = self.csrf_client.post('/oauth/', {'example': 'example'}, HTTP_AUTHORIZATION=auth) self.assertIn(response.status_code, (status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN)) + @unittest.skipUnless(oauth_provider, 'django-oauth-plus not installed') + @unittest.skipUnless(oauth, 'oauth2 not installed') def test_post_form_consumer_status_not_accepted_failing_oauth(self): """Ensure POSTing when consumer status is anything other than ACCEPTED fails""" - for consumer_status in (oauth_consts.CANCELED, oauth_consts.PENDING, oauth_consts.REJECTED): + for consumer_status in (self.consts.CANCELED, self.consts.PENDING, self.consts.REJECTED): self.consumer.status = consumer_status self.consumer.save() @@ -285,27 +300,35 @@ class OAuthTests(TestCase): response = self.csrf_client.post('/oauth/', {'example': 'example'}, HTTP_AUTHORIZATION=auth) self.assertIn(response.status_code, (status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN)) + @unittest.skipUnless(oauth_provider, 'django-oauth-plus not installed') + @unittest.skipUnless(oauth, 'oauth2 not installed') def test_post_form_with_request_token_failing_oauth(self): """Ensure POSTing with unauthorized request token instead of access token fails""" - self.token.token_type = OAuthToken.REQUEST + self.token.token_type = self.token.REQUEST self.token.save() auth = self._create_authorization_header() response = self.csrf_client.post('/oauth/', {'example': 'example'}, HTTP_AUTHORIZATION=auth) self.assertIn(response.status_code, (status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN)) + @unittest.skipUnless(oauth_provider, 'django-oauth-plus not installed') + @unittest.skipUnless(oauth, 'oauth2 not installed') def test_post_form_with_urlencoded_parameters(self): """Ensure POSTing with x-www-form-urlencoded auth parameters passes""" params = self._create_authorization_url_parameters() response = self.csrf_client.post('/oauth/', params) self.assertEqual(response.status_code, 200) + @unittest.skipUnless(oauth_provider, 'django-oauth-plus not installed') + @unittest.skipUnless(oauth, 'oauth2 not installed') def test_get_form_with_url_parameters(self): """Ensure GETing with auth in url parameters passes""" params = self._create_authorization_url_parameters() response = self.csrf_client.get('/oauth/', params) self.assertEqual(response.status_code, 200) + @unittest.skipUnless(oauth_provider, 'django-oauth-plus not installed') + @unittest.skipUnless(oauth, 'oauth2 not installed') def test_post_hmac_sha1_signature_passes(self): """Ensure POSTing using HMAC_SHA1 signature method passes""" params = { -- cgit v1.2.3 From a4304458f5a07acc400b7630a59a4a0996e166d9 Mon Sep 17 00:00:00 2001 From: swistakm Date: Tue, 26 Feb 2013 11:27:06 +0100 Subject: runtest.settings fixed if django-oauth-plus or oauth2 are not installed oauth_provider can be added to INSTALLED_APPS only if these packages are installed --- rest_framework/runtests/settings.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'rest_framework') diff --git a/rest_framework/runtests/settings.py b/rest_framework/runtests/settings.py index 683669ce..eb3f1115 100644 --- a/rest_framework/runtests/settings.py +++ b/rest_framework/runtests/settings.py @@ -98,10 +98,18 @@ INSTALLED_APPS = ( 'rest_framework', 'rest_framework.authtoken', 'rest_framework.tests', - 'oauth_provider', - ) +# OAuth is optional and won't work if there is no oauth_provider & oauth2 +try: + import oauth_provider + import oauth2 +except ImportError: + pass +else: + INSTALLED_APPS += ('oauth_provider',) + + STATIC_URL = '/static/' PASSWORD_HASHERS = ( -- cgit v1.2.3 From 55ea5b9460842f6b8aefa67575a6d25b2d479593 Mon Sep 17 00:00:00 2001 From: swistakm Date: Wed, 27 Feb 2013 10:58:13 +0100 Subject: import compat version of unittest --- rest_framework/tests/authentication.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'rest_framework') diff --git a/rest_framework/tests/authentication.py b/rest_framework/tests/authentication.py index d1c978fc..8ef9d3ff 100644 --- a/rest_framework/tests/authentication.py +++ b/rest_framework/tests/authentication.py @@ -2,6 +2,7 @@ from __future__ import unicode_literals from django.contrib.auth.models import User from django.http import HttpResponse from django.test import Client, TestCase +from django.utils import unittest import time from rest_framework import HTTP_HEADER_ENCODING, status from rest_framework import permissions @@ -13,7 +14,7 @@ from rest_framework.compat import oauth from rest_framework.compat import oauth_provider import json import base64 -import unittest + class MockView(APIView): permission_classes = (permissions.IsAuthenticated,) -- cgit v1.2.3