Skip to content

Commit

Permalink
Merge 7336805 into db5b852
Browse files Browse the repository at this point in the history
  • Loading branch information
georgedorn committed Aug 7, 2016
2 parents db5b852 + 7336805 commit dc4fe69
Show file tree
Hide file tree
Showing 5 changed files with 50 additions and 93 deletions.
1 change: 1 addition & 0 deletions .travis.yml
Expand Up @@ -13,6 +13,7 @@ env:
- DJANGO_VERSION=dj17
- DJANGO_VERSION=dj18
- DJANGO_VERSION=dj19
- DJANGO_VERSION=dj110
- DJANGO_VERSION=djdev

matrix:
Expand Down
10 changes: 8 additions & 2 deletions tastypie/authentication.py
Expand Up @@ -4,6 +4,7 @@
import hmac
import time
import uuid
import warnings

from django.conf import settings
from django.contrib.auth import authenticate
Expand All @@ -12,7 +13,7 @@
from django.utils.six.moves.urllib.parse import urlparse
from django.utils.translation import ugettext as _

from tastypie.compat import get_user_model, get_username_field
from tastypie.compat import get_user_model, get_username_field, unsalt_token
from tastypie.http import HttpUnauthorized

try:
Expand Down Expand Up @@ -169,11 +170,14 @@ def is_authenticated(self, request, **kwargs):
password=password
)
else:
if not self.require_active and 'django.contrib.auth.backends.ModelBackend' in settings.AUTHENTICATION_BACKENDS:
warnings.warn("Authenticating inactive users via ModelUserBackend not supported for Django >= 1.10")
user = authenticate(username=username, password=password)

if user is None:
return self._unauthorized()

# Kept for backwards-compatibility with Django < 1.10
if not self.check_active(user):
return False

Expand Down Expand Up @@ -317,8 +321,10 @@ def is_authenticated(self, request, **kwargs):
return False

request_csrf_token = request.META.get('HTTP_X_CSRFTOKEN', '')
request_csrf_token = _sanitize_token(request_csrf_token)

if not constant_time_compare(request_csrf_token, csrf_token):
if not constant_time_compare(unsalt_token(request_csrf_token),
unsalt_token(csrf_token)):
return False

return request.user.is_authenticated()
Expand Down
12 changes: 11 additions & 1 deletion tastypie/compat.py
Expand Up @@ -4,7 +4,6 @@
from django.conf import settings
from django.contrib.auth import get_user_model # flake8: noqa


__all__ = ['get_user_model', 'get_username_field', 'AUTH_USER_MODEL']


Expand All @@ -20,3 +19,14 @@ def get_module_name(meta):


atomic_decorator = django.db.transaction.atomic

# Compatability for salted vs unsalted CSRF tokens;
# Django 1.10's _sanitize_token also hashes it, so it can't be compared directly.
# Solution is to call _sanitize_token on both tokens, then unsalt or noop both
try:
from django.middleware.csrf import _unsalt_cipher_token
def unsalt_token(token):
return _unsalt_cipher_token(token)
except ImportError:
def unsalt_token(token):
return token
84 changes: 11 additions & 73 deletions tests/core/tests/authentication.py
Expand Up @@ -33,8 +33,8 @@ def test_is_authenticated(self):
auth = Authentication()
request = HttpRequest()
# Doesn't matter. Always true.
self.assertTrue(auth.is_authenticated(None))
self.assertTrue(auth.is_authenticated(request))
self.assertEqual(auth.is_authenticated(None), True)
self.assertEqual(auth.is_authenticated(request), True)

def test_get_identifier(self):
auth = Authentication()
Expand All @@ -46,15 +46,6 @@ def test_get_identifier(self):
request.META['REMOTE_HOST'] = 'nebula.local'
self.assertEqual(auth.get_identifier(request), '127.0.0.1_nebula.local')

def test_check_active_false(self):
auth = Authentication(require_active=False)
user = User.objects.get(username='johndoe')
self.assertTrue(auth.check_active(user))

auth = Authentication(require_active=False)
user = User.objects.get(username='bobdoe')
self.assertTrue(auth.check_active(user))

def test_check_active_true(self):
auth = Authentication(require_active=True)
user = User.objects.get(username='johndoe')
Expand Down Expand Up @@ -124,17 +115,9 @@ def test_check_active_true(self):
bob_doe.set_password('pass')
bob_doe.save()
request.META['HTTP_AUTHORIZATION'] = 'Basic %s' % base64.b64encode('bobdoe:pass'.encode('utf-8')).decode('utf-8')
self.assertFalse(auth.is_authenticated(request))

def test_check_active_false(self):
auth = BasicAuthentication(require_active=False)
request = HttpRequest()

