From 2e835c7c9b297b87faa7acb8a2dcd6839b10566e Mon Sep 17 00:00:00 2001 From: Alejandro Rodriguez Date: Tue, 31 Jan 2017 12:05:05 +0100 Subject: [PATCH 01/28] Alter json description --- .../migrations/0002_alter_json_description.py | 21 +++++++++++++++++++ src/wirecloud/catalogue/models.py | 3 ++- src/wirecloud/platform/widget/views.py | 2 +- src/wirecloud/platform/wiring/views.py | 5 +++-- 4 files changed, 27 insertions(+), 4 deletions(-) create mode 100644 src/wirecloud/catalogue/migrations/0002_alter_json_description.py diff --git a/src/wirecloud/catalogue/migrations/0002_alter_json_description.py b/src/wirecloud/catalogue/migrations/0002_alter_json_description.py new file mode 100644 index 0000000000..72e569e428 --- /dev/null +++ b/src/wirecloud/catalogue/migrations/0002_alter_json_description.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2017-01-30 12:24 +from __future__ import unicode_literals + +from django.db import migrations +import wirecloud.commons.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('catalogue', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='catalogueresource', + name='json_description', + field=wirecloud.commons.fields.JSONField(default={}, verbose_name='JSON description'), + ), + ] diff --git a/src/wirecloud/catalogue/models.py b/src/wirecloud/catalogue/models.py index 44008cacfc..f3ecae38ec 100644 --- a/src/wirecloud/catalogue/models.py +++ b/src/wirecloud/catalogue/models.py @@ -32,6 +32,7 @@ from whoosh.query import And, Every, Or, Term from whoosh.sorting import FieldFacet, FunctionFacet +from wirecloud.commons.fields import JSONField from wirecloud.commons.searchers import get_search_engine from wirecloud.commons.utils.http import get_absolute_reverse_url from wirecloud.commons.utils.template.parsers import TemplateParser @@ -65,7 +66,7 @@ class CatalogueResource(models.Model): popularity = models.DecimalField(_('popularity'), default=0, max_digits=2, decimal_places=1) - json_description = models.TextField(_('JSON description')) + json_description = JSONField(_('JSON description')) @property def local_uri_part(self): diff --git a/src/wirecloud/platform/widget/views.py b/src/wirecloud/platform/widget/views.py index 6c46cdeea5..8adfeaa111 100644 --- a/src/wirecloud/platform/widget/views.py +++ b/src/wirecloud/platform/widget/views.py @@ -54,7 +54,7 @@ def process_widget_code(request, resource): mode = request.GET.get('mode', 'classic') theme = request.GET.get('theme', get_active_theme_name()) - widget_info = json.loads(resource.json_description) + widget_info = resource.json_description # check if the xhtml code has been cached if widget_info['contents']['cacheable'] is True: diff --git a/src/wirecloud/platform/wiring/views.py b/src/wirecloud/platform/wiring/views.py index 4ada19401b..0ea19c395e 100644 --- a/src/wirecloud/platform/wiring/views.py +++ b/src/wirecloud/platform/wiring/views.py @@ -80,7 +80,7 @@ def checkWiring(self, request, new_wiring_status, old_wiring_status, can_update_ old_preference = old_operator['preferences'][preference_name] new_preference = operator['preferences'][preference_name] - # Check if the preference is + # Check if the preference is secure preference_secure = False if operator_preferences: for pref in operator_preferences: @@ -97,6 +97,7 @@ def checkWiring(self, request, new_wiring_status, old_wiring_status, can_update_ if preference_secure and not can_update_secure: new_preference["value"] = old_preference["value"] + return True @authentication_required @@ -164,7 +165,7 @@ def read(self, request, vendor, name, version): key = get_operator_cache_key(operator, get_current_domain(request), mode) cached_response = cache.get(key) if cached_response is None: - options = json.loads(operator.json_description) + options = operator.json_description js_files = options['js_files'] base_url = get_absolute_reverse_url('wirecloud.showcase_media', kwargs={ From b15201fd31b9e136cd77497c87402f4ffbf0f259 Mon Sep 17 00:00:00 2001 From: Alejandro Rodriguez Date: Tue, 31 Jan 2017 12:06:19 +0100 Subject: [PATCH 02/28] Create multuser support migration --- src/wirecloud/platform/migration_utils.py | 72 +++++++++++++++++++ .../0005_add_multiuser_variable_support.py | 18 +++++ 2 files changed, 90 insertions(+) create mode 100644 src/wirecloud/platform/migration_utils.py create mode 100644 src/wirecloud/platform/migrations/0005_add_multiuser_variable_support.py diff --git a/src/wirecloud/platform/migration_utils.py b/src/wirecloud/platform/migration_utils.py new file mode 100644 index 0000000000..f86db3df96 --- /dev/null +++ b/src/wirecloud/platform/migration_utils.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2017 CoNWeT Lab., Universidad Politécnica de Madrid + +# This file is part of Wirecloud. + +# Wirecloud is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# Wirecloud is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with Wirecloud. If not, see . + +from __future__ import unicode_literals + +from django.db import migrations, models +from django.db.migrations.exceptions import IrreversibleError +import six + +def update_variables_structure(apps, schema_editor): + + mutate = lambda value, userID: {"users": {userID: value}} + Workspace = apps.get_model("platform", "workspace") + + for workspace in Workspace.objects.select_related('creator').all(): + owner = workspace.creator.id + + # Update operators + wiring = workspace.wiringStatus + for op in wiring["operators"]: + wiring["operators"][op]["preferences"] = {k: mutate(v, owner) for k, v in six.iteritems(wiring["operators"][op]["preferences"])} + workspace.save() + + # Update widgets + for tab in workspace.tab_set.all(): + for widget in tab.iwidget_set.all(): + widget.variables = {k: mutate(v, owner) for k, v in six.iteritems(widget.variables)} + widget.save() + +def reverse_variables_structure(apps, schema_editor): + + # Check no multiuser widgets + CatalogueResource = apps.get_model("catalogue", "CatalogueResource") + + for component in CatalogueResource.objects.filter(type__in=(0, 2)).all(): + for property in component.json_description["properties"]: + if property.get("multiuser", False): + uri = component.vendor + '/' + component.short_name + '/' + component.version + raise IrreversibleError("Component %s requires multiuser support. Uninstall it before downgrading." % uri) + + mutate = lambda value, userID: value["users"]["%s" % userID] + Workspace = apps.get_model("platform", "workspace") + for workspace in Workspace.objects.select_related('creator').all(): + owner = workspace.creator.id + + # Update operators + wiring = workspace.wiringStatus + for op in wiring["operators"]: + wiring["operators"][op]["preferences"] = {k: mutate(v, owner) for k, v in six.iteritems(wiring["operators"][op]["preferences"])} + workspace.save() + + # Update widgets + for tab in workspace.tab_set.all(): + for widget in tab.iwidget_set.all(): + widget.variables = {k: mutate(v, owner) for k, v in six.iteritems(widget.variables)} + widget.save() \ No newline at end of file diff --git a/src/wirecloud/platform/migrations/0005_add_multiuser_variable_support.py b/src/wirecloud/platform/migrations/0005_add_multiuser_variable_support.py new file mode 100644 index 0000000000..d184cf0c92 --- /dev/null +++ b/src/wirecloud/platform/migrations/0005_add_multiuser_variable_support.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2017-01-30 10:49 +from __future__ import unicode_literals + +from django.db import migrations +from wirecloud.platform.migration_utils import update_variables_structure, reverse_variables_structure + + +class Migration(migrations.Migration): + + dependencies = [ + ('catalogue', '0002_alter_json_description'), + ('platform', '0004_auto_20160915_0024'), + ] + + operations = [ + migrations.RunPython(update_variables_structure, reverse_variables_structure), + ] From 472893cab6296db2e6ec7af9561b9293ffb9d7cd Mon Sep 17 00:00:00 2001 From: Alejandro Rodriguez Date: Wed, 1 Feb 2017 13:00:54 +0100 Subject: [PATCH 03/28] Fix json_description format --- src/wirecloud/catalogue/views.py | 2 +- src/wirecloud/platform/workspace/mashupTemplateGenerator.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wirecloud/catalogue/views.py b/src/wirecloud/catalogue/views.py index 811bb6b1b8..9cfb7cddb8 100644 --- a/src/wirecloud/catalogue/views.py +++ b/src/wirecloud/catalogue/views.py @@ -279,7 +279,7 @@ class ResourceDocumentationEntry(Resource): def read(self, request, vendor, name, version): resource = get_object_or_404(CatalogueResource, vendor=vendor, short_name=name, version=version) - resource_info = json.loads(resource.json_description) + resource_info = resource.json_description if resource_info['doc'] == '': raise Http404 diff --git a/src/wirecloud/platform/workspace/mashupTemplateGenerator.py b/src/wirecloud/platform/workspace/mashupTemplateGenerator.py index 823fd6de15..106e3b32f4 100644 --- a/src/wirecloud/platform/workspace/mashupTemplateGenerator.py +++ b/src/wirecloud/platform/workspace/mashupTemplateGenerator.py @@ -259,7 +259,7 @@ def build_json_template_from_workspace(options, workspace, user): vendor, name, version = operator['name'].split('/') resource = CatalogueResource.objects.get(vendor=vendor, short_name=name, version=version) - operator_info = json.loads(resource.json_description) + operator_info = resource.json_description operator_params = parametrization['ioperators'].get(id_, {}) for pref_index, preference in enumerate(operator_info['preferences']): From 5f734c97de2b0e9a73f26114f50f8436b7664d46 Mon Sep 17 00:00:00 2001 From: Alejandro Rodriguez Date: Fri, 3 Feb 2017 11:57:15 +0100 Subject: [PATCH 04/28] Add multiuser server-side for preferences and properties --- .../fixtures/user_with_workspaces.json | 25 ++-- .../platform/fixtures/test_data.json | 12 +- src/wirecloud/platform/iwidget/models.py | 7 +- src/wirecloud/platform/iwidget/utils.py | 11 +- src/wirecloud/platform/iwidget/views.py | 24 ++-- .../platform/localcatalogue/tests.py | 9 +- src/wirecloud/platform/tests/__init__.py | 2 +- src/wirecloud/platform/tests/rest_api.py | 84 ++++++++--- src/wirecloud/platform/wiring/tests.py | 57 +++++--- src/wirecloud/platform/wiring/views.py | 22 ++- .../workspace/mashupTemplateGenerator.py | 15 +- .../workspace/mashupTemplateParser.py | 11 +- src/wirecloud/platform/workspace/tests.py | 88 ++++++++---- src/wirecloud/platform/workspace/utils.py | 58 +++++--- src/wirecloud/proxy/processors.py | 11 +- src/wirecloud/proxy/tests.py | 135 +++++++++--------- 16 files changed, 360 insertions(+), 211 deletions(-) diff --git a/src/wirecloud/commons/fixtures/user_with_workspaces.json b/src/wirecloud/commons/fixtures/user_with_workspaces.json index f5c5208e42..f7d95236c6 100644 --- a/src/wirecloud/commons/fixtures/user_with_workspaces.json +++ b/src/wirecloud/commons/fixtures/user_with_workspaces.json @@ -11,7 +11,7 @@ "refused_version": null, "readOnly": false, "positions": "{\"widget\":{\"minimized\":false,\"height\":24,\"width\":6,\"zIndex\":0,\"left\":0,\"top\":0,\"fulldragboard\":false},\"icon\":{\"top\":0,\"left\":0}}", - "variables": "{\"list\": \"default\", \"text\": \"initial text\", \"boolean\": false, \"password\": \"default\", \"number\": 2}" + "variables": "{\"list\": {\"users\": {\"4\": \"default\"}}, \"text\": {\"users\": {\"4\": \"initial text\"}}, \"boolean\": {\"users\": {\"4\": false}}, \"password\": {\"users\": {\"4\": \"default\"}}, \"number\": {\"users\": {\"4\": 2}}}" } }, { @@ -26,7 +26,8 @@ "refused_version": null, "readOnly": false, "positions": "{\"widget\":{\"minimized\":false,\"height\":24,\"width\":6,\"zIndex\":1,\"left\":6,\"top\":0,\"fulldragboard\":false},\"icon\":{\"top\":0,\"left\":0}}", - "variables": "{\"list\": \"default\", \"text\": \"initial text\", \"boolean\": false, \"password\": \"default\", \"number\": 2}" + "variables": "{\"list\": {\"users\": {\"4\": \"default\"}}, \"text\": {\"users\": {\"4\": \"initial text\"}}, \"boolean\": {\"users\": {\"4\": false}}, \"password\": {\"users\": {\"4\": \"default\"}}, \"number\": {\"users\": {\"4\": 2}}}" + } }, { @@ -41,7 +42,7 @@ "refused_version": null, "readOnly": false, "positions": "{\"widget\":{\"minimized\":false,\"height\":24,\"width\":6,\"zIndex\":0,\"left\":0,\"top\":0,\"fulldragboard\":false},\"icon\":{\"top\":0,\"left\":0}}", - "variables": "{\"list\": \"default\", \"text\": \"initial text\", \"boolean\": false, \"password\": \"default\", \"number\": 2}" + "variables": "{\"list\": {\"users\": {\"4\": \"default\"}}, \"text\": {\"users\": {\"4\": \"initial text\"}}, \"boolean\": {\"users\": {\"4\": false}}, \"password\": {\"users\": {\"4\": \"default\"}}, \"number\": {\"users\": {\"4\": 2}}}" } }, { @@ -56,7 +57,7 @@ "refused_version": null, "readOnly": true, "positions": "{\"widget\":{\"minimized\":false,\"height\":24,\"width\":6,\"zIndex\":1,\"left\":6,\"top\":0,\"fulldragboard\":false},\"icon\":{\"top\":0,\"left\":0}}", - "variables": "{\"list\": \"default\", \"text\": \"initial text\", \"boolean\": false, \"password\": \"default\", \"number\": 2}" + "variables": "{\"list\": {\"users\": {\"4\": \"default\"}}, \"text\": {\"users\": {\"4\": \"initial text\"}}, \"boolean\": {\"users\": {\"4\": false}}, \"password\": {\"users\": {\"4\": \"default\"}}, \"number\": {\"users\": {\"4\": 2}}}" } }, { @@ -71,7 +72,7 @@ "refused_version": null, "readOnly": false, "positions": "{\"widget\":{\"minimized\":false,\"height\":24,\"width\":6,\"zIndex\":0,\"left\":0,\"top\":0,\"fulldragboard\":false},\"icon\":{\"top\":0,\"left\":0}}", - "variables": "{\"list\": \"default\", \"text\": \"initial text\", \"boolean\": false, \"password\": \"default\", \"number\": 2}" + "variables": "{\"list\": {\"users\": {\"4\": \"default\"}}, \"text\": {\"users\": {\"4\": \"initial text\"}}, \"boolean\": {\"users\": {\"4\": false}}, \"password\": {\"users\": {\"4\": \"default\"}}, \"number\": {\"users\": {\"4\": 2}}}" } }, { @@ -86,7 +87,7 @@ "refused_version": null, "readOnly": false, "positions": "{\"widget\":{\"minimized\":false,\"height\":24,\"width\":6,\"zIndex\":1,\"left\":6,\"top\":0,\"fulldragboard\":false},\"icon\":{\"top\":0,\"left\":0}}", - "variables": "{\"list\": \"default\", \"text\": \"initial text\", \"boolean\": false, \"password\": \"default\", \"number\": 2}" + "variables": "{\"list\": {\"users\": {\"4\": \"default\"}}, \"text\": {\"users\": {\"4\": \"initial text\"}}, \"boolean\": {\"users\": {\"4\": false}}, \"password\": {\"users\": {\"4\": \"default\"}}, \"number\": {\"users\": {\"4\": 2}}}" } }, { @@ -101,7 +102,7 @@ "refused_version": null, "readOnly": false, "positions": "{\"widget\":{\"minimized\":false,\"height\":24,\"width\":6,\"zIndex\":2,\"left\":12,\"top\":0,\"fulldragboard\":false},\"icon\":{\"top\":0,\"left\":0}}", - "variables": "{\"list\": \"default\", \"text\": \"initial text\", \"boolean\": false, \"password\": \"default\", \"number\": 2}" + "variables": "{\"list\": {\"users\": {\"4\": \"default\"}}, \"text\": {\"users\": {\"4\": \"initial text\"}}, \"boolean\": {\"users\": {\"4\": false}}, \"password\": {\"users\": {\"4\": \"default\"}}, \"number\": {\"users\": {\"4\": 2}}}" } }, { @@ -116,7 +117,7 @@ "refused_version": null, "readOnly": false, "positions": "{\"widget\":{\"minimized\":false,\"height\":24,\"width\":6,\"zIndex\":0,\"left\":0,\"top\":0,\"fulldragboard\":false},\"icon\":{\"top\":0,\"left\":0}}", - "variables": "{\"list\": \"default\", \"text\": \"initial text\", \"boolean\": false, \"password\": \"default\", \"number\": 2}" + "variables": "{\"list\": {\"users\": {\"4\": \"default\"}}, \"text\": {\"users\": {\"4\": \"initial text\"}}, \"boolean\": {\"users\": {\"4\": false}}, \"password\": {\"users\": {\"4\": \"default\"}}, \"number\": {\"users\": {\"4\": 2}}}" } }, { @@ -131,7 +132,7 @@ "refused_version": null, "readOnly": false, "positions": "{\"widget\":{\"minimized\":false,\"height\":24,\"width\":6,\"zIndex\":1,\"left\":6,\"top\":0,\"fulldragboard\":false},\"icon\":{\"top\":0,\"left\":0}}", - "variables": "{\"list\": \"default\", \"text\": \"initial text\", \"boolean\": false, \"password\": \"default\", \"number\": 2}" + "variables": "{\"list\": {\"users\": {\"4\": \"default\"}}, \"text\": {\"users\": {\"4\": \"initial text\"}}, \"boolean\": {\"users\": {\"4\": false}}, \"password\": {\"users\": {\"4\": \"default\"}}, \"number\": {\"users\": {\"4\": 2}}}" } }, { @@ -146,7 +147,7 @@ "refused_version": null, "readOnly": false, "positions": "{\"widget\":{\"minimized\":false,\"height\":24,\"width\":6,\"zIndex\":0,\"left\":0,\"top\":0,\"fulldragboard\":false},\"icon\":{\"top\":0,\"left\":0}}", - "variables": "{\"list\": \"default\", \"text\": \"initial text\", \"boolean\": false, \"password\": \"default\", \"number\": 2}" + "variables": "{\"list\": {\"users\": {\"4\": \"default\"}}, \"text\": {\"users\": {\"4\": \"initial text\"}}, \"boolean\": {\"users\": {\"4\": false}}, \"password\": {\"users\": {\"4\": \"default\"}}, \"number\": {\"users\": {\"4\": 2}}}" } }, { @@ -161,7 +162,7 @@ "refused_version": null, "readOnly": false, "positions": "{\"widget\":{\"minimized\":false,\"height\":24,\"width\":6,\"zIndex\":1,\"left\":6,\"top\":0,\"fulldragboard\":false},\"icon\":{\"top\":0,\"left\":0}}", - "variables": "{\"list\": \"default\", \"text\": \"initial text\", \"boolean\": false, \"password\": \"default\", \"number\": 2}" + "variables": "{\"list\": {\"users\": {\"4\": \"default\"}}, \"text\": {\"users\": {\"4\": \"initial text\"}}, \"boolean\": {\"users\": {\"4\": false}}, \"password\": {\"users\": {\"4\": \"default\"}}, \"number\": {\"users\": {\"4\": 2}}}" } }, { @@ -287,7 +288,7 @@ "name": "workspaceSecure", "public": false, "creator": "4", - "wiringStatus": "{\"operators\": {\"1\": {\"name\": \"Wirecloud/TestOperatorSecure/1.0\", \"preferences\": {\"pref_secure\": {\"secure\": true, \"readonly\": false, \"hidden\": false, \"value\": \"\"}, \"username\": {\"secure\": false, \"readonly\": true, \"hidden\": false, \"value\": \"username\"}}}, \"2\": {\"name\": \"Wirecloud/TestOperatorSecure/1.0\", \"preferences\": {\"pref_secure\": {\"secure\": true, \"readonly\": false, \"hidden\": false, \"value\": \"test_password\"}, \"username\": {\"secure\": false, \"readonly\": true, \"hidden\": false, \"value\": \"test_username\"}}}}, \"connections\": []}" + "wiringStatus": "{\"operators\": {\"1\": {\"name\": \"Wirecloud/TestOperatorSecure/1.0\", \"preferences\": {\"pref_secure\": {\"secure\": true, \"readonly\": false, \"hidden\": false, \"value\": {\"users\": {\"4\": \"\"}}}, \"username\": {\"secure\": false, \"readonly\": true, \"hidden\": false, \"value\": {\"users\": {\"4\": \"username\"}}}}}, \"2\": {\"name\": \"Wirecloud/TestOperatorSecure/1.0\", \"preferences\": {\"pref_secure\": {\"secure\": true, \"readonly\": false, \"hidden\": false, \"value\": {\"users\": {\"4\": \"test_password\"}}}, \"username\": {\"secure\": false, \"readonly\": true, \"hidden\": false, \"value\": {\"users\": {\"4\": \"test_username\"}}}}}}, \"connections\": []}" } }, { diff --git a/src/wirecloud/platform/fixtures/test_data.json b/src/wirecloud/platform/fixtures/test_data.json index b8b46e8f08..621f203bd3 100644 --- a/src/wirecloud/platform/fixtures/test_data.json +++ b/src/wirecloud/platform/fixtures/test_data.json @@ -60,7 +60,7 @@ "name": "workspace", "public": true, "creator": "2", - "wiringStatus": "{\"operators\": {\"1\": {\"name\": \"Wirecloud/TestOperator/1.0\", \"preferences\": {\"pref_with_val\": {\"readonly\": false, \"hidden\": false, \"value\": \"value1\"}, \"readonly_pref\": {\"readonly\": false, \"hidden\": false, \"value\": \"value2\"}, \"hidden_pref\": {\"readonly\": false, \"hidden\": false, \"value\": \"value3\"}, \"empty_pref\": {\"readonly\": false, \"hidden\": false, \"value\": \"value4\"}}}, \"2\": {\"name\": \"Wirecloud/TestOperator/1.0\", \"preferences\": {\"pref_with_val\": {\"readonly\": false, \"hidden\": false, \"value\": \"value1\"}, \"readonly_pref\": {\"readonly\": false, \"hidden\": false, \"value\": \"value2\"}, \"hidden_pref\": {\"readonly\": false, \"hidden\": false, \"value\": \"value3\"}, \"empty_pref\": {\"readonly\": false, \"hidden\": false, \"value\": \"\"}}}}, \"connections\": []}" + "wiringStatus": "{\"operators\": {\"1\": {\"name\": \"Wirecloud/TestOperator/1.0\", \"preferences\": {\"pref_with_val\": {\"readonly\": false, \"hidden\": false, \"value\": {\"users\": {\"2\": \"value1\" }}}, \"readonly_pref\": {\"readonly\": false, \"hidden\": false, \"value\": {\"users\": {\"2\": \"value2\"}}}, \"hidden_pref\": {\"readonly\": false, \"hidden\": false, \"value\": {\"users\": {\"2\": \"value3\"}}}, \"empty_pref\": {\"readonly\": false, \"hidden\": false, \"value\": {\"users\": {\"2\": \"value4\"}}}}}, \"2\": {\"name\": \"Wirecloud/TestOperator/1.0\", \"preferences\": {\"pref_with_val\": {\"readonly\": false, \"hidden\": false, \"value\": {\"users\": {\"2\": \"value1\"}}}, \"readonly_pref\": {\"readonly\": false, \"hidden\": false, \"value\": {\"users\": {\"2\": \"value2\"}}}, \"hidden_pref\": {\"readonly\": false, \"hidden\": false, \"value\": {\"users\": {\"2\": \"value3\"}}}, \"empty_pref\": {\"readonly\": false, \"hidden\": false, \"value\": {\"users\": {\"2\": \"\"}}}}}}, \"connections\": []}" } }, { @@ -80,7 +80,7 @@ "name": "workspaceSecure", "public": false, "creator": "2", - "wiringStatus": "{\"operators\": {\"1\": {\"name\": \"Wirecloud/TestOperatorSecure/1.0\", \"preferences\": {\"pref_secure\": {\"secure\": true, \"readonly\": false, \"hidden\": false, \"value\": \"\"}, \"username\": {\"secure\": false, \"readonly\": false, \"hidden\": false, \"value\": \"username\"}}}, \"2\": {\"name\": \"Wirecloud/TestOperatorSecure/1.0\", \"preferences\": {\"pref_secure\": {\"secure\": true, \"readonly\": false, \"hidden\": false, \"value\": \"test_password\"}, \"username\": {\"secure\": false, \"readonly\": false, \"hidden\": false, \"value\": \"test_username\"}}}}, \"connections\": []}" + "wiringStatus": "{\"operators\": {\"1\": {\"name\": \"Wirecloud/TestOperatorSecure/1.0\", \"preferences\": {\"pref_secure\": {\"secure\": true, \"readonly\": false, \"hidden\": false, \"value\": {\"users\": {\"2\": \"\"}}}, \"username\": {\"secure\": false, \"readonly\": false, \"hidden\": false, \"value\": {\"users\": {\"2\": \"username\"}}}}}, \"2\": {\"name\": \"Wirecloud/TestOperatorSecure/1.0\", \"preferences\": {\"pref_secure\": {\"secure\": true, \"readonly\": false, \"hidden\": false, \"value\": {\"users\": {\"2\": \"test_password\"}}}, \"username\": {\"secure\": false, \"readonly\": false, \"hidden\": false, \"value\": {\"users\": {\"2\": \"test_username\"}}}}}}, \"connections\": []}" } }, { @@ -126,7 +126,7 @@ "name": "Test Widget", "positions": "{\"widget\":{\"minimized\":false,\"height\":24,\"width\":6,\"zIndex\":0,\"left\":0,\"top\":0,\"fulldragboard\":false},\"icon\":{\"top\":0,\"left\":0}}", "tab": "1", - "variables": "{\"password\": \"test_password\", \"username\": \"test_username\", \"prop\": \"test_data\"}" + "variables": "{\"password\": {\"users\": {\"2\": \"test_password\"}}, \"username\": {\"users\": {\"2\": \"test_username\"}}, \"prop\": {\"users\": {\"2\": \"test_data\"}}}" } }, { @@ -138,7 +138,7 @@ "name": "Test Widget 2", "positions": "{\"widget\":{\"minimized\":false,\"height\":24,\"width\":6,\"zIndex\":1,\"left\":5,\"top\":0,\"fulldragboard\":false},\"icon\":{\"top\":0,\"left\":0}}", "tab": "1", - "variables": "{\"password\": \"test_password\", \"username\": \"test_username\", \"prop\": \"test_data\"}" + "variables": "{\"password\": {\"users\": {\"2\": \"test_password\"}}, \"username\": {\"users\": {\"2\": \"test_username\"}}, \"prop\": {\"users\": {\"2\": \"test_data\"}}}" } }, { @@ -150,7 +150,7 @@ "name": "Secure Widget 1", "positions": "{\"widget\":{\"minimized\":false,\"height\":24,\"width\":6,\"zIndex\":0,\"left\":0,\"top\":0,\"fulldragboard\":false},\"icon\":{\"top\":0,\"left\":0}}", "tab": "2", - "variables": "{\"password\": \"\", \"username\": \"test_username\", \"prop\": \"test_data\"}" + "variables": "{\"password\": {\"users\": {\"2\": \"\"}}, \"username\": {\"users\": {\"2\": \"test_username\"}}, \"prop\": {\"users\": {\"2\": \"test_data\"}}}" } }, { @@ -162,7 +162,7 @@ "name": "Secure Widget 2", "positions": "{\"widget\":{\"minimized\":false,\"height\":24,\"width\":6,\"zIndex\":0,\"left\":0,\"top\":0,\"fulldragboard\":false},\"icon\":{\"top\":0,\"left\":0}}", "tab": "2", - "variables": "{\"password\": \"test_password\", \"username\": \"test_username\", \"prop\": \"test_data\"}" + "variables": "{\"password\": {\"users\": {\"2\": \"test_password\"}}, \"username\": {\"users\": {\"2\": \"test_username\"}}, \"prop\": {\"users\": {\"2\": \"test_data\"}}}" } }, { diff --git a/src/wirecloud/platform/iwidget/models.py b/src/wirecloud/platform/iwidget/models.py index 93b6f97e5b..a17478a02e 100644 --- a/src/wirecloud/platform/iwidget/models.py +++ b/src/wirecloud/platform/iwidget/models.py @@ -45,7 +45,7 @@ class Meta: def __str__(self): return str(self.pk) - def set_variable_value(self, var_name, value): + def set_variable_value(self, var_name, value, user): iwidget_info = self.widget.resource.get_processed_info(translate=False, process_variables=True) @@ -58,7 +58,10 @@ def set_variable_value(self, var_name, value): elif vardef['type'] == 'number': value = float(value) - self.variables[var_name] = value + if "users" in self.variables.get(var_name, ""): + self.variables[var_name]["users"] = {"%s" % user.id: value} + else: + self.variables[var_name] = {"users": {"%s" % user.id: value}} def save(self, *args, **kwargs): diff --git a/src/wirecloud/platform/iwidget/utils.py b/src/wirecloud/platform/iwidget/utils.py index 0427e6f514..3ef4b3aaae 100644 --- a/src/wirecloud/platform/iwidget/utils.py +++ b/src/wirecloud/platform/iwidget/utils.py @@ -47,11 +47,10 @@ def process_initial_value(vardef, initial_value=None): elif vardef.get('value', None) is not None: value = vardef['value'] elif vardef['default']: - value = vardef['default'] + value = parse_value_from_text(vardef, vardef['default']) else: value = '' - - return parse_value_from_text(vardef, value) + return value def update_title_value(iwidget, data): @@ -143,14 +142,14 @@ def update_widget_value(iwidget, data, user, required=False): raise ValueError('Missing widget info') -def set_initial_values(iwidget, initial_values, iwidget_info): +def set_initial_values(iwidget, initial_values, iwidget_info, user): for vardef in (iwidget_info['preferences'] + iwidget_info['properties']): if vardef['name'] in initial_values: initial_value = initial_values[vardef['name']] else: initial_value = None - iwidget.set_variable_value(vardef['name'], process_initial_value(vardef, initial_value)) + iwidget.set_variable_value(vardef['name'], process_initial_value(vardef, initial_value), user) def SaveIWidget(iwidget, user, tab, initial_variable_values=None, commit=True): @@ -180,7 +179,7 @@ def SaveIWidget(iwidget, user, tab, initial_variable_values=None, commit=True): } if initial_variable_values is not None: - set_initial_values(new_iwidget, initial_variable_values, iwidget_info) + set_initial_values(new_iwidget, initial_variable_values, iwidget_info, user) update_title_value(new_iwidget, iwidget) update_position(new_iwidget, 'widget', iwidget) diff --git a/src/wirecloud/platform/iwidget/views.py b/src/wirecloud/platform/iwidget/views.py index e6bf1785a0..ed6e763b84 100644 --- a/src/wirecloud/platform/iwidget/views.py +++ b/src/wirecloud/platform/iwidget/views.py @@ -171,9 +171,6 @@ class IWidgetPreferences(Resource): def create(self, request, workspace_id, tab_id, iwidget_id): workspace = get_object_or_404(Workspace, pk=workspace_id) - if not request.user.is_superuser and workspace.creator != request.user: - msg = _('You have not enough permission for updating the preferences of the iwidget') - return build_error_response(request, 403, msg) iwidget = get_object_or_404(IWidget.objects.select_related('widget__resource'), pk=iwidget_id) if iwidget.tab_id != int(tab_id): @@ -194,7 +191,14 @@ def create(self, request, workspace_id, tab_id, iwidget_id): msg = _('"%s" preference is read only.') % var_name return build_error_response(request, 403, msg) - iwidget.set_variable_value(var_name, new_values[var_name]) + # Check if its multiuser + if not vardef.get("multiuser", False): + # No multiuser -> Check permisisons + if not request.user.is_superuser and workspace.creator != request.user: + msg = _('You have not enough permission for updating the preferences of the iwidget') + return build_error_response(request, 403, msg) + + iwidget.set_variable_value(var_name, new_values[var_name], request.user) iwidget.save() return HttpResponse(status=204) @@ -208,9 +212,6 @@ class IWidgetProperties(Resource): def create(self, request, workspace_id, tab_id, iwidget_id): workspace = get_object_or_404(Workspace, pk=workspace_id) - if not request.user.is_superuser and workspace.creator != request.user: - msg = _('You have not enough permission for updating the persistent variables of this widget') - return build_error_response(request, 403, msg) iwidget = get_object_or_404(IWidget, pk=iwidget_id) if iwidget.tab_id != int(tab_id): @@ -225,7 +226,14 @@ def create(self, request, workspace_id, tab_id, iwidget_id): msg = _('Invalid persistent variable: "%s"') % var_name return build_error_response(request, 422, msg) - iwidget.set_variable_value(var_name, new_values[var_name]) + # Check if its multiuser + if not iwidget_info['variables']['properties'][var_name].get("multiuser", False): + # No multiuser -> Check permisisons + if workspace.creator != request.user: + msg = _('You have not enough permission for updating the persistent variables of this widget') + return build_error_response(request, 403, msg) + + iwidget.set_variable_value(var_name, new_values[var_name], request.user) iwidget.save() return HttpResponse(status=204) diff --git a/src/wirecloud/platform/localcatalogue/tests.py b/src/wirecloud/platform/localcatalogue/tests.py index 4a2123c326..acd5499b2b 100644 --- a/src/wirecloud/platform/localcatalogue/tests.py +++ b/src/wirecloud/platform/localcatalogue/tests.py @@ -20,7 +20,6 @@ from __future__ import unicode_literals from io import BytesIO -import json import os.path import time import zipfile @@ -116,7 +115,7 @@ def check_basic_widget_info(self, resource): self.assertEqual(data['preferences'], [{'default': 'value', 'secure': False, 'name': 'pref', 'label': 'Preference label', 'type': 'list', 'options': [{'value': '1', 'label': 'Option name'}], 'readonly': False, 'description': 'Preference description', 'value': None}]) self.assertEqual(len(data['wiring']['inputs']), 1) - self.assertEqual(data['wiring']['inputs'], [{'name': 'slot', 'label': 'Slot label', 'type': 'text', 'description': '','friendcode': 'test_friend_code', 'actionlabel': ''}]) + self.assertEqual(data['wiring']['inputs'], [{'name': 'slot', 'label': 'Slot label', 'type': 'text', 'description': '', 'friendcode': 'test_friend_code', 'actionlabel': ''}]) self.assertEqual(len(data['wiring']['outputs']), 1) self.assertEqual(data['wiring']['outputs'], [{'name': 'event', 'label': 'Event label', 'type': 'text', 'description': '', 'friendcode': 'test_friend_code'}]) @@ -127,7 +126,7 @@ def test_widget_with_minimal_info(self): added, resource = install_resource_to_user(self.user, file_contents=file_contents) self.assertTrue(added) - resource_info = json.loads(resource.json_description) + resource_info = resource.json_description self.assertEqual(resource.vendor, 'Wirecloud') self.assertEqual(resource.short_name, 'test') self.assertEqual(resource.version, '0.1') @@ -335,7 +334,7 @@ def test_template_translations(self): self.assertEqual(data['preferences'], [{'default': 'value', 'secure': False, 'name': 'pref', 'label': 'Etiqueta de la preferencia', 'type': 'list', 'options': [{'value': '1', 'label': 'Nombre de la opción'}], 'readonly': False, 'description': 'Descripción de la preferencia', 'value': None}]) self.assertEqual(len(data['wiring']['inputs']), 1) - self.assertEqual(data['wiring']['inputs'], [{'name': 'slot', 'label': 'Etiqueta del endpoint de entrada', 'type': 'text', 'description': '','friendcode': 'test_friend_code', 'actionlabel': ''}]) + self.assertEqual(data['wiring']['inputs'], [{'name': 'slot', 'label': 'Etiqueta del endpoint de entrada', 'type': 'text', 'description': '', 'friendcode': 'test_friend_code', 'actionlabel': ''}]) self.assertEqual(len(data['wiring']['outputs']), 1) self.assertEqual(data['wiring']['outputs'], [{'name': 'event', 'label': 'Etiqueta del endpoint de salida', 'type': 'text', 'description': '', 'friendcode': 'test_friend_code'}]) @@ -357,7 +356,7 @@ def test_repeated_translation_indexes(self): self.assertEqual(data['preferences'], [{'default': 'value', 'secure': False, 'name': 'pref', 'label': 'Label', 'readonly': False, 'type': 'text', 'description': 'Preference description', 'value': None}]) self.assertEqual(len(data['wiring']['inputs']), 1) - self.assertEqual(data['wiring']['inputs'], [{'name': 'slot', 'label': 'Label', 'type': 'text', 'description': '','friendcode': 'test_friend_code', 'actionlabel': ''}]) + self.assertEqual(data['wiring']['inputs'], [{'name': 'slot', 'label': 'Label', 'type': 'text', 'description': '', 'friendcode': 'test_friend_code', 'actionlabel': ''}]) self.assertEqual(len(data['wiring']['outputs']), 1) self.assertEqual(data['wiring']['outputs'], [{'name': 'event', 'label': 'Label', 'type': 'text', 'description': '', 'friendcode': 'test_friend_code'}]) diff --git a/src/wirecloud/platform/tests/__init__.py b/src/wirecloud/platform/tests/__init__.py index ebb824d6ae..95930d36c7 100644 --- a/src/wirecloud/platform/tests/__init__.py +++ b/src/wirecloud/platform/tests/__init__.py @@ -28,5 +28,5 @@ from wirecloud.platform.markets.tests import * from wirecloud.platform.wiring.tests import * from wirecloud.platform.widget.tests import CodeTransformationTestCase, WidgetModuleTestCase -from wirecloud.platform.workspace.tests import WorkspaceTestCase, WorkspaceCacheTestCase, ParameterizedWorkspaceParseTestCase, ParameterizedWorkspaceGenerationTestCase +from wirecloud.platform.workspace.tests import WorkspaceMigrationsTestCase, WorkspaceTestCase, WorkspaceCacheTestCase, ParameterizedWorkspaceParseTestCase, ParameterizedWorkspaceGenerationTestCase from wirecloud.proxy.tests import ProxyTests, ProxySecureDataTests diff --git a/src/wirecloud/platform/tests/rest_api.py b/src/wirecloud/platform/tests/rest_api.py index daed773873..f751fb5475 100644 --- a/src/wirecloud/platform/tests/rest_api.py +++ b/src/wirecloud/platform/tests/rest_api.py @@ -1201,7 +1201,7 @@ def test_workspace_wiring_entry_patch_preference_value_read_only_permission(self self.assertEqual(response.status_code, 403) # Check if preferences changed - self.assertEqual(Workspace.objects.get(pk=202).wiringStatus["operators"]["2"]["preferences"]["username"]["value"], "test_username") + self.assertEqual(Workspace.objects.get(pk=202).wiringStatus["operators"]["2"]["preferences"]["username"]["value"]["users"]["4"], "test_username") def test_workspace_wiring_entry_patch_empty_request(self): @@ -1243,7 +1243,6 @@ def test_workspace_wiring_entry_patch_invalid_patch_operation(self): response = self.client.patch(url, json.dumps(data), content_type='application/json-patch+json; charset=UTF-8') self.assertEqual(response.status_code, 400) - def test_tab_collection_post_requires_authentication(self): url = reverse('wirecloud.tab_collection', kwargs={'workspace_id': 1}) @@ -1340,7 +1339,6 @@ def test_tab_collection_post_bad_request_syntax(self): url = reverse('wirecloud.tab_collection', kwargs={'workspace_id': 1}) check_post_bad_request_syntax(self, url) - def test_tab_entry_get_requires_permission(self): url = reverse('wirecloud.tab_entry', kwargs={'workspace_id': 1, 'tab_id': 1}) @@ -1888,9 +1886,10 @@ def test_widget_code_entry_get_bad_encoding(self): with open(os.path.join(base_dir, 'test.html'), 'wb') as f: f.write('
á'.encode('iso-8859-15')) resource = CatalogueResource.objects.get(vendor='Wirecloud', short_name='Test', version='1.0') - json_description = json.loads(resource.json_description) + json_description = resource.json_description + json_description['contents']['contenttype'] = 'application/xhtml+xml' - resource.json_description = json.dumps(json_description) + resource.json_description = json_description resource.save() # Authenticate @@ -1912,9 +1911,10 @@ def test_widget_code_entry_get_bad_encoding_noncacheable(self): with open(os.path.join(base_dir, 'test.html'), 'wb') as f: f.write('
á'.encode('iso-8859-15')) resource = CatalogueResource.objects.get(vendor='Wirecloud', short_name='Test', version='1.0') - json_description = json.loads(resource.json_description) + json_description = resource.json_description + json_description['contents']['contenttype'] = 'application/xhtml+xml' - resource.json_description = json.dumps(json_description) + resource.json_description = json_description resource.save() from wirecloud.platform.models import XHTML @@ -1965,9 +1965,10 @@ def test_widget_code_entry_get_invalid_html(self): with open(os.path.join(base_dir, 'test.html'), 'wb') as f: f.write(b'
') resource = CatalogueResource.objects.get(vendor='Wirecloud', short_name='Test', version='1.0') - json_description = json.loads(resource.json_description) + json_description = resource.json_description + json_description['contents']['contenttype'] = 'application/xhtml+xml' - resource.json_description = json.dumps(json_description) + resource.json_description = json_description resource.save() # Authenticate @@ -2253,7 +2254,7 @@ def update_iwidget_preference(): # IWidget preferences should be updated variables = IWidget.objects.get(pk=2).variables - self.assertEqual(variables['text'], 'new value') + self.assertEqual(variables['text']["users"]["4"], 'new value') check_cache_is_purged(self, 2, update_iwidget_preference) @@ -2316,7 +2317,8 @@ def test_iwidget_preferences_entry_post_nonexistent_preference(self): def test_iwidget_preferences_entry_post_readonly_preference(self): resource = CatalogueResource.objects.get(vendor="Wirecloud", short_name="Test", version="1.0") - json_description = json.loads(resource.json_description) + json_description = resource.json_description + json_description['preferences'] = [{'secure': False, 'name': 'text', 'default': 'initial text', 'label': 'text', 'type': 'text', 'description': 'text preference', 'readonly': True}] resource.json_description = json.dumps(json_description) resource.save() @@ -2335,12 +2337,13 @@ def test_iwidget_preferences_entry_post_readonly_preference(self): response_data = json.loads(response.content.decode('utf-8')) self.assertTrue(isinstance(response_data, dict)) - def _todo_create_property(self): + def _todo_create_property(self, multiuser=False): # TODO resource = CatalogueResource.objects.get(vendor="Wirecloud", short_name="Test", version="1.0") - json_description = json.loads(resource.json_description) - json_description['properties'] = [{'secure': False, 'name': 'prop', 'default': '', 'label': '', 'type': 'text'}] + json_description = resource.json_description + + json_description['properties'] = [{'secure': False, 'name': 'prop', 'default': '', 'label': '', 'type': 'text', 'multiuser': multiuser}] resource.json_description = json.dumps(json_description) resource.save() # end TODO @@ -2364,6 +2367,7 @@ def iwidget_preference_not_created(self): check_post_requires_authentication(self, url, json.dumps(data), iwidget_preference_not_created) def test_iwidget_properties_entry_post_requires_permission(self): + self._todo_create_property() url = reverse('wirecloud.iwidget_properties', kwargs={'workspace_id': 2, 'tab_id': 101, 'iwidget_id': 2}) data = { @@ -2371,6 +2375,22 @@ def test_iwidget_properties_entry_post_requires_permission(self): } check_post_requires_permission(self, url, json.dumps(data)) + def test_iwidget_properties_entry_post_multiuser_doesnt_require_permission(self): + self._todo_create_property(True) + + # Authenticate + self.client.login(username='emptyuser', password='admin') + + url = reverse('wirecloud.iwidget_properties', kwargs={'workspace_id': 2, 'tab_id': 101, 'iwidget_id': 2}) + data = { + 'prop': 'new value', + } + + response = self.client.post(url, json.dumps(data), content_type='application/json; charset=UTF-8', HTTP_ACCEPT='application/json') + self.assertEqual(response.status_code, 204) + variables = IWidget.objects.get(pk=2).variables + self.assertEqual(variables['prop']["users"]["5"], 'new value') + def test_iwidget_properties_entry_post(self): self._todo_create_property() @@ -2391,9 +2411,32 @@ def update_iwidget_property(): # IWidget properties should be updated variables = IWidget.objects.get(pk=2).variables - self.assertEqual(variables['prop'], 'new value') + + self.assertEqual(variables['prop']['users']['4'], 'new value') check_cache_is_purged(self, 2, update_iwidget_property) + def test_iwidget_properties_entry_post_multiuser(self): + self._todo_create_property(True) + + url = reverse('wirecloud.iwidget_properties', kwargs={'workspace_id': 2, 'tab_id': 101, 'iwidget_id': 2}) + + # Authenticate + self.client.login(username='user_with_workspaces', password='admin') + + # Make the request + def update_iwidget_multiuser_property(): + data = { + 'prop': 'new value', + } + response = self.client.post(url, json.dumps(data), content_type='application/json; charset=UTF-8', HTTP_ACCEPT='application/json') + self.assertEqual(response.status_code, 204) + self.assertEqual(response.content.decode('utf-8'), '') + + # IWidget properties should be updated + variables = IWidget.objects.get(pk=2).variables + self.assertEqual(variables['prop']['users']['4'], 'new value') + check_cache_is_purged(self, 2, update_iwidget_multiuser_property) + def test_widget_properties_post_workspace_not_found(self): url = reverse('wirecloud.iwidget_properties', kwargs={'workspace_id': 404, 'tab_id': 101, 'iwidget_id': 2}) @@ -3035,7 +3078,7 @@ def test_resource_collection_post_public(self): response = self.client.post(url, f.read(), content_type="application/octet-stream", HTTP_ACCEPT='application/json') self.assertEqual(response.status_code, 201) - resource = CatalogueResource.objects.get(vendor= 'Wirecloud', short_name= 'Test_Selenium', version= '1.0') + resource = CatalogueResource.objects.get(vendor='Wirecloud', short_name='Test_Selenium', version='1.0') self.assertTrue(resource.public) self.assertEqual(list(resource.users.values_list('username', flat=True)), ['admin']) @@ -3051,7 +3094,7 @@ def test_resource_collection_post_public_normuser(self): response = self.client.post(url, f.read(), content_type="application/octet-stream", HTTP_ACCEPT='application/json') self.assertEqual(response.status_code, 403) - self.assertRaises(CatalogueResource.DoesNotExist, CatalogueResource.objects.get, vendor= 'Wirecloud', short_name= 'Test_Selenium', version= '1.0') + self.assertRaises(CatalogueResource.DoesNotExist, CatalogueResource.objects.get, vendor='Wirecloud', short_name='Test_Selenium', version='1.0') def test_resource_entry_get_requires_authentication(self): @@ -3105,12 +3148,12 @@ def test_resource_entry_delete_uninstall(self): response = self.client.delete(url, HTTP_ACCEPT='application/json') self.assertEqual(response.status_code, 204) - resource = CatalogueResource.objects.get(vendor= 'Wirecloud', short_name= 'Test', version= '1.0') + resource = CatalogueResource.objects.get(vendor='Wirecloud', short_name='Test', version='1.0') self.assertFalse(resource.users.filter(username='admin').exists()) def test_resource_entry_delete(self): - resource = CatalogueResource.objects.get(vendor= 'Wirecloud', short_name= 'Test', version= '1.0') + resource = CatalogueResource.objects.get(vendor='Wirecloud', short_name='Test', version='1.0') resource.users.clear() resource.users.add(2) resource.public = False @@ -3122,7 +3165,7 @@ def test_resource_entry_delete(self): response = self.client.delete(url, HTTP_ACCEPT='application/json') self.assertEqual(response.status_code, 204) - self.assertRaises(CatalogueResource.DoesNotExist, CatalogueResource.objects.get, vendor= 'Wirecloud', short_name= 'Test', version= '1.0') + self.assertRaises(CatalogueResource.DoesNotExist, CatalogueResource.objects.get, vendor='Wirecloud', short_name='Test', version='1.0') def test_resource_entry_delete_not_found(self): @@ -3749,7 +3792,6 @@ def merge_workspaces(): } response = self.client.post(url, json.dumps(data), content_type='application/json; charset=UTF-8', HTTP_ACCEPT='application/json') self.assertEqual(response.status_code, 204) - # Check new workspace status_code workspace = Workspace.objects.get(pk=2) tabs = workspace.tab_set.order_by('position')[:] diff --git a/src/wirecloud/platform/wiring/tests.py b/src/wirecloud/platform/wiring/tests.py index b9e9422934..80a948d432 100644 --- a/src/wirecloud/platform/wiring/tests.py +++ b/src/wirecloud/platform/wiring/tests.py @@ -261,7 +261,7 @@ def test_read_only_preferences_cannot_be_modified(self): 'id': '1', 'name': 'Wirecloud/TestOperator/1.0', 'preferences': { - 'pref1': {'hidden': False, 'readonly': True, 'value': 'a'}, + 'pref1': {'hidden': False, 'readonly': True, 'value': {"users": {"2": 'a'}}}, } }, }, @@ -278,7 +278,7 @@ def test_read_only_preferences_cannot_be_modified(self): 'id': '1', 'name': 'Wirecloud/TestOperator/1.0', 'preferences': { - 'pref1': {'readonly': True, 'value': 'b'}, + 'pref1': {'readonly': True, 'value': {"users": {"2": 'b'}}}, } }, }, @@ -296,7 +296,7 @@ def test_normal_preferences_can_be_removed(self): 'id': '1', 'name': 'Wirecloud/TestOperator/1.0', 'preferences': { - 'pref1': {'hidden': False, 'readonly': False, 'value': 'a'}, + 'pref1': {'hidden': False, 'readonly': False, 'value': {"users": {"2": 'a'}}}, } }, }, @@ -329,7 +329,7 @@ def test_read_only_preferences_cannot_be_removed(self): 'id': '1', 'name': 'Wirecloud/TestOperator/1.0', 'preferences': { - 'pref1': {'hidden': False, 'readonly': True, 'value': 'a'}, + 'pref1': {'hidden': False, 'readonly': True, 'value': {"users": {"2": 'a'}}}, } }, }, @@ -428,7 +428,7 @@ def test_hidden_status_cannot_be_removed(self): 'id': '1', 'name': 'Wirecloud/TestOperator/1.0', 'preferences': { - 'pref1': {'hidden': True, 'readonly': False, 'value': 'a'}, + 'pref1': {'hidden': True, 'readonly': False, 'value': {"users": {"2": 'a'}}}, } }, }, @@ -445,7 +445,7 @@ def test_hidden_status_cannot_be_removed(self): 'id': '1', 'name': 'Wirecloud/TestOperator/1.0', 'preferences': { - 'pref1': {'hidden': False, 'readonly': False, 'value': 'a'}, + 'pref1': {'hidden': False, 'readonly': False, 'value': {"users": {"2": 'a'}}}, } }, }, @@ -479,7 +479,7 @@ def test_normal_preferences_can_be_added(self): 'name': 'Wirecloud/TestOperator/1.0', 'preferences': { 'pref1': {'value': 'a'}, - 'pref2': {'hidden': False, 'readonly': False, 'value': 'a'}, + 'pref2': {'hidden': False, 'readonly': False, 'value': {"users": {"2": 'a'}}}, }, }, }, @@ -497,8 +497,8 @@ def test_normal_preferences_can_be_updated(self): 'id': '1', 'name': 'Wirecloud/TestOperator/1.0', 'preferences': { - 'pref1': {'hidden': True, 'readonly': False, 'value': 'a'}, - 'pref2': {'hidden': False, 'readonly': False, 'value': 'b'}, + 'pref1': {'hidden': True, 'readonly': False, 'value': {"users": {"2": 'a'}}}, + 'pref2': {'hidden': False, 'readonly': False, 'value': {"users": {"2": 'b'}}}, }, }, }, @@ -515,8 +515,8 @@ def test_normal_preferences_can_be_updated(self): 'id': '1', 'name': 'Wirecloud/TestOperator/1.0', 'preferences': { - 'pref1': {'hidden': True, 'readonly': False, 'value': 'b'}, - 'pref2': {'hidden': False, 'readonly': False, 'value': 'c'}, + 'pref1': {'hidden': True, 'readonly': False, 'value': {"users": {"2": 'b'}}}, + 'pref2': {'hidden': False, 'readonly': False, 'value': {"users": {"2": 'c'}}}, } }, }, @@ -525,7 +525,7 @@ def test_normal_preferences_can_be_updated(self): response = client.put(self.wiring_url, data, content_type='application/json') self.assertEqual(response.status_code, 204) - def test_operator_added(self): + def test_operator_added_put(self): client = Client() client.login(username='test', password='test') @@ -536,8 +536,8 @@ def test_operator_added(self): 'id': '1', 'name': 'Wirecloud/TestOperator/1.0', 'preferences': { - 'pref1': {'hidden': False, 'readonly': False, 'value': 'b'}, - 'pref2': {'hidden': False, 'readonly': False, 'value': 'c'}, + 'pref1': {'hidden': False, 'readonly': False, 'value': {"users": {"2": 'b'}}}, + 'pref2': {'hidden': False, 'readonly': False, 'value': {"users": {"2": 'c'}}}, } }, }, @@ -546,6 +546,27 @@ def test_operator_added(self): response = client.put(self.wiring_url, data, content_type='application/json') self.assertEqual(response.status_code, 204) + def test_operator_added_patch(self): + client = Client() + client.login(username='test', password='test') + + data = json.dumps([ + { + 'op': "add", + 'path': "/operators/1", + 'value': { + 'id': '1', + 'name': 'Wirecloud/TestOperator/1.0', + 'preferences': { + 'pref1': {'hidden': False, 'readonly': False, 'value': {"users": {"2": 'b'}}}, + 'pref2': {'hidden': False, 'readonly': False, 'value': {"users": {"2": 'c'}}}, + }, + }, + } + ]) + response = client.patch(self.wiring_url, data, content_type='application/json-patch+json') + self.assertEqual(response.status_code, 204) + def test_iwidget_removed(self): workspace = Workspace.objects.get(id=self.workspace_id) @@ -1446,9 +1467,9 @@ def _reupload_and_use_operator(self, reload=False): prefix = 'test_' workspace = Workspace.objects.get(id=2) workspace.wiringStatus['operators']['0']['preferences'] = { - 'prefix': {"readonly": False, "hidden": False, "value": prefix}, - 'exception_on_event': {"readonly": False, "hidden": False, "value": 'true'}, - 'test_logging': {"readonly": False, "hidden": False, "value": 'true'} + 'prefix': {"readonly": False, "hidden": False, "value": {"users": {"2": prefix}}}, + 'exception_on_event': {"readonly": False, "hidden": False, "value": {"users": {"2": 'true'}}}, + 'test_logging': {"readonly": False, "hidden": False, "value": {"users": {"2": 'true'}}} } workspace.save() @@ -1771,7 +1792,7 @@ def test_input_endpoint_exceptions(self): # Enable operator exceptions workspace = Workspace.objects.get(id=2) - workspace.wiringStatus['operators']['0']['preferences']['exception_on_event'] = {"readonly": False, "hidden": False, "value": 'true'} + workspace.wiringStatus['operators']['0']['preferences']['exception_on_event'] = {"readonly": False, "hidden": False, "value": {"users": {"2": 'true'}}} workspace.save() # Check exceptions diff --git a/src/wirecloud/platform/wiring/views.py b/src/wirecloud/platform/wiring/views.py index 0ea19c395e..dc2006f472 100644 --- a/src/wirecloud/platform/wiring/views.py +++ b/src/wirecloud/platform/wiring/views.py @@ -17,7 +17,6 @@ # You should have received a copy of the GNU Affero General Public License # along with Wirecloud. If not, see . -import json import jsonpatch from django.core.cache import cache @@ -36,6 +35,16 @@ class WiringEntry(Resource): + def handleMultiuser(self, request, new_preference, old_preference): + new_value = new_preference["value"] + + if old_preference is not None: + new_preference = old_preference + else: + new_preference["value"] = {"users": {}} + + new_preference["value"]["users"]["%s" % request.user.id] = new_value + def checkWiring(self, request, new_wiring_status, old_wiring_status, can_update_secure=False): # Check read only connections old_read_only_connections = [connection for connection in old_wiring_status['connections'] if connection.get('readonly', False)] @@ -50,6 +59,7 @@ def checkWiring(self, request, new_wiring_status, old_wiring_status, can_update_ # Check operator preferences for operator_id, operator in six.iteritems(new_wiring_status['operators']): + old_operator = None if operator_id in old_wiring_status['operators']: old_operator = old_wiring_status['operators'][operator_id] added_preferences = set(operator['preferences'].keys()) - set(old_operator['preferences'].keys()) @@ -71,6 +81,10 @@ def checkWiring(self, request, new_wiring_status, old_wiring_status, can_update_ for preference_name in added_preferences: if operator['preferences'][preference_name].get('readonly', False) or operator['preferences'][preference_name].get('hidden', False): return build_error_response(request, 403, _('Read only and hidden preferences cannot be created using this API')) + # Handle multiuser + old_preference = old_operator['preferences'].get(preference_name, None) if old_operator else None + new_preference = operator['preferences'][preference_name] + self.handleMultiuser(request, new_preference, old_preference) for preference_name in removed_preferences: if old_operator['preferences'][preference_name].get('readonly', False) or old_operator['preferences'][preference_name].get('hidden', False): @@ -86,7 +100,7 @@ def checkWiring(self, request, new_wiring_status, old_wiring_status, can_update_ for pref in operator_preferences: if pref["name"] == preference_name: preference_secure = pref.get("secure", False) - operator_preferences.remove(pref) # Speed up search + operator_preferences.remove(pref) # Speed up search break if old_preference.get('readonly', False) != new_preference.get('readonly', False) or old_preference.get('hidden', False) != new_preference.get('hidden', False): @@ -97,6 +111,9 @@ def checkWiring(self, request, new_wiring_status, old_wiring_status, can_update_ if preference_secure and not can_update_secure: new_preference["value"] = old_preference["value"] + else: + # Handle multiuser + self.handleMultiuser(request, new_preference, old_preference) return True @@ -128,7 +145,6 @@ def patch(self, request, workspace_id): return build_error_response(request, 403, _('You are not allowed to update this workspace')) old_wiring_status = workspace.wiringStatus - try: new_wiring_status = jsonpatch.apply_patch(old_wiring_status, parse_json_request(request)) except jsonpatch.JsonPointerException: diff --git a/src/wirecloud/platform/workspace/mashupTemplateGenerator.py b/src/wirecloud/platform/workspace/mashupTemplateGenerator.py index 106e3b32f4..97865bcb3e 100644 --- a/src/wirecloud/platform/workspace/mashupTemplateGenerator.py +++ b/src/wirecloud/platform/workspace/mashupTemplateGenerator.py @@ -19,8 +19,6 @@ from __future__ import unicode_literals -import json - import six from wirecloud.catalogue.models import CatalogueResource @@ -46,12 +44,11 @@ def get_workspace_description(workspace): return get_iwidgets_description(included_iwidgets) -def get_current_operator_pref_value(operator, preference): - +def get_current_operator_pref_value(operator, preference, user): if preference['name'] in operator['preferences']: - return "%s" % operator['preferences'][preference['name']]['value'] + return operator['preferences'][preference['name']]['value'] else: - return preference['default'] + return {"users": {"%s" % user.id: preference['default']}} def process_iwidget(workspace, iwidget, wiring, parametrization, readOnlyWidgets): @@ -274,14 +271,14 @@ def build_json_template_from_workspace(options, workspace, user): continue value = None elif source == 'current': - value = get_current_operator_pref_value(operator, preference) + value = get_current_operator_pref_value(operator, preference, user) elif source == 'custom': - value = ioperator_param_desc['value'] + value = {"users": {"%s" % user.id: ioperator_param_desc['value']}} else: raise Exception('Invalid preference value source: %s' % source) else: - value = get_current_operator_pref_value(operator, preference) + value = get_current_operator_pref_value(operator, preference, user) operator_data['preferences'][preference['name']] = { 'readonly': status != 'normal', diff --git a/src/wirecloud/platform/workspace/mashupTemplateParser.py b/src/wirecloud/platform/workspace/mashupTemplateParser.py index 6c7646f522..71614a054f 100644 --- a/src/wirecloud/platform/workspace/mashupTemplateParser.py +++ b/src/wirecloud/platform/workspace/mashupTemplateParser.py @@ -95,6 +95,7 @@ def check_mashup_dependencies(template, user): def map_id(endpoint_view, id_mapping): return id_mapping[endpoint_view['type']]["%s" % endpoint_view['id']]['id'] + def is_valid_connection(connection, id_mapping): def is_valid_endpoint(endpoint): @@ -102,6 +103,7 @@ def is_valid_endpoint(endpoint): return is_valid_endpoint(connection['source']) and is_valid_endpoint(connection['target']) + def _remap_component_ids(id_mapping, components_description, isGlobal=False): operators = {} @@ -261,14 +263,17 @@ def fillWorkspaceUsingTemplate(workspace, template): read_only = pref.get('readonly') if pref.get('value', None) is not None: value = pref['value'] + if isinstance(value, dict): + value = value["users"].get("%s" % workspace.creator.id, iwidget_info['variables']['preferences'][pref_name]['default']) else: value = iwidget_info['variables']['preferences'][pref_name]['default'] + + # Build multiuser structure if read_only: iwidget_forced_values[pref_name] = {'value': value, 'hidden': pref.get('hidden', False)} else: initial_variable_values[pref_name] = processor.process(value) - - set_initial_values(iwidget, initial_variable_values, iwidget_info) + set_initial_values(iwidget, initial_variable_values, iwidget_info, workspace.creator) iwidget.save() if len(iwidget_forced_values) > 0: @@ -307,6 +312,8 @@ def fillWorkspaceUsingTemplate(workspace, template): if pref.get('readonly', False): ioperator_forced_values[pref_id] = {'value': pref.get('value'), 'hidden': pref.get('hidden', False)} + workspace.wiringStatus['operators'][new_id]["preferences"][pref_id]["value"] = {'users': {"%s" % workspace.creator.id: pref["value"]}} + if len(ioperator_forced_values) > 0: new_forced_values['ioperator'][new_id] = ioperator_forced_values diff --git a/src/wirecloud/platform/workspace/tests.py b/src/wirecloud/platform/workspace/tests.py index 31c6799731..148e8525f3 100644 --- a/src/wirecloud/platform/workspace/tests.py +++ b/src/wirecloud/platform/workspace/tests.py @@ -26,9 +26,9 @@ import json from django.contrib.auth.models import User -from django.core.cache import cache from mock import Mock, create_autospec import six +from unittest import TestCase from wirecloud.commons.utils.template import TemplateParser from wirecloud.commons.utils.testcases import uses_extra_resources, WirecloudTestCase @@ -40,12 +40,48 @@ from wirecloud.platform.workspace.searchers import WorkspaceSearcher from wirecloud.platform.workspace.utils import get_global_workspace_data from wirecloud.platform.workspace.views import createEmptyWorkspace +from wirecloud.platform.migration_utils import update_variables_structure # Avoid nose to repeat these tests (they are run through wirecloud/tests/__init__.py) __test__ = False +class WorkspaceMigrationsTestCase(TestCase): + + tags = ('wirecloud-migrations', 'wirecloud-platform-migrations', 'wirecloud-noselenium', 'current') + + def test_add_multiuser_support_forward_empty(self): + + apps_mock = Mock() + apps_mock.get_model("platform", "workspace").objects.select_related('creator').all.return_value = [ + ] + + update_variables_structure(apps_mock, None) + + def test_add_multiuser_support_forward(self): + + widget1_mock = Mock( + variables={"varname": "varvalue", "varname2": "varvalue2"} + ) + tab_mock = Mock() + tab_mock.iwidget_set.all.return_value = [widget1_mock] + + workspace_mock = Mock() + workspace_mock.tab_set.all.return_value = [ + tab_mock + ] + workspace_mock.creator.id = "2" + + apps_mock = Mock() + apps_mock.get_model("platform", "workspace").objects.select_related('creator').all.return_value = [workspace_mock] + + update_variables_structure(apps_mock, None) + + widget1_mock.save.assert_called_with() + self.assertEqual(widget1_mock.variables, {"varname": {"users": {"2": "varvalue"}}, "varname2": {"users": {"2": "varvalue2"}}}) + + class WorkspaceTestCase(WirecloudTestCase): fixtures = ('test_data',) @@ -70,7 +106,7 @@ def test_get_global_workspace_data(self): properties = tab['iwidgets'][0]['properties'] self.assertEqual(properties['prop']['value'], 'test_data') - def test_secure_preferences_censor (self): + def test_secure_preferences_censor(self): workspace = Workspace.objects.get(pk=202) data = json.loads(get_global_workspace_data(workspace, self.user).get_data()) self.assertEqual(len(data['tabs']), 1) @@ -131,7 +167,6 @@ def setUp(self): self.user = User.objects.get(username='test') self.workspace = Workspace.objects.get(pk=1) - # Fill cache self.initial_info = get_global_workspace_data(self.workspace, self.user) @@ -142,7 +177,7 @@ def test_workspace_data_is_cached(self): def test_updating_preferences_invalidates_cache(self): iwidget = self.workspace.tab_set.get(pk=1).iwidget_set.get(pk=1) - iwidget.set_variable_value('username', 'new_username') + iwidget.set_variable_value('username', 'new_username', self.user) iwidget.save() workspace_info = get_global_workspace_data(self.workspace, self.user) @@ -159,7 +194,7 @@ def test_updating_preferences_invalidates_cache(self): def test_updating_properties_invalidates_cache(self): iwidget = self.workspace.tab_set.get(pk=1).iwidget_set.get(pk=1) - iwidget.set_variable_value('prop', 'new_data') + iwidget.set_variable_value('prop', 'new_data', self.user) iwidget.save() workspace_info = get_global_workspace_data(self.workspace, self.user) @@ -174,7 +209,6 @@ def test_updating_properties_invalidates_cache(self): self.assertEqual(properties['prop']['value'], 'new_data') def test_widget_instantiation_invalidates_cache(self): - tab = self.workspace.tab_set.get(pk=1) iwidget_data = { 'widget': 'Test/Test Widget/1.0.0', @@ -272,10 +306,10 @@ def setUp(self): # password variables must be encrypted iwidget = IWidget.objects.get(pk=1) - iwidget.set_variable_value('password', 'test_password') + iwidget.set_variable_value('password', 'test_password', self.user) iwidget.save() iwidget = IWidget.objects.get(pk=2) - iwidget.set_variable_value('password', 'test_password') + iwidget.set_variable_value('password', 'test_password', self.user) iwidget.save() def assertXPathText(self, root_element, xpath, content): @@ -319,16 +353,16 @@ def check_workspace_xml_wiring(self, template): self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="1"]', 'name', 'TestOperator') self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="1"]', 'version', '1.0') self.assertXPathCount(template, '/mashup/structure/wiring/operator[@id="1"]/preferencevalue', 4) - self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="1"]/preferencevalue[@name="pref_with_val"]', 'value', 'value1') + self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="1"]/preferencevalue[@name="pref_with_val"]', 'value', "{u'users': {u'2': u'value1'}}") self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="1"]/preferencevalue[@name="pref_with_val"]', 'readonly', 'false', optional=True) self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="1"]/preferencevalue[@name="pref_with_val"]', 'hidden', 'false', optional=True) - self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="1"]/preferencevalue[@name="readonly_pref"]', 'value', 'value2') + self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="1"]/preferencevalue[@name="readonly_pref"]', 'value', "{u'users': {u'2': u'value2'}}") self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="1"]/preferencevalue[@name="readonly_pref"]', 'readonly', 'false', optional=True) self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="1"]/preferencevalue[@name="readonly_pref"]', 'hidden', 'false', optional=True) - self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="1"]/preferencevalue[@name="hidden_pref"]', 'value', 'value3') + self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="1"]/preferencevalue[@name="hidden_pref"]', 'value', "{u'users': {u'2': u'value3'}}") self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="1"]/preferencevalue[@name="hidden_pref"]', 'readonly', 'false', optional=True) self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="1"]/preferencevalue[@name="hidden_pref"]', 'hidden', 'false', optional=True) - self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="1"]/preferencevalue[@name="empty_pref"]', 'value', 'value4') + self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="1"]/preferencevalue[@name="empty_pref"]', 'value', "{u'users': {u'2': u'value4'}}") self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="1"]/preferencevalue[@name="empty_pref"]', 'readonly', 'false', optional=True) self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="1"]/preferencevalue[@name="empty_pref"]', 'hidden', 'false', optional=True) @@ -336,20 +370,19 @@ def check_workspace_xml_wiring(self, template): self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="2"]', 'name', 'TestOperator') self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="2"]', 'version', '1.0') self.assertXPathCount(template, '/mashup/structure/wiring/operator[@id="2"]/preferencevalue', 4) - self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="2"]/preferencevalue[@name="pref_with_val"]', 'value', 'value1') + self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="2"]/preferencevalue[@name="pref_with_val"]', 'value', "{u'users': {u'2': u'value1'}}") self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="2"]/preferencevalue[@name="pref_with_val"]', 'readonly', 'false', optional=True) self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="2"]/preferencevalue[@name="pref_with_val"]', 'hidden', 'false', optional=True) - self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="2"]/preferencevalue[@name="readonly_pref"]', 'value', 'value2') + self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="2"]/preferencevalue[@name="readonly_pref"]', 'value', "{u'users': {u'2': u'value2'}}") self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="2"]/preferencevalue[@name="readonly_pref"]', 'readonly', 'false', optional=True) self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="2"]/preferencevalue[@name="readonly_pref"]', 'hidden', 'false', optional=True) - self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="2"]/preferencevalue[@name="hidden_pref"]', 'value', 'value3') + self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="2"]/preferencevalue[@name="hidden_pref"]', 'value', "{u'users': {u'2': u'value3'}}") self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="2"]/preferencevalue[@name="hidden_pref"]', 'readonly', 'false', optional=True) self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="2"]/preferencevalue[@name="hidden_pref"]', 'hidden', 'false', optional=True) - self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="2"]/preferencevalue[@name="empty_pref"]', 'value', '') + self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="2"]/preferencevalue[@name="empty_pref"]', 'value', "{u'users': {u'2': u''}}") self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="2"]/preferencevalue[@name="empty_pref"]', 'readonly', 'false', optional=True) self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="2"]/preferencevalue[@name="empty_pref"]', 'hidden', 'false', optional=True) - def get_rdf_element(self, graph, base, ns, predicate): element = None @@ -403,17 +436,17 @@ def check_workspace_rdf_wiring(self, graph, mashup_uri): name = self.get_rdf_element(graph, preference, self.DCTERMS, 'title') if six.text_type(name) == 'pref_with_val': pref_with_val_found = True - self.assertRDFElement(graph, preference, self.WIRE, 'value', 'value1') + self.assertRDFElement(graph, preference, self.WIRE, 'value', "{u'users': {u'2': u'value1'}}") elif six.text_type(name) == 'readonly_pref': readonly_pref_found = True - self.assertRDFElement(graph, preference, self.WIRE, 'value', 'value2') + self.assertRDFElement(graph, preference, self.WIRE, 'value', "{u'users': {u'2': u'value2'}}") elif six.text_type(name) == 'hidden_pref': hidden_pref_found = True - self.assertRDFElement(graph, preference, self.WIRE, 'value', 'value3') + self.assertRDFElement(graph, preference, self.WIRE, 'value', "{u'users': {u'2': u'value3'}}") elif six.text_type(name) == 'empty_pref': empty_pref_found = True content = six.text_type(self.get_rdf_element(graph, preference, self.WIRE, 'value')) - self.assertIn(content, ('', 'value4')) + self.assertIn(content, ("{u'users': {u'2': u''}}", "{u'users': {u'2': u'value4'}}")) else: self.fail() @@ -578,27 +611,27 @@ def test_build_xml_template_from_workspace_forced_values(self): self.assertXPathAttr(template, '/mashup/structure/tab[1]/resource[@id="2"]/preferencevalue[@name="boolean"]', 'hidden', 'false', optional=True) self.assertXPathAttr(template, '/mashup/structure/tab[1]/resource[@id="2"]/preferencevalue[@name="boolean"]', 'value', 'false') - self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="1"]/preferencevalue[@name="pref_with_val"]', 'value', 'new_value1') + self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="1"]/preferencevalue[@name="pref_with_val"]', 'value', "{u'users': {u'2': u'new_value1'}}") self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="1"]/preferencevalue[@name="pref_with_val"]', 'readonly', 'false', optional=True) self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="1"]/preferencevalue[@name="pref_with_val"]', 'hidden', 'false', optional=True) - self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="1"]/preferencevalue[@name="readonly_pref"]', 'value', 'new_value2') + self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="1"]/preferencevalue[@name="readonly_pref"]', 'value', "{u'users': {u'2': u'new_value2'}}") self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="1"]/preferencevalue[@name="readonly_pref"]', 'readonly', 'true') self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="1"]/preferencevalue[@name="readonly_pref"]', 'hidden', 'false', optional=True) - self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="1"]/preferencevalue[@name="hidden_pref"]', 'value', 'new_value3') + self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="1"]/preferencevalue[@name="hidden_pref"]', 'value', "{u'users': {u'2': u'new_value3'}}") self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="1"]/preferencevalue[@name="hidden_pref"]', 'readonly', 'true') self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="1"]/preferencevalue[@name="hidden_pref"]', 'hidden', 'true') - self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="1"]/preferencevalue[@name="empty_pref"]', 'value', '') + self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="1"]/preferencevalue[@name="empty_pref"]', 'value', "{u'users': {u'2': u''}}") self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="1"]/preferencevalue[@name="empty_pref"]', 'readonly', 'false', optional=True) self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="1"]/preferencevalue[@name="empty_pref"]', 'hidden', 'false', optional=True) self.assertXPathCount(template, '/mashup/structure/wiring/operator[@id="2"]/preferencevalue[@name="pref_with_val"]', 0) - self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="2"]/preferencevalue[@name="readonly_pref"]', 'value', 'value2') + self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="2"]/preferencevalue[@name="readonly_pref"]', 'value', "{u'users': {u'2': u'value2'}}") self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="2"]/preferencevalue[@name="readonly_pref"]', 'readonly', 'true') self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="2"]/preferencevalue[@name="readonly_pref"]', 'hidden', 'false', optional=True) self.assertXPathMissingAttr(template, '/mashup/structure/wiring/operator[@id="2"]/preferencevalue[@name="hidden_pref"]', 'value') self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="2"]/preferencevalue[@name="hidden_pref"]', 'readonly', 'true') self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="2"]/preferencevalue[@name="hidden_pref"]', 'hidden', 'true') - self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="2"]/preferencevalue[@name="empty_pref"]', 'value', '') + self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="2"]/preferencevalue[@name="empty_pref"]', 'value', "{u'users': {u'2': u''}}") self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="2"]/preferencevalue[@name="empty_pref"]', 'readonly', 'false', optional=True) self.assertXPathAttr(template, '/mashup/structure/wiring/operator[@id="2"]/preferencevalue[@name="empty_pref"]', 'hidden', 'false', optional=True) @@ -784,7 +817,6 @@ def test_build_rdf_template_from_workspace_forced_values(self): self.assertTrue(username_count == 1 and password_count == 1 and boolean_count == 1 and list_count == 0) - def test_build_rdf_template_from_workspace_utf8_char(self): options = { 'vendor': 'Wirecloud Test Suite', diff --git a/src/wirecloud/platform/workspace/utils.py b/src/wirecloud/platform/workspace/utils.py index 1a47328a4a..b28c4e0a2a 100644 --- a/src/wirecloud/platform/workspace/utils.py +++ b/src/wirecloud/platform/workspace/utils.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2008-2016 CoNWeT Lab., Universidad Politécnica de Madrid +# Copyright (c) 2008-2017 CoNWeT Lab., Universidad Politécnica de Madrid # This file is part of Wirecloud. @@ -105,8 +105,6 @@ def decrypt_value(value): def sync_base_workspaces(user): - from wirecloud.platform.workspace.mashupTemplateParser import buildWorkspaceFromTemplate - reload_showcase = False managers = get_workspace_managers() @@ -165,7 +163,7 @@ def get_workspace_list(user): return workspaces -def _process_variable(iwidget, svariwidget, vardef, forced_values, values_by_varname, values_by_varid): +def _process_variable(iwidget, svariwidget, vardef, forced_values, values_by_varname, values_by_varid, user): varname = vardef['name'] entry = { 'type': vardef['type'], @@ -184,7 +182,13 @@ def _process_variable(iwidget, svariwidget, vardef, forced_values, values_by_var entry['hidden'] = fv_entry.get('hidden', False) else: - entry['value'] = iwidget.variables.get(varname, parse_value_from_text(entry, vardef['default'])) + value = iwidget.variables.get(varname, None) + if value is None or value["users"].get("%s" % user.id, None) is None: + value = parse_value_from_text(entry, vardef['default']) + else: + value = value["users"].get("%s" % user.id, None) + + entry['value'] = value entry['readonly'] = False entry['hidden'] = False @@ -196,7 +200,6 @@ def _populate_variables_values_cache(workspace, user, key, forced_values=None): """ populates VariableValue cached values for that user """ values_by_varid = {} values_by_varname = {} - if forced_values is None: context_values = get_context_values(workspace, user) preferences = get_workspace_preference_values(workspace) @@ -204,7 +207,7 @@ def _populate_variables_values_cache(workspace, user, key, forced_values=None): for iwidget in IWidget.objects.filter(tab__workspace=workspace): # forced_values uses string keys - svariwidget = str(iwidget.id) + svariwidget = "%s" % iwidget.id values_by_varname[iwidget.id] = {} if iwidget.widget is None: @@ -213,10 +216,10 @@ def _populate_variables_values_cache(workspace, user, key, forced_values=None): iwidget_info = iwidget.widget.resource.get_processed_info() for vardef in iwidget_info['preferences']: - _process_variable(iwidget, svariwidget, vardef, forced_values, values_by_varname, values_by_varid) + _process_variable(iwidget, svariwidget, vardef, forced_values, values_by_varname, values_by_varid, user) for vardef in iwidget_info['properties']: - _process_variable(iwidget, svariwidget, vardef, forced_values, values_by_varname, values_by_varid) + _process_variable(iwidget, svariwidget, vardef, forced_values, values_by_varname, values_by_varid, user) values = { 'by_varid': values_by_varid, @@ -279,12 +282,13 @@ def get_variable_value_from_varname(self, iwidget, var_name): return value - def get_variable_data(self, iwidget, var_name): + # Get preference data + def get_variable_data(self, iwidget, var_name, user): values = self.get_variable_values() entry = values['by_varname'][iwidget.id][var_name] # If secure and has value, censor it - if entry["secure"] and entry["value"] != "": + if entry['secure'] and entry["value"] != "": value = "********" else: value = entry["value"] @@ -297,6 +301,19 @@ def get_variable_data(self, iwidget, var_name): 'value': value, } + # Get the persistent property data + def get_property_data(self, iwidget, var_name, user): + values = self.get_variable_values() + entry = values['by_varname'][iwidget.id][var_name] + + return { + 'name': var_name, + 'value': entry['value'], + 'readonly': entry['readonly'], + 'secure': entry['secure'], + 'hidden': entry['hidden'], + } + def get_workspace_data(workspace, user): @@ -455,8 +472,8 @@ def _get_global_workspace_data(workspaceDAO, user): else: tabs = [createTab(_('Tab'), workspaceDAO)] - data_ret['tabs'] = [get_tab_data(tab, workspace=workspaceDAO, cache_manager=cache_manager) for tab in tabs] - data_ret['wiring'] = workspaceDAO.wiringStatus + data_ret['tabs'] = [get_tab_data(tab, workspace=workspaceDAO, cache_manager=cache_manager, user=user) for tab in tabs] + data_ret['wiring'] = deepcopy(workspaceDAO.wiringStatus) for operator_id, operator in six.iteritems(data_ret['wiring'].get('operators', {})): try: (vendor, name, version) = operator['name'].split('/') @@ -476,12 +493,19 @@ def _get_global_workspace_data(workspaceDAO, user): operator_forced_values = forced_values['ioperator'].get(operator_id, {}) for preference_name, preference in six.iteritems(operator.get('preferences', {})): vardef = operator_info['variables']['preferences'].get(preference_name) + value = preference.get('value', None) if preference_name in operator_forced_values: preference['value'] = operator_forced_values[preference_name]['value'] - elif preference.get('value') is None and vardef is not None: + elif value is None or value["users"].get("%s" % user.id, None) is None: + # If not defined / not defined for the current user, take the default value preference['value'] = parse_value_from_text(vardef, vardef['default']) + else: + preference['value'] = value["users"].get("%s" % user.id) + + # Secure censor if vardef is not None and vardef["secure"]: preference['value'] = "" if preference.get('value') is None or preference.get('value') == "" else "********" + return json.dumps(data_ret, cls=LazyEncoder) @@ -509,7 +533,7 @@ def get_tab_data(tab, workspace=None, cache_manager=None, user=None): 'name': tab.name, 'visible': tab.visible, 'preferences': get_tab_preference_values(tab), - 'iwidgets': [get_iwidget_data(widget, workspace, cache_manager) for widget in tab.iwidget_set.order_by('id')] + 'iwidgets': [get_iwidget_data(widget, workspace, cache_manager, user) for widget in tab.iwidget_set.order_by('id')] } @@ -544,8 +568,8 @@ def get_iwidget_data(iwidget, workspace, cache_manager=None, user=None): cache_manager = VariableValueCacheManager(workspace, user) iwidget_info = iwidget.widget.resource.get_processed_info() - data_ret['preferences'] = {preference['name']: cache_manager.get_variable_data(iwidget, preference['name']) for preference in iwidget_info['preferences']} - data_ret['properties'] = {property['name']: cache_manager.get_variable_data(iwidget, property['name']) for property in iwidget_info['properties']} + data_ret['preferences'] = {preference['name']: cache_manager.get_variable_data(iwidget, preference['name'], user) for preference in iwidget_info['preferences']} + data_ret['properties'] = {property['name']: cache_manager.get_property_data(iwidget, property['name'], user) for property in iwidget_info['properties']} return data_ret diff --git a/src/wirecloud/proxy/processors.py b/src/wirecloud/proxy/processors.py index 6630c1ccf9..b01114a98b 100644 --- a/src/wirecloud/proxy/processors.py +++ b/src/wirecloud/proxy/processors.py @@ -39,7 +39,7 @@ VAR_REF_RE = re.compile(r'^((?Pc)/)?(?P.+)$', re.S) -def get_variable_value_by_ref(ref, cache_manager, component_id, component_type="widget"): +def get_variable_value_by_ref(ref, user, cache_manager, component_id, component_type="widget"): result = VAR_REF_RE.match(ref) try: # Get widget variables @@ -50,7 +50,7 @@ def get_variable_value_by_ref(ref, cache_manager, component_id, component_type=" return cache_manager.get_variable_value_from_varname(component_id, result.group('var_name')) # Get operator variables elif component_type == "operator": - return cache_manager.workspace.wiringStatus["operators"][component_id]["preferences"][result.group('var_name')]["value"] + return cache_manager.workspace.wiringStatus["operators"][component_id]["preferences"][result.group('var_name')]["value"]["users"]["%s" % user.id] else: raise ValidationError() @@ -105,7 +105,7 @@ def process_secure_data(text, request, component_id, component_type): var_ref = options.get('var_ref', '') check_empty_params(substr=substr, var_ref=var_ref) - value = get_variable_value_by_ref(var_ref, cache_manager, component_id, component_type) + value = get_variable_value_by_ref(var_ref, request['user'], cache_manager, component_id, component_type) check_invalid_refs(var_ref=value) encoding = options.get('encoding', 'none') @@ -127,8 +127,8 @@ def process_secure_data(text, request, component_id, component_type): password_ref = options.get('pass_ref', '') check_empty_params(user_ref=user_ref, password_ref=password_ref) - user_value = get_variable_value_by_ref(user_ref, cache_manager, component_id, component_type) - password_value = get_variable_value_by_ref(password_ref, cache_manager, component_id, component_type) + user_value = get_variable_value_by_ref(user_ref, request['user'], cache_manager, component_id, component_type) + password_value = get_variable_value_by_ref(password_ref, request['user'], cache_manager, component_id, component_type) check_invalid_refs(user_ref=user_value, password_ref=password_value) token = base64.b64encode((user_value + ':' + password_value).encode('utf8'))[:-1] @@ -140,7 +140,6 @@ def process_secure_data(text, request, component_id, component_type): class SecureDataProcessor(object): def process_request(self, request): - # Process secure data from the X-WireCloud-Secure-Data header if WIRECLOUD_SECURE_DATA_HEADER in request['headers']: secure_data_value = request['headers'][WIRECLOUD_SECURE_DATA_HEADER] diff --git a/src/wirecloud/proxy/tests.py b/src/wirecloud/proxy/tests.py index fcbccc3e4a..c6724de446 100644 --- a/src/wirecloud/proxy/tests.py +++ b/src/wirecloud/proxy/tests.py @@ -27,9 +27,10 @@ from django.core.cache import cache from django.core.urlresolvers import reverse from django.test import Client, override_settings +from django.contrib.auth.models import User from wirecloud.commons.utils.testcases import DynamicWebServer, WirecloudTestCase -from wirecloud.platform.models import IWidget, Workspace +from wirecloud.platform.models import IWidget from wirecloud.platform.plugins import clear_cache @@ -458,21 +459,21 @@ def echo_response(method, url, *args, **kwargs): secure_data_header = 'action=basic_auth, user_ref=' + user_ref + ', pass_ref=' + pass_ref response = self.client.post(self.basic_url, - 'username=|username|&password=|password|', - content_type='application/x-www-form-urlencoded', - HTTP_HOST='localhost', - HTTP_REFERER='http://localhost/test/workspaceSecure', - HTTP_X_WIRECLOUD_SECURE_DATA=secure_data_header, - HTTP_WIRECLOUD_COMPONENT_TYPE="operator", - HTTP_WIRECLOUD_COMPONENT_ID="2") + 'username=|username|&password=|password|', + content_type='application/x-www-form-urlencoded', + HTTP_HOST='localhost', + HTTP_REFERER='http://localhost/test/workspaceSecure', + HTTP_X_WIRECLOUD_SECURE_DATA=secure_data_header, + HTTP_WIRECLOUD_COMPONENT_TYPE="operator", + HTTP_WIRECLOUD_COMPONENT_ID="2") self.assertEqual(response.status_code, 200) self.assertEqual(self.read_response(response), b'username=|username|&password=|password|') def test_secure_data(self): - + user = User.objects.get(username='test') iwidget = IWidget.objects.get(pk=1) - iwidget.set_variable_value('password', 'test_password') + iwidget.set_variable_value('password', 'test_password', user) iwidget.save() self.assertNotEqual(iwidget.variables['password'], 'test_password') @@ -487,25 +488,25 @@ def echo_response(method, url, *args, **kwargs): secure_data_header = 'action=data, substr=|password|, var_ref=' + pass_ref secure_data_header += '&action=data, substr=|username|, var_ref=' + user_ref response = self.client.post(self.basic_url, - 'username=|username|&password=|password|', - content_type='application/x-www-form-urlencoded', - HTTP_HOST='localhost', - HTTP_REFERER='http://localhost/test/workspace', - HTTP_X_WIRECLOUD_SECURE_DATA=secure_data_header, - HTTP_WIRECLOUD_COMPONENT_TYPE="widget", - HTTP_WIRECLOUD_COMPONENT_ID="1") + 'username=|username|&password=|password|', + content_type='application/x-www-form-urlencoded', + HTTP_HOST='localhost', + HTTP_REFERER='http://localhost/test/workspace', + HTTP_X_WIRECLOUD_SECURE_DATA=secure_data_header, + HTTP_WIRECLOUD_COMPONENT_TYPE="widget", + HTTP_WIRECLOUD_COMPONENT_ID="1") self.assertEqual(response.status_code, 200) self.assertEqual(self.read_response(response), b'username=test_username&password=test_password') secure_data_header = 'action=basic_auth, user_ref=' + user_ref + ', pass_ref=' + pass_ref response = self.client.post(self.basic_url, - 'username=|username|&password=|password|', - content_type='application/x-www-form-urlencoded', - HTTP_HOST='localhost', - HTTP_REFERER='http://localhost/test/workspace', - HTTP_X_WIRECLOUD_SECURE_DATA=secure_data_header, - HTTP_WIRECLOUD_COMPONENT_ID="1") + 'username=|username|&password=|password|', + content_type='application/x-www-form-urlencoded', + HTTP_HOST='localhost', + HTTP_REFERER='http://localhost/test/workspace', + HTTP_X_WIRECLOUD_SECURE_DATA=secure_data_header, + HTTP_WIRECLOUD_COMPONENT_ID="1") self.assertEqual(response.status_code, 200) self.assertEqual(self.read_response(response), b'username=|username|&password=|password|') @@ -514,12 +515,12 @@ def echo_response(method, url, *args, **kwargs): secure_data_header = 'action=data, substr=|password|, var_ref=c/test_password' secure_data_header += '&action=data, substr=|username|, var_ref=c/test_username' response = self.client.post(self.basic_url, - 'username=|username|&password=|password|', - content_type='application/x-www-form-urlencoded', - HTTP_HOST='localhost', - HTTP_REFERER='http://localhost/test/workspace', - HTTP_X_WIRECLOUD_SECURE_DATA=secure_data_header, - HTTP_WIRECLOUD_COMPONENT_ID="1") + 'username=|username|&password=|password|', + content_type='application/x-www-form-urlencoded', + HTTP_HOST='localhost', + HTTP_REFERER='http://localhost/test/workspace', + HTTP_X_WIRECLOUD_SECURE_DATA=secure_data_header, + HTTP_WIRECLOUD_COMPONENT_ID="1") self.assertEqual(response.status_code, 200) self.assertEqual(self.read_response(response), b'username=test_username&password=test_password') @@ -528,12 +529,12 @@ def echo_response(method, url, *args, **kwargs): secure_data_header = 'action=data, substr=|password|, var_ref=c%2Fa%3D%2C%20z , encoding=url' secure_data_header += '&action=data, substr=|username|, var_ref=c%2Fa%3D%2C%20z' response = self.client.post(self.basic_url, - 'username=|username|&password=|password|', - content_type='application/x-www-form-urlencoded', - HTTP_HOST='localhost', - HTTP_REFERER='http://localhost/test/workspace', - HTTP_X_WIRECLOUD_SECURE_DATA=secure_data_header, - HTTP_WIRECLOUD_COMPONENT_ID="1") + 'username=|username|&password=|password|', + content_type='application/x-www-form-urlencoded', + HTTP_HOST='localhost', + HTTP_REFERER='http://localhost/test/workspace', + HTTP_X_WIRECLOUD_SECURE_DATA=secure_data_header, + HTTP_WIRECLOUD_COMPONENT_ID="1") self.assertEqual(response.status_code, 200) self.assertEqual(self.read_response(response), b'username=a=, z&password=a%3D%2C%20z') @@ -541,12 +542,12 @@ def echo_response(method, url, *args, **kwargs): # Secure data header using encoding=base64 secure_data_header = 'action=data, substr=|password|, var_ref=password, encoding=base64' response = self.client.post(self.basic_url, - 'username=|username|&password=|password|', - content_type='application/x-www-form-urlencoded', - HTTP_HOST='localhost', - HTTP_REFERER='http://localhost/test/workspace', - HTTP_X_WIRECLOUD_SECURE_DATA=secure_data_header, - HTTP_WIRECLOUD_COMPONENT_ID="1") + 'username=|username|&password=|password|', + content_type='application/x-www-form-urlencoded', + HTTP_HOST='localhost', + HTTP_REFERER='http://localhost/test/workspace', + HTTP_X_WIRECLOUD_SECURE_DATA=secure_data_header, + HTTP_WIRECLOUD_COMPONENT_ID="1") self.assertEqual(response.status_code, 200) self.assertEqual(self.read_response(response), b'username=|username|&password=dGVzdF9wYXNzd29yZA=') @@ -555,12 +556,12 @@ def check_invalid_ref(self, invalid_ref): secure_data_header = 'action=data, substr=|password|, var_ref=' + invalid_ref response = self.client.post(self.basic_url, - 'username=|username|&password=|password|', - content_type='application/x-www-form-urlencoded', - HTTP_ACCEPT='application/json', - HTTP_HOST='localhost', - HTTP_REFERER='http://localhost/test/workspace', - HTTP_X_WIRECLOUD_SECURE_DATA=secure_data_header) + 'username=|username|&password=|password|', + content_type='application/x-www-form-urlencoded', + HTTP_ACCEPT='application/json', + HTTP_HOST='localhost', + HTTP_REFERER='http://localhost/test/workspace', + HTTP_X_WIRECLOUD_SECURE_DATA=secure_data_header) self.assertEqual(response.status_code, 422) response_data = json.loads(response.content.decode('utf-8')) @@ -590,11 +591,11 @@ def echo_response(method, url, *args, **kwargs): secure_data_header = 'action=data, substr=|password|, var_ref=c/test_password' secure_data_header += '&& &action=data, substr=|username|, var_ref=c/test_username' response = self.client.post(self.basic_url, - 'username=|username|&password=|password|', - content_type='application/x-www-form-urlencoded', - HTTP_HOST='localhost', - HTTP_REFERER='http://localhost/test/workspace', - HTTP_X_WIRECLOUD_SECURE_DATA=secure_data_header) + 'username=|username|&password=|password|', + content_type='application/x-www-form-urlencoded', + HTTP_HOST='localhost', + HTTP_REFERER='http://localhost/test/workspace', + HTTP_X_WIRECLOUD_SECURE_DATA=secure_data_header) self.assertEqual(response.status_code, 200) self.assertEqual(self.read_response(response), b'username=test_username&password=test_password') @@ -607,11 +608,11 @@ def test_secure_data_invalid_action(self): # Secure data header with empty parameters secure_data_header = 'action=invalidaction, user_ref=asdf' response = self.client.post(self.basic_url, - 'username=|username|&password=|password|', - content_type='application/x-www-form-urlencoded', - HTTP_HOST='localhost', - HTTP_REFERER='http://localhost/test/workspace', - HTTP_X_WIRECLOUD_SECURE_DATA=secure_data_header) + 'username=|username|&password=|password|', + content_type='application/x-www-form-urlencoded', + HTTP_HOST='localhost', + HTTP_REFERER='http://localhost/test/workspace', + HTTP_X_WIRECLOUD_SECURE_DATA=secure_data_header) self.assertEqual(response.status_code, 422) @@ -626,21 +627,21 @@ def echo_response(method, url, *args, **kwargs): # Secure data header with empty parameters secure_data_header = 'action=basic_auth, user_ref=, pass_ref=' response = self.client.post(self.basic_url, - 'username=|username|&password=|password|', - content_type='application/x-www-form-urlencoded', - HTTP_HOST='localhost', - HTTP_REFERER='http://localhost/test/workspace', - HTTP_X_WIRECLOUD_SECURE_DATA=secure_data_header) + 'username=|username|&password=|password|', + content_type='application/x-www-form-urlencoded', + HTTP_HOST='localhost', + HTTP_REFERER='http://localhost/test/workspace', + HTTP_X_WIRECLOUD_SECURE_DATA=secure_data_header) self.assertEqual(response.status_code, 422) # Secure data header missing parameters secure_data_header = 'action=basic_auth' response = self.client.post(self.basic_url, - 'username=|username|&password=|password|', - content_type='application/x-www-form-urlencoded', - HTTP_HOST='localhost', - HTTP_REFERER='http://localhost/test/workspace', - HTTP_X_WIRECLOUD_SECURE_DATA=secure_data_header) + 'username=|username|&password=|password|', + content_type='application/x-www-form-urlencoded', + HTTP_HOST='localhost', + HTTP_REFERER='http://localhost/test/workspace', + HTTP_X_WIRECLOUD_SECURE_DATA=secure_data_header) self.assertEqual(response.status_code, 422) From ba0516b85a15003ba1cd94aadbbdd92c40aa4d39 Mon Sep 17 00:00:00 2001 From: Alejandro Rodriguez Date: Fri, 3 Feb 2017 11:58:10 +0100 Subject: [PATCH 05/28] Fix @migration_utils style --- src/wirecloud/platform/migration_utils.py | 80 ++++++++++++----------- 1 file changed, 41 insertions(+), 39 deletions(-) diff --git a/src/wirecloud/platform/migration_utils.py b/src/wirecloud/platform/migration_utils.py index f86db3df96..5269324b46 100644 --- a/src/wirecloud/platform/migration_utils.py +++ b/src/wirecloud/platform/migration_utils.py @@ -23,50 +23,52 @@ from django.db.migrations.exceptions import IrreversibleError import six + def update_variables_structure(apps, schema_editor): - mutate = lambda value, userID: {"users": {userID: value}} - Workspace = apps.get_model("platform", "workspace") + mutate = lambda value, userID: {"users": {userID: value}} + Workspace = apps.get_model("platform", "workspace") + + for workspace in Workspace.objects.select_related('creator').all(): + owner = workspace.creator.id - for workspace in Workspace.objects.select_related('creator').all(): - owner = workspace.creator.id + # Update operators + wiring = workspace.wiringStatus + for op in wiring["operators"]: + wiring["operators"][op]["preferences"] = {k: mutate(v, owner) for k, v in six.iteritems(wiring["operators"][op]["preferences"])} + workspace.save() - # Update operators - wiring = workspace.wiringStatus - for op in wiring["operators"]: - wiring["operators"][op]["preferences"] = {k: mutate(v, owner) for k, v in six.iteritems(wiring["operators"][op]["preferences"])} - workspace.save() + # Update widgets + for tab in workspace.tab_set.all(): + for widget in tab.iwidget_set.all(): + widget.variables = {k: mutate(v, owner) for k, v in six.iteritems(widget.variables)} + widget.save() - # Update widgets - for tab in workspace.tab_set.all(): - for widget in tab.iwidget_set.all(): - widget.variables = {k: mutate(v, owner) for k, v in six.iteritems(widget.variables)} - widget.save() def reverse_variables_structure(apps, schema_editor): - # Check no multiuser widgets - CatalogueResource = apps.get_model("catalogue", "CatalogueResource") - - for component in CatalogueResource.objects.filter(type__in=(0, 2)).all(): - for property in component.json_description["properties"]: - if property.get("multiuser", False): - uri = component.vendor + '/' + component.short_name + '/' + component.version - raise IrreversibleError("Component %s requires multiuser support. Uninstall it before downgrading." % uri) - - mutate = lambda value, userID: value["users"]["%s" % userID] - Workspace = apps.get_model("platform", "workspace") - for workspace in Workspace.objects.select_related('creator').all(): - owner = workspace.creator.id - - # Update operators - wiring = workspace.wiringStatus - for op in wiring["operators"]: - wiring["operators"][op]["preferences"] = {k: mutate(v, owner) for k, v in six.iteritems(wiring["operators"][op]["preferences"])} - workspace.save() - - # Update widgets - for tab in workspace.tab_set.all(): - for widget in tab.iwidget_set.all(): - widget.variables = {k: mutate(v, owner) for k, v in six.iteritems(widget.variables)} - widget.save() \ No newline at end of file + # Check no multiuser widgets + CatalogueResource = apps.get_model("catalogue", "CatalogueResource") + + for component in CatalogueResource.objects.filter(type__in=(0, 2)).all(): + for property in component.json_description["properties"]: + if property.get("multiuser", False): + uri = component.vendor + '/' + component.short_name + '/' + component.version + raise IrreversibleError("Component %s requires multiuser support. Uninstall it before downgrading." % uri) + + mutate = lambda value, userID: value["users"]["%s" % userID] + Workspace = apps.get_model("platform", "workspace") + for workspace in Workspace.objects.select_related('creator').all(): + owner = workspace.creator.id + + # Update operators + wiring = workspace.wiringStatus + for op in wiring["operators"]: + wiring["operators"][op]["preferences"] = {k: mutate(v, owner) for k, v in six.iteritems(wiring["operators"][op]["preferences"])} + workspace.save() + + # Update widgets + for tab in workspace.tab_set.all(): + for widget in tab.iwidget_set.all(): + widget.variables = {k: mutate(v, owner) for k, v in six.iteritems(widget.variables)} + widget.save() From 9a9c4635559f5708ec5f3c12bf1b3158e2a1c2a8 Mon Sep 17 00:00:00 2001 From: Alejandro Rodriguez Date: Fri, 3 Feb 2017 12:03:23 +0100 Subject: [PATCH 06/28] Add multiuser to component parsers --- src/wirecloud/commons/tests/template.py | 8 ++++++++ src/wirecloud/commons/utils/template/parsers/json.py | 1 + src/wirecloud/commons/utils/template/parsers/rdf.py | 1 + src/wirecloud/commons/utils/template/parsers/xml.py | 2 +- .../commons/utils/template/schemas/xml_schema.xsd | 10 ++++++++++ 5 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/wirecloud/commons/tests/template.py b/src/wirecloud/commons/tests/template.py index af7a862025..256b1c042a 100644 --- a/src/wirecloud/commons/tests/template.py +++ b/src/wirecloud/commons/tests/template.py @@ -152,6 +152,7 @@ def setUpClass(cls): 'label': 'Prop1', 'description': 'description 1', 'default': 'value1', + 'multiuser': False, }, { 'name': 'prop2', @@ -160,6 +161,7 @@ def setUpClass(cls): 'label': 'Prop2', 'description': 'description 2', 'default': 'value2', + 'multiuser': False, } ], 'wiring': { @@ -279,6 +281,7 @@ def setUpClass(cls): 'label': '__MSG_prop1_label__', 'description': '__MSG_prop1_description__', 'default': 'value1', + 'multiuser': False, }, { 'name': 'prop2', @@ -287,6 +290,7 @@ def setUpClass(cls): 'label': '__MSG_prop2_label__', 'description': '__MSG_prop2_description__', 'default': 'value2', + 'multiuser': False, } ], 'wiring': { @@ -1146,6 +1150,7 @@ def setUpClass(cls): 'label': 'Prop1', 'description': 'description 1', 'default': 'value1', + 'multiuser': False, }, { 'name': 'prop2', @@ -1154,6 +1159,7 @@ def setUpClass(cls): 'label': 'Prop2', 'description': 'description 2', 'default': 'value2', + 'multiuser': False, } ], 'wiring': { @@ -1379,6 +1385,7 @@ def setUpClass(cls): 'label': '', 'description': '', 'default': '', + 'multiuser': False, }, { 'name': 'prop2', @@ -1387,6 +1394,7 @@ def setUpClass(cls): 'label': '', 'description': '', 'default': '', + 'multiuser': False, }, ], 'wiring': { diff --git a/src/wirecloud/commons/utils/template/parsers/json.py b/src/wirecloud/commons/utils/template/parsers/json.py index 9afa8fcbd5..3e7a6ff559 100644 --- a/src/wirecloud/commons/utils/template/parsers/json.py +++ b/src/wirecloud/commons/utils/template/parsers/json.py @@ -204,6 +204,7 @@ def _init(self): self._check_string_fields(('name', 'type'), place=prop, required=True) self._check_string_fields(('label', 'description', 'default'), place=prop) self._check_boolean_fields(('secure',), place=prop, default=False) + self._check_boolean_fields(('multiuser',), place=prop, default=False) if self._info['type'] == 'widget': diff --git a/src/wirecloud/commons/utils/template/parsers/rdf.py b/src/wirecloud/commons/utils/template/parsers/rdf.py index 4aee68afcc..634bc7b71a 100644 --- a/src/wirecloud/commons/utils/template/parsers/rdf.py +++ b/src/wirecloud/commons/utils/template/parsers/rdf.py @@ -587,6 +587,7 @@ def _parse_widget_info(self): 'description': self._get_translation_field(DCTERMS, 'description', prop, var_name + '_description', required=False, type='vdef', variable=var_name, field='description'), 'default': self._get_field(WIRE, 'default', prop, required=False), 'secure': self._get_field(WIRE, 'secure', prop, required=False).lower() == 'true', + 'multiuser': self._get_field(WIRE, 'multiuser', prop, required=False).lower() == 'true', }) self._parse_wiring_info() diff --git a/src/wirecloud/commons/utils/template/parsers/xml.py b/src/wirecloud/commons/utils/template/parsers/xml.py index c68f65d59d..4e1e1a3a2a 100644 --- a/src/wirecloud/commons/utils/template/parsers/xml.py +++ b/src/wirecloud/commons/utils/template/parsers/xml.py @@ -105,7 +105,6 @@ class ApplicationMashupTemplateParser(object): _parsed = False def __init__(self, template): - self._info = {} self._translation_indexes = {} @@ -490,6 +489,7 @@ def _parse_component_persistentvariables(self): 'description': prop.get('description', ''), 'default': prop.get('default', ''), 'secure': prop.get('secure', 'false').lower() == 'true', + 'multiuser': prop.get('multiuser', 'false').lower() == 'true' }) def _parse_preference_values(self, element): diff --git a/src/wirecloud/commons/utils/template/schemas/xml_schema.xsd b/src/wirecloud/commons/utils/template/schemas/xml_schema.xsd index 0277ede616..a9327c437a 100644 --- a/src/wirecloud/commons/utils/template/schemas/xml_schema.xsd +++ b/src/wirecloud/commons/utils/template/schemas/xml_schema.xsd @@ -339,6 +339,16 @@ + + + + Text that will be used to reference this + variable in the user interface. This field can be + translated. + + + + From 6d151b8172bf239accc6699d94b0a0fe4cb4e8ed Mon Sep 17 00:00:00 2001 From: Alejandro Rodriguez Date: Fri, 3 Feb 2017 13:46:44 +0100 Subject: [PATCH 07/28] Fix multiuser properties not being built properly --- src/wirecloud/platform/wiring/views.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/wirecloud/platform/wiring/views.py b/src/wirecloud/platform/wiring/views.py index dc2006f472..709187f6e3 100644 --- a/src/wirecloud/platform/wiring/views.py +++ b/src/wirecloud/platform/wiring/views.py @@ -44,6 +44,7 @@ def handleMultiuser(self, request, new_preference, old_preference): new_preference["value"] = {"users": {}} new_preference["value"]["users"]["%s" % request.user.id] = new_value + return new_preference def checkWiring(self, request, new_wiring_status, old_wiring_status, can_update_secure=False): # Check read only connections @@ -82,9 +83,9 @@ def checkWiring(self, request, new_wiring_status, old_wiring_status, can_update_ if operator['preferences'][preference_name].get('readonly', False) or operator['preferences'][preference_name].get('hidden', False): return build_error_response(request, 403, _('Read only and hidden preferences cannot be created using this API')) # Handle multiuser - old_preference = old_operator['preferences'].get(preference_name, None) if old_operator else None new_preference = operator['preferences'][preference_name] - self.handleMultiuser(request, new_preference, old_preference) + new_preference["value"] = {"users": {"%s" % request.user.id: new_preference["value"]}} + operator['preferences'][preference_name] = new_preference for preference_name in removed_preferences: if old_operator['preferences'][preference_name].get('readonly', False) or old_operator['preferences'][preference_name].get('hidden', False): @@ -111,9 +112,11 @@ def checkWiring(self, request, new_wiring_status, old_wiring_status, can_update_ if preference_secure and not can_update_secure: new_preference["value"] = old_preference["value"] - else: + operator['preferences'][preference_name] = new_preference + elif new_preference["value"] != old_preference["value"]: # Handle multiuser - self.handleMultiuser(request, new_preference, old_preference) + new_preference = self.handleMultiuser(request, new_preference, old_preference) + operator['preferences'][preference_name] = new_preference return True @@ -155,7 +158,6 @@ def patch(self, request, workspace_id): result = self.checkWiring(request, new_wiring_status, old_wiring_status, can_update_secure=True) if result is not True: return result - workspace.wiringStatus = new_wiring_status workspace.save() From 0177720bdacd31dea045962358ca75a76578bac5 Mon Sep 17 00:00:00 2001 From: Alejandro Rodriguez Date: Tue, 7 Feb 2017 13:03:12 +0100 Subject: [PATCH 08/28] Fix parsers not using unicode strings --- src/wirecloud/catalogue/utils.py | 3 +- src/wirecloud/commons/tests/template.py | 24 ++- .../commons/utils/template/parsers/json.py | 7 +- .../commons/utils/template/parsers/rdf.py | 9 +- .../commons/utils/template/parsers/xml.py | 156 +++++++++--------- 5 files changed, 106 insertions(+), 93 deletions(-) diff --git a/src/wirecloud/catalogue/utils.py b/src/wirecloud/catalogue/utils.py index 953506b6f8..4bdefc13cb 100644 --- a/src/wirecloud/catalogue/utils.py +++ b/src/wirecloud/catalogue/utils.py @@ -226,7 +226,6 @@ def add_packaged_resource(file, user, wgt_file=None, template=None, deploy_only= if not deploy_only: resource_info.update(overrides) - resource = CatalogueResource.objects.create( short_name=resource_info['name'], vendor=resource_info['vendor'], @@ -236,7 +235,7 @@ def add_packaged_resource(file, user, wgt_file=None, template=None, deploy_only= template_uri=file_name, creation_date=now(), popularity='0.0', - json_description=json.dumps(resource_info) + json_description=resource_info ) return resource diff --git a/src/wirecloud/commons/tests/template.py b/src/wirecloud/commons/tests/template.py index 256b1c042a..d73406664f 100644 --- a/src/wirecloud/commons/tests/template.py +++ b/src/wirecloud/commons/tests/template.py @@ -131,7 +131,8 @@ def setUpClass(cls): 'label': 'Preference label', 'description': 'Preference description', 'default': 'value', - 'value': None + 'value': None, + 'multiuser': False, }, { 'name': 'pref2', @@ -141,7 +142,8 @@ def setUpClass(cls): 'label': 'Preference label', 'description': 'Preference description', 'default': '', - 'value': '5' + 'value': '5', + 'multiuser': False, } ], 'properties': [ @@ -260,7 +262,8 @@ def setUpClass(cls): 'label': '__MSG_pref1_label__', 'description': '__MSG_pref1_description__', 'default': 'value', - 'value': None + 'value': None, + 'multiuser': False, }, { 'name': 'pref2', @@ -270,7 +273,8 @@ def setUpClass(cls): 'label': '__MSG_pref2_label__', 'description': '__MSG_pref2_description__', 'default': '', - 'value': '5' + 'value': '5', + 'multiuser': False, } ], 'properties': [ @@ -1129,7 +1133,8 @@ def setUpClass(cls): 'label': 'Preference label', 'description': 'Preference description', 'default': '', - 'value': None + 'value': None, + 'multiuser': False, }, { 'name': 'pref2', @@ -1139,7 +1144,8 @@ def setUpClass(cls): 'label': 'Preference label', 'description': 'Preference description', 'default': 'value', - 'value': '5' + 'value': '5', + 'multiuser': False, } ], 'properties': [ @@ -1332,7 +1338,8 @@ def setUpClass(cls): 'label': '', 'description': '', 'default': '', - 'value': None + 'value': None, + 'multiuser': False, }, { 'name': 'pref2', @@ -1342,7 +1349,8 @@ def setUpClass(cls): 'label': '', 'description': '', 'default': '', - 'value': None + 'value': None, + 'multiuser': False, }, ], 'properties': [], diff --git a/src/wirecloud/commons/utils/template/parsers/json.py b/src/wirecloud/commons/utils/template/parsers/json.py index 3e7a6ff559..02840bcb6d 100644 --- a/src/wirecloud/commons/utils/template/parsers/json.py +++ b/src/wirecloud/commons/utils/template/parsers/json.py @@ -176,7 +176,7 @@ def _check_behaviour_view_fields(self, data): self._check_connection_handles(connection) def _add_translation_index(self, value, **kwargs): - index = get_trans_index(value) + index = get_trans_index(text_type(value)) if not index: return @@ -187,7 +187,7 @@ def _add_translation_index(self, value, **kwargs): def _init(self): - self._check_string_fields(('title', 'description', 'longdescription', 'email', 'homepage','doc', 'changelog', 'image', 'smartphoneimage', 'license', 'licenseurl', 'issuetracker')) + self._check_string_fields(('title', 'description', 'longdescription', 'email', 'homepage', 'doc', 'changelog', 'image', 'smartphoneimage', 'license', 'licenseurl', 'issuetracker')) self._check_contacts_fields(('authors', 'contributors')) # Normalize/check preferences and properties (only for widgets and operators) @@ -199,6 +199,7 @@ def _init(self): self._check_string_fields(('label', 'description', 'default'), place=preference) self._check_boolean_fields(('readonly', 'secure'), place=preference, default=False) self._check_string_fields(('value',), place=preference, null=True, default=None) + self._check_boolean_fields(('multiuser',), place=preference, default=False) for prop in self._info['properties']: self._check_string_fields(('name', 'type'), place=prop, required=True) @@ -252,7 +253,7 @@ def _init(self): for behaviour in self._info['wiring']['visualdescription']['behaviours']: self._check_behaviour_view_fields(behaviour) - if not 'wiring' in self._info: + if 'wiring' not in self._info: self._info['wiring'] = {} self._check_array_fields(('inputs', 'outputs'), place=self._info['wiring'], required=False) diff --git a/src/wirecloud/commons/utils/template/parsers/rdf.py b/src/wirecloud/commons/utils/template/parsers/rdf.py index 634bc7b71a..9dac5e323e 100644 --- a/src/wirecloud/commons/utils/template/parsers/rdf.py +++ b/src/wirecloud/commons/utils/template/parsers/rdf.py @@ -112,8 +112,8 @@ def _init(self): def _add_translation_index(self, value, **kwargs): if value not in self._translation_indexes: - self._translation_indexes[value] = [] - self._translation_indexes[value].append(kwargs) + self._translation_indexes[text_type(value)] = [] + self._translation_indexes[text_type(value)].append(kwargs) def _get_translation_field(self, namespace, element, subject, translation_name, required=True, **kwargs): @@ -203,7 +203,7 @@ def _parse_people_field(self, namespace, element, subject): def _parse_extra_info(self): if self._info['type'] == 'widget' or self._info['type'] == 'operator': - self._parse_widget_info() + self._parse_component_info() elif self._info['type'] == 'mashup': self._parse_workspace_info() @@ -542,7 +542,7 @@ def _parse_wiring_views(self, wiring_element): self._info['wiring']['outputs'] = outputs # END TODO - def _parse_widget_info(self): + def _parse_component_info(self): # Preference info self._info['preferences'] = [] @@ -561,6 +561,7 @@ def _parse_widget_info(self): 'default': self._get_field(WIRE, 'default', preference, required=False), 'value': self._get_field(WIRE, 'value', preference, required=False, default=None), 'secure': self._get_field(WIRE, 'secure', preference, required=False).lower() == 'true', + 'multiuser': self._get_field(WIRE, 'multiuser', preference, required=False).lower() == 'true' } if preference_info['type'] == 'list': preference_info['options'] = [] diff --git a/src/wirecloud/commons/utils/template/parsers/xml.py b/src/wirecloud/commons/utils/template/parsers/xml.py index 4e1e1a3a2a..d5cb94e735 100644 --- a/src/wirecloud/commons/utils/template/parsers/xml.py +++ b/src/wirecloud/commons/utils/template/parsers/xml.py @@ -156,7 +156,7 @@ def get_xpath(self, query, element, required=True): return None def _add_translation_index(self, value, **kwargs): - index = get_trans_index(value) + index = get_trans_index(text_type(value)) if not index: return @@ -190,10 +190,9 @@ def _get_field(self, xpath, element, required=True): raise TemplateParseException(msg % {'field': xpath}) def _parse_basic_info(self): - - self._info['vendor'] = self._doc.get('vendor', '').strip() - self._info['name'] = self._doc.get('name', '').strip() - self._info['version'] = self._doc.get('version', '').strip() + self._info['vendor'] = text_type(self._doc.get('vendor', '').strip()) + self._info['name'] = text_type(self._doc.get('name', '').strip()) + self._info['version'] = text_type(self._doc.get('version', '').strip()) self._info['title'] = self._get_field(DISPLAY_NAME_XPATH, self._component_description, required=False) self._add_translation_index(self._info['title'], type='resource', field='title') @@ -224,8 +223,8 @@ def _parse_requirements(self): for requirement in self._xpath(FEATURE_XPATH, requirements_elements[0]): self._info['requirements'].append({ - 'type': 'feature', - 'name': requirement.get('name').strip() + 'type': u'feature', + 'name': text_type(requirement.get('name').strip()) }) def _parse_visualdescription_info(self, visualdescription_element): @@ -239,8 +238,8 @@ def _parse_wiring_behaviour_view_info(self, target, behaviours_element): for behaviour in self._xpath(BEHAVIOUR_XPATH, behaviours_element): behaviour_info = get_behaviour_skeleton() - behaviour_info['title'] = behaviour.get('title') - behaviour_info['description'] = behaviour.get('description') + behaviour_info['title'] = text_type(behaviour.get('title')) + behaviour_info['description'] = text_type(behaviour.get('description')) self._parse_wiring_component_view_info(behaviour_info, behaviour) self._parse_wiring_connection_view_info(behaviour_info, behaviour) @@ -272,8 +271,8 @@ def _parse_wiring_connection_view_info(self, target, connections_element): for connection in self._xpath(CONNECTION_XPATH, connections_element): connection_info = { - 'sourcename': connection.get('sourcename'), - 'targetname': connection.get('targetname'), + 'sourcename': text_type(connection.get('sourcename')), + 'targetname': text_type(connection.get('targetname')), } sourcehandle_element = self.get_xpath(SOURCEHANDLE_XPATH, connection, required=False) @@ -285,7 +284,7 @@ def _parse_wiring_connection_view_info(self, target, connections_element): 'y': int(sourcehandle_element.get('y')) } else: - connection_info['sourcehandle'] = 'auto' + connection_info['sourcehandle'] = u'auto' if targethandle_element is not None: connection_info['targethandle'] = { @@ -293,7 +292,7 @@ def _parse_wiring_connection_view_info(self, target, connections_element): 'y': int(targethandle_element.get('y')) } else: - connection_info['targethandle'] = 'auto' + connection_info['targethandle'] = u'auto' target['connections'].append(connection_info) @@ -312,27 +311,27 @@ def _parse_wiring_info(self): wiring_element = wiring_elements[0] for slot in self._xpath(INPUT_ENDPOINT_XPATH, wiring_element): - self._add_translation_index(slot.get('label'), type='inputendpoint', variable=slot.get('name')) - self._add_translation_index(slot.get('actionlabel', ''), type='inputendpoint', variable=slot.get('name')) - self._add_translation_index(slot.get('description', ''), type='inputendpoint', variable=slot.get('name')) + self._add_translation_index(text_type(slot.get('label')), type='inputendpoint', variable=slot.get('name')) + self._add_translation_index(text_type(slot.get('actionlabel', '')), type='inputendpoint', variable=slot.get('name')) + self._add_translation_index(text_type(slot.get('description', '')), type='inputendpoint', variable=slot.get('name')) self._info['wiring']['inputs'].append({ - 'name': slot.get('name'), - 'type': slot.get('type'), - 'label': slot.get('label', ''), - 'description': slot.get('description', ''), - 'actionlabel': slot.get('actionlabel', ''), - 'friendcode': slot.get('friendcode', ''), + 'name': text_type(slot.get('name')), + 'type': text_type(slot.get('type')), + 'label': text_type(slot.get('label', '')), + 'description': text_type(slot.get('description', '')), + 'actionlabel': text_type(slot.get('actionlabel', '')), + 'friendcode': text_type(slot.get('friendcode', '')), }) for event in self._xpath(OUTPUT_ENDPOINT_XPATH, wiring_element): - self._add_translation_index(event.get('label'), type='outputendpoint', variable=event.get('name')) - self._add_translation_index(event.get('description', ''), type='outputendpoint', variable=event.get('name')) + self._add_translation_index(text_type(event.get('label')), type='outputendpoint', variable=event.get('name')) + self._add_translation_index(text_type(event.get('description', '')), type='outputendpoint', variable=event.get('name')) self._info['wiring']['outputs'].append({ - 'name': event.get('name'), - 'type': event.get('type'), - 'label': event.get('label', ''), - 'description': event.get('description', ''), - 'friendcode': event.get('friendcode', ''), + 'name': text_type(event.get('name')), + 'type': text_type(event.get('type')), + 'label': text_type(event.get('label', '')), + 'description': text_type(event.get('description', '')), + 'friendcode': text_type(event.get('friendcode', '')), }) if self._info['type'] == "mashup": @@ -341,7 +340,7 @@ def _parse_wiring_info(self): if mashup_wiring_element is None: return - self._info['wiring']['version'] = mashup_wiring_element.get('version', "1.0") + self._info['wiring']['version'] = text_type(mashup_wiring_element.get('version', "1.0")) self._parse_wiring_connection_info(mashup_wiring_element) self._parse_wiring_operator_info(mashup_wiring_element) @@ -374,14 +373,14 @@ def _parse_wiring_connection_info(self, wiring_element): connection_info = { 'readonly': connection.get('readonly', 'false').lower() == 'true', 'source': { - 'type': source_element.get('type'), - 'endpoint': source_element.get('endpoint'), - 'id': source_element.get('id'), + 'type': text_type(source_element.get('type')), + 'endpoint': text_type(source_element.get('endpoint')), + 'id': text_type(source_element.get('id')), }, 'target': { - 'type': target_element.get('type'), - 'endpoint': target_element.get('endpoint'), - 'id': target_element.get('id'), + 'type': text_type(target_element.get('type')), + 'endpoint': text_type(target_element.get('endpoint')), + 'id': text_type(target_element.get('id')), } } @@ -395,16 +394,17 @@ def _parse_wiring_operator_info(self, wiring_element): for operator in self._xpath(IOPERATOR_XPATH, wiring_element): operator_info = { - 'id': operator.get('id'), - 'name': '/'.join((operator.get('vendor'), operator.get('name'), operator.get('version'))), + 'id': text_type(operator.get('id')), + 'name': text_type('/'.join((operator.get('vendor'), operator.get('name'), operator.get('version')))), 'preferences': {}, } for pref in self._xpath(PREFERENCE_VALUE_XPATH, operator): - operator_info['preferences'][pref.get('name')] = { + pref_value = pref.get('value') + operator_info['preferences'][text_type(pref.get('name'))] = { 'readonly': pref.get('readonly', 'false').lower() == 'true', 'hidden': pref.get('hidden', 'false').lower() == 'true', - 'value': pref.get('value'), + 'value': text_type(pref_value) if pref_value is not None else None, } self._info['wiring']['operators'][operator_info['id']] = operator_info @@ -417,7 +417,7 @@ def _parse_widget_info(self): xhtml_element = self._xpath(CODE_XPATH, self._doc)[0] self._info['contents'] = { - 'src': xhtml_element.get('src'), + 'src': text_type(xhtml_element.get('src')), 'contenttype': xhtml_element.get('contenttype', 'text/html'), 'charset': xhtml_element.get('charset', 'utf-8'), 'useplatformstyle': xhtml_element.get('useplatformstyle', 'false').lower() == 'true', @@ -445,7 +445,7 @@ def _parse_operator_info(self): self._info['js_files'] = [] for script in self._xpath(SCRIPT_XPATH, self._doc): - self._info['js_files'].append(script.get('src')) + self._info['js_files'].append(text_type(script.get('src'))) def _parse_component_preferences(self): @@ -454,14 +454,15 @@ def _parse_component_preferences(self): self._add_translation_index(preference.get('label'), type='vdef', variable=preference.get('name'), field='label') self._add_translation_index(preference.get('description', ''), type='vdef', variable=preference.get('name'), field='description') preference_info = { - 'name': preference.get('name'), - 'type': preference.get('type'), - 'label': preference.get('label', ''), - 'description': preference.get('description', ''), + 'name': text_type(preference.get('name')), + 'type': text_type(preference.get('type')), + 'label': text_type(preference.get('label', '')), + 'description': text_type(preference.get('description', '')), 'readonly': preference.get('readonly', 'false').lower() == 'true', - 'default': preference.get('default', ''), + 'default': text_type(preference.get('default', '')), 'value': preference.get('value'), 'secure': preference.get('secure', 'false').lower() == 'true', + 'multiuser': False } if preference_info['type'] == 'list': @@ -470,7 +471,7 @@ def _parse_component_preferences(self): option_label = option.get('label', option.get('name')) self._add_translation_index(option_label, type='upo', variable=preference.get('name'), option=option_index) preference_info['options'].append({ - 'label': option_label, + 'label': text_type(option_label), 'value': option.get('value'), }) @@ -483,11 +484,11 @@ def _parse_component_persistentvariables(self): self._add_translation_index(prop.get('label'), type='vdef', variable=prop.get('name')) self._add_translation_index(prop.get('description', ''), type='vdef', variable=prop.get('name')) self._info['properties'].append({ - 'name': prop.get('name'), - 'type': prop.get('type'), - 'label': prop.get('label', ''), - 'description': prop.get('description', ''), - 'default': prop.get('default', ''), + 'name': text_type(prop.get('name')), + 'type': text_type(prop.get('type')), + 'label': text_type(prop.get('label', '')), + 'description': text_type(prop.get('description', '')), + 'default': text_type(prop.get('default', '')), 'secure': prop.get('secure', 'false').lower() == 'true', 'multiuser': prop.get('multiuser', 'false').lower() == 'true' }) @@ -496,7 +497,8 @@ def _parse_preference_values(self, element): values = {} for preference in self._xpath(PREFERENCE_VALUE_XPATH, element): - values[preference.get('name')] = preference.get('value') + pref_value = preference.get('value') + values[text_type(preference.get('name'))] = text_type(pref_value) if pref_value is not None else None return values @@ -517,16 +519,16 @@ def _parse_workspace_info(self): self._info['embedded'] = [] for component in self._xpath(EMBEDDEDRESOURCE_XPATH, self._doc): self._info['embedded'].append({ - 'vendor': component.get('vendor'), - 'name': component.get('name'), - 'version': component.get('version'), - 'src': component.get('src') + 'vendor': text_type(component.get('vendor')), + 'name': text_type(component.get('name')), + 'version': text_type(component.get('version')), + 'src': text_type(component.get('src')) }) tabs = [] for tab in self._xpath(TAB_XPATH, workspace_structure): tab_info = { - 'name': tab.get('name'), + 'name': text_type(tab.get('name')), 'preferences': self._parse_preference_values(tab), 'resources': [], } @@ -536,39 +538,41 @@ def _parse_workspace_info(self): rendering = self.get_xpath(RENDERING_XPATH, widget) widget_info = { - 'id': widget.get('id'), - 'name': widget.get('name'), - 'vendor': widget.get('vendor'), - 'version': widget.get('version'), - 'title': widget.get('title'), + 'id': text_type(widget.get('id')), + 'name': text_type(widget.get('name')), + 'vendor': text_type(widget.get('vendor')), + 'version': text_type(widget.get('version')), + 'title': text_type(widget.get('title')), 'readonly': widget.get('readonly', '').lower() == 'true', 'properties': {}, 'preferences': {}, 'position': { - 'x': position.get('x'), - 'y': position.get('y'), - 'z': position.get('z'), + 'x': text_type(position.get('x')), + 'y': text_type(position.get('y')), + 'z': text_type(position.get('z')), }, 'rendering': { 'fulldragboard': rendering.get('fulldragboard', 'false').lower() == 'true', 'minimized': rendering.get('minimized', 'false').lower() == 'true', - 'width': rendering.get('width'), - 'height': rendering.get('height'), - 'layout': rendering.get('layout'), + 'width': text_type(rendering.get('width')), + 'height': text_type(rendering.get('height')), + 'layout': text_type(rendering.get('layout')), }, } for prop in self._xpath(PROPERTIES_XPATH, widget): - widget_info['properties'][prop.get('name')] = { + prop_value = prop.get('value') + widget_info['properties'][text_type(prop.get('name'))] = { 'readonly': prop.get('readonly', 'false').lower() == 'true', - 'value': prop.get('value'), + 'value': text_type(prop_value) if prop_value is not None else None, } for pref in self._xpath(PREFERENCE_VALUE_XPATH, widget): - widget_info['preferences'][pref.get('name')] = { + pref_value = pref.get('value') + widget_info['preferences'][text_type(pref.get('name'))] = { 'readonly': pref.get('readonly', 'false').lower() == 'true', 'hidden': pref.get('hidden', 'false').lower() == 'true', - 'value': pref.get('value'), + 'value': text_type(pref_value) if pref_value is not None else None, } tab_info['resources'].append(widget_info) @@ -593,7 +597,7 @@ def _parse_translation_catalogue(self): extra_translations = set() translations = translations_elements[0] - self._info['default_lang'] = translations.get('default') + self._info['default_lang'] = text_type(translations.get('default')) for translation in self._xpath(TRANSLATION_XPATH, translations): current_catalogue = {} @@ -602,7 +606,7 @@ def _parse_translation_catalogue(self): if msg.get('name') not in self._translation_indexes: extra_translations.add(msg.get('name')) - current_catalogue[msg.get('name')] = msg.text + current_catalogue[msg.get('name')] = text_type(msg.text) self._info['translations'][translation.get('lang')] = current_catalogue From ddf6dd006ef299c706fbe8c1c8307f9fe543ff28 Mon Sep 17 00:00:00 2001 From: Alejandro Rodriguez Date: Tue, 7 Feb 2017 13:05:19 +0100 Subject: [PATCH 09/28] Fix multiuser migrations --- src/wirecloud/platform/migration_utils.py | 31 ++++++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/src/wirecloud/platform/migration_utils.py b/src/wirecloud/platform/migration_utils.py index 5269324b46..26c8c6486e 100644 --- a/src/wirecloud/platform/migration_utils.py +++ b/src/wirecloud/platform/migration_utils.py @@ -19,14 +19,32 @@ from __future__ import unicode_literals -from django.db import migrations, models from django.db.migrations.exceptions import IrreversibleError import six +def mutate_forwards_operator(preference, userID): + preference["value"] = {"users": {"%s" % userID: preference["value"]}} + return preference + + +def mutate_forwards_widget(preference, userID): + preference = {"users": {"%s" % userID: preference}} + return preference + + +def mutate_backwards_operator(preference, userID): + preference["value"] = preference["value"]["users"]["%s" % userID] + return preference + + +def mutate_backwards_widget(preference, userID): + preference = preference["users"]["%s" % userID] + return preference + + def update_variables_structure(apps, schema_editor): - mutate = lambda value, userID: {"users": {userID: value}} Workspace = apps.get_model("platform", "workspace") for workspace in Workspace.objects.select_related('creator').all(): @@ -35,13 +53,13 @@ def update_variables_structure(apps, schema_editor): # Update operators wiring = workspace.wiringStatus for op in wiring["operators"]: - wiring["operators"][op]["preferences"] = {k: mutate(v, owner) for k, v in six.iteritems(wiring["operators"][op]["preferences"])} + wiring["operators"][op]["preferences"] = {k: mutate_forwards_operator(v, owner) for k, v in six.iteritems(wiring["operators"][op]["preferences"])} workspace.save() # Update widgets for tab in workspace.tab_set.all(): for widget in tab.iwidget_set.all(): - widget.variables = {k: mutate(v, owner) for k, v in six.iteritems(widget.variables)} + widget.variables = {k: mutate_forwards_widget(v, owner) for k, v in six.iteritems(widget.variables)} widget.save() @@ -56,7 +74,6 @@ def reverse_variables_structure(apps, schema_editor): uri = component.vendor + '/' + component.short_name + '/' + component.version raise IrreversibleError("Component %s requires multiuser support. Uninstall it before downgrading." % uri) - mutate = lambda value, userID: value["users"]["%s" % userID] Workspace = apps.get_model("platform", "workspace") for workspace in Workspace.objects.select_related('creator').all(): owner = workspace.creator.id @@ -64,11 +81,11 @@ def reverse_variables_structure(apps, schema_editor): # Update operators wiring = workspace.wiringStatus for op in wiring["operators"]: - wiring["operators"][op]["preferences"] = {k: mutate(v, owner) for k, v in six.iteritems(wiring["operators"][op]["preferences"])} + wiring["operators"][op]["preferences"] = {k: mutate_backwards_operator(v, owner) for k, v in six.iteritems(wiring["operators"][op]["preferences"])} workspace.save() # Update widgets for tab in workspace.tab_set.all(): for widget in tab.iwidget_set.all(): - widget.variables = {k: mutate(v, owner) for k, v in six.iteritems(widget.variables)} + widget.variables = {k: mutate_backwards_widget(v, owner) for k, v in six.iteritems(widget.variables)} widget.save() From 2372541663039e29fcd6fa569c284ed381783f8f Mon Sep 17 00:00:00 2001 From: Alejandro Rodriguez Date: Tue, 7 Feb 2017 13:11:19 +0100 Subject: [PATCH 10/28] Add multiuser variable handling --- src/wirecloud/platform/tests/rest_api.py | 6 +- src/wirecloud/platform/wiring/views.py | 79 ++++++++++++++++++++--- src/wirecloud/platform/workspace/utils.py | 28 ++++---- 3 files changed, 89 insertions(+), 24 deletions(-) diff --git a/src/wirecloud/platform/tests/rest_api.py b/src/wirecloud/platform/tests/rest_api.py index f751fb5475..49c723f883 100644 --- a/src/wirecloud/platform/tests/rest_api.py +++ b/src/wirecloud/platform/tests/rest_api.py @@ -1183,7 +1183,7 @@ def test_workspace_wiring_entry_patch_preference_value(self): self.assertEqual(response.status_code, 204) # Check if preferences changed - self.assertEqual(Workspace.objects.get(pk=202).wiringStatus["operators"]["2"]["preferences"]["pref_secure"]["value"], "helloWorld") + self.assertEqual(Workspace.objects.get(pk=202).wiringStatus["operators"]["2"]["preferences"]["pref_secure"]["value"]["users"]["4"], "helloWorld") def test_workspace_wiring_entry_patch_preference_value_read_only_permission(self): url = reverse('wirecloud.workspace_wiring', kwargs={'workspace_id': 202}) @@ -4239,7 +4239,7 @@ def test_workspace_publish_including_images(self): # Check images has been uploaded test_mashup = CatalogueResource.objects.get(short_name='test-published-mashup') base_dir = catalogue.wgt_deployer.get_base_dir('Wirecloud', 'test-published-mashup', '1.0.5') - test_mashup_info = json.loads(test_mashup.json_description) + test_mashup_info = test_mashup.json_description image_path = os.path.join(base_dir, test_mashup_info['image']) self.assertTrue(filecmp.cmp(original_catalogue_image, image_path)) @@ -4271,7 +4271,7 @@ def test_workspace_publish_including_longdescription(self): # Check long description field has been converted into a file test_mashup = CatalogueResource.objects.get(short_name='test-published-mashup') base_dir = catalogue.wgt_deployer.get_base_dir('Wirecloud', 'test-published-mashup', '1.0.5') - test_mashup_info = json.loads(test_mashup.json_description) + test_mashup_info = test_mashup.json_description image_path = os.path.join(base_dir, test_mashup_info['longdescription']) with open(image_path, 'rb') as f: diff --git a/src/wirecloud/platform/wiring/views.py b/src/wirecloud/platform/wiring/views.py index 709187f6e3..9bcf250a71 100644 --- a/src/wirecloud/platform/wiring/views.py +++ b/src/wirecloud/platform/wiring/views.py @@ -46,6 +46,59 @@ def handleMultiuser(self, request, new_preference, old_preference): new_preference["value"]["users"]["%s" % request.user.id] = new_value return new_preference + def checkMultiuserWiring(self, request, new_wiring_status, old_wiring_status, can_update_secure=False): + if new_wiring_status["connections"] != old_wiring_status["connections"]: + return build_error_response(request, 403, _('You are not allowed to update this workspace')) + + for operator_id, operator in six.iteritems(new_wiring_status['operators']): + if operator_id not in old_wiring_status['operators']: + return build_error_response(request, 403, _('You are not allowed to update this workspace')) + + old_operator = old_wiring_status['operators'][operator_id] + if old_operator["name"] != operator["name"] or old_operator["id"] != operator["id"]: + return build_error_response(request, 403, _('You are not allowed to update this workspace')) + + vendor, name, version = operator["name"].split("/") + try: + operator_preferences = CatalogueResource.objects.get(vendor=vendor, short_name=name, version=version).get_processed_info()["preferences"] + except CatalogueResource.DoesNotExist: + operator_preferences = None + + for preference_name in operator['preferences']: + old_preference = old_operator['preferences'][preference_name] + new_preference = operator['preferences'][preference_name] + + if old_preference["hidden"] != new_preference["hidden"] or old_preference["readonly"] != new_preference["readonly"]: + return build_error_response(request, 403, _('You are not allowed to update this workspace')) + + # Check if its multiuser + preference_secure = False + preference_multiuser = False + if operator_preferences: + for pref in operator_preferences: + if pref["name"] == preference_name: + preference_secure = pref.get("secure", False) + preference_multiuser = pref.get("multiuser", False) + operator_preferences.remove(pref) # Speed up search for next preferences + break + + # Variables can only be updated if multisuer + if not preference_multiuser: + if old_preference["value"] != new_preference["value"]: + return build_error_response(request, 403, _('You are not allowed to update this workspace')) + else: + continue + + # Update variable value + if preference_secure and not can_update_secure: + new_preference["value"] = old_preference["value"] + elif new_preference["value"] != old_preference["value"]: + # Handle multiuser + new_preference = self.handleMultiuser(request, new_preference, old_preference) + operator['preferences'][preference_name] = new_preference + + return True + def checkWiring(self, request, new_wiring_status, old_wiring_status, can_update_secure=False): # Check read only connections old_read_only_connections = [connection for connection in old_wiring_status['connections'] if connection.get('readonly', False)] @@ -57,7 +110,6 @@ def checkWiring(self, request, new_wiring_status, old_wiring_status, can_update_ for connection in old_read_only_connections: if connection not in new_read_only_connections: return build_error_response(request, 403, _('You are not allowed to remove or update read only connections')) - # Check operator preferences for operator_id, operator in six.iteritems(new_wiring_status['operators']): old_operator = None @@ -66,7 +118,6 @@ def checkWiring(self, request, new_wiring_status, old_wiring_status, can_update_ added_preferences = set(operator['preferences'].keys()) - set(old_operator['preferences'].keys()) removed_preferences = set(old_operator['preferences'].keys()) - set(operator['preferences'].keys()) updated_preferences = set(operator['preferences'].keys()).intersection(old_operator['preferences'].keys()) - vendor, name, version = operator["name"].split("/") try: operator_preferences = CatalogueResource.objects.get(vendor=vendor, short_name=name, version=version).get_processed_info()["preferences"] @@ -82,6 +133,7 @@ def checkWiring(self, request, new_wiring_status, old_wiring_status, can_update_ for preference_name in added_preferences: if operator['preferences'][preference_name].get('readonly', False) or operator['preferences'][preference_name].get('hidden', False): return build_error_response(request, 403, _('Read only and hidden preferences cannot be created using this API')) + # Handle multiuser new_preference = operator['preferences'][preference_name] new_preference["value"] = {"users": {"%s" % request.user.id: new_preference["value"]}} @@ -112,11 +164,10 @@ def checkWiring(self, request, new_wiring_status, old_wiring_status, can_update_ if preference_secure and not can_update_secure: new_preference["value"] = old_preference["value"] - operator['preferences'][preference_name] = new_preference elif new_preference["value"] != old_preference["value"]: # Handle multiuser new_preference = self.handleMultiuser(request, new_preference, old_preference) - operator['preferences'][preference_name] = new_preference + operator['preferences'][preference_name] = new_preference return True @@ -125,13 +176,17 @@ def checkWiring(self, request, new_wiring_status, old_wiring_status, can_update_ def update(self, request, workspace_id): workspace = get_object_or_404(Workspace, id=workspace_id) - if not request.user.is_superuser and workspace.creator != request.user: - return build_error_response(request, 403, _('You are not allowed to update this workspace')) new_wiring_status = parse_json_request(request) old_wiring_status = workspace.wiringStatus - result = self.checkWiring(request, new_wiring_status, old_wiring_status) + if workspace.creator == request.user or request.user.is_superuser: + result = self.checkWiring(request, new_wiring_status, old_wiring_status, can_update_secure=True) + elif workspace.is_available_for(request.user): + result = self.checkMultiuserWiring(request, new_wiring_status, old_wiring_status, can_update_secure=True) + else: + return build_error_response(request, 403, _('You are not allowed to update this workspace')) + if result is not True: return result @@ -144,8 +199,6 @@ def update(self, request, workspace_id): @consumes(('application/json-patch+json',)) def patch(self, request, workspace_id): workspace = get_object_or_404(Workspace, id=workspace_id) - if not request.user.is_superuser and workspace.creator != request.user: - return build_error_response(request, 403, _('You are not allowed to update this workspace')) old_wiring_status = workspace.wiringStatus try: @@ -155,7 +208,13 @@ def patch(self, request, workspace_id): except jsonpatch.InvalidJsonPatch: return build_error_response(request, 400, _('Invalid JSON patch')) - result = self.checkWiring(request, new_wiring_status, old_wiring_status, can_update_secure=True) + if workspace.creator == request.user or request.user.is_superuser: + result = self.checkWiring(request, new_wiring_status, old_wiring_status, can_update_secure=True) + elif workspace.is_available_for(request.user): + result = self.checkMultiuserWiring(request, new_wiring_status, old_wiring_status, can_update_secure=True) + else: + return build_error_response(request, 403, _('You are not allowed to update this workspace')) + if result is not True: return result workspace.wiringStatus = new_wiring_status diff --git a/src/wirecloud/platform/workspace/utils.py b/src/wirecloud/platform/workspace/utils.py index b28c4e0a2a..a093b1dd8a 100644 --- a/src/wirecloud/platform/workspace/utils.py +++ b/src/wirecloud/platform/workspace/utils.py @@ -163,7 +163,7 @@ def get_workspace_list(user): return workspaces -def _process_variable(iwidget, svariwidget, vardef, forced_values, values_by_varname, values_by_varid, user): +def _process_variable(iwidget, svariwidget, vardef, forced_values, values_by_varname, values_by_varid, current_user, workspace_creator): varname = vardef['name'] entry = { 'type': vardef['type'], @@ -182,11 +182,13 @@ def _process_variable(iwidget, svariwidget, vardef, forced_values, values_by_var entry['hidden'] = fv_entry.get('hidden', False) else: + # Handle multiuser variables value = iwidget.variables.get(varname, None) - if value is None or value["users"].get("%s" % user.id, None) is None: + variable_user = current_user if vardef.get("multiuser", False) else workspace_creator + if value is None or value["users"].get("%s" % variable_user.id, None) is None: value = parse_value_from_text(entry, vardef['default']) else: - value = value["users"].get("%s" % user.id, None) + value = value["users"].get("%s" % variable_user.id, None) entry['value'] = value entry['readonly'] = False @@ -216,10 +218,10 @@ def _populate_variables_values_cache(workspace, user, key, forced_values=None): iwidget_info = iwidget.widget.resource.get_processed_info() for vardef in iwidget_info['preferences']: - _process_variable(iwidget, svariwidget, vardef, forced_values, values_by_varname, values_by_varid, user) + _process_variable(iwidget, svariwidget, vardef, forced_values, values_by_varname, values_by_varid, user, workspace.creator) for vardef in iwidget_info['properties']: - _process_variable(iwidget, svariwidget, vardef, forced_values, values_by_varname, values_by_varid, user) + _process_variable(iwidget, svariwidget, vardef, forced_values, values_by_varname, values_by_varid, user, workspace.creator) values = { 'by_varid': values_by_varid, @@ -283,7 +285,7 @@ def get_variable_value_from_varname(self, iwidget, var_name): return value # Get preference data - def get_variable_data(self, iwidget, var_name, user): + def get_variable_data(self, iwidget, var_name): values = self.get_variable_values() entry = values['by_varname'][iwidget.id][var_name] @@ -302,7 +304,7 @@ def get_variable_data(self, iwidget, var_name, user): } # Get the persistent property data - def get_property_data(self, iwidget, var_name, user): + def get_property_data(self, iwidget, var_name): values = self.get_variable_values() entry = values['by_varname'][iwidget.id][var_name] @@ -494,13 +496,17 @@ def _get_global_workspace_data(workspaceDAO, user): for preference_name, preference in six.iteritems(operator.get('preferences', {})): vardef = operator_info['variables']['preferences'].get(preference_name) value = preference.get('value', None) + + # Handle multiuser + variable_user = user if vardef is not None and vardef["multiuser"] else workspaceDAO.creator + if preference_name in operator_forced_values: preference['value'] = operator_forced_values[preference_name]['value'] - elif value is None or value["users"].get("%s" % user.id, None) is None: + elif value is None or value["users"].get("%s" % variable_user.id, None) is None: # If not defined / not defined for the current user, take the default value preference['value'] = parse_value_from_text(vardef, vardef['default']) else: - preference['value'] = value["users"].get("%s" % user.id) + preference['value'] = value["users"].get("%s" % variable_user.id) # Secure censor if vardef is not None and vardef["secure"]: @@ -568,8 +574,8 @@ def get_iwidget_data(iwidget, workspace, cache_manager=None, user=None): cache_manager = VariableValueCacheManager(workspace, user) iwidget_info = iwidget.widget.resource.get_processed_info() - data_ret['preferences'] = {preference['name']: cache_manager.get_variable_data(iwidget, preference['name'], user) for preference in iwidget_info['preferences']} - data_ret['properties'] = {property['name']: cache_manager.get_property_data(iwidget, property['name'], user) for property in iwidget_info['properties']} + data_ret['preferences'] = {preference['name']: cache_manager.get_variable_data(iwidget, preference['name']) for preference in iwidget_info['preferences']} + data_ret['properties'] = {property['name']: cache_manager.get_property_data(iwidget, property['name']) for property in iwidget_info['properties']} return data_ret From 30ce3dc0469cd1b135b0c7ca79be2c2f4ca251e0 Mon Sep 17 00:00:00 2001 From: Alejandro Rodriguez Date: Wed, 8 Feb 2017 13:30:45 +0100 Subject: [PATCH 11/28] Add persistent variables to operators --- .../fixtures/user_with_workspaces.json | 10 +- .../platform/fixtures/test_data.json | 4 +- .../js/WirecloudAPI/WirecloudOperatorAPI.js | 23 +++- .../wirecloud/MashableApplicationComponent.js | 15 ++- .../static/js/wirecloud/PropertyCommiter.js | 55 ++++++++- .../static/js/wirecloud/wiring/Operator.js | 37 +++++- src/wirecloud/platform/tests/rest_api.py | 6 +- src/wirecloud/platform/wiring/tests.py | 48 +++++--- src/wirecloud/platform/wiring/views.py | 108 ++++++++++++++++-- src/wirecloud/platform/workspace/utils.py | 21 ++++ 10 files changed, 283 insertions(+), 44 deletions(-) diff --git a/src/wirecloud/commons/fixtures/user_with_workspaces.json b/src/wirecloud/commons/fixtures/user_with_workspaces.json index f7d95236c6..17b7205874 100644 --- a/src/wirecloud/commons/fixtures/user_with_workspaces.json +++ b/src/wirecloud/commons/fixtures/user_with_workspaces.json @@ -230,7 +230,7 @@ "model": "platform.workspace", "fields": { "forcedValues": "", - "wiringStatus": "{\"connections\": [{\"source\": {\"type\": \"widget\", \"id\": 1, \"endpoint\": \"outputendpoint\"}, \"readonly\": false, \"target\": {\"type\": \"widget\", \"id\": 2, \"endpoint\": \"inputendpoint\"}}, {\"source\": {\"type\": \"widget\", \"id\": 2, \"endpoint\": \"outputendpoint\"}, \"readonly\": false, \"target\": {\"type\": \"operator\", \"id\": 0, \"endpoint\": \"input\"}}, {\"source\": {\"type\": \"operator\", \"id\": 0, \"endpoint\": \"output\"}, \"readonly\": false, \"target\": {\"type\": \"widget\", \"id\": 1, \"endpoint\": \"inputendpoint\"}}], \"operators\": {\"0\": {\"preferences\": {}, \"name\": \"Wirecloud/TestOperator/1.0\", \"id\": \"0\"}}, \"version\": \"2.0\", \"visualdescription\": {\"connections\": [{\"sourcename\": \"widget/1/outputendpoint\", \"targethandle\": \"auto\", \"targetname\": \"widget/2/inputendpoint\", \"sourcehandle\": \"auto\"}, {\"sourcename\": \"widget/2/outputendpoint\", \"targethandle\": \"auto\", \"targetname\": \"operator/0/input\", \"sourcehandle\": \"auto\"}, {\"sourcename\": \"operator/0/output\", \"targethandle\": \"auto\", \"targetname\": \"widget/1/inputendpoint\", \"sourcehandle\": \"auto\"}], \"behaviours\": [], \"components\": {\"operator\": {\"0\": {\"position\": {\"y\": 20, \"x\": 440}, \"collapsed\": false, \"endpoints\": {\"source\": [\"output\"], \"target\": [\"input\"]}}}, \"widget\": {\"1\": {\"position\": {\"y\": 280, \"x\": 260}, \"name\": \"Wirecloud/Test/1.0\", \"endpoints\": {\"source\": [\"outputendpoint\"], \"target\": [\"inputendpoint\"]}}, \"2\": {\"position\": {\"y\": 10, \"x\": 60}, \"name\": \"Wirecloud/Test/1.0\", \"endpoints\": {\"source\": [\"outputendpoint\"], \"target\": [\"inputendpoint\"]}}}}}}", + "wiringStatus": "{\"connections\": [{\"source\": {\"type\": \"widget\", \"id\": 1, \"endpoint\": \"outputendpoint\"}, \"readonly\": false, \"target\": {\"type\": \"widget\", \"id\": 2, \"endpoint\": \"inputendpoint\"}}, {\"source\": {\"type\": \"widget\", \"id\": 2, \"endpoint\": \"outputendpoint\"}, \"readonly\": false, \"target\": {\"type\": \"operator\", \"id\": 0, \"endpoint\": \"input\"}}, {\"source\": {\"type\": \"operator\", \"id\": 0, \"endpoint\": \"output\"}, \"readonly\": false, \"target\": {\"type\": \"widget\", \"id\": 1, \"endpoint\": \"inputendpoint\"}}], \"operators\": {\"0\": {\"preferences\": {}, \"name\": \"Wirecloud/TestOperator/1.0\", \"properties\": {}, \"id\": \"0\"}}, \"version\": \"2.0\", \"visualdescription\": {\"connections\": [{\"sourcename\": \"widget/1/outputendpoint\", \"targethandle\": \"auto\", \"targetname\": \"widget/2/inputendpoint\", \"sourcehandle\": \"auto\"}, {\"sourcename\": \"widget/2/outputendpoint\", \"targethandle\": \"auto\", \"targetname\": \"operator/0/input\", \"sourcehandle\": \"auto\"}, {\"sourcename\": \"operator/0/output\", \"targethandle\": \"auto\", \"targetname\": \"widget/1/inputendpoint\", \"sourcehandle\": \"auto\"}], \"behaviours\": [], \"components\": {\"operator\": {\"0\": {\"position\": {\"y\": 20, \"x\": 440}, \"collapsed\": false, \"endpoints\": {\"source\": [\"output\"], \"target\": [\"input\"]}}}, \"widget\": {\"1\": {\"position\": {\"y\": 280, \"x\": 260}, \"name\": \"Wirecloud/Test/1.0\", \"properties\": {}, \"endpoints\": {\"source\": [\"outputendpoint\"], \"target\": [\"inputendpoint\"]}}, \"2\": {\"position\": {\"y\": 10, \"x\": 60}, \"name\": \"Wirecloud/Test/1.0\", \"properties\": {}, \"endpoints\": {\"source\": [\"outputendpoint\"], \"target\": [\"inputendpoint\"]}}}}}}", "name": "Workspace", "groups": [], "creator": 4 @@ -241,7 +241,7 @@ "model": "platform.workspace", "fields": { "forcedValues": "", - "wiringStatus": "{\"connections\": [{\"source\": {\"type\": \"widget\", \"id\": 3, \"endpoint\": \"outputendpoint\"}, \"readonly\": false, \"target\": {\"type\": \"widget\", \"id\": 4, \"endpoint\": \"inputendpoint\"}}, {\"source\": {\"type\": \"widget\", \"id\": 4, \"endpoint\": \"outputendpoint\"}, \"readonly\": false, \"target\": {\"type\": \"operator\", \"id\": 0, \"endpoint\": \"input\"}}, {\"source\": {\"type\": \"operator\", \"id\": 0, \"endpoint\": \"output\"}, \"readonly\": false, \"target\": {\"type\": \"widget\", \"id\": 3, \"endpoint\": \"inputendpoint\"}}], \"operators\": {\"0\": {\"preferences\": {}, \"name\": \"Wirecloud/TestOperator/1.0\", \"id\": \"0\"}}, \"version\": \"2.0\", \"visualdescription\": {\"connections\": [{\"sourcename\": \"widget/3/outputendpoint\", \"targethandle\": {\"x\": -150, \"y\": 0}, \"targetname\": \"widget/4/inputendpoint\", \"sourcehandle\": {\"x\": 150, \"y\": 0}}, {\"sourcename\": \"widget/4/outputendpoint\", \"targethandle\": {\"x\": -150, \"y\": 0}, \"targetname\": \"operator/0/input\", \"sourcehandle\": {\"x\": 150, \"y\": 0}}, {\"sourcename\": \"operator/0/output\", \"targethandle\": {\"x\": -150, \"y\": 0}, \"targetname\": \"widget/3/inputendpoint\", \"sourcehandle\": {\"x\": 150, \"y\": 0}}], \"behaviours\": [], \"components\": {\"operator\": {\"0\": {\"position\": {\"y\": 256, \"x\": 84}, \"collapsed\": false, \"endpoints\": {\"source\": [\"output\"], \"target\": [\"input\"]}}}, \"widget\": {\"3\": {\"position\": {\"y\": 44, \"x\": 84}, \"endpoints\": {\"source\": [\"outputendpoint\"], \"target\": [\"inputendpoint\"]}}, \"4\": {\"position\": {\"y\": 153, \"x\": 84}, \"endpoints\": {\"source\": [\"outputendpoint\"], \"target\": [\"inputendpoint\"]}}}}}}", + "wiringStatus": "{\"connections\": [{\"source\": {\"type\": \"widget\", \"id\": 3, \"endpoint\": \"outputendpoint\"}, \"readonly\": false, \"target\": {\"type\": \"widget\", \"id\": 4, \"endpoint\": \"inputendpoint\"}}, {\"source\": {\"type\": \"widget\", \"id\": 4, \"endpoint\": \"outputendpoint\"}, \"readonly\": false, \"target\": {\"type\": \"operator\", \"id\": 0, \"endpoint\": \"input\"}}, {\"source\": {\"type\": \"operator\", \"id\": 0, \"endpoint\": \"output\"}, \"readonly\": false, \"target\": {\"type\": \"widget\", \"id\": 3, \"endpoint\": \"inputendpoint\"}}], \"operators\": {\"0\": {\"preferences\": {}, \"name\": \"Wirecloud/TestOperator/1.0\", \"properties\": {}, \"id\": \"0\"}}, \"version\": \"2.0\", \"visualdescription\": {\"connections\": [{\"sourcename\": \"widget/3/outputendpoint\", \"targethandle\": {\"x\": -150, \"y\": 0}, \"targetname\": \"widget/4/inputendpoint\", \"sourcehandle\": {\"x\": 150, \"y\": 0}}, {\"sourcename\": \"widget/4/outputendpoint\", \"targethandle\": {\"x\": -150, \"y\": 0}, \"targetname\": \"operator/0/input\", \"sourcehandle\": {\"x\": 150, \"y\": 0}}, {\"sourcename\": \"operator/0/output\", \"targethandle\": {\"x\": -150, \"y\": 0}, \"targetname\": \"widget/3/inputendpoint\", \"sourcehandle\": {\"x\": 150, \"y\": 0}}], \"behaviours\": [], \"components\": {\"operator\": {\"0\": {\"position\": {\"y\": 256, \"x\": 84}, \"collapsed\": false, \"endpoints\": {\"source\": [\"output\"], \"target\": [\"input\"]}}}, \"widget\": {\"3\": {\"position\": {\"y\": 44, \"x\": 84}, \"endpoints\": {\"source\": [\"outputendpoint\"], \"target\": [\"inputendpoint\"]}}, \"4\": {\"position\": {\"y\": 153, \"x\": 84}, \"endpoints\": {\"source\": [\"outputendpoint\"], \"target\": [\"inputendpoint\"]}}}}}}", "name": "Pending Events", "groups": [], "creator": 4 @@ -252,7 +252,7 @@ "model": "platform.workspace", "fields": { "forcedValues": "", - "wiringStatus": "{\"connections\": [{\"source\": {\"endpoint\": \"outputendpoint\", \"type\": \"widget\", \"id\": 5}, \"readonly\": false, \"target\": {\"endpoint\": \"inputendpoint\", \"type\": \"widget\", \"id\": 6}}, {\"source\": {\"endpoint\": \"outputendpoint\", \"type\": \"widget\", \"id\": 6}, \"readonly\": false, \"target\": {\"endpoint\": \"input\", \"type\": \"operator\", \"id\": 0}}, {\"source\": {\"endpoint\": \"output\", \"type\": \"operator\", \"id\": 0}, \"readonly\": false, \"target\": {\"endpoint\": \"inputendpoint\", \"type\": \"widget\", \"id\": 5}}], \"operators\": {\"0\": {\"preferences\": {}, \"name\": \"Wirecloud/TestOperator/1.0\", \"id\": \"0\"}}, \"version\": \"2.0\", \"visualdescription\": {\"connections\": [{\"sourcename\": \"widget/5/outputendpoint\", \"targethandle\": {\"y\": 0, \"x\": -150}, \"targetname\": \"widget/6/inputendpoint\", \"sourcehandle\": {\"y\": 0, \"x\": 150}}, {\"sourcename\": \"widget/6/outputendpoint\", \"targethandle\": {\"y\": 0, \"x\": -150}, \"targetname\": \"operator/0/input\", \"sourcehandle\": {\"y\": 0, \"x\": 150}}, {\"sourcename\": \"operator/0/output\", \"targethandle\": {\"y\": 0, \"x\": -150}, \"targetname\": \"widget/5/inputendpoint\", \"sourcehandle\": {\"y\": 0, \"x\": 150}}], \"behaviours\": [], \"components\": {\"operator\": {\"0\": {\"position\": {\"y\": 296, \"x\": 84}, \"collapsed\": false, \"endpoints\": {\"source\": [\"output\"], \"target\": [\"input\"]}}}, \"widget\": {\"5\": {\"position\": {\"y\": 44, \"x\": 84}, \"endpoints\": {\"source\": [\"outputendpoint\"], \"target\": [\"inputendpoint\"]}}, \"6\": {\"position\": {\"y\": 153, \"x\": 256}, \"endpoints\": {\"source\": [\"outputendpoint\"], \"target\": [\"inputendpoint\"]}}}}}}", + "wiringStatus": "{\"connections\": [{\"source\": {\"endpoint\": \"outputendpoint\", \"type\": \"widget\", \"id\": 5}, \"readonly\": false, \"target\": {\"endpoint\": \"inputendpoint\", \"type\": \"widget\", \"id\": 6}}, {\"source\": {\"endpoint\": \"outputendpoint\", \"type\": \"widget\", \"id\": 6}, \"readonly\": false, \"target\": {\"endpoint\": \"input\", \"type\": \"operator\", \"id\": 0}}, {\"source\": {\"endpoint\": \"output\", \"type\": \"operator\", \"id\": 0}, \"readonly\": false, \"target\": {\"endpoint\": \"inputendpoint\", \"type\": \"widget\", \"id\": 5}}], \"operators\": {\"0\": {\"preferences\": {}, \"name\": \"Wirecloud/TestOperator/1.0\", \"properties\": {}, \"id\": \"0\"}}, \"version\": \"2.0\", \"visualdescription\": {\"connections\": [{\"sourcename\": \"widget/5/outputendpoint\", \"targethandle\": {\"y\": 0, \"x\": -150}, \"targetname\": \"widget/6/inputendpoint\", \"sourcehandle\": {\"y\": 0, \"x\": 150}}, {\"sourcename\": \"widget/6/outputendpoint\", \"targethandle\": {\"y\": 0, \"x\": -150}, \"targetname\": \"operator/0/input\", \"sourcehandle\": {\"y\": 0, \"x\": 150}}, {\"sourcename\": \"operator/0/output\", \"targethandle\": {\"y\": 0, \"x\": -150}, \"targetname\": \"widget/5/inputendpoint\", \"sourcehandle\": {\"y\": 0, \"x\": 150}}], \"behaviours\": [], \"components\": {\"operator\": {\"0\": {\"position\": {\"y\": 296, \"x\": 84}, \"collapsed\": false, \"endpoints\": {\"source\": [\"output\"], \"target\": [\"input\"]}}}, \"widget\": {\"5\": {\"position\": {\"y\": 44, \"x\": 84}, \"endpoints\": {\"source\": [\"outputendpoint\"], \"target\": [\"inputendpoint\"]}}, \"6\": {\"position\": {\"y\": 153, \"x\": 256}, \"endpoints\": {\"source\": [\"outputendpoint\"], \"target\": [\"inputendpoint\"]}}}}}}", "name": "Public Workspace", "public": true, "groups": [], @@ -275,7 +275,7 @@ "model": "platform.workspace", "fields": { "forcedValues": "", - "wiringStatus": "{\"connections\": [{\"source\": {\"type\": \"widget\", \"id\": 10, \"endpoint\": \"outputendpoint\"}, \"readonly\": false, \"target\": {\"type\": \"widget\", \"id\": 11, \"endpoint\": \"inputendpoint\"}}, {\"source\": {\"type\": \"widget\", \"id\": 11, \"endpoint\": \"outputendpoint\"}, \"readonly\": false, \"target\": {\"type\": \"operator\", \"id\": 0, \"endpoint\": \"input\"}}, {\"source\": {\"type\": \"operator\", \"id\": 0, \"endpoint\": \"output\"}, \"readonly\": false, \"target\": {\"type\": \"widget\", \"id\": 10, \"endpoint\": \"inputendpoint\"}}], \"operators\": {\"0\": {\"preferences\": {}, \"name\": \"Wirecloud/TestOperator/1.0\", \"id\": \"0\"}}, \"version\": \"2.0\", \"visualdescription\": {\"connections\": [{\"sourcename\": \"widget/10/outputendpoint\", \"targethandle\": \"auto\", \"targetname\": \"widget/11/inputendpoint\", \"sourcehandle\": \"auto\"}, {\"sourcename\": \"widget/11/outputendpoint\", \"targethandle\": \"auto\", \"targetname\": \"operator/0/input\", \"sourcehandle\": \"auto\"}, {\"sourcename\": \"operator/0/output\", \"targethandle\": \"auto\", \"targetname\": \"widget/10/inputendpoint\", \"sourcehandle\": \"auto\"}], \"behaviours\": [{\"title\": \"Test 1\", \"components\": {\"operator\": {\"0\": {}}, \"widget\": {\"11\": {}}}, \"connections\": [{\"sourcename\": \"widget/11/outputendpoint\", \"targetname\": \"operator/0/input\"}]}, {\"title\": \"Test 2\", \"components\": {\"operator\": {\"0\": {}}, \"widget\": {\"10\": {}, \"11\": {}}}, \"connections\": [{\"sourcename\": \"widget/11/outputendpoint\", \"targetname\": \"operator/0/input\"}, {\"sourcename\": \"operator/0/output\", \"targetname\": \"widget/10/inputendpoint\"}, {\"sourcename\": \"widget/10/outputendpoint\", \"targetname\": \"widget/11/inputendpoint\"}]}], \"components\": {\"operator\": {\"0\": {\"position\": {\"y\": 20, \"x\": 440}, \"collapsed\": false, \"endpoints\": {\"source\": [\"output\"], \"target\": [\"input\"]}}}, \"widget\": {\"10\": {\"position\": {\"y\": 280, \"x\": 260}, \"name\": \"Wirecloud/Test/1.0\", \"endpoints\": {\"source\": [\"outputendpoint\"], \"target\": [\"inputendpoint\"]}}, \"11\": {\"position\": {\"y\": 10, \"x\": 60}, \"name\": \"Wirecloud/Test/1.0\", \"endpoints\": {\"source\": [\"outputendpoint\"], \"target\": [\"inputendpoint\"]}}}}}}", + "wiringStatus": "{\"connections\": [{\"source\": {\"type\": \"widget\", \"id\": 10, \"endpoint\": \"outputendpoint\"}, \"readonly\": false, \"target\": {\"type\": \"widget\", \"id\": 11, \"endpoint\": \"inputendpoint\"}}, {\"source\": {\"type\": \"widget\", \"id\": 11, \"endpoint\": \"outputendpoint\"}, \"readonly\": false, \"target\": {\"type\": \"operator\", \"id\": 0, \"endpoint\": \"input\"}}, {\"source\": {\"type\": \"operator\", \"id\": 0, \"endpoint\": \"output\"}, \"readonly\": false, \"target\": {\"type\": \"widget\", \"id\": 10, \"endpoint\": \"inputendpoint\"}}], \"operators\": {\"0\": {\"preferences\": {}, \"name\": \"Wirecloud/TestOperator/1.0\", \"properties\": {}, \"id\": \"0\"}}, \"version\": \"2.0\", \"visualdescription\": {\"connections\": [{\"sourcename\": \"widget/10/outputendpoint\", \"targethandle\": \"auto\", \"targetname\": \"widget/11/inputendpoint\", \"sourcehandle\": \"auto\"}, {\"sourcename\": \"widget/11/outputendpoint\", \"targethandle\": \"auto\", \"targetname\": \"operator/0/input\", \"sourcehandle\": \"auto\"}, {\"sourcename\": \"operator/0/output\", \"targethandle\": \"auto\", \"targetname\": \"widget/10/inputendpoint\", \"sourcehandle\": \"auto\"}], \"behaviours\": [{\"title\": \"Test 1\", \"components\": {\"operator\": {\"0\": {}}, \"widget\": {\"11\": {}}}, \"connections\": [{\"sourcename\": \"widget/11/outputendpoint\", \"targetname\": \"operator/0/input\"}]}, {\"title\": \"Test 2\", \"components\": {\"operator\": {\"0\": {}}, \"widget\": {\"10\": {}, \"11\": {}}}, \"connections\": [{\"sourcename\": \"widget/11/outputendpoint\", \"targetname\": \"operator/0/input\"}, {\"sourcename\": \"operator/0/output\", \"targetname\": \"widget/10/inputendpoint\"}, {\"sourcename\": \"widget/10/outputendpoint\", \"targetname\": \"widget/11/inputendpoint\"}]}], \"components\": {\"operator\": {\"0\": {\"position\": {\"y\": 20, \"x\": 440}, \"collapsed\": false, \"endpoints\": {\"source\": [\"output\"], \"target\": [\"input\"]}}}, \"widget\": {\"10\": {\"position\": {\"y\": 280, \"x\": 260}, \"name\": \"Wirecloud/Test/1.0\", \"properties\": {}, \"endpoints\": {\"source\": [\"outputendpoint\"], \"target\": [\"inputendpoint\"]}}, \"11\": {\"position\": {\"y\": 10, \"x\": 60}, \"name\": \"Wirecloud/Test/1.0\", \"properties\": {}, \"endpoints\": {\"source\": [\"outputendpoint\"], \"target\": [\"inputendpoint\"]}}}}}}", "name": "WorkspaceBehaviours", "groups": [], "creator": 4 @@ -288,7 +288,7 @@ "name": "workspaceSecure", "public": false, "creator": "4", - "wiringStatus": "{\"operators\": {\"1\": {\"name\": \"Wirecloud/TestOperatorSecure/1.0\", \"preferences\": {\"pref_secure\": {\"secure\": true, \"readonly\": false, \"hidden\": false, \"value\": {\"users\": {\"4\": \"\"}}}, \"username\": {\"secure\": false, \"readonly\": true, \"hidden\": false, \"value\": {\"users\": {\"4\": \"username\"}}}}}, \"2\": {\"name\": \"Wirecloud/TestOperatorSecure/1.0\", \"preferences\": {\"pref_secure\": {\"secure\": true, \"readonly\": false, \"hidden\": false, \"value\": {\"users\": {\"4\": \"test_password\"}}}, \"username\": {\"secure\": false, \"readonly\": true, \"hidden\": false, \"value\": {\"users\": {\"4\": \"test_username\"}}}}}}, \"connections\": []}" + "wiringStatus": "{\"operators\": {\"1\": {\"name\": \"Wirecloud/TestOperatorSecure/1.0\", \"properties\": {}, \"preferences\": {\"pref_secure\": {\"secure\": true, \"readonly\": false, \"hidden\": false, \"value\": {\"users\": {\"4\": \"\"}}}, \"username\": {\"secure\": false, \"readonly\": true, \"hidden\": false, \"value\": {\"users\": {\"4\": \"username\"}}}}}, \"2\": {\"name\": \"Wirecloud/TestOperatorSecure/1.0\", \"properties\": {}, \"preferences\": {\"pref_secure\": {\"secure\": true, \"readonly\": false, \"hidden\": false, \"value\": {\"users\": {\"4\": \"test_password\"}}}, \"username\": {\"secure\": false, \"readonly\": true, \"hidden\": false, \"value\": {\"users\": {\"4\": \"test_username\"}}}}}}, \"connections\": []}" } }, { diff --git a/src/wirecloud/platform/fixtures/test_data.json b/src/wirecloud/platform/fixtures/test_data.json index 621f203bd3..c1354b1c3c 100644 --- a/src/wirecloud/platform/fixtures/test_data.json +++ b/src/wirecloud/platform/fixtures/test_data.json @@ -60,7 +60,7 @@ "name": "workspace", "public": true, "creator": "2", - "wiringStatus": "{\"operators\": {\"1\": {\"name\": \"Wirecloud/TestOperator/1.0\", \"preferences\": {\"pref_with_val\": {\"readonly\": false, \"hidden\": false, \"value\": {\"users\": {\"2\": \"value1\" }}}, \"readonly_pref\": {\"readonly\": false, \"hidden\": false, \"value\": {\"users\": {\"2\": \"value2\"}}}, \"hidden_pref\": {\"readonly\": false, \"hidden\": false, \"value\": {\"users\": {\"2\": \"value3\"}}}, \"empty_pref\": {\"readonly\": false, \"hidden\": false, \"value\": {\"users\": {\"2\": \"value4\"}}}}}, \"2\": {\"name\": \"Wirecloud/TestOperator/1.0\", \"preferences\": {\"pref_with_val\": {\"readonly\": false, \"hidden\": false, \"value\": {\"users\": {\"2\": \"value1\"}}}, \"readonly_pref\": {\"readonly\": false, \"hidden\": false, \"value\": {\"users\": {\"2\": \"value2\"}}}, \"hidden_pref\": {\"readonly\": false, \"hidden\": false, \"value\": {\"users\": {\"2\": \"value3\"}}}, \"empty_pref\": {\"readonly\": false, \"hidden\": false, \"value\": {\"users\": {\"2\": \"\"}}}}}}, \"connections\": []}" + "wiringStatus": "{\"operators\": {\"1\": {\"name\": \"Wirecloud/TestOperator/1.0\", \"properties\": {}, \"preferences\": {\"pref_with_val\": {\"readonly\": false, \"hidden\": false, \"value\": {\"users\": {\"2\": \"value1\" }}}, \"readonly_pref\": {\"readonly\": false, \"hidden\": false, \"value\": {\"users\": {\"2\": \"value2\"}}}, \"hidden_pref\": {\"readonly\": false, \"hidden\": false, \"value\": {\"users\": {\"2\": \"value3\"}}}, \"empty_pref\": {\"readonly\": false, \"hidden\": false, \"value\": {\"users\": {\"2\": \"value4\"}}}}}, \"2\": {\"name\": \"Wirecloud/TestOperator/1.0\", \"properties\": {}, \"preferences\": {\"pref_with_val\": {\"readonly\": false, \"hidden\": false, \"value\": {\"users\": {\"2\": \"value1\"}}}, \"readonly_pref\": {\"readonly\": false, \"hidden\": false, \"value\": {\"users\": {\"2\": \"value2\"}}}, \"hidden_pref\": {\"readonly\": false, \"hidden\": false, \"value\": {\"users\": {\"2\": \"value3\"}}}, \"empty_pref\": {\"readonly\": false, \"hidden\": false, \"value\": {\"users\": {\"2\": \"\"}}}}}}, \"connections\": []}" } }, { @@ -80,7 +80,7 @@ "name": "workspaceSecure", "public": false, "creator": "2", - "wiringStatus": "{\"operators\": {\"1\": {\"name\": \"Wirecloud/TestOperatorSecure/1.0\", \"preferences\": {\"pref_secure\": {\"secure\": true, \"readonly\": false, \"hidden\": false, \"value\": {\"users\": {\"2\": \"\"}}}, \"username\": {\"secure\": false, \"readonly\": false, \"hidden\": false, \"value\": {\"users\": {\"2\": \"username\"}}}}}, \"2\": {\"name\": \"Wirecloud/TestOperatorSecure/1.0\", \"preferences\": {\"pref_secure\": {\"secure\": true, \"readonly\": false, \"hidden\": false, \"value\": {\"users\": {\"2\": \"test_password\"}}}, \"username\": {\"secure\": false, \"readonly\": false, \"hidden\": false, \"value\": {\"users\": {\"2\": \"test_username\"}}}}}}, \"connections\": []}" + "wiringStatus": "{\"operators\": {\"1\": {\"name\": \"Wirecloud/TestOperatorSecure/1.0\", \"properties\": {}, \"preferences\": {\"pref_secure\": {\"secure\": true, \"readonly\": false, \"hidden\": false, \"value\": {\"users\": {\"2\": \"\"}}}, \"username\": {\"secure\": false, \"readonly\": false, \"hidden\": false, \"value\": {\"users\": {\"2\": \"username\"}}}}}, \"2\": {\"name\": \"Wirecloud/TestOperatorSecure/1.0\", \"properties\": {}, \"preferences\": {\"pref_secure\": {\"secure\": true, \"readonly\": false, \"hidden\": false, \"value\": {\"users\": {\"2\": \"test_password\"}}}, \"username\": {\"secure\": false, \"readonly\": false, \"hidden\": false, \"value\": {\"users\": {\"2\": \"test_username\"}}}}}}, \"connections\": []}" } }, { diff --git a/src/wirecloud/platform/static/js/WirecloudAPI/WirecloudOperatorAPI.js b/src/wirecloud/platform/static/js/WirecloudAPI/WirecloudOperatorAPI.js index 18c7ef3a8a..90b5b4ea6d 100644 --- a/src/wirecloud/platform/static/js/WirecloudAPI/WirecloudOperatorAPI.js +++ b/src/wirecloud/platform/static/js/WirecloudAPI/WirecloudOperatorAPI.js @@ -26,10 +26,22 @@ "use strict"; - var platform, ioperator, endpoint_name, inputs, outputs; + var platform, ioperator, endpoint_name, inputs, outputs, IOperatorVariable; platform = window.parent; + IOperatorVariable = function IOperatorVariable(variable) { + this.set = function set(value) { + variable.set(value); + }; + + this.get = function get() { + return variable.get(); + }; + Object.freeze(this); + }; + + // Init resource entry (in this case an operator) so other API files can make // use of it ioperator = platform.Wirecloud.UserInterfaceManager.views.workspace.model.findOperator(MashupPlatform.priv.id); @@ -44,6 +56,15 @@ } }); + Object.defineProperty(window.MashupPlatform.operator, 'getVariable', { + value: function getVariable(name) { + var variable = ioperator.properties[name]; + if (variable != null) { + return new IOperatorVariable(variable); + } + } + }); + // Inputs inputs = {}; for (endpoint_name in ioperator.inputs) { diff --git a/src/wirecloud/platform/static/js/wirecloud/MashableApplicationComponent.js b/src/wirecloud/platform/static/js/wirecloud/MashableApplicationComponent.js index b7d35d75ca..55dea9d773 100644 --- a/src/wirecloud/platform/static/js/wirecloud/MashableApplicationComponent.js +++ b/src/wirecloud/platform/static/js/wirecloud/MashableApplicationComponent.js @@ -27,7 +27,7 @@ "use strict"; ns.MashableApplicationComponent = function MashableApplicationComponent(desc) { - var vendor, name, version, i, inputs, outputs, preference; + var vendor, name, version, i, inputs, outputs, preference, property; // Vendor if (!('vendor' in desc) || desc.vendor.trim() === '') { @@ -95,6 +95,19 @@ Object.freeze(this.preferences); Object.freeze(this.preferenceList); + // PropertyList + if (desc.type === "operator") { + this.properties = {}; + this.propertyList = []; + for (i = 0; i < desc.properties.length; i++) { + property = new Wirecloud.UserPrefDef(desc.properties[i].name, desc.properties[i].type, desc.properties[i]); + this.properties[property.name] = property; + this.propertyList.push(property); + } + Object.freeze(this.properties); + Object.freeze(this.propertyList); + } + // Requirements this.requirements = desc.requirements; Object.freeze(this.requirements); diff --git a/src/wirecloud/platform/static/js/wirecloud/PropertyCommiter.js b/src/wirecloud/platform/static/js/wirecloud/PropertyCommiter.js index 875e87f437..c13847e7b7 100644 --- a/src/wirecloud/platform/static/js/wirecloud/PropertyCommiter.js +++ b/src/wirecloud/platform/static/js/wirecloud/PropertyCommiter.js @@ -26,8 +26,8 @@ "use strict"; - var PropertyCommiter = function PropertyCommiter(iwidget) { - Object.defineProperty(this, 'iwidget', {value: iwidget}); + var PropertyCommiter = function PropertyCommiter(component) { + Object.defineProperty(this, 'component', {value: component}); this.values = {}; this.pending_values = null; this.timeout = null; @@ -58,10 +58,19 @@ this.timeout = null; this.pending_values = {}; + if (this.component.type === "widget") { + commitWidgetProperties.call(this) + } else { + commitOperatorProperties.call(this) + } + + }; + + var commitWidgetProperties = function commitWidgetProperties() { var url = Wirecloud.URLs.IWIDGET_PROPERTIES.evaluate({ - workspace_id: this.iwidget.tab.workspace.id, - tab_id: this.iwidget.tab.id, - iwidget_id: this.iwidget.id + workspace_id: this.component.tab.workspace.id, + tab_id: this.component.tab.id, + iwidget_id: this.component.id }); Wirecloud.io.makeRequest(url, { contentType: 'application/json', @@ -78,7 +87,41 @@ this.timeout = setTimeout(this.commit.bind(this), 30000); }.bind(this) }); - }; + } + + var commitOperatorProperties = function commitOperatorProperties() { + var url = (Wirecloud.URLs.WIRING_ENTRY.evaluate({ + workspace_id: this.component.wiring.workspace.id, + })); + + // Build patch request + var requestBody = []; + for (var key in this.values) { + var property = this.values[key]; + requestBody.push({ + op: "replace", + path: "/operators/" + this.component.id + "/properties/" + key + "/value", + value: property + }); + } + + Wirecloud.io.makeRequest(url, { + method: 'PATCH', + contentType: 'application/json-patch+json', + postBody: JSON.stringify(requestBody), + onSuccess: function () { + this.values = this.pending_values; + if (Object.keys(this.values).length > 0) { + this.timeout = setTimeout(this.commit.bind(this), 1000); + } + this.pending_values = null; + }.bind(this), + onFailure: function () { + this.values = Wirecloud.Utils.merge(this.values, this.pending_values); + this.timeout = setTimeout(this.commit.bind(this), 30000); + }.bind(this) + }); + } Wirecloud.PropertyCommiter = PropertyCommiter; diff --git a/src/wirecloud/platform/static/js/wirecloud/wiring/Operator.js b/src/wirecloud/platform/static/js/wirecloud/wiring/Operator.js index 94689173eb..86c4220e04 100644 --- a/src/wirecloud/platform/static/js/wirecloud/wiring/Operator.js +++ b/src/wirecloud/platform/static/js/wirecloud/wiring/Operator.js @@ -156,6 +156,7 @@ build_endpoints.call(this); build_prefs.call(this, data.preferences); + build_props.call(this, data.properties); this.logManager.log(utils.gettext("Operator created successfully."), Wirecloud.constants.LOGGING.DEBUG_MSG); }; @@ -279,7 +280,7 @@ * @returns {Object} */ toJSON: function toJSON() { - var name, preferences = {}; + var name, preferences = {}, properties = {}; for (name in this.preferences) { preferences[name] = { @@ -289,10 +290,19 @@ }; } + for (name in this.properties) { + properties[name] = { + hidden: this.properties[name].hidden, + readonly: this.properties[name].readonly, + value: this.properties[name].value + }; + } + return { id: this.id, name: this.meta.uri, - preferences: preferences + preferences: preferences, + properties: properties }; }, @@ -383,6 +393,29 @@ }, this); }; + var build_props = function build_props(initial_values) { + var i, properties, prop_info; + + properties = this.meta.propertyList; + this.propertyList = []; + this.properties = {}; + this.propertyCommiter = new Wirecloud.PropertyCommiter(this); + for (i = 0; i < properties.length; i++) { + if (initial_values != null) { + prop_info = initial_values[properties[i].name]; + } else { + prop_info = null + } + + if (prop_info != null) { + this.propertyList[i] = new Wirecloud.PersistentVariable(properties[i], this.propertyCommiter, prop_info.readonly, prop_info.value); + } else { + this.propertyList[i] = new Wirecloud.PersistentVariable(properties[i], this.propertyCommiter, false, properties[i].default); + } + this.properties[properties[i].name] = this.propertyList[i]; + } + } + var send_pending_event = function send_pending_event(pendingEvent) { this.inputs[pendingEvent.endpoint].propagate(pendingEvent.value); }; diff --git a/src/wirecloud/platform/tests/rest_api.py b/src/wirecloud/platform/tests/rest_api.py index 49c723f883..8fadb54ea5 100644 --- a/src/wirecloud/platform/tests/rest_api.py +++ b/src/wirecloud/platform/tests/rest_api.py @@ -1054,7 +1054,7 @@ def test_workspace_wiring_entry_put(self): url = reverse('wirecloud.workspace_wiring', kwargs={'workspace_id': 1}) new_wiring_status = { - 'operators': {'0': {'name': 'Wirecloud/TestOperator/1.0', 'preferences': {}}}, + 'operators': {'0': {'name': 'Wirecloud/TestOperator/1.0', 'preferences': {}, 'properties': {}}}, 'connections': [], } @@ -1099,7 +1099,7 @@ def test_workspace_wiring_entry_patch_requires_authentication(self): data = json.dumps([{ 'op': "add", 'path': "/operators/0", - 'value': {'operators': {'0': {'name': 'Wirecloud/TestOperator/1.0', 'preferences': {}}}}, + 'value': {'operators': {'0': {'name': 'Wirecloud/TestOperator/1.0', 'preferences': {}, 'properties': {}}}}, }]) response = self.client.patch(url, data, content_type='application/json-patch+json; charset=UTF-8', HTTP_ACCEPT='application/json') @@ -1127,7 +1127,7 @@ def test_workspace_wiring_entry_patch(self): url = reverse('wirecloud.workspace_wiring', kwargs={'workspace_id': 1}) new_wiring_status = { - 'operators': {'0': {'name': 'Wirecloud/TestOperator/1.0', 'preferences': {}}}, + 'operators': {'0': {'name': 'Wirecloud/TestOperator/1.0', 'preferences': {}, 'properties': {}}}, 'connections': [], } diff --git a/src/wirecloud/platform/wiring/tests.py b/src/wirecloud/platform/wiring/tests.py index 80a948d432..661d8c3534 100644 --- a/src/wirecloud/platform/wiring/tests.py +++ b/src/wirecloud/platform/wiring/tests.py @@ -262,7 +262,8 @@ def test_read_only_preferences_cannot_be_modified(self): 'name': 'Wirecloud/TestOperator/1.0', 'preferences': { 'pref1': {'hidden': False, 'readonly': True, 'value': {"users": {"2": 'a'}}}, - } + }, + 'properties': {} }, }, 'connections': [], @@ -279,7 +280,8 @@ def test_read_only_preferences_cannot_be_modified(self): 'name': 'Wirecloud/TestOperator/1.0', 'preferences': { 'pref1': {'readonly': True, 'value': {"users": {"2": 'b'}}}, - } + }, + 'properties': {} }, }, 'connections': [], @@ -297,7 +299,8 @@ def test_normal_preferences_can_be_removed(self): 'name': 'Wirecloud/TestOperator/1.0', 'preferences': { 'pref1': {'hidden': False, 'readonly': False, 'value': {"users": {"2": 'a'}}}, - } + }, + 'properties': {} }, }, 'connections': [], @@ -312,7 +315,8 @@ def test_normal_preferences_can_be_removed(self): '1': { 'id': '1', 'name': 'Wirecloud/TestOperator/1.0', - 'preferences': {} + 'preferences': {}, + 'properties': {} }, }, 'connections': [], @@ -330,7 +334,8 @@ def test_read_only_preferences_cannot_be_removed(self): 'name': 'Wirecloud/TestOperator/1.0', 'preferences': { 'pref1': {'hidden': False, 'readonly': True, 'value': {"users": {"2": 'a'}}}, - } + }, + 'properties': {} }, }, 'connections': [], @@ -345,7 +350,8 @@ def test_read_only_preferences_cannot_be_removed(self): '1': { 'id': '1', 'name': 'Wirecloud/TestOperator/1.0', - 'preferences': {} + 'preferences': {}, + 'properties': {} }, }, 'connections': [], @@ -361,7 +367,8 @@ def test_read_only_preferences_cannot_be_added(self): '1': { 'id': '1', 'name': 'Wirecloud/TestOperator/1.0', - 'preferences': {} + 'preferences': {}, + 'properties': {} }, }, 'connections': [], @@ -378,7 +385,8 @@ def test_read_only_preferences_cannot_be_added(self): 'name': 'Wirecloud/TestOperator/1.0', 'preferences': { 'pref1': {'readonly': True}, - } + }, + 'properties': {} }, }, 'connections': [], @@ -394,7 +402,8 @@ def test_hidden_preferences_cannot_be_added(self): '1': { 'id': '1', 'name': 'Wirecloud/TestOperator/1.0', - 'preferences': {} + 'preferences': {}, + 'properties': {} }, }, 'connections': [], @@ -411,7 +420,8 @@ def test_hidden_preferences_cannot_be_added(self): 'name': 'Wirecloud/TestOperator/1.0', 'preferences': { 'pref1': {'hidden': True}, - } + }, + 'properties': {} }, }, 'connections': [], @@ -429,7 +439,8 @@ def test_hidden_status_cannot_be_removed(self): 'name': 'Wirecloud/TestOperator/1.0', 'preferences': { 'pref1': {'hidden': True, 'readonly': False, 'value': {"users": {"2": 'a'}}}, - } + }, + 'properties': {} }, }, 'connections': [], @@ -446,7 +457,8 @@ def test_hidden_status_cannot_be_removed(self): 'name': 'Wirecloud/TestOperator/1.0', 'preferences': { 'pref1': {'hidden': False, 'readonly': False, 'value': {"users": {"2": 'a'}}}, - } + }, + 'properties': {} }, }, 'connections': [], @@ -462,7 +474,8 @@ def test_normal_preferences_can_be_added(self): '1': { 'id': '1', 'name': 'Wirecloud/TestOperator/1.0', - 'preferences': {} + 'preferences': {}, + 'properties': {} }, }, 'connections': [], @@ -481,6 +494,7 @@ def test_normal_preferences_can_be_added(self): 'pref1': {'value': 'a'}, 'pref2': {'hidden': False, 'readonly': False, 'value': {"users": {"2": 'a'}}}, }, + 'properties': {} }, }, 'connections': [], @@ -500,6 +514,7 @@ def test_normal_preferences_can_be_updated(self): 'pref1': {'hidden': True, 'readonly': False, 'value': {"users": {"2": 'a'}}}, 'pref2': {'hidden': False, 'readonly': False, 'value': {"users": {"2": 'b'}}}, }, + 'properties': {} }, }, 'connections': [], @@ -517,7 +532,8 @@ def test_normal_preferences_can_be_updated(self): 'preferences': { 'pref1': {'hidden': True, 'readonly': False, 'value': {"users": {"2": 'b'}}}, 'pref2': {'hidden': False, 'readonly': False, 'value': {"users": {"2": 'c'}}}, - } + }, + 'properties': {} }, }, 'connections': [], @@ -538,7 +554,8 @@ def test_operator_added_put(self): 'preferences': { 'pref1': {'hidden': False, 'readonly': False, 'value': {"users": {"2": 'b'}}}, 'pref2': {'hidden': False, 'readonly': False, 'value': {"users": {"2": 'c'}}}, - } + }, + 'properties': {} }, }, 'connections': [], @@ -561,6 +578,7 @@ def test_operator_added_patch(self): 'pref1': {'hidden': False, 'readonly': False, 'value': {"users": {"2": 'b'}}}, 'pref2': {'hidden': False, 'readonly': False, 'value': {"users": {"2": 'c'}}}, }, + 'properties': {} }, } ]) diff --git a/src/wirecloud/platform/wiring/views.py b/src/wirecloud/platform/wiring/views.py index 9bcf250a71..77c167209f 100644 --- a/src/wirecloud/platform/wiring/views.py +++ b/src/wirecloud/platform/wiring/views.py @@ -35,16 +35,16 @@ class WiringEntry(Resource): - def handleMultiuser(self, request, new_preference, old_preference): - new_value = new_preference["value"] + def handleMultiuser(self, request, new_variable, old_variable): + new_value = new_variable["value"] - if old_preference is not None: - new_preference = old_preference + if old_variable is not None: + new_variable = old_variable else: - new_preference["value"] = {"users": {}} + new_variable["value"] = {"users": {}} - new_preference["value"]["users"]["%s" % request.user.id] = new_value - return new_preference + new_variable["value"]["users"]["%s" % request.user.id] = new_value + return new_variable def checkMultiuserWiring(self, request, new_wiring_status, old_wiring_status, can_update_secure=False): if new_wiring_status["connections"] != old_wiring_status["connections"]: @@ -63,7 +63,12 @@ def checkMultiuserWiring(self, request, new_wiring_status, old_wiring_status, ca operator_preferences = CatalogueResource.objects.get(vendor=vendor, short_name=name, version=version).get_processed_info()["preferences"] except CatalogueResource.DoesNotExist: operator_preferences = None + try: + operator_properties = CatalogueResource.objects.get(vendor=vendor, short_name=name, version=version).get_processed_info()["preferences"] + except CatalogueResource.DoesNotExist: + operator_properties = None + # Check preferences for preference_name in operator['preferences']: old_preference = old_operator['preferences'][preference_name] new_preference = operator['preferences'][preference_name] @@ -97,6 +102,40 @@ def checkMultiuserWiring(self, request, new_wiring_status, old_wiring_status, ca new_preference = self.handleMultiuser(request, new_preference, old_preference) operator['preferences'][preference_name] = new_preference + # Check properties + for property_name in operator['property']: + old_property = old_operator['property'][property_name] + new_property = operator['property'][property_name] + + if old_property["hidden"] != new_property["hidden"] or old_property["readonly"] != new_property["readonly"]: + return build_error_response(request, 403, _('You are not allowed to update this workspace')) + + # Check if its multiuser + property_secure = False + property_multiuser = False + if operator_properties: + for pref in operator_properties: + if pref["name"] == property_name: + property_secure = pref.get("secure", False) + property_multiuser = pref.get("multiuser", False) + operator_properties.remove(pref) # Speed up search for next properties + break + + # Variables can only be updated if multisuer + if not property_multiuser: + if old_property["value"] != new_property["value"]: + return build_error_response(request, 403, _('You are not allowed to update this workspace')) + else: + continue + + # Update variable value + if property_secure and not can_update_secure: + new_property["value"] = old_property["value"] + elif new_property["value"] != old_property["value"]: + # Handle multiuser + new_property = self.handleMultiuser(request, new_property, old_property) + operator['properties'][property_name] = new_property + return True def checkWiring(self, request, new_wiring_status, old_wiring_status, can_update_secure=False): @@ -110,7 +149,7 @@ def checkWiring(self, request, new_wiring_status, old_wiring_status, can_update_ for connection in old_read_only_connections: if connection not in new_read_only_connections: return build_error_response(request, 403, _('You are not allowed to remove or update read only connections')) - # Check operator preferences + # Check operator preferences and properties for operator_id, operator in six.iteritems(new_wiring_status['operators']): old_operator = None if operator_id in old_wiring_status['operators']: @@ -118,18 +157,29 @@ def checkWiring(self, request, new_wiring_status, old_wiring_status, can_update_ added_preferences = set(operator['preferences'].keys()) - set(old_operator['preferences'].keys()) removed_preferences = set(old_operator['preferences'].keys()) - set(operator['preferences'].keys()) updated_preferences = set(operator['preferences'].keys()).intersection(old_operator['preferences'].keys()) + + added_properties = set(operator['properties'].keys()) - set(old_operator['properties'].keys()) + removed_properties = set(old_operator['properties'].keys()) - set(operator['properties'].keys()) + updated_properties = set(operator['properties'].keys()).intersection(old_operator['properties'].keys()) vendor, name, version = operator["name"].split("/") try: operator_preferences = CatalogueResource.objects.get(vendor=vendor, short_name=name, version=version).get_processed_info()["preferences"] except CatalogueResource.DoesNotExist: operator_preferences = None - pass + try: + operator_properties = CatalogueResource.objects.get(vendor=vendor, short_name=name, version=version).get_processed_info()["preferences"] + except CatalogueResource.DoesNotExist: + operator_properties = None else: # New operator added_preferences = operator['preferences'].keys() removed_preferences = () updated_preferences = () + added_properties = operator['properties'].keys() + removed_properties = () + updated_properties = () + # Handle preferences for preference_name in added_preferences: if operator['preferences'][preference_name].get('readonly', False) or operator['preferences'][preference_name].get('hidden', False): return build_error_response(request, 403, _('Read only and hidden preferences cannot be created using this API')) @@ -169,6 +219,46 @@ def checkWiring(self, request, new_wiring_status, old_wiring_status, can_update_ new_preference = self.handleMultiuser(request, new_preference, old_preference) operator['preferences'][preference_name] = new_preference + # Handle properties + for property_name in added_properties: + if operator['properties'][property_name].get('readonly', False) or operator['properties'][property_name].get('hidden', False): + return build_error_response(request, 403, _('Read only and hidden properties cannot be created using this API')) + + # Handle multiuser + new_property = operator['properties'][property_name] + new_property["value"] = {"users": {"%s" % request.user.id: new_property["value"]}} + operator['properties'][property_name] = new_property + + for property_name in removed_properties: + if old_operator['properties'][property_name].get('readonly', False) or old_operator['properties'][property_name].get('hidden', False): + return build_error_response(request, 403, _('Read only and hidden properties cannot be removed')) + + for property_name in updated_properties: + old_property = old_operator['properties'][property_name] + new_property = operator['properties'][property_name] + + # Check if the property is secure + property_secure = False + if operator_properties: + for prop in operator_properties: + if prop["name"] == property_name: + property_secure = prop.get("secure", False) + operator_properties.remove(prop) # Speed up search + break + + if old_property.get('readonly', False) != new_property.get('readonly', False) or old_property.get('hidden', False) != new_property.get('hidden', False): + return build_error_response(request, 403, _('Read only and hidden status cannot be changed using this API')) + + if new_property.get('readonly', False) and new_property.get('value') != old_property.get('value'): + return build_error_response(request, 403, _('Read only properties cannot be updated')) + + if property_secure and not can_update_secure: + new_property["value"] = old_property["value"] + elif new_property["value"] != old_property["value"]: + # Handle multiuser + new_property = self.handleMultiuser(request, new_property, old_property) + operator['properties'][property_name] = new_property + return True @authentication_required diff --git a/src/wirecloud/platform/workspace/utils.py b/src/wirecloud/platform/workspace/utils.py index a093b1dd8a..9d19e1194d 100644 --- a/src/wirecloud/platform/workspace/utils.py +++ b/src/wirecloud/platform/workspace/utils.py @@ -493,6 +493,7 @@ def _get_global_workspace_data(workspaceDAO, user): operator_info = resource.get_processed_info(process_variables=True) operator_forced_values = forced_values['ioperator'].get(operator_id, {}) + # Build operator preference data for preference_name, preference in six.iteritems(operator.get('preferences', {})): vardef = operator_info['variables']['preferences'].get(preference_name) value = preference.get('value', None) @@ -512,6 +513,26 @@ def _get_global_workspace_data(workspaceDAO, user): if vardef is not None and vardef["secure"]: preference['value'] = "" if preference.get('value') is None or preference.get('value') == "" else "********" + # Build operator property data + for property_name, property in six.iteritems(operator.get('properties', {})): + vardef = operator_info['variables']['properties'].get(property_name) + value = property.get('value', None) + + # Handle multiuser + variable_user = user if vardef is not None and vardef["multiuser"] else workspaceDAO.creator + + if property_name in operator_forced_values: + property['value'] = operator_forced_values[property_name]['value'] + elif value is None or value["users"].get("%s" % variable_user.id, None) is None: + # If not defined / not defined for the current user, take the default value + property['value'] = parse_value_from_text(vardef, vardef['default']) + else: + property['value'] = value["users"].get("%s" % variable_user.id) + + # Secure censor + if vardef is not None and vardef["secure"]: + property['value'] = "" if property.get('value') is None or property.get('value') == "" else "********" + return json.dumps(data_ret, cls=LazyEncoder) From b017a8f1ac9eeb673631effc982611422e2e831f Mon Sep 17 00:00:00 2001 From: Alejandro Rodriguez Date: Thu, 9 Feb 2017 10:23:07 +0100 Subject: [PATCH 12/28] Fix users with no permissions adding multiuser properties to iwidgets --- src/wirecloud/platform/iwidget/views.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/wirecloud/platform/iwidget/views.py b/src/wirecloud/platform/iwidget/views.py index ed6e763b84..92a65ae080 100644 --- a/src/wirecloud/platform/iwidget/views.py +++ b/src/wirecloud/platform/iwidget/views.py @@ -228,10 +228,15 @@ def create(self, request, workspace_id, tab_id, iwidget_id): # Check if its multiuser if not iwidget_info['variables']['properties'][var_name].get("multiuser", False): - # No multiuser -> Check permisisons + # No multiuser -> Check permissions if workspace.creator != request.user: msg = _('You have not enough permission for updating the persistent variables of this widget') return build_error_response(request, 403, msg) + else: + # Multiuser -> Check permissions + if not workspace.is_available_for(request.user): + msg = _('You have not enough permission for updating the persistent variables of this widget') + return build_error_response(request, 403, msg) iwidget.set_variable_value(var_name, new_values[var_name], request.user) From 48c1c815ff29ed0a64f60066fc643d083a0f988f Mon Sep 17 00:00:00 2001 From: Alejandro Rodriguez Date: Thu, 9 Feb 2017 10:57:50 +0100 Subject: [PATCH 13/28] Add migration tests multiuser structure --- src/wirecloud/platform/migration_utils.py | 5 +- .../0005_add_multiuser_variable_support.py | 4 +- src/wirecloud/platform/workspace/tests.py | 97 +++++++++++++++++-- 3 files changed, 93 insertions(+), 13 deletions(-) diff --git a/src/wirecloud/platform/migration_utils.py b/src/wirecloud/platform/migration_utils.py index 26c8c6486e..45362fe03c 100644 --- a/src/wirecloud/platform/migration_utils.py +++ b/src/wirecloud/platform/migration_utils.py @@ -43,7 +43,7 @@ def mutate_backwards_widget(preference, userID): return preference -def update_variables_structure(apps, schema_editor): +def multiuser_variables_structure_forwards(apps, schema_editor): Workspace = apps.get_model("platform", "workspace") @@ -61,9 +61,10 @@ def update_variables_structure(apps, schema_editor): for widget in tab.iwidget_set.all(): widget.variables = {k: mutate_forwards_widget(v, owner) for k, v in six.iteritems(widget.variables)} widget.save() + pass -def reverse_variables_structure(apps, schema_editor): +def multiuser_variables_structure_backwards(apps, schema_editor): # Check no multiuser widgets CatalogueResource = apps.get_model("catalogue", "CatalogueResource") diff --git a/src/wirecloud/platform/migrations/0005_add_multiuser_variable_support.py b/src/wirecloud/platform/migrations/0005_add_multiuser_variable_support.py index d184cf0c92..c307236e1b 100644 --- a/src/wirecloud/platform/migrations/0005_add_multiuser_variable_support.py +++ b/src/wirecloud/platform/migrations/0005_add_multiuser_variable_support.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals from django.db import migrations -from wirecloud.platform.migration_utils import update_variables_structure, reverse_variables_structure +from wirecloud.platform.migration_utils import multiuser_variables_structure_forwards, multiuser_variables_structure_backwards class Migration(migrations.Migration): @@ -14,5 +14,5 @@ class Migration(migrations.Migration): ] operations = [ - migrations.RunPython(update_variables_structure, reverse_variables_structure), + migrations.RunPython(multiuser_variables_structure_forwards, multiuser_variables_structure_backwards), ] diff --git a/src/wirecloud/platform/workspace/tests.py b/src/wirecloud/platform/workspace/tests.py index 148e8525f3..c0d869ef69 100644 --- a/src/wirecloud/platform/workspace/tests.py +++ b/src/wirecloud/platform/workspace/tests.py @@ -40,7 +40,8 @@ from wirecloud.platform.workspace.searchers import WorkspaceSearcher from wirecloud.platform.workspace.utils import get_global_workspace_data from wirecloud.platform.workspace.views import createEmptyWorkspace -from wirecloud.platform.migration_utils import update_variables_structure +from wirecloud.platform.migration_utils import multiuser_variables_structure_forwards, multiuser_variables_structure_backwards +from django.db.migrations.exceptions import IrreversibleError # Avoid nose to repeat these tests (they are run through wirecloud/tests/__init__.py) @@ -49,7 +50,7 @@ class WorkspaceMigrationsTestCase(TestCase): - tags = ('wirecloud-migrations', 'wirecloud-platform-migrations', 'wirecloud-noselenium', 'current') + tags = ('wirecloud-migrations', 'wirecloud-platform-migrations', 'wirecloud-noselenium') def test_add_multiuser_support_forward_empty(self): @@ -57,30 +58,108 @@ def test_add_multiuser_support_forward_empty(self): apps_mock.get_model("platform", "workspace").objects.select_related('creator').all.return_value = [ ] - update_variables_structure(apps_mock, None) + multiuser_variables_structure_forwards(apps_mock, None) def test_add_multiuser_support_forward(self): widget1_mock = Mock( variables={"varname": "varvalue", "varname2": "varvalue2"} ) + widget2_mock = Mock( + variables={"varname": "hello", "varname2": "world"} + ) + widget3_mock = Mock( + variables={"varname": "varvalue", "varname2": "varvalue2"} + ) tab_mock = Mock() - tab_mock.iwidget_set.all.return_value = [widget1_mock] + tab_mock.iwidget_set.all.return_value = [widget1_mock, widget2_mock] - workspace_mock = Mock() - workspace_mock.tab_set.all.return_value = [ - tab_mock - ] + tab_mock2 = Mock() + tab_mock2.iwidget_set.all.return_value = [widget3_mock] + + workspace_mock = Mock( + wiringStatus={"operators": { + "1": {"preferences": {"varname": {"value": "varvalue"}, "varname2": {"value": "varvalue2"}}}, + "2": {"preferences": {"varname": {"value": "hello"}, "varname2": {"value": "world"}}}} + }) + workspace_mock.tab_set.all.return_value = [tab_mock, tab_mock2] workspace_mock.creator.id = "2" apps_mock = Mock() apps_mock.get_model("platform", "workspace").objects.select_related('creator').all.return_value = [workspace_mock] - update_variables_structure(apps_mock, None) + multiuser_variables_structure_forwards(apps_mock, None) widget1_mock.save.assert_called_with() self.assertEqual(widget1_mock.variables, {"varname": {"users": {"2": "varvalue"}}, "varname2": {"users": {"2": "varvalue2"}}}) + self.assertEqual(widget2_mock.variables, {"varname": {"users": {"2": "hello"}}, "varname2": {"users": {"2": "world"}}}) + self.assertEqual(widget3_mock.variables, {"varname": {"users": {"2": "varvalue"}}, "varname2": {"users": {"2": "varvalue2"}}}) + + self.assertEqual(workspace_mock.wiringStatus, {"operators": + {"1": {"preferences": {"varname": {"value": {"users": {"2": "varvalue"}}}, "varname2": {"value": {"users": {"2": "varvalue2"}}}}}, + "2": {"preferences": {"varname": {"value": {"users": {"2": "hello"}}}, "varname2": {"value": {"users": {"2": "world"}}}}}}}) + + def test_add_multiuser_support_backward_empty(self): + + apps_mock = Mock() + apps_mock.get_model("platform", "workspace").objects.select_related('creator').all.return_value = [] + apps_mock.get_model("catalogue", "CatalogueResource").objects.filter(type__in=(0, 2)).all.return_value = [] + + multiuser_variables_structure_backwards(apps_mock, None) + + def test_add_multiuser_support_backward_multiuser_components(self): + multiuser_resource = Mock( + vendor="testvendor", + short_name="testname", + version="testversion") + multiuser_resource.json_description = {"properties": [{"multiuser": True}]} + + apps_mock = Mock() + apps_mock.get_model("platform", "workspace").objects.select_related('creator').all.return_value = [] + apps_mock.get_model("catalogue", "CatalogueResource").objects.filter(type__in=(0, 2)).all.return_value = [multiuser_resource] + + self.assertRaises(IrreversibleError, multiuser_variables_structure_backwards, apps=apps_mock, schema_editor=None) + + def test_add_multiuser_support_backward(self): + + widget1_mock = Mock( + variables={"varname": {"users": {"2": "varvalue"}}, "varname2": {"users": {"2": "varvalue2"}}} + ) + widget2_mock = Mock( + variables={"varname": {"users": {"2": "hello"}}, "varname2": {"users": {"2": "world"}}} + ) + widget3_mock = Mock( + variables={"varname": {"users": {"2": "varvalue"}}, "varname2": {"users": {"2": "varvalue2"}}} + ) + tab_mock = Mock() + tab_mock.iwidget_set.all.return_value = [widget1_mock, widget2_mock] + + tab_mock2 = Mock() + tab_mock2.iwidget_set.all.return_value = [widget3_mock] + + workspace_mock = Mock( + wiringStatus={"operators": { + "1": {"preferences": {"varname": {"value": {"users": {"2": "varvalue"}}}, "varname2": {"value": {"users": {"2": "varvalue2"}}}}}, + "2": {"preferences": {"varname": {"value": {"users": {"2": "hello"}}}, "varname2": {"value": {"users": {"2": "world"}}}}}} + }) + workspace_mock.tab_set.all.return_value = [tab_mock, tab_mock2] + workspace_mock.creator.id = "2" + + apps_mock = Mock() + apps_mock.get_model("platform", "workspace").objects.select_related('creator').all.return_value = [workspace_mock] + apps_mock.get_model("catalogue", "CatalogueResource").objects.filter(type__in=(0, 2)).all.return_value = [] + + + multiuser_variables_structure_backwards(apps_mock, None) + + widget1_mock.save.assert_called_with() + self.assertEqual(widget1_mock.variables, {"varname": "varvalue", "varname2": "varvalue2"}) + self.assertEqual(widget2_mock.variables, {"varname": "hello", "varname2": "world"}) + self.assertEqual(widget3_mock.variables, {"varname": "varvalue", "varname2": "varvalue2"}) + self.assertEqual(workspace_mock.wiringStatus, {"operators": + {"1": {"preferences": {"varname": {"value": "varvalue"}, "varname2": {"value": "varvalue2"}}}, + "2": {"preferences": {"varname": {"value": "hello"}, "varname2": {"value": "world"}}}}}) class WorkspaceTestCase(WirecloudTestCase): From 1c2dd4e8c42f522eb059192b3139fd7a9bcd89db Mon Sep 17 00:00:00 2001 From: Alejandro Rodriguez Date: Thu, 9 Feb 2017 10:58:17 +0100 Subject: [PATCH 14/28] Improve multiuser description --- src/wirecloud/commons/utils/template/schemas/xml_schema.xsd | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/wirecloud/commons/utils/template/schemas/xml_schema.xsd b/src/wirecloud/commons/utils/template/schemas/xml_schema.xsd index a9327c437a..3b93f804da 100644 --- a/src/wirecloud/commons/utils/template/schemas/xml_schema.xsd +++ b/src/wirecloud/commons/utils/template/schemas/xml_schema.xsd @@ -339,12 +339,10 @@ - + - Text that will be used to reference this - variable in the user interface. This field can be - translated. + If set to true, this variable will store a value for each user with access rights to the dashboard. From ed13f620cb03d52f3bbbbcbe7cea8e10733a2fec Mon Sep 17 00:00:00 2001 From: Alejandro Rodriguez Date: Thu, 9 Feb 2017 13:15:28 +0100 Subject: [PATCH 15/28] Fix tests rest-api iwidget properties post multiuser requires permission --- src/wirecloud/platform/tests/rest_api.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/wirecloud/platform/tests/rest_api.py b/src/wirecloud/platform/tests/rest_api.py index 8fadb54ea5..87aec7aeda 100644 --- a/src/wirecloud/platform/tests/rest_api.py +++ b/src/wirecloud/platform/tests/rest_api.py @@ -2375,7 +2375,7 @@ def test_iwidget_properties_entry_post_requires_permission(self): } check_post_requires_permission(self, url, json.dumps(data)) - def test_iwidget_properties_entry_post_multiuser_doesnt_require_permission(self): + def test_iwidget_properties_entry_post_multiuser_requires_permission(self): self._todo_create_property(True) # Authenticate @@ -2386,10 +2386,7 @@ def test_iwidget_properties_entry_post_multiuser_doesnt_require_permission(self) 'prop': 'new value', } - response = self.client.post(url, json.dumps(data), content_type='application/json; charset=UTF-8', HTTP_ACCEPT='application/json') - self.assertEqual(response.status_code, 204) - variables = IWidget.objects.get(pk=2).variables - self.assertEqual(variables['prop']["users"]["5"], 'new value') + check_post_requires_permission(self, url, json.dumps(data)) def test_iwidget_properties_entry_post(self): From 0d5d51baf7cb6b560055330cd890e16ee7a9b633 Mon Sep 17 00:00:00 2001 From: Alejandro Rodriguez Date: Thu, 9 Feb 2017 13:17:15 +0100 Subject: [PATCH 16/28] Fix wiring post/patch properties typo and add default values --- src/wirecloud/platform/wiring/views.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/wirecloud/platform/wiring/views.py b/src/wirecloud/platform/wiring/views.py index 77c167209f..88dbb50c80 100644 --- a/src/wirecloud/platform/wiring/views.py +++ b/src/wirecloud/platform/wiring/views.py @@ -64,7 +64,7 @@ def checkMultiuserWiring(self, request, new_wiring_status, old_wiring_status, ca except CatalogueResource.DoesNotExist: operator_preferences = None try: - operator_properties = CatalogueResource.objects.get(vendor=vendor, short_name=name, version=version).get_processed_info()["preferences"] + operator_properties = CatalogueResource.objects.get(vendor=vendor, short_name=name, version=version).get_processed_info()["properties"] except CatalogueResource.DoesNotExist: operator_properties = None @@ -73,7 +73,7 @@ def checkMultiuserWiring(self, request, new_wiring_status, old_wiring_status, ca old_preference = old_operator['preferences'][preference_name] new_preference = operator['preferences'][preference_name] - if old_preference["hidden"] != new_preference["hidden"] or old_preference["readonly"] != new_preference["readonly"]: + if old_preference.get("hidden", False) != new_preference.get("hidden", False) or old_preference.get("readonly", False) != new_preference.get("readonly", False): return build_error_response(request, 403, _('You are not allowed to update this workspace')) # Check if its multiuser @@ -103,11 +103,11 @@ def checkMultiuserWiring(self, request, new_wiring_status, old_wiring_status, ca operator['preferences'][preference_name] = new_preference # Check properties - for property_name in operator['property']: - old_property = old_operator['property'][property_name] - new_property = operator['property'][property_name] + for property_name in operator['properties']: + old_property = old_operator['properties'][property_name] + new_property = operator['properties'][property_name] - if old_property["hidden"] != new_property["hidden"] or old_property["readonly"] != new_property["readonly"]: + if old_property.get("hidden", False) != new_property.get("hidden", False) or old_property.get("readonly", False) != new_property.get("readonly", False): return build_error_response(request, 403, _('You are not allowed to update this workspace')) # Check if its multiuser From df218aecfd9688ac3e9c7c0b002f20cb32b48c25 Mon Sep 17 00:00:00 2001 From: Alejandro Rodriguez Date: Fri, 10 Feb 2017 11:35:57 +0100 Subject: [PATCH 17/28] Add multiuser documentation --- docs/development/macdl_rdf.md | 11 ++++++++++- docs/development/macdl_xml.md | 1 + 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/development/macdl_rdf.md b/docs/development/macdl_rdf.md index c3769198ad..ec4d5f65d0 100644 --- a/docs/development/macdl_rdf.md +++ b/docs/development/macdl_rdf.md @@ -115,7 +115,7 @@ know in order to make it persistent. - **URI**: `http://wirecloud.conwet.fi.upm.es/ns/widget#PlatformStateProperty` - **Properties include**: `dcterms:title`, `dcterms:description`, `wire:type`, - `rdfs:label`, `wire:default`, `wire:secure` + `rdfs:label`, `wire:default`, `wire:secure`, `wire:multiuser` - **Used with**: `wire:hasPlatformStateProperty` @@ -300,6 +300,15 @@ secure. - **Range**: `rdfs:Literal` +#### The `wire:multiuser` property + +This property states whether or not a component persistent variable is +multiuser. Multiuser persistent variables store a value for each user with access rights to the dashboard. + +- **URI**: `http://wirecloud.conwet.fi.upm.es/ns/Widget#value` +- **Range**: `rdfs:Literal` + + #### The `wire:index` property This property states the logical order of elements of the same type. diff --git a/docs/development/macdl_xml.md b/docs/development/macdl_xml.md index 49756b60f2..15bdc5d8d2 100644 --- a/docs/development/macdl_xml.md +++ b/docs/development/macdl_xml.md @@ -177,6 +177,7 @@ following attributes: disallowed if the value of this attribute is true (the value of this variable will be usable through the Application Mashup cross-domain proxy). default: `false`. +- `multiuser`: This persisten value will store it's own value for each user with access rigths to the dashboard. ### The wiring element From 0d05474638e50d2d05fd3c19ea7b5dcb0b544a34 Mon Sep 17 00:00:00 2001 From: Alejandro Rodriguez Date: Wed, 15 Feb 2017 10:46:40 +0100 Subject: [PATCH 18/28] Refactor client operator properties --- .../wirecloud/MashableApplicationComponent.js | 15 +----------- .../static/js/wirecloud/wiring/Operator.js | 23 +++++++++---------- .../js/wirecloud/wiring/OperatorMeta.js | 11 +++++++++ 3 files changed, 23 insertions(+), 26 deletions(-) diff --git a/src/wirecloud/platform/static/js/wirecloud/MashableApplicationComponent.js b/src/wirecloud/platform/static/js/wirecloud/MashableApplicationComponent.js index 55dea9d773..b7d35d75ca 100644 --- a/src/wirecloud/platform/static/js/wirecloud/MashableApplicationComponent.js +++ b/src/wirecloud/platform/static/js/wirecloud/MashableApplicationComponent.js @@ -27,7 +27,7 @@ "use strict"; ns.MashableApplicationComponent = function MashableApplicationComponent(desc) { - var vendor, name, version, i, inputs, outputs, preference, property; + var vendor, name, version, i, inputs, outputs, preference; // Vendor if (!('vendor' in desc) || desc.vendor.trim() === '') { @@ -95,19 +95,6 @@ Object.freeze(this.preferences); Object.freeze(this.preferenceList); - // PropertyList - if (desc.type === "operator") { - this.properties = {}; - this.propertyList = []; - for (i = 0; i < desc.properties.length; i++) { - property = new Wirecloud.UserPrefDef(desc.properties[i].name, desc.properties[i].type, desc.properties[i]); - this.properties[property.name] = property; - this.propertyList.push(property); - } - Object.freeze(this.properties); - Object.freeze(this.propertyList); - } - // Requirements this.requirements = desc.requirements; Object.freeze(this.requirements); diff --git a/src/wirecloud/platform/static/js/wirecloud/wiring/Operator.js b/src/wirecloud/platform/static/js/wirecloud/wiring/Operator.js index 86c4220e04..217c07eb1f 100644 --- a/src/wirecloud/platform/static/js/wirecloud/wiring/Operator.js +++ b/src/wirecloud/platform/static/js/wirecloud/wiring/Operator.js @@ -394,26 +394,24 @@ }; var build_props = function build_props(initial_values) { - var i, properties, prop_info; + if (initial_values == null) { + initial_values = {}; + } - properties = this.meta.propertyList; + var properties = this.meta.propertyList; this.propertyList = []; this.properties = {}; this.propertyCommiter = new Wirecloud.PropertyCommiter(this); - for (i = 0; i < properties.length; i++) { - if (initial_values != null) { - prop_info = initial_values[properties[i].name]; - } else { - prop_info = null - } + properties.forEach((property, index) => { + var prop_info = initial_values[property.name]; if (prop_info != null) { - this.propertyList[i] = new Wirecloud.PersistentVariable(properties[i], this.propertyCommiter, prop_info.readonly, prop_info.value); + this.propertyList[index] = new Wirecloud.PersistentVariable(property, this.propertyCommiter, prop_info.readonly, prop_info.value); } else { - this.propertyList[i] = new Wirecloud.PersistentVariable(properties[i], this.propertyCommiter, false, properties[i].default); + this.propertyList[index] = new Wirecloud.PersistentVariable(property, this.propertyCommiter, false, property.default); } - this.properties[properties[i].name] = this.propertyList[i]; - } + this.properties[property.name] = this.propertyList[index]; + }); } var send_pending_event = function send_pending_event(pendingEvent) { @@ -428,6 +426,7 @@ privates.get(this).meta = meta; build_endpoints.call(this); build_prefs.call(this, this.preferences); + build_props.call(this, this.properties); if (this.loaded) { on_unload.call(this); diff --git a/src/wirecloud/platform/static/js/wirecloud/wiring/OperatorMeta.js b/src/wirecloud/platform/static/js/wirecloud/wiring/OperatorMeta.js index b5a05e24ea..40e89c97f9 100644 --- a/src/wirecloud/platform/static/js/wirecloud/wiring/OperatorMeta.js +++ b/src/wirecloud/platform/static/js/wirecloud/wiring/OperatorMeta.js @@ -43,6 +43,17 @@ Wirecloud.MashableApplicationComponent.call(this, description); + // Properties + this.properties = {}; + this.propertyList = []; + for (var i = 0; i < description.properties.length; i++) { + var property = new Wirecloud.PersistentVariableDef(description.properties[i].name, description.properties[i].type, description.properties[i]); + this.properties[property.name] = property; + this.propertyList.push(property); + } + Object.freeze(this.properties); + Object.freeze(this.propertyList); + if (this.missing) { this.codeurl = Wirecloud.URLs.MISSING_WIDGET_CODE_ENTRY; } else { From c6e0b18e4e8e0de742187381aa32ca6346599e68 Mon Sep 17 00:00:00 2001 From: Alejandro Rodriguez Date: Wed, 15 Feb 2017 10:47:42 +0100 Subject: [PATCH 19/28] Fix parsing json description no longer needed --- src/wirecloud/catalogue/tests/tests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wirecloud/catalogue/tests/tests.py b/src/wirecloud/catalogue/tests/tests.py index 6fb74f2e8f..9649672146 100644 --- a/src/wirecloud/catalogue/tests/tests.py +++ b/src/wirecloud/catalogue/tests/tests.py @@ -697,7 +697,7 @@ def test_upload_of_basic_packaged_widget(self): self.assertTrue(os.path.exists(os.path.join(widget_path, 'documentation/index.html'))) self.assertFalse(os.path.exists(os.path.join(widget_path, 'test.html'))) widget = CatalogueResource.objects.get(vendor='Wirecloud', short_name='Test', version='0.1') - widget_info = json.loads(widget.json_description) + widget_info = widget.json_description self.assertEqual(widget.template_uri, 'Wirecloud_Test_0.1.wgt') self.assertEqual(widget_info['image'], 'images/catalogue.png') @@ -742,7 +742,7 @@ def test_upload_of_packaged_operators(self): self.assertTrue(os.path.exists(os.path.join(operator_path, 'doc/images/image.png'))) self.assertTrue(os.path.exists(os.path.join(operator_path, 'doc/index.html'))) operator = CatalogueResource.objects.get(vendor='Wirecloud', short_name='basic-operator', version='0.1') - operator_info = json.loads(operator.json_description) + operator_info = operator.json_description self.assertEqual(operator.template_uri, 'Wirecloud_basic-operator_0.1.wgt') self.assertEqual(operator_info['image'], 'images/catalogue.png') From 4c9196fc750f34f33084324ae9d2b07628d4b2bf Mon Sep 17 00:00:00 2001 From: Alejandro Rodriguez Date: Wed, 15 Feb 2017 10:48:28 +0100 Subject: [PATCH 20/28] Fix missing the multiuser property on test checks --- src/wirecloud/platform/localcatalogue/tests.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/wirecloud/platform/localcatalogue/tests.py b/src/wirecloud/platform/localcatalogue/tests.py index acd5499b2b..3151e21bc9 100644 --- a/src/wirecloud/platform/localcatalogue/tests.py +++ b/src/wirecloud/platform/localcatalogue/tests.py @@ -109,10 +109,10 @@ def check_basic_widget_info(self, resource): self.assertEqual(data['licenseurl'], 'http://www.apache.org/licenses/LICENSE-2.0.html') self.assertEqual(len(data['properties']), 1) - self.assertEqual(data['properties'], [{'default': '', 'secure': False, 'name': 'prop', 'label': 'Property label', 'type': 'text', 'description': ''}]) + self.assertEqual(data['properties'], [{'default': '', 'secure': False, 'name': 'prop', 'label': 'Property label', 'type': 'text', 'description': '', 'multiuser': False}]) self.assertEqual(len(data['preferences']), 1) - self.assertEqual(data['preferences'], [{'default': 'value', 'secure': False, 'name': 'pref', 'label': 'Preference label', 'type': 'list', 'options': [{'value': '1', 'label': 'Option name'}], 'readonly': False, 'description': 'Preference description', 'value': None}]) + self.assertEqual(data['preferences'], [{'default': 'value', 'secure': False, 'name': 'pref', 'label': 'Preference label', 'type': 'list', 'options': [{'value': '1', 'label': 'Option name'}], 'readonly': False, 'description': 'Preference description', 'value': None, 'multiuser': False}]) self.assertEqual(len(data['wiring']['inputs']), 1) self.assertEqual(data['wiring']['inputs'], [{'name': 'slot', 'label': 'Slot label', 'type': 'text', 'description': '', 'friendcode': 'test_friend_code', 'actionlabel': ''}]) @@ -328,10 +328,10 @@ def test_template_translations(self): self.assertEqual(data['doc'], 'doc/index.html') self.assertEqual(len(data['properties']), 1) - self.assertEqual(data['properties'], [{'default': '', 'secure': False, 'name': 'prop', 'label': 'Etiqueta de la propiedad', 'type': 'text', 'description': ''}]) + self.assertEqual(data['properties'], [{'default': '', 'secure': False, 'name': 'prop', 'label': 'Etiqueta de la propiedad', 'type': 'text', 'description': '', 'multiuser': False}]) self.assertEqual(len(data['preferences']), 1) - self.assertEqual(data['preferences'], [{'default': 'value', 'secure': False, 'name': 'pref', 'label': 'Etiqueta de la preferencia', 'type': 'list', 'options': [{'value': '1', 'label': 'Nombre de la opción'}], 'readonly': False, 'description': 'Descripción de la preferencia', 'value': None}]) + self.assertEqual(data['preferences'], [{'default': 'value', 'secure': False, 'name': 'pref', 'label': 'Etiqueta de la preferencia', 'type': 'list', 'options': [{'value': '1', 'label': 'Nombre de la opción'}], 'readonly': False, 'description': 'Descripción de la preferencia', 'value': None, 'multiuser': False}]) self.assertEqual(len(data['wiring']['inputs']), 1) self.assertEqual(data['wiring']['inputs'], [{'name': 'slot', 'label': 'Etiqueta del endpoint de entrada', 'type': 'text', 'description': '', 'friendcode': 'test_friend_code', 'actionlabel': ''}]) @@ -350,10 +350,10 @@ def test_repeated_translation_indexes(self): self.assertEqual(data['version'], '0.2') self.assertEqual(len(data['properties']), 1) - self.assertEqual(data['properties'], [{'default': '', 'secure': False, 'name': 'prop', 'label': 'Label', 'type': 'text', 'description': ''}]) + self.assertEqual(data['properties'], [{'default': '', 'secure': False, 'name': 'prop', 'label': 'Label', 'type': 'text', 'description': '', 'multiuser': False}]) self.assertEqual(len(data['preferences']), 1) - self.assertEqual(data['preferences'], [{'default': 'value', 'secure': False, 'name': 'pref', 'label': 'Label', 'readonly': False, 'type': 'text', 'description': 'Preference description', 'value': None}]) + self.assertEqual(data['preferences'], [{'default': 'value', 'secure': False, 'name': 'pref', 'label': 'Label', 'readonly': False, 'type': 'text', 'description': 'Preference description', 'value': None, 'multiuser': False}]) self.assertEqual(len(data['wiring']['inputs']), 1) self.assertEqual(data['wiring']['inputs'], [{'name': 'slot', 'label': 'Label', 'type': 'text', 'description': '', 'friendcode': 'test_friend_code', 'actionlabel': ''}]) From f33906650210a02fecfa14edb35d11c09346d6b4 Mon Sep 17 00:00:00 2001 From: Alejandro Rodriguez Date: Wed, 15 Feb 2017 10:49:48 +0100 Subject: [PATCH 21/28] cleanup --- docs/development/macdl_rdf.md | 2 +- src/wirecloud/platform/migration_utils.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/development/macdl_rdf.md b/docs/development/macdl_rdf.md index ec4d5f65d0..04a73c7410 100644 --- a/docs/development/macdl_rdf.md +++ b/docs/development/macdl_rdf.md @@ -115,7 +115,7 @@ know in order to make it persistent. - **URI**: `http://wirecloud.conwet.fi.upm.es/ns/widget#PlatformStateProperty` - **Properties include**: `dcterms:title`, `dcterms:description`, `wire:type`, - `rdfs:label`, `wire:default`, `wire:secure`, `wire:multiuser` + `rdfs:label`, `wire:default`, `wire:secure`, `wire:multiuser` - **Used with**: `wire:hasPlatformStateProperty` diff --git a/src/wirecloud/platform/migration_utils.py b/src/wirecloud/platform/migration_utils.py index 45362fe03c..e801bb4530 100644 --- a/src/wirecloud/platform/migration_utils.py +++ b/src/wirecloud/platform/migration_utils.py @@ -61,7 +61,6 @@ def multiuser_variables_structure_forwards(apps, schema_editor): for widget in tab.iwidget_set.all(): widget.variables = {k: mutate_forwards_widget(v, owner) for k, v in six.iteritems(widget.variables)} widget.save() - pass def multiuser_variables_structure_backwards(apps, schema_editor): From 2b33905630cbe2d3ecc9e3bc3e4e72c41775c0ff Mon Sep 17 00:00:00 2001 From: Alejandro Rodriguez Date: Wed, 15 Feb 2017 10:50:42 +0100 Subject: [PATCH 22/28] Fix user value being overwritten --- src/wirecloud/platform/workspace/utils.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/wirecloud/platform/workspace/utils.py b/src/wirecloud/platform/workspace/utils.py index 9d19e1194d..f66bbf1988 100644 --- a/src/wirecloud/platform/workspace/utils.py +++ b/src/wirecloud/platform/workspace/utils.py @@ -437,17 +437,17 @@ def _get_global_workspace_data(workspaceDAO, user): data_ret['users'] = [] - for user in workspaceDAO.users.all(): + for u in workspaceDAO.users.all(): try: - is_organization = user.organization is not None + is_organization = u.organization is not None except: is_organization = False data_ret['users'].append({ - "fullname": user.get_full_name(), - "username": user.username, + "fullname": u.get_full_name(), + "username": u.username, "organization": is_organization, - "accesslevel": "owner" if workspaceDAO.creator == user else "read", + "accesslevel": "owner" if workspaceDAO.creator == u else "read", }) # Process forced variable values From b79962604e932597ca5f86a6c1b1b1078fa0391b Mon Sep 17 00:00:00 2001 From: Alejandro Rodriguez Date: Thu, 16 Feb 2017 12:43:32 +0100 Subject: [PATCH 23/28] Refactor checkWiring logic --- src/wirecloud/platform/wiring/views.py | 163 ++++++++++++------------- 1 file changed, 76 insertions(+), 87 deletions(-) diff --git a/src/wirecloud/platform/wiring/views.py b/src/wirecloud/platform/wiring/views.py index 88dbb50c80..ef4abaf5ef 100644 --- a/src/wirecloud/platform/wiring/views.py +++ b/src/wirecloud/platform/wiring/views.py @@ -24,6 +24,7 @@ from django.shortcuts import get_object_or_404 from django.utils.translation import ugettext as _ import six +from copy import deepcopy from wirecloud.catalogue.models import CatalogueResource from wirecloud.commons.baseviews import Resource @@ -39,101 +40,92 @@ def handleMultiuser(self, request, new_variable, old_variable): new_value = new_variable["value"] if old_variable is not None: - new_variable = old_variable + new_variable = deepcopy(old_variable) else: new_variable["value"] = {"users": {}} new_variable["value"]["users"]["%s" % request.user.id] = new_value return new_variable - def checkMultiuserWiring(self, request, new_wiring_status, old_wiring_status, can_update_secure=False): - if new_wiring_status["connections"] != old_wiring_status["connections"]: + def checkSameWiring(self, object1, object2): + if len(object1.keys()) != len(object2.keys()): + return False + for key in set(object1.keys()): + if key == "value": + pass + elif isinstance(object1[key], dict): + if not self.checkSameWiring(object1[key], object2[key]): + return False + else: + if not object1[key] == object2[key]: + return False + + return True + + def checkMultiuserWiring(self, request, new_wiring_status, old_wiring_status, owner, can_update_secure=False): + if not self.checkSameWiring(new_wiring_status, old_wiring_status): return build_error_response(request, 403, _('You are not allowed to update this workspace')) for operator_id, operator in six.iteritems(new_wiring_status['operators']): - if operator_id not in old_wiring_status['operators']: - return build_error_response(request, 403, _('You are not allowed to update this workspace')) - old_operator = old_wiring_status['operators'][operator_id] - if old_operator["name"] != operator["name"] or old_operator["id"] != operator["id"]: - return build_error_response(request, 403, _('You are not allowed to update this workspace')) vendor, name, version = operator["name"].split("/") try: - operator_preferences = CatalogueResource.objects.get(vendor=vendor, short_name=name, version=version).get_processed_info()["preferences"] + resource = CatalogueResource.objects.get(vendor=vendor, short_name=name, version=version).get_processed_info(process_variables=True) + operator_preferences = resource["variables"]["preferences"] + operator_properties = resource["variables"]["properties"] except CatalogueResource.DoesNotExist: - operator_preferences = None - try: - operator_properties = CatalogueResource.objects.get(vendor=vendor, short_name=name, version=version).get_processed_info()["properties"] - except CatalogueResource.DoesNotExist: - operator_properties = None + operator_preferences = [] + operator_properties = [] # Check preferences for preference_name in operator['preferences']: old_preference = old_operator['preferences'][preference_name] new_preference = operator['preferences'][preference_name] - if old_preference.get("hidden", False) != new_preference.get("hidden", False) or old_preference.get("readonly", False) != new_preference.get("readonly", False): - return build_error_response(request, 403, _('You are not allowed to update this workspace')) - # Check if its multiuser - preference_secure = False - preference_multiuser = False - if operator_preferences: - for pref in operator_preferences: - if pref["name"] == preference_name: - preference_secure = pref.get("secure", False) - preference_multiuser = pref.get("multiuser", False) - operator_preferences.remove(pref) # Speed up search for next preferences - break - - # Variables can only be updated if multisuer - if not preference_multiuser: - if old_preference["value"] != new_preference["value"]: - return build_error_response(request, 403, _('You are not allowed to update this workspace')) - else: - continue + if preference_name in operator_preferences: + pref = operator_preferences[preference_name] + preference_secure = pref.get("secure", False) + else: + preference_secure = False # Update variable value - if preference_secure and not can_update_secure: - new_preference["value"] = old_preference["value"] - elif new_preference["value"] != old_preference["value"]: - # Handle multiuser - new_preference = self.handleMultiuser(request, new_preference, old_preference) - operator['preferences'][preference_name] = new_preference + if not preference_secure or can_update_secure: + # Variables can only be updated if multisuer + if old_preference["value"]["users"]["%s" % owner.id] != new_preference["value"]: + return build_error_response(request, 403, _('You are not allowed to update this workspace')) + + operator['preferences'][preference_name] = old_preference # Check properties for property_name in operator['properties']: old_property = old_operator['properties'][property_name] new_property = operator['properties'][property_name] - if old_property.get("hidden", False) != new_property.get("hidden", False) or old_property.get("readonly", False) != new_property.get("readonly", False): - return build_error_response(request, 403, _('You are not allowed to update this workspace')) - # Check if its multiuser - property_secure = False - property_multiuser = False - if operator_properties: - for pref in operator_properties: - if pref["name"] == property_name: - property_secure = pref.get("secure", False) - property_multiuser = pref.get("multiuser", False) - operator_properties.remove(pref) # Speed up search for next properties - break - - # Variables can only be updated if multisuer - if not property_multiuser: - if old_property["value"] != new_property["value"]: - return build_error_response(request, 403, _('You are not allowed to update this workspace')) - else: - continue + if property_name in operator_properties: + prop = operator_properties[property_name] + property_secure = prop.get("secure", False) + property_multiuser = prop.get("multiuser", False) + else: + property_secure = False + property_multiuser = False # Update variable value if property_secure and not can_update_secure: new_property["value"] = old_property["value"] - elif new_property["value"] != old_property["value"]: - # Handle multiuser - new_property = self.handleMultiuser(request, new_property, old_property) + else: + # Variables can only be updated if multisuer + if not property_multiuser: + if old_property["value"]["users"]["%s" % owner.id] != new_property["value"]: + return build_error_response(request, 403, _('You are not allowed to update this workspace')) + else: + operator['properties'][property_name] = old_property + else: + # Handle multiuser + new_property = self.handleMultiuser(request, new_property, old_property) + operator['properties'][property_name] = new_property return True @@ -149,6 +141,7 @@ def checkWiring(self, request, new_wiring_status, old_wiring_status, can_update_ for connection in old_read_only_connections: if connection not in new_read_only_connections: return build_error_response(request, 403, _('You are not allowed to remove or update read only connections')) + # Check operator preferences and properties for operator_id, operator in six.iteritems(new_wiring_status['operators']): old_operator = None @@ -163,13 +156,12 @@ def checkWiring(self, request, new_wiring_status, old_wiring_status, can_update_ updated_properties = set(operator['properties'].keys()).intersection(old_operator['properties'].keys()) vendor, name, version = operator["name"].split("/") try: - operator_preferences = CatalogueResource.objects.get(vendor=vendor, short_name=name, version=version).get_processed_info()["preferences"] + resource = CatalogueResource.objects.get(vendor=vendor, short_name=name, version=version).get_processed_info(process_variables=True) + operator_preferences = resource["variables"]["preferences"] + operator_properties = resource["variables"]["properties"] except CatalogueResource.DoesNotExist: - operator_preferences = None - try: - operator_properties = CatalogueResource.objects.get(vendor=vendor, short_name=name, version=version).get_processed_info()["preferences"] - except CatalogueResource.DoesNotExist: - operator_properties = None + operator_preferences = [] + operator_properties = [] else: # New operator added_preferences = operator['preferences'].keys() @@ -180,6 +172,7 @@ def checkWiring(self, request, new_wiring_status, old_wiring_status, can_update_ updated_properties = () # Handle preferences + for preference_name in added_preferences: if operator['preferences'][preference_name].get('readonly', False) or operator['preferences'][preference_name].get('hidden', False): return build_error_response(request, 403, _('Read only and hidden preferences cannot be created using this API')) @@ -197,14 +190,12 @@ def checkWiring(self, request, new_wiring_status, old_wiring_status, can_update_ old_preference = old_operator['preferences'][preference_name] new_preference = operator['preferences'][preference_name] - # Check if the preference is secure - preference_secure = False - if operator_preferences: - for pref in operator_preferences: - if pref["name"] == preference_name: - preference_secure = pref.get("secure", False) - operator_preferences.remove(pref) # Speed up search - break + # Check if its multiuser + if preference_name in operator_preferences: + pref = operator_preferences[preference_name] + preference_secure = pref.get("secure", False) + else: + preference_secure = False if old_preference.get('readonly', False) != new_preference.get('readonly', False) or old_preference.get('hidden', False) != new_preference.get('hidden', False): return build_error_response(request, 403, _('Read only and hidden status cannot be changed using this API')) @@ -237,14 +228,12 @@ def checkWiring(self, request, new_wiring_status, old_wiring_status, can_update_ old_property = old_operator['properties'][property_name] new_property = operator['properties'][property_name] - # Check if the property is secure - property_secure = False - if operator_properties: - for prop in operator_properties: - if prop["name"] == property_name: - property_secure = prop.get("secure", False) - operator_properties.remove(prop) # Speed up search - break + # Check if its multiuser + if property_name in operator_properties: + prop = operator_properties[property_name] + property_secure = prop.get("secure", False) + else: + property_secure = False if old_property.get('readonly', False) != new_property.get('readonly', False) or old_property.get('hidden', False) != new_property.get('hidden', False): return build_error_response(request, 403, _('Read only and hidden status cannot be changed using this API')) @@ -271,9 +260,9 @@ def update(self, request, workspace_id): old_wiring_status = workspace.wiringStatus if workspace.creator == request.user or request.user.is_superuser: - result = self.checkWiring(request, new_wiring_status, old_wiring_status, can_update_secure=True) + result = self.checkWiring(request, new_wiring_status, old_wiring_status, can_update_secure=False) elif workspace.is_available_for(request.user): - result = self.checkMultiuserWiring(request, new_wiring_status, old_wiring_status, can_update_secure=True) + result = self.checkMultiuserWiring(request, new_wiring_status, old_wiring_status, workspace.creator, can_update_secure=False) else: return build_error_response(request, 403, _('You are not allowed to update this workspace')) @@ -289,8 +278,8 @@ def update(self, request, workspace_id): @consumes(('application/json-patch+json',)) def patch(self, request, workspace_id): workspace = get_object_or_404(Workspace, id=workspace_id) - old_wiring_status = workspace.wiringStatus + try: new_wiring_status = jsonpatch.apply_patch(old_wiring_status, parse_json_request(request)) except jsonpatch.JsonPointerException: @@ -301,7 +290,7 @@ def patch(self, request, workspace_id): if workspace.creator == request.user or request.user.is_superuser: result = self.checkWiring(request, new_wiring_status, old_wiring_status, can_update_secure=True) elif workspace.is_available_for(request.user): - result = self.checkMultiuserWiring(request, new_wiring_status, old_wiring_status, can_update_secure=True) + result = self.checkMultiuserWiring(request, new_wiring_status, old_wiring_status, workspace.creator, can_update_secure=True) else: return build_error_response(request, 403, _('You are not allowed to update this workspace')) From 67107e0e27383fbbaaae6d9b7480f0bfa953c294 Mon Sep 17 00:00:00 2001 From: Alejandro Rodriguez Date: Thu, 16 Feb 2017 12:50:44 +0100 Subject: [PATCH 24/28] Add wiring preferences/properties tests --- .../platform/fixtures/test_data.json | 44 ++ src/wirecloud/platform/wiring/tests.py | 567 +++++++++++++++++- 2 files changed, 604 insertions(+), 7 deletions(-) diff --git a/src/wirecloud/platform/fixtures/test_data.json b/src/wirecloud/platform/fixtures/test_data.json index c1354b1c3c..37d06505c2 100644 --- a/src/wirecloud/platform/fixtures/test_data.json +++ b/src/wirecloud/platform/fixtures/test_data.json @@ -42,6 +42,24 @@ "date_joined": "2011-03-17T17:21:19Z" } }, + { + "model": "auth.User", + "pk": "4", + "fields": { + "username": "test3", + "first_name": "", + "last_name": "", + "is_active": true, + "is_superuser": false, + "is_staff": false, + "last_login": "2011-03-17T17:21:19Z", + "groups": [1], + "user_permissions": [], + "password": "pbkdf2_sha256$30000$Td6dJ7DYEmYb$NSq/ZzEp3Glq4RdW+Vf/oSFp40NNjXWK2zbbNX/nj/k=", + "email": "", + "date_joined": "2011-03-17T17:21:19Z" + } + }, { "model": "platform.XHTML", "pk": "1", @@ -99,6 +117,14 @@ "workspace": "202" } }, + { + "model": "platform.UserWorkspace", + "pk": "3", + "fields": { + "user": "3", + "workspace": "1" + } + }, { "model": "platform.Tab", "pk": "1", @@ -219,6 +245,24 @@ "users": [2] } }, + { + "pk": 4, + "model": "catalogue.CatalogueResource", + "fields": { + "groups": [], + "template_uri": "Wirecloud_TestOperatorMultiuser_1.0.wgt", + "popularity": "0", + "vendor": "Wirecloud", + "short_name": "TestOperatorMultiuser", + "json_description": "{\"default_lang\": \"en\", \"vendor\": \"Wirecloud\", \"description\": \"Test operator description\", \"translations\": {}, \"smartphoneimage\": \"\", \"translation_index_usage\": {}, \"title\": \"TestOperator\", \"properties\": [{\"default\": \"\",\"multiuser\": true, \"name\": \"prop1\", \"secure\": false, \"label\": \"Prefix\", \"type\": \"text\", \"description\": \"\"}], \"js_files\": [\"js/main.js\"], \"requirements\": [], \"preferences\": [], \"authors\": \"admin\", \"wiring\": {\"inputs\": [{\"friendcode\": \"test_friend_code\", \"actionlabel\": \"\", \"name\": \"input\", \"label\": \"input\", \"type\": \"text\", \"description\": \"\"}], \"outputs\": [{\"friendcode\": \"test_friend_code\", \"description\": \"\", \"type\": \"text\", \"name\": \"output\", \"label\": \"output\"}]}, \"name\": \"TestOperator\", \"image\": \"images/catalogue.png\", \"version\": \"1.0\", \"context\": [], \"widget_height\": \"\", \"email\": \"test@example.com\", \"type\": \"operator\", \"widget_width\": \"\", \"doc\": \"doc/index.html\"}", + "creator": 2, + "creation_date": "2011-05-13T11:24:03Z", + "version": "1.0", + "type": 2, + "public": false, + "users": [2] + } + }, { "model": "platform.Widget", "pk": "1", diff --git a/src/wirecloud/platform/wiring/tests.py b/src/wirecloud/platform/wiring/tests.py index 661d8c3534..f5edbc2b50 100644 --- a/src/wirecloud/platform/wiring/tests.py +++ b/src/wirecloud/platform/wiring/tests.py @@ -492,7 +492,7 @@ def test_normal_preferences_can_be_added(self): 'name': 'Wirecloud/TestOperator/1.0', 'preferences': { 'pref1': {'value': 'a'}, - 'pref2': {'hidden': False, 'readonly': False, 'value': {"users": {"2": 'a'}}}, + 'pref2': {'hidden': False, 'readonly': False, 'value': 'a'}, }, 'properties': {} }, @@ -501,6 +501,9 @@ def test_normal_preferences_can_be_added(self): }) response = client.put(self.wiring_url, data, content_type='application/json') self.assertEqual(response.status_code, 204) + workspace = Workspace.objects.get(id=self.workspace_id) + self.assertEqual(workspace.wiringStatus["operators"]["1"]["preferences"]["pref1"]["value"], {"users": {"2": "a"}}) + self.assertEqual(workspace.wiringStatus["operators"]["1"]["preferences"]["pref2"]["value"], {"users": {"2": "a"}}) def test_normal_preferences_can_be_updated(self): @@ -530,8 +533,8 @@ def test_normal_preferences_can_be_updated(self): 'id': '1', 'name': 'Wirecloud/TestOperator/1.0', 'preferences': { - 'pref1': {'hidden': True, 'readonly': False, 'value': {"users": {"2": 'b'}}}, - 'pref2': {'hidden': False, 'readonly': False, 'value': {"users": {"2": 'c'}}}, + 'pref1': {'hidden': True, 'readonly': False, 'value': 'b'}, + 'pref2': {'hidden': False, 'readonly': False, 'value': 'c'}, }, 'properties': {} }, @@ -540,6 +543,556 @@ def test_normal_preferences_can_be_updated(self): }) response = client.put(self.wiring_url, data, content_type='application/json') self.assertEqual(response.status_code, 204) + workspace = Workspace.objects.get(id=self.workspace_id) + self.assertEqual(workspace.wiringStatus["operators"]["1"]["preferences"]["pref1"]["value"], {"users": {"2": "b"}}) + self.assertEqual(workspace.wiringStatus["operators"]["1"]["preferences"]["pref2"]["value"], {"users": {"2": "c"}}) + + def test_secure_preferences_can_be_updated(self): + workspace = Workspace.objects.get(id=self.workspace_id) + workspace.wiringStatus = { + 'operators': { + '1': { + 'id': '1', + 'name': 'Wirecloud/TestOperator/1.0', + 'preferences': { + 'pref1': {'hidden': True, 'readonly': False, 'value': {"users": {"2": 'a'}}, 'secure': True}, + }, + 'properties': {} + }, + }, + 'connections': [], + } + workspace.save() + + client = Client() + client.login(username='test', password='test') + + data = json.dumps([{ + "op": "replace", + "path": "/operators/1/preferences/pref1/value", + "value": "helloWorld", + }]) + + response = client.patch(self.wiring_url, data, content_type='application/json-patch+json') + self.assertEqual(response.status_code, 204) + workspace = Workspace.objects.get(id=self.workspace_id) + self.assertEqual(workspace.wiringStatus["operators"]["1"]["preferences"]["pref1"]["value"], {"users": {"2": "helloWorld"}}) + + def test_normal_properties_can_be_created(self): + + workspace = Workspace.objects.get(id=self.workspace_id) + workspace.wiringStatus = { + 'operators': { + '1': { + 'id': '1', + 'name': 'Wirecloud/TestOperator/1.0', + 'preferences': {}, + 'properties': {} + }, + }, + 'connections': [], + } + workspace.save() + + client = Client() + client.login(username='test', password='test') + + data = json.dumps({ + 'operators': { + '1': { + 'id': '1', + 'name': 'Wirecloud/TestOperator/1.0', + 'preferences': {}, + 'properties': {'prop1': {'hidden': False, 'readonly': False, 'value': 'b'}} + }, + }, + 'connections': [], + }) + response = client.put(self.wiring_url, data, content_type='application/json') + self.assertEqual(response.status_code, 204) + workspace = Workspace.objects.get(id=self.workspace_id) + self.assertEqual(workspace.wiringStatus["operators"]["1"]["properties"]["prop1"]["value"], {"users": {"2": "b"}}) + + def test_readonly_properties_cannot_be_created(self): + + workspace = Workspace.objects.get(id=self.workspace_id) + workspace.wiringStatus = { + 'operators': { + '1': { + 'id': '1', + 'name': 'Wirecloud/TestOperator/1.0', + 'preferences': {}, + 'properties': {} + }, + }, + 'connections': [], + } + workspace.save() + + client = Client() + client.login(username='test', password='test') + + data = json.dumps({ + 'operators': { + '1': { + 'id': '1', + 'name': 'Wirecloud/TestOperator/1.0', + 'preferences': {}, + 'properties': {'prop1': {'hidden': False, 'readonly': True, 'value': 'b'}} + }, + }, + 'connections': [], + }) + response = client.put(self.wiring_url, data, content_type='application/json') + self.assertEqual(response.status_code, 403) + + def test_hidden_properties_cannot_be_created(self): + + workspace = Workspace.objects.get(id=self.workspace_id) + workspace.wiringStatus = { + 'operators': { + '1': { + 'id': '1', + 'name': 'Wirecloud/TestOperator/1.0', + 'preferences': {}, + 'properties': {} + }, + }, + 'connections': [], + } + workspace.save() + + client = Client() + client.login(username='test', password='test') + + data = json.dumps({ + 'operators': { + '1': { + 'id': '1', + 'name': 'Wirecloud/TestOperator/1.0', + 'preferences': {}, + 'properties': {'prop1': {'hidden': True, 'readonly': False, 'value': 'b'}} + }, + }, + 'connections': [], + }) + response = client.put(self.wiring_url, data, content_type='application/json') + self.assertEqual(response.status_code, 403) + + def test_normal_properties_can_be_removed(self): + + workspace = Workspace.objects.get(id=self.workspace_id) + workspace.wiringStatus = { + 'operators': { + '1': { + 'id': '1', + 'name': 'Wirecloud/TestOperator/1.0', + 'preferences': {}, + 'properties': {'prop1': {'hidden': False, 'readonly': False, 'value': 'b'}} + }, + }, + 'connections': [], + } + workspace.save() + + client = Client() + client.login(username='test', password='test') + + data = json.dumps({ + 'operators': { + '1': { + 'id': '1', + 'name': 'Wirecloud/TestOperator/1.0', + 'preferences': {}, + 'properties': {} + }, + }, + 'connections': [], + }) + response = client.put(self.wiring_url, data, content_type='application/json') + self.assertEqual(response.status_code, 204) + + def test_read_properties_cannot_be_removed(self): + + workspace = Workspace.objects.get(id=self.workspace_id) + workspace.wiringStatus = { + 'operators': { + '1': { + 'id': '1', + 'name': 'Wirecloud/TestOperator/1.0', + 'preferences': {}, + 'properties': {'prop1': {'hidden': False, 'readonly': True, 'value': 'b'}} + }, + }, + 'connections': [], + } + workspace.save() + + client = Client() + client.login(username='test', password='test') + + data = json.dumps({ + 'operators': { + '1': { + 'id': '1', + 'name': 'Wirecloud/TestOperator/1.0', + 'preferences': {}, + 'properties': {} + }, + }, + 'connections': [], + }) + response = client.put(self.wiring_url, data, content_type='application/json') + self.assertEqual(response.status_code, 403) + + def test_hidden_properties_cannot_be_removed(self): + + workspace = Workspace.objects.get(id=self.workspace_id) + workspace.wiringStatus = { + 'operators': { + '1': { + 'id': '1', + 'name': 'Wirecloud/TestOperator/1.0', + 'preferences': {}, + 'properties': {'prop1': {'hidden': True, 'readonly': False, 'value': 'b'}} + }, + }, + 'connections': [], + } + workspace.save() + + client = Client() + client.login(username='test', password='test') + + data = json.dumps({ + 'operators': { + '1': { + 'id': '1', + 'name': 'Wirecloud/TestOperator/1.0', + 'preferences': {}, + 'properties': {} + }, + }, + 'connections': [], + }) + response = client.put(self.wiring_url, data, content_type='application/json') + self.assertEqual(response.status_code, 403) + + def test_multiuser_properties_can_be_updated_by_owner(self): + workspace = Workspace.objects.get(id=self.workspace_id) + workspace.public = False + workspace.wiringStatus = { + 'operators': { + '1': { + 'id': '1', + 'name': 'Wirecloud/TestOperatorMultiuser/1.0', + 'preferences': {}, + 'properties': { + 'prop1': {'hidden': True, 'readonly': False, 'value': {"users": {"2": 'a'}}}, + } + }, + }, + 'connections': [], + } + workspace.save() + + client = Client() + client.login(username='test', password='test') + + data = json.dumps({ + 'operators': { + '1': { + 'id': '1', + 'name': 'Wirecloud/TestOperatorMultiuser/1.0', + 'preferences': {}, + 'properties': { + 'prop1': {'hidden': True, 'readonly': False, 'value': 'b'}, + } + }, + }, + 'connections': [], + }) + response = client.put(self.wiring_url, data, content_type='application/json') + self.assertEqual(response.status_code, 204) + workspace = Workspace.objects.get(id=self.workspace_id) + self.assertEqual(workspace.wiringStatus["operators"]["1"]["properties"]["prop1"]["value"], {"users": {"2": "b"}}) + + def test_multiuser_properties_can_be_updated_by_allowed_users(self): + workspace = Workspace.objects.get(id=self.workspace_id) + workspace.public = False + workspace.wiringStatus = { + 'operators': { + '1': { + 'id': '1', + 'name': 'Wirecloud/TestOperatorMultiuser/1.0', + 'preferences': { + 'pref1': {'hidden': True, 'readonly': False, 'value': {"users": {"2": 'default'}}}, + }, + 'properties': { + 'prop1': {'hidden': True, 'readonly': False, 'value': {"users": {"2": 'a'}}}, + 'prop2': {'hidden': True, 'readonly': False, 'value': {"users": {"2": 'b'}}}, + } + }, + }, + 'connections': [], + } + workspace.save() + + client = Client() + client.login(username='test2', password='test') + + data = json.dumps({ + 'operators': { + '1': { + 'id': '1', + 'name': 'Wirecloud/TestOperatorMultiuser/1.0', + 'preferences': { + 'pref1': {'hidden': True, 'readonly': False, 'value': 'default'}, + }, + 'properties': { + 'prop1': {'hidden': True, 'readonly': False, 'value': 'b'}, + 'prop2': {'hidden': True, 'readonly': False, 'value': 'b'}, + } + }, + }, + 'connections': [], + }) + response = client.put(self.wiring_url, data, content_type='application/json') + self.assertEqual(response.status_code, 204) + workspace = Workspace.objects.get(id=self.workspace_id) + self.assertEqual(workspace.wiringStatus["operators"]["1"]["properties"]["prop1"]["value"], {"users": {"2": "a", "3": "b"}}) + + def test_nonmultiuser_properties_cannot_be_updated_by_allowed_users(self): + workspace = Workspace.objects.get(id=self.workspace_id) + workspace.public = False + workspace.wiringStatus = { + 'operators': { + '1': { + 'id': '1', + 'name': 'Wirecloud/TestOperatorMultiuser/1.0', + 'preferences': {}, + 'properties': { + 'prop2': {'hidden': True, 'readonly': False, 'value': {"users": {"2": 'a'}}}, + } + }, + }, + 'connections': [], + } + workspace.save() + + client = Client() + client.login(username='test2', password='test') + + data = json.dumps({ + 'operators': { + '1': { + 'id': '1', + 'name': 'Wirecloud/TestOperatorMultiuser/1.0', + 'preferences': {}, + 'properties': { + 'prop2': {'hidden': True, 'readonly': False, 'value': 'c'}, + } + }, + }, + 'connections': [], + }) + response = client.put(self.wiring_url, data, content_type='application/json') + self.assertEqual(response.status_code, 403) + + def test_preferences_cannot_be_updated_by_allowed_users(self): + workspace = Workspace.objects.get(id=self.workspace_id) + workspace.public = False + workspace.wiringStatus = { + 'operators': { + '1': { + 'id': '1', + 'name': 'Wirecloud/TestOperatorMultiuser/1.0', + 'preferences': { + 'pref1': {'hidden': True, 'readonly': False, 'value': {"users": {"2": 'default'}}}, + }, + 'properties': { + 'prop1': {'hidden': True, 'readonly': False, 'value': {"users": {"2": 'a'}}}, + } + }, + }, + 'connections': [], + } + workspace.save() + + client = Client() + client.login(username='test2', password='test') + + data = json.dumps({ + 'operators': { + '1': { + 'id': '1', + 'name': 'Wirecloud/TestOperatorMultiuser/1.0', + 'preferences': { + 'pref1': {'hidden': True, 'readonly': False, 'value': 'randomValue'}, + }, + 'properties': { + 'prop1': {'hidden': True, 'readonly': False, 'value': 'b'}, + } + }, + }, + 'connections': [], + }) + response = client.put(self.wiring_url, data, content_type='application/json') + self.assertEqual(response.status_code, 403) + + def test_non_owners_cannot_remove_wiring_elements(self): + workspace = Workspace.objects.get(id=self.workspace_id) + workspace.public = False + workspace.wiringStatus = { + 'operators': { + '1': { + 'id': '1', + 'name': 'Wirecloud/TestOperatorMultiuser/1.0', + 'preferences': {}, + 'properties': { + 'prop1': {'hidden': True, 'readonly': False, 'value': {"users": {"2": 'b'}}}, + } + }, + }, + 'connections': [], + } + workspace.save() + + client = Client() + client.login(username='test2', password='test') + + data = json.dumps({ + 'operators': { + '1': { + 'id': '1', + 'name': 'Wirecloud/TestOperatorMultiuser/1.0', + 'preferences': {}, + 'properties': {} + }, + }, + 'connections': [], + }) + response = client.put(self.wiring_url, data, content_type='application/json') + self.assertEqual(response.status_code, 403) + workspace = Workspace.objects.get(id=self.workspace_id) + self.assertEqual(workspace.wiringStatus["operators"]["1"]["properties"]["prop1"]["value"], {"users": {"2": "b"}}) + + def test_non_owners_cannot_add_wiring_elements(self): + workspace = Workspace.objects.get(id=self.workspace_id) + workspace.public = False + workspace.wiringStatus = { + 'operators': { + '1': { + 'id': '1', + 'name': 'Wirecloud/TestOperatorMultiuser/1.0', + 'preferences': {}, + 'properties': { + } + }, + }, + 'connections': [], + } + workspace.save() + + client = Client() + client.login(username='test2', password='test') + + data = json.dumps({ + 'operators': { + '1': { + 'id': '1', + 'name': 'Wirecloud/TestOperatorMultiuser/1.0', + 'preferences': {}, + 'properties': { + 'prop1': {'hidden': True, 'readonly': False, 'value': 'b'}, + } + }, + }, + 'connections': [], + }) + response = client.put(self.wiring_url, data, content_type='application/json') + self.assertEqual(response.status_code, 403) + workspace = Workspace.objects.get(id=self.workspace_id) + self.assertEqual(len(workspace.wiringStatus["operators"]["1"]["properties"]), 0) + + def test_non_owners_cannot_modify_wiring_elements(self): + workspace = Workspace.objects.get(id=self.workspace_id) + workspace.public = False + workspace.wiringStatus = { + 'operators': { + '1': { + 'id': '1', + 'name': 'Wirecloud/TestOperatorMultiuser/1.0', + 'preferences': { + 'pref1': {'hidden': True, 'readonly': False, 'value': {"users": {"2": 'b'}}}, + }, + 'properties': { + } + }, + }, + 'connections': [], + } + workspace.save() + + client = Client() + client.login(username='test2', password='test') + + data = json.dumps({ + 'operators': { + '1': { + 'id': '1', + 'name': 'Wirecloud/TestOperatorMultiuser/1.0', + 'preferences': { + 'pref1': {'hidden': True, 'readonly': True, 'value': 'b'}, + }, + 'properties': {} + }, + }, + 'connections': [], + }) + response = client.put(self.wiring_url, data, content_type='application/json') + self.assertEqual(response.status_code, 403) + workspace = Workspace.objects.get(id=self.workspace_id) + self.assertEqual(workspace.wiringStatus["operators"]["1"]["preferences"]["pref1"]["value"], {"users": {"2": "b"}}) + + def test_multiuser_properties_cannot_be_updated_by_other(self): + workspace = Workspace.objects.get(id=self.workspace_id) + workspace.public = False + workspace.wiringStatus = { + 'operators': { + '1': { + 'id': '1', + 'name': 'Wirecloud/TestOperatorMultiuser/1.0', + 'preferences': {}, + 'properties': { + 'prop1': {'hidden': True, 'readonly': False, 'value': {"users": {"2": 'a'}}} + } + }, + }, + 'connections': [], + } + workspace.save() + + client = Client() + client.login(username='test3', password='test') + + data = json.dumps({ + 'operators': { + '1': { + 'id': '1', + 'name': 'Wirecloud/TestOperatorMultiuser/1.0', + 'preferences': {}, + 'properties': { + 'prop1': {'hidden': True, 'readonly': False, 'value': 'b'}, + } + }, + }, + 'connections': [], + }) + response = client.put(self.wiring_url, data, content_type='application/json') + self.assertEqual(response.status_code, 403) + workspace = Workspace.objects.get(id=self.workspace_id) + self.assertEqual(workspace.wiringStatus["operators"]["1"]["properties"]["prop1"]["value"], {"users": {"2": "a"}}) def test_operator_added_put(self): @@ -552,8 +1105,8 @@ def test_operator_added_put(self): 'id': '1', 'name': 'Wirecloud/TestOperator/1.0', 'preferences': { - 'pref1': {'hidden': False, 'readonly': False, 'value': {"users": {"2": 'b'}}}, - 'pref2': {'hidden': False, 'readonly': False, 'value': {"users": {"2": 'c'}}}, + 'pref1': {'hidden': False, 'readonly': False, 'value': 'b'}, + 'pref2': {'hidden': False, 'readonly': False, 'value': 'c'}, }, 'properties': {} }, @@ -575,8 +1128,8 @@ def test_operator_added_patch(self): 'id': '1', 'name': 'Wirecloud/TestOperator/1.0', 'preferences': { - 'pref1': {'hidden': False, 'readonly': False, 'value': {"users": {"2": 'b'}}}, - 'pref2': {'hidden': False, 'readonly': False, 'value': {"users": {"2": 'c'}}}, + 'pref1': {'hidden': False, 'readonly': False, 'value': 'b'}, + 'pref2': {'hidden': False, 'readonly': False, 'value': 'c'}, }, 'properties': {} }, From f39f1e1cd54267cf95af200bedd0bcfbe35371bf Mon Sep 17 00:00:00 2001 From: Alejandro Rodriguez Date: Thu, 16 Feb 2017 13:44:58 +0100 Subject: [PATCH 25/28] Fix error when saving wiring with no changes as an allowed user --- src/wirecloud/platform/wiring/views.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/wirecloud/platform/wiring/views.py b/src/wirecloud/platform/wiring/views.py index ef4abaf5ef..afe04df9d6 100644 --- a/src/wirecloud/platform/wiring/views.py +++ b/src/wirecloud/platform/wiring/views.py @@ -121,7 +121,7 @@ def checkMultiuserWiring(self, request, new_wiring_status, old_wiring_status, ow if old_property["value"]["users"]["%s" % owner.id] != new_property["value"]: return build_error_response(request, 403, _('You are not allowed to update this workspace')) else: - operator['properties'][property_name] = old_property + new_property["value"] = old_property["value"] else: # Handle multiuser new_property = self.handleMultiuser(request, new_property, old_property) @@ -234,7 +234,6 @@ def checkWiring(self, request, new_wiring_status, old_wiring_status, can_update_ property_secure = prop.get("secure", False) else: property_secure = False - if old_property.get('readonly', False) != new_property.get('readonly', False) or old_property.get('hidden', False) != new_property.get('hidden', False): return build_error_response(request, 403, _('Read only and hidden status cannot be changed using this API')) From 2bc6f6693b01ce5b0356994d3c6d08c28223b564 Mon Sep 17 00:00:00 2001 From: Alejandro Rodriguez Date: Thu, 16 Feb 2017 13:47:41 +0100 Subject: [PATCH 26/28] Improve wiring tests --- .../platform/fixtures/test_data.json | 2 +- src/wirecloud/platform/wiring/tests.py | 381 +++++++++++++++++- 2 files changed, 375 insertions(+), 8 deletions(-) diff --git a/src/wirecloud/platform/fixtures/test_data.json b/src/wirecloud/platform/fixtures/test_data.json index 37d06505c2..7f42e976c4 100644 --- a/src/wirecloud/platform/fixtures/test_data.json +++ b/src/wirecloud/platform/fixtures/test_data.json @@ -254,7 +254,7 @@ "popularity": "0", "vendor": "Wirecloud", "short_name": "TestOperatorMultiuser", - "json_description": "{\"default_lang\": \"en\", \"vendor\": \"Wirecloud\", \"description\": \"Test operator description\", \"translations\": {}, \"smartphoneimage\": \"\", \"translation_index_usage\": {}, \"title\": \"TestOperator\", \"properties\": [{\"default\": \"\",\"multiuser\": true, \"name\": \"prop1\", \"secure\": false, \"label\": \"Prefix\", \"type\": \"text\", \"description\": \"\"}], \"js_files\": [\"js/main.js\"], \"requirements\": [], \"preferences\": [], \"authors\": \"admin\", \"wiring\": {\"inputs\": [{\"friendcode\": \"test_friend_code\", \"actionlabel\": \"\", \"name\": \"input\", \"label\": \"input\", \"type\": \"text\", \"description\": \"\"}], \"outputs\": [{\"friendcode\": \"test_friend_code\", \"description\": \"\", \"type\": \"text\", \"name\": \"output\", \"label\": \"output\"}]}, \"name\": \"TestOperator\", \"image\": \"images/catalogue.png\", \"version\": \"1.0\", \"context\": [], \"widget_height\": \"\", \"email\": \"test@example.com\", \"type\": \"operator\", \"widget_width\": \"\", \"doc\": \"doc/index.html\"}", + "json_description": "{\"default_lang\": \"en\", \"vendor\": \"Wirecloud\", \"description\": \"Test operator description\", \"translations\": {}, \"smartphoneimage\": \"\", \"translation_index_usage\": {}, \"title\": \"TestOperator\", \"properties\": [{\"default\": \"\",\"multiuser\": true, \"name\": \"prop1\", \"secure\": false, \"label\": \"Prefix\", \"type\": \"text\", \"description\": \"\"}], \"js_files\": [\"js/main.js\"], \"requirements\": [], \"preferences\": [{\"default\": \"\",\"multiuser\": false, \"name\": \"pref1\", \"secure\": false, \"label\": \"Prefix\", \"type\": \"text\", \"description\": \"\"}], \"authors\": \"admin\", \"wiring\": {\"inputs\": [{\"friendcode\": \"test_friend_code\", \"actionlabel\": \"\", \"name\": \"input\", \"label\": \"input\", \"type\": \"text\", \"description\": \"\"}], \"outputs\": [{\"friendcode\": \"test_friend_code\", \"description\": \"\", \"type\": \"text\", \"name\": \"output\", \"label\": \"output\"}]}, \"name\": \"TestOperator\", \"image\": \"images/catalogue.png\", \"version\": \"1.0\", \"context\": [], \"widget_height\": \"\", \"email\": \"test@example.com\", \"type\": \"operator\", \"widget_width\": \"\", \"doc\": \"doc/index.html\"}", "creator": 2, "creation_date": "2011-05-13T11:24:03Z", "version": "1.0", diff --git a/src/wirecloud/platform/wiring/tests.py b/src/wirecloud/platform/wiring/tests.py index f5edbc2b50..a472a34f21 100644 --- a/src/wirecloud/platform/wiring/tests.py +++ b/src/wirecloud/platform/wiring/tests.py @@ -679,6 +679,105 @@ def test_hidden_properties_cannot_be_created(self): response = client.put(self.wiring_url, data, content_type='application/json') self.assertEqual(response.status_code, 403) + def test_readonly_properties_status_cannot_be_updated(self): + workspace = Workspace.objects.get(id=self.workspace_id) + workspace.wiringStatus = { + 'operators': { + '1': { + 'id': '1', + 'name': 'Wirecloud/TestOperator/1.0', + 'preferences': {}, + 'properties': {'prop1': {'hidden': False, 'readonly': True, 'value': 'a'}} + }, + }, + 'connections': [], + } + workspace.save() + + client = Client() + client.login(username='test', password='test') + + data = json.dumps({ + 'operators': { + '1': { + 'id': '1', + 'name': 'Wirecloud/TestOperator/1.0', + 'preferences': {}, + 'properties': {'prop1': {'hidden': False, 'readonly': False, 'value': 'a'}} + }, + }, + 'connections': [], + }) + + response = client.put(self.wiring_url, data, content_type='application/json') + self.assertEqual(response.status_code, 403) + + def test_readonly_properties_value_cannot_be_updated(self): + workspace = Workspace.objects.get(id=self.workspace_id) + workspace.wiringStatus = { + 'operators': { + '1': { + 'id': '1', + 'name': 'Wirecloud/TestOperator/1.0', + 'preferences': {}, + 'properties': {'prop1': {'hidden': False, 'readonly': True, 'value': 'a'}} + }, + }, + 'connections': [], + } + workspace.save() + + client = Client() + client.login(username='test', password='test') + + data = json.dumps({ + 'operators': { + '1': { + 'id': '1', + 'name': 'Wirecloud/TestOperator/1.0', + 'preferences': {}, + 'properties': {'prop1': {'hidden': False, 'readonly': True, 'value': 'b'}} + }, + }, + 'connections': [], + }) + + response = client.put(self.wiring_url, data, content_type='application/json') + self.assertEqual(response.status_code, 403) + + def test_hidden_properties_status_cannot_be_updated(self): + workspace = Workspace.objects.get(id=self.workspace_id) + workspace.wiringStatus = { + 'operators': { + '1': { + 'id': '1', + 'name': 'Wirecloud/TestOperator/1.0', + 'preferences': {}, + 'properties': {'prop1': {'hidden': True, 'readonly': False, 'value': {"users": {"2": 'a'}}}} + }, + }, + 'connections': [], + } + workspace.save() + + client = Client() + client.login(username='test', password='test') + + data = json.dumps({ + 'operators': { + '1': { + 'id': '1', + 'name': 'Wirecloud/TestOperator/1.0', + 'preferences': {}, + 'properties': {'prop1': {'hidden': False, 'readonly': False, 'value': 'a'}} + }, + }, + 'connections': [], + }) + + response = client.put(self.wiring_url, data, content_type='application/json') + self.assertEqual(response.status_code, 403) + def test_normal_properties_can_be_removed(self): workspace = Workspace.objects.get(id=self.workspace_id) @@ -786,9 +885,11 @@ def test_multiuser_properties_can_be_updated_by_owner(self): '1': { 'id': '1', 'name': 'Wirecloud/TestOperatorMultiuser/1.0', - 'preferences': {}, + 'preferences': { + 'pref1': {'hidden': True, 'readonly': False, 'value': {"users": {"2": 'default'}}} + }, 'properties': { - 'prop1': {'hidden': True, 'readonly': False, 'value': {"users": {"2": 'a'}}}, + 'prop1': {'hidden': True, 'readonly': False, 'value': {"users": {"2": 'a'}}} } }, }, @@ -804,7 +905,9 @@ def test_multiuser_properties_can_be_updated_by_owner(self): '1': { 'id': '1', 'name': 'Wirecloud/TestOperatorMultiuser/1.0', - 'preferences': {}, + 'preferences': { + 'pref1': {'hidden': True, 'readonly': False, 'value': 'default'} + }, 'properties': { 'prop1': {'hidden': True, 'readonly': False, 'value': 'b'}, } @@ -817,6 +920,47 @@ def test_multiuser_properties_can_be_updated_by_owner(self): workspace = Workspace.objects.get(id=self.workspace_id) self.assertEqual(workspace.wiringStatus["operators"]["1"]["properties"]["prop1"]["value"], {"users": {"2": "b"}}) + def test_non_existent_oeprators_can_be_created_by_owner(self): + workspace = Workspace.objects.get(id=self.workspace_id) + workspace.public = False + workspace.wiringStatus = { + 'operators': { + '1': { + 'id': '1', + 'name': 'Wirecloud/TestRandomOperator/1.0', + 'preferences': { + 'pref1': {'hidden': True, 'readonly': False, 'value': {"users": {"2": 'a'}}} + }, + 'properties': { + 'prop1': {'hidden': True, 'readonly': False, 'value': {"users": {"2": 'a'}}} + } + }, + }, + 'connections': [], + } + workspace.save() + + client = Client() + client.login(username='test', password='test') + + data = json.dumps({ + 'operators': { + '1': { + 'id': '1', + 'name': 'Wirecloud/TestRandomOperator/1.0', + 'preferences': { + 'pref1': {'hidden': True, 'readonly': False, 'value': 'a'} + }, + 'properties': { + 'prop1': {'hidden': True, 'readonly': False, 'value': 'b'} + } + }, + }, + 'connections': [], + }) + response = client.put(self.wiring_url, data, content_type='application/json') + self.assertEqual(response.status_code, 204) + def test_multiuser_properties_can_be_updated_by_allowed_users(self): workspace = Workspace.objects.get(id=self.workspace_id) workspace.public = False @@ -862,6 +1006,185 @@ def test_multiuser_properties_can_be_updated_by_allowed_users(self): workspace = Workspace.objects.get(id=self.workspace_id) self.assertEqual(workspace.wiringStatus["operators"]["1"]["properties"]["prop1"]["value"], {"users": {"2": "a", "3": "b"}}) + def test_nonexistent_operator_properties_cannot_be_updated_by_allowed_users(self): + workspace = Workspace.objects.get(id=self.workspace_id) + workspace.public = False + workspace.wiringStatus = { + 'operators': { + '1': { + 'id': '1', + 'name': 'Wirecloud/TestRandomOperator/1.0', + 'preferences': { + 'pref1': {'hidden': True, 'readonly': False, 'value': {"users": {"2": 'default'}}}, + }, + 'properties': { + 'prop1': {'hidden': True, 'readonly': False, 'value': {"users": {"2": 'a'}}}, + 'prop2': {'hidden': True, 'readonly': False, 'value': {"users": {"2": 'b'}}}, + } + }, + }, + 'connections': [], + } + workspace.save() + + client = Client() + client.login(username='test2', password='test') + + data = json.dumps({ + 'operators': { + '1': { + 'id': '1', + 'name': 'Wirecloud/TestRandomOperator/1.0', + 'preferences': { + 'pref1': {'hidden': True, 'readonly': False, 'value': 'default'}, + }, + 'properties': { + 'prop1': {'hidden': True, 'readonly': False, 'value': 'b'}, + 'prop2': {'hidden': True, 'readonly': False, 'value': 'b'}, + } + }, + }, + 'connections': [], + }) + response = client.put(self.wiring_url, data, content_type='application/json') + self.assertEqual(response.status_code, 403) + + def test_save_wiring_by_owner(self): + workspace = Workspace.objects.get(id=self.workspace_id) + workspace.public = False + workspace.wiringStatus = { + 'operators': { + '1': { + 'id': '1', + 'name': 'Wirecloud/TestOperatorMultiuser/1.0', + 'preferences': { + 'pref1': {'hidden': True, 'readonly': False, 'value': {"users": {"2": 'default'}}}, + }, + 'properties': { + 'prop1': {'hidden': True, 'readonly': False, 'value': {"users": {"2": 'a'}}}, + 'prop2': {'hidden': True, 'readonly': False, 'value': {"users": {"2": 'b'}}}, + } + }, + }, + 'connections': [], + } + workspace.save() + + client = Client() + client.login(username='test', password='test') + + data = json.dumps({ + 'operators': { + '1': { + 'id': '1', + 'name': 'Wirecloud/TestOperatorMultiuser/1.0', + 'preferences': { + 'pref1': {'hidden': True, 'readonly': False, 'value': 'default'}, + }, + 'properties': { + 'prop1': {'hidden': True, 'readonly': False, 'value': 'a'}, + 'prop2': {'hidden': True, 'readonly': False, 'value': 'b'}, + } + }, + }, + 'connections': [], + }) + response = client.put(self.wiring_url, data, content_type='application/json') + self.assertEqual(response.status_code, 204) + workspace = Workspace.objects.get(id=self.workspace_id) + self.assertEqual(workspace.wiringStatus["operators"]["1"]["properties"]["prop1"]["value"], {"users": {"2": "a"}}) + + def test_save_wiring_by_allowed_users(self): + workspace = Workspace.objects.get(id=self.workspace_id) + workspace.public = False + workspace.wiringStatus = { + 'operators': { + '1': { + 'id': '1', + 'name': 'Wirecloud/TestOperatorMultiuser/1.0', + 'preferences': { + 'pref1': {'hidden': True, 'readonly': False, 'value': {"users": {"2": 'default'}}}, + }, + 'properties': { + 'prop1': {'hidden': True, 'readonly': False, 'value': {"users": {"2": 'a'}}}, + 'prop2': {'hidden': True, 'readonly': False, 'value': {"users": {"2": 'b'}}}, + } + }, + }, + 'connections': [], + } + workspace.save() + + client = Client() + client.login(username='test2', password='test') + + data = json.dumps({ + 'operators': { + '1': { + 'id': '1', + 'name': 'Wirecloud/TestOperatorMultiuser/1.0', + 'preferences': { + 'pref1': {'hidden': True, 'readonly': False, 'value': 'default'}, + }, + 'properties': { + 'prop1': {'hidden': True, 'readonly': False, 'value': 'a'}, + 'prop2': {'hidden': True, 'readonly': False, 'value': 'b'}, + } + }, + }, + 'connections': [], + }) + response = client.put(self.wiring_url, data, content_type='application/json') + self.assertEqual(response.status_code, 204) + workspace = Workspace.objects.get(id=self.workspace_id) + self.assertEqual(workspace.wiringStatus["operators"]["1"]["properties"]["prop1"]["value"], {"users": {"2": "a", "3": "a"}}) + self.assertEqual(workspace.wiringStatus["operators"]["1"]["properties"]["prop2"]["value"], {"users": {"2": "b"}}) + + def test_save_wiring_nonexistent_operator_by_allowed_users(self): + workspace = Workspace.objects.get(id=self.workspace_id) + workspace.public = False + workspace.wiringStatus = { + 'operators': { + '1': { + 'id': '1', + 'name': 'Wirecloud/TestRandomOperator/1.0', + 'preferences': { + 'pref1': {'hidden': True, 'readonly': False, 'value': {"users": {"2": 'default'}}}, + }, + 'properties': { + 'prop1': {'hidden': True, 'readonly': False, 'value': {"users": {"2": 'a'}}}, + 'prop2': {'hidden': True, 'readonly': False, 'value': {"users": {"2": 'b'}}}, + } + }, + }, + 'connections': [], + } + workspace.save() + + client = Client() + client.login(username='test2', password='test') + + data = json.dumps({ + 'operators': { + '1': { + 'id': '1', + 'name': 'Wirecloud/TestRandomOperator/1.0', + 'preferences': { + 'pref1': {'hidden': True, 'readonly': False, 'value': 'default'}, + }, + 'properties': { + 'prop1': {'hidden': True, 'readonly': False, 'value': 'a'}, + 'prop2': {'hidden': True, 'readonly': False, 'value': 'b'}, + } + }, + }, + 'connections': [], + }) + response = client.put(self.wiring_url, data, content_type='application/json') + self.assertEqual(response.status_code, 204) + workspace = Workspace.objects.get(id=self.workspace_id) + self.assertEqual(workspace.wiringStatus["operators"]["1"]["properties"]["prop1"]["value"], {"users": {"2": "a"}}) + def test_nonmultiuser_properties_cannot_be_updated_by_allowed_users(self): workspace = Workspace.objects.get(id=self.workspace_id) workspace.public = False @@ -1024,7 +1347,7 @@ def test_non_owners_cannot_modify_wiring_elements(self): 'id': '1', 'name': 'Wirecloud/TestOperatorMultiuser/1.0', 'preferences': { - 'pref1': {'hidden': True, 'readonly': False, 'value': {"users": {"2": 'b'}}}, + 'pref2': {'hidden': True, 'readonly': False, 'value': {"users": {"2": 'b'}}}, }, 'properties': { } @@ -1043,7 +1366,7 @@ def test_non_owners_cannot_modify_wiring_elements(self): 'id': '1', 'name': 'Wirecloud/TestOperatorMultiuser/1.0', 'preferences': { - 'pref1': {'hidden': True, 'readonly': True, 'value': 'b'}, + 'pref2': {'hidden': True, 'readonly': True, 'value': 'b'}, }, 'properties': {} }, @@ -1053,7 +1376,7 @@ def test_non_owners_cannot_modify_wiring_elements(self): response = client.put(self.wiring_url, data, content_type='application/json') self.assertEqual(response.status_code, 403) workspace = Workspace.objects.get(id=self.workspace_id) - self.assertEqual(workspace.wiringStatus["operators"]["1"]["preferences"]["pref1"]["value"], {"users": {"2": "b"}}) + self.assertEqual(workspace.wiringStatus["operators"]["1"]["preferences"]["pref2"]["value"], {"users": {"2": "b"}}) def test_multiuser_properties_cannot_be_updated_by_other(self): workspace = Workspace.objects.get(id=self.workspace_id) @@ -1116,7 +1439,7 @@ def test_operator_added_put(self): response = client.put(self.wiring_url, data, content_type='application/json') self.assertEqual(response.status_code, 204) - def test_operator_added_patch(self): + def test_operator_added_patch_owner(self): client = Client() client.login(username='test', password='test') @@ -1138,6 +1461,50 @@ def test_operator_added_patch(self): response = client.patch(self.wiring_url, data, content_type='application/json-patch+json') self.assertEqual(response.status_code, 204) + def test_operator_added_patch_allowed(self): + client = Client() + client.login(username='test2', password='test') + + data = json.dumps([ + { + 'op': "add", + 'path': "/operators/1", + 'value': { + 'id': '1', + 'name': 'Wirecloud/TestOperator/1.0', + 'preferences': { + 'pref1': {'hidden': False, 'readonly': False, 'value': 'b'}, + 'pref2': {'hidden': False, 'readonly': False, 'value': 'c'}, + }, + 'properties': {} + }, + } + ]) + response = client.patch(self.wiring_url, data, content_type='application/json-patch+json') + self.assertEqual(response.status_code, 403) + + def test_operator_added_patch_other(self): + client = Client() + client.login(username='test3', password='test') + + data = json.dumps([ + { + 'op': "add", + 'path': "/operators/1", + 'value': { + 'id': '1', + 'name': 'Wirecloud/TestOperator/1.0', + 'preferences': { + 'pref1': {'hidden': False, 'readonly': False, 'value': 'b'}, + 'pref2': {'hidden': False, 'readonly': False, 'value': 'c'}, + }, + 'properties': {} + }, + } + ]) + response = client.patch(self.wiring_url, data, content_type='application/json-patch+json') + self.assertEqual(response.status_code, 403) + def test_iwidget_removed(self): workspace = Workspace.objects.get(id=self.workspace_id) From 65fb09925115dd945151dd84ca111ea705ef847f Mon Sep 17 00:00:00 2001 From: Alejandro Rodriguez Date: Fri, 17 Feb 2017 11:08:45 +0100 Subject: [PATCH 27/28] Add more operator variables tests --- .../platform/fixtures/test_data.json | 2 +- src/wirecloud/platform/wiring/tests.py | 260 +++++++++++++++++- src/wirecloud/platform/wiring/views.py | 11 +- src/wirecloud/platform/workspace/tests.py | 37 ++- 4 files changed, 295 insertions(+), 15 deletions(-) diff --git a/src/wirecloud/platform/fixtures/test_data.json b/src/wirecloud/platform/fixtures/test_data.json index 7f42e976c4..6b56735ee3 100644 --- a/src/wirecloud/platform/fixtures/test_data.json +++ b/src/wirecloud/platform/fixtures/test_data.json @@ -254,7 +254,7 @@ "popularity": "0", "vendor": "Wirecloud", "short_name": "TestOperatorMultiuser", - "json_description": "{\"default_lang\": \"en\", \"vendor\": \"Wirecloud\", \"description\": \"Test operator description\", \"translations\": {}, \"smartphoneimage\": \"\", \"translation_index_usage\": {}, \"title\": \"TestOperator\", \"properties\": [{\"default\": \"\",\"multiuser\": true, \"name\": \"prop1\", \"secure\": false, \"label\": \"Prefix\", \"type\": \"text\", \"description\": \"\"}], \"js_files\": [\"js/main.js\"], \"requirements\": [], \"preferences\": [{\"default\": \"\",\"multiuser\": false, \"name\": \"pref1\", \"secure\": false, \"label\": \"Prefix\", \"type\": \"text\", \"description\": \"\"}], \"authors\": \"admin\", \"wiring\": {\"inputs\": [{\"friendcode\": \"test_friend_code\", \"actionlabel\": \"\", \"name\": \"input\", \"label\": \"input\", \"type\": \"text\", \"description\": \"\"}], \"outputs\": [{\"friendcode\": \"test_friend_code\", \"description\": \"\", \"type\": \"text\", \"name\": \"output\", \"label\": \"output\"}]}, \"name\": \"TestOperator\", \"image\": \"images/catalogue.png\", \"version\": \"1.0\", \"context\": [], \"widget_height\": \"\", \"email\": \"test@example.com\", \"type\": \"operator\", \"widget_width\": \"\", \"doc\": \"doc/index.html\"}", + "json_description": "{\"default_lang\": \"en\", \"vendor\": \"Wirecloud\", \"description\": \"Test operator description\", \"translations\": {}, \"smartphoneimage\": \"\", \"translation_index_usage\": {}, \"title\": \"TestOperator\", \"properties\": [{\"default\": \"\",\"multiuser\": true, \"name\": \"prop1\", \"secure\": false, \"label\": \"Prefix\", \"type\": \"text\", \"description\": \"\"}, {\"default\": \"\",\"multiuser\": true, \"name\": \"prop3\", \"secure\": true, \"label\": \"Prefix\", \"type\": \"text\", \"description\": \"\"}], \"js_files\": [\"js/main.js\"], \"requirements\": [], \"preferences\": [{\"default\": \"\",\"multiuser\": false, \"name\": \"pref1\", \"secure\": false, \"label\": \"Prefix\", \"type\": \"text\", \"description\": \"\"}, {\"default\": \"\",\"multiuser\": false, \"name\": \"pref2\", \"secure\": true, \"label\": \"Prefix\", \"type\": \"text\", \"description\": \"\"}], \"authors\": \"admin\", \"wiring\": {\"inputs\": [{\"friendcode\": \"test_friend_code\", \"actionlabel\": \"\", \"name\": \"input\", \"label\": \"input\", \"type\": \"text\", \"description\": \"\"}], \"outputs\": [{\"friendcode\": \"test_friend_code\", \"description\": \"\", \"type\": \"text\", \"name\": \"output\", \"label\": \"output\"}]}, \"name\": \"TestOperator\", \"image\": \"images/catalogue.png\", \"version\": \"1.0\", \"context\": [], \"widget_height\": \"\", \"email\": \"test@example.com\", \"type\": \"operator\", \"widget_width\": \"\", \"doc\": \"doc/index.html\"}", "creator": 2, "creation_date": "2011-05-13T11:24:03Z", "version": "1.0", diff --git a/src/wirecloud/platform/wiring/tests.py b/src/wirecloud/platform/wiring/tests.py index a472a34f21..06c8c5c94c 100644 --- a/src/wirecloud/platform/wiring/tests.py +++ b/src/wirecloud/platform/wiring/tests.py @@ -1006,6 +1006,230 @@ def test_multiuser_properties_can_be_updated_by_allowed_users(self): workspace = Workspace.objects.get(id=self.workspace_id) self.assertEqual(workspace.wiringStatus["operators"]["1"]["properties"]["prop1"]["value"], {"users": {"2": "a", "3": "b"}}) + def test_multiuser_secure_properties_can_be_updated_by_owner_patch(self): + workspace = Workspace.objects.get(id=self.workspace_id) + workspace.public = False + workspace.wiringStatus = { + 'operators': { + '1': { + 'id': '1', + 'name': 'Wirecloud/TestOperatorMultiuser/1.0', + 'preferences': {}, + 'properties': { + 'prop3': {'hidden': True, 'readonly': False, 'value': {"users": {"2": 'a'}}}, + } + }, + }, + 'connections': [], + } + workspace.save() + + client = Client() + client.login(username='test', password='test') + + data = json.dumps([ + { + 'op': "replace", + 'path': "/operators/1/properties/prop3/value", + 'value': "c" + } + ]) + + response = client.patch(self.wiring_url, data, content_type='application/json-patch+json') + self.assertEqual(response.status_code, 204) + workspace = Workspace.objects.get(id=self.workspace_id) + self.assertEqual(workspace.wiringStatus["operators"]["1"]["properties"]["prop3"]["value"], {"users": {"2": "c"}}) + + def test_multiuser_secure_properties_can_be_updated_by_allowed_users_patch(self): + workspace = Workspace.objects.get(id=self.workspace_id) + workspace.public = False + workspace.wiringStatus = { + 'operators': { + '1': { + 'id': '1', + 'name': 'Wirecloud/TestOperatorMultiuser/1.0', + 'preferences': {}, + 'properties': { + 'prop3': {'hidden': True, 'readonly': False, 'value': {"users": {"3": 'a'}}}, + } + }, + }, + 'connections': [], + } + workspace.save() + + client = Client() + client.login(username='test2', password='test') + + data = json.dumps([ + { + 'op': "replace", + 'path': "/operators/1/properties/prop3/value", + 'value': "c" + } + ]) + + response = client.patch(self.wiring_url, data, content_type='application/json-patch+json') + self.assertEqual(response.status_code, 204) + workspace = Workspace.objects.get(id=self.workspace_id) + self.assertEqual(workspace.wiringStatus["operators"]["1"]["properties"]["prop3"]["value"], {"users": {"3": "c"}}) + + def test_multiuser_secure_properties_cannot_be_updated_by_owner_put(self): + workspace = Workspace.objects.get(id=self.workspace_id) + workspace.public = False + workspace.wiringStatus = { + 'operators': { + '1': { + 'id': '1', + 'name': 'Wirecloud/TestOperatorMultiuser/1.0', + 'preferences': {}, + 'properties': { + 'prop3': {'hidden': True, 'readonly': False, 'value': {"users": {"2": 'a', "3": 'a'}}}, + } + }, + }, + 'connections': [], + } + workspace.save() + + client = Client() + client.login(username='test', password='test') + + data = json.dumps({ + 'operators': { + '1': { + 'id': '1', + 'name': 'Wirecloud/TestOperatorMultiuser/1.0', + 'preferences': {}, + 'properties': { + 'prop3': {'hidden': True, 'readonly': False, 'value': 'c'}, + } + }, + }, + 'connections': [], + }) + response = client.put(self.wiring_url, data, content_type='application/json') + self.assertEqual(response.status_code, 204) + workspace = Workspace.objects.get(id=self.workspace_id) + self.assertEqual(workspace.wiringStatus["operators"]["1"]["properties"]["prop3"]["value"], {"users": {"2": "a", "3": "a"}}) + + def test_multiuser_secure_properties_cannot_be_updated_by_allowed_users_put(self): + workspace = Workspace.objects.get(id=self.workspace_id) + workspace.public = False + workspace.wiringStatus = { + 'operators': { + '1': { + 'id': '1', + 'name': 'Wirecloud/TestOperatorMultiuser/1.0', + 'preferences': {}, + 'properties': { + 'prop3': {'hidden': True, 'readonly': False, 'value': {"users": {"2": 'a', "3": 'a'}}}, + } + }, + }, + 'connections': [], + } + workspace.save() + + client = Client() + client.login(username='test2', password='test') + + data = json.dumps({ + 'operators': { + '1': { + 'id': '1', + 'name': 'Wirecloud/TestOperatorMultiuser/1.0', + 'preferences': {}, + 'properties': { + 'prop3': {'hidden': True, 'readonly': False, 'value': 'c'}, + } + }, + }, + 'connections': [], + }) + response = client.put(self.wiring_url, data, content_type='application/json') + self.assertEqual(response.status_code, 204) + workspace = Workspace.objects.get(id=self.workspace_id) + self.assertEqual(workspace.wiringStatus["operators"]["1"]["properties"]["prop3"]["value"], {"users": {"2": "a", "3": "a"}}) + + def test_multiuser_secure_preferences_not_updated_by_owner_put(self): + workspace = Workspace.objects.get(id=self.workspace_id) + workspace.public = False + workspace.wiringStatus = { + 'operators': { + '1': { + 'id': '1', + 'name': 'Wirecloud/TestOperatorMultiuser/1.0', + 'preferences': { + 'pref2': {'hidden': True, 'readonly': False, 'value': {"users": {"2": 'a'}}} + }, + 'properties': {} + }, + }, + 'connections': [], + } + workspace.save() + + client = Client() + client.login(username='test', password='test') + + data = json.dumps({ + 'operators': { + '1': { + 'id': '1', + 'name': 'Wirecloud/TestOperatorMultiuser/1.0', + 'preferences': { + 'pref2': {'hidden': True, 'readonly': False, 'value': 'b'} + }, + 'properties': {} + }, + }, + 'connections': [], + }) + response = client.put(self.wiring_url, data, content_type='application/json') + self.assertEqual(response.status_code, 204) + workspace = Workspace.objects.get(id=self.workspace_id) + self.assertEqual(workspace.wiringStatus["operators"]["1"]["preferences"]["pref2"]["value"], {"users": {"2": "a"}}) + + def test_multiuser_secure_preferences_not_updated_by_allowed_users_put(self): + workspace = Workspace.objects.get(id=self.workspace_id) + workspace.public = False + workspace.wiringStatus = { + 'operators': { + '1': { + 'id': '1', + 'name': 'Wirecloud/TestOperatorMultiuser/1.0', + 'preferences': { + 'pref2': {'hidden': True, 'readonly': False, 'value': {"users": {"2": 'a'}}} + }, + 'properties': {} + }, + }, + 'connections': [], + } + workspace.save() + + client = Client() + client.login(username='test2', password='test') + + data = json.dumps({ + 'operators': { + '1': { + 'id': '1', + 'name': 'Wirecloud/TestOperatorMultiuser/1.0', + 'preferences': { + 'pref2': {'hidden': True, 'readonly': False, 'value': 'b'} + }, + 'properties': {} + }, + }, + 'connections': [], + }) + response = client.put(self.wiring_url, data, content_type='application/json') + self.assertEqual(response.status_code, 204) + workspace = Workspace.objects.get(id=self.workspace_id) + self.assertEqual(workspace.wiringStatus["operators"]["1"]["preferences"]["pref2"]["value"], {"users": {"2": "a"}}) + def test_nonexistent_operator_properties_cannot_be_updated_by_allowed_users(self): workspace = Workspace.objects.get(id=self.workspace_id) workspace.public = False @@ -1378,7 +1602,7 @@ def test_non_owners_cannot_modify_wiring_elements(self): workspace = Workspace.objects.get(id=self.workspace_id) self.assertEqual(workspace.wiringStatus["operators"]["1"]["preferences"]["pref2"]["value"], {"users": {"2": "b"}}) - def test_multiuser_properties_cannot_be_updated_by_other(self): + def test_multiuser_properties_cannot_be_updated_by_other_put(self): workspace = Workspace.objects.get(id=self.workspace_id) workspace.public = False workspace.wiringStatus = { @@ -1417,6 +1641,40 @@ def test_multiuser_properties_cannot_be_updated_by_other(self): workspace = Workspace.objects.get(id=self.workspace_id) self.assertEqual(workspace.wiringStatus["operators"]["1"]["properties"]["prop1"]["value"], {"users": {"2": "a"}}) + def test_multiuser_properties_cannot_be_updated_by_other_patch(self): + workspace = Workspace.objects.get(id=self.workspace_id) + workspace.public = False + workspace.wiringStatus = { + 'operators': { + '1': { + 'id': '1', + 'name': 'Wirecloud/TestOperatorMultiuser/1.0', + 'preferences': {}, + 'properties': { + 'prop1': {'hidden': True, 'readonly': False, 'value': {"users": {"2": 'a'}}} + } + }, + }, + 'connections': [], + } + workspace.save() + + client = Client() + client.login(username='test3', password='test') + + data = json.dumps([ + { + 'op': "replace", + 'path': "/operators/1/properties/prop1/value", + 'value': "c" + } + ]) + + response = client.patch(self.wiring_url, data, content_type='application/json-patch+json') + self.assertEqual(response.status_code, 403) + workspace = Workspace.objects.get(id=self.workspace_id) + self.assertEqual(workspace.wiringStatus["operators"]["1"]["properties"]["prop1"]["value"], {"users": {"2": "a"}}) + def test_operator_added_put(self): client = Client() diff --git a/src/wirecloud/platform/wiring/views.py b/src/wirecloud/platform/wiring/views.py index afe04df9d6..4d07e45d5c 100644 --- a/src/wirecloud/platform/wiring/views.py +++ b/src/wirecloud/platform/wiring/views.py @@ -36,17 +36,16 @@ class WiringEntry(Resource): + # Build multiuser structure with the new value, keeping the other users values def handleMultiuser(self, request, new_variable, old_variable): new_value = new_variable["value"] - if old_variable is not None: - new_variable = deepcopy(old_variable) - else: - new_variable["value"] = {"users": {}} + new_variable = deepcopy(old_variable) new_variable["value"]["users"]["%s" % request.user.id] = new_value return new_variable + # Checks if two objects are the same def checkSameWiring(self, object1, object2): if len(object1.keys()) != len(object2.keys()): return False @@ -205,7 +204,7 @@ def checkWiring(self, request, new_wiring_status, old_wiring_status, can_update_ if preference_secure and not can_update_secure: new_preference["value"] = old_preference["value"] - elif new_preference["value"] != old_preference["value"]: + else: # Handle multiuser new_preference = self.handleMultiuser(request, new_preference, old_preference) operator['preferences'][preference_name] = new_preference @@ -242,7 +241,7 @@ def checkWiring(self, request, new_wiring_status, old_wiring_status, can_update_ if property_secure and not can_update_secure: new_property["value"] = old_property["value"] - elif new_property["value"] != old_property["value"]: + else: # Handle multiuser new_property = self.handleMultiuser(request, new_property, old_property) operator['properties'][property_name] = new_property diff --git a/src/wirecloud/platform/workspace/tests.py b/src/wirecloud/platform/workspace/tests.py index c0d869ef69..f603156d37 100644 --- a/src/wirecloud/platform/workspace/tests.py +++ b/src/wirecloud/platform/workspace/tests.py @@ -95,9 +95,9 @@ def test_add_multiuser_support_forward(self): self.assertEqual(widget2_mock.variables, {"varname": {"users": {"2": "hello"}}, "varname2": {"users": {"2": "world"}}}) self.assertEqual(widget3_mock.variables, {"varname": {"users": {"2": "varvalue"}}, "varname2": {"users": {"2": "varvalue2"}}}) - self.assertEqual(workspace_mock.wiringStatus, {"operators": - {"1": {"preferences": {"varname": {"value": {"users": {"2": "varvalue"}}}, "varname2": {"value": {"users": {"2": "varvalue2"}}}}}, - "2": {"preferences": {"varname": {"value": {"users": {"2": "hello"}}}, "varname2": {"value": {"users": {"2": "world"}}}}}}}) + self.assertEqual(workspace_mock.wiringStatus, {"operators": + {"1": {"preferences": {"varname": {"value": {"users": {"2": "varvalue"}}}, "varname2": {"value": {"users": {"2": "varvalue2"}}}}}, + "2": {"preferences": {"varname": {"value": {"users": {"2": "hello"}}}, "varname2": {"value": {"users": {"2": "world"}}}}}}}) def test_add_multiuser_support_backward_empty(self): @@ -149,7 +149,6 @@ def test_add_multiuser_support_backward(self): apps_mock.get_model("platform", "workspace").objects.select_related('creator').all.return_value = [workspace_mock] apps_mock.get_model("catalogue", "CatalogueResource").objects.filter(type__in=(0, 2)).all.return_value = [] - multiuser_variables_structure_backwards(apps_mock, None) widget1_mock.save.assert_called_with() @@ -157,9 +156,10 @@ def test_add_multiuser_support_backward(self): self.assertEqual(widget2_mock.variables, {"varname": "hello", "varname2": "world"}) self.assertEqual(widget3_mock.variables, {"varname": "varvalue", "varname2": "varvalue2"}) - self.assertEqual(workspace_mock.wiringStatus, {"operators": - {"1": {"preferences": {"varname": {"value": "varvalue"}, "varname2": {"value": "varvalue2"}}}, - "2": {"preferences": {"varname": {"value": "hello"}, "varname2": {"value": "world"}}}}}) + self.assertEqual(workspace_mock.wiringStatus, {"operators": + {"1": {"preferences": {"varname": {"value": "varvalue"}, "varname2": {"value": "varvalue2"}}}, + "2": {"preferences": {"varname": {"value": "hello"}, "varname2": {"value": "world"}}}}}) + class WorkspaceTestCase(WirecloudTestCase): @@ -185,6 +185,29 @@ def test_get_global_workspace_data(self): properties = tab['iwidgets'][0]['properties'] self.assertEqual(properties['prop']['value'], 'test_data') + def test_get_global_workspace_data_harvest_operator_properties(self): + workspace = Workspace.objects.get(id=1) + workspace.wiringStatus = { + 'operators': { + '1': { + 'id': '1', + 'name': 'Wirecloud/TestOperatorMultiuser/1.0', + 'preferences': { + 'pref1': {'hidden': True, 'readonly': False, 'value': {"users": {"2": 'default'}}} + }, + 'properties': { + 'prop1': {'hidden': True, 'readonly': False, 'value': {"users": {"2": 'a'}}}, + 'prop3': {'hidden': True, 'readonly': False, 'value': {"users": {"2": 'a'}}}, + } + }, + }, + 'connections': [], + } + data = json.loads(get_global_workspace_data(workspace, self.user).get_data()) + + self.assertEqual(data["wiring"]["operators"]["1"]["properties"]["prop1"]["value"], "a") + self.assertEqual(data["wiring"]["operators"]["1"]["properties"]["prop3"]["value"], "********") + def test_secure_preferences_censor(self): workspace = Workspace.objects.get(pk=202) data = json.loads(get_global_workspace_data(workspace, self.user).get_data()) From a5b7f192c36f6037b9703e50ae3531d6f96b91da Mon Sep 17 00:00:00 2001 From: Alejandro Rodriguez Date: Fri, 17 Feb 2017 11:56:16 +0100 Subject: [PATCH 28/28] Rename tests --- src/wirecloud/platform/wiring/tests.py | 30 +++++++++++++------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/wirecloud/platform/wiring/tests.py b/src/wirecloud/platform/wiring/tests.py index 06c8c5c94c..0f4069dacb 100644 --- a/src/wirecloud/platform/wiring/tests.py +++ b/src/wirecloud/platform/wiring/tests.py @@ -920,7 +920,7 @@ def test_multiuser_properties_can_be_updated_by_owner(self): workspace = Workspace.objects.get(id=self.workspace_id) self.assertEqual(workspace.wiringStatus["operators"]["1"]["properties"]["prop1"]["value"], {"users": {"2": "b"}}) - def test_non_existent_oeprators_can_be_created_by_owner(self): + def test_missing_operators_are_maintained_on_owner_requests(self): workspace = Workspace.objects.get(id=self.workspace_id) workspace.public = False workspace.wiringStatus = { @@ -1006,7 +1006,7 @@ def test_multiuser_properties_can_be_updated_by_allowed_users(self): workspace = Workspace.objects.get(id=self.workspace_id) self.assertEqual(workspace.wiringStatus["operators"]["1"]["properties"]["prop1"]["value"], {"users": {"2": "a", "3": "b"}}) - def test_multiuser_secure_properties_can_be_updated_by_owner_patch(self): + def test_multiuser_secure_properties_can_be_updated_by_owner_patches(self): workspace = Workspace.objects.get(id=self.workspace_id) workspace.public = False workspace.wiringStatus = { @@ -1040,7 +1040,7 @@ def test_multiuser_secure_properties_can_be_updated_by_owner_patch(self): workspace = Workspace.objects.get(id=self.workspace_id) self.assertEqual(workspace.wiringStatus["operators"]["1"]["properties"]["prop3"]["value"], {"users": {"2": "c"}}) - def test_multiuser_secure_properties_can_be_updated_by_allowed_users_patch(self): + def test_multiuser_secure_properties_can_be_updated_by_allowed_user_patches(self): workspace = Workspace.objects.get(id=self.workspace_id) workspace.public = False workspace.wiringStatus = { @@ -1074,7 +1074,7 @@ def test_multiuser_secure_properties_can_be_updated_by_allowed_users_patch(self) workspace = Workspace.objects.get(id=self.workspace_id) self.assertEqual(workspace.wiringStatus["operators"]["1"]["properties"]["prop3"]["value"], {"users": {"3": "c"}}) - def test_multiuser_secure_properties_cannot_be_updated_by_owner_put(self): + def test_multiuser_secure_properties_are_not_updated_by_owner_puts(self): workspace = Workspace.objects.get(id=self.workspace_id) workspace.public = False workspace.wiringStatus = { @@ -1113,7 +1113,7 @@ def test_multiuser_secure_properties_cannot_be_updated_by_owner_put(self): workspace = Workspace.objects.get(id=self.workspace_id) self.assertEqual(workspace.wiringStatus["operators"]["1"]["properties"]["prop3"]["value"], {"users": {"2": "a", "3": "a"}}) - def test_multiuser_secure_properties_cannot_be_updated_by_allowed_users_put(self): + def test_multiuser_secure_properties_are_not_updated_by_allowed_user_puts(self): workspace = Workspace.objects.get(id=self.workspace_id) workspace.public = False workspace.wiringStatus = { @@ -1152,7 +1152,7 @@ def test_multiuser_secure_properties_cannot_be_updated_by_allowed_users_put(self workspace = Workspace.objects.get(id=self.workspace_id) self.assertEqual(workspace.wiringStatus["operators"]["1"]["properties"]["prop3"]["value"], {"users": {"2": "a", "3": "a"}}) - def test_multiuser_secure_preferences_not_updated_by_owner_put(self): + def test_multiuser_secure_preferences_are_not_updated_by_owner_puts(self): workspace = Workspace.objects.get(id=self.workspace_id) workspace.public = False workspace.wiringStatus = { @@ -1191,7 +1191,7 @@ def test_multiuser_secure_preferences_not_updated_by_owner_put(self): workspace = Workspace.objects.get(id=self.workspace_id) self.assertEqual(workspace.wiringStatus["operators"]["1"]["preferences"]["pref2"]["value"], {"users": {"2": "a"}}) - def test_multiuser_secure_preferences_not_updated_by_allowed_users_put(self): + def test_multiuser_secure_preferences_are_not_updated_by_allowed_user_puts(self): workspace = Workspace.objects.get(id=self.workspace_id) workspace.public = False workspace.wiringStatus = { @@ -1230,7 +1230,7 @@ def test_multiuser_secure_preferences_not_updated_by_allowed_users_put(self): workspace = Workspace.objects.get(id=self.workspace_id) self.assertEqual(workspace.wiringStatus["operators"]["1"]["preferences"]["pref2"]["value"], {"users": {"2": "a"}}) - def test_nonexistent_operator_properties_cannot_be_updated_by_allowed_users(self): + def test_missing_operator_properties_cannot_be_updated_by_allowed_users(self): workspace = Workspace.objects.get(id=self.workspace_id) workspace.public = False workspace.wiringStatus = { @@ -1364,7 +1364,7 @@ def test_save_wiring_by_allowed_users(self): self.assertEqual(workspace.wiringStatus["operators"]["1"]["properties"]["prop1"]["value"], {"users": {"2": "a", "3": "a"}}) self.assertEqual(workspace.wiringStatus["operators"]["1"]["properties"]["prop2"]["value"], {"users": {"2": "b"}}) - def test_save_wiring_nonexistent_operator_by_allowed_users(self): + def test_save_wiring_with_missing_operator_by_allowed_users(self): workspace = Workspace.objects.get(id=self.workspace_id) workspace.public = False workspace.wiringStatus = { @@ -1409,7 +1409,7 @@ def test_save_wiring_nonexistent_operator_by_allowed_users(self): workspace = Workspace.objects.get(id=self.workspace_id) self.assertEqual(workspace.wiringStatus["operators"]["1"]["properties"]["prop1"]["value"], {"users": {"2": "a"}}) - def test_nonmultiuser_properties_cannot_be_updated_by_allowed_users(self): + def test_normal_properties_cannot_be_updated_by_allowed_users(self): workspace = Workspace.objects.get(id=self.workspace_id) workspace.public = False workspace.wiringStatus = { @@ -1602,7 +1602,7 @@ def test_non_owners_cannot_modify_wiring_elements(self): workspace = Workspace.objects.get(id=self.workspace_id) self.assertEqual(workspace.wiringStatus["operators"]["1"]["preferences"]["pref2"]["value"], {"users": {"2": "b"}}) - def test_multiuser_properties_cannot_be_updated_by_other_put(self): + def test_unauthorized_users_cannot_update_wiring_with_puts(self): workspace = Workspace.objects.get(id=self.workspace_id) workspace.public = False workspace.wiringStatus = { @@ -1641,7 +1641,7 @@ def test_multiuser_properties_cannot_be_updated_by_other_put(self): workspace = Workspace.objects.get(id=self.workspace_id) self.assertEqual(workspace.wiringStatus["operators"]["1"]["properties"]["prop1"]["value"], {"users": {"2": "a"}}) - def test_multiuser_properties_cannot_be_updated_by_other_patch(self): + def test_unauthorized_users_cannot_update_wiring_with_patches(self): workspace = Workspace.objects.get(id=self.workspace_id) workspace.public = False workspace.wiringStatus = { @@ -1697,7 +1697,7 @@ def test_operator_added_put(self): response = client.put(self.wiring_url, data, content_type='application/json') self.assertEqual(response.status_code, 204) - def test_operator_added_patch_owner(self): + def test_operator_added_patch_by_owner(self): client = Client() client.login(username='test', password='test') @@ -1719,7 +1719,7 @@ def test_operator_added_patch_owner(self): response = client.patch(self.wiring_url, data, content_type='application/json-patch+json') self.assertEqual(response.status_code, 204) - def test_operator_added_patch_allowed(self): + def test_operator_added_patch_by_allowed_user(self): client = Client() client.login(username='test2', password='test') @@ -1741,7 +1741,7 @@ def test_operator_added_patch_allowed(self): response = client.patch(self.wiring_url, data, content_type='application/json-patch+json') self.assertEqual(response.status_code, 403) - def test_operator_added_patch_other(self): + def test_operator_added_patch_unauthorized_user(self): client = Client() client.login(username='test3', password='test')