From a6ebdb053c1f1db73386961a0a648056fc33aeba Mon Sep 17 00:00:00 2001 From: Emmanuel Cazenave Date: Thu, 3 May 2018 11:40:25 +0200 Subject: [PATCH 1/2] add UI for import/export site (#23210) --- src/authentic2/forms.py | 4 + .../authentic2/manager/homepage.html | 9 +- .../authentic2/manager/site_import.html | 18 ++++ src/authentic2/manager/urls.py | 4 + src/authentic2/manager/views.py | 46 +++++++++- tests/test_manager.py | 84 +++++++++++++++++++ 6 files changed, 162 insertions(+), 3 deletions(-) create mode 100644 src/authentic2/manager/templates/authentic2/manager/site_import.html diff --git a/src/authentic2/forms.py b/src/authentic2/forms.py index 2f387a93..804481f8 100644 --- a/src/authentic2/forms.py +++ b/src/authentic2/forms.py @@ -191,3 +191,7 @@ class AuthenticationForm(auth_forms.AuthenticationForm): if keys: self.exponential_backoff.success(*keys) return self.cleaned_data + + +class SiteImportForm(forms.Form): + site_json = forms.FileField(label=_('Site Export File')) diff --git a/src/authentic2/manager/templates/authentic2/manager/homepage.html b/src/authentic2/manager/templates/authentic2/manager/homepage.html index 77d37c50..7f1f3833 100644 --- a/src/authentic2/manager/templates/authentic2/manager/homepage.html +++ b/src/authentic2/manager/templates/authentic2/manager/homepage.html @@ -5,7 +5,14 @@ {% endblock %} {% block appbar %} - {% blocktrans %}Here you can manage objects related to organizational units, users, roles and applications.{% endblocktrans %} +

{% blocktrans %}Here you can manage objects related to organizational units, users, roles and applications.{% endblocktrans %}

+ {% if user.is_superuser %} + + + {% endif %} {% endblock %} {% block content %} diff --git a/src/authentic2/manager/templates/authentic2/manager/site_import.html b/src/authentic2/manager/templates/authentic2/manager/site_import.html new file mode 100644 index 00000000..b6afd2b4 --- /dev/null +++ b/src/authentic2/manager/templates/authentic2/manager/site_import.html @@ -0,0 +1,18 @@ +{% extends "authentic2/manager/form.html" %} +{% load i18n %} + +{% block appbar %} +

{% trans "Site Import" %}

+{% endblock %} + +{% block content %} + +
+ {% csrf_token %} + {{ form.as_p }} +
+ + {% trans 'Cancel' %} +
+
+{% endblock %} diff --git a/src/authentic2/manager/urls.py b/src/authentic2/manager/urls.py index bac85aca..a5a21ce8 100644 --- a/src/authentic2/manager/urls.py +++ b/src/authentic2/manager/urls.py @@ -115,6 +115,10 @@ urlpatterns = required( # backoffice menu as json url(r'^menu.json$', views.menu_json), + + # general management + url(r'^site-export/$', views.site_export, name='a2-manager-site-export'), + url(r'^site-import/$', views.site_import, name='a2-manager-site-import'), ) ) diff --git a/src/authentic2/manager/views.py b/src/authentic2/manager/views.py index be85bb19..8f5c2b3c 100644 --- a/src/authentic2/manager/views.py +++ b/src/authentic2/manager/views.py @@ -5,7 +5,7 @@ from django.core.exceptions import PermissionDenied from django.views.generic.base import ContextMixin from django.views.generic.edit import FormMixinBase from django.views.generic import (FormView, UpdateView, CreateView, DeleteView, TemplateView, - DetailView) + DetailView, View) from django.views.generic.detail import SingleObjectMixin from django.http import HttpResponse, Http404 from django.utils.encoding import force_text @@ -21,7 +21,8 @@ from django_select2.views import AutoResponseView from django_rbac.utils import get_ou_model -from authentic2.forms import modelform_factory +from authentic2.data_transfer import export_site, import_site, DataImportError, ImportContext +from authentic2.forms import modelform_factory, SiteImportForm from authentic2.utils import redirect, batch_queryset from authentic2.decorators import json as json_view from authentic2 import hooks @@ -608,3 +609,44 @@ class Select2View(AutoResponseView): return widget select2 = Select2View.as_view() + + +class SiteExport(View): + + def get(self, request, *args, **kwargs): + if not request.user.is_superuser: + raise PermissionDenied + return HttpResponse( + json.dumps(export_site(), indent=4), content_type='application/json') + + +site_export = SiteExport.as_view() + + +class SiteImportView(FormView): + form_class = SiteImportForm + template_name = 'authentic2/manager/site_import.html' + success_url = reverse_lazy('a2-manager-homepage') + + def form_valid(self, form): + try: + json_site = json.load(self.request.FILES['site_json']) + except ValueError: + form.add_error('site_json', _('File is not in the expected JSON format.')) + return self.form_invalid(form) + + try: + import_site(json_site, ImportContext()) + except DataImportError as e: + form.add_error('site_json', unicode(e)) + return self.form_invalid(form) + + return super(SiteImportView, self).form_valid(form) + + def dispatch(self, request, *args, **kwargs): + if not request.user.is_superuser: + raise PermissionDenied + return super(SiteImportView, self).dispatch(request, *args, **kwargs) + + +site_import = SiteImportView.as_view() diff --git a/tests/test_manager.py b/tests/test_manager.py index d664ef99..f1fff03b 100644 --- a/tests/test_manager.py +++ b/tests/test_manager.py @@ -1,9 +1,12 @@ import pytest +import json from urlparse import urlparse from django.core.urlresolvers import reverse from django.core import mail +from webtest import Upload + from authentic2.a2_rbac.utils import get_default_ou from django_rbac.utils import get_ou_model, get_role_model @@ -552,3 +555,84 @@ def test_manager_many_ou_auto_admin_role(app, ou1, admin, user_with_auto_admin_r assert set(names) == {u'Auto Admin Role'} test_user_listing_auto_admin_role(user_with_auto_admin_role) + + +def test_manager_site_export(app, superuser): + response = login(app, superuser, '/manage/site-export/') + assert 'roles' in response.json + assert 'ous' in response.json + + +def test_manager_site_export_forbidden(app, simple_user): + login(app, simple_user) + app.get('/manage/site-export/', status=403) + + +def test_manager_site_import(app, db, superuser): + site_import = login(app, superuser, '/manage/site-import/') + form = site_import.form + site_export = { + 'roles': [ + { + "description": "", "service": None, "name": "basic", + "attributes": [], + "ou": { + "slug": "default", + "uuid": "ba60d9e6c2874636883bdd604b23eab2", + "name": "Collectivit\u00e9 par d\u00e9faut" + }, + "external_id": "", + "slug": "basic", + "uuid": "6eb7bbf64bf547119120f925f0e560ac" + }] + } + form['site_json'] = Upload( + 'site_export.json', json.dumps(site_export), 'application/octet-stream') + res = form.submit() + assert res.status_code == 302 + assert get_role_model().objects.get(slug='basic') + + +def test_manager_site_import_error(app, db, superuser): + site_import = login(app, superuser, '/manage/site-import/') + form = site_import.form + site_export = { + 'roles': [ + { + "description": "", "service": None, "name": "basic", + "attributes": [], + "ou": { + "slug": "unkown-ou", + "uuid": "ba60d9e6c2874636883bdd604b23eab2", + "name": "unkown ou" + }, + "external_id": "", + "slug": "basic", + "uuid": "6eb7bbf64bf547119120f925f0e560ac" + }] + } + form['site_json'] = Upload( + 'site_export.json', json.dumps(site_export), 'application/octet-stream') + res = form.submit() + assert res.status_code == 200 + assert 'missing Organizational Unit' in res.text + Role = get_role_model() + with pytest.raises(Role.DoesNotExist): + Role.objects.get(slug='basic') + + +def test_manager_site_import_forbidden(app, simple_user): + login(app, simple_user) + app.get('/manage/site-import/', status=403) + + +def test_manager_homepatge_import_export(superuser, app): + manager_home_page = login(app, superuser, reverse('a2-manager-homepage')) + assert 'site-import' in manager_home_page.text + assert 'site-export' in manager_home_page.text + + +def test_manager_homepatge_import_export_hidden(admin, app): + manager_home_page = login(app, admin, reverse('a2-manager-homepage')) + assert 'site-import' not in manager_home_page.text + assert 'site-export' not in manager_home_page.text -- 2.17.0