Skip to content

Commit 33da18a

Browse files
authored
Merge pull request tobami#276 from Kami/fix_authentication_bug
Fix authentication helper so it works with newer versions of Django (Django 2+)
2 parents b362022 + 72ec4e7 commit 33da18a

File tree

3 files changed

+155
-2
lines changed

3 files changed

+155
-2
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ matrix:
1414
- python: "3.5"
1515
env: DJANGO_VERSION=2.1
1616
install:
17-
- pip install flake8
17+
- pip install flake8 mock
1818
- pip install -q Django==$DJANGO_VERSION
1919
- python setup.py install
2020
before_script:

codespeed/auth.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import logging
2+
import types
23
from functools import wraps
34
from django.contrib.auth import authenticate, login
45
from django.http import HttpResponse, HttpResponseForbidden
@@ -9,6 +10,20 @@
910
logger = logging.getLogger(__name__)
1011

1112

13+
def is_authenticated(request):
14+
# NOTE: We do type check so we also support newer versions of Django when
15+
# is_authenticated and some other methods have been properties
16+
if isinstance(request.user.is_authenticated, (types.FunctionType,
17+
types.MethodType)):
18+
return request.user.is_authenticated()
19+
elif isinstance(request.user.is_authenticated, bool):
20+
return request.user.is_authenticated
21+
else:
22+
logger.info('Got unexpected type for request.user.is_authenticated '
23+
'variable')
24+
return False
25+
26+
1227
def basic_auth_required(realm='default'):
1328
def _helper(func):
1429
@wraps(func)
@@ -18,7 +33,7 @@ def _decorator(request, *args, **kwargs):
1833
if settings.ALLOW_ANONYMOUS_POST:
1934
logger.debug('allowing anonymous post')
2035
allowed = True
21-
elif hasattr(request, 'user') and request.user.is_authenticated():
36+
elif hasattr(request, 'user') and is_authenticated(request=request):
2237
allowed = True
2338
elif 'HTTP_AUTHORIZATION' in request.META:
2439
logger.debug('checking for http authorization header')

codespeed/tests/test_auth.py

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
# -*- coding: utf-8 -*-
2+
3+
import mock
4+
5+
from django.test import TestCase, override_settings
6+
from django.http import HttpResponse
7+
from django.contrib.auth.models import AnonymousUser
8+
from django.test import RequestFactory
9+
10+
from codespeed.auth import basic_auth_required
11+
from codespeed.views import add_result
12+
13+
14+
@override_settings(ALLOW_ANONYMOUS_POST=False)
15+
class AuthModuleTestCase(TestCase):
16+
@override_settings(ALLOW_ANONYMOUS_POST=True)
17+
def test_allow_anonymous_post_is_true(self):
18+
wrapped_function = mock.Mock()
19+
wrapped_function.__name__ = 'mock'
20+
wrapped_function.return_value = 'success'
21+
22+
request = mock.Mock()
23+
request.user = AnonymousUser()
24+
request.META = {}
25+
26+
res = basic_auth_required()(wrapped_function)(request=request)
27+
self.assertEqual(wrapped_function.call_count, 1)
28+
self.assertEqual(res, 'success')
29+
30+
def test_basic_auth_required_django_pre_2_0_succesful_auth(self):
31+
# request.user.is_authenticated is a method (pre Django 2.0)
32+
user = mock.Mock()
33+
user.is_authenticated = lambda: True
34+
35+
request = mock.Mock()
36+
request.user = user
37+
38+
wrapped_function = mock.Mock()
39+
wrapped_function.__name__ = 'mock'
40+
wrapped_function.return_value = 'success'
41+
42+
res = basic_auth_required()(wrapped_function)(request=request)
43+
self.assertEqual(wrapped_function.call_count, 1)
44+
self.assertEqual(res, 'success')
45+
46+
def test_basic_auth_required_django_pre_2_0_failed_auth(self):
47+
# request.user.is_authenticated is a method (pre Django 2.0)
48+
user = mock.Mock()
49+
user.is_authenticated = lambda: False
50+
51+
request = mock.Mock()
52+
request.user = user
53+
request.META = {}
54+
55+
wrapped_function = mock.Mock()
56+
wrapped_function.__name__ = 'mock'
57+
58+
res = basic_auth_required()(wrapped_function)(request=request)
59+
self.assertTrue(isinstance(res, HttpResponse))
60+
self.assertEqual(res.status_code, 401)
61+
self.assertEqual(wrapped_function.call_count, 0)
62+
63+
# Also test with actual AnonymousUser class which will have different
64+
# implementation under different Django versions
65+
request.user = AnonymousUser()
66+
67+
res = basic_auth_required()(wrapped_function)(request=request)
68+
self.assertTrue(isinstance(res, HttpResponse))
69+
self.assertEqual(res.status_code, 401)
70+
self.assertEqual(wrapped_function.call_count, 0)
71+
72+
def test_basic_auth_required_django_post_2_0_successful_auth(self):
73+
# request.user.is_authenticated is a property (post Django 2.0)
74+
user = mock.Mock()
75+
user.is_authenticated = True
76+
77+
request = mock.Mock()
78+
request.user = user
79+
80+
wrapped_function = mock.Mock()
81+
wrapped_function.__name__ = 'mock'
82+
wrapped_function.return_value = 'success'
83+
84+
res = basic_auth_required()(wrapped_function)(request=request)
85+
self.assertEqual(wrapped_function.call_count, 1)
86+
self.assertEqual(res, 'success')
87+
88+
def test_basic_auth_required_django_post_2_0_failed_auth(self):
89+
# request.user.is_authenticated is a property (post Django 2.0)
90+
user = mock.Mock()
91+
user.is_authenticated = False
92+
93+
request = mock.Mock()
94+
request.user = user
95+
request.META = {}
96+
97+
wrapped_function = mock.Mock()
98+
wrapped_function.__name__ = 'mock'
99+
100+
res = basic_auth_required()(wrapped_function)(request=request)
101+
self.assertTrue(isinstance(res, HttpResponse))
102+
self.assertEqual(res.status_code, 401)
103+
self.assertEqual(wrapped_function.call_count, 0)
104+
105+
# Also test with actual AnonymousUser class which will have different
106+
# implementation under different Django versions
107+
request.user = AnonymousUser()
108+
109+
res = basic_auth_required()(wrapped_function)(request=request)
110+
self.assertTrue(isinstance(res, HttpResponse))
111+
self.assertEqual(res.status_code, 401)
112+
self.assertEqual(wrapped_function.call_count, 0)
113+
114+
@mock.patch('codespeed.views.save_result', mock.Mock())
115+
def test_basic_auth_with_failed_auth_request_factory(self):
116+
request_factory = RequestFactory()
117+
118+
request = request_factory.get('/timeline')
119+
request.user = AnonymousUser()
120+
request.method = 'POST'
121+
122+
response = add_result(request)
123+
self.assertEqual(response.status_code, 403)
124+
125+
@mock.patch('codespeed.views.create_report_if_enough_data', mock.Mock())
126+
@mock.patch('codespeed.views.save_result', mock.Mock(return_value=([1, 2, 3], None)))
127+
def test_basic_auth_successefull_auth_request_factory(self):
128+
request_factory = RequestFactory()
129+
130+
user = mock.Mock()
131+
user.is_authenticated = True
132+
133+
request = request_factory.get('/result/add')
134+
request.user = user
135+
request.method = 'POST'
136+
137+
response = add_result(request)
138+
self.assertEqual(response.status_code, 202)

0 commit comments

Comments
 (0)