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 __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.core.exceptions import PermissionDenied
|
||||||
from django.core.mail import send_mail
|
from django.core.mail import send_mail
|
||||||
from django.core import validators
|
from django.core import validators
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
@ -267,18 +268,32 @@ def _user_get_all_permissions(user, obj):
|
||||||
|
|
||||||
|
|
||||||
def _user_has_perm(user, perm, obj):
|
def _user_has_perm(user, perm, obj):
|
||||||
|
"""
|
||||||
|
A backend can raise `PermissionDenied` to short-circuit permission checking.
|
||||||
|
"""
|
||||||
for backend in auth.get_backends():
|
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):
|
if backend.has_perm(user, perm, obj):
|
||||||
return True
|
return True
|
||||||
|
except PermissionDenied:
|
||||||
|
return False
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def _user_has_module_perms(user, app_label):
|
def _user_has_module_perms(user, app_label):
|
||||||
|
"""
|
||||||
|
A backend can raise `PermissionDenied` to short-circuit permission checking.
|
||||||
|
"""
|
||||||
for backend in auth.get_backends():
|
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):
|
if backend.has_module_perms(user, app_label):
|
||||||
return True
|
return True
|
||||||
|
except PermissionDenied:
|
||||||
|
return False
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -398,7 +398,7 @@ class InActiveUserBackendTest(TestCase):
|
||||||
|
|
||||||
class PermissionDeniedBackend(object):
|
class PermissionDeniedBackend(object):
|
||||||
"""
|
"""
|
||||||
Always raises PermissionDenied.
|
Always raises PermissionDenied in `authenticate`, `has_perm` and `has_module_perms`.
|
||||||
"""
|
"""
|
||||||
supports_object_permissions = True
|
supports_object_permissions = True
|
||||||
supports_anonymous_user = True
|
supports_anonymous_user = True
|
||||||
|
@ -407,6 +407,12 @@ class PermissionDeniedBackend(object):
|
||||||
def authenticate(self, username=None, password=None):
|
def authenticate(self, username=None, password=None):
|
||||||
raise PermissionDenied
|
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
|
@skipIfCustomUser
|
||||||
class PermissionDeniedBackendTest(TestCase):
|
class PermissionDeniedBackendTest(TestCase):
|
||||||
|
@ -430,6 +436,26 @@ class PermissionDeniedBackendTest(TestCase):
|
||||||
def test_authenticates(self):
|
def test_authenticates(self):
|
||||||
self.assertEqual(authenticate(username='test', password='test'), self.user1)
|
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):
|
class NewModelBackend(ModelBackend):
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -36,7 +36,11 @@ Minor features
|
||||||
:mod:`django.contrib.auth`
|
: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`
|
: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
|
returned by all backends. That is, Django grants a permission to a user that
|
||||||
any one backend grants.
|
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
|
The simple backend above could implement permissions for the magic admin
|
||||||
fairly simply::
|
fairly simply::
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue