Commit 8c427448 authored by Mateusz Haligowski's avatar Mateusz Haligowski Committed by Anssi Kääriäinen
Browse files

Fixed #15915 -- Cleaned handling of duplicate permission codenames

Previously, a duplicate model, codename for permission would lead to
database integrity error. Cleaned the implementation so that this case
now raises an CommandError instead.
parent 218abcc9
Loading
Loading
Loading
Loading
+36 −5
Original line number Diff line number Diff line
@@ -9,6 +9,7 @@ import unicodedata

from django.contrib.auth import models as auth_app, get_user_model
from django.core import exceptions
from django.core.management.base import CommandError
from django.db.models import get_models, signals
from django.utils import six
from django.utils.six.moves import input
@@ -18,13 +19,43 @@ def _get_permission_codename(action, opts):
    return '%s_%s' % (action, opts.object_name.lower())


def _get_all_permissions(opts):
    "Returns (codename, name) for all permissions in the given opts."
def _get_all_permissions(opts, ctype):
    """
    Returns (codename, name) for all permissions in the given opts.
    """
    builtin = _get_builtin_permissions(opts)
    custom = list(opts.permissions)
    _check_permission_clashing(custom, builtin, ctype)
    return builtin + custom

def _get_builtin_permissions(opts):
    """
    Returns (codename, name) for all autogenerated permissions.
    """
    perms = []
    for action in ('add', 'change', 'delete'):
        perms.append((_get_permission_codename(action, opts), 'Can %s %s' % (action, opts.verbose_name_raw)))
    return perms + list(opts.permissions)
        perms.append((_get_permission_codename(action, opts),
            'Can %s %s' % (action, opts.verbose_name_raw)))
    return perms

def _check_permission_clashing(custom, builtin, ctype):
    """
    Check that permissions for a model do not clash. Raises CommandError if
    there are duplicate permissions.
    """
    pool = set()
    builtin_codenames = set(p[0] for p in builtin)
    for codename, _name in custom:
        if codename in pool:
            raise CommandError(
                "The permission codename '%s' is duplicated for model '%s.%s'." %
                (codename, ctype.app_label, ctype.model_class().__name__))
        elif codename in builtin_codenames:
            raise CommandError(
                "The permission codename '%s' clashes with a builtin permission "
                "for model '%s.%s'." %
                (codename, ctype.app_label, ctype.model_class().__name__))
        pool.add(codename)

def create_permissions(app, created_models, verbosity, **kwargs):
    from django.contrib.contenttypes.models import ContentType
@@ -39,7 +70,7 @@ def create_permissions(app, created_models, verbosity, **kwargs):
    for klass in app_models:
        ctype = ContentType.objects.get_for_model(klass)
        ctypes.add(ctype)
        for perm in _get_all_permissions(klass._meta):
        for perm in _get_all_permissions(klass._meta, ctype):
            searched_perms.append((ctype, perm))

    # Find all the Permissions that have a context_type for a model we're
+41 −0
Original line number Diff line number Diff line
@@ -2,6 +2,7 @@ from __future__ import unicode_literals
from datetime import date

from django.contrib.auth import models, management
from django.contrib.auth.management import create_permissions
from django.contrib.auth.management.commands import changepassword
from django.contrib.auth.models import User
from django.contrib.auth.tests import CustomUser
@@ -167,3 +168,43 @@ class CreatesuperuserManagementCommandTestCase(TestCase):
            )

        self.assertEqual(CustomUser.objects.count(), 0)


class PermissionDuplicationTestCase(TestCase):

    def setUp(self):
        self._original_user_permission = models.User._meta.permissions

    def tearUp(self):
        models.User._meta.permissions = self._original_user_permissions

    def test_duplicated_permissions(self):
        """
        Test that we show proper error message if we are trying to create
        duplicate permissions.
        """
        # check duplicated default permission
        models.Permission._meta.permissions = [
           ('change_permission', 'Can edit permission (duplicate)')]
        self.assertRaisesRegexp(CommandError,
            "The permission codename 'change_permission' clashes with a "
            "builtin permission for model 'auth.Permission'.",
            create_permissions, models, [], verbosity=0)

        # check duplicated custom permissions
        models.Permission._meta.permissions = [
            ('my_custom_permission', 'Some permission'),
            ('other_one', 'Some other permission'),
            ('my_custom_permission', 'Some permission with duplicate permission code'),
        ]
        self.assertRaisesRegexp(CommandError,
            "The permission codename 'my_custom_permission' is duplicated for model "
            "'auth.Permission'.",
            create_permissions, models, [], verbosity=0)

        # should not raise anything
        models.Permission._meta.permissions = [
            ('my_custom_permission', 'Some permission'),
            ('other_one', 'Some other permission'),
        ]
        create_permissions(models, [], verbosity=0)