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:
parent
ebd70d4d00
commit
2e364a0aac
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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`
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
|
@ -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…
Reference in New Issue