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.
This commit is contained in:
parent
7058b595b6
commit
1520748dac
|
@ -1,6 +1,6 @@
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured, PermissionDenied
|
||||||
from django.utils.importlib import import_module
|
from django.utils.importlib import import_module
|
||||||
from django.contrib.auth.signals import user_logged_in, user_logged_out, user_login_failed
|
from django.contrib.auth.signals import user_logged_in, user_logged_out, user_login_failed
|
||||||
|
|
||||||
|
@ -60,6 +60,9 @@ def authenticate(**credentials):
|
||||||
except TypeError:
|
except TypeError:
|
||||||
# This backend doesn't accept these credentials as arguments. Try the next one.
|
# This backend doesn't accept these credentials as arguments. Try the next one.
|
||||||
continue
|
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:
|
if user is None:
|
||||||
continue
|
continue
|
||||||
# Annotate the user object with the path of the backend.
|
# Annotate the user object with the path of the backend.
|
||||||
|
|
|
@ -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.utils import skipIfCustomUser
|
||||||
from django.contrib.auth.tests.custom_user import ExtensionUser
|
from django.contrib.auth.tests.custom_user import ExtensionUser
|
||||||
from django.contrib.contenttypes.models import ContentType
|
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 import TestCase
|
||||||
from django.test.utils import override_settings
|
from django.test.utils import override_settings
|
||||||
|
|
||||||
|
@ -323,3 +324,37 @@ class InActiveUserBackendTest(TestCase):
|
||||||
def test_has_module_perms(self):
|
def test_has_module_perms(self):
|
||||||
self.assertEqual(self.user1.has_module_perms("app1"), False)
|
self.assertEqual(self.user1.has_module_perms("app1"), False)
|
||||||
self.assertEqual(self.user1.has_module_perms("app2"), 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)
|
||||||
|
|
|
@ -17,6 +17,12 @@ deprecation process for some features`_.
|
||||||
What's new in Django 1.6
|
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
|
Backwards incompatible changes in 1.6
|
||||||
=====================================
|
=====================================
|
||||||
|
|
||||||
|
|
|
@ -2391,6 +2391,12 @@ processing at the first positive match.
|
||||||
you need to force users to re-authenticate using different methods. A simple
|
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()``.
|
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
|
Writing an authentication backend
|
||||||
---------------------------------
|
---------------------------------
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue