From e3cf67c57aa32266e32152880c3216e72c9bda6e Mon Sep 17 00:00:00 2001 From: Emmanuel Cazenave Date: Mon, 9 Apr 2018 14:10:21 +0200 Subject: [PATCH 2/2] create 'import_site' and 'export_site' commands (#16514) --- src/authentic2/a2_rbac/models.py | 36 + src/authentic2/data_transfer.py | 281 +++++ src/authentic2/management/commands/export_site.py | 24 + src/authentic2/management/commands/import_site.py | 71 ++ src/authentic2/utils.py | 5 + src/django_rbac/models.py | 11 + tests/data/export_site.json | 1361 +++++++++++++++++++++ tests/test_a2_rbac.py | 158 ++- tests/test_data_transfer.py | 471 +++++++ tests/test_import_export_site_cmd.py | 132 ++ 10 files changed, 2549 insertions(+), 1 deletion(-) create mode 100644 src/authentic2/data_transfer.py create mode 100644 src/authentic2/management/commands/export_site.py create mode 100644 src/authentic2/management/commands/import_site.py create mode 100644 tests/data/export_site.json create mode 100644 tests/test_data_transfer.py create mode 100644 tests/test_import_export_site_cmd.py diff --git a/src/authentic2/a2_rbac/models.py b/src/authentic2/a2_rbac/models.py index 75e47df8..4f8a5aaa 100644 --- a/src/authentic2/a2_rbac/models.py +++ b/src/authentic2/a2_rbac/models.py @@ -92,6 +92,15 @@ class OrganizationalUnit(OrganizationalUnitAbstractBase): def cached(cls): return cls.objects.all() + def export_json(self): + return { + 'uuid': self.uuid, 'slug': self.slug, 'name': self.name, + 'description': self.description, 'default': self.default, + 'email_is_unique': self.email_is_unique, + 'username_is_unique': self.username_is_unique, + 'validate_emails': self.validate_emails + } + OrganizationalUnit._meta.natural_key = [['uuid'], ['slug'], ['name']] @@ -213,6 +222,29 @@ class Role(RoleAbstractBase): 'ou__slug': self.ou.slug if self.ou else None, } + def export_json(self, attributes=False, parents=False, permissions=False): + d = { + 'uuid': self.uuid, 'slug': self.slug, 'name': self.name, + 'description': self.description, 'external_id': self.external_id, + 'ou': self.ou and self.ou.natural_key_json(), + 'service': self.service and self.service.natural_key_json() + } + + if attributes: + for attribute in self.attributes.all(): + d.setdefault('attributes', []).append(attribute.to_json()) + + if parents: + RoleParenting = rbac_utils.get_role_parenting_model() + for parenting in RoleParenting.objects.filter(child_id=self.id, direct=True): + d.setdefault('parents', []).append(parenting.parent.natural_key_json()) + + if permissions: + for perm in self.permissions.all(): + d.setdefault('permissions', []).append(perm.export_json()) + + return d + Role._meta.natural_key = [ ['uuid'], ['slug', 'ou'], ['name', 'ou'], ['slug', 'service'], ['name', 'service'] @@ -250,6 +282,10 @@ class RoleAttribute(models.Model): ('role', 'name', 'kind', 'value'), ) + def to_json(self): + return {'name': self.name, 'kind': self.kind, 'value': self.value} + + GenericRelation(Permission, content_type_field='target_ct', object_id_field='target_id').contribute_to_class(ContentType, 'admin_perms') diff --git a/src/authentic2/data_transfer.py b/src/authentic2/data_transfer.py new file mode 100644 index 00000000..346d2803 --- /dev/null +++ b/src/authentic2/data_transfer.py @@ -0,0 +1,281 @@ +from django.contrib.contenttypes.models import ContentType + +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.a2_rbac.models import RoleAttribute +from authentic2.utils import update_model + + +def export_site(): + return { + 'roles': export_roles(get_role_model().objects.all()), + 'ous': export_ou(get_ou_model().objects.all()) + } + + +def export_ou(ou_query_set): + return [ou.export_json() for ou in ou_query_set] + + +def export_roles(role_queryset): + """ Serialize roles in role_queryset + """ + return [ + role.export_json(attributes=True, parents=True, permissions=True) + for role in role_queryset + ] + + +def search_ou(ou_d): + try: + OU = get_ou_model() + return OU.objects.get_by_natural_key_json(ou_d) + except OU.DoesNotExist: + return None + + +def search_role(role_d): + Role = get_role_model() + try: + Role = get_role_model() + return Role.objects.get_by_natural_key_json(role_d) + except Role.DoesNotExist: + return None + + +class ImportContext(object): + """ Holds information on how to perform the import. + + ou_delete_orphans: if True any existing ou that is not found in the export will + be deleted + + role_delete_orphans: if True any existing role that is not found in the export will + be deleted + + + role_attributes_update: for each role in the import data, + attributes will deleted and re-created + + + role_parentings_update: for each role in the import data, + parentings will deleted and re-created + + role_permissions_update: for each role in the import data, + permissions will deleted and re-created + """ + + def __init__( + self, role_delete_orphans=False, role_parentings_update=True, + role_permissions_update=True, role_attributes_update=True, + ou_delete_orphans=False): + self.role_delete_orphans = role_delete_orphans + self.ou_delete_orphans = ou_delete_orphans + self.role_parentings_update = role_parentings_update + self.role_permissions_update = role_permissions_update + self.role_attributes_update = role_attributes_update + + +class DataImportError(Exception): + pass + + +class RoleDeserializer(object): + + def __init__(self, d, import_context): + self._import_context = import_context + self._obj = None + self._parents = None + self._attributes = None + self._permissions = None + + self._role_d = dict() + for key, value in d.items(): + if key == 'parents': + self._parents = value + elif key == 'attributes': + self._attributes = value + elif key == 'permissions': + self._permissions = value + else: + self._role_d[key] = value + + def deserialize(self): + ou_d = self._role_d['ou'] + has_ou = bool(ou_d) + ou = None if not has_ou else search_ou(ou_d) + if has_ou and not ou: + raise DataImportError( + "Can't import role because missing Organizational Unit : " + "%s" % ou_d) + + kwargs = self._role_d.copy() + del kwargs['ou'] + del kwargs['service'] + if has_ou: + kwargs['ou'] = ou + + obj = search_role(self._role_d) + if obj: # Role already exist + self._obj = obj + status = 'updated' + update_model(self._obj, kwargs) + else: # Create role + self._obj = get_role_model().objects.create(**kwargs) + status = 'created' + + # Ensure admin role is created. + # Absoluteley necessary to create + # parentings relationship later on, + # since we don't deserialize technical role. + self._obj.get_admin_role() + return self._obj, status + + def attributes(self): + """ Update attributes (delete everything then create) + """ + created, deleted = [], [] + for attr in self._obj.attributes.all(): + attr.delete() + deleted.append(attr) + # Create attributes + if self._attributes: + for attr_dict in self._attributes: + attr_dict['role'] = self._obj + created.append(RoleAttribute.objects.create(**attr_dict)) + + return created, deleted + + def parentings(self): + """ Update parentings (delete everything then create) + """ + created, deleted = [], [] + Parenting = get_role_parenting_model() + for parenting in Parenting.objects.filter(child=self._obj, direct=True): + parenting.delete() + deleted.append(parenting) + + if self._parents: + for parent_d in self._parents: + parent = search_role(parent_d) + if not parent: + raise DataImportError("Could not find role : %s" % parent_d) + created.append(Parenting.objects.create( + child=self._obj, direct=True, parent=parent)) + + return created, deleted + + def permissions(self): + """ Update permissions (delete everything then create) + """ + created, deleted = [], [] + for perm in self._obj.permissions.all(): + perm.delete() + deleted.append(perm) + self._obj.permissions.clear() + if self._permissions: + for perm in self._permissions: + op = Operation.objects.get_by_natural_key_json(perm['operation']) + ou = get_ou_model().objects.get_by_natural_key_json( + perm['ou']) if perm['ou'] else None + ct = ContentType.objects.get_by_natural_key_json(perm['target_ct']) + target = ct.model_class().objects.get_by_natural_key_json(perm['target']) + perm = get_permission_model().objects.create( + operation=op, ou=ou, target_ct=ct, target_id=target.pk) + self._obj.permissions.add(perm) + created.append(perm) + + return created, deleted + + +class ImportResult(object): + + def __init__(self): + self.roles = {'created': [], 'updated': []} + self.ous = {'created': [], 'updated': []} + self.attributes = {'created': [], 'deleted': []} + self.parentings = {'created': [], 'deleted': []} + self.permissions = {'created': [], 'deleted': []} + + def update_roles(self, role, d_status): + self.roles[d_status].append(role) + + def update_ous(self, ou, status): + self.ous[status].append(ou) + + def _bulk_update(self, attrname, created, deleted): + attr = getattr(self, attrname) + attr['created'].extend(created) + attr['deleted'].extend(deleted) + + def update_attributes(self, created, deleted): + self._bulk_update('attributes', created, deleted) + + def update_parentings(self, created, deleted): + self._bulk_update('parentings', created, deleted) + + def update_permissions(self, created, deleted): + self._bulk_update('permissions', created, deleted) + + def to_str(self, verbose=False): + res = "" + for attr in ('roles', 'ous', 'parentings', 'permissions', 'attributes'): + data = getattr(self, attr) + for status in ('created', 'updated', 'deleted'): + if status in data: + s_data = data[status] + res += "%s %s %s\n" % (len(s_data), attr, status) + return res + + +def import_ou(ou_d): + OU = get_ou_model() + # ou = search_ou([ou_d['slug']]) + ou = search_ou(ou_d) + if ou is None: + ou = OU.objects.create(**ou_d) + status = 'created' + else: + update_model(ou, ou_d) + status = 'updated' + # Ensure admin role is created + ou.get_admin_role() + return ou, status + + +def import_site(json_d, import_context): + result = ImportResult() + + for ou_d in json_d.get('ous', []): + result.update_ous(*import_ou(ou_d)) + + roles_ds = [RoleDeserializer(role_d, import_context) for role_d in json_d.get('roles', []) + if not role_d['slug'].startswith('_')] + + for ds in roles_ds: + result.update_roles(*ds.deserialize()) + + if import_context.role_attributes_update: + for ds in roles_ds: + result.update_attributes(*ds.attributes()) + + if import_context.role_parentings_update: + for ds in roles_ds: + result.update_parentings(*ds.parentings()) + + if import_context.role_permissions_update: + for ds in roles_ds: + result.update_permissions(*ds.permissions()) + + if import_context.ou_delete_orphans: + raise DataImportError( + "Unsupported context value for ou_delete_orphans : %s" % ( + import_context.ou_delete_orphans)) + + if import_context.role_delete_orphans: + # FIXME : delete each role that is in DB but not in the export + raise DataImportError( + "Unsupported context value for role_delete_orphans : %s" % ( + import_context.role_delete_orphans)) + + return result diff --git a/src/authentic2/management/commands/export_site.py b/src/authentic2/management/commands/export_site.py new file mode 100644 index 00000000..58c6ddda --- /dev/null +++ b/src/authentic2/management/commands/export_site.py @@ -0,0 +1,24 @@ +import json +import sys + +from django.core.management.base import BaseCommand + +from authentic2.data_transfer import export_site +from django_rbac.utils import get_role_model + + +class Command(BaseCommand): + help = 'Export site' + + def add_arguments(self, parser): + parser.add_argument('--output', metavar='FILE', default=None, + help='name of a file to write output to') + + def handle(self, *args, **options): + if options['output']: + output, close = open(options['output'], 'w'), True + else: + output, close = sys.stdout, False + json.dump(export_site(), output, indent=4) + if close: + output.close() diff --git a/src/authentic2/management/commands/import_site.py b/src/authentic2/management/commands/import_site.py new file mode 100644 index 00000000..29c5cade --- /dev/null +++ b/src/authentic2/management/commands/import_site.py @@ -0,0 +1,71 @@ +import contextlib +import json +import sys + +from django.conf import settings +from django.core.management.base import BaseCommand +from django.db import transaction +from django.utils import translation + +from authentic2.data_transfer import import_site, ImportContext + + +class DryRunException(Exception): + pass + + +def create_context_args(options): + kwargs = {} + if options['option']: + for context_op in options['option']: + context_op = context_op.replace('-', '_') + if context_op.startswith('no_'): + kwargs[context_op[3:]] = False + else: + kwargs[context_op] = True + return kwargs + + +# Borrowed from https://bugs.python.org/issue10049#msg118599 +@contextlib.contextmanager +def provision_contextm(dry_run, settings): + if dry_run and 'hobo.agent.authentic2' in settings.INSTALLED_APPS: + import hobo.agent.authentic2 + with hobo.agent.authentic2.provisionning.Provisionning(): + yield + else: + yield + + +class Command(BaseCommand): + help = 'Import site' + + def add_arguments(self, parser): + parser.add_argument( + 'filename', metavar='FILENAME', type=str, help='name of file to import') + parser.add_argument( + '--dry-run', action='store_true', dest='dry_run', help='Really perform the import') + parser.add_argument( + '-o', '--option', action='append', help='Import context options', + choices=[ + 'role-delete-orphans', 'ou-delete-orphans', 'no-role-permissions-update', + 'no-role-attributes-update', 'no-role-parentings-update']) + + def handle(self, filename, **options): + translation.activate(settings.LANGUAGE_CODE) + dry_run = options['dry_run'] + msg = "Dry run\n" if dry_run else "Real run\n" + c_kwargs = create_context_args(options) + try: + with open(filename, 'r') as f: + with provision_contextm(dry_run, settings): + with transaction.atomic(): + sys.stdout.write(msg) + result = import_site(json.load(f), ImportContext(**c_kwargs)) + if dry_run: + raise DryRunException() + except DryRunException: + pass + sys.stdout.write(result.to_str()) + sys.stdout.write("Success\n") + translation.deactivate() diff --git a/src/authentic2/utils.py b/src/authentic2/utils.py index 0978f604..366f3a5f 100644 --- a/src/authentic2/utils.py +++ b/src/authentic2/utils.py @@ -1047,3 +1047,8 @@ def send_email_change_email(user, email, request=None, context=None, template_na context=ctx, legacy_subject_templates=legacy_subject_templates, legacy_body_templates=legacy_body_templates) + + +def update_model(obj, d): + for attr, value in d.items(): + setattr(obj, attr, value) diff --git a/src/django_rbac/models.py b/src/django_rbac/models.py index 6b2f9509..9c9bfeb3 100644 --- a/src/django_rbac/models.py +++ b/src/django_rbac/models.py @@ -114,6 +114,9 @@ class Operation(models.Model): def __unicode__(self): return unicode(_(self.name)) + def export_json(self): + return {'slug': self.slug, 'name': self.name} + objects = managers.OperationManager() @@ -145,6 +148,14 @@ class PermissionAbstractBase(models.Model): self.target and self.target_ct.natural_key(), self.target and self.target.natural_key()] + def export_json(self): + return { + "operation": self.operation.natural_key_json(), + "ou": self.ou and self.ou.natural_key_json(), + 'target_ct': self.target_ct.natural_key_json(), + "target": self.target.natural_key_json() + } + def __unicode__(self): ct = ContentType.objects.get_for_id(self.target_ct_id) ct_ct = ContentType.objects.get_for_model(ContentType) diff --git a/tests/data/export_site.json b/tests/data/export_site.json new file mode 100644 index 00000000..680f999f --- /dev/null +++ b/tests/data/export_site.json @@ -0,0 +1,1361 @@ +{ + "ous": [ + { + "uuid": "ba60d9e6c2874636883bdd604b23eab2", + "name": "Collectivit\u00e9 par d\u00e9faut", + "slug": "default" + }, + { + "uuid": "dd13db24a1754ed19288dcb147f9e73a", + "name": "ou deux", + "slug": "ou-deux" + }, + { + "uuid": "0c2eb94896e4440bb2ca53091948cdf4", + "name": "ou un ", + "slug": "ou-un" + } + ], + "roles": [ + { + "uuid": "0b47461473144334a3d5266b91adfa55", + "service": { + "ou": { + "uuid": "ba60d9e6c2874636883bdd604b23eab2", + "name": "Collectivit\u00e9 par d\u00e9faut", + "slug": "default" + }, + "name": "Hobo", + "slug": "hobo" + }, + "attributes": [ + { + "kind": "string", + "name": "is_superuser", + "value": "true" + } + ], + "admin_scope_id": null, + "ou": { + "uuid": "ba60d9e6c2874636883bdd604b23eab2", + "name": "Collectivit\u00e9 par d\u00e9faut", + "slug": "default" + }, + "description": "", + "admin_scope_ct_id": null, + "external_id": "", + "slug": "_a2-hobo-superuser", + "name": "Administrateur de Hobo" + }, + { + "uuid": "540fe45b326040b2b891fc350635e891", + "service": { + "ou": { + "uuid": "ba60d9e6c2874636883bdd604b23eab2", + "name": "Collectivit\u00e9 par d\u00e9faut", + "slug": "default" + }, + "name": "Portail Citoyen", + "slug": "portal" + }, + "attributes": [ + { + "kind": "string", + "name": "is_superuser", + "value": "true" + } + ], + "admin_scope_id": null, + "ou": { + "uuid": "ba60d9e6c2874636883bdd604b23eab2", + "name": "Collectivit\u00e9 par d\u00e9faut", + "slug": "default" + }, + "description": "", + "admin_scope_ct_id": null, + "external_id": "", + "slug": "_a2-hobo-superuser", + "name": "Administrateur de Portail Citoyen" + }, + { + "uuid": "abd2d89a4e354cbc9b8fdc1d977c96da", + "service": { + "ou": { + "uuid": "ba60d9e6c2874636883bdd604b23eab2", + "name": "Collectivit\u00e9 par d\u00e9faut", + "slug": "default" + }, + "name": "D\u00e9marches", + "slug": "services" + }, + "attributes": [ + { + "kind": "string", + "name": "is_superuser", + "value": "true" + } + ], + "admin_scope_id": null, + "ou": { + "uuid": "ba60d9e6c2874636883bdd604b23eab2", + "name": "Collectivit\u00e9 par d\u00e9faut", + "slug": "default" + }, + "description": "", + "admin_scope_ct_id": null, + "external_id": "", + "slug": "_a2-hobo-superuser", + "name": "Administrateur de D\u00e9marches" + }, + { + "uuid": "d064a245046743a0baac8513c009554d", + "service": { + "ou": { + "uuid": "ba60d9e6c2874636883bdd604b23eab2", + "name": "Collectivit\u00e9 par d\u00e9faut", + "slug": "default" + }, + "name": "Portail Agent", + "slug": "portal-agent" + }, + "attributes": [ + { + "kind": "string", + "name": "is_superuser", + "value": "true" + } + ], + "admin_scope_id": null, + "ou": { + "uuid": "ba60d9e6c2874636883bdd604b23eab2", + "name": "Collectivit\u00e9 par d\u00e9faut", + "slug": "default" + }, + "description": "", + "admin_scope_ct_id": null, + "external_id": "", + "slug": "_a2-hobo-superuser", + "name": "Administrateur de Portail Agent" + }, + { + "uuid": "dce09d79743d496eaa50bbd96c04c65e", + "service": { + "ou": { + "uuid": "ba60d9e6c2874636883bdd604b23eab2", + "name": "Collectivit\u00e9 par d\u00e9faut", + "slug": "default" + }, + "name": "Passerelle", + "slug": "passerelle" + }, + "attributes": [ + { + "kind": "string", + "name": "is_superuser", + "value": "true" + } + ], + "admin_scope_id": null, + "ou": { + "uuid": "ba60d9e6c2874636883bdd604b23eab2", + "name": "Collectivit\u00e9 par d\u00e9faut", + "slug": "default" + }, + "description": "", + "admin_scope_ct_id": null, + "external_id": "", + "slug": "_a2-hobo-superuser", + "name": "Administrateur de Passerelle" + }, + { + "uuid": "b043396ad9e942bcb6dd21a467e0f230", + "service": { + "ou": { + "uuid": "ba60d9e6c2874636883bdd604b23eab2", + "name": "Collectivit\u00e9 par d\u00e9faut", + "slug": "default" + }, + "name": "Porte-documents", + "slug": "porte-doc" + }, + "attributes": [ + { + "kind": "string", + "name": "is_superuser", + "value": "true" + } + ], + "admin_scope_id": null, + "ou": { + "uuid": "ba60d9e6c2874636883bdd604b23eab2", + "name": "Collectivit\u00e9 par d\u00e9faut", + "slug": "default" + }, + "description": "", + "admin_scope_ct_id": null, + "external_id": "", + "slug": "_a2-hobo-superuser", + "name": "Administrateur de Porte-documents" + }, + { + "uuid": "bc6582d1fec549ff86c5ea6e91756e7b", + "service": { + "ou": { + "uuid": "ba60d9e6c2874636883bdd604b23eab2", + "name": "Collectivit\u00e9 par d\u00e9faut", + "slug": "default" + }, + "name": "Agendas", + "slug": "agendas" + }, + "attributes": [ + { + "kind": "string", + "name": "is_superuser", + "value": "true" + } + ], + "admin_scope_id": null, + "ou": { + "uuid": "ba60d9e6c2874636883bdd604b23eab2", + "name": "Collectivit\u00e9 par d\u00e9faut", + "slug": "default" + }, + "description": "", + "admin_scope_ct_id": null, + "external_id": "", + "slug": "_a2-hobo-superuser", + "name": "Administrateur de Agendas" + }, + { + "uuid": "479ada08e2274067b969d290685f1496", + "service": null, + "permissions": [ + { + "operation": { + "slug": "change", + "name": "Modifier" + }, + "ou": null, + "target_ct": [ + "a2_rbac", + "role" + ], + "target": [ + "_a2-managers-of-role-role-deux", + [ + "default" + ], + null + ] + }, + { + "operation": { + "slug": "view", + "name": "Visualisation" + }, + "ou": null, + "target_ct": [ + "contenttypes", + "contenttype" + ], + "target": [ + "custom_user", + "user" + ] + }, + { + "operation": { + "slug": "admin", + "name": "Administration" + }, + "ou": { + "uuid": "ba60d9e6c2874636883bdd604b23eab2", + "name": "Collectivit\u00e9 par d\u00e9faut", + "slug": "default" + }, + "target_ct": [ + "a2_rbac", + "role" + ], + "target": [ + "role-deux", + [ + "default" + ], + null + ] + } + ], + "admin_scope_id": 9, + "ou": { + "uuid": "ba60d9e6c2874636883bdd604b23eab2", + "name": "Collectivit\u00e9 par d\u00e9faut", + "slug": "default" + }, + "description": "", + "admin_scope_ct_id": 36, + "external_id": "", + "slug": "_a2-managers-of-role-role-deux", + "name": "Administrateur du r\u00f4le \u00ab\u00a0role deux\u00a0\u00bb" + }, + { + "uuid": "6de808e99fb3442ea7bc4ae8d8c33a43", + "service": null, + "permissions": [ + { + "operation": { + "slug": "change", + "name": "Modifier" + }, + "ou": null, + "target_ct": [ + "a2_rbac", + "role" + ], + "target": [ + "_a2-managers-of-role-role-un", + [ + "default" + ], + null + ] + }, + { + "operation": { + "slug": "view", + "name": "Visualisation" + }, + "ou": null, + "target_ct": [ + "contenttypes", + "contenttype" + ], + "target": [ + "custom_user", + "user" + ] + }, + { + "operation": { + "slug": "admin", + "name": "Administration" + }, + "ou": { + "uuid": "ba60d9e6c2874636883bdd604b23eab2", + "name": "Collectivit\u00e9 par d\u00e9faut", + "slug": "default" + }, + "target_ct": [ + "a2_rbac", + "role" + ], + "target": [ + "role-un", + [ + "ou-un" + ], + null + ] + } + ], + "admin_scope_id": 7, + "ou": { + "uuid": "ba60d9e6c2874636883bdd604b23eab2", + "name": "Collectivit\u00e9 par d\u00e9faut", + "slug": "default" + }, + "description": "", + "admin_scope_ct_id": 36, + "external_id": "", + "slug": "_a2-managers-of-role-role-un", + "name": "Administrateur du r\u00f4le \u00ab\u00a0role un\u00a0\u00bb" + }, + { + "description": "role deux descr", + "slug": "role-deux", + "name": "role deux", + "uuid": "9e4f8189c5054c109ae25b8c633eca78", + "service": null, + "admin_scope_id": null, + "admin_scope_ct_id": null, + "parents": [ + { + "uuid": "aa83b6c8f7f2420ea855b6c06e5f7e80", + "service": null, + "admin_scope_id": 26, + "ou": { + "uuid": "0c2eb94896e4440bb2ca53091948cdf4", + "name": "ou un ", + "slug": "ou-un" + }, + "description": "", + "admin_scope_ct_id": 36, + "external_id": "", + "slug": "_a2-managers-of-role-role-un", + "name": "Administrateur du r\u00f4le \u00ab\u00a0role un\u00a0\u00bb" + } + ], + "attributes": [ + { + "kind": "json", + "name": "emails", + "value": "[]" + }, + { + "kind": "json", + "name": "details", + "value": "\"role deux detail\"" + }, + { + "kind": "json", + "name": "emails_to_members", + "value": "true" + } + ], + "ou": { + "uuid": "ba60d9e6c2874636883bdd604b23eab2", + "name": "Collectivit\u00e9 par d\u00e9faut", + "slug": "default" + }, + "permissions": [ + { + "operation": { + "slug": "delete", + "name": "Supprimer" + }, + "ou": { + "uuid": "dd13db24a1754ed19288dcb147f9e73a", + "name": "ou deux", + "slug": "ou-deux" + }, + "target_ct": [ + "contenttypes", + "contenttype" + ], + "target": [ + "custom_user", + "user" + ] + }, + { + "operation": { + "slug": "search", + "name": "Rechercher" + }, + "ou": { + "uuid": "ba60d9e6c2874636883bdd604b23eab2", + "name": "Collectivit\u00e9 par d\u00e9faut", + "slug": "default" + }, + "target_ct": [ + "contenttypes", + "contenttype" + ], + "target": [ + "admin", + "logentry" + ] + }, + { + "operation": { + "slug": "view", + "name": "Visualisation" + }, + "ou": { + "uuid": "0c2eb94896e4440bb2ca53091948cdf4", + "name": "ou un ", + "slug": "ou-un" + }, + "target_ct": [ + "contenttypes", + "contenttype" + ], + "target": [ + "dashboard", + "dashboardpreferences" + ] + } + ], + "external_id": "" + }, + { + "uuid": "486cf6897b9743b18f74c7047cf054d9", + "service": null, + "permissions": [ + { + "operation": { + "slug": "admin", + "name": "Administration" + }, + "ou": { + "uuid": "ba60d9e6c2874636883bdd604b23eab2", + "name": "Collectivit\u00e9 par d\u00e9faut", + "slug": "default" + }, + "target_ct": [ + "contenttypes", + "contenttype" + ], + "target": [ + "a2_rbac", + "role" + ] + }, + { + "operation": { + "slug": "view", + "name": "Visualisation" + }, + "ou": { + "uuid": "ba60d9e6c2874636883bdd604b23eab2", + "name": "Collectivit\u00e9 par d\u00e9faut", + "slug": "default" + }, + "target_ct": [ + "contenttypes", + "contenttype" + ], + "target": [ + "custom_user", + "user" + ] + }, + { + "operation": { + "slug": "search", + "name": "Rechercher" + }, + "ou": null, + "target_ct": [ + "a2_rbac", + "organizationalunit" + ], + "target": [ + "default" + ] + } + ], + "admin_scope_id": 12, + "ou": { + "uuid": "ba60d9e6c2874636883bdd604b23eab2", + "name": "Collectivit\u00e9 par d\u00e9faut", + "slug": "default" + }, + "description": "", + "admin_scope_ct_id": 36, + "external_id": "", + "slug": "_a2-administrateur-des-roles-default", + "name": "R\u00f4les - Collectivit\u00e9 par d\u00e9faut" + }, + { + "uuid": "d472b169995043ebbe6658b351a70f86", + "service": null, + "permissions": [ + { + "operation": { + "slug": "admin", + "name": "Administration" + }, + "ou": { + "uuid": "ba60d9e6c2874636883bdd604b23eab2", + "name": "Collectivit\u00e9 par d\u00e9faut", + "slug": "default" + }, + "target_ct": [ + "contenttypes", + "contenttype" + ], + "target": [ + "custom_user", + "user" + ] + }, + { + "operation": { + "slug": "search", + "name": "Rechercher" + }, + "ou": null, + "target_ct": [ + "a2_rbac", + "organizationalunit" + ], + "target": [ + "default" + ] + } + ], + "admin_scope_id": 15, + "ou": { + "uuid": "ba60d9e6c2874636883bdd604b23eab2", + "name": "Collectivit\u00e9 par d\u00e9faut", + "slug": "default" + }, + "description": "", + "admin_scope_ct_id": 36, + "external_id": "", + "slug": "_a2-administrateur-des-utilisateurs-default", + "name": "Utilisateurs - Collectivit\u00e9 par d\u00e9faut" + }, + { + "uuid": "69bd392d338f437eb908d16de3af9723", + "service": null, + "permissions": [ + { + "operation": { + "slug": "admin", + "name": "Administration" + }, + "ou": { + "uuid": "dd13db24a1754ed19288dcb147f9e73a", + "name": "ou deux", + "slug": "ou-deux" + }, + "target_ct": [ + "contenttypes", + "contenttype" + ], + "target": [ + "a2_rbac", + "role" + ] + }, + { + "operation": { + "slug": "view", + "name": "Visualisation" + }, + "ou": { + "uuid": "dd13db24a1754ed19288dcb147f9e73a", + "name": "ou deux", + "slug": "ou-deux" + }, + "target_ct": [ + "contenttypes", + "contenttype" + ], + "target": [ + "custom_user", + "user" + ] + }, + { + "operation": { + "slug": "search", + "name": "Rechercher" + }, + "ou": null, + "target_ct": [ + "a2_rbac", + "organizationalunit" + ], + "target": [ + "ou-deux" + ] + } + ], + "admin_scope_id": 22, + "ou": { + "uuid": "dd13db24a1754ed19288dcb147f9e73a", + "name": "ou deux", + "slug": "ou-deux" + }, + "description": "", + "admin_scope_ct_id": 36, + "external_id": "", + "slug": "_a2-administrateur-des-roles-ou-deux", + "name": "R\u00f4les - ou deux" + }, + { + "uuid": "7ca8d6f933054021a179a3d86ca19efb", + "service": null, + "permissions": [ + { + "operation": { + "slug": "admin", + "name": "Administration" + }, + "ou": { + "uuid": "dd13db24a1754ed19288dcb147f9e73a", + "name": "ou deux", + "slug": "ou-deux" + }, + "target_ct": [ + "contenttypes", + "contenttype" + ], + "target": [ + "custom_user", + "user" + ] + }, + { + "operation": { + "slug": "search", + "name": "Rechercher" + }, + "ou": null, + "target_ct": [ + "a2_rbac", + "organizationalunit" + ], + "target": [ + "ou-deux" + ] + } + ], + "admin_scope_id": 25, + "ou": { + "uuid": "dd13db24a1754ed19288dcb147f9e73a", + "name": "ou deux", + "slug": "ou-deux" + }, + "description": "", + "admin_scope_ct_id": 36, + "external_id": "", + "slug": "_a2-administrateur-des-utilisateurs-ou-deux", + "name": "Utilisateurs - ou deux" + }, + { + "uuid": "aa83b6c8f7f2420ea855b6c06e5f7e80", + "service": null, + "permissions": [ + { + "operation": { + "slug": "change", + "name": "Modifier" + }, + "ou": null, + "target_ct": [ + "a2_rbac", + "role" + ], + "target": [ + "_a2-managers-of-role-role-un", + [ + "ou-un" + ], + null + ] + }, + { + "operation": { + "slug": "view", + "name": "Visualisation" + }, + "ou": null, + "target_ct": [ + "contenttypes", + "contenttype" + ], + "target": [ + "custom_user", + "user" + ] + }, + { + "operation": { + "slug": "admin", + "name": "Administration" + }, + "ou": { + "uuid": "0c2eb94896e4440bb2ca53091948cdf4", + "name": "ou un ", + "slug": "ou-un" + }, + "target_ct": [ + "a2_rbac", + "role" + ], + "target": [ + "role-un", + [ + "ou-un" + ], + null + ] + } + ], + "admin_scope_id": 26, + "ou": { + "uuid": "0c2eb94896e4440bb2ca53091948cdf4", + "name": "ou un ", + "slug": "ou-un" + }, + "description": "", + "admin_scope_ct_id": 36, + "external_id": "", + "slug": "_a2-managers-of-role-role-un", + "name": "Administrateur du r\u00f4le \u00ab\u00a0role un\u00a0\u00bb" + }, + { + "uuid": "241492ead56c4773abf97271ea88b0a9", + "service": null, + "permissions": [ + { + "operation": { + "slug": "admin", + "name": "Administration" + }, + "ou": { + "uuid": "0c2eb94896e4440bb2ca53091948cdf4", + "name": "ou un ", + "slug": "ou-un" + }, + "target_ct": [ + "contenttypes", + "contenttype" + ], + "target": [ + "a2_rbac", + "role" + ] + }, + { + "operation": { + "slug": "view", + "name": "Visualisation" + }, + "ou": { + "uuid": "0c2eb94896e4440bb2ca53091948cdf4", + "name": "ou un ", + "slug": "ou-un" + }, + "target_ct": [ + "contenttypes", + "contenttype" + ], + "target": [ + "custom_user", + "user" + ] + }, + { + "operation": { + "slug": "search", + "name": "Rechercher" + }, + "ou": null, + "target_ct": [ + "a2_rbac", + "organizationalunit" + ], + "target": [ + "ou-un" + ] + } + ], + "admin_scope_id": 17, + "ou": { + "uuid": "0c2eb94896e4440bb2ca53091948cdf4", + "name": "ou un ", + "slug": "ou-un" + }, + "description": "", + "admin_scope_ct_id": 36, + "external_id": "", + "slug": "_a2-administrateur-des-roles-ou-un", + "name": "R\u00f4les - ou un " + }, + { + "description": "role un descr", + "slug": "role-un", + "name": "role un", + "uuid": "8bf850a1d43e4df4a8162d4108366f5c", + "service": null, + "admin_scope_id": null, + "admin_scope_ct_id": null, + "parents": [ + { + "uuid": "aa83b6c8f7f2420ea855b6c06e5f7e80", + "service": null, + "admin_scope_id": 26, + "ou": { + "uuid": "0c2eb94896e4440bb2ca53091948cdf4", + "name": "ou un ", + "slug": "ou-un" + }, + "description": "", + "admin_scope_ct_id": 36, + "external_id": "", + "slug": "_a2-managers-of-role-role-un", + "name": "Administrateur du r\u00f4le \u00ab\u00a0role un\u00a0\u00bb" + }, + { + "uuid": "9e4f8189c5054c109ae25b8c633eca78", + "service": null, + "admin_scope_id": null, + "ou": { + "uuid": "ba60d9e6c2874636883bdd604b23eab2", + "name": "Collectivit\u00e9 par d\u00e9faut", + "slug": "default" + }, + "description": "role deux descr", + "admin_scope_ct_id": null, + "external_id": "", + "slug": "role-deux", + "name": "role deux" + } + ], + "attributes": [ + { + "kind": "json", + "name": "details", + "value": "\"role un detail\"" + }, + { + "kind": "json", + "name": "emails", + "value": "[]" + }, + { + "kind": "json", + "name": "emails_to_members", + "value": "true" + } + ], + "ou": { + "uuid": "0c2eb94896e4440bb2ca53091948cdf4", + "name": "ou un ", + "slug": "ou-un" + }, + "external_id": "" + }, + { + "uuid": "1b8d7e5f421640f69af5692e876b96d9", + "service": null, + "permissions": [ + { + "operation": { + "slug": "admin", + "name": "Administration" + }, + "ou": { + "uuid": "0c2eb94896e4440bb2ca53091948cdf4", + "name": "ou un ", + "slug": "ou-un" + }, + "target_ct": [ + "contenttypes", + "contenttype" + ], + "target": [ + "custom_user", + "user" + ] + }, + { + "operation": { + "slug": "search", + "name": "Rechercher" + }, + "ou": null, + "target_ct": [ + "a2_rbac", + "organizationalunit" + ], + "target": [ + "ou-un" + ] + } + ], + "admin_scope_id": 20, + "ou": { + "uuid": "0c2eb94896e4440bb2ca53091948cdf4", + "name": "ou un ", + "slug": "ou-un" + }, + "description": "", + "admin_scope_ct_id": 36, + "external_id": "", + "slug": "_a2-administrateur-des-utilisateurs-ou-un", + "name": "Utilisateurs - ou un " + }, + { + "description": "", + "slug": "_a2-manager", + "permissions": [ + { + "operation": { + "slug": "change", + "name": "Modifier" + }, + "ou": null, + "target_ct": [ + "a2_rbac", + "role" + ], + "target": [ + "_a2-manager", + null, + null + ] + } + ], + "uuid": "daf3857c77bd41f0b10fa94a6e360f84", + "service": null, + "admin_scope_id": null, + "admin_scope_ct_id": null, + "name": "Administrateur", + "parents": [ + { + "uuid": "5d1ccc95259b4a0694e9c66ae04293c3", + "service": null, + "admin_scope_id": 4, + "ou": null, + "description": "", + "admin_scope_ct_id": 36, + "external_id": "", + "slug": "_a2-administrateur-des-utilisateurs", + "name": "Administrateur des utilisateurs" + }, + { + "uuid": "ab0a5e998a9f47dcad9a90729d4087ea", + "service": null, + "admin_scope_id": 5, + "ou": null, + "description": "", + "admin_scope_ct_id": 36, + "external_id": "", + "slug": "_a2-administrateur-des-roles", + "name": "Administrateur des r\u00f4les" + }, + { + "uuid": "9a05246e3f5041b8935f98051aa1054f", + "service": null, + "admin_scope_id": 6, + "ou": null, + "description": "", + "admin_scope_ct_id": 36, + "external_id": "", + "slug": "_a2-administrateur-des-entites", + "name": "Administrateur des entit\u00e9s" + } + ], + "ou": null, + "external_id": "" + }, + { + "description": "", + "slug": "_a2-managers-of-default", + "permissions": [ + { + "operation": { + "slug": "view", + "name": "Visualisation" + }, + "ou": null, + "target_ct": [ + "a2_rbac", + "organizationalunit" + ], + "target": [ + "default" + ] + } + ], + "uuid": "b2f44cfdedcb48b996ab2885bf024e7c", + "service": null, + "admin_scope_id": 11, + "admin_scope_ct_id": 36, + "name": "Administrateur de la collectivit\u00e9 \u00ab\u00a0Collectivit\u00e9 par d\u00e9faut\u00a0\u00bb", + "parents": [ + { + "uuid": "486cf6897b9743b18f74c7047cf054d9", + "service": null, + "admin_scope_id": 12, + "ou": { + "uuid": "ba60d9e6c2874636883bdd604b23eab2", + "name": "Collectivit\u00e9 par d\u00e9faut", + "slug": "default" + }, + "description": "", + "admin_scope_ct_id": 36, + "external_id": "", + "slug": "_a2-administrateur-des-roles-default", + "name": "R\u00f4les - Collectivit\u00e9 par d\u00e9faut" + }, + { + "uuid": "d472b169995043ebbe6658b351a70f86", + "service": null, + "admin_scope_id": 15, + "ou": { + "uuid": "ba60d9e6c2874636883bdd604b23eab2", + "name": "Collectivit\u00e9 par d\u00e9faut", + "slug": "default" + }, + "description": "", + "admin_scope_ct_id": 36, + "external_id": "", + "slug": "_a2-administrateur-des-utilisateurs-default", + "name": "Utilisateurs - Collectivit\u00e9 par d\u00e9faut" + } + ], + "ou": null, + "external_id": "" + }, + { + "description": "", + "slug": "_a2-managers-of-ou-deux", + "permissions": [ + { + "operation": { + "slug": "view", + "name": "Visualisation" + }, + "ou": null, + "target_ct": [ + "a2_rbac", + "organizationalunit" + ], + "target": [ + "ou-deux" + ] + } + ], + "uuid": "341e1030af4846f9be8f3e520a876220", + "service": null, + "admin_scope_id": 21, + "admin_scope_ct_id": 36, + "name": "Administrateur de la collectivit\u00e9 \u00ab\u00a0ou deux\u00a0\u00bb", + "parents": [ + { + "uuid": "69bd392d338f437eb908d16de3af9723", + "service": null, + "admin_scope_id": 22, + "ou": { + "uuid": "dd13db24a1754ed19288dcb147f9e73a", + "name": "ou deux", + "slug": "ou-deux" + }, + "description": "", + "admin_scope_ct_id": 36, + "external_id": "", + "slug": "_a2-administrateur-des-roles-ou-deux", + "name": "R\u00f4les - ou deux" + }, + { + "uuid": "7ca8d6f933054021a179a3d86ca19efb", + "service": null, + "admin_scope_id": 25, + "ou": { + "uuid": "dd13db24a1754ed19288dcb147f9e73a", + "name": "ou deux", + "slug": "ou-deux" + }, + "description": "", + "admin_scope_ct_id": 36, + "external_id": "", + "slug": "_a2-administrateur-des-utilisateurs-ou-deux", + "name": "Utilisateurs - ou deux" + } + ], + "ou": null, + "external_id": "" + }, + { + "description": "", + "slug": "_a2-managers-of-ou-un", + "permissions": [ + { + "operation": { + "slug": "view", + "name": "Visualisation" + }, + "ou": null, + "target_ct": [ + "a2_rbac", + "organizationalunit" + ], + "target": [ + "ou-un" + ] + } + ], + "uuid": "79a890b193914797b5699f5b43ccda01", + "service": null, + "admin_scope_id": 16, + "admin_scope_ct_id": 36, + "name": "Administrateur de la collectivit\u00e9 \u00ab\u00a0ou un \u00a0\u00bb", + "parents": [ + { + "uuid": "241492ead56c4773abf97271ea88b0a9", + "service": null, + "admin_scope_id": 17, + "ou": { + "uuid": "0c2eb94896e4440bb2ca53091948cdf4", + "name": "ou un ", + "slug": "ou-un" + }, + "description": "", + "admin_scope_ct_id": 36, + "external_id": "", + "slug": "_a2-administrateur-des-roles-ou-un", + "name": "R\u00f4les - ou un " + }, + { + "uuid": "1b8d7e5f421640f69af5692e876b96d9", + "service": null, + "admin_scope_id": 20, + "ou": { + "uuid": "0c2eb94896e4440bb2ca53091948cdf4", + "name": "ou un ", + "slug": "ou-un" + }, + "description": "", + "admin_scope_ct_id": 36, + "external_id": "", + "slug": "_a2-administrateur-des-utilisateurs-ou-un", + "name": "Utilisateurs - ou un " + } + ], + "ou": null, + "external_id": "" + }, + { + "uuid": "9a05246e3f5041b8935f98051aa1054f", + "service": null, + "permissions": [ + { + "operation": { + "slug": "admin", + "name": "Administration" + }, + "ou": null, + "target_ct": [ + "contenttypes", + "contenttype" + ], + "target": [ + "a2_rbac", + "organizationalunit" + ] + }, + { + "operation": { + "slug": "search", + "name": "Rechercher" + }, + "ou": null, + "target_ct": [ + "contenttypes", + "contenttype" + ], + "target": [ + "a2_rbac", + "organizationalunit" + ] + } + ], + "admin_scope_id": 6, + "ou": null, + "description": "", + "admin_scope_ct_id": 36, + "external_id": "", + "slug": "_a2-administrateur-des-entites", + "name": "Administrateur des entit\u00e9s" + }, + { + "uuid": "ab0a5e998a9f47dcad9a90729d4087ea", + "service": null, + "permissions": [ + { + "operation": { + "slug": "admin", + "name": "Administration" + }, + "ou": null, + "target_ct": [ + "contenttypes", + "contenttype" + ], + "target": [ + "a2_rbac", + "role" + ] + }, + { + "operation": { + "slug": "view", + "name": "Visualisation" + }, + "ou": null, + "target_ct": [ + "contenttypes", + "contenttype" + ], + "target": [ + "custom_user", + "user" + ] + }, + { + "operation": { + "slug": "search", + "name": "Rechercher" + }, + "ou": null, + "target_ct": [ + "contenttypes", + "contenttype" + ], + "target": [ + "a2_rbac", + "organizationalunit" + ] + } + ], + "admin_scope_id": 5, + "ou": null, + "description": "", + "admin_scope_ct_id": 36, + "external_id": "", + "slug": "_a2-administrateur-des-roles", + "name": "Administrateur des r\u00f4les" + }, + { + "uuid": "5d1ccc95259b4a0694e9c66ae04293c3", + "service": null, + "permissions": [ + { + "operation": { + "slug": "admin", + "name": "Administration" + }, + "ou": null, + "target_ct": [ + "contenttypes", + "contenttype" + ], + "target": [ + "custom_user", + "user" + ] + }, + { + "operation": { + "slug": "search", + "name": "Rechercher" + }, + "ou": null, + "target_ct": [ + "contenttypes", + "contenttype" + ], + "target": [ + "a2_rbac", + "organizationalunit" + ] + } + ], + "admin_scope_id": 4, + "ou": null, + "description": "", + "admin_scope_ct_id": 36, + "external_id": "", + "slug": "_a2-administrateur-des-utilisateurs", + "name": "Administrateur des utilisateurs" + } + ] +} \ No newline at end of file diff --git a/tests/test_a2_rbac.py b/tests/test_a2_rbac.py index f619f26f..a5069603 100644 --- a/tests/test_a2_rbac.py +++ b/tests/test_a2_rbac.py @@ -1,7 +1,11 @@ import pytest +from django.contrib.contenttypes.models import ContentType +from django_rbac.utils import get_permission_model +from django_rbac.models import Operation +from authentic2.a2_rbac.models import Role, OrganizationalUnit as OU, RoleAttribute from authentic2.models import Service -from authentic2.a2_rbac.models import Role, OrganizationalUnit as OU +from authentic2.utils import get_hex_uuid def test_role_natural_key(db): @@ -24,3 +28,155 @@ def test_role_natural_key(db): Role.objects.get_by_natural_key(*r2.natural_key()) with pytest.raises(Role.DoesNotExist): Role.objects.get_by_natural_key(*r4.natural_key()) + + +def test_basic_role_export_json(db): + role = Role.objects.create( + name='basic role', slug='basic-role', description='basic role description') + role_dict = role.export_json() + assert role_dict['name'] == role.name + assert role_dict['slug'] == role.slug + assert role_dict['uuid'] == role.uuid + assert role_dict['description'] == role.description + assert role_dict['external_id'] == role.external_id + assert role_dict['ou'] is None + assert role_dict['service'] is None + + +def test_role_with_ou_export_json(db): + ou = OU.objects.create(name='ou', slug='ou') + role = Role.objects.create(name='some role', ou=ou) + role_dict = role.export_json() + assert role_dict['ou'] == {'uuid': ou.uuid, 'slug': ou.slug, 'name': ou.name} + + +def test_role_with_service_export_json(db): + service = Service.objects.create(name='service name', slug='service-name') + role = Role.objects.create(name='some role', service=service) + role_dict = role.export_json() + assert role_dict['service'] == {'slug': service.slug, 'ou': None} + + +def test_role_with_service_with_ou_export_json(db): + ou = OU.objects.create(name='ou', slug='ou') + service = Service.objects.create(name='service name', slug='service-name', ou=ou) + role = Role.objects.create(name='some role', service=service) + role_dict = role.export_json() + assert role_dict['service'] == { + 'slug': service.slug, 'ou': {'uuid': ou.uuid, 'slug': 'ou', 'name': 'ou'}} + + +def test_role_with_attributes_export_json(db): + role = Role.objects.create(name='some role') + attr1 = RoleAttribute.objects.create( + role=role, name='attr1_name', kind='string', value='attr1_value') + attr2 = RoleAttribute.objects.create( + role=role, name='attr2_name', kind='string', value='attr2_value') + + role_dict = role.export_json(attributes=True) + attributes = role_dict['attributes'] + assert len(attributes) == 2 + + expected_attr_names = set([attr1.name, attr2.name]) + for attr_dict in attributes: + assert attr_dict['name'] in expected_attr_names + expected_attr_names.remove(attr_dict['name']) + target_attr = RoleAttribute.objects.filter(name=attr_dict['name']).first() + assert attr_dict['kind'] == target_attr.kind + assert attr_dict['value'] == target_attr.value + + +def test_role_with_parents_export_json(db): + grand_parent_role = Role.objects.create( + name='test grand parent role', slug='test-grand-parent-role') + parent_1_role = Role.objects.create( + name='test parent 1 role', slug='test-parent-1-role') + parent_1_role.add_parent(grand_parent_role) + parent_2_role = Role.objects.create( + name='test parent 2 role', slug='test-parent-2-role') + parent_2_role.add_parent(grand_parent_role) + child_role = Role.objects.create( + name='test child role', slug='test-child-role') + child_role.add_parent(parent_1_role) + child_role.add_parent(parent_2_role) + + child_role_dict = child_role.export_json(parents=True) + assert child_role_dict['slug'] == child_role.slug + parents = child_role_dict['parents'] + assert len(parents) == 2 + expected_slugs = set([parent_1_role.slug, parent_2_role.slug]) + for parent in parents: + assert parent['slug'] in expected_slugs + expected_slugs.remove(parent['slug']) + + grand_parent_role_dict = grand_parent_role.export_json(parents=True) + assert grand_parent_role_dict['slug'] == grand_parent_role.slug + assert 'parents' not in grand_parent_role_dict + + parent_1_role_dict = parent_1_role.export_json(parents=True) + assert parent_1_role_dict['slug'] == parent_1_role.slug + parents = parent_1_role_dict['parents'] + assert len(parents) == 1 + assert parents[0]['slug'] == grand_parent_role.slug + + parent_2_role_dict = parent_2_role.export_json(parents=True) + assert parent_2_role_dict['slug'] == parent_2_role.slug + parents = parent_2_role_dict['parents'] + assert len(parents) == 1 + assert parents[0]['slug'] == grand_parent_role.slug + + +def test_role_with_permission_export_json(db): + some_ou = OU.objects.create(name='some ou', slug='some-ou') + role = Role.objects.create(name='role name', slug='role-slug') + other_role = Role.objects.create( + name='other role name', slug='other-role-slug', uuid=get_hex_uuid(), ou=some_ou) + ou = OU.objects.create(name='basic ou', slug='basic-ou', description='basic ou description') + Permission = get_permission_model() + op = Operation.objects.first() + perm_saml = Permission.objects.create( + operation=op, ou=ou, + target_ct=ContentType.objects.get_for_model(ContentType), + target_id=ContentType.objects.get(app_label="saml", model="libertyprovider").pk) + role.permissions.add(perm_saml) + perm_role = Permission.objects.create( + operation=op, ou=None, + target_ct=ContentType.objects.get_for_model(Role), + target_id=other_role.pk) + role.permissions.add(perm_role) + + export = role.export_json(permissions=True) + permissions = export['permissions'] + assert len(permissions) == 2 + assert permissions[0] == { + 'operation': {'slug': 'add'}, + 'ou': {'uuid': ou.uuid, 'slug': ou.slug, 'name': ou.name}, + 'target_ct': {'app_label': u'contenttypes', 'model': u'contenttype'}, + 'target': {'model': u'libertyprovider', 'app_label': u'saml'} + } + assert permissions[1] == { + 'operation': {'slug': 'add'}, + 'ou': None, + 'target_ct': {'app_label': u'a2_rbac', 'model': u'role'}, + 'target': { + 'slug': u'other-role-slug', 'service': None, 'uuid': other_role.uuid, + 'ou': { + 'slug': u'some-ou', 'uuid': some_ou.uuid, 'name': u'some ou' + }, + 'name': u'other role name'} + } + + +def test_ou_export_json(db): + ou = OU.objects.create( + name='basic ou', slug='basic-ou', description='basic ou description', + username_is_unique=True, email_is_unique=True, default=False, validate_emails=True) + ou_dict = ou.export_json() + assert ou_dict['name'] == ou.name + assert ou_dict['slug'] == ou.slug + assert ou_dict['uuid'] == ou.uuid + assert ou_dict['description'] == ou.description + assert ou_dict['username_is_unique'] == ou.username_is_unique + assert ou_dict['email_is_unique'] == ou.email_is_unique + assert ou_dict['default'] == ou.default + assert ou_dict['validate_emails'] == ou.validate_emails diff --git a/tests/test_data_transfer.py b/tests/test_data_transfer.py new file mode 100644 index 00000000..dc073ea4 --- /dev/null +++ b/tests/test_data_transfer.py @@ -0,0 +1,471 @@ +import json + +from django_rbac.utils import get_role_model, get_ou_model +import py +import pytest + +from authentic2.a2_rbac.models import RoleParenting +from authentic2.data_transfer import ( + DataImportError, export_roles, import_site, export_ou, ImportContext, + RoleDeserializer, search_role, import_ou) +from authentic2.utils import get_hex_uuid + + +Role = get_role_model() +OU = get_ou_model() + + +def test_export_basic_role(db): + role = Role.objects.create(name='basic role', slug='basic-role', uuid=get_hex_uuid()) + query_set = Role.objects.filter(uuid=role.uuid) + roles = export_roles(query_set) + assert len(roles) == 1 + role_dict = roles[0] + for key, value in role.export_json().items(): + assert role_dict[key] == value + + +def test_export_role_with_parents(db): + grand_parent_role = Role.objects.create( + name='test grand parent role', slug='test-grand-parent-role', uuid=get_hex_uuid()) + parent_1_role = Role.objects.create( + name='test parent 1 role', slug='test-parent-1-role', uuid=get_hex_uuid()) + parent_1_role.add_parent(grand_parent_role) + parent_2_role = Role.objects.create( + name='test parent 2 role', slug='test-parent-2-role', uuid=get_hex_uuid()) + parent_2_role.add_parent(grand_parent_role) + child_role = Role.objects.create( + name='test child role', slug='test-child-role', uuid=get_hex_uuid()) + child_role.add_parent(parent_1_role) + child_role.add_parent(parent_2_role) + + query_set = Role.objects.filter(slug__startswith='test').order_by('slug') + roles = export_roles(query_set) + assert len(roles) == 4 + + child_role_dict = roles[0] + assert child_role_dict['slug'] == child_role.slug + parents = child_role_dict['parents'] + assert len(parents) == 2 + expected_slugs = set([parent_1_role.slug, parent_2_role.slug]) + for parent in parents: + assert parent['slug'] in expected_slugs + expected_slugs.remove(parent['slug']) + + grand_parent_role_dict = roles[1] + assert grand_parent_role_dict['slug'] == grand_parent_role.slug + + parent_1_role_dict = roles[2] + assert parent_1_role_dict['slug'] == parent_1_role.slug + parents = parent_1_role_dict['parents'] + assert len(parents) == 1 + assert parents[0]['slug'] == grand_parent_role.slug + + parent_2_role_dict = roles[3] + assert parent_2_role_dict['slug'] == parent_2_role.slug + parents = parent_2_role_dict['parents'] + assert len(parents) == 1 + assert parents[0]['slug'] == grand_parent_role.slug + + +def test_export_ou(db): + ou = OU.objects.create(name='ou name', slug='ou-slug', description='ou description') + ous = export_ou(OU.objects.filter(name='ou name')) + assert len(ous) == 1 + ou_d = ous[0] + assert ou_d['name'] == ou.name + assert ou_d['slug'] == ou.slug + assert ou_d['description'] == ou.description + + +def test_search_role_by_uuid(db): + uuid = get_hex_uuid() + role_d = {'uuid': uuid, 'slug': 'role-slug'} + role = Role.objects.create(**role_d) + assert role == search_role({'uuid': uuid, 'slug': 'other-role-slug'}) + + +def test_search_role_by_slug(db): + role_d = {'uuid': get_hex_uuid(), 'slug': 'role-slug'} + role = Role.objects.create(**role_d) + assert role == search_role({ + 'uuid': get_hex_uuid(), 'slug': 'role-slug', + 'ou': None, 'service': None}) + + +def test_search_role_not_found(db): + assert search_role( + { + 'uuid': get_hex_uuid(), 'slug': 'role-slug', 'name': 'role name', + 'ou': None, 'service': None}) is None + + +def test_search_role_slug_not_unique(db): + role1_d = {'uuid': get_hex_uuid(), 'slug': 'role-slug', 'name': 'role name'} + role2_d = {'uuid': get_hex_uuid(), 'slug': 'role-slug', 'name': 'role name'} + ou = OU.objects.create(name='some ou', slug='some-ou') + role1 = Role.objects.create(ou=ou, **role1_d) + Role.objects.create(**role2_d) + assert role1 == search_role(role1.export_json()) + + +def test_role_deserializer(db): + rd = RoleDeserializer({ + 'name': 'some role', 'description': 'some role description', 'slug': 'some-role', + 'uuid': get_hex_uuid(), 'ou': None, 'service': None}, ImportContext()) + assert rd._parents is None + assert rd._attributes is None + assert rd._obj is None + role, status = rd.deserialize() + assert status == 'created' + assert role.name == 'some role' + assert role.description == 'some role description' + assert role.slug == 'some-role' + assert rd._obj == role + + +def test_role_deserializer_with_ou(db): + ou = OU.objects.create(name='some ou', slug='some-ou') + rd = RoleDeserializer({ + 'uuid': get_hex_uuid(), 'name': 'some role', 'description': 'some role description', + 'slug': 'some-role', 'ou': {'slug': 'some-ou'}, 'service': None}, ImportContext()) + role, status = rd.deserialize() + assert role.ou == ou + + +def test_role_deserializer_missing_ou(db): + rd = RoleDeserializer({ + 'uuid': get_hex_uuid(), 'name': 'some role', 'description': 'role description', + 'slug': 'some-role', 'ou': {'slug': 'some-ou'}, 'service': None}, + ImportContext()) + with pytest.raises(DataImportError): + rd.deserialize() + + +def test_role_deserializer_update_ou(db): + ou1 = OU.objects.create(name='ou 1', slug='ou-1') + ou2 = OU.objects.create(name='ou 2', slug='ou-2') + uuid = get_hex_uuid() + existing_role = Role.objects.create(uuid=uuid, slug='some-role', ou=ou1) + rd = RoleDeserializer({ + 'uuid': uuid, 'name': 'some-role', 'slug': 'some-role', + 'ou': {'slug': 'ou-2'}, 'service': None}, ImportContext()) + role, status = rd.deserialize() + assert role == existing_role + assert role.ou == ou2 + + +def test_role_deserializer_update_fields(db): + uuid = get_hex_uuid() + existing_role = Role.objects.create(uuid=uuid, slug='some-role', name='some role') + rd = RoleDeserializer({ + 'uuid': uuid, 'slug': 'some-role', 'name': 'some role changed', + 'ou': None, 'service': None}, ImportContext()) + role, status = rd.deserialize() + assert role == existing_role + assert role.name == 'some role changed' + + +def test_role_deserializer_with_attributes(db): + + attributes_data = { + 'attr1_name': dict(name='attr1_name', kind='string', value='attr1_value'), + 'attr2_name': dict(name='attr2_name', kind='string', value='attr2_value') + } + rd = RoleDeserializer({ + 'uuid': get_hex_uuid(), 'name': 'some role', 'description': 'some role description', + 'slug': 'some-role', 'attributes': list(attributes_data.values()), + 'ou': None, 'service': None}, ImportContext()) + role, status = rd.deserialize() + created, deleted = rd.attributes() + assert role.attributes.count() == 2 + assert len(created) == 2 + + for attr in created: + attr_dict = attributes_data[attr.name] + assert attr_dict['name'] == attr.name + assert attr_dict['kind'] == attr.kind + assert attr_dict['value'] == attr.value + del attributes_data[attr.name] + + +def test_role_deserializer_creates_admin_role(db): + role_dict = { + 'name': 'some role', 'slug': 'some-role', 'uuid': get_hex_uuid(), + 'ou': None, 'service': None} + rd = RoleDeserializer(role_dict, ImportContext()) + rd.deserialize() + Role.objects.get(slug='_a2-managers-of-role-some-role') + + +def test_role_deserializer_parenting_existing_parent(db): + parent_role_dict = { + 'name': 'grand parent role', 'slug': 'grand-parent-role', 'uuid': get_hex_uuid(), + 'ou': None, 'service': None} + parent_role = Role.objects.create(**parent_role_dict) + child_role_dict = { + 'name': 'child role', 'slug': 'child-role', 'parents': [parent_role_dict], + 'uuid': get_hex_uuid(), 'ou': None, 'service': None} + + rd = RoleDeserializer(child_role_dict, ImportContext()) + child_role, status = rd.deserialize() + created, deleted = rd.parentings() + + assert len(created) == 1 + parenting = created[0] + assert parenting.direct is True + assert parenting.parent == parent_role + assert parenting.child == child_role + + +def test_role_deserializer_parenting_non_existing_parent(db): + parent_role_dict = { + 'name': 'grand parent role', 'slug': 'grand-parent-role', 'uuid': get_hex_uuid(), + 'ou': None, 'service': None} + child_role_dict = { + 'name': 'child role', 'slug': 'child-role', 'parents': [parent_role_dict], + 'uuid': get_hex_uuid(), 'ou': None, 'service': None} + rd = RoleDeserializer(child_role_dict, ImportContext()) + rd.deserialize() + with pytest.raises(DataImportError) as excinfo: + rd.parentings() + + assert "Could not find role" in str(excinfo.value) + + +def test_role_deserializer_permissions(db): + ou = OU.objects.create(slug='some-ou') + other_role_dict = { + 'name': 'other role', 'slug': 'other-role-slug', 'uuid': get_hex_uuid(), 'ou': ou} + other_role = Role.objects.create(**other_role_dict) + other_role_dict['permisison'] = { + "operation": { + "slug": "admin" + }, + "ou": { + "slug": "default", + "name": "Collectivit\u00e9 par d\u00e9faut" + }, + 'target_ct': {'app_label': u'a2_rbac', 'model': u'role'}, + "target": { + "slug": "role-deux", + "ou": { + "slug": "default", + "name": "Collectivit\u00e9 par d\u00e9faut" + }, + "service": None, + "name": "role deux" + } + } + some_role_dict = { + 'name': 'some role', 'slug': 'some-role', 'uuid': get_hex_uuid(), + 'ou': None, 'service': None} + some_role_dict['permissions'] = [ + { + 'operation': {'slug': 'add'}, + 'ou': None, + 'target_ct': {'app_label': u'a2_rbac', 'model': u'role'}, + 'target': { + "slug": u'other-role-slug', 'ou': {'slug': 'some-ou'}, 'service': None} + } + ] + + import_context = ImportContext() + rd = RoleDeserializer(some_role_dict, import_context) + rd.deserialize() + perm_created, perm_deleted = rd.permissions() + + assert len(perm_created) == 1 + assert len(perm_deleted) == 0 + del some_role_dict['permissions'] + role = Role.objects.get(slug=some_role_dict['slug']) + assert role.permissions.count() == 1 + perm = role.permissions.first() + assert perm.operation.slug == 'add' + assert not perm.ou + assert perm.target == other_role + + # that one should delete permissions + rd = RoleDeserializer(some_role_dict, import_context) + role, _ = rd.deserialize() + perm_created, perm_deleted = rd.permissions() + assert role.permissions.count() == 0 + assert len(perm_created) == 0 + assert len(perm_deleted) == 1 + + +def test_permission_on_role(db): + perm_ou = OU.objects.create(slug='perm-ou', name='perm ou') + perm_role = Role.objects.create(slug='perm-role', ou=perm_ou, name='perm role') + + some_role_dict = { + 'name': 'some role', 'slug': 'some-role-slug', 'ou': None, 'service': None} + some_role_dict['permissions'] = [{ + "operation": { + "slug": "admin" + }, + "ou": { + "slug": "perm-ou", + "name": "perm-ou" + }, + 'target_ct': {'app_label': u'a2_rbac', 'model': u'role'}, + "target": { + "slug": "perm-role", + "ou": { + "slug": "perm-ou", + "name": "perm ou" + }, + "service": None, + "name": "perm role" + } + }] + + import_context = ImportContext() + rd = RoleDeserializer(some_role_dict, import_context) + rd.deserialize() + perm_created, perm_deleted = rd.permissions() + assert len(perm_created) == 1 + perm = perm_created[0] + assert perm.target == perm_role + assert perm.ou == perm_ou + assert perm.operation.slug == 'admin' + + +def test_permission_on_contentype(db): + perm_ou = OU.objects.create(slug='perm-ou', name='perm ou') + some_role_dict = { + 'name': 'some role', 'slug': 'some-role-slug', 'ou': None, 'service': None} + some_role_dict['permissions'] = [{ + "operation": { + "slug": "admin" + }, + "ou": { + "slug": "perm-ou", + "name": "perm-ou" + }, + 'target_ct': {"model": "contenttype", "app_label": "contenttypes"}, + "target": {"model": "logentry", "app_label": "admin"} + }] + + import_context = ImportContext() + rd = RoleDeserializer(some_role_dict, import_context) + rd.deserialize() + perm_created, perm_deleted = rd.permissions() + assert len(perm_created) == 1 + perm = perm_created[0] + assert perm.target.app_label == 'admin' + assert perm.target.model == 'logentry' + assert perm.ou == perm_ou + + +def import_ou_created(db): + uuid = get_hex_uuid() + ou_d = {'uuid': uuid, 'slug': 'ou-slug', 'name': 'ou name'} + ou, status = import_ou(ou_d) + assert status == 'created' + assert ou.uuid == ou_d['uuid'] + assert ou.slug == ou_d['slug'] + assert ou.name == ou_d['name'] + + +def import_ou_updated(db): + ou = OU.objects.create(slug='some-ou', name='ou name') + ou_d = {'uuid': ou.uuid, 'slug': ou.slug, 'name': 'new name'} + ou_updated, status = import_ou(ou_d) + assert status == 'updated' + assert ou == ou_updated + assert ou.name == 'new name' + + +def testi_import_site_empty(): + res = import_site({}, ImportContext()) + assert res.roles == {'created': [], 'updated': []} + assert res.ous == {'created': [], 'updated': []} + assert res.parentings == {'created': [], 'deleted': []} + + +def test_import_site_roles(db): + parent_role_dict = { + 'name': 'grand parent role', 'slug': 'grand-parent-role', 'uuid': get_hex_uuid(), + 'ou': None, 'service': None} + child_role_dict = { + 'name': 'child role', 'slug': 'child-role', 'parents': [parent_role_dict], + 'uuid': get_hex_uuid(), 'ou': None, 'service': None} + roles = [ + parent_role_dict, + child_role_dict + ] + res = import_site({'roles': roles}, ImportContext()) + created_roles = res.roles['created'] + assert len(created_roles) == 2 + parent_role = Role.objects.get(**parent_role_dict) + del child_role_dict['parents'] + child_role = Role.objects.get(**child_role_dict) + assert created_roles[0] == parent_role + assert created_roles[1] == child_role + + assert len(res.parentings['created']) == 1 + assert res.parentings['created'][0] == RoleParenting.objects.get( + child=child_role, parent=parent_role, direct=True) + + +def test_roles_import_ignore_technical_role(db): + roles = [{ + 'name': 'some role', 'description': 'some role description', 'slug': '_some-role'}] + res = import_site({'roles': roles}, ImportContext()) + assert res.roles == {'created': [], 'updated': []} + + +def test_roles_import_ignore_technical_role_with_service(db): + roles = [{ + 'name': 'some role', 'description': 'some role description', 'slug': '_some-role'}] + res = import_site({'roles': roles}, ImportContext()) + assert res.roles == {'created': [], 'updated': []} + + +def test_import_role_handle_manager_role_parenting(db): + parent_role_dict = { + 'name': 'grand parent role', 'slug': 'grand-parent-role', 'uuid': get_hex_uuid(), + 'ou': None, 'service': None} + parent_role_manager_dict = { + 'name': 'Administrateur du role grand parent role', + 'slug': '_a2-managers-of-role-grand-parent-role', 'uuid': get_hex_uuid(), + 'ou': None, 'service': None} + child_role_dict = { + 'name': 'child role', 'slug': 'child-role', + 'parents': [parent_role_dict, parent_role_manager_dict], + 'uuid': get_hex_uuid(), 'ou': None, 'service': None} + import_site({'roles': [child_role_dict, parent_role_dict]}, ImportContext()) + child = Role.objects.get(slug='child-role') + manager = Role.objects.get(slug='_a2-managers-of-role-grand-parent-role') + RoleParenting.objects.get(child=child, parent=manager, direct=True) + + +def test_import_roles_role_delete_orphans(db): + roles = [{ + 'name': 'some role', 'description': 'some role description', 'slug': '_some-role'}] + with pytest.raises(DataImportError): + import_site({'roles': roles}, ImportContext(role_delete_orphans=True)) + + +def test_import_ou(db): + uuid = get_hex_uuid() + name = 'ou name' + ous = [{'uuid': uuid, 'slug': 'ou-slug', 'name': name}] + res = import_site({'ous': ous}, ImportContext()) + assert len(res.ous['created']) == 1 + ou = res.ous['created'][0] + assert ou.uuid == uuid + assert ou.name == name + Role.objects.get(slug='_a2-managers-of-ou-slug') + + +def test_import_ou_already_existing(db): + uuid = get_hex_uuid() + ou_d = {'uuid': uuid, 'slug': 'ou-slug', 'name': 'ou name'} + ou = OU.objects.create(**ou_d) + num_ous = OU.objects.count() + res = import_site({'ous': [ou_d]}, ImportContext()) + assert len(res.ous['created']) == 0 + assert num_ous == OU.objects.count() + assert ou == OU.objects.get(uuid=uuid) diff --git a/tests/test_import_export_site_cmd.py b/tests/test_import_export_site_cmd.py new file mode 100644 index 00000000..e8fd9291 --- /dev/null +++ b/tests/test_import_export_site_cmd.py @@ -0,0 +1,132 @@ +import __builtin__ +import json + +from django.core import management +import pytest + +from django_rbac.utils import get_role_model + + +def dummy_export_site(*args): + return {'roles': [{'name': 'role1'}]} + + +def test_export_role_cmd_stdout(db, capsys, monkeypatch): + import authentic2.management.commands.export_site + monkeypatch.setattr( + authentic2.management.commands.export_site, 'export_site', dummy_export_site) + management.call_command('export_site') + out, err = capsys.readouterr() + assert json.loads(out) == dummy_export_site() + + +def test_export_role_cmd_to_file(db, monkeypatch, tmpdir): + import authentic2.management.commands.export_site + monkeypatch.setattr( + authentic2.management.commands.export_site, 'export_site', dummy_export_site) + outfile = tmpdir.join('export.json') + management.call_command('export_site', '--output', outfile.strpath) + with outfile.open('r') as f: + assert json.loads(f.read()) == dummy_export_site() + + +def test_import_site_cmd(db, tmpdir, monkeypatch): + export_file = tmpdir.join('roles-export.json') + with export_file.open('w'): + export_file.write(json.dumps({'roles': []})) + management.call_command('import_site', export_file.strpath) + + +def test_import_site_cmd_infos_on_stdout(db, tmpdir, monkeypatch, capsys): + export_file = tmpdir.join('roles-export.json') + with export_file.open('w'): + export_file.write(json.dumps( + {'roles': [{ + 'uuid': 'dqfewrvesvews2532', 'slug': 'role-slug', 'name': 'role-name', + 'ou': None, 'service': None}]})) + + management.call_command('import_site', export_file.strpath) + + out, err = capsys.readouterr() + assert "Real run" in out + assert "1 roles created" in out + assert "0 roles updated" in out + + +def test_import_site_transaction_rollback_on_error(db, tmpdir, monkeypatch, capsys): + export_file = tmpdir.join('roles-export.json') + with export_file.open('w'): + export_file.write(json.dumps({'roles': []})) + + Role = get_role_model() + + def exception_import_site(*args): + Role.objects.create(slug='role-slug') + raise Exception() + + import authentic2.management.commands.import_site + monkeypatch.setattr( + authentic2.management.commands.import_site, 'import_site', exception_import_site) + + with pytest.raises(Exception): + management.call_command('import_site', export_file.strpath) + + with pytest.raises(Role.DoesNotExist): + Role.objects.get(slug='role-slug') + + +def test_import_site_transaction_rollback_on_dry_run(db, tmpdir, monkeypatch, capsys): + export_file = tmpdir.join('roles-export.json') + with export_file.open('w'): + export_file.write(json.dumps( + {'roles': [{ + 'uuid': 'dqfewrvesvews2532', 'slug': 'role-slug', 'name': 'role-name', + 'ou': None, 'service': None}]})) + + Role = get_role_model() + + management.call_command('import_site', '--dry-run', export_file.strpath) + + with pytest.raises(Role.DoesNotExist): + Role.objects.get(slug='role-slug') + + +def test_import_site_cmd_unhandled_context_option(db, tmpdir, monkeypatch, capsys): + from authentic2.data_transfer import DataImportError + + export_file = tmpdir.join('roles-export.json') + with export_file.open('w'): + export_file.write(json.dumps( + {'roles': [{ + 'uuid': 'dqfewrvesvews2532', 'slug': 'role-slug', 'name': 'role-name', + 'ou': None, 'service': None}]})) + + get_role_model().objects.create(uuid='dqfewrvesvews2532', slug='role-slug', name='role-name') + + with pytest.raises(DataImportError): + management.call_command( + 'import_site', '-o', 'role-delete-orphans', export_file.strpath) + + +def test_import_site_cmd_unknown_context_option(db, tmpdir, monkeypatch, capsys): + from django.core.management.base import CommandError + export_file = tmpdir.join('roles-export.json') + with pytest.raises(CommandError): + management.call_command('import_site', '-o', 'unknown-option', export_file.strpath) + + +def test_import_site_confirm_prompt_yes(db, tmpdir, monkeypatch): + export_file = tmpdir.join('roles-export.json') + with export_file.open('w'): + export_file.write(json.dumps( + {'roles': [{ + 'uuid': 'dqfewrvesvews2532', 'slug': 'role-slug', 'name': 'role-name', + 'ou': None, 'service': None}]})) + + def yes_raw_input(*args, **kwargs): + return 'yes' + + monkeypatch.setattr(__builtin__, 'raw_input', yes_raw_input) + + management.call_command('import_site', export_file.strpath, stdin='yes') + assert get_role_model().objects.get(uuid='dqfewrvesvews2532') -- 2.16.3