2015-06-03 20:55:34 +03:00
|
|
|
|
# coding: utf-8
|
|
|
|
|
|
2013-02-01 18:03:28 +04:00
|
|
|
|
from __future__ import unicode_literals
|
2015-06-25 23:55:51 +03:00
|
|
|
|
|
|
|
|
|
import base64
|
|
|
|
|
|
2017-01-23 18:08:59 +03:00
|
|
|
|
import pytest
|
2018-08-07 10:18:56 +03:00
|
|
|
|
from django.conf import settings
|
2015-06-25 23:55:51 +03:00
|
|
|
|
from django.conf.urls import include, url
|
2012-09-20 16:06:27 +04:00
|
|
|
|
from django.contrib.auth.models import User
|
2016-01-05 19:20:22 +03:00
|
|
|
|
from django.db import models
|
2012-12-20 03:12:27 +04:00
|
|
|
|
from django.http import HttpResponse
|
2016-06-01 17:31:00 +03:00
|
|
|
|
from django.test import TestCase, override_settings
|
2014-09-06 02:30:01 +04:00
|
|
|
|
from django.utils import six
|
2015-06-25 23:55:51 +03:00
|
|
|
|
|
|
|
|
|
from rest_framework import (
|
|
|
|
|
HTTP_HEADER_ENCODING, exceptions, permissions, renderers, status
|
|
|
|
|
)
|
2013-02-28 21:58:58 +04:00
|
|
|
|
from rest_framework.authentication import (
|
2017-10-05 21:41:38 +03:00
|
|
|
|
BaseAuthentication, BasicAuthentication, RemoteUserAuthentication,
|
|
|
|
|
SessionAuthentication, TokenAuthentication
|
|
|
|
|
)
|
2012-09-20 16:06:27 +04:00
|
|
|
|
from rest_framework.authtoken.models import Token
|
2016-06-01 17:31:00 +03:00
|
|
|
|
from rest_framework.authtoken.views import obtain_auth_token
|
2015-06-25 23:55:51 +03:00
|
|
|
|
from rest_framework.response import Response
|
|
|
|
|
from rest_framework.test import APIClient, APIRequestFactory
|
2012-12-20 03:12:27 +04:00
|
|
|
|
from rest_framework.views import APIView
|
2012-09-20 16:06:27 +04:00
|
|
|
|
|
2013-06-28 20:17:39 +04:00
|
|
|
|
factory = APIRequestFactory()
|
2013-02-28 21:58:58 +04:00
|
|
|
|
|
|
|
|
|
|
2016-01-05 18:58:16 +03:00
|
|
|
|
class CustomToken(models.Model):
|
|
|
|
|
key = models.CharField(max_length=40, primary_key=True)
|
2016-06-02 16:39:10 +03:00
|
|
|
|
user = models.OneToOneField(User, on_delete=models.CASCADE)
|
2016-01-05 18:58:16 +03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class CustomTokenAuthentication(TokenAuthentication):
|
|
|
|
|
model = CustomToken
|
|
|
|
|
|
|
|
|
|
|
2016-05-04 12:53:34 +03:00
|
|
|
|
class CustomKeywordTokenAuthentication(TokenAuthentication):
|
|
|
|
|
keyword = 'Bearer'
|
|
|
|
|
|
|
|
|
|
|
2012-09-20 16:06:27 +04:00
|
|
|
|
class MockView(APIView):
|
|
|
|
|
permission_classes = (permissions.IsAuthenticated,)
|
|
|
|
|
|
2013-02-28 21:58:58 +04:00
|
|
|
|
def get(self, request):
|
|
|
|
|
return HttpResponse({'a': 1, 'b': 2, 'c': 3})
|
|
|
|
|
|
2012-09-20 16:06:27 +04:00
|
|
|
|
def post(self, request):
|
|
|
|
|
return HttpResponse({'a': 1, 'b': 2, 'c': 3})
|
|
|
|
|
|
|
|
|
|
def put(self, request):
|
|
|
|
|
return HttpResponse({'a': 1, 'b': 2, 'c': 3})
|
|
|
|
|
|
2013-02-28 21:58:58 +04:00
|
|
|
|
|
2015-06-11 01:45:23 +03:00
|
|
|
|
urlpatterns = [
|
2016-06-01 12:40:54 +03:00
|
|
|
|
url(
|
|
|
|
|
r'^session/$',
|
|
|
|
|
MockView.as_view(authentication_classes=[SessionAuthentication])
|
|
|
|
|
),
|
|
|
|
|
url(
|
|
|
|
|
r'^basic/$',
|
|
|
|
|
MockView.as_view(authentication_classes=[BasicAuthentication])
|
|
|
|
|
),
|
2017-08-11 12:35:00 +03:00
|
|
|
|
url(
|
|
|
|
|
r'^remote-user/$',
|
|
|
|
|
MockView.as_view(authentication_classes=[RemoteUserAuthentication])
|
|
|
|
|
),
|
2016-06-01 12:40:54 +03:00
|
|
|
|
url(
|
|
|
|
|
r'^token/$',
|
|
|
|
|
MockView.as_view(authentication_classes=[TokenAuthentication])
|
|
|
|
|
),
|
|
|
|
|
url(
|
|
|
|
|
r'^customtoken/$',
|
|
|
|
|
MockView.as_view(authentication_classes=[CustomTokenAuthentication])
|
|
|
|
|
),
|
|
|
|
|
url(
|
|
|
|
|
r'^customkeywordtoken/$',
|
|
|
|
|
MockView.as_view(
|
|
|
|
|
authentication_classes=[CustomKeywordTokenAuthentication]
|
|
|
|
|
)
|
|
|
|
|
),
|
2016-06-01 17:31:00 +03:00
|
|
|
|
url(r'^auth-token/$', obtain_auth_token),
|
2015-06-11 02:01:47 +03:00
|
|
|
|
url(r'^auth/', include('rest_framework.urls', namespace='rest_framework')),
|
2015-06-11 01:45:23 +03:00
|
|
|
|
]
|
2012-09-20 16:06:27 +04:00
|
|
|
|
|
2014-08-19 16:28:07 +04:00
|
|
|
|
|
2016-06-01 17:31:00 +03:00
|
|
|
|
@override_settings(ROOT_URLCONF='tests.test_authentication')
|
2012-09-20 16:06:27 +04:00
|
|
|
|
class BasicAuthTests(TestCase):
|
|
|
|
|
"""Basic authentication"""
|
|
|
|
|
def setUp(self):
|
2013-06-28 20:50:30 +04:00
|
|
|
|
self.csrf_client = APIClient(enforce_csrf_checks=True)
|
2012-09-20 16:06:27 +04:00
|
|
|
|
self.username = 'john'
|
|
|
|
|
self.email = 'lennon@thebeatles.com'
|
|
|
|
|
self.password = 'password'
|
2016-06-01 12:40:54 +03:00
|
|
|
|
self.user = User.objects.create_user(
|
|
|
|
|
self.username, self.email, self.password
|
|
|
|
|
)
|
2012-09-20 16:06:27 +04:00
|
|
|
|
|
|
|
|
|
def test_post_form_passing_basic_auth(self):
|
|
|
|
|
"""Ensure POSTing json over basic auth with correct credentials passes and does not require CSRF"""
|
2013-02-01 18:03:28 +04:00
|
|
|
|
credentials = ('%s:%s' % (self.username, self.password))
|
2016-06-01 12:40:54 +03:00
|
|
|
|
base64_credentials = base64.b64encode(
|
|
|
|
|
credentials.encode(HTTP_HEADER_ENCODING)
|
|
|
|
|
).decode(HTTP_HEADER_ENCODING)
|
2013-02-01 18:03:28 +04:00
|
|
|
|
auth = 'Basic %s' % base64_credentials
|
2016-06-01 12:40:54 +03:00
|
|
|
|
response = self.csrf_client.post(
|
|
|
|
|
'/basic/',
|
|
|
|
|
{'example': 'example'},
|
|
|
|
|
HTTP_AUTHORIZATION=auth
|
|
|
|
|
)
|
2016-12-05 21:33:13 +03:00
|
|
|
|
assert response.status_code == status.HTTP_200_OK
|
2012-09-20 16:06:27 +04:00
|
|
|
|
|
|
|
|
|
def test_post_json_passing_basic_auth(self):
|
|
|
|
|
"""Ensure POSTing form over basic auth with correct credentials passes and does not require CSRF"""
|
2013-02-01 18:03:28 +04:00
|
|
|
|
credentials = ('%s:%s' % (self.username, self.password))
|
2016-06-01 12:40:54 +03:00
|
|
|
|
base64_credentials = base64.b64encode(
|
|
|
|
|
credentials.encode(HTTP_HEADER_ENCODING)
|
|
|
|
|
).decode(HTTP_HEADER_ENCODING)
|
2013-02-01 18:03:28 +04:00
|
|
|
|
auth = 'Basic %s' % base64_credentials
|
2016-06-01 12:40:54 +03:00
|
|
|
|
response = self.csrf_client.post(
|
|
|
|
|
'/basic/',
|
|
|
|
|
{'example': 'example'},
|
|
|
|
|
format='json',
|
|
|
|
|
HTTP_AUTHORIZATION=auth
|
|
|
|
|
)
|
2016-12-05 21:33:13 +03:00
|
|
|
|
assert response.status_code == status.HTTP_200_OK
|
2012-09-20 16:06:27 +04:00
|
|
|
|
|
2016-05-03 11:24:55 +03:00
|
|
|
|
def test_regression_handle_bad_base64_basic_auth_header(self):
|
|
|
|
|
"""Ensure POSTing JSON over basic auth with incorrectly padded Base64 string is handled correctly"""
|
|
|
|
|
# regression test for issue in 'rest_framework.authentication.BasicAuthentication.authenticate'
|
2017-04-07 17:28:35 +03:00
|
|
|
|
# https://github.com/encode/django-rest-framework/issues/4089
|
2016-05-03 11:24:55 +03:00
|
|
|
|
auth = 'Basic =a='
|
2016-06-01 12:40:54 +03:00
|
|
|
|
response = self.csrf_client.post(
|
|
|
|
|
'/basic/',
|
|
|
|
|
{'example': 'example'},
|
|
|
|
|
format='json',
|
|
|
|
|
HTTP_AUTHORIZATION=auth
|
|
|
|
|
)
|
2016-12-05 21:33:13 +03:00
|
|
|
|
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
2016-05-03 11:24:55 +03:00
|
|
|
|
|
2012-09-20 16:06:27 +04:00
|
|
|
|
def test_post_form_failing_basic_auth(self):
|
|
|
|
|
"""Ensure POSTing form over basic auth without correct credentials fails"""
|
2013-01-22 01:29:49 +04:00
|
|
|
|
response = self.csrf_client.post('/basic/', {'example': 'example'})
|
2016-12-05 21:33:13 +03:00
|
|
|
|
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
2012-09-20 16:06:27 +04:00
|
|
|
|
|
|
|
|
|
def test_post_json_failing_basic_auth(self):
|
|
|
|
|
"""Ensure POSTing json over basic auth without correct credentials fails"""
|
2016-06-01 12:40:54 +03:00
|
|
|
|
response = self.csrf_client.post(
|
|
|
|
|
'/basic/',
|
|
|
|
|
{'example': 'example'},
|
|
|
|
|
format='json'
|
|
|
|
|
)
|
2016-12-05 21:33:13 +03:00
|
|
|
|
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
|
|
|
|
assert response['WWW-Authenticate'] == 'Basic realm="api"'
|
2012-09-20 16:06:27 +04:00
|
|
|
|
|
2017-01-23 18:08:59 +03:00
|
|
|
|
def test_fail_post_if_credentials_are_missing(self):
|
|
|
|
|
response = self.csrf_client.post(
|
|
|
|
|
'/basic/', {'example': 'example'}, HTTP_AUTHORIZATION='Basic ')
|
|
|
|
|
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
|
|
|
|
|
|
|
|
|
def test_fail_post_if_credentials_contain_spaces(self):
|
|
|
|
|
response = self.csrf_client.post(
|
|
|
|
|
'/basic/', {'example': 'example'},
|
|
|
|
|
HTTP_AUTHORIZATION='Basic foo bar'
|
|
|
|
|
)
|
|
|
|
|
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
|
|
|
|
|
2012-09-20 16:06:27 +04:00
|
|
|
|
|
2016-06-01 17:31:00 +03:00
|
|
|
|
@override_settings(ROOT_URLCONF='tests.test_authentication')
|
2012-09-20 16:06:27 +04:00
|
|
|
|
class SessionAuthTests(TestCase):
|
|
|
|
|
"""User session authentication"""
|
|
|
|
|
def setUp(self):
|
2013-06-28 20:50:30 +04:00
|
|
|
|
self.csrf_client = APIClient(enforce_csrf_checks=True)
|
|
|
|
|
self.non_csrf_client = APIClient(enforce_csrf_checks=False)
|
2012-09-20 16:06:27 +04:00
|
|
|
|
self.username = 'john'
|
|
|
|
|
self.email = 'lennon@thebeatles.com'
|
|
|
|
|
self.password = 'password'
|
2016-06-01 12:40:54 +03:00
|
|
|
|
self.user = User.objects.create_user(
|
|
|
|
|
self.username, self.email, self.password
|
|
|
|
|
)
|
2012-09-20 16:06:27 +04:00
|
|
|
|
|
|
|
|
|
def tearDown(self):
|
|
|
|
|
self.csrf_client.logout()
|
|
|
|
|
|
2014-09-01 12:07:05 +04:00
|
|
|
|
def test_login_view_renders_on_get(self):
|
|
|
|
|
"""
|
|
|
|
|
Ensure the login template renders for a basic GET.
|
|
|
|
|
|
2017-04-07 17:28:35 +03:00
|
|
|
|
cf. [#1810](https://github.com/encode/django-rest-framework/pull/1810)
|
2014-09-01 12:07:05 +04:00
|
|
|
|
"""
|
|
|
|
|
response = self.csrf_client.get('/auth/login/')
|
2016-12-05 21:47:58 +03:00
|
|
|
|
content = response.content.decode('utf8')
|
|
|
|
|
assert '<label for="id_username">Username:</label>' in content
|
2014-09-01 12:07:05 +04:00
|
|
|
|
|
2012-09-20 16:06:27 +04:00
|
|
|
|
def test_post_form_session_auth_failing_csrf(self):
|
|
|
|
|
"""
|
|
|
|
|
Ensure POSTing form over session authentication without CSRF token fails.
|
|
|
|
|
"""
|
|
|
|
|
self.csrf_client.login(username=self.username, password=self.password)
|
2013-01-22 01:29:49 +04:00
|
|
|
|
response = self.csrf_client.post('/session/', {'example': 'example'})
|
2016-12-05 21:33:13 +03:00
|
|
|
|
assert response.status_code == status.HTTP_403_FORBIDDEN
|
2012-09-20 16:06:27 +04:00
|
|
|
|
|
2018-08-07 10:18:56 +03:00
|
|
|
|
def test_post_form_session_auth_passing_csrf(self):
|
|
|
|
|
"""
|
|
|
|
|
Ensure POSTing form over session authentication with CSRF token succeeds.
|
|
|
|
|
Regression test for #6088
|
|
|
|
|
"""
|
|
|
|
|
from django.middleware.csrf import _get_new_csrf_token
|
|
|
|
|
|
|
|
|
|
self.csrf_client.login(username=self.username, password=self.password)
|
|
|
|
|
|
|
|
|
|
# Set the csrf_token cookie so that CsrfViewMiddleware._get_token() works
|
|
|
|
|
token = _get_new_csrf_token()
|
|
|
|
|
self.csrf_client.cookies[settings.CSRF_COOKIE_NAME] = token
|
|
|
|
|
|
|
|
|
|
# Post the token matching the cookie value
|
|
|
|
|
response = self.csrf_client.post('/session/', {
|
|
|
|
|
'example': 'example',
|
|
|
|
|
'csrfmiddlewaretoken': token,
|
|
|
|
|
})
|
|
|
|
|
assert response.status_code == status.HTTP_200_OK
|
|
|
|
|
|
2012-09-20 16:06:27 +04:00
|
|
|
|
def test_post_form_session_auth_passing(self):
|
|
|
|
|
"""
|
2016-06-01 12:40:54 +03:00
|
|
|
|
Ensure POSTing form over session authentication with logged in
|
|
|
|
|
user and CSRF token passes.
|
2012-09-20 16:06:27 +04:00
|
|
|
|
"""
|
2016-06-01 12:40:54 +03:00
|
|
|
|
self.non_csrf_client.login(
|
|
|
|
|
username=self.username, password=self.password
|
|
|
|
|
)
|
|
|
|
|
response = self.non_csrf_client.post(
|
|
|
|
|
'/session/', {'example': 'example'}
|
|
|
|
|
)
|
2016-12-05 21:33:13 +03:00
|
|
|
|
assert response.status_code == status.HTTP_200_OK
|
2012-09-20 16:06:27 +04:00
|
|
|
|
|
|
|
|
|
def test_put_form_session_auth_passing(self):
|
|
|
|
|
"""
|
2016-06-01 12:40:54 +03:00
|
|
|
|
Ensure PUTting form over session authentication with
|
|
|
|
|
logged in user and CSRF token passes.
|
2012-09-20 16:06:27 +04:00
|
|
|
|
"""
|
2016-06-01 12:40:54 +03:00
|
|
|
|
self.non_csrf_client.login(
|
|
|
|
|
username=self.username, password=self.password
|
|
|
|
|
)
|
|
|
|
|
response = self.non_csrf_client.put(
|
|
|
|
|
'/session/', {'example': 'example'}
|
|
|
|
|
)
|
2016-12-05 21:33:13 +03:00
|
|
|
|
assert response.status_code == status.HTTP_200_OK
|
2012-09-20 16:06:27 +04:00
|
|
|
|
|
|
|
|
|
def test_post_form_session_auth_failing(self):
|
|
|
|
|
"""
|
|
|
|
|
Ensure POSTing form over session authentication without logged in user fails.
|
|
|
|
|
"""
|
2013-01-22 01:29:49 +04:00
|
|
|
|
response = self.csrf_client.post('/session/', {'example': 'example'})
|
2016-12-05 21:33:13 +03:00
|
|
|
|
assert response.status_code == status.HTTP_403_FORBIDDEN
|
2012-09-20 16:06:27 +04:00
|
|
|
|
|
|
|
|
|
|
2016-01-05 18:58:16 +03:00
|
|
|
|
class BaseTokenAuthTests(object):
|
2012-09-20 16:06:27 +04:00
|
|
|
|
"""Token authentication"""
|
2016-01-05 18:58:16 +03:00
|
|
|
|
model = None
|
|
|
|
|
path = None
|
2016-05-04 12:53:34 +03:00
|
|
|
|
header_prefix = 'Token '
|
2012-09-20 16:06:27 +04:00
|
|
|
|
|
|
|
|
|
def setUp(self):
|
2013-06-28 20:50:30 +04:00
|
|
|
|
self.csrf_client = APIClient(enforce_csrf_checks=True)
|
2012-09-20 16:06:27 +04:00
|
|
|
|
self.username = 'john'
|
|
|
|
|
self.email = 'lennon@thebeatles.com'
|
|
|
|
|
self.password = 'password'
|
2016-06-01 12:40:54 +03:00
|
|
|
|
self.user = User.objects.create_user(
|
|
|
|
|
self.username, self.email, self.password
|
|
|
|
|
)
|
2012-09-20 16:06:27 +04:00
|
|
|
|
|
|
|
|
|
self.key = 'abcd1234'
|
2016-01-05 18:58:16 +03:00
|
|
|
|
self.token = self.model.objects.create(key=self.key, user=self.user)
|
2012-09-20 16:06:27 +04:00
|
|
|
|
|
|
|
|
|
def test_post_form_passing_token_auth(self):
|
2016-06-01 12:40:54 +03:00
|
|
|
|
"""
|
|
|
|
|
Ensure POSTing json over token auth with correct
|
|
|
|
|
credentials passes and does not require CSRF
|
|
|
|
|
"""
|
2016-05-04 12:53:34 +03:00
|
|
|
|
auth = self.header_prefix + self.key
|
2016-06-01 12:40:54 +03:00
|
|
|
|
response = self.csrf_client.post(
|
|
|
|
|
self.path, {'example': 'example'}, HTTP_AUTHORIZATION=auth
|
|
|
|
|
)
|
2016-12-05 21:33:13 +03:00
|
|
|
|
assert response.status_code == status.HTTP_200_OK
|
2012-09-20 16:06:27 +04:00
|
|
|
|
|
2017-01-23 18:08:59 +03:00
|
|
|
|
def test_fail_authentication_if_user_is_not_active(self):
|
|
|
|
|
user = User.objects.create_user('foo', 'bar', 'baz')
|
|
|
|
|
user.is_active = False
|
|
|
|
|
user.save()
|
|
|
|
|
self.model.objects.create(key='foobar_token', user=user)
|
|
|
|
|
response = self.csrf_client.post(
|
|
|
|
|
self.path, {'example': 'example'},
|
|
|
|
|
HTTP_AUTHORIZATION=self.header_prefix + 'foobar_token'
|
|
|
|
|
)
|
|
|
|
|
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
|
|
|
|
|
2016-01-05 18:42:22 +03:00
|
|
|
|
def test_fail_post_form_passing_nonexistent_token_auth(self):
|
|
|
|
|
# use a nonexistent token key
|
2016-05-04 12:53:34 +03:00
|
|
|
|
auth = self.header_prefix + 'wxyz6789'
|
2016-06-01 12:40:54 +03:00
|
|
|
|
response = self.csrf_client.post(
|
|
|
|
|
self.path, {'example': 'example'}, HTTP_AUTHORIZATION=auth
|
|
|
|
|
)
|
2016-12-05 21:33:13 +03:00
|
|
|
|
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
2016-01-05 18:42:22 +03:00
|
|
|
|
|
2017-01-23 18:08:59 +03:00
|
|
|
|
def test_fail_post_if_token_is_missing(self):
|
|
|
|
|
response = self.csrf_client.post(
|
|
|
|
|
self.path, {'example': 'example'},
|
|
|
|
|
HTTP_AUTHORIZATION=self.header_prefix)
|
|
|
|
|
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
|
|
|
|
|
|
|
|
|
def test_fail_post_if_token_contains_spaces(self):
|
|
|
|
|
response = self.csrf_client.post(
|
|
|
|
|
self.path, {'example': 'example'},
|
|
|
|
|
HTTP_AUTHORIZATION=self.header_prefix + 'foo bar'
|
|
|
|
|
)
|
|
|
|
|
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
|
|
|
|
|
2015-06-03 20:55:34 +03:00
|
|
|
|
def test_fail_post_form_passing_invalid_token_auth(self):
|
|
|
|
|
# add an 'invalid' unicode character
|
2016-05-04 12:53:34 +03:00
|
|
|
|
auth = self.header_prefix + self.key + "¸"
|
2016-06-01 12:40:54 +03:00
|
|
|
|
response = self.csrf_client.post(
|
|
|
|
|
self.path, {'example': 'example'}, HTTP_AUTHORIZATION=auth
|
|
|
|
|
)
|
2016-12-05 21:33:13 +03:00
|
|
|
|
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
2015-06-03 20:55:34 +03:00
|
|
|
|
|
2012-09-20 16:06:27 +04:00
|
|
|
|
def test_post_json_passing_token_auth(self):
|
2016-06-01 12:40:54 +03:00
|
|
|
|
"""
|
|
|
|
|
Ensure POSTing form over token auth with correct
|
|
|
|
|
credentials passes and does not require CSRF
|
|
|
|
|
"""
|
2016-05-04 12:53:34 +03:00
|
|
|
|
auth = self.header_prefix + self.key
|
2016-06-01 12:40:54 +03:00
|
|
|
|
response = self.csrf_client.post(
|
|
|
|
|
self.path, {'example': 'example'},
|
|
|
|
|
format='json', HTTP_AUTHORIZATION=auth
|
|
|
|
|
)
|
2016-12-05 21:33:13 +03:00
|
|
|
|
assert response.status_code == status.HTTP_200_OK
|
2012-09-20 16:06:27 +04:00
|
|
|
|
|
2015-02-04 17:08:41 +03:00
|
|
|
|
def test_post_json_makes_one_db_query(self):
|
2016-06-01 12:40:54 +03:00
|
|
|
|
"""
|
|
|
|
|
Ensure that authenticating a user using a
|
|
|
|
|
token performs only one DB query
|
|
|
|
|
"""
|
2016-05-04 12:53:34 +03:00
|
|
|
|
auth = self.header_prefix + self.key
|
2015-02-09 20:43:20 +03:00
|
|
|
|
|
|
|
|
|
def func_to_test():
|
2016-06-01 12:40:54 +03:00
|
|
|
|
return self.csrf_client.post(
|
|
|
|
|
self.path, {'example': 'example'},
|
|
|
|
|
format='json', HTTP_AUTHORIZATION=auth
|
|
|
|
|
)
|
2015-02-09 20:43:20 +03:00
|
|
|
|
|
2015-02-04 17:08:41 +03:00
|
|
|
|
self.assertNumQueries(1, func_to_test)
|
|
|
|
|
|
2012-09-20 16:06:27 +04:00
|
|
|
|
def test_post_form_failing_token_auth(self):
|
2016-06-01 12:40:54 +03:00
|
|
|
|
"""
|
|
|
|
|
Ensure POSTing form over token auth without correct credentials fails
|
|
|
|
|
"""
|
2016-01-05 18:58:16 +03:00
|
|
|
|
response = self.csrf_client.post(self.path, {'example': 'example'})
|
2016-12-05 21:33:13 +03:00
|
|
|
|
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
2012-09-20 16:06:27 +04:00
|
|
|
|
|
|
|
|
|
def test_post_json_failing_token_auth(self):
|
2016-06-01 12:40:54 +03:00
|
|
|
|
"""
|
|
|
|
|
Ensure POSTing json over token auth without correct credentials fails
|
|
|
|
|
"""
|
|
|
|
|
response = self.csrf_client.post(
|
|
|
|
|
self.path, {'example': 'example'}, format='json'
|
|
|
|
|
)
|
2016-12-05 21:33:13 +03:00
|
|
|
|
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
2012-09-20 16:06:27 +04:00
|
|
|
|
|
2016-01-05 18:58:16 +03:00
|
|
|
|
|
2016-06-01 17:31:00 +03:00
|
|
|
|
@override_settings(ROOT_URLCONF='tests.test_authentication')
|
2016-01-05 18:58:16 +03:00
|
|
|
|
class TokenAuthTests(BaseTokenAuthTests, TestCase):
|
|
|
|
|
model = Token
|
|
|
|
|
path = '/token/'
|
|
|
|
|
|
2012-09-20 16:06:27 +04:00
|
|
|
|
def test_token_has_auto_assigned_key_if_none_provided(self):
|
|
|
|
|
"""Ensure creating a token with no key will auto-assign a key"""
|
2012-10-09 12:57:31 +04:00
|
|
|
|
self.token.delete()
|
2016-01-05 18:58:16 +03:00
|
|
|
|
token = self.model.objects.create(user=self.user)
|
2016-12-05 21:33:13 +03:00
|
|
|
|
assert bool(token.key)
|
2012-11-11 04:17:50 +04:00
|
|
|
|
|
2014-04-28 15:35:55 +04:00
|
|
|
|
def test_generate_key_returns_string(self):
|
|
|
|
|
"""Ensure generate_key returns a string"""
|
2016-01-05 18:58:16 +03:00
|
|
|
|
token = self.model()
|
2014-04-28 15:35:55 +04:00
|
|
|
|
key = token.generate_key()
|
2016-12-05 21:33:13 +03:00
|
|
|
|
assert isinstance(key, six.string_types)
|
2014-04-28 15:35:55 +04:00
|
|
|
|
|
2012-11-11 04:17:50 +04:00
|
|
|
|
def test_token_login_json(self):
|
|
|
|
|
"""Ensure token login view using JSON POST works."""
|
2013-06-28 20:50:30 +04:00
|
|
|
|
client = APIClient(enforce_csrf_checks=True)
|
2016-06-01 12:40:54 +03:00
|
|
|
|
response = client.post(
|
|
|
|
|
'/auth-token/',
|
|
|
|
|
{'username': self.username, 'password': self.password},
|
|
|
|
|
format='json'
|
|
|
|
|
)
|
2016-12-05 21:33:13 +03:00
|
|
|
|
assert response.status_code == status.HTTP_200_OK
|
|
|
|
|
assert response.data['token'] == self.key
|
2012-11-11 04:17:50 +04:00
|
|
|
|
|
|
|
|
|
def test_token_login_json_bad_creds(self):
|
2016-06-01 12:40:54 +03:00
|
|
|
|
"""
|
|
|
|
|
Ensure token login view using JSON POST fails if
|
|
|
|
|
bad credentials are used
|
|
|
|
|
"""
|
2013-06-28 20:50:30 +04:00
|
|
|
|
client = APIClient(enforce_csrf_checks=True)
|
2016-06-01 12:40:54 +03:00
|
|
|
|
response = client.post(
|
|
|
|
|
'/auth-token/',
|
|
|
|
|
{'username': self.username, 'password': "badpass"},
|
|
|
|
|
format='json'
|
|
|
|
|
)
|
2016-12-05 21:33:13 +03:00
|
|
|
|
assert response.status_code == 400
|
2012-11-11 04:17:50 +04:00
|
|
|
|
|
|
|
|
|
def test_token_login_json_missing_fields(self):
|
|
|
|
|
"""Ensure token login view using JSON POST fails if missing fields."""
|
2013-06-28 20:50:30 +04:00
|
|
|
|
client = APIClient(enforce_csrf_checks=True)
|
2012-12-08 02:25:16 +04:00
|
|
|
|
response = client.post('/auth-token/',
|
2013-06-28 20:50:30 +04:00
|
|
|
|
{'username': self.username}, format='json')
|
2016-12-05 21:33:13 +03:00
|
|
|
|
assert response.status_code == 400
|
2012-11-11 04:17:50 +04:00
|
|
|
|
|
|
|
|
|
def test_token_login_form(self):
|
|
|
|
|
"""Ensure token login view using form POST works."""
|
2013-06-28 20:50:30 +04:00
|
|
|
|
client = APIClient(enforce_csrf_checks=True)
|
2016-12-05 21:33:13 +03:00
|
|
|
|
response = client.post(
|
|
|
|
|
'/auth-token/',
|
|
|
|
|
{'username': self.username, 'password': self.password}
|
|
|
|
|
)
|
|
|
|
|
assert response.status_code == status.HTTP_200_OK
|
|
|
|
|
assert response.data['token'] == self.key
|
2013-02-28 21:58:58 +04:00
|
|
|
|
|
|
|
|
|
|
2016-06-01 17:31:00 +03:00
|
|
|
|
@override_settings(ROOT_URLCONF='tests.test_authentication')
|
2016-01-05 18:58:16 +03:00
|
|
|
|
class CustomTokenAuthTests(BaseTokenAuthTests, TestCase):
|
|
|
|
|
model = CustomToken
|
|
|
|
|
path = '/customtoken/'
|
|
|
|
|
|
|
|
|
|
|
2016-06-01 17:31:00 +03:00
|
|
|
|
@override_settings(ROOT_URLCONF='tests.test_authentication')
|
2016-05-04 12:53:34 +03:00
|
|
|
|
class CustomKeywordTokenAuthTests(BaseTokenAuthTests, TestCase):
|
|
|
|
|
model = Token
|
|
|
|
|
path = '/customkeywordtoken/'
|
|
|
|
|
header_prefix = 'Bearer '
|
|
|
|
|
|
|
|
|
|
|
2013-02-28 21:58:58 +04:00
|
|
|
|
class IncorrectCredentialsTests(TestCase):
|
|
|
|
|
def test_incorrect_credentials(self):
|
|
|
|
|
"""
|
|
|
|
|
If a request contains bad authentication credentials, then
|
|
|
|
|
authentication should run and error, even if no permissions
|
|
|
|
|
are set on the view.
|
|
|
|
|
"""
|
|
|
|
|
class IncorrectCredentialsAuth(BaseAuthentication):
|
|
|
|
|
def authenticate(self, request):
|
|
|
|
|
raise exceptions.AuthenticationFailed('Bad credentials')
|
|
|
|
|
|
|
|
|
|
request = factory.get('/')
|
|
|
|
|
view = MockView.as_view(
|
|
|
|
|
authentication_classes=(IncorrectCredentialsAuth,),
|
|
|
|
|
permission_classes=()
|
|
|
|
|
)
|
|
|
|
|
response = view(request)
|
2016-12-05 21:33:13 +03:00
|
|
|
|
assert response.status_code == status.HTTP_403_FORBIDDEN
|
|
|
|
|
assert response.data == {'detail': 'Bad credentials'}
|
2013-03-07 13:01:53 +04:00
|
|
|
|
|
|
|
|
|
|
2013-06-03 15:32:57 +04:00
|
|
|
|
class FailingAuthAccessedInRenderer(TestCase):
|
|
|
|
|
def setUp(self):
|
|
|
|
|
class AuthAccessingRenderer(renderers.BaseRenderer):
|
|
|
|
|
media_type = 'text/plain'
|
|
|
|
|
format = 'txt'
|
|
|
|
|
|
|
|
|
|
def render(self, data, media_type=None, renderer_context=None):
|
|
|
|
|
request = renderer_context['request']
|
2017-10-05 21:41:38 +03:00
|
|
|
|
if request.user.is_authenticated:
|
2013-06-03 15:32:57 +04:00
|
|
|
|
return b'authenticated'
|
|
|
|
|
return b'not authenticated'
|
|
|
|
|
|
|
|
|
|
class FailingAuth(BaseAuthentication):
|
|
|
|
|
def authenticate(self, request):
|
|
|
|
|
raise exceptions.AuthenticationFailed('authentication failed')
|
|
|
|
|
|
|
|
|
|
class ExampleView(APIView):
|
|
|
|
|
authentication_classes = (FailingAuth,)
|
|
|
|
|
renderer_classes = (AuthAccessingRenderer,)
|
|
|
|
|
|
|
|
|
|
def get(self, request):
|
|
|
|
|
return Response({'foo': 'bar'})
|
|
|
|
|
|
|
|
|
|
self.view = ExampleView.as_view()
|
|
|
|
|
|
|
|
|
|
def test_failing_auth_accessed_in_renderer(self):
|
|
|
|
|
"""
|
|
|
|
|
When authentication fails the renderer should still be able to access
|
|
|
|
|
`request.user` without raising an exception. Particularly relevant
|
|
|
|
|
to HTML responses that might reasonably access `request.user`.
|
|
|
|
|
"""
|
|
|
|
|
request = factory.get('/')
|
|
|
|
|
response = self.view(request)
|
|
|
|
|
content = response.render().content
|
2016-12-05 21:33:13 +03:00
|
|
|
|
assert content == b'not authenticated'
|
2016-04-07 18:24:26 +03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class NoAuthenticationClassesTests(TestCase):
|
|
|
|
|
def test_permission_message_with_no_authentication_classes(self):
|
|
|
|
|
"""
|
2016-08-08 11:32:22 +03:00
|
|
|
|
An unauthenticated request made against a view that contains no
|
2016-04-07 18:24:26 +03:00
|
|
|
|
`authentication_classes` but do contain `permissions_classes` the error
|
|
|
|
|
code returned should be 403 with the exception's message.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
class DummyPermission(permissions.BasePermission):
|
|
|
|
|
message = 'Dummy permission message'
|
|
|
|
|
|
|
|
|
|
def has_permission(self, request, view):
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
request = factory.get('/')
|
|
|
|
|
view = MockView.as_view(
|
|
|
|
|
authentication_classes=(),
|
|
|
|
|
permission_classes=(DummyPermission,),
|
|
|
|
|
)
|
|
|
|
|
response = view(request)
|
2016-12-05 21:33:13 +03:00
|
|
|
|
assert response.status_code == status.HTTP_403_FORBIDDEN
|
|
|
|
|
assert response.data == {'detail': 'Dummy permission message'}
|
2017-01-23 18:08:59 +03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class BasicAuthenticationUnitTests(TestCase):
|
|
|
|
|
|
|
|
|
|
def test_base_authentication_abstract_method(self):
|
|
|
|
|
with pytest.raises(NotImplementedError):
|
|
|
|
|
BaseAuthentication().authenticate({})
|
|
|
|
|
|
|
|
|
|
def test_basic_authentication_raises_error_if_user_not_found(self):
|
|
|
|
|
auth = BasicAuthentication()
|
|
|
|
|
with pytest.raises(exceptions.AuthenticationFailed):
|
|
|
|
|
auth.authenticate_credentials('invalid id', 'invalid password')
|
|
|
|
|
|
|
|
|
|
def test_basic_authentication_raises_error_if_user_not_active(self):
|
|
|
|
|
from rest_framework import authentication
|
|
|
|
|
|
|
|
|
|
class MockUser(object):
|
|
|
|
|
is_active = False
|
|
|
|
|
old_authenticate = authentication.authenticate
|
|
|
|
|
authentication.authenticate = lambda **kwargs: MockUser()
|
|
|
|
|
auth = authentication.BasicAuthentication()
|
|
|
|
|
with pytest.raises(exceptions.AuthenticationFailed) as error:
|
|
|
|
|
auth.authenticate_credentials('foo', 'bar')
|
|
|
|
|
assert 'User inactive or deleted.' in str(error)
|
|
|
|
|
authentication.authenticate = old_authenticate
|
2017-08-11 12:35:00 +03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@override_settings(ROOT_URLCONF='tests.test_authentication',
|
|
|
|
|
AUTHENTICATION_BACKENDS=('django.contrib.auth.backends.RemoteUserBackend',))
|
|
|
|
|
class RemoteUserAuthenticationUnitTests(TestCase):
|
|
|
|
|
def setUp(self):
|
|
|
|
|
self.username = 'john'
|
|
|
|
|
self.email = 'lennon@thebeatles.com'
|
|
|
|
|
self.password = 'password'
|
|
|
|
|
self.user = User.objects.create_user(
|
|
|
|
|
self.username, self.email, self.password
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def test_remote_user_works(self):
|
|
|
|
|
response = self.client.post('/remote-user/',
|
|
|
|
|
REMOTE_USER=self.username)
|
|
|
|
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|