bob_doe = User.objects.get(username='bobdoe')
bob_doe.set_password('pass')
bob_doe.save()
request.META['HTTP_AUTHORIZATION'] = 'Basic %s' % base64.b64encode('bobdoe:pass'.encode('utf-8')).decode('utf-8')
self.assertTrue(auth.is_authenticated(request))
auth_res = auth.is_authenticated(request)
# is_authenticated() returns HttpUnauthorized for inactive users in Django >= 1.10, False for < 1.10
self.assertTrue(auth_res is False or isinstance(auth_res, HttpUnauthorized))


class ApiKeyAuthenticationTestCase(TestCase):
Expand Down Expand Up @@ -222,16 +205,7 @@ def test_check_active_true(self):
bob_doe = User.objects.get(username='bobdoe')
create_api_key(User, instance=bob_doe, created=True)
request.META['HTTP_AUTHORIZATION'] = 'ApiKey bobdoe:%s' % bob_doe.api_key.key
self.assertFalse(auth.is_authenticated(request))

def test_check_active_false(self):
auth = BasicAuthentication(require_active=False)
request = HttpRequest()

bob_doe = User.objects.get(username='bobdoe')
create_api_key(User, instance=bob_doe, created=True)
request.META['HTTP_AUTHORIZATION'] = 'ApiKey bobdoe:%s' % bob_doe.api_key.key
self.assertTrue(auth.is_authenticated(request))
self.assertEqual(auth.is_authenticated(request), False)


class SessionAuthenticationTestCase(TestCase):
Expand Down Expand Up @@ -264,13 +238,13 @@ def test_is_authenticated(self):

# Logged in.
request.user = User.objects.get(username='johndoe')
self.assertTrue(auth.is_authenticated(request))
self.assertEqual(auth.is_authenticated(request), True)

# Logged in (with GET & no token).
request.method = 'GET'
request.META = {}
request.user = User.objects.get(username='johndoe')
self.assertTrue(auth.is_authenticated(request))
self.assertEqual(auth.is_authenticated(request), True)

# Secure & wrong referrer.
class SecureRequest(HttpRequest):
Expand All @@ -292,7 +266,7 @@ def _get_scheme(self):

# Secure & correct referrer.
request.META['HTTP_REFERER'] = 'https://example.com/'
self.assertTrue(auth.is_authenticated(request))
self.assertEqual(auth.is_authenticated(request), True)

def test_get_identifier(self):
auth = SessionAuthentication()
Expand Down Expand Up @@ -379,24 +353,6 @@ def test_check_active_true(self):
auth_request = auth.is_authenticated(request)
self.assertFalse(auth_request)

def test_check_active_false(self):
auth = DigestAuthentication(require_active=False)
request = HttpRequest()

bob_doe = User.objects.get(username='bobdoe')
create_api_key(User, instance=bob_doe, created=True)
auth_request = auth.is_authenticated(request)
request.META['HTTP_AUTHORIZATION'] = python_digest.build_authorization_request(
username=bob_doe.username,
method=request.method,
uri='/',
nonce_count=1,
digest_challenge=python_digest.parse_digest_challenge(auth_request['WWW-Authenticate']),
password=bob_doe.api_key.key
)
auth_request = auth.is_authenticated(request)
self.assertTrue(auth_request, True)


@skipIf(not oauth2 or not oauth_provider, "oauth provider not installed")
class OAuthAuthenticationTestCase(TestCase):
Expand Down Expand Up @@ -473,24 +429,6 @@ def test_check_active_true(self):
resp = auth.is_authenticated(self.request)
self.assertFalse(resp)

def test_check_active_false(self):
auth = OAuthAuthentication(require_active=False)

# No username/api_key details should fail.
self.request.REQUEST = self.request.GET = {
'oauth_consumer_key': '123',
'oauth_nonce': 'abc',
'oauth_signature': '&',
'oauth_signature_method': 'PLAINTEXT',
'oauth_timestamp': str(int(time.time())),
'oauth_token': 'bar',
}
self.request.META['Authorization'] = 'OAuth ' + ','.join(
[key + '=' + value for key, value in self.request.REQUEST.items()])
resp = auth.is_authenticated(self.request)
self.assertTrue(resp)
self.assertEqual(self.request.user.pk, self.user_inactive.pk)


class MultiAuthenticationTestCase(TestCase):
fixtures = ['note_testdata.json']
Expand Down Expand Up @@ -528,14 +466,14 @@ def test_apikey_and_authentication_enforce_user(self):
request3.POST['api_key'] = 'invalid key'

# session auth should pass if since john_doe is logged in
self.assertTrue(session_auth.is_authenticated(request1))
self.assertEqual(session_auth.is_authenticated(request1), True)
# api key auth should fail because of invalid api key
self.assertEqual(isinstance(api_key_auth.is_authenticated(request2), HttpUnauthorized), True)

