Fixed #15716 - Authentication backends can short-circuit authorization.

Authorization backends can now raise PermissionDenied in "has_perm"
and "has_module_perms" to short-circuit authorization process.
This commit is contained in:
Jorge C. Leitão 2014-05-08 22:06:46 +02:00 committed by Tim Graham
parent ebd70d4d00
commit 2e364a0aac
4 changed files with 57 additions and 4 deletions

View File

@ -1,5 +1,6 @@
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
@ -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

View File

@ -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
@ -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):
@ -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

View File

@ -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`
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -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::