mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-07-22 06:03:57 -06:00
Use HttpRequestManager to acquire new tokens
This is a re-write from a previous attempt. Instead of requests, which doesn't properly use SSL certificates installed on the computer among other things, we'll now use the HttpRequestManager which uses QNetworkManager under the hood and properly uses system settings. The QNetworkManager is asynchronous which would normally be very nice, but due to the nature of this call we want to make it synchronous so we'll use a lock here. Contributes to issue CURA-8539.
This commit is contained in:
parent
a292524af6
commit
f1c763ad9f
1 changed files with 55 additions and 28 deletions
|
@ -1,16 +1,19 @@
|
||||||
# Copyright (c) 2021 Ultimaker B.V.
|
# Copyright (c) 2021 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
|
|
||||||
import json
|
|
||||||
import secrets
|
|
||||||
from hashlib import sha512
|
|
||||||
from base64 import b64encode
|
from base64 import b64encode
|
||||||
|
from datetime import datetime
|
||||||
|
from hashlib import sha512
|
||||||
|
from PyQt5.QtNetwork import QNetworkReply
|
||||||
|
import secrets
|
||||||
|
from threading import Lock
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
import requests
|
import requests
|
||||||
|
import urllib.parse
|
||||||
|
|
||||||
from UM.i18n import i18nCatalog
|
from UM.i18n import i18nCatalog
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
|
from UM.TaskManagement.HttpRequestManager import HttpRequestManager # To download log-in tokens.
|
||||||
|
|
||||||
from cura.OAuth2.Models import AuthenticationResponse, UserProfile, OAuth2Settings
|
from cura.OAuth2.Models import AuthenticationResponse, UserProfile, OAuth2Settings
|
||||||
catalog = i18nCatalog("cura")
|
catalog = i18nCatalog("cura")
|
||||||
|
@ -23,6 +26,8 @@ class AuthorizationHelpers:
|
||||||
def __init__(self, settings: "OAuth2Settings") -> None:
|
def __init__(self, settings: "OAuth2Settings") -> None:
|
||||||
self._settings = settings
|
self._settings = settings
|
||||||
self._token_url = "{}/token".format(self._settings.OAUTH_SERVER_URL)
|
self._token_url = "{}/token".format(self._settings.OAUTH_SERVER_URL)
|
||||||
|
self._request_lock = Lock()
|
||||||
|
self._auth_response = None # type: Optional[AuthenticationResponse]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def settings(self) -> "OAuth2Settings":
|
def settings(self) -> "OAuth2Settings":
|
||||||
|
@ -46,10 +51,19 @@ class AuthorizationHelpers:
|
||||||
"code_verifier": verification_code,
|
"code_verifier": verification_code,
|
||||||
"scope": self._settings.CLIENT_SCOPES if self._settings.CLIENT_SCOPES is not None else "",
|
"scope": self._settings.CLIENT_SCOPES if self._settings.CLIENT_SCOPES is not None else "",
|
||||||
}
|
}
|
||||||
try:
|
headers = {"Content-type": "application/x-www-form-urlencoded"}
|
||||||
return self.parseTokenResponse(requests.post(self._token_url, data = data)) # type: ignore
|
self._request_lock.acquire()
|
||||||
except requests.exceptions.ConnectionError as connection_error:
|
HttpRequestManager.getInstance().post(
|
||||||
return AuthenticationResponse(success = False, err_message = f"Unable to connect to remote server: {connection_error}")
|
self._token_url,
|
||||||
|
data = urllib.parse.urlencode(data).encode("UTF-8"),
|
||||||
|
headers_dict = headers,
|
||||||
|
callback = self.parseTokenResponse
|
||||||
|
)
|
||||||
|
self._request_lock.acquire(timeout = 60) # Block until the request is completed. 1 minute timeout.
|
||||||
|
response = self._auth_response
|
||||||
|
self._auth_response = None
|
||||||
|
self._request_lock.release()
|
||||||
|
return response
|
||||||
|
|
||||||
def getAccessTokenUsingRefreshToken(self, refresh_token: str) -> "AuthenticationResponse":
|
def getAccessTokenUsingRefreshToken(self, refresh_token: str) -> "AuthenticationResponse":
|
||||||
"""Request the access token from the authorization server using a refresh token.
|
"""Request the access token from the authorization server using a refresh token.
|
||||||
|
@ -66,15 +80,21 @@ class AuthorizationHelpers:
|
||||||
"refresh_token": refresh_token,
|
"refresh_token": refresh_token,
|
||||||
"scope": self._settings.CLIENT_SCOPES if self._settings.CLIENT_SCOPES is not None else "",
|
"scope": self._settings.CLIENT_SCOPES if self._settings.CLIENT_SCOPES is not None else "",
|
||||||
}
|
}
|
||||||
try:
|
headers = {"Content-type": "application/x-www-form-urlencoded"}
|
||||||
return self.parseTokenResponse(requests.post(self._token_url, data = data)) # type: ignore
|
self._request_lock.acquire()
|
||||||
except requests.exceptions.ConnectionError:
|
HttpRequestManager.getInstance().post(
|
||||||
return AuthenticationResponse(success = False, err_message = "Unable to connect to remote server")
|
self._token_url,
|
||||||
except OSError as e:
|
data = urllib.parse.urlencode(data).encode("UTF-8"),
|
||||||
return AuthenticationResponse(success = False, err_message = "Operating system is unable to set up a secure connection: {err}".format(err = str(e)))
|
headers_dict = headers,
|
||||||
|
callback = self.parseTokenResponse
|
||||||
|
)
|
||||||
|
self._request_lock.acquire(timeout = 60) # Block until the request is completed. 1 minute timeout.
|
||||||
|
response = self._auth_response
|
||||||
|
self._auth_response = None
|
||||||
|
self._request_lock.release()
|
||||||
|
return response
|
||||||
|
|
||||||
@staticmethod
|
def parseTokenResponse(self, token_response: QNetworkReply) -> None:
|
||||||
def parseTokenResponse(token_response: requests.models.Response) -> "AuthenticationResponse":
|
|
||||||
"""Parse the token response from the authorization server into an AuthenticationResponse object.
|
"""Parse the token response from the authorization server into an AuthenticationResponse object.
|
||||||
|
|
||||||
:param token_response: The JSON string data response from the authorization server.
|
:param token_response: The JSON string data response from the authorization server.
|
||||||
|
@ -82,25 +102,32 @@ class AuthorizationHelpers:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
token_data = None
|
token_data = None
|
||||||
|
http = HttpRequestManager.getInstance()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
token_data = json.loads(token_response.text)
|
token_data = http.readJSON(token_response)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
Logger.log("w", "Could not parse token response data: %s", token_response.text)
|
Logger.log("w", "Could not parse token response data: %s", http.readText(token_response))
|
||||||
|
|
||||||
if not token_data:
|
if not token_data:
|
||||||
return AuthenticationResponse(success = False, err_message = catalog.i18nc("@message", "Could not read response."))
|
self._auth_response = AuthenticationResponse(success = False, err_message = catalog.i18nc("@message", "Could not read response."))
|
||||||
|
self._request_lock.release()
|
||||||
|
return
|
||||||
|
|
||||||
if token_response.status_code not in (200, 201):
|
if token_response.error() != QNetworkReply.NetworkError.NoError:
|
||||||
return AuthenticationResponse(success = False, err_message = token_data["error_description"])
|
self._auth_response = AuthenticationResponse(success = False, err_message = token_data["error_description"])
|
||||||
|
self._request_lock.release()
|
||||||
|
return
|
||||||
|
|
||||||
return AuthenticationResponse(success=True,
|
self._auth_response = 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))
|
received_at = datetime.now().strftime(TOKEN_TIMESTAMP_FORMAT))
|
||||||
|
self._request_lock.release()
|
||||||
|
return
|
||||||
|
|
||||||
def parseJWT(self, access_token: str) -> Optional["UserProfile"]:
|
def parseJWT(self, access_token: str) -> Optional["UserProfile"]:
|
||||||
"""Calls the authentication API endpoint to get the token data.
|
"""Calls the authentication API endpoint to get the token data.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue