Code

Ticket #2550: ticket2550d.diff

File ticket2550d.diff, 4.9 KB (added by namn, 21 months ago)

ticket2550d.diff

Line 
1diff --git a/django/contrib/auth/__init__.py b/django/contrib/auth/__init__.py
2index 0b3ccf7..c0a8b2a 100644
3--- a/django/contrib/auth/__init__.py
4+++ b/django/contrib/auth/__init__.py
5@@ -1,4 +1,4 @@
6-from django.core.exceptions import ImproperlyConfigured
7+from django.core.exceptions import ImproperlyConfigured, PermissionDenied
8 from django.utils.importlib import import_module
9 from django.contrib.auth.signals import user_logged_in, user_logged_out
10 
11@@ -40,6 +40,9 @@ def authenticate(**credentials):
12         except TypeError:
13             # This backend doesn't accept these credentials as arguments. Try the next one.
14             continue
15+        except PermissionDenied:
16+            # This backend says to stop in our tracks - this user should not be allowed in at all.
17+            return None
18         if user is None:
19             continue
20         # Annotate the user object with the path of the backend.
21diff --git a/django/contrib/auth/tests/__init__.py b/django/contrib/auth/tests/__init__.py
22index 16eaa5c..8cd2499 100644
23--- a/django/contrib/auth/tests/__init__.py
24+++ b/django/contrib/auth/tests/__init__.py
25@@ -1,6 +1,6 @@
26 from django.contrib.auth.tests.auth_backends import (BackendTest,
27     RowlevelBackendTest, AnonymousUserBackendTest, NoBackendsTest,
28-    InActiveUserBackendTest)
29+    InActiveUserBackendTest, PermissionDeniedBackendTest)
30 from django.contrib.auth.tests.basic import BasicTestCase
31 from django.contrib.auth.tests.context_processors import AuthContextProcessorTests
32 from django.contrib.auth.tests.decorators import LoginRequiredTestCase
33diff --git a/django/contrib/auth/tests/auth_backends.py b/django/contrib/auth/tests/auth_backends.py
34index 9a4d8f9..a75532e 100644
35--- a/django/contrib/auth/tests/auth_backends.py
36+++ b/django/contrib/auth/tests/auth_backends.py
37@@ -3,7 +3,8 @@ from __future__ import unicode_literals
38 from django.conf import settings
39 from django.contrib.auth.models import User, Group, Permission, AnonymousUser
40 from django.contrib.contenttypes.models import ContentType
41-from django.core.exceptions import ImproperlyConfigured
42+from django.core.exceptions import ImproperlyConfigured, PermissionDenied
43+from django.contrib.auth import authenticate
44 from django.test import TestCase
45 from django.test.utils import override_settings
46 
47@@ -258,3 +259,37 @@ class InActiveUserBackendTest(TestCase):
48     def test_has_module_perms(self):
49         self.assertEqual(self.user1.has_module_perms("app1"), False)
50         self.assertEqual(self.user1.has_module_perms("app2"), False)
51+
52+
53+class PermissionDeniedBackend(object):
54+    """
55+    Always raises PermissionDenied.
56+    """
57+    supports_object_permissions = True
58+    supports_anonymous_user = True
59+    supports_inactive_user = True
60+
61+    def authenticate(self, username=None, password=None):
62+        raise PermissionDenied
63+
64+
65+class PermissionDeniedBackendTest(TestCase):
66+    """
67+    Tests that other backends are not checked once a backend raises PermissionDenied
68+    """
69+    backend = 'django.contrib.auth.tests.auth_backends.PermissionDeniedBackend'
70+
71+    def setUp(self):
72+        self.user1 = User.objects.create_user('test', 'test@example.com', 'test')
73+        self.user1.save()
74+
75+    @override_settings(AUTHENTICATION_BACKENDS=(backend, ) +
76+            tuple(settings.AUTHENTICATION_BACKENDS))
77+    def test_permission_denied(self):
78+        "user is not authenticated after a backend raises permission denied #2550"
79+        self.assertEqual(authenticate(username='test', password='test'), None)
80+
81+    @override_settings(AUTHENTICATION_BACKENDS=tuple(
82+            settings.AUTHENTICATION_BACKENDS) + (backend, ))
83+    def test_authenticates(self):
84+        self.assertEqual(authenticate(username='test', password='test'), self.user1)
85diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt
86index fd9ae4f..2f65430 100644
87--- a/docs/releases/1.5.txt
88+++ b/docs/releases/1.5.txt
89@@ -111,6 +111,9 @@ Django 1.5 also includes several smaller improvements worth noting:
90   argument. By default the batch_size is unlimited except for SQLite where
91   single batch is limited so that 999 parameters per query isn't exceeded.
92 
93+* Authentication backend can raise ``PermissionDenied`` to immediately fail
94+  the authentication chain.
95+
96 Backwards incompatible changes in 1.5
97 =====================================
98 
99diff --git a/docs/topics/auth.txt b/docs/topics/auth.txt
100index c0e56db..e78381f 100644
101--- a/docs/topics/auth.txt
102+++ b/docs/topics/auth.txt
103@@ -1764,6 +1764,12 @@ processing at the first positive match.
104     you need to force users to re-authenticate using different methods. A simple
105     way to do that is simply to execute ``Session.objects.all().delete()``.
106 
107+.. versionadded:: 1.4
108+
109+If a backend raises a :class:`~django.core.exceptions.PermissionDenied`
110+exception, authentication will immediately fail. Django won't check the backends that follow.
111+
112+
113 Writing an authentication backend
114 ---------------------------------
115