summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJannis Leidel <jannis@leidel.info>2012-11-17 20:24:54 +0100
committerJannis Leidel <jannis@leidel.info>2012-11-17 20:24:54 +0100
commit1520748dac95a7f114e4bb2feeee04d46c720494 (patch)
tree8eecdb5b188c469766b9422f42a1cb605a6e56ff
parent7058b595b668b49daef8beb76a2b5c5f1d991b00 (diff)
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.
-rw-r--r--django/contrib/auth/__init__.py5
-rw-r--r--django/contrib/auth/tests/auth_backends.py37
-rw-r--r--docs/releases/1.6.txt6
-rw-r--r--docs/topics/auth.txt6
4 files changed, 52 insertions, 2 deletions
diff --git a/django/contrib/auth/__init__.py b/django/contrib/auth/__init__.py
index dd4a8484f5..5dbda44501 100644
--- a/django/contrib/auth/__init__.py
+++ b/django/contrib/auth/__init__.py
@@ -1,6 +1,6 @@
import re
-from django.core.exceptions import ImproperlyConfigured
+from django.core.exceptions import ImproperlyConfigured, PermissionDenied
from django.utils.importlib import import_module
from django.contrib.auth.signals import user_logged_in, user_logged_out, user_login_failed
@@ -60,6 +60,9 @@ def authenticate(**credentials):
except TypeError:
# This backend doesn't accept these credentials as arguments. Try the next one.
continue
+ except PermissionDenied:
+ # This backend says to stop in our tracks - this user should not be allowed in at all.
+ return None
if user is None:
continue
# Annotate the user object with the path of the backend.
diff --git a/django/contrib/auth/tests/auth_backends.py b/django/contrib/auth/tests/auth_backends.py
index e92f159ff9..2ab0bd0efa 100644
--- a/django/contrib/auth/tests/auth_backends.py
+++ b/django/contrib/auth/tests/auth_backends.py
@@ -6,7 +6,8 @@ from django.contrib.auth.models import User, Group, Permission, AnonymousUser
from django.contrib.auth.tests.utils import skipIfCustomUser
from django.contrib.auth.tests.custom_user import ExtensionUser
from django.contrib.contenttypes.models import ContentType
-from django.core.exceptions import ImproperlyConfigured
+from django.core.exceptions import ImproperlyConfigured, PermissionDenied
+from django.contrib.auth import authenticate
from django.test import TestCase
from django.test.utils import override_settings
@@ -323,3 +324,37 @@ class InActiveUserBackendTest(TestCase):
def test_has_module_perms(self):
self.assertEqual(self.user1.has_module_perms("app1"), False)
self.assertEqual(self.user1.has_module_perms("app2"), False)
+
+
+class PermissionDeniedBackend(object):
+ """
+ Always raises PermissionDenied.
+ """
+ supports_object_permissions = True
+ supports_anonymous_user = True
+ supports_inactive_user = True
+
+ def authenticate(self, username=None, password=None):
+ raise PermissionDenied
+
+
+class PermissionDeniedBackendTest(TestCase):
+ """
+ Tests that other backends are not checked once a backend raises PermissionDenied
+ """
+ backend = 'django.contrib.auth.tests.auth_backends.PermissionDeniedBackend'
+
+ def setUp(self):
+ self.user1 = User.objects.create_user('test', 'test@example.com', 'test')
+ self.user1.save()
+
+ @override_settings(AUTHENTICATION_BACKENDS=(backend, ) +
+ tuple(settings.AUTHENTICATION_BACKENDS))
+ def test_permission_denied(self):
+ "user is not authenticated after a backend raises permission denied #2550"
+ self.assertEqual(authenticate(username='test', password='test'), None)
+
+ @override_settings(AUTHENTICATION_BACKENDS=tuple(
+ settings.AUTHENTICATION_BACKENDS) + (backend, ))
+ def test_authenticates(self):
+ self.assertEqual(authenticate(username='test', password='test'), self.user1)
diff --git a/docs/releases/1.6.txt b/docs/releases/1.6.txt
index ef162a8de3..49649bc6b8 100644
--- a/docs/releases/1.6.txt
+++ b/docs/releases/1.6.txt
@@ -17,6 +17,12 @@ deprecation process for some features`_.
What's new in Django 1.6
========================
+Minor features
+~~~~~~~~~~~~~~
+
+* Authentication backends can raise ``PermissionDenied`` to immediately fail
+ the authentication chain.
+
Backwards incompatible changes in 1.6
=====================================
diff --git a/docs/topics/auth.txt b/docs/topics/auth.txt
index aed482f710..6d8e3c66c3 100644
--- a/docs/topics/auth.txt
+++ b/docs/topics/auth.txt
@@ -2391,6 +2391,12 @@ processing at the first positive match.
you need to force users to re-authenticate using different methods. A simple
way to do that is simply to execute ``Session.objects.all().delete()``.
+.. versionadded:: 1.6
+
+If a backend raises a :class:`~django.core.exceptions.PermissionDenied`
+exception, authentication will immediately fail. Django won't check the
+backends that follow.
+
Writing an authentication backend
---------------------------------