Browse Source

Merge pull request #3 from Ultimaker/master

update
MaukCC 5 years ago
parent
commit
3c6f31b81f

+ 1 - 0
CMakeLists.txt

@@ -23,6 +23,7 @@ set(CURA_BUILDTYPE "" CACHE STRING "Build type of Cura, eg. 'PPA'")
 set(CURA_CLOUD_API_ROOT "" CACHE STRING "Alternative Cura cloud API root")
 set(CURA_CLOUD_API_VERSION "" CACHE STRING "Alternative Cura cloud API version")
 set(CURA_CLOUD_ACCOUNT_API_ROOT "" CACHE STRING "Alternative Cura cloud account API version")
+set(CURA_MARKETPLACE_ROOT "" CACHE STRING "Alternative Marketplace location")
 
 configure_file(${CMAKE_SOURCE_DIR}/cura.desktop.in ${CMAKE_BINARY_DIR}/cura.desktop @ONLY)
 

+ 7 - 0
cmake/CuraTests.cmake

@@ -56,6 +56,13 @@ function(cura_add_test)
     endif()
 endfunction()
 
+#Add test for import statements which are not compatible with all builds
+add_test(
+    NAME "invalid-imports"
+    COMMAND ${Python3_EXECUTABLE} scripts/check_invalid_imports.py
+    WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
+)
+
 cura_add_test(NAME pytest-main DIRECTORY ${CMAKE_SOURCE_DIR}/tests PYTHONPATH "${CMAKE_SOURCE_DIR}|${URANIUM_DIR}")
 
 file(GLOB_RECURSE _plugins plugins/*/__init__.py)

+ 6 - 5
cura/API/__init__.py

@@ -28,11 +28,12 @@ class CuraAPI(QObject):
     #   The main reason for this is that we want to prevent consumers of API to have a dependency on CuraApplication.
     #   Since the API is intended to be used by plugins, the cura application should have already created this.
     def __new__(cls, application: Optional["CuraApplication"] = None):
-        if cls.__instance is None:
-            if application is None:
-                raise Exception("Upon first time creation, the application must be set.")
-            cls.__instance = super(CuraAPI, cls).__new__(cls)
-            cls._application = application
+        if cls.__instance is not None:
+            raise RuntimeError("Tried to create singleton '{class_name}' more than once.".format(class_name = CuraAPI.__name__))
+        if application is None:
+            raise RuntimeError("Upon first time creation, the application must be set.")
+        cls.__instance = super(CuraAPI, cls).__new__(cls)
+        cls._application = application
         return cls.__instance
 
     def __init__(self, application: Optional["CuraApplication"] = None) -> None:

+ 2 - 2
cura/ApplicationMetadata.py

@@ -1,4 +1,4 @@
-# Copyright (c) 2018 Ultimaker B.V.
+# Copyright (c) 2020 Ultimaker B.V.
 # Cura is released under the terms of the LGPLv3 or higher.
 
 # ---------
@@ -13,7 +13,7 @@ DEFAULT_CURA_DEBUG_MODE = False
 # 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.
-CuraSDKVersion = "7.0.0"
+CuraSDKVersion = "7.1.0"
 
 try:
     from cura.CuraVersion import CuraAppName  # type: ignore

+ 8 - 0
cura/Backups/Backup.py

@@ -145,6 +145,14 @@ class Backup:
     #   \return Whether we had success or not.
     @staticmethod
     def _extractArchive(archive: "ZipFile", target_path: str) -> bool:
+
+        # Implement security recommendations: Sanity check on zip files will make it harder to spoof.
+        from cura.CuraApplication import CuraApplication
+        config_filename = CuraApplication.getInstance().getApplicationName() + ".cfg"  # Should be there if valid.
+        if config_filename not in [file.filename for file in archive.filelist]:
+            Logger.logException("e", "Unable to extract the backup due to corruption of compressed file(s).")
+            return False
+
         Logger.log("d", "Removing current data in location: %s", target_path)
         Resources.factoryReset()
         Logger.log("d", "Extracting backup to location: %s", target_path)

+ 26 - 11
cura/CuraApplication.py

@@ -1,4 +1,4 @@
-# Copyright (c) 2019 Ultimaker B.V.
+# Copyright (c) 2020 Ultimaker B.V.
 # Cura is released under the terms of the LGPLv3 or higher.
 
 import os
@@ -191,8 +191,6 @@ class CuraApplication(QtApplication):
 
         self._cura_formula_functions = None  # type: Optional[CuraFormulaFunctions]
 
-        self._cura_package_manager = None
-
         self._machine_action_manager = None  # type: Optional[MachineActionManager.MachineActionManager]
 
         self.empty_container = None  # type: EmptyInstanceContainer
@@ -350,6 +348,9 @@ class CuraApplication(QtApplication):
         for dir_name in ["extruders", "machine_instances", "materials", "plugins", "quality", "quality_changes", "user", "variants", "intent"]:
             Resources.addExpectedDirNameInData(dir_name)
 
+        app_root = os.path.abspath(os.path.join(os.path.dirname(sys.executable)))
+        Resources.addSearchPath(os.path.join(app_root, "share", "cura", "resources"))
+
         Resources.addSearchPath(os.path.join(self._app_install_dir, "share", "cura", "resources"))
         if not hasattr(sys, "frozen"):
             resource_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), "..", "resources")
@@ -393,6 +394,8 @@ class CuraApplication(QtApplication):
         SettingFunction.registerOperator("extruderValues", self._cura_formula_functions.getValuesInAllExtruders)
         SettingFunction.registerOperator("resolveOrValue", self._cura_formula_functions.getResolveOrValue)
         SettingFunction.registerOperator("defaultExtruderPosition", self._cura_formula_functions.getDefaultExtruderPosition)
+        SettingFunction.registerOperator("valueFromContainer", self._cura_formula_functions.getValueFromContainerAtIndex)
+        SettingFunction.registerOperator("extruderValueFromContainer", self._cura_formula_functions.getValueFromContainerAtIndexInExtruder)
 
     # Adds all resources and container related resources.
     def __addAllResourcesAndContainerResources(self) -> None:
@@ -632,6 +635,12 @@ class CuraApplication(QtApplication):
     def showPreferences(self) -> None:
         self.showPreferencesWindow.emit()
 
+    # This is called by drag-and-dropping curapackage files.
+    @pyqtSlot(QUrl)
+    def installPackageViaDragAndDrop(self, file_url: str) -> Optional[str]:
+        filename = QUrl(file_url).toLocalFile()
+        return self._package_manager.installPackage(filename)
+
     @override(Application)
     def getGlobalContainerStack(self) -> Optional["GlobalStack"]:
         return self._global_container_stack
@@ -1827,15 +1836,21 @@ class CuraApplication(QtApplication):
 
     def _onContextMenuRequested(self, x: float, y: float) -> None:
         # Ensure we select the object if we request a context menu over an object without having a selection.
-        if not Selection.hasSelection():
-            node = self.getController().getScene().findObject(cast(SelectionPass, self.getRenderer().getRenderPass("selection")).getIdAtPosition(x, y))
-            if node:
-                parent = node.getParent()
-                while(parent and parent.callDecoration("isGroup")):
-                    node = parent
-                    parent = node.getParent()
+        if Selection.hasSelection():
+            return
+        selection_pass = cast(SelectionPass, self.getRenderer().getRenderPass("selection"))
+        if not selection_pass:  # If you right-click before the rendering has been initialised there might not be a selection pass yet.
+            print("--------------ding! Got the crash.")
+            return
+        node = self.getController().getScene().findObject(selection_pass.getIdAtPosition(x, y))
+        if not node:
+            return
+        parent = node.getParent()
+        while parent and parent.callDecoration("isGroup"):
+            node = parent
+            parent = node.getParent()
 
-                Selection.add(node)
+        Selection.add(node)
 
     @pyqtSlot()
     def showMoreInformationDialogForAnonymousDataCollection(self):

+ 2 - 1
cura/CuraVersion.py.in

@@ -1,4 +1,4 @@
-# Copyright (c) 2018 Ultimaker B.V.
+# Copyright (c) 2020 Ultimaker B.V.
 # Cura is released under the terms of the LGPLv3 or higher.
 
 CuraAppName = "@CURA_APP_NAME@"
@@ -9,3 +9,4 @@ CuraDebugMode = True if "@_cura_debugmode@" == "ON" else False
 CuraCloudAPIRoot = "@CURA_CLOUD_API_ROOT@"
 CuraCloudAPIVersion = "@CURA_CLOUD_API_VERSION@"
 CuraCloudAccountAPIRoot = "@CURA_CLOUD_ACCOUNT_API_ROOT@"
+CuraMarketplaceRoot = "@CURA_MARKETPLACE_ROOT@"

+ 4 - 3
cura/OAuth2/AuthorizationHelpers.py

@@ -115,9 +115,10 @@ class AuthorizationHelpers:
         )
 
     @staticmethod
-    ##  Generate a 16-character verification code.
-    #   \param code_length: How long should the code be?
-    def generateVerificationCode(code_length: int = 16) -> str:
+    ##  Generate a verification code of arbitrary length.
+    #   \param code_length: How long should the code be? This should never be lower than 16, but it's probably better to
+    #   leave it at 32
+    def generateVerificationCode(code_length: int = 32) -> str:
         return "".join(random.choice("0123456789ABCDEF") for i in range(code_length))
 
     @staticmethod

+ 10 - 1
cura/OAuth2/AuthorizationRequestHandler.py

@@ -25,6 +25,8 @@ class AuthorizationRequestHandler(BaseHTTPRequestHandler):
         self.authorization_callback = None  # type: Optional[Callable[[AuthenticationResponse], None]]
         self.verification_code = None  # type: Optional[str]
 
+        self.state = None  # type: Optional[str]
+
     # CURA-6609: Some browser seems to issue a HEAD instead of GET request as the callback.
     def do_HEAD(self) -> None:
         self.do_GET()
@@ -58,7 +60,14 @@ class AuthorizationRequestHandler(BaseHTTPRequestHandler):
     #   \return HTTP ResponseData containing a success page to show to the user.
     def _handleCallback(self, query: Dict[Any, List]) -> Tuple[ResponseData, Optional[AuthenticationResponse]]:
         code = self._queryGet(query, "code")
-        if code and self.authorization_helpers is not None and self.verification_code is not None:
+        state = self._queryGet(query, "state")
+        if state != self.state:
+            token_response = AuthenticationResponse(
+                success = False,
+                err_message=catalog.i18nc("@message",
+                                          "The provided state is not correct.")
+            )
+        elif code and self.authorization_helpers is not None and self.verification_code is not None:
             # If the code was returned we get the access token.
             token_response = self.authorization_helpers.getAccessTokenUsingAuthorizationCode(
                 code, self.verification_code)

+ 3 - 0
cura/OAuth2/AuthorizationRequestServer.py

@@ -25,3 +25,6 @@ class AuthorizationRequestServer(HTTPServer):
     ##  Set the verification code on the request handler.
     def setVerificationCode(self, verification_code: str) -> None:
         self.RequestHandlerClass.verification_code = verification_code  # type: ignore
+
+    def setState(self, state: str) -> None:
+        self.RequestHandlerClass.state = state  # type: ignore

Some files were not shown because too many files changed in this diff