Merge pull request #17967 from Ultimaker/CURA-11406_fix_auth_refresh_after_sleep

Add retry to OAuth token refresh failure
This commit is contained in:
Remco Burema 2024-02-14 17:12:54 +01:00 committed by GitHub
commit 015ce80a9f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 34 additions and 7 deletions

View file

@ -16,6 +16,7 @@ from UM.TaskManagement.HttpRequestManager import HttpRequestManager # To downlo
catalog = i18nCatalog("cura")
TOKEN_TIMESTAMP_FORMAT = "%Y-%m-%d %H:%M:%S"
REQUEST_TIMEOUT = 5 # Seconds
class AuthorizationHelpers:
@ -53,7 +54,8 @@ class AuthorizationHelpers:
data = urllib.parse.urlencode(data).encode("UTF-8"),
headers_dict = headers,
callback = lambda response: self.parseTokenResponse(response, callback),
error_callback = lambda response, _: self.parseTokenResponse(response, callback)
error_callback = lambda response, _: self.parseTokenResponse(response, callback),
timeout = REQUEST_TIMEOUT
)
def getAccessTokenUsingRefreshToken(self, refresh_token: str, callback: Callable[[AuthenticationResponse], None]) -> None:
@ -77,7 +79,9 @@ class AuthorizationHelpers:
data = urllib.parse.urlencode(data).encode("UTF-8"),
headers_dict = headers,
callback = lambda response: self.parseTokenResponse(response, callback),
error_callback = lambda response, _: self.parseTokenResponse(response, callback)
error_callback = lambda response, _: self.parseTokenResponse(response, callback),
urgent = True,
timeout = REQUEST_TIMEOUT
)
def parseTokenResponse(self, token_response: QNetworkReply, callback: Callable[[AuthenticationResponse], None]) -> None:
@ -122,7 +126,8 @@ class AuthorizationHelpers:
check_token_url,
headers_dict = headers,
callback = lambda reply: self._parseUserProfile(reply, success_callback, failed_callback),
error_callback = lambda _, _2: failed_callback() if failed_callback is not None else None
error_callback = lambda _, _2: failed_callback() if failed_callback is not None else None,
timeout = REQUEST_TIMEOUT
)
def _parseUserProfile(self, reply: QNetworkReply, success_callback: Optional[Callable[[UserProfile], None]], failed_callback: Optional[Callable[[], None]] = None) -> None:

View file

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