Commit 8daec78c authored by Luke Plant's avatar Luke Plant
Browse files

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
parent 3f501198
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -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():
+3 −0
Original line number Diff line number Diff line
@@ -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))
+58 −29
Original line number Diff line number Diff line
@@ -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):
        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 []
+1 −1
Original line number Diff line number Diff line
@@ -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'
+86 −8
Original line number Diff line number Diff line
@@ -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())
Loading