Commit 9bf652df authored by Jannis Leidel's avatar Jannis Leidel
Browse files

Fixed #11010 - Add a foundation for object permissions to authentication...

Fixed #11010 - Add a foundation for object permissions to authentication backends. Thanks to Florian Apolloner for writing the initial patch.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@11807 bcc190cf-cafb-0310-a4f2-bffc1f526a37
parent 2c2f5aee
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
import datetime
from warnings import warn
from django.core.exceptions import ImproperlyConfigured
from django.utils.importlib import import_module

@@ -19,6 +20,12 @@ def load_backend(path):
        cls = getattr(mod, attr)
    except AttributeError:
        raise ImproperlyConfigured, 'Module "%s" does not define a "%s" authentication backend' % (module, attr)
    try:
        getattr(cls, 'supports_object_permissions')
    except AttributeError:
        warn("Authentication backends without a `supports_object_permissions` attribute are deprecated. Please define it in %s." % cls,
             PendingDeprecationWarning)
        cls.supports_object_permissions = False
    return cls()

def get_backends():
+2 −0
Original line number Diff line number Diff line
@@ -11,6 +11,8 @@ class ModelBackend(object):
    """
    Authenticates against django.contrib.auth.models.User.
    """
    supports_object_permissions = False

    # TODO: Model, login attribute name and password attribute name should be
    # configurable.
    def authenticate(self, username=None, password=None):
+41 −16
Original line number Diff line number Diff line
@@ -121,7 +121,8 @@ class UserManager(models.Manager):
        return ''.join([choice(allowed_chars) for i in range(length)])

class User(models.Model):
    """Users within the Django authentication system are represented by this model.
    """
    Users within the Django authentication system are represented by this model.

    Username and password are required. Other fields are optional.
    """
@@ -151,11 +152,16 @@ class User(models.Model):
        return "/users/%s/" % urllib.quote(smart_str(self.username))

    def is_anonymous(self):
        "Always returns False. This is a way of comparing User objects to anonymous users."
        """
        Always returns False. This is a way of comparing User objects to
        anonymous users.
        """
        return False

    def is_authenticated(self):
        """Always return True. This is a way to tell if the user has been authenticated in templates.
        """
        Always return True. This is a way to tell if the user has been
        authenticated in templates.
        """
        return True

@@ -194,30 +200,41 @@ class User(models.Model):
    def has_usable_password(self):
        return self.password != UNUSABLE_PASSWORD

    def get_group_permissions(self):
    def get_group_permissions(self, obj=None):
        """
        Returns a list of permission strings that this user has through
        his/her groups. This method queries all available auth backends.
        If an object is passed in, only permissions matching this object
        are returned.
        """
        permissions = set()
        for backend in auth.get_backends():
            if hasattr(backend, "get_group_permissions"):
                permissions.update(backend.get_group_permissions(self))
                if obj is not None and backend.supports_object_permissions:
                    group_permissions = backend.get_group_permissions(self, obj)
                else:
                    group_permissions = backend.get_group_permissions(self)
                permissions.update(group_permissions)
        return permissions

    def get_all_permissions(self):
    def get_all_permissions(self, obj=None):
        permissions = set()
        for backend in auth.get_backends():
            if hasattr(backend, "get_all_permissions"):
                permissions.update(backend.get_all_permissions(self))
                if obj is not None and backend.supports_object_permissions:
                    all_permissions = backend.get_all_permissions(self, obj)
                else:
                    all_permissions = backend.get_all_permissions(self)
                permissions.update(all_permissions)
        return permissions

    def has_perm(self, perm):
    def has_perm(self, perm, obj=None):
        """
        Returns True if the user has the specified permission. This method
        queries all available auth backends, but returns immediately if any
        backend returns True. Thus, a user who has permission from a single
        auth backend is assumed to have permission in general.
        auth backend is assumed to have permission in general. If an object
        is provided, permissions for this specific object are checked.
        """
        # Inactive users have no permissions.
        if not self.is_active:
@@ -230,14 +247,22 @@ class User(models.Model):
        # Otherwise we need to check the backends.
        for backend in auth.get_backends():
            if hasattr(backend, "has_perm"):
                if obj is not None and backend.supports_object_permissions:
                    if backend.has_perm(self, perm, obj):
                        return True
                else:
                    if backend.has_perm(self, perm):
                        return True
        return False

    def has_perms(self, perm_list):
        """Returns True if the user has each of the specified permissions."""
    def has_perms(self, perm_list, obj=None):
        """
        Returns True if the user has each of the specified permissions.
        If object is passed, it checks if the user has all required perms
        for this object.
        """
        for perm in perm_list:
            if not self.has_perm(perm):
            if not self.has_perm(perm, obj):
                return False
        return True

@@ -358,10 +383,10 @@ class AnonymousUser(object):
        return self._user_permissions
    user_permissions = property(_get_user_permissions)

    def has_perm(self, perm):
    def has_perm(self, perm, obj=None):
        return False

    def has_perms(self, perm_list):
    def has_perms(self, perm_list, obj=None):
        return False

    def has_module_perms(self, module):
+1 −0
Original line number Diff line number Diff line
@@ -4,6 +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.tokens import TOKEN_GENERATOR_TESTS

