mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-07-13 01:37:51 -06:00
Merge branch '4.0' of github.com:Ultimaker/Cura
This commit is contained in:
commit
a8f66a558c
15 changed files with 112 additions and 79 deletions
|
@ -57,7 +57,6 @@ class Account(QObject):
|
||||||
|
|
||||||
def initialize(self) -> None:
|
def initialize(self) -> None:
|
||||||
self._authorization_service.initialize(self._application.getPreferences())
|
self._authorization_service.initialize(self._application.getPreferences())
|
||||||
|
|
||||||
self._authorization_service.onAuthStateChanged.connect(self._onLoginStateChanged)
|
self._authorization_service.onAuthStateChanged.connect(self._onLoginStateChanged)
|
||||||
self._authorization_service.onAuthenticationError.connect(self._onLoginStateChanged)
|
self._authorization_service.onAuthenticationError.connect(self._onLoginStateChanged)
|
||||||
self._authorization_service.loadAuthDataFromPreferences()
|
self._authorization_service.loadAuthDataFromPreferences()
|
||||||
|
|
|
@ -1,18 +1,20 @@
|
||||||
# Copyright (c) 2019 Ultimaker B.V.
|
# Copyright (c) 2019 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
from datetime import datetime
|
||||||
from base64 import b64encode
|
|
||||||
from hashlib import sha512
|
|
||||||
import json
|
import json
|
||||||
import random
|
import random
|
||||||
import requests
|
from hashlib import sha512
|
||||||
|
from base64 import b64encode
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
from UM.i18n import i18nCatalog
|
from UM.i18n import i18nCatalog
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
from cura.OAuth2.Models import AuthenticationResponse, UserProfile, OAuth2Settings
|
|
||||||
|
|
||||||
|
from cura.OAuth2.Models import AuthenticationResponse, UserProfile, OAuth2Settings
|
||||||
catalog = i18nCatalog("cura")
|
catalog = i18nCatalog("cura")
|
||||||
|
TOKEN_TIMESTAMP_FORMAT = "%Y-%m-%d %H:%M:%S"
|
||||||
|
|
||||||
## Class containing several helpers to deal with the authorization flow.
|
## Class containing several helpers to deal with the authorization flow.
|
||||||
class AuthorizationHelpers:
|
class AuthorizationHelpers:
|
||||||
|
@ -72,12 +74,13 @@ class AuthorizationHelpers:
|
||||||
if token_response.status_code not in (200, 201):
|
if token_response.status_code not in (200, 201):
|
||||||
return AuthenticationResponse(success = False, err_message = token_data["error_description"])
|
return AuthenticationResponse(success = False, err_message = token_data["error_description"])
|
||||||
|
|
||||||
return AuthenticationResponse(success = True,
|
return AuthenticationResponse(success=True,
|
||||||
token_type = token_data["token_type"],
|
token_type=token_data["token_type"],
|
||||||
access_token = token_data["access_token"],
|
access_token=token_data["access_token"],
|
||||||
refresh_token = token_data["refresh_token"],
|
refresh_token=token_data["refresh_token"],
|
||||||
expires_in = token_data["expires_in"],
|
expires_in=token_data["expires_in"],
|
||||||
scope = token_data["scope"])
|
scope=token_data["scope"],
|
||||||
|
received_at=datetime.now().strftime(TOKEN_TIMESTAMP_FORMAT))
|
||||||
|
|
||||||
## Calls the authentication API endpoint to get the token data.
|
## Calls the authentication API endpoint to get the token data.
|
||||||
# \param access_token: The encoded JWT token.
|
# \param access_token: The encoded JWT token.
|
||||||
|
|
|
@ -21,7 +21,7 @@ class AuthorizationRequestHandler(BaseHTTPRequestHandler):
|
||||||
super().__init__(request, client_address, server)
|
super().__init__(request, client_address, server)
|
||||||
|
|
||||||
# These values will be injected by the HTTPServer that this handler belongs to.
|
# These values will be injected by the HTTPServer that this handler belongs to.
|
||||||
self.authorization_helpers = None # type: Optional["AuthorizationHelpers"]
|
self.authorization_helpers = None # type: Optional[AuthorizationHelpers]
|
||||||
self.authorization_callback = None # type: Optional[Callable[[AuthenticationResponse], None]]
|
self.authorization_callback = None # type: Optional[Callable[[AuthenticationResponse], None]]
|
||||||
self.verification_code = None # type: Optional[str]
|
self.verification_code = None # type: Optional[str]
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import webbrowser
|
import webbrowser
|
||||||
|
from datetime import datetime, timedelta
|
||||||
from typing import Optional, TYPE_CHECKING
|
from typing import Optional, TYPE_CHECKING
|
||||||
from urllib.parse import urlencode
|
from urllib.parse import urlencode
|
||||||
import requests.exceptions
|
import requests.exceptions
|
||||||
|
@ -12,7 +13,7 @@ from UM.Logger import Logger
|
||||||
from UM.Signal import Signal
|
from UM.Signal import Signal
|
||||||
|
|
||||||
from cura.OAuth2.LocalAuthorizationServer import LocalAuthorizationServer
|
from cura.OAuth2.LocalAuthorizationServer import LocalAuthorizationServer
|
||||||
from cura.OAuth2.AuthorizationHelpers import AuthorizationHelpers
|
from cura.OAuth2.AuthorizationHelpers import AuthorizationHelpers, TOKEN_TIMESTAMP_FORMAT
|
||||||
from cura.OAuth2.Models import AuthenticationResponse
|
from cura.OAuth2.Models import AuthenticationResponse
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
@ -87,17 +88,19 @@ class AuthorizationService:
|
||||||
|
|
||||||
## Get the access token as provided by the repsonse data.
|
## Get the access token as provided by the repsonse data.
|
||||||
def getAccessToken(self) -> Optional[str]:
|
def getAccessToken(self) -> Optional[str]:
|
||||||
if not self.getUserProfile():
|
|
||||||
# We check if we can get the user profile.
|
|
||||||
# If we can't get it, that means the access token (JWT) was invalid or expired.
|
|
||||||
Logger.log("w", "Unable to get the user profile.")
|
|
||||||
return None
|
|
||||||
|
|
||||||
if self._auth_data is None:
|
if self._auth_data is None:
|
||||||
Logger.log("d", "No auth data to retrieve the access_token from")
|
Logger.log("d", "No auth data to retrieve the access_token from")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return self._auth_data.access_token
|
# Check if the current access token is expired and refresh it if that is the case.
|
||||||
|
# We have a fallback on a date far in the past for currently stored auth data in cura.cfg.
|
||||||
|
received_at = datetime.strptime(self._auth_data.received_at, TOKEN_TIMESTAMP_FORMAT) \
|
||||||
|
if self._auth_data.received_at else datetime(2000, 1, 1)
|
||||||
|
expiry_date = received_at + timedelta(seconds = float(self._auth_data.expires_in or 0))
|
||||||
|
if datetime.now() > expiry_date:
|
||||||
|
self.refreshAccessToken()
|
||||||
|
|
||||||
|
return self._auth_data.access_token if self._auth_data else None
|
||||||
|
|
||||||
## Try to refresh the access token. This should be used when it has expired.
|
## Try to refresh the access token. This should be used when it has expired.
|
||||||
def refreshAccessToken(self) -> None:
|
def refreshAccessToken(self) -> None:
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
# Copyright (c) 2019 Ultimaker B.V.
|
# Copyright (c) 2019 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
|
@ -38,12 +37,13 @@ class AuthenticationResponse(BaseModel):
|
||||||
expires_in = None # type: Optional[str]
|
expires_in = None # type: Optional[str]
|
||||||
scope = None # type: Optional[str]
|
scope = None # type: Optional[str]
|
||||||
err_message = None # type: Optional[str]
|
err_message = None # type: Optional[str]
|
||||||
|
received_at = None # type: Optional[str]
|
||||||
|
|
||||||
|
|
||||||
## Response status template.
|
## Response status template.
|
||||||
class ResponseStatus(BaseModel):
|
class ResponseStatus(BaseModel):
|
||||||
code = 200 # type: int
|
code = 200 # type: int
|
||||||
message = "" # type str
|
message = "" # type: str
|
||||||
|
|
||||||
|
|
||||||
## Response data template.
|
## Response data template.
|
||||||
|
|
|
@ -511,13 +511,13 @@ class MachineManager(QObject):
|
||||||
|
|
||||||
@pyqtProperty(str, notify = globalContainerChanged)
|
@pyqtProperty(str, notify = globalContainerChanged)
|
||||||
def activeMachineFirmwareVersion(self) -> str:
|
def activeMachineFirmwareVersion(self) -> str:
|
||||||
if not self._printer_output_devices[0]:
|
if not self._printer_output_devices:
|
||||||
return ""
|
return ""
|
||||||
return self._printer_output_devices[0].firmwareVersion
|
return self._printer_output_devices[0].firmwareVersion
|
||||||
|
|
||||||
@pyqtProperty(str, notify = globalContainerChanged)
|
@pyqtProperty(str, notify = globalContainerChanged)
|
||||||
def activeMachineAddress(self) -> str:
|
def activeMachineAddress(self) -> str:
|
||||||
if not self._printer_output_devices[0]:
|
if not self._printer_output_devices:
|
||||||
return ""
|
return ""
|
||||||
return self._printer_output_devices[0].address
|
return self._printer_output_devices[0].address
|
||||||
|
|
||||||
|
@ -547,14 +547,18 @@ class MachineManager(QObject):
|
||||||
return bool(self._printer_output_devices) and len(self._printer_output_devices[0].printers) > 1
|
return bool(self._printer_output_devices) and len(self._printer_output_devices[0].printers) > 1
|
||||||
|
|
||||||
@pyqtProperty(bool, notify = printerConnectedStatusChanged)
|
@pyqtProperty(bool, notify = printerConnectedStatusChanged)
|
||||||
def activeMachineHasActiveNetworkConnection(self) -> bool:
|
def activeMachineHasNetworkConnection(self) -> bool:
|
||||||
# A network connection is only available if any output device is actually a network connected device.
|
# A network connection is only available if any output device is actually a network connected device.
|
||||||
return any(d.connectionType == ConnectionType.NetworkConnection for d in self._printer_output_devices)
|
return any(d.connectionType == ConnectionType.NetworkConnection for d in self._printer_output_devices)
|
||||||
|
|
||||||
@pyqtProperty(bool, notify = printerConnectedStatusChanged)
|
@pyqtProperty(bool, notify = printerConnectedStatusChanged)
|
||||||
def activeMachineHasActiveCloudConnection(self) -> bool:
|
def activeMachineHasCloudConnection(self) -> bool:
|
||||||
# A cloud connection is only available if any output device actually is a cloud connected device.
|
# A cloud connection is only available if any output device actually is a cloud connected device.
|
||||||
return any(d.connectionType == ConnectionType.CloudConnection for d in self._printer_output_devices)
|
return any(d.connectionType == ConnectionType.CloudConnection for d in self._printer_output_devices)
|
||||||
|
|
||||||
|
@pyqtProperty(bool, notify = printerConnectedStatusChanged)
|
||||||
|
def activeMachineIsUsingCloudConnection(self) -> bool:
|
||||||
|
return self.activeMachineHasCloudConnection and not self.activeMachineHasNetworkConnection
|
||||||
|
|
||||||
def activeMachineNetworkKey(self) -> str:
|
def activeMachineNetworkKey(self) -> str:
|
||||||
if self._global_container_stack:
|
if self._global_container_stack:
|
||||||
|
|
|
@ -24,7 +24,7 @@ Item
|
||||||
|
|
||||||
// If the printer is a cloud printer or not. Other items base their enabled state off of this boolean. In the future
|
// If the printer is a cloud printer or not. Other items base their enabled state off of this boolean. In the future
|
||||||
// they might not need to though.
|
// they might not need to though.
|
||||||
property bool cloudConnection: Cura.MachineManager.activeMachineHasActiveCloudConnection
|
property bool cloudConnection: Cura.MachineManager.activeMachineIsUsingCloudConnection
|
||||||
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: childrenRect.height
|
height: childrenRect.height
|
||||||
|
|
|
@ -30,7 +30,7 @@ Item
|
||||||
|
|
||||||
// If the printer is a cloud printer or not. Other items base their enabled state off of this boolean. In the future
|
// If the printer is a cloud printer or not. Other items base their enabled state off of this boolean. In the future
|
||||||
// they might not need to though.
|
// they might not need to though.
|
||||||
property bool cloudConnection: Cura.MachineManager.activeMachineHasActiveCloudConnection
|
property bool cloudConnection: Cura.MachineManager.activeMachineIsUsingCloudConnection
|
||||||
|
|
||||||
width: 834 * screenScaleFactor // TODO: Theme!
|
width: 834 * screenScaleFactor // TODO: Theme!
|
||||||
height: childrenRect.height
|
height: childrenRect.height
|
||||||
|
|
|
@ -103,8 +103,9 @@ class CloudApiClient:
|
||||||
request = QNetworkRequest(QUrl(path))
|
request = QNetworkRequest(QUrl(path))
|
||||||
if content_type:
|
if content_type:
|
||||||
request.setHeader(QNetworkRequest.ContentTypeHeader, content_type)
|
request.setHeader(QNetworkRequest.ContentTypeHeader, content_type)
|
||||||
if self._account.isLoggedIn:
|
access_token = self._account.accessToken
|
||||||
request.setRawHeader(b"Authorization", "Bearer {}".format(self._account.accessToken).encode())
|
if access_token:
|
||||||
|
request.setRawHeader(b"Authorization", "Bearer {}".format(access_token).encode())
|
||||||
return request
|
return request
|
||||||
|
|
||||||
## Parses the given JSON network reply into a status code and a dictionary, handling unexpected errors as well.
|
## Parses the given JSON network reply into a status code and a dictionary, handling unexpected errors as well.
|
||||||
|
|
|
@ -4,6 +4,7 @@ import json
|
||||||
from queue import Queue
|
from queue import Queue
|
||||||
from threading import Event, Thread
|
from threading import Event, Thread
|
||||||
from time import time
|
from time import time
|
||||||
|
import os
|
||||||
|
|
||||||
from zeroconf import Zeroconf, ServiceBrowser, ServiceStateChange, ServiceInfo
|
from zeroconf import Zeroconf, ServiceBrowser, ServiceStateChange, ServiceInfo
|
||||||
from PyQt5.QtNetwork import QNetworkRequest, QNetworkAccessManager
|
from PyQt5.QtNetwork import QNetworkRequest, QNetworkAccessManager
|
||||||
|
@ -412,13 +413,18 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
|
||||||
active_machine = self._application.getMachineManager().activeMachine # type: Optional["GlobalStack"]
|
active_machine = self._application.getMachineManager().activeMachine # type: Optional["GlobalStack"]
|
||||||
if active_machine:
|
if active_machine:
|
||||||
|
|
||||||
# Check 1: Printer isn't already configured for cloud
|
# Check 1A: Printer isn't already configured for cloud
|
||||||
if ConnectionType.CloudConnection.value in active_machine.configuredConnectionTypes:
|
if ConnectionType.CloudConnection.value in active_machine.configuredConnectionTypes:
|
||||||
Logger.log("d", "Active machine was already configured for cloud.")
|
Logger.log("d", "Active machine was already configured for cloud.")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Check 1B: Printer isn't already configured for cloud
|
||||||
|
if active_machine.getMetaDataEntry("cloud_flow_complete", False):
|
||||||
|
Logger.log("d", "Active machine was already configured for cloud.")
|
||||||
|
return
|
||||||
|
|
||||||
# Check 2: User did not already say "Don't ask me again"
|
# Check 2: User did not already say "Don't ask me again"
|
||||||
if active_machine.getMetaDataEntry("show_cloud_message", "value") is False:
|
if active_machine.getMetaDataEntry("do_not_show_cloud_message", False):
|
||||||
Logger.log("d", "Active machine shouldn't ask about cloud anymore.")
|
Logger.log("d", "Active machine shouldn't ask about cloud anymore.")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -428,7 +434,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
|
||||||
return
|
return
|
||||||
|
|
||||||
# Check 4: Machine is configured for network connectivity
|
# Check 4: Machine is configured for network connectivity
|
||||||
if not self._application.getMachineManager().activeMachineHasActiveNetworkConnection:
|
if not self._application.getMachineManager().activeMachineHasNetworkConnection:
|
||||||
Logger.log("d", "Cloud Flow not possible: Machine is not connected!")
|
Logger.log("d", "Cloud Flow not possible: Machine is not connected!")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -448,7 +454,9 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
|
||||||
if not self._start_cloud_flow_message:
|
if not self._start_cloud_flow_message:
|
||||||
self._start_cloud_flow_message = Message(
|
self._start_cloud_flow_message = Message(
|
||||||
text = i18n_catalog.i18nc("@info:status", "Send and monitor print jobs from anywhere using your Ultimaker account."),
|
text = i18n_catalog.i18nc("@info:status", "Send and monitor print jobs from anywhere using your Ultimaker account."),
|
||||||
image_source = "../../../../../Cura/plugins/UM3NetworkPrinting/resources/svg/cloud-flow-start.svg",
|
lifetime = 0,
|
||||||
|
image_source = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "resources", "svg",
|
||||||
|
"cloud-flow-start.svg"),
|
||||||
image_caption = i18n_catalog.i18nc("@info:status", "Connect to Ultimaker Cloud"),
|
image_caption = i18n_catalog.i18nc("@info:status", "Connect to Ultimaker Cloud"),
|
||||||
option_text = i18n_catalog.i18nc("@action", "Don't ask me again for this printer."),
|
option_text = i18n_catalog.i18nc("@action", "Don't ask me again for this printer."),
|
||||||
option_state = False
|
option_state = False
|
||||||
|
@ -469,7 +477,8 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
|
||||||
self._cloud_flow_complete_message = Message(
|
self._cloud_flow_complete_message = Message(
|
||||||
text = i18n_catalog.i18nc("@info:status", "You can now send and monitor print jobs from anywhere using your Ultimaker account."),
|
text = i18n_catalog.i18nc("@info:status", "You can now send and monitor print jobs from anywhere using your Ultimaker account."),
|
||||||
lifetime = 30,
|
lifetime = 30,
|
||||||
image_source = "../../../../../Cura/plugins/UM3NetworkPrinting/resources/svg/cloud-flow-completed.svg",
|
image_source = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "resources", "svg",
|
||||||
|
"cloud-flow-completed.svg"),
|
||||||
image_caption = i18n_catalog.i18nc("@info:status", "Connected!")
|
image_caption = i18n_catalog.i18nc("@info:status", "Connected!")
|
||||||
)
|
)
|
||||||
self._cloud_flow_complete_message.addAction("", i18n_catalog.i18nc("@action", "Review your connection"), "", "", 1) # TODO: Icon
|
self._cloud_flow_complete_message.addAction("", i18n_catalog.i18nc("@action", "Review your connection"), "", "", 1) # TODO: Icon
|
||||||
|
@ -479,13 +488,13 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
|
||||||
# Set the machine's cloud flow as complete so we don't ask the user again and again for cloud connected printers
|
# Set the machine's cloud flow as complete so we don't ask the user again and again for cloud connected printers
|
||||||
active_machine = self._application.getMachineManager().activeMachine
|
active_machine = self._application.getMachineManager().activeMachine
|
||||||
if active_machine:
|
if active_machine:
|
||||||
active_machine.setMetaDataEntry("cloud_flow_complete", True)
|
active_machine.setMetaDataEntry("do_not_show_cloud_message", True)
|
||||||
return
|
return
|
||||||
|
|
||||||
def _onDontAskMeAgain(self, messageId: str, checked: bool) -> None:
|
def _onDontAskMeAgain(self, messageId: str) -> None:
|
||||||
active_machine = self._application.getMachineManager().activeMachine # type: Optional["GlobalStack"]
|
active_machine = self._application.getMachineManager().activeMachine # type: Optional["GlobalStack"]
|
||||||
if active_machine:
|
if active_machine:
|
||||||
active_machine.setMetaDataEntry("show_cloud_message", False)
|
active_machine.setMetaDataEntry("do_not_show_cloud_message", True)
|
||||||
Logger.log("d", "Will not ask the user again to cloud connect for current printer.")
|
Logger.log("d", "Will not ask the user again to cloud connect for current printer.")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ UM.Dialog
|
||||||
anchors.topMargin: - margin
|
anchors.topMargin: - margin
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
|
||||||
color: UM.Theme.getColor("viewport_background")
|
color: UM.Theme.getColor("main_window_header_background")
|
||||||
}
|
}
|
||||||
|
|
||||||
Image
|
Image
|
||||||
|
@ -52,7 +52,7 @@ UM.Dialog
|
||||||
|
|
||||||
text: catalog.i18nc("@label","version: %1").arg(UM.Application.version)
|
text: catalog.i18nc("@label","version: %1").arg(UM.Application.version)
|
||||||
font: UM.Theme.getFont("large_bold")
|
font: UM.Theme.getFont("large_bold")
|
||||||
color: UM.Theme.getColor("text")
|
color: UM.Theme.getColor("button_text")
|
||||||
anchors.right : logo.right
|
anchors.right : logo.right
|
||||||
anchors.top: logo.bottom
|
anchors.top: logo.bottom
|
||||||
anchors.topMargin: (UM.Theme.getSize("default_margin").height / 2) | 0
|
anchors.topMargin: (UM.Theme.getSize("default_margin").height / 2) | 0
|
||||||
|
|
|
@ -11,8 +11,8 @@ Cura.ExpandablePopup
|
||||||
{
|
{
|
||||||
id: machineSelector
|
id: machineSelector
|
||||||
|
|
||||||
property bool isNetworkPrinter: Cura.MachineManager.activeMachineHasActiveNetworkConnection
|
property bool isNetworkPrinter: Cura.MachineManager.activeMachineHasNetworkConnection
|
||||||
property bool isCloudPrinter: Cura.MachineManager.activeMachineHasActiveCloudConnection
|
property bool isCloudPrinter: Cura.MachineManager.activeMachineHasCloudConnection
|
||||||
property bool isGroup: Cura.MachineManager.activeMachineIsGroup
|
property bool isGroup: Cura.MachineManager.activeMachineIsGroup
|
||||||
|
|
||||||
contentPadding: UM.Theme.getSize("default_lining").width
|
contentPadding: UM.Theme.getSize("default_lining").width
|
||||||
|
|
16
resources/themes/cura-dark/icons/sign_in_to_cloud.svg
Normal file
16
resources/themes/cura-dark/icons/sign_in_to_cloud.svg
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="78px" height="53px" viewBox="0 0 78 53" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<!-- Generator: Sketch 52.6 (67491) - http://www.bohemiancoding.com/sketch -->
|
||||||
|
<title>Group-cloud Copy</title>
|
||||||
|
<desc>Created with Sketch.</desc>
|
||||||
|
<g id="Sign-in-/Message-restyle" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="Artboard" transform="translate(-162.000000, -16.000000)">
|
||||||
|
<g id="Group-cloud-Copy" transform="translate(162.000000, 16.000000)">
|
||||||
|
<path d="M71.6139989,39.7264219 C71.5196898,37.5579439 70.9711289,35.5059663 70.0601048,33.6613194 L71.6937566,33.6613194 C72.8434847,33.6613194 73.787904,32.731337 73.787904,31.5991845 L73.8289659,31.5991845 L73.8289659,6.57052798 C73.8289659,6.1257538 73.4594104,5.80228166 73.0487931,5.80228166 L58.158788,5.80228166 L58.158788,25.1687374 C57.4862997,25.0807183 56.800231,25.0352941 56.103441,25.0352941 C55.374501,25.0352941 54.6572944,25.0850068 53.9550947,25.1811929 L53.9550947,5.80228166 L53.8935021,5.80228166 L53.8935021,4.68260671 C53.7612249,4.35507342 53.4329996,4.13943248 53.0774006,4.13943248 L24.4830482,4.13943248 C23.9749094,4.13943248 23.605354,4.54882691 23.605354,5.0037096 L23.605354,33.1609482 C23.605354,34.4346197 24.6678259,35.4808499 25.9612699,35.4808499 L41.3906551,35.4808499 C40.8638727,37.0247578 40.5782993,38.67857 40.5782993,40.3983852 C40.5782993,41.0436856 40.6185041,41.6796936 40.6965701,42.3040902 L25.2683535,42.3040902 C24.7602147,42.3495785 24.252076,42.5315316 23.8825205,42.8499495 L23.605354,43.122879 C23.3281874,43.5777618 22.7738542,43.6687383 22.219521,43.6687383 L19.7250218,43.6687383 C19.5864385,43.6687383 19.4478552,43.5322734 19.4478552,43.3958088 L19.4478552,39.7264219 L5.21483793,39.7264219 C4.76315904,39.7668558 4.31148018,39.928592 3.98298644,40.2116301 L3.73661615,40.4542343 C3.49024586,40.8585745 2.99750527,40.9394424 2.50476467,40.9394424 L0.287432013,40.9394424 C0.164246864,40.9394424 0.0410617159,40.8181403 0.0410617159,40.6968382 L0.0410617159,2.5675603 C0.0410617159,2.44625825 0.164246864,2.3249562 0.287432013,2.3249562 L19.4478552,2.3249562 L19.4478552,0.50037096 C19.4478552,0.363906153 19.5864385,0.227441345 19.7250218,0.227441345 L57.8816214,0.227441345 C58.0202046,0.227441345 58.158788,0.363906153 58.158788,0.50037096 L58.158788,2.3249562 L77.3192117,2.3249562 C77.4423969,2.3249562 77.565582,2.44625825 77.565582,2.5675603 L77.565582,40.6968382 C77.565582,40.8181403 77.4423969,40.9394424 77.3192117,40.9394424 L75.101879,40.9394424 C74.6502001,40.8990083 74.1985212,40.7372724 73.8700276,40.4542343 L73.6236573,40.2116301 C73.377287,39.8072899 72.8845464,39.7264219 72.3918058,39.7264219 L71.6139989,39.7264219 Z M19.4478552,5.80228166 L4.51678875,5.80228166 C4.06510989,5.80228166 3.73661615,6.16618781 3.73661615,6.57052798 L3.73661615,31.5991845 C3.73661615,32.731337 4.68103562,33.6613194 5.83076366,33.6613194 L19.4478552,33.6613194 L19.4478552,5.80228166 Z" id="Combined-Shape" fill="#E5E5E5" fill-rule="nonzero"></path>
|
||||||
|
<g id="Group" transform="translate(43.578299, 28.035294)" fill="#3282FF">
|
||||||
|
<path d="M12.5251415,24.7888094 C5.60769686,24.7888094 1.77635684e-14,19.2396454 1.77635684e-14,12.3944047 C1.77635684e-14,5.54916401 5.60769686,-4.26325641e-14 12.5251415,-4.26325641e-14 C19.4425861,-4.26325641e-14 25.050283,5.54916401 25.050283,12.3944047 C25.050283,19.2396454 19.4425861,24.7888094 12.5251415,24.7888094 Z M17.9438152,10.4266859 C17.7896596,9.07601357 16.6334927,8.04314651 15.2460924,8.04314651 C14.8607034,8.04314651 14.5523922,8.12259783 14.244081,8.28150044 C13.5503809,7.16918208 12.3171362,6.45412027 11.0068137,6.45412027 C8.84863548,6.45412027 7.15292402,8.20204914 7.15292402,10.4266859 C7.15292402,10.4266859 7.15292402,10.4266859 7.15292402,10.5061372 C5.84260152,10.6650398 4.8405902,11.8568095 4.8405902,13.2074818 C4.8405902,14.7170567 6.0738349,15.9882777 7.53831299,15.9882777 C8.69447989,15.9882777 16.1710259,15.9882777 17.5584262,15.9882777 C19.0229043,15.9882777 20.256149,14.7170567 20.256149,13.2074818 C20.256149,11.7773582 19.2541376,10.6650398 17.9438152,10.4266859 Z" id="Combined-Shape"></path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 4.2 KiB |
|
@ -1,25 +1,14 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<svg width="291px" height="209px" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
<svg width="78px" height="53px" viewBox="0 0 78 53" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
<!-- Generator: Sketch 52.5 (67469) - http://www.bohemiancoding.com/sketch -->
|
<!-- Generator: Sketch 52.6 (67491) - http://www.bohemiancoding.com/sketch -->
|
||||||
<title>Group 2</title>
|
<title>Group-cloud</title>
|
||||||
<desc>Created with Sketch.</desc>
|
<desc>Created with Sketch.</desc>
|
||||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
<g id="Sign-in-/Message-restyle" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
<g id="CC--Cloud-connection-succes" transform="translate(-570.000000, -132.000000)">
|
<g id="Artboard" transform="translate(-22.000000, -16.000000)">
|
||||||
<g id="Group-2" transform="translate(570.000000, 132.000000)">
|
<g id="Group-cloud" transform="translate(22.000000, 16.000000)">
|
||||||
<g id="Icon/-group-printer/-connected" fill="#08073F" fill-rule="nonzero">
|
<path d="M71.6139989,39.7264219 C71.5196898,37.5579439 70.9711289,35.5059663 70.0601048,33.6613194 L71.6937566,33.6613194 C72.8434847,33.6613194 73.787904,32.731337 73.787904,31.5991845 L73.8289659,31.5991845 L73.8289659,6.57052798 C73.8289659,6.1257538 73.4594104,5.80228166 73.0487931,5.80228166 L58.158788,5.80228166 L58.158788,25.1687374 C57.4862997,25.0807183 56.800231,25.0352941 56.103441,25.0352941 C55.374501,25.0352941 54.6572944,25.0850068 53.9550947,25.1811929 L53.9550947,5.80228166 L53.8935021,5.80228166 L53.8935021,4.68260671 C53.7612249,4.35507342 53.4329996,4.13943248 53.0774006,4.13943248 L24.4830482,4.13943248 C23.9749094,4.13943248 23.605354,4.54882691 23.605354,5.0037096 L23.605354,33.1609482 C23.605354,34.4346197 24.6678259,35.4808499 25.9612699,35.4808499 L41.3906551,35.4808499 C40.8638727,37.0247578 40.5782993,38.67857 40.5782993,40.3983852 C40.5782993,41.0436856 40.6185041,41.6796936 40.6965701,42.3040902 L25.2683535,42.3040902 C24.7602147,42.3495785 24.252076,42.5315316 23.8825205,42.8499495 L23.605354,43.122879 C23.3281874,43.5777618 22.7738542,43.6687383 22.219521,43.6687383 L19.7250218,43.6687383 C19.5864385,43.6687383 19.4478552,43.5322734 19.4478552,43.3958088 L19.4478552,39.7264219 L5.21483793,39.7264219 C4.76315904,39.7668558 4.31148018,39.928592 3.98298644,40.2116301 L3.73661615,40.4542343 C3.49024586,40.8585745 2.99750527,40.9394424 2.50476467,40.9394424 L0.287432013,40.9394424 C0.164246864,40.9394424 0.0410617159,40.8181403 0.0410617159,40.6968382 L0.0410617159,2.5675603 C0.0410617159,2.44625825 0.164246864,2.3249562 0.287432013,2.3249562 L19.4478552,2.3249562 L19.4478552,0.50037096 C19.4478552,0.363906153 19.5864385,0.227441345 19.7250218,0.227441345 L57.8816214,0.227441345 C58.0202046,0.227441345 58.158788,0.363906153 58.158788,0.50037096 L58.158788,2.3249562 L77.3192117,2.3249562 C77.4423969,2.3249562 77.565582,2.44625825 77.565582,2.5675603 L77.565582,40.6968382 C77.565582,40.8181403 77.4423969,40.9394424 77.3192117,40.9394424 L75.101879,40.9394424 C74.6502001,40.8990083 74.1985212,40.7372724 73.8700276,40.4542343 L73.6236573,40.2116301 C73.377287,39.8072899 72.8845464,39.7264219 72.3918058,39.7264219 L71.6139989,39.7264219 Z M19.4478552,5.80228166 L4.51678875,5.80228166 C4.06510989,5.80228166 3.73661615,6.16618781 3.73661615,6.57052798 L3.73661615,31.5991845 C3.73661615,32.731337 4.68103562,33.6613194 5.83076366,33.6613194 L19.4478552,33.6613194 L19.4478552,5.80228166 Z" id="Combined-Shape" fill="#08073F" fill-rule="nonzero"></path>
|
||||||
<g id="printer-group">
|
<g id="Group" transform="translate(43.578299, 28.035294)" fill="#3282FF">
|
||||||
<g id="Group-Copy" transform="translate(0.000000, 0.218255)">
|
<path d="M12.5251415,24.7888094 C5.60769686,24.7888094 1.77635684e-14,19.2396454 1.77635684e-14,12.3944047 C1.77635684e-14,5.54916401 5.60769686,-4.26325641e-14 12.5251415,-4.26325641e-14 C19.4425861,-4.26325641e-14 25.050283,5.54916401 25.050283,12.3944047 C25.050283,19.2396454 19.4425861,24.7888094 12.5251415,24.7888094 Z M17.9438152,10.4266859 C17.7896596,9.07601357 16.6334927,8.04314651 15.2460924,8.04314651 C14.8607034,8.04314651 14.5523922,8.12259783 14.244081,8.28150044 C13.5503809,7.16918208 12.3171362,6.45412027 11.0068137,6.45412027 C8.84863548,6.45412027 7.15292402,8.20204914 7.15292402,10.4266859 C7.15292402,10.4266859 7.15292402,10.4266859 7.15292402,10.5061372 C5.84260152,10.6650398 4.8405902,11.8568095 4.8405902,13.2074818 C4.8405902,14.7170567 6.0738349,15.9882777 7.53831299,15.9882777 C8.69447989,15.9882777 16.1710259,15.9882777 17.5584262,15.9882777 C19.0229043,15.9882777 20.256149,14.7170567 20.256149,13.2074818 C20.256149,11.7773582 19.2541376,10.6650398 17.9438152,10.4266859 Z" id="Combined-Shape"></path>
|
||||||
<g id="UM3">
|
|
||||||
<path d="M151.645765,136.481718 C149.925833,142.069331 149,148.004918 149,154.156745 C149,157.125641 149.215633,160.044173 149.632091,162.897534 L94.5646946,162.897534 C92.6599327,163.073639 90.7551708,163.778061 89.3698894,165.010799 L88.3309283,166.067432 C87.2919673,167.828487 85.2140452,168.180697 83.1361231,168.180697 L73.7854738,168.180697 C73.2659933,168.180697 72.7465127,167.652381 72.7465127,167.124065 L72.7465127,152.918227 L19.3939394,152.918227 C17.7008177,153.074764 16.007696,153.700917 14.7763348,154.796684 L13.8528138,155.735914 C12.9292929,157.301296 11.0822511,157.614372 9.23520921,157.614372 L0.923520928,157.614372 C0.461760462,157.614372 1.42108547e-13,157.144757 1.42108547e-13,156.675142 L1.42108547e-13,9.0596475 C1.42108547e-13,8.59003299 0.461760462,8.12041848 0.923520928,8.12041848 L72.7465127,8.12041848 L72.7465127,1.05663265 C72.7465127,0.528316328 73.2659933,-2.84217094e-14 73.7854738,-2.84217094e-14 L216.815777,-2.84217094e-14 C217.335257,-2.84217094e-14 217.854738,0.528316328 217.854738,1.05663265 L217.854738,8.12041848 L289.677732,8.12041848 C290.139493,8.12041848 290.601253,8.59003299 290.601253,9.0596475 L290.601253,156.675142 C290.601253,157.144757 290.139493,157.614372 289.677732,157.614372 L281.366043,157.614372 C279.672922,157.457833 277.9798,156.831681 276.748439,155.735914 L275.824918,154.796684 C274.901397,153.231303 273.054355,152.918227 271.207313,152.918227 L268.987471,152.918227 C268.818229,144.560013 266.939851,136.621364 263.687558,129.437501 L268.590671,129.437501 C272.900435,129.437501 276.440598,125.837123 276.440598,121.454054 L276.594519,121.454054 L276.594519,24.5569264 C276.594519,22.8350065 275.209237,21.5827012 273.670035,21.5827012 L217.854738,21.5827012 L217.854738,94.8055789 C214.965144,94.3781586 212.008429,94.1567452 209,94.1567452 C206.665515,94.1567452 204.362169,94.2900687 202.097162,94.5495169 L202.097162,21.5827012 L202.097162,18.4910714 C202.097162,16.5539116 200.538721,15.145068 198.807119,15.145068 L161.616164,15.145068 L128.985089,15.145068 L91.6209717,15.145068 C89.7162098,15.145068 88.3309283,16.730017 88.3309283,18.4910714 L88.3309283,21.5827012 L88.3309283,127.50034 C88.3309283,128.164624 88.4032089,128.812928 88.5401496,129.437501 C89.4197136,133.449106 92.9667866,136.481718 97.1620971,136.481718 L128.985089,136.481718 L151.645765,136.481718 Z M72.7465127,129.437501 L72.7465127,21.5827012 L16.7772968,21.5827012 C15.0841751,21.5827012 13.8528138,22.9915447 13.8528138,24.5569264 L13.8528138,121.454054 C13.8528138,125.837123 17.3929774,129.437501 21.7027417,129.437501 L72.7465127,129.437501 Z" id="Combined-Shape"></path>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g id="Group-cloud" transform="translate(156.000000, 103.000000)">
|
|
||||||
<g id="Group" transform="translate(0.394933, 0.724044)">
|
|
||||||
<circle id="Oval-Copy" fill="#3282FF" cx="52.6050671" cy="52.601776" r="52.1311475"></circle>
|
|
||||||
<path d="M74.8002981,45.4035747 C74.1684054,39.8133538 69.4292101,35.538479 63.7421759,35.538479 C62.1624441,35.538479 60.8986587,35.8673156 59.6348733,36.5249886 C56.7913562,31.9212773 51.7362146,28.9617486 46.3651267,28.9617486 C37.5186289,28.9617486 30.5678092,36.1961521 30.5678092,45.4035747 C30.5678092,45.4035747 30.5678092,45.4035747 30.5678092,45.7324112 C25.1967213,46.3900842 21.0894188,51.322632 21.0894188,56.9128529 C21.0894188,63.1607468 26.1445604,68.4221311 32.147541,68.4221311 C36.8867362,68.4221311 67.533532,68.4221311 73.2205663,68.4221311 C79.2235469,68.4221311 84.2786885,63.1607468 84.2786885,56.9128529 C84.2786885,50.9937956 80.171386,46.3900842 74.8002981,45.4035747 Z" id="Path" fill="#FFFFFF"></path>
|
|
||||||
</g>
|
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
|
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.2 KiB |
|
@ -1,8 +1,9 @@
|
||||||
import webbrowser
|
import webbrowser
|
||||||
|
from datetime import datetime
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
from UM.Preferences import Preferences
|
from UM.Preferences import Preferences
|
||||||
from cura.OAuth2.AuthorizationHelpers import AuthorizationHelpers
|
from cura.OAuth2.AuthorizationHelpers import AuthorizationHelpers, TOKEN_TIMESTAMP_FORMAT
|
||||||
from cura.OAuth2.AuthorizationService import AuthorizationService
|
from cura.OAuth2.AuthorizationService import AuthorizationService
|
||||||
from cura.OAuth2.LocalAuthorizationServer import LocalAuthorizationServer
|
from cura.OAuth2.LocalAuthorizationServer import LocalAuthorizationServer
|
||||||
from cura.OAuth2.Models import OAuth2Settings, AuthenticationResponse, UserProfile
|
from cura.OAuth2.Models import OAuth2Settings, AuthenticationResponse, UserProfile
|
||||||
|
@ -12,19 +13,27 @@ OAUTH_ROOT = "https://account.ultimaker.com"
|
||||||
CLOUD_API_ROOT = "https://api.ultimaker.com"
|
CLOUD_API_ROOT = "https://api.ultimaker.com"
|
||||||
|
|
||||||
OAUTH_SETTINGS = OAuth2Settings(
|
OAUTH_SETTINGS = OAuth2Settings(
|
||||||
OAUTH_SERVER_URL= OAUTH_ROOT,
|
OAUTH_SERVER_URL= OAUTH_ROOT,
|
||||||
CALLBACK_PORT=CALLBACK_PORT,
|
CALLBACK_PORT=CALLBACK_PORT,
|
||||||
CALLBACK_URL="http://localhost:{}/callback".format(CALLBACK_PORT),
|
CALLBACK_URL="http://localhost:{}/callback".format(CALLBACK_PORT),
|
||||||
CLIENT_ID="",
|
CLIENT_ID="",
|
||||||
CLIENT_SCOPES="",
|
CLIENT_SCOPES="",
|
||||||
AUTH_DATA_PREFERENCE_KEY="test/auth_data",
|
AUTH_DATA_PREFERENCE_KEY="test/auth_data",
|
||||||
AUTH_SUCCESS_REDIRECT="{}/app/auth-success".format(OAUTH_ROOT),
|
AUTH_SUCCESS_REDIRECT="{}/app/auth-success".format(OAUTH_ROOT),
|
||||||
AUTH_FAILED_REDIRECT="{}/app/auth-error".format(OAUTH_ROOT)
|
AUTH_FAILED_REDIRECT="{}/app/auth-error".format(OAUTH_ROOT)
|
||||||
)
|
)
|
||||||
|
|
||||||
FAILED_AUTH_RESPONSE = AuthenticationResponse(success = False, err_message = "FAILURE!")
|
FAILED_AUTH_RESPONSE = AuthenticationResponse(
|
||||||
|
success = False,
|
||||||
|
err_message = "FAILURE!"
|
||||||
|
)
|
||||||
|
|
||||||
SUCCESFULL_AUTH_RESPONSE = AuthenticationResponse(access_token = "beep", refresh_token = "beep?")
|
SUCCESSFUL_AUTH_RESPONSE = AuthenticationResponse(
|
||||||
|
access_token = "beep",
|
||||||
|
refresh_token = "beep?",
|
||||||
|
received_at = datetime.now().strftime(TOKEN_TIMESTAMP_FORMAT),
|
||||||
|
expires_in = 300 # 5 minutes should be more than enough for testing
|
||||||
|
)
|
||||||
|
|
||||||
MALFORMED_AUTH_RESPONSE = AuthenticationResponse()
|
MALFORMED_AUTH_RESPONSE = AuthenticationResponse()
|
||||||
|
|
||||||
|
@ -64,7 +73,7 @@ def test_storeAuthData(get_user_profile) -> None:
|
||||||
authorization_service.initialize()
|
authorization_service.initialize()
|
||||||
|
|
||||||
# Write stuff to the preferences.
|
# Write stuff to the preferences.
|
||||||
authorization_service._storeAuthData(SUCCESFULL_AUTH_RESPONSE)
|
authorization_service._storeAuthData(SUCCESSFUL_AUTH_RESPONSE)
|
||||||
preference_value = preferences.getValue(OAUTH_SETTINGS.AUTH_DATA_PREFERENCE_KEY)
|
preference_value = preferences.getValue(OAUTH_SETTINGS.AUTH_DATA_PREFERENCE_KEY)
|
||||||
# Check that something was actually put in the preferences
|
# Check that something was actually put in the preferences
|
||||||
assert preference_value is not None and preference_value != {}
|
assert preference_value is not None and preference_value != {}
|
||||||
|
@ -73,7 +82,7 @@ def test_storeAuthData(get_user_profile) -> None:
|
||||||
second_auth_service = AuthorizationService(OAUTH_SETTINGS, preferences)
|
second_auth_service = AuthorizationService(OAUTH_SETTINGS, preferences)
|
||||||
second_auth_service.initialize()
|
second_auth_service.initialize()
|
||||||
second_auth_service.loadAuthDataFromPreferences()
|
second_auth_service.loadAuthDataFromPreferences()
|
||||||
assert second_auth_service.getAccessToken() == SUCCESFULL_AUTH_RESPONSE.access_token
|
assert second_auth_service.getAccessToken() == SUCCESSFUL_AUTH_RESPONSE.access_token
|
||||||
|
|
||||||
|
|
||||||
@patch.object(LocalAuthorizationServer, "stop")
|
@patch.object(LocalAuthorizationServer, "stop")
|
||||||
|
@ -101,9 +110,9 @@ def test_loginAndLogout() -> None:
|
||||||
authorization_service.onAuthStateChanged.emit = MagicMock()
|
authorization_service.onAuthStateChanged.emit = MagicMock()
|
||||||
authorization_service.initialize()
|
authorization_service.initialize()
|
||||||
|
|
||||||
# Let the service think there was a succesfull response
|
# Let the service think there was a successful response
|
||||||
with patch.object(AuthorizationHelpers, "parseJWT", return_value=UserProfile()):
|
with patch.object(AuthorizationHelpers, "parseJWT", return_value=UserProfile()):
|
||||||
authorization_service._onAuthStateChanged(SUCCESFULL_AUTH_RESPONSE)
|
authorization_service._onAuthStateChanged(SUCCESSFUL_AUTH_RESPONSE)
|
||||||
|
|
||||||
# Ensure that the error signal was not triggered
|
# Ensure that the error signal was not triggered
|
||||||
assert authorization_service.onAuthenticationError.emit.call_count == 0
|
assert authorization_service.onAuthenticationError.emit.call_count == 0
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue