Loading django/contrib/auth/models.py +17 −2 Original line number Diff line number Diff line from __future__ import unicode_literals from django.core.exceptions import PermissionDenied from django.core.mail import send_mail from django.core import validators from django.db import models Loading Loading @@ -267,18 +268,32 @@ def _user_get_all_permissions(user, obj): def _user_has_perm(user, perm, obj): """ A backend can raise `PermissionDenied` to short-circuit permission checking. """ for backend in auth.get_backends(): if hasattr(backend, "has_perm"): if not hasattr(backend, 'has_perm'): continue try: if backend.has_perm(user, perm, obj): return True except PermissionDenied: return False return False def _user_has_module_perms(user, app_label): """ A backend can raise `PermissionDenied` to short-circuit permission checking. """ for backend in auth.get_backends(): if hasattr(backend, "has_module_perms"): if not hasattr(backend, 'has_module_perms'): continue try: if backend.has_module_perms(user, app_label): return True except PermissionDenied: return False return False Loading django/contrib/auth/tests/test_auth_backends.py +27 −1 Original line number Diff line number Diff line Loading @@ -398,7 +398,7 @@ class InActiveUserBackendTest(TestCase): class PermissionDeniedBackend(object): """ Always raises PermissionDenied. Always raises PermissionDenied in `authenticate`, `has_perm` and `has_module_perms`. """ supports_object_permissions = True supports_anonymous_user = True Loading @@ -407,6 +407,12 @@ class PermissionDeniedBackend(object): def authenticate(self, username=None, password=None): raise PermissionDenied def has_perm(self, user_obj, perm, obj=None): raise PermissionDenied def has_module_perms(self, user_obj, app_label): raise PermissionDenied @skipIfCustomUser class PermissionDeniedBackendTest(TestCase): Loading @@ -430,6 +436,26 @@ class PermissionDeniedBackendTest(TestCase): def test_authenticates(self): self.assertEqual(authenticate(username='test', password='test'), self.user1) @override_settings(AUTHENTICATION_BACKENDS=(backend, ) + tuple(settings.AUTHENTICATION_BACKENDS)) def test_has_perm_denied(self): content_type = ContentType.objects.get_for_model(Group) perm = Permission.objects.create(name='test', content_type=content_type, codename='test') self.user1.user_permissions.add(perm) self.assertIs(self.user1.has_perm('auth.test'), False) self.assertIs(self.user1.has_module_perms('auth'), False) @override_settings(AUTHENTICATION_BACKENDS=tuple( settings.AUTHENTICATION_BACKENDS) + (backend, )) def test_has_perm(self): content_type = ContentType.objects.get_for_model(Group) perm = Permission.objects.create(name='test', content_type=content_type, codename='test') self.user1.user_permissions.add(perm) self.assertIs(self.user1.has_perm('auth.test'), True) self.assertIs(self.user1.has_module_perms('auth'), True) class NewModelBackend(ModelBackend): pass Loading docs/releases/1.8.txt +5 −1 Original line number Diff line number Diff line Loading @@ -36,7 +36,11 @@ Minor features :mod:`django.contrib.auth` ^^^^^^^^^^^^^^^^^^^^^^^^^^ * ... * Authorization backends can now raise :class:`~django.core.exceptions.PermissionDenied` in :meth:`~django.contrib.auth.models.User.has_perm` and :meth:`~django.contrib.auth.models.User.has_module_perms` to short-circuit permission checking. :mod:`django.contrib.formtools` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Loading docs/topics/auth/customizing.txt +8 −0 Original line number Diff line number Diff line Loading @@ -180,6 +180,14 @@ The permissions given to the user will be the superset of all permissions returned by all backends. That is, Django grants a permission to a user that any one backend grants. .. versionadded:: 1.8 If a backend raises a :class:`~django.core.exceptions.PermissionDenied` exception in :meth:`~django.contrib.auth.models.User.has_perm()` or :meth:`~django.contrib.auth.models.User.has_module_perms()`, the authorization will immediately fail and Django won't check the backends that follow. The simple backend above could implement permissions for the magic admin fairly simply:: Loading Loading
django/contrib/auth/models.py +17 −2 Original line number Diff line number Diff line from __future__ import unicode_literals from django.core.exceptions import PermissionDenied from django.core.mail import send_mail from django.core import validators from django.db import models Loading Loading @@ -267,18 +268,32 @@ def _user_get_all_permissions(user, obj): def _user_has_perm(user, perm, obj): """ A backend can raise `PermissionDenied` to short-circuit permission checking. """ for backend in auth.get_backends(): if hasattr(backend, "has_perm"): if not hasattr(backend, 'has_perm'): continue try: if backend.has_perm(user, perm, obj): return True except PermissionDenied: return False return False def _user_has_module_perms(user, app_label): """ A backend can raise `PermissionDenied` to short-circuit permission checking. """ for backend in auth.get_backends(): if hasattr(backend, "has_module_perms"): if not hasattr(backend, 'has_module_perms'): continue try: if backend.has_module_perms(user, app_label): return True except PermissionDenied: return False return False Loading
django/contrib/auth/tests/test_auth_backends.py +27 −1 Original line number Diff line number Diff line Loading @@ -398,7 +398,7 @@ class InActiveUserBackendTest(TestCase): class PermissionDeniedBackend(object): """ Always raises PermissionDenied. Always raises PermissionDenied in `authenticate`, `has_perm` and `has_module_perms`. """ supports_object_permissions = True supports_anonymous_user = True Loading @@ -407,6 +407,12 @@ class PermissionDeniedBackend(object): def authenticate(self, username=None, password=None): raise PermissionDenied def has_perm(self, user_obj, perm, obj=None): raise PermissionDenied def has_module_perms(self, user_obj, app_label): raise PermissionDenied @skipIfCustomUser class PermissionDeniedBackendTest(TestCase): Loading @@ -430,6 +436,26 @@ class PermissionDeniedBackendTest(TestCase): def test_authenticates(self): self.assertEqual(authenticate(username='test', password='test'), self.user1) @override_settings(AUTHENTICATION_BACKENDS=(backend, ) + tuple(settings.AUTHENTICATION_BACKENDS)) def test_has_perm_denied(self): content_type = ContentType.objects.get_for_model(Group) perm = Permission.objects.create(name='test', content_type=content_type, codename='test') self.user1.user_permissions.add(perm) self.assertIs(self.user1.has_perm('auth.test'), False) self.assertIs(self.user1.has_module_perms('auth'), False) @override_settings(AUTHENTICATION_BACKENDS=tuple( settings.AUTHENTICATION_BACKENDS) + (backend, )) def test_has_perm(self): content_type = ContentType.objects.get_for_model(Group) perm = Permission.objects.create(name='test', content_type=content_type, codename='test') self.user1.user_permissions.add(perm) self.assertIs(self.user1.has_perm('auth.test'), True) self.assertIs(self.user1.has_module_perms('auth'), True) class NewModelBackend(ModelBackend): pass Loading
docs/releases/1.8.txt +5 −1 Original line number Diff line number Diff line Loading @@ -36,7 +36,11 @@ Minor features :mod:`django.contrib.auth` ^^^^^^^^^^^^^^^^^^^^^^^^^^ * ... * Authorization backends can now raise :class:`~django.core.exceptions.PermissionDenied` in :meth:`~django.contrib.auth.models.User.has_perm` and :meth:`~django.contrib.auth.models.User.has_module_perms` to short-circuit permission checking. :mod:`django.contrib.formtools` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Loading
docs/topics/auth/customizing.txt +8 −0 Original line number Diff line number Diff line Loading @@ -180,6 +180,14 @@ The permissions given to the user will be the superset of all permissions returned by all backends. That is, Django grants a permission to a user that any one backend grants. .. versionadded:: 1.8 If a backend raises a :class:`~django.core.exceptions.PermissionDenied` exception in :meth:`~django.contrib.auth.models.User.has_perm()` or :meth:`~django.contrib.auth.models.User.has_module_perms()`, the authorization will immediately fail and Django won't check the backends that follow. The simple backend above could implement permissions for the magic admin fairly simply:: Loading