# multi auth shouldn't change users if api key auth fails
# multi auth passes since session auth is valid
self.assertEqual(request3.user.username, 'johndoe')
self.assertTrue(auth.is_authenticated(request3))
self.assertEqual(auth.is_authenticated(request3), True)
self.assertEqual(request3.user.username, 'johndoe')

def test_apikey_and_authentication(self):
Expand Down
36 changes: 19 additions & 17 deletions tox.ini
@@ -1,6 +1,7 @@
[tox]
envlist =
py{34,27}-djdev,
py{34,27}-dj110,
py{34,27}-dj19,
py{34,27}-dj18,
py{34,27}-dj17,
Expand All @@ -16,17 +17,17 @@ test-executable =
setenv =
PYTHONPATH = {toxinidir}:{toxinidir}/tests
commands =
dj{17,18,19,dev}: {[testenv]test-executable} test -p '*' core.tests --settings=settings_core
dj{17,18,19,dev}: {[testenv]test-executable} test basic.tests --settings=settings_basic
dj{17,18,19,dev}: {[testenv]test-executable} test related_resource.tests --settings=settings_related
dj{17,18,19,dev}: {[testenv]test-executable} test alphanumeric.tests --settings=settings_alphanumeric
dj{17,18,19,dev}: {[testenv]test-executable} test authorization.tests --settings=settings_authorization
dj{17,18,19,dev}: {[testenv]test-executable} test content_gfk.tests --settings=settings_content_gfk
dj{17,18,19,dev}: {[testenv]test-executable} test customuser.tests --settings=settings_customuser
dj{17,18,19,dev}: {[testenv]test-executable} test namespaced.tests --settings=settings_namespaced
dj{17,18,19,dev}: {[testenv]test-executable} test slashless.tests --settings=settings_slashless
dj{17,18,19,dev}: {[testenv]test-executable} test validation.tests --settings=settings_validation
dj{17,18,19,dev}: {[testenv]test-executable} test gis.tests --settings=settings_gis_spatialite
dj{17,18,19,110,dev}: {[testenv]test-executable} test -p '*' core.tests --settings=settings_core
dj{17,18,19,110,dev}: {[testenv]test-executable} test basic.tests --settings=settings_basic
dj{17,18,19,110,dev}: {[testenv]test-executable} test related_resource.tests --settings=settings_related
dj{17,18,19,110,dev}: {[testenv]test-executable} test alphanumeric.tests --settings=settings_alphanumeric
dj{17,18,19,110,dev}: {[testenv]test-executable} test authorization.tests --settings=settings_authorization
dj{17,18,19,110,dev}: {[testenv]test-executable} test content_gfk.tests --settings=settings_content_gfk
dj{17,18,19,110,dev}: {[testenv]test-executable} test customuser.tests --settings=settings_customuser
dj{17,18,19,110,dev}: {[testenv]test-executable} test namespaced.tests --settings=settings_namespaced
dj{17,18,19,110,dev}: {[testenv]test-executable} test slashless.tests --settings=settings_slashless
dj{17,18,19,110,dev}: {[testenv]test-executable} test validation.tests --settings=settings_validation
dj{17,18,19,110,dev}: {[testenv]test-executable} test gis.tests --settings=settings_gis_spatialite

docs: sphinx-build -W -b html -d {envtmpdir}/doctrees . {envtmpdir}/html
docs: sphinx-build -W -b doctest -d {envtmpdir}/doctrees . {envtmpdir}/html
Expand All @@ -41,14 +42,15 @@ deps =
dj17: Django>=1.7,<1.8
dj18: Django>=1.8,<1.9
dj19: Django>=1.9,<1.10
dj110: Django>=1.10,<1.11
djdev: https://github.com/django/django/archive/master.tar.gz

py27-dj{17,18,19}: django-oauth-plus==2.2.8
py27-dj{17,18,19,dev}: python-digest
py27-dj{17,18,19,dev}: oauth2
py27-dj{17,18,19,dev}: pysqlite==2.7.0
py34-dj{17,18,19,dev}: python3-digest>=1.8b4
dj{17,18,19,dev}: -r{toxinidir}/tests/requirements.txt
py27-dj{17,18,19,110}: django-oauth-plus==2.2.8
py27-dj{17,18,19,110,dev}: python-digest
py27-dj{17,18,19,110,dev}: oauth2
py27-dj{17,18,19,110,dev}: pysqlite==2.7.0
py34-dj{17,18,19,110,dev}: python3-digest>=1.8b4
dj{17,18,19,110,dev}: -r{toxinidir}/tests/requirements.txt

docs: Sphinx
docs: Django>=1.9,<1.10
Expand Down

0 comments on commit dc4fe69

Please sign in to comment.