From b64a1e58f9e6c3dd93e480703906291ea4c43724 Mon Sep 17 00:00:00 2001 From: Benjamin Dauvergne Date: Wed, 15 Apr 2020 11:29:21 +0200 Subject: [PATCH 3/4] data_transfer: validate models before updating/creating them (#41342) To prevent collision between roles when altering their name and their slug is already unique. Ex.: R1(slug=a, name=A) -import-> R1(slug=a, name=B) R2(slug=b, name=B) --- src/authentic2/data_transfer.py | 29 ++++++++++++++++++++++++++++- tests/test_data_transfer.py | 17 +++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/src/authentic2/data_transfer.py b/src/authentic2/data_transfer.py index dfbba927..00509f0e 100644 --- a/src/authentic2/data_transfer.py +++ b/src/authentic2/data_transfer.py @@ -16,14 +16,16 @@ from functools import wraps -from django.core.exceptions import ValidationError +from django.core.exceptions import ValidationError, NON_FIELD_ERRORS from django.contrib.contenttypes.models import ContentType from django.utils.translation import ugettext_lazy as _ +from django.utils.text import format_lazy from django_rbac.models import Operation from django_rbac.utils import ( get_ou_model, get_role_model, get_role_parenting_model, get_permission_model) +from authentic2.decorators import errorcollector from authentic2.a2_rbac.models import RoleAttribute from authentic2.utils.lazy import lazy_join @@ -31,6 +33,31 @@ from authentic2.utils.lazy import lazy_join def update_model(obj, d): for attr, value in d.items(): setattr(obj, attr, value) + errors = {} + with errorcollector(errors): + if hasattr(obj, 'validate'): + obj.validate() + + with errorcollector(errors): + if hasattr(obj, 'validate_unique'): + obj.validate_unique() + if errors: + errorlist = [] + for key, messages in list(errors.items()): + if key == NON_FIELD_ERRORS: + errorlist.extend(messages) + else: + value = getattr(obj, key) + + def error_list(messages): + for message in messages: + if isinstance(message, ValidationError): + yield message.message + else: + yield message + for message in error_list(messages): + errorlist.append(format_lazy(u'{}="{}": {}', obj.__class__._meta.get_field(key).verbose_name, value, message)) + raise ValidationError(errorlist) obj.save() diff --git a/tests/test_data_transfer.py b/tests/test_data_transfer.py index 37d6b1e1..07a0816c 100644 --- a/tests/test_data_transfer.py +++ b/tests/test_data_transfer.py @@ -542,3 +542,20 @@ def test_export_site(db): d = export_site(ExportContext(export_ous=False)) assert 'ous' not in d + +def test_role_validate_unique(db): + ou = OU.objects.create(name='ou', slug='ou') + Role.objects.create(name='role1', slug='role1', ou=ou) + Role.objects.create(name='role2', slug='role2', ou=ou) + + data = { + 'roles': [ + { + 'name': 'role1', + 'slug': 'role2', + 'ou': {'slug': 'ou'}, + } + ] + } + with pytest.raises(ValidationError, match=r'Role "role1": name="role1": Name already used'): + import_site(data) -- 2.24.0