# The password for the fixture data users is 'password'
+149 −0
Original line number Diff line number Diff line
from django.conf import settings
from django.contrib.auth.models import User, Group, Permission, AnonymousUser
from django.contrib.contenttypes.models import ContentType
from django.test import TestCase


class BackendTest(TestCase):

    backend = 'django.contrib.auth.backends.ModelBackend'

    def setUp(self):
        self.curr_auth = settings.AUTHENTICATION_BACKENDS
        settings.AUTHENTICATION_BACKENDS = (self.backend,)
        User.objects.create_user('test', 'test@example.com', 'test')

    def tearDown(self):
        settings.AUTHENTICATION_BACKENDS = self.curr_auth

    def test_has_perm(self):
        user = User.objects.get(username='test')
        self.assertEqual(user.has_perm('auth.test'), False)
        user.is_staff = True
        user.save()
        self.assertEqual(user.has_perm('auth.test'), False)
        user.is_superuser = True
        user.save()
        self.assertEqual(user.has_perm('auth.test'), True)
        user.is_staff = False
        user.is_superuser = False
        user.save()
        self.assertEqual(user.has_perm('auth.test'), False)

    def test_custom_perms(self):
        user = User.objects.get(username='test')
        content_type=ContentType.objects.get_for_model(Group)
        perm = Permission.objects.create(name='test', content_type=content_type, codename='test')
        user.user_permissions.add(perm)
        user.save()

        # reloading user to purge the _perm_cache
        user = User.objects.get(username='test')
        self.assertEqual(user.get_all_permissions() == set([u'auth.test']), True)
        self.assertEqual(user.get_group_permissions(), set([]))
        self.assertEqual(user.has_module_perms('Group'), False)
        self.assertEqual(user.has_module_perms('auth'), True)
        perm = Permission.objects.create(name='test2', content_type=content_type, codename='test2')
        user.user_permissions.add(perm)
        user.save()
        perm = Permission.objects.create(name='test3', content_type=content_type, codename='test3')
        user.user_permissions.add(perm)
        user.save()
        user = User.objects.get(username='test')
        self.assertEqual(user.get_all_permissions(), set([u'auth.test2', u'auth.test', u'auth.test3']))
        self.assertEqual(user.has_perm('test'), False)
        self.assertEqual(user.has_perm('auth.test'), True)
        self.assertEqual(user.has_perms(['auth.test2', 'auth.test3']), True)
        perm = Permission.objects.create(name='test_group', content_type=content_type, codename='test_group')
        group = Group.objects.create(name='test_group')
        group.permissions.add(perm)
        group.save()
        user.groups.add(group)
        user = User.objects.get(username='test')
        exp = set([u'auth.test2', u'auth.test', u'auth.test3', u'auth.test_group'])
        self.assertEqual(user.get_all_permissions(), exp)
        self.assertEqual(user.get_group_permissions(), set([u'auth.test_group']))
        self.assertEqual(user.has_perms(['auth.test3', 'auth.test_group']), True)

        user = AnonymousUser()
        self.assertEqual(user.has_perm('test'), False)
        self.assertEqual(user.has_perms(['auth.test2', 'auth.test3']), False)


class TestObj(object):
    pass


class SimpleRowlevelBackend(object):
    supports_object_permissions = True

    def has_perm(self, user, perm, obj=None):
        if not obj:
            return # We only support row level perms

        if isinstance(obj, TestObj):
            if user.username == 'test2':
                return True
            elif isinstance(user, AnonymousUser) and perm == 'anon':
                return True
        return False

    def get_all_permissions(self, user, obj=None):
        if not obj:
            return [] # We only support row level perms

        if not isinstance(obj, TestObj):
            return ['none']

        if user.username == 'test2':
            return ['simple', 'advanced']
        else:
            return ['simple']

    def get_group_permissions(self, user, obj=None):
        if not obj:
            return # We only support row level perms

        if not isinstance(obj, TestObj):
            return ['none']

        if 'test_group' in [group.name for group in user.groups.all()]:
            return ['group_perm']
        else:
            return ['none']


class RowlevelBackendTest(TestCase):

    backend = 'django.contrib.auth.tests.auth_backends.SimpleRowlevelBackend'

    def setUp(self):
        self.curr_auth = settings.AUTHENTICATION_BACKENDS
        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')

    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.user2.has_perm('perm', TestObj()), True)
        self.assertEqual(self.user2.has_perm('perm'), False)
        self.assertEqual(self.user2.has_perms(['simple', 'advanced'], TestObj()), True)
        self.assertEqual(self.user3.has_perm('perm', TestObj()), False)
        self.assertEqual(self.user3.has_perm('anon', TestObj()), False)
        self.assertEqual(self.user3.has_perms(['simple', 'advanced'], TestObj()), False)

    def test_get_all_permissions(self):
        self.assertEqual(self.user1.get_all_permissions(TestObj()), set(['simple']))
        self.assertEqual(self.user2.get_all_permissions(TestObj()), set(['simple', 'advanced']))
        self.assertEqual(self.user2.get_all_permissions(), set([]))

    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']))
Loading