Skip to content

Commit 1520748

Browse files
committed
Fixed #2550 -- Allow the auth backends to raise the PermissionDenied exception to completely stop the authentication chain. Many thanks to namn, danielr, Dan Julius, Łukasz Rekucki, Aashu Dwivedi and umbrae for working this over the years.
1 parent 7058b59 commit 1520748

File tree

4 files changed

+52
-2
lines changed

4 files changed

+52
-2
lines changed

django/contrib/auth/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import re
22

3-
from django.core.exceptions import ImproperlyConfigured
3+
from django.core.exceptions import ImproperlyConfigured, PermissionDenied
44
from django.utils.importlib import import_module
55
from django.contrib.auth.signals import user_logged_in, user_logged_out, user_login_failed
66

@@ -60,6 +60,9 @@ def authenticate(**credentials):
6060
except TypeError:
6161
# This backend doesn't accept these credentials as arguments. Try the next one.
6262
continue
63+
except PermissionDenied:
64+
# This backend says to stop in our tracks - this user should not be allowed in at all.
65+
return None
6366
if user is None:
6467
continue
6568
# Annotate the user object with the path of the backend.

django/contrib/auth/tests/auth_backends.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
from django.contrib.auth.tests.utils import skipIfCustomUser
77
from django.contrib.auth.tests.custom_user import ExtensionUser
88
from django.contrib.contenttypes.models import ContentType
9-
from django.core.exceptions import ImproperlyConfigured
9+
from django.core.exceptions import ImproperlyConfigured, PermissionDenied
10+
from django.contrib.auth import authenticate
1011
from django.test import TestCase
1112
from django.test.utils import override_settings
1213

@@ -323,3 +324,37 @@ def test_has_perm(self):
323324
def test_has_module_perms(self):
324325
self.assertEqual(self.user1.has_module_perms("app1"), False)
325326
self.assertEqual(self.user1.has_module_perms("app2"), False)
327+
328+
329+
class PermissionDeniedBackend(object):
330+
"""
331+
Always raises PermissionDenied.
332+
"""
333+
supports_object_permissions = True
334+
supports_anonymous_user = True
335+
supports_inactive_user = True
336+
337+
def authenticate(self, username=None, password=None):
338+
raise PermissionDenied
339+
340+
341+
class PermissionDeniedBackendTest(TestCase):
342+
"""
343+
Tests that other backends are not checked once a backend raises PermissionDenied
344+
"""
345+
backend = 'django.contrib.auth.tests.auth_backends.PermissionDeniedBackend'
346+
347+
def setUp(self):
348+
self.user1 = User.objects.create_user('test', '[email protected]', 'test')
349+
self.user1.save()
350+
351+
@override_settings(AUTHENTICATION_BACKENDS=(backend, ) +
352+
tuple(settings.AUTHENTICATION_BACKENDS))
353+
def test_permission_denied(self):
354+
"user is not authenticated after a backend raises permission denied #2550"
355+
self.assertEqual(authenticate(username='test', password='test'), None)
356+
357+
@override_settings(AUTHENTICATION_BACKENDS=tuple(
358+
settings.AUTHENTICATION_BACKENDS) + (backend, ))
359+
def test_authenticates(self):
360+
self.assertEqual(authenticate(username='test', password='test'), self.user1)

docs/releases/1.6.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ deprecation process for some features`_.
1717
What's new in Django 1.6
1818
========================
1919

20+
Minor features
21+
~~~~~~~~~~~~~~
22+
23+
* Authentication backends can raise ``PermissionDenied`` to immediately fail
24+
the authentication chain.
25+
2026
Backwards incompatible changes in 1.6
2127
=====================================
2228

docs/topics/auth.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2391,6 +2391,12 @@ processing at the first positive match.
23912391
you need to force users to re-authenticate using different methods. A simple
23922392
way to do that is simply to execute ``Session.objects.all().delete()``.
23932393

2394+
.. versionadded:: 1.6
2395+
2396+
If a backend raises a :class:`~django.core.exceptions.PermissionDenied`
2397+
exception, authentication will immediately fail. Django won't check the
2398+
backends that follow.
2399+
23942400
Writing an authentication backend
23952401
---------------------------------
23962402

0 commit comments

Comments
 (0)