mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-07-06 14:37:29 -06:00
Finish postprocessing script signature checking
CURA-7319
This commit is contained in:
parent
7f89c7e740
commit
5b045f89b1
3 changed files with 101 additions and 17 deletions
|
@ -1,24 +1,24 @@
|
|||
# Copyright (c) 2018 Jaime van Kessel, Ultimaker B.V.
|
||||
# The PostProcessingPlugin is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, pyqtSlot
|
||||
from typing import Dict, Type, TYPE_CHECKING, List, Optional, cast
|
||||
|
||||
from UM.Trust import Trust
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
from UM.Resources import Resources
|
||||
from UM.Application import Application
|
||||
from UM.Extension import Extension
|
||||
from UM.Logger import Logger
|
||||
|
||||
import configparser # The script lists are stored in metadata as serialised config files.
|
||||
import importlib.util
|
||||
import io # To allow configparser to write to a string.
|
||||
import os.path
|
||||
import pkgutil
|
||||
import sys
|
||||
import importlib.util
|
||||
from typing import Dict, Type, TYPE_CHECKING, List, Optional, cast
|
||||
|
||||
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, pyqtSlot
|
||||
|
||||
from UM.Application import Application
|
||||
from UM.Extension import Extension
|
||||
from UM.Logger import Logger
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
from UM.Resources import Resources
|
||||
from UM.Trust import Trust
|
||||
from UM.i18n import i18nCatalog
|
||||
from cura import ApplicationMetadata
|
||||
from cura.CuraApplication import CuraApplication
|
||||
|
||||
i18n_catalog = i18nCatalog("cura")
|
||||
|
@ -162,12 +162,13 @@ class PostProcessingPlugin(QObject, Extension):
|
|||
# Iterate over all scripts.
|
||||
if script_name not in sys.modules:
|
||||
try:
|
||||
file_location = os.path.join(path, script_name + ".py")
|
||||
trust_instance = Trust.getInstanceOrNone()
|
||||
if trust_instance is not None and Trust.signatureFileExistsFor(file_location):
|
||||
if not trust_instance.signedFileCheck(file_location):
|
||||
raise Exception("Can't validate script {0}".format(file_location))
|
||||
spec = importlib.util.spec_from_file_location(__name__ + "." + script_name, file_location)
|
||||
file_path = os.path.join(path, script_name + ".py")
|
||||
if not self._isScriptAllowed(file_path):
|
||||
Logger.warning("Skipped loading post-processing script {}: not trusted".format(file_path))
|
||||
continue
|
||||
|
||||
spec = importlib.util.spec_from_file_location(__name__ + "." + script_name,
|
||||
file_path)
|
||||
loaded_script = importlib.util.module_from_spec(spec)
|
||||
if spec.loader is None:
|
||||
continue
|
||||
|
@ -340,4 +341,22 @@ class PostProcessingPlugin(QObject, Extension):
|
|||
if global_container_stack is not None:
|
||||
global_container_stack.propertyChanged.emit("post_processing_plugin", "value")
|
||||
|
||||
@staticmethod
|
||||
def _isScriptAllowed(file_path) -> bool:
|
||||
"""Checks whether the given file is allowed to be loaded"""
|
||||
if not ApplicationMetadata.IsEnterpriseVersion:
|
||||
# No signature needed
|
||||
return True
|
||||
|
||||
if os.path.split(file_path) == os.path.join(Resources.getStoragePath(Resources.Resources), "scripts"):
|
||||
# Bundled scripts are trusted.
|
||||
return True
|
||||
|
||||
trust_instance = Trust.getInstanceOrNone()
|
||||
if trust_instance is not None and Trust.signatureFileExistsFor(file_path):
|
||||
if trust_instance.signedFileCheck(file_path):
|
||||
return True
|
||||
|
||||
return False # Default verdict should be False, being the most secure fallback
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
|
||||
import os
|
||||
import sys
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
from pytest import fixture
|
||||
|
||||
from UM.Resources import Resources
|
||||
from UM.Trust import Trust
|
||||
from ..PostProcessingPlugin import PostProcessingPlugin
|
||||
|
||||
# not sure if needed
|
||||
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
|
||||
|
||||
""" In this file, commnunity refers to regular Cura for makers."""
|
||||
|
||||
|
||||
# noinspection PyProtectedMember
|
||||
@patch("cura.ApplicationMetadata.IsEnterpriseVersion", False)
|
||||
def test_community_user_script_allowed():
|
||||
assert PostProcessingPlugin._isScriptAllowed("blaat.py")
|
||||
|
||||
# noinspection PyProtectedMember
|
||||
@patch("cura.ApplicationMetadata.IsEnterpriseVersion", False)
|
||||
def test_community_bundled_script_allowed():
|
||||
assert PostProcessingPlugin._isScriptAllowed(_bundled_file_path())
|
||||
|
||||
# noinspection PyProtectedMember
|
||||
@patch("cura.ApplicationMetadata.IsEnterpriseVersion", True)
|
||||
def test_enterprise_unsigned_user_script_not_allowed():
|
||||
assert not PostProcessingPlugin._isScriptAllowed("blaat.py")
|
||||
|
||||
@fixture
|
||||
def mocked_get_instance_or_none():
|
||||
mocked_trust = MagicMock()
|
||||
mocked_trust.signedFileCheck = MagicMock(return_value=True)
|
||||
return mocked_trust
|
||||
|
||||
@fixture
|
||||
def mocked_get_signature_file_exists_for():
|
||||
return MagicMock(return_value=True)
|
||||
|
||||
# noinspection PyProtectedMember
|
||||
@patch("cura.ApplicationMetadata.IsEnterpriseVersion", True)
|
||||
@patch("UM.Trust", "signatureFileExistsFor")
|
||||
@patch("UM.Trust.Trust.getInstanceOrNone")
|
||||
def test_enterprise_signed_user_script_allowed(mocked_instance_or_none, mocked_get_instance_or_none):
|
||||
file_path = "blaat.py"
|
||||
realSignatureFileExistsFor = Trust.signatureFileExistsFor
|
||||
Trust.signatureFileExistsFor = MagicMock(return_value=True)
|
||||
assert PostProcessingPlugin._isScriptAllowed(file_path)
|
||||
|
||||
# cleanup
|
||||
Trust.signatureFileExistsFor = realSignatureFileExistsFor
|
||||
|
||||
# noinspection PyProtectedMember
|
||||
@patch("cura.ApplicationMetadata.IsEnterpriseVersion", False)
|
||||
def test_enterprise_bundled_script_allowed():
|
||||
assert PostProcessingPlugin._isScriptAllowed(_bundled_file_path())
|
||||
|
||||
|
||||
def _bundled_file_path():
|
||||
return os.path.join(
|
||||
Resources.getStoragePath(Resources.Resources) + "scripts/blaat.py"
|
||||
)
|
0
plugins/PostProcessingPlugin/tests/__init__.py
Normal file
0
plugins/PostProcessingPlugin/tests/__init__.py
Normal file
Loading…
Add table
Add a link
Reference in a new issue