Merge branch '4.0' of github.com:Ultimaker/Cura

This commit is contained in:
Jaime van Kessel 2019-02-14 10:53:01 +01:00
commit a8f66a558c
15 changed files with 112 additions and 79 deletions

View file

@ -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()

View file

@ -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:
@ -77,7 +79,8 @@ class AuthorizationHelpers:
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.

View file

@ -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]

View file

@ -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:

View file

@ -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.

View file

@ -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,15 +547,19 @@ 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:
return self._global_container_stack.getMetaDataEntry("um_network_key", "") return self._global_container_stack.getMetaDataEntry("um_network_key", "")

View file

@ -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

View file

@ -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

View file

@ -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.

View file

@ -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

View file

@ -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

View file

@ -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

View 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

View file

@ -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

Before After
Before After

View file

@ -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
@ -22,9 +23,17 @@ OAUTH_SETTINGS = OAuth2Settings(
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