Fixed #12557 - AnonymousUser should check auth backends for permissions
Thanks to hvdklauw for the idea and work on the patch. git-svn-id: http://code.djangoproject.com/svn/django/trunk@12316 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
3f50119868
commit
8daec78cfd
|
@ -26,6 +26,12 @@ def load_backend(path):
|
|||
warn("Authentication backends without a `supports_object_permissions` attribute are deprecated. Please define it in %s." % cls,
|
||||
PendingDeprecationWarning)
|
||||
cls.supports_object_permissions = False
|
||||
try:
|
||||
getattr(cls, 'supports_anonymous_user')
|
||||
except AttributeError:
|
||||
warn("Authentication backends without a `supports_anonymous_user` attribute are deprecated. Please define it in %s." % cls,
|
||||
PendingDeprecationWarning)
|
||||
cls.supports_anonymous_user = False
|
||||
return cls()
|
||||
|
||||
def get_backends():
|
||||
|
|
|
@ -12,6 +12,7 @@ class ModelBackend(object):
|
|||
Authenticates against django.contrib.auth.models.User.
|
||||
"""
|
||||
supports_object_permissions = False
|
||||
supports_anonymous_user = True
|
||||
|
||||
# TODO: Model, login attribute name and password attribute name should be
|
||||
# configurable.
|
||||
|
@ -58,6 +59,8 @@ class ModelBackend(object):
|
|||
return user_obj._group_perm_cache
|
||||
|
||||
def get_all_permissions(self, user_obj):
|
||||
if user_obj.is_anonymous():
|
||||
return set()
|
||||
if not hasattr(user_obj, '_perm_cache'):
|
||||
user_obj._perm_cache = set([u"%s.%s" % (p.content_type.app_label, p.codename) for p in user_obj.user_permissions.select_related()])
|
||||
user_obj._perm_cache.update(self.get_group_permissions(user_obj))
|
||||
|
|
|
@ -128,6 +128,49 @@ class UserManager(models.Manager):
|
|||
from random import choice
|
||||
return ''.join([choice(allowed_chars) for i in range(length)])
|
||||
|
||||
|
||||
# A few helper functions for common logic between User and AnonymousUser.
|
||||
def _user_get_all_permissions(user, obj):
|
||||
permissions = set()
|
||||
anon = user.is_anonymous()
|
||||
for backend in auth.get_backends():
|
||||
if not anon or backend.supports_anonymous_user:
|
||||
if hasattr(backend, "get_all_permissions"):
|
||||
if obj is not None:
|
||||
if backend.supports_object_permissions:
|
||||
permissions.update(
|
||||
backend.get_all_permissions(user, obj)
|
||||
)
|
||||
else:
|
||||
permissions.update(backend.get_all_permissions(user))
|
||||
return permissions
|
||||
|
||||
|
||||
def _user_has_perm(user, perm, obj):
|
||||
anon = user.is_anonymous()
|
||||
for backend in auth.get_backends():
|
||||
if not anon or backend.supports_anonymous_user:
|
||||
if hasattr(backend, "has_perm"):
|
||||
if obj is not None:
|
||||
if (backend.supports_object_permissions and
|
||||
backend.has_perm(user, perm, obj)):
|
||||
return True
|
||||
else:
|
||||
if backend.has_perm(user, perm):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _user_has_module_perms(user, app_label):
|
||||
anon = user.is_anonymous()
|
||||
for backend in auth.get_backends():
|
||||
if not anon or backend.supports_anonymous_user:
|
||||
if hasattr(backend, "has_module_perms"):
|
||||
if backend.has_module_perms(user, app_label):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class User(models.Model):
|
||||
"""
|
||||
Users within the Django authentication system are represented by this model.
|
||||
|
@ -228,17 +271,7 @@ class User(models.Model):
|
|||
return permissions
|
||||
|
||||
def get_all_permissions(self, obj=None):
|
||||
permissions = set()
|
||||
for backend in auth.get_backends():
|
||||
if hasattr(backend, "get_all_permissions"):
|
||||
if obj is not None:
|
||||
if backend.supports_object_permissions:
|
||||
permissions.update(
|
||||
backend.get_all_permissions(self, obj)
|
||||
)
|
||||
else:
|
||||
permissions.update(backend.get_all_permissions(self))
|
||||
return permissions
|
||||
return _user_get_all_permissions(self, obj)
|
||||
|
||||
def has_perm(self, perm, obj=None):
|
||||
"""
|
||||
|
@ -257,16 +290,7 @@ class User(models.Model):
|
|||
return True
|
||||
|
||||
# Otherwise we need to check the backends.
|
||||
for backend in auth.get_backends():
|
||||
if hasattr(backend, "has_perm"):
|
||||
if obj is not None:
|
||||
if (backend.supports_object_permissions and
|
||||
backend.has_perm(self, perm, obj)):
|
||||
return True
|
||||
else:
|
||||
if backend.has_perm(self, perm):
|
||||
return True
|
||||
return False
|
||||
return _user_has_perm(self, perm, obj)
|
||||
|
||||
def has_perms(self, perm_list, obj=None):
|
||||
"""
|
||||
|
@ -290,11 +314,7 @@ class User(models.Model):
|
|||
if self.is_superuser:
|
||||
return True
|
||||
|
||||
for backend in auth.get_backends():
|
||||
if hasattr(backend, "has_module_perms"):
|
||||
if backend.has_module_perms(self, app_label):
|
||||
return True
|
||||
return False
|
||||
return _user_has_module_perms(self, app_label)
|
||||
|
||||
def get_and_delete_messages(self):
|
||||
messages = []
|
||||
|
@ -396,14 +416,23 @@ class AnonymousUser(object):
|
|||
return self._user_permissions
|
||||
user_permissions = property(_get_user_permissions)
|
||||
|
||||
def get_group_permissions(self, obj=None):
|
||||
return set()
|
||||
|
||||
def get_all_permissions(self, obj=None):
|
||||
return _user_get_all_permissions(self, obj=obj)
|
||||
|
||||
def has_perm(self, perm, obj=None):
|
||||
return False
|
||||
return _user_has_perm(self, perm, obj=obj)
|
||||
|
||||
def has_perms(self, perm_list, obj=None):
|
||||
return False
|
||||
for perm in perm_list:
|
||||
if not self.has_perm(perm, obj):
|
||||
return False
|
||||
return True
|
||||
|
||||
def has_module_perms(self, module):
|
||||
return False
|
||||
return _user_has_module_perms(self, module)
|
||||
|
||||
def get_and_delete_messages(self):
|
||||
return []
|
||||
|
|
|
@ -4,7 +4,7 @@ from django.contrib.auth.tests.views \
|
|||
from django.contrib.auth.tests.forms import FORM_TESTS
|
||||
from django.contrib.auth.tests.remote_user \
|
||||
import RemoteUserTest, RemoteUserNoCreateTest, RemoteUserCustomTest
|
||||
from django.contrib.auth.tests.auth_backends import BackendTest, RowlevelBackendTest
|
||||
from django.contrib.auth.tests.auth_backends import BackendTest, RowlevelBackendTest, AnonymousUserBackendTest, NoAnonymousUserBackendTest
|
||||
from django.contrib.auth.tests.tokens import TOKEN_GENERATOR_TESTS
|
||||
|
||||
# The password for the fixture data users is 'password'
|
||||
|
|
|
@ -88,8 +88,6 @@ class BackendTest(TestCase):
|
|||
self.assertEqual(user.get_all_permissions(), set(['auth.test']))
|
||||
|
||||
|
||||
|
||||
|
||||
class TestObj(object):
|
||||
pass
|
||||
|
||||
|
@ -97,6 +95,9 @@ class TestObj(object):
|
|||
class SimpleRowlevelBackend(object):
|
||||
supports_object_permissions = True
|
||||
|
||||
# This class also supports tests for anonymous user permissions,
|
||||
# via subclasses which just set the 'supports_anonymous_user' attribute.
|
||||
|
||||
def has_perm(self, user, perm, obj=None):
|
||||
if not obj:
|
||||
return # We only support row level perms
|
||||
|
@ -104,10 +105,14 @@ class SimpleRowlevelBackend(object):
|
|||
if isinstance(obj, TestObj):
|
||||
if user.username == 'test2':
|
||||
return True
|
||||
elif isinstance(user, AnonymousUser) and perm == 'anon':
|
||||
elif user.is_anonymous() and perm == 'anon':
|
||||
# not reached due to supports_anonymous_user = False
|
||||
return True
|
||||
return False
|
||||
|
||||
def has_module_perms(self, user, app_label):
|
||||
return app_label == "app1"
|
||||
|
||||
def get_all_permissions(self, user, obj=None):
|
||||
if not obj:
|
||||
return [] # We only support row level perms
|
||||
|
@ -115,6 +120,8 @@ class SimpleRowlevelBackend(object):
|
|||
if not isinstance(obj, TestObj):
|
||||
return ['none']
|
||||
|
||||
if user.is_anonymous():
|
||||
return ['anon']
|
||||
if user.username == 'test2':
|
||||
return ['simple', 'advanced']
|
||||
else:
|
||||
|
@ -134,7 +141,9 @@ class SimpleRowlevelBackend(object):
|
|||
|
||||
|
||||
class RowlevelBackendTest(TestCase):
|
||||
|
||||
"""
|
||||
Tests for auth backend that supports object level permissions
|
||||
"""
|
||||
backend = 'django.contrib.auth.tests.auth_backends.SimpleRowlevelBackend'
|
||||
|
||||
def setUp(self):
|
||||
|
@ -142,8 +151,7 @@ class RowlevelBackendTest(TestCase):
|
|||
settings.AUTHENTICATION_BACKENDS = self.curr_auth + (self.backend,)
|
||||
self.user1 = User.objects.create_user('test', 'test@example.com', 'test')
|
||||
self.user2 = User.objects.create_user('test2', 'test2@example.com', 'test')
|
||||
self.user3 = AnonymousUser()
|
||||
self.user4 = User.objects.create_user('test4', 'test4@example.com', 'test')
|
||||
self.user3 = User.objects.create_user('test3', 'test3@example.com', 'test')
|
||||
|
||||
def tearDown(self):
|
||||
settings.AUTHENTICATION_BACKENDS = self.curr_auth
|
||||
|
@ -165,5 +173,75 @@ class RowlevelBackendTest(TestCase):
|
|||
def test_get_group_permissions(self):
|
||||
content_type=ContentType.objects.get_for_model(Group)
|
||||
group = Group.objects.create(name='test_group')
|
||||
self.user4.groups.add(group)
|
||||
self.assertEqual(self.user4.get_group_permissions(TestObj()), set(['group_perm']))
|
||||
self.user3.groups.add(group)
|
||||
self.assertEqual(self.user3.get_group_permissions(TestObj()), set(['group_perm']))
|
||||
|
||||
|
||||
class AnonymousUserBackend(SimpleRowlevelBackend):
|
||||
|
||||
supports_anonymous_user = True
|
||||
|
||||
|
||||
class NoAnonymousUserBackend(SimpleRowlevelBackend):
|
||||
|
||||
supports_anonymous_user = False
|
||||
|
||||
|
||||
class AnonymousUserBackendTest(TestCase):
|
||||
"""
|
||||
Tests for AnonymousUser delegating to backend if it has 'supports_anonymous_user' = True
|
||||
"""
|
||||
|
||||
backend = 'django.contrib.auth.tests.auth_backends.AnonymousUserBackend'
|
||||
|
||||
def setUp(self):
|
||||
self.curr_auth = settings.AUTHENTICATION_BACKENDS
|
||||
settings.AUTHENTICATION_BACKENDS = (self.backend,)
|
||||
self.user1 = AnonymousUser()
|
||||
|
||||
def tearDown(self):
|
||||
settings.AUTHENTICATION_BACKENDS = self.curr_auth
|
||||
|
||||
def test_has_perm(self):
|
||||
self.assertEqual(self.user1.has_perm('perm', TestObj()), False)
|
||||
self.assertEqual(self.user1.has_perm('anon', TestObj()), True)
|
||||
|
||||
def test_has_perms(self):
|
||||
self.assertEqual(self.user1.has_perms(['anon'], TestObj()), True)
|
||||
self.assertEqual(self.user1.has_perms(['anon', 'perm'], TestObj()), False)
|
||||
|
||||
def test_has_module_perms(self):
|
||||
self.assertEqual(self.user1.has_module_perms("app1"), True)
|
||||
self.assertEqual(self.user1.has_module_perms("app2"), False)
|
||||
|
||||
def test_get_all_permissions(self):
|
||||
self.assertEqual(self.user1.get_all_permissions(TestObj()), set(['anon']))
|
||||
|
||||
|
||||
class NoAnonymousUserBackendTest(TestCase):
|
||||
"""
|
||||
Tests that AnonymousUser does not delegate to backend if it has 'supports_anonymous_user' = False
|
||||
"""
|
||||
backend = 'django.contrib.auth.tests.auth_backends.NoAnonymousUserBackend'
|
||||
|
||||
def setUp(self):
|
||||
self.curr_auth = settings.AUTHENTICATION_BACKENDS
|
||||
settings.AUTHENTICATION_BACKENDS = self.curr_auth + (self.backend,)
|
||||
self.user1 = AnonymousUser()
|
||||
|
||||
def tearDown(self):
|
||||
settings.AUTHENTICATION_BACKENDS = self.curr_auth
|
||||
|
||||
def test_has_perm(self):
|
||||
self.assertEqual(self.user1.has_perm('perm', TestObj()), False)
|
||||
self.assertEqual(self.user1.has_perm('anon', TestObj()), False)
|
||||
|
||||
def test_has_perms(self):
|
||||
self.assertEqual(self.user1.has_perms(['anon'], TestObj()), False)
|
||||
|
||||
def test_has_module_perms(self):
|
||||
self.assertEqual(self.user1.has_module_perms("app1"), False)
|
||||
self.assertEqual(self.user1.has_module_perms("app2"), False)
|
||||
|
||||
def test_get_all_permissions(self):
|
||||
self.assertEqual(self.user1.get_all_permissions(TestObj()), set())
|
||||
|
|
|
@ -13,9 +13,9 @@ their deprecation, as per the :ref:`Django deprecation policy
|
|||
hooking up admin URLs. This has been deprecated since the 1.1
|
||||
release.
|
||||
|
||||
* Authentication backends need to define the boolean attribute
|
||||
``supports_object_permissions``. The old backend style is deprecated
|
||||
since the 1.2 release.
|
||||
* Authentication backends need to define the boolean attributes
|
||||
``supports_object_permissions`` and ``supports_anonymous_user``.
|
||||
The old backend style is deprecated since the 1.2 release.
|
||||
|
||||
* 1.4
|
||||
* ``CsrfResponseMiddleware``. This has been deprecated since the 1.2
|
||||
|
@ -56,6 +56,11 @@ their deprecation, as per the :ref:`Django deprecation policy
|
|||
permission checking. The ``supports_object_permissions`` variable
|
||||
is not checked any longer and can be removed.
|
||||
|
||||
* Authentication backends need to support the ``AnonymousUser``
|
||||
being passed to all methods dealing with permissions.
|
||||
The ``supports_anonymous_user`` variable is not checked any
|
||||
longer and can be removed.
|
||||
|
||||
* The ability to specify a callable template loader rather than a
|
||||
``Loader`` class will be removed, as will the ``load_template_source``
|
||||
functions that are included with the built in template loaders for
|
||||
|
|
|
@ -558,3 +558,13 @@ Although there is no implementation of this in core, a custom authentication
|
|||
backend can provide this implementation and it will be used by
|
||||
:class:`django.contrib.auth.models.User`. See the :ref:`authentication docs
|
||||
<topics-auth>` for more information.
|
||||
|
||||
Permissions for anonymous users
|
||||
-------------------------------
|
||||
|
||||
If you provide a custom auth backend with ``supports_anonymous_user`` set to
|
||||
``True``, AnonymousUser will check the backend for permissions, just like
|
||||
User already did. This is useful for centralizing permission handling - apps
|
||||
can always delegate the question of whether something is allowed or not to
|
||||
the authorization/authentication backend. See the :ref:`authentication
|
||||
docs <topics-auth>` for more details.
|
||||
|
|
|
@ -1559,6 +1559,38 @@ the ``auth_permission`` table most of the time.
|
|||
|
||||
.. _django/contrib/auth/backends.py: http://code.djangoproject.com/browser/django/trunk/django/contrib/auth/backends.py
|
||||
|
||||
Authorization for anonymous users
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. versionchanged:: 1.2
|
||||
|
||||
An anonymous user is one that is not authenticated i.e. they have provided no
|
||||
valid authentication details. However, that does not necessarily mean they are
|
||||
not authorized to do anything. At the most basic level, most Web sites
|
||||
authorize anonymous users to browse most of the site, and many allow anonymous
|
||||
posting of comments etc.
|
||||
|
||||
Django's permission framework does not have a place to store permissions for
|
||||
anonymous users. However, it has a foundation that allows custom authentication
|
||||
backends to specify authorization for anonymous users. This is especially useful
|
||||
for the authors of re-usable apps, who can delegate all questions of authorization
|
||||
to the auth backend, rather than needing settings, for example, to control
|
||||
anonymous access.
|
||||
|
||||
To enable this in your own backend, you must set the class attribute
|
||||
``supports_anonymous_user`` to ``True``. (This precaution is to maintain
|
||||
compatibility with backends that assume that all user objects are actual
|
||||
instances of the :class:`django.contrib.auth.models.User` class). With this
|
||||
in place, :class:`django.contrib.auth.models.AnonymousUser` will delegate all
|
||||
the relevant permission methods to the authentication backends.
|
||||
|
||||
A nonexistent ``supports_anonymous_user`` attribute will raise a hidden
|
||||
``PendingDeprecationWarning`` if used in Django 1.2. In Django 1.3, this
|
||||
warning will be upgraded to a ``DeprecationWarning``, which will be displayed
|
||||
loudly. Additionally ``supports_anonymous_user`` will be set to ``False``.
|
||||
Django 1.4 will assume that every backend supports anonymous users being
|
||||
passed to the authorization methods.
|
||||
|
||||
Handling object permissions
|
||||
---------------------------
|
||||
|
||||
|
|
Loading…
Reference in New Issue