|
@@ -1,4 +1,4 @@
|
|
|
-# Copyright (c) 2021 Ultimaker B.V.
|
|
|
+# Copyright (c) 2024 UltiMaker
|
|
|
# Cura is released under the terms of the LGPLv3 or higher.
|
|
|
|
|
|
import json
|
|
@@ -6,13 +6,14 @@ from datetime import datetime, timedelta
|
|
|
from typing import Callable, Dict, Optional, TYPE_CHECKING, Union
|
|
|
from urllib.parse import urlencode, quote_plus
|
|
|
|
|
|
-from PyQt6.QtCore import QUrl
|
|
|
+from PyQt6.QtCore import QUrl, QTimer
|
|
|
from PyQt6.QtGui import QDesktopServices
|
|
|
|
|
|
from UM.Logger import Logger
|
|
|
from UM.Message import Message
|
|
|
from UM.Signal import Signal
|
|
|
from UM.i18n import i18nCatalog
|
|
|
+from UM.TaskManagement.HttpRequestManager import HttpRequestManager # To download log-in tokens.
|
|
|
from cura.OAuth2.AuthorizationHelpers import AuthorizationHelpers, TOKEN_TIMESTAMP_FORMAT
|
|
|
from cura.OAuth2.LocalAuthorizationServer import LocalAuthorizationServer
|
|
|
from cura.OAuth2.Models import AuthenticationResponse, BaseModel
|
|
@@ -25,6 +26,8 @@ if TYPE_CHECKING:
|
|
|
|
|
|
MYCLOUD_LOGOFF_URL = "https://account.ultimaker.com/logoff?utm_source=cura&utm_medium=software&utm_campaign=change-account-before-adding-printers"
|
|
|
|
|
|
+REFRESH_TOKEN_MAX_RETRIES = 15
|
|
|
+REFRESH_TOKEN_RETRY_INTERVAL = 1000
|
|
|
|
|
|
class AuthorizationService:
|
|
|
"""The authorization service is responsible for handling the login flow, storing user credentials and providing
|
|
@@ -57,6 +60,12 @@ class AuthorizationService:
|
|
|
|
|
|
self.onAuthStateChanged.connect(self._authChanged)
|
|
|
|
|
|
+ self._refresh_token_retries = 0
|
|
|
+ self._refresh_token_retry_timer = QTimer()
|
|
|
+ self._refresh_token_retry_timer.setInterval(REFRESH_TOKEN_RETRY_INTERVAL)
|
|
|
+ self._refresh_token_retry_timer.setSingleShot(True)
|
|
|
+ self._refresh_token_retry_timer.timeout.connect(self.refreshAccessToken)
|
|
|
+
|
|
|
def _authChanged(self, logged_in):
|
|
|
if logged_in and self._unable_to_get_data_message is not None:
|
|
|
self._unable_to_get_data_message.hide()
|
|
@@ -167,16 +176,29 @@ class AuthorizationService:
|
|
|
return
|
|
|
|
|
|
def process_auth_data(response: AuthenticationResponse) -> None:
|
|
|
+ self._currently_refreshing_token = False
|
|
|
+
|
|
|
if response.success:
|
|
|
+ self._refresh_token_retries = 0
|
|
|
self._storeAuthData(response)
|
|
|
+ HttpRequestManager.getInstance().setDelayRequests(False)
|
|
|
self.onAuthStateChanged.emit(logged_in = True)
|
|
|
else:
|
|
|
- Logger.warning("Failed to get a new access token from the server.")
|
|
|
- self.onAuthStateChanged.emit(logged_in = False)
|
|
|
+ if self._refresh_token_retries >= REFRESH_TOKEN_MAX_RETRIES:
|
|
|
+ self._refresh_token_retries = 0
|
|
|
+ Logger.warning("Failed to get a new access token from the server, giving up.")
|
|
|
+ HttpRequestManager.getInstance().setDelayRequests(False)
|
|
|
+ self.onAuthStateChanged.emit(logged_in = False)
|
|
|
+ else:
|
|
|
+ # Retry a bit later, network may be offline right now and will hopefully be back soon
|
|
|
+ Logger.warning("Failed to get a new access token from the server, retrying later.")
|
|
|
+ self._refresh_token_retries += 1
|
|
|
+ self._refresh_token_retry_timer.start()
|
|
|
|
|
|
if self._currently_refreshing_token:
|
|
|
Logger.debug("Was already busy refreshing token. Do not start a new request.")
|
|
|
return
|
|
|
+ HttpRequestManager.getInstance().setDelayRequests(True)
|
|
|
self._currently_refreshing_token = True
|
|
|
self._auth_helpers.getAccessTokenUsingRefreshToken(self._auth_data.refresh_token, process_auth_data)
|
|
|
|