From 7780d76eb6f840c40f1ef95b9fa45c4b47bb48a2 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Thu, 24 Oct 2019 17:31:53 +0200 Subject: [PATCH 1/8] Intiial work on 'Singing of Plugins and Material Packages'. Most of the work is in Uranium right now -- especially since the plugins part is picked up first, and there are already plugins at Uraniums level. part of CURA-6856 --- cura/ApplicationMetadata.py | 5 +++++ cura/CuraApplication.py | 2 ++ 2 files changed, 7 insertions(+) diff --git a/cura/ApplicationMetadata.py b/cura/ApplicationMetadata.py index daa937197c..25be4b31e9 100644 --- a/cura/ApplicationMetadata.py +++ b/cura/ApplicationMetadata.py @@ -42,6 +42,11 @@ try: except ImportError: CuraDebugMode = DEFAULT_CURA_DEBUG_MODE +try: + from cura.CuraVersion import CuraIsEnterpriseVersion # type: ignore +except ImportError: + CuraIsEnterpriseVersion = True # (DEFAULT_CURA_BUILD_TYPE != "") + # Each release has a fixed SDK version coupled with it. It doesn't make sense to make it configurable because, for # example Cura 3.2 with SDK version 6.1 will not work. So the SDK version is hard-coded here and left out of the # CuraVersion.py.in template. diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 8caccc786e..9c2fd1b931 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -719,6 +719,8 @@ class CuraApplication(QtApplication): ## Handle loading of all plugin types (and the backend explicitly) # \sa PluginRegistry def _loadPlugins(self) -> None: + self._plugin_registry.setCheckIfTrusted(ApplicationMetadata.CuraIsEnterpriseVersion) + self._plugin_registry.addType("profile_reader", self._addProfileReader) self._plugin_registry.addType("profile_writer", self._addProfileWriter) From 298eb27c7f4a2440c4b5f811f44a6ea3a4e26800 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Tue, 29 Oct 2019 17:45:19 +0100 Subject: [PATCH 2/8] Add possibility to check material-profiles. Needed to add the filename to deserialize, feels a bit unsafe as an optional parameter, will discuss tomorrow. part of CURA-6856 --- cura/Settings/ContainerManager.py | 2 +- plugins/LegacyProfileReader/LegacyProfileReader.py | 2 +- plugins/XmlMaterialProfile/XmlMaterialProfile.py | 14 +++++++++++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/cura/Settings/ContainerManager.py b/cura/Settings/ContainerManager.py index 4a4a7b64dd..0daf5305c4 100644 --- a/cura/Settings/ContainerManager.py +++ b/cura/Settings/ContainerManager.py @@ -247,7 +247,7 @@ class ContainerManager(QObject): try: with open(file_url, "rt", encoding = "utf-8") as f: - container.deserialize(f.read()) + container.deserialize(f.read(), file_url) except PermissionError: return {"status": "error", "message": "Permission denied when trying to read the file."} except ContainerFormatError: diff --git a/plugins/LegacyProfileReader/LegacyProfileReader.py b/plugins/LegacyProfileReader/LegacyProfileReader.py index 013bab6f11..87b26eb4ec 100644 --- a/plugins/LegacyProfileReader/LegacyProfileReader.py +++ b/plugins/LegacyProfileReader/LegacyProfileReader.py @@ -157,7 +157,7 @@ class LegacyProfileReader(ProfileReader): data = stream.getvalue() profile = InstanceContainer(profile_id) - profile.deserialize(data) # Also performs the version upgrade. + profile.deserialize(data, file_name) # Also performs the version upgrade. profile.setDirty(True) #We need to return one extruder stack and one global stack. diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py index 093638d594..752f17feb4 100644 --- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py +++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py @@ -15,8 +15,9 @@ import UM.Dictionary from UM.Settings.InstanceContainer import InstanceContainer from UM.Settings.ContainerRegistry import ContainerRegistry from UM.ConfigurationErrorMessage import ConfigurationErrorMessage +from UM.Trust import Trust -from cura.CuraApplication import CuraApplication +from cura.CuraApplication import ApplicationMetadata, CuraApplication from cura.Machines.ContainerTree import ContainerTree from cura.Machines.VariantType import VariantType @@ -470,6 +471,17 @@ class XmlMaterialProfile(InstanceContainer): ## Overridden from InstanceContainer def deserialize(self, serialized, file_name = None): + + # NOTE: In an enterprise environment, IT might not trust every material package the user installs. + # In that case, check if this package is trusted first, and return prematurely if not. + if file_name is not None and ApplicationMetadata.CuraIsEnterpriseVersion: + from UM.Application import Application + install_prefix = os.path.abspath(Application.getInstallPrefix()) + common_path = os.path.commonpath([install_prefix, file_name]) + if common_path is None or not common_path.startswith(install_prefix): + if not Trust.getInstance().signedFileCheck(file_name): + raise Exception("Trust-check failed for material file {0}.".format(file_name)) + containers_to_add = [] # update the serialized data first from UM.Settings.Interfaces import ContainerInterface From 5908ff60eeec16b135b2f1b401bda26d32e089a4 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Thu, 31 Oct 2019 13:37:24 +0100 Subject: [PATCH 3/8] Move verification check from plugin to the import of containers. part of CURA-6856 --- plugins/XmlMaterialProfile/XmlMaterialProfile.py | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py index 752f17feb4..093638d594 100644 --- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py +++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py @@ -15,9 +15,8 @@ import UM.Dictionary from UM.Settings.InstanceContainer import InstanceContainer from UM.Settings.ContainerRegistry import ContainerRegistry from UM.ConfigurationErrorMessage import ConfigurationErrorMessage -from UM.Trust import Trust -from cura.CuraApplication import ApplicationMetadata, CuraApplication +from cura.CuraApplication import CuraApplication from cura.Machines.ContainerTree import ContainerTree from cura.Machines.VariantType import VariantType @@ -471,17 +470,6 @@ class XmlMaterialProfile(InstanceContainer): ## Overridden from InstanceContainer def deserialize(self, serialized, file_name = None): - - # NOTE: In an enterprise environment, IT might not trust every material package the user installs. - # In that case, check if this package is trusted first, and return prematurely if not. - if file_name is not None and ApplicationMetadata.CuraIsEnterpriseVersion: - from UM.Application import Application - install_prefix = os.path.abspath(Application.getInstallPrefix()) - common_path = os.path.commonpath([install_prefix, file_name]) - if common_path is None or not common_path.startswith(install_prefix): - if not Trust.getInstance().signedFileCheck(file_name): - raise Exception("Trust-check failed for material file {0}.".format(file_name)) - containers_to_add = [] # update the serialized data first from UM.Settings.Interfaces import ContainerInterface From aca7f4551e84b4a53e7250844942e51c552416c9 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Thu, 31 Oct 2019 19:01:23 +0100 Subject: [PATCH 4/8] Make verified material containers read-only, even for variants. part CURA-6856 --- plugins/XmlMaterialProfile/XmlMaterialProfile.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py index 093638d594..3812c5255c 100644 --- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py +++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py @@ -720,6 +720,8 @@ class XmlMaterialProfile(InstanceContainer): new_hotend_material._dirty = False if is_new_material: + if ContainerRegistry.getInstance().isReadOnly(self.getId()): + ContainerRegistry.getInstance().setReadOnlyExplicitly(new_hotend_material.getId()) containers_to_add.append(new_hotend_material) # there is only one ID for a machine. Once we have reached here, it means we have already found From 4bae97529c6c31e7a45c56709244f589f4ccbaf4 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Fri, 1 Nov 2019 17:03:20 +0100 Subject: [PATCH 5/8] Should check plugins if build-type is 'essentials' now. part of CURA-6856 --- cura/ApplicationMetadata.py | 4 ++-- cura/CuraApplication.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cura/ApplicationMetadata.py b/cura/ApplicationMetadata.py index 25be4b31e9..45a14587ef 100644 --- a/cura/ApplicationMetadata.py +++ b/cura/ApplicationMetadata.py @@ -43,9 +43,9 @@ except ImportError: CuraDebugMode = DEFAULT_CURA_DEBUG_MODE try: - from cura.CuraVersion import CuraIsEnterpriseVersion # type: ignore + from cura.CuraVersion import CuraIsSecuredVersion # type: ignore except ImportError: - CuraIsEnterpriseVersion = True # (DEFAULT_CURA_BUILD_TYPE != "") + CuraIsSecuredVersion = (CuraBuildType.lower() in ["essentials", "enterprise", "assured", "secure", "secured"]) # Each release has a fixed SDK version coupled with it. It doesn't make sense to make it configurable because, for # example Cura 3.2 with SDK version 6.1 will not work. So the SDK version is hard-coded here and left out of the diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 5dfe0ad041..ac1cca8217 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -720,7 +720,7 @@ class CuraApplication(QtApplication): ## Handle loading of all plugin types (and the backend explicitly) # \sa PluginRegistry def _loadPlugins(self) -> None: - self._plugin_registry.setCheckIfTrusted(ApplicationMetadata.CuraIsEnterpriseVersion) + self._plugin_registry.setCheckIfTrusted(ApplicationMetadata.CuraIsSecuredVersion) self._plugin_registry.addType("profile_reader", self._addProfileReader) self._plugin_registry.addType("profile_writer", self._addProfileWriter) From 34c080c4bda51950b1355603640336a7c0fb89b0 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Thu, 7 Nov 2019 09:55:41 +0100 Subject: [PATCH 6/8] Add _trustHook() to MockContainer CURA-6856 --- tests/Settings/MockContainer.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/Settings/MockContainer.py b/tests/Settings/MockContainer.py index 533938c631..0400359154 100644 --- a/tests/Settings/MockContainer.py +++ b/tests/Settings/MockContainer.py @@ -78,6 +78,10 @@ class MockContainer(ContainerInterface, UM.PluginObject.PluginObject): def getAllKeys(self): pass + # Should return false (or even throw an exception) if trust (or other verification) is invalidated. + def _trustHook(self, file_name: Optional[str]) -> bool: + return True + def setProperty(self, key, property_name, property_value, container = None, set_from_cache = False): pass From 5e255b548ee5b76aa7a84a438f5893029e2def50 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Thu, 7 Nov 2019 10:21:01 +0100 Subject: [PATCH 7/8] Fix TestLegacyProfileReader --- .../LegacyProfileReader/tests/TestLegacyProfileReader.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/LegacyProfileReader/tests/TestLegacyProfileReader.py b/plugins/LegacyProfileReader/tests/TestLegacyProfileReader.py index 05f49017a3..cd0f681828 100644 --- a/plugins/LegacyProfileReader/tests/TestLegacyProfileReader.py +++ b/plugins/LegacyProfileReader/tests/TestLegacyProfileReader.py @@ -5,7 +5,6 @@ import configparser # An input for some functions we're testing. import os.path # To find the integration test .ini files. import pytest # To register tests with. import unittest.mock # To mock the application, plug-in and container registry out. -import os.path import sys sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")) @@ -16,6 +15,7 @@ import UM.Settings.InstanceContainer # To intercept the serialised data from the import LegacyProfileReader as LegacyProfileReaderModule # To get the directory of the module. + @pytest.fixture def legacy_profile_reader(): try: @@ -162,7 +162,7 @@ def test_read(legacy_profile_reader, file_name): plugin_registry.getPluginPath = unittest.mock.MagicMock(return_value = os.path.dirname(LegacyProfileReaderModule.__file__)) # Mock out the resulting InstanceContainer so that we can intercept the data before it's passed through the version upgrader. - def deserialize(self, data): # Intercepts the serialised data that we'd perform the version upgrade from when deserializing. + def deserialize(self, data, filename): # Intercepts the serialised data that we'd perform the version upgrade from when deserializing. global intercepted_data intercepted_data = data @@ -192,4 +192,4 @@ def test_read(legacy_profile_reader, file_name): assert parser["metadata"]["type"] == "quality_changes" assert parser["metadata"]["quality_type"] == "normal" assert parser["metadata"]["position"] == "0" - assert parser["metadata"]["setting_version"] == "5" # Yes, before we upgraded. \ No newline at end of file + assert parser["metadata"]["setting_version"] == "5" # Yes, before we upgraded. From c3d1ab093146402169e7f7da2c38e8cc2bb35946 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Tue, 12 Nov 2019 08:40:50 +0100 Subject: [PATCH 8/8] Small refactor: Rename in UM. part of CURA-6856 --- plugins/XmlMaterialProfile/XmlMaterialProfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py index 3812c5255c..948751ab8b 100644 --- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py +++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py @@ -721,7 +721,7 @@ class XmlMaterialProfile(InstanceContainer): if is_new_material: if ContainerRegistry.getInstance().isReadOnly(self.getId()): - ContainerRegistry.getInstance().setReadOnlyExplicitly(new_hotend_material.getId()) + ContainerRegistry.getInstance().setExplicitReadOnly(new_hotend_material.getId()) containers_to_add.append(new_hotend_material) # there is only one ID for a machine. Once we have reached here, it means we have already found