mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-07-20 21:27:50 -06:00
Updated comments in cura/OAuth2
Used DOX_2_RST.py to convert doxygen style comments to sphinx style comments
This commit is contained in:
parent
a503149452
commit
b032101e55
6 changed files with 135 additions and 71 deletions
|
@ -16,23 +16,27 @@ from cura.OAuth2.Models import AuthenticationResponse, UserProfile, OAuth2Settin
|
||||||
catalog = i18nCatalog("cura")
|
catalog = i18nCatalog("cura")
|
||||||
TOKEN_TIMESTAMP_FORMAT = "%Y-%m-%d %H:%M:%S"
|
TOKEN_TIMESTAMP_FORMAT = "%Y-%m-%d %H:%M:%S"
|
||||||
|
|
||||||
## Class containing several helpers to deal with the authorization flow.
|
|
||||||
class AuthorizationHelpers:
|
class AuthorizationHelpers:
|
||||||
|
"""Class containing several helpers to deal with the authorization flow."""
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
## The OAuth2 settings object.
|
|
||||||
def settings(self) -> "OAuth2Settings":
|
def settings(self) -> "OAuth2Settings":
|
||||||
|
"""The OAuth2 settings object."""
|
||||||
|
|
||||||
return self._settings
|
return self._settings
|
||||||
|
|
||||||
## Request the access token from the authorization server.
|
|
||||||
# \param authorization_code: The authorization code from the 1st step.
|
|
||||||
# \param verification_code: The verification code needed for the PKCE
|
|
||||||
# extension.
|
|
||||||
# \return An AuthenticationResponse object.
|
|
||||||
def getAccessTokenUsingAuthorizationCode(self, authorization_code: str, verification_code: str) -> "AuthenticationResponse":
|
def getAccessTokenUsingAuthorizationCode(self, authorization_code: str, verification_code: str) -> "AuthenticationResponse":
|
||||||
|
"""Request the access token from the authorization server.
|
||||||
|
|
||||||
|
:param authorization_code: The authorization code from the 1st step.
|
||||||
|
:param verification_code: The verification code needed for the PKCE extension.
|
||||||
|
:return: An AuthenticationResponse object.
|
||||||
|
"""
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
"client_id": self._settings.CLIENT_ID if self._settings.CLIENT_ID is not None else "",
|
"client_id": self._settings.CLIENT_ID if self._settings.CLIENT_ID is not None else "",
|
||||||
"redirect_uri": self._settings.CALLBACK_URL if self._settings.CALLBACK_URL is not None else "",
|
"redirect_uri": self._settings.CALLBACK_URL if self._settings.CALLBACK_URL is not None else "",
|
||||||
|
@ -46,10 +50,13 @@ class AuthorizationHelpers:
|
||||||
except requests.exceptions.ConnectionError:
|
except requests.exceptions.ConnectionError:
|
||||||
return AuthenticationResponse(success=False, err_message="Unable to connect to remote server")
|
return AuthenticationResponse(success=False, err_message="Unable to connect to remote server")
|
||||||
|
|
||||||
## Request the access token from the authorization server using a refresh token.
|
|
||||||
# \param refresh_token:
|
|
||||||
# \return An AuthenticationResponse object.
|
|
||||||
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.
|
||||||
|
|
||||||
|
:param refresh_token:
|
||||||
|
:return: An AuthenticationResponse object.
|
||||||
|
"""
|
||||||
|
|
||||||
Logger.log("d", "Refreshing the access token.")
|
Logger.log("d", "Refreshing the access token.")
|
||||||
data = {
|
data = {
|
||||||
"client_id": self._settings.CLIENT_ID if self._settings.CLIENT_ID is not None else "",
|
"client_id": self._settings.CLIENT_ID if self._settings.CLIENT_ID is not None else "",
|
||||||
|
@ -64,10 +71,13 @@ class AuthorizationHelpers:
|
||||||
return AuthenticationResponse(success=False, err_message="Unable to connect to remote server")
|
return AuthenticationResponse(success=False, err_message="Unable to connect to remote server")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
## Parse the token response from the authorization server into an AuthenticationResponse object.
|
|
||||||
# \param token_response: The JSON string data response from the authorization server.
|
|
||||||
# \return An AuthenticationResponse object.
|
|
||||||
def parseTokenResponse(token_response: requests.models.Response) -> "AuthenticationResponse":
|
def parseTokenResponse(token_response: requests.models.Response) -> "AuthenticationResponse":
|
||||||
|
"""Parse the token response from the authorization server into an AuthenticationResponse object.
|
||||||
|
|
||||||
|
:param token_response: The JSON string data response from the authorization server.
|
||||||
|
:return: An AuthenticationResponse object.
|
||||||
|
"""
|
||||||
|
|
||||||
token_data = None
|
token_data = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -89,10 +99,13 @@ class AuthorizationHelpers:
|
||||||
scope=token_data["scope"],
|
scope=token_data["scope"],
|
||||||
received_at=datetime.now().strftime(TOKEN_TIMESTAMP_FORMAT))
|
received_at=datetime.now().strftime(TOKEN_TIMESTAMP_FORMAT))
|
||||||
|
|
||||||
## Calls the authentication API endpoint to get the token data.
|
|
||||||
# \param access_token: The encoded JWT token.
|
|
||||||
# \return Dict containing some profile data.
|
|
||||||
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.
|
||||||
|
|
||||||
|
:param access_token: The encoded JWT token.
|
||||||
|
:return: Dict containing some profile data.
|
||||||
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
token_request = requests.get("{}/check-token".format(self._settings.OAUTH_SERVER_URL), headers = {
|
token_request = requests.get("{}/check-token".format(self._settings.OAUTH_SERVER_URL), headers = {
|
||||||
"Authorization": "Bearer {}".format(access_token)
|
"Authorization": "Bearer {}".format(access_token)
|
||||||
|
@ -115,16 +128,22 @@ class AuthorizationHelpers:
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
## Generate a verification code of arbitrary length.
|
|
||||||
# \param code_length: How long should the code be? This should never be lower than 16, but it's probably better to
|
|
||||||
# leave it at 32
|
|
||||||
def generateVerificationCode(code_length: int = 32) -> str:
|
def generateVerificationCode(code_length: int = 32) -> str:
|
||||||
|
"""Generate a verification code of arbitrary length.
|
||||||
|
|
||||||
|
:param code_length:: How long should the code be? This should never be lower than 16, but it's probably
|
||||||
|
better to leave it at 32
|
||||||
|
"""
|
||||||
|
|
||||||
return "".join(random.choice("0123456789ABCDEF") for i in range(code_length))
|
return "".join(random.choice("0123456789ABCDEF") for i in range(code_length))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
## Generates a base64 encoded sha512 encrypted version of a given string.
|
|
||||||
# \param verification_code:
|
|
||||||
# \return The encrypted code in base64 format.
|
|
||||||
def generateVerificationCodeChallenge(verification_code: str) -> str:
|
def generateVerificationCodeChallenge(verification_code: str) -> str:
|
||||||
|
"""Generates a base64 encoded sha512 encrypted version of a given string.
|
||||||
|
|
||||||
|
:param verification_code:
|
||||||
|
:return: The encrypted code in base64 format.
|
||||||
|
"""
|
||||||
|
|
||||||
encoded = sha512(verification_code.encode()).digest()
|
encoded = sha512(verification_code.encode()).digest()
|
||||||
return b64encode(encoded, altchars = b"_-").decode()
|
return b64encode(encoded, altchars = b"_-").decode()
|
||||||
|
|
|
@ -14,9 +14,12 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
catalog = i18nCatalog("cura")
|
catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
## This handler handles all HTTP requests on the local web server.
|
|
||||||
# It also requests the access token for the 2nd stage of the OAuth flow.
|
|
||||||
class AuthorizationRequestHandler(BaseHTTPRequestHandler):
|
class AuthorizationRequestHandler(BaseHTTPRequestHandler):
|
||||||
|
"""This handler handles all HTTP requests on the local web server.
|
||||||
|
|
||||||
|
It also requests the access token for the 2nd stage of the OAuth flow.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, request, client_address, server) -> None:
|
def __init__(self, request, client_address, server) -> None:
|
||||||
super().__init__(request, client_address, server)
|
super().__init__(request, client_address, server)
|
||||||
|
|
||||||
|
@ -55,10 +58,13 @@ class AuthorizationRequestHandler(BaseHTTPRequestHandler):
|
||||||
# This will cause the server to shut down, so we do it at the very end of the request handling.
|
# This will cause the server to shut down, so we do it at the very end of the request handling.
|
||||||
self.authorization_callback(token_response)
|
self.authorization_callback(token_response)
|
||||||
|
|
||||||
## Handler for the callback URL redirect.
|
|
||||||
# \param query Dict containing the HTTP query parameters.
|
|
||||||
# \return HTTP ResponseData containing a success page to show to the user.
|
|
||||||
def _handleCallback(self, query: Dict[Any, List]) -> Tuple[ResponseData, Optional[AuthenticationResponse]]:
|
def _handleCallback(self, query: Dict[Any, List]) -> Tuple[ResponseData, Optional[AuthenticationResponse]]:
|
||||||
|
"""Handler for the callback URL redirect.
|
||||||
|
|
||||||
|
:param query: Dict containing the HTTP query parameters.
|
||||||
|
:return: HTTP ResponseData containing a success page to show to the user.
|
||||||
|
"""
|
||||||
|
|
||||||
code = self._queryGet(query, "code")
|
code = self._queryGet(query, "code")
|
||||||
state = self._queryGet(query, "state")
|
state = self._queryGet(query, "state")
|
||||||
if state != self.state:
|
if state != self.state:
|
||||||
|
@ -95,9 +101,10 @@ class AuthorizationRequestHandler(BaseHTTPRequestHandler):
|
||||||
self.authorization_helpers.settings.AUTH_FAILED_REDIRECT
|
self.authorization_helpers.settings.AUTH_FAILED_REDIRECT
|
||||||
), token_response
|
), token_response
|
||||||
|
|
||||||
## Handle all other non-existing server calls.
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _handleNotFound() -> ResponseData:
|
def _handleNotFound() -> ResponseData:
|
||||||
|
"""Handle all other non-existing server calls."""
|
||||||
|
|
||||||
return ResponseData(status = HTTP_STATUS["NOT_FOUND"], content_type = "text/html", data_stream = b"Not found.")
|
return ResponseData(status = HTTP_STATUS["NOT_FOUND"], content_type = "text/html", data_stream = b"Not found.")
|
||||||
|
|
||||||
def _sendHeaders(self, status: "ResponseStatus", content_type: str, redirect_uri: str = None) -> None:
|
def _sendHeaders(self, status: "ResponseStatus", content_type: str, redirect_uri: str = None) -> None:
|
||||||
|
@ -110,7 +117,8 @@ class AuthorizationRequestHandler(BaseHTTPRequestHandler):
|
||||||
def _sendData(self, data: bytes) -> None:
|
def _sendData(self, data: bytes) -> None:
|
||||||
self.wfile.write(data)
|
self.wfile.write(data)
|
||||||
|
|
||||||
## Convenience helper for getting values from a pre-parsed query string
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _queryGet(query_data: Dict[Any, List], key: str, default: Optional[str] = None) -> Optional[str]:
|
def _queryGet(query_data: Dict[Any, List], key: str, default: Optional[str] = None) -> Optional[str]:
|
||||||
|
"""Convenience helper for getting values from a pre-parsed query string"""
|
||||||
|
|
||||||
return query_data.get(key, [default])[0]
|
return query_data.get(key, [default])[0]
|
||||||
|
|
|
@ -9,21 +9,26 @@ if TYPE_CHECKING:
|
||||||
from cura.OAuth2.AuthorizationHelpers import AuthorizationHelpers
|
from cura.OAuth2.AuthorizationHelpers import AuthorizationHelpers
|
||||||
|
|
||||||
|
|
||||||
## The authorization request callback handler server.
|
|
||||||
# This subclass is needed to be able to pass some data to the request handler.
|
|
||||||
# This cannot be done on the request handler directly as the HTTPServer
|
|
||||||
# creates an instance of the handler after init.
|
|
||||||
class AuthorizationRequestServer(HTTPServer):
|
class AuthorizationRequestServer(HTTPServer):
|
||||||
## Set the authorization helpers instance on the request handler.
|
"""The authorization request callback handler server.
|
||||||
|
|
||||||
|
This subclass is needed to be able to pass some data to the request handler. This cannot be done on the request
|
||||||
|
handler directly as the HTTPServer creates an instance of the handler after init.
|
||||||
|
"""
|
||||||
|
|
||||||
def setAuthorizationHelpers(self, authorization_helpers: "AuthorizationHelpers") -> None:
|
def setAuthorizationHelpers(self, authorization_helpers: "AuthorizationHelpers") -> None:
|
||||||
|
"""Set the authorization helpers instance on the request handler."""
|
||||||
|
|
||||||
self.RequestHandlerClass.authorization_helpers = authorization_helpers # type: ignore
|
self.RequestHandlerClass.authorization_helpers = authorization_helpers # type: ignore
|
||||||
|
|
||||||
## Set the authorization callback on the request handler.
|
|
||||||
def setAuthorizationCallback(self, authorization_callback: Callable[["AuthenticationResponse"], Any]) -> None:
|
def setAuthorizationCallback(self, authorization_callback: Callable[["AuthenticationResponse"], Any]) -> None:
|
||||||
|
"""Set the authorization callback on the request handler."""
|
||||||
|
|
||||||
self.RequestHandlerClass.authorization_callback = authorization_callback # type: ignore
|
self.RequestHandlerClass.authorization_callback = authorization_callback # type: ignore
|
||||||
|
|
||||||
## Set the verification code on the request handler.
|
|
||||||
def setVerificationCode(self, verification_code: str) -> None:
|
def setVerificationCode(self, verification_code: str) -> None:
|
||||||
|
"""Set the verification code on the request handler."""
|
||||||
|
|
||||||
self.RequestHandlerClass.verification_code = verification_code # type: ignore
|
self.RequestHandlerClass.verification_code = verification_code # type: ignore
|
||||||
|
|
||||||
def setState(self, state: str) -> None:
|
def setState(self, state: str) -> None:
|
||||||
|
|
|
@ -25,9 +25,11 @@ if TYPE_CHECKING:
|
||||||
from UM.Preferences import Preferences
|
from UM.Preferences import Preferences
|
||||||
|
|
||||||
|
|
||||||
## The authorization service is responsible for handling the login flow,
|
|
||||||
# storing user credentials and providing account information.
|
|
||||||
class AuthorizationService:
|
class AuthorizationService:
|
||||||
|
"""The authorization service is responsible for handling the login flow, storing user credentials and providing
|
||||||
|
account information.
|
||||||
|
"""
|
||||||
|
|
||||||
# Emit signal when authentication is completed.
|
# Emit signal when authentication is completed.
|
||||||
onAuthStateChanged = Signal()
|
onAuthStateChanged = Signal()
|
||||||
|
|
||||||
|
@ -59,11 +61,16 @@ class AuthorizationService:
|
||||||
if self._preferences:
|
if self._preferences:
|
||||||
self._preferences.addPreference(self._settings.AUTH_DATA_PREFERENCE_KEY, "{}")
|
self._preferences.addPreference(self._settings.AUTH_DATA_PREFERENCE_KEY, "{}")
|
||||||
|
|
||||||
## Get the user profile as obtained from the JWT (JSON Web Token).
|
|
||||||
# If the JWT is not yet parsed, calling this will take care of that.
|
|
||||||
# \return UserProfile if a user is logged in, None otherwise.
|
|
||||||
# \sa _parseJWT
|
|
||||||
def getUserProfile(self) -> Optional["UserProfile"]:
|
def getUserProfile(self) -> Optional["UserProfile"]:
|
||||||
|
"""Get the user profile as obtained from the JWT (JSON Web Token).
|
||||||
|
|
||||||
|
If the JWT is not yet parsed, calling this will take care of that.
|
||||||
|
|
||||||
|
:return: UserProfile if a user is logged in, None otherwise.
|
||||||
|
|
||||||
|
See also: :py:method:`cura.OAuth2.AuthorizationService.AuthorizationService._parseJWT`
|
||||||
|
"""
|
||||||
|
|
||||||
if not self._user_profile:
|
if not self._user_profile:
|
||||||
# If no user profile was stored locally, we try to get it from JWT.
|
# If no user profile was stored locally, we try to get it from JWT.
|
||||||
try:
|
try:
|
||||||
|
@ -81,9 +88,12 @@ class AuthorizationService:
|
||||||
|
|
||||||
return self._user_profile
|
return self._user_profile
|
||||||
|
|
||||||
## Tries to parse the JWT (JSON Web Token) data, which it does if all the needed data is there.
|
|
||||||
# \return UserProfile if it was able to parse, None otherwise.
|
|
||||||
def _parseJWT(self) -> Optional["UserProfile"]:
|
def _parseJWT(self) -> Optional["UserProfile"]:
|
||||||
|
"""Tries to parse the JWT (JSON Web Token) data, which it does if all the needed data is there.
|
||||||
|
|
||||||
|
:return: UserProfile if it was able to parse, None otherwise.
|
||||||
|
"""
|
||||||
|
|
||||||
if not self._auth_data or self._auth_data.access_token is None:
|
if not self._auth_data or self._auth_data.access_token is None:
|
||||||
# If no auth data exists, we should always log in again.
|
# If no auth data exists, we should always log in again.
|
||||||
Logger.log("d", "There was no auth data or access token")
|
Logger.log("d", "There was no auth data or access token")
|
||||||
|
@ -106,8 +116,9 @@ class AuthorizationService:
|
||||||
self._storeAuthData(self._auth_data)
|
self._storeAuthData(self._auth_data)
|
||||||
return self._auth_helpers.parseJWT(self._auth_data.access_token)
|
return self._auth_helpers.parseJWT(self._auth_data.access_token)
|
||||||
|
|
||||||
## Get the access token as provided by the repsonse data.
|
|
||||||
def getAccessToken(self) -> Optional[str]:
|
def getAccessToken(self) -> Optional[str]:
|
||||||
|
"""Get the access token as provided by the repsonse data."""
|
||||||
|
|
||||||
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
|
||||||
|
@ -122,8 +133,9 @@ class AuthorizationService:
|
||||||
|
|
||||||
return self._auth_data.access_token if self._auth_data else None
|
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.
|
|
||||||
def refreshAccessToken(self) -> None:
|
def refreshAccessToken(self) -> None:
|
||||||
|
"""Try to refresh the access token. This should be used when it has expired."""
|
||||||
|
|
||||||
if self._auth_data is None or self._auth_data.refresh_token is None:
|
if self._auth_data is None or self._auth_data.refresh_token is None:
|
||||||
Logger.log("w", "Unable to refresh access token, since there is no refresh token.")
|
Logger.log("w", "Unable to refresh access token, since there is no refresh token.")
|
||||||
return
|
return
|
||||||
|
@ -135,14 +147,16 @@ class AuthorizationService:
|
||||||
Logger.log("w", "Failed to get a new access token from the server.")
|
Logger.log("w", "Failed to get a new access token from the server.")
|
||||||
self.onAuthStateChanged.emit(logged_in = False)
|
self.onAuthStateChanged.emit(logged_in = False)
|
||||||
|
|
||||||
## Delete the authentication data that we have stored locally (eg; logout)
|
|
||||||
def deleteAuthData(self) -> None:
|
def deleteAuthData(self) -> None:
|
||||||
|
"""Delete the authentication data that we have stored locally (eg; logout)"""
|
||||||
|
|
||||||
if self._auth_data is not None:
|
if self._auth_data is not None:
|
||||||
self._storeAuthData()
|
self._storeAuthData()
|
||||||
self.onAuthStateChanged.emit(logged_in = False)
|
self.onAuthStateChanged.emit(logged_in = False)
|
||||||
|
|
||||||
## Start the flow to become authenticated. This will start a new webbrowser tap, prompting the user to login.
|
|
||||||
def startAuthorizationFlow(self) -> None:
|
def startAuthorizationFlow(self) -> None:
|
||||||
|
"""Start the flow to become authenticated. This will start a new webbrowser tap, prompting the user to login."""
|
||||||
|
|
||||||
Logger.log("d", "Starting new OAuth2 flow...")
|
Logger.log("d", "Starting new OAuth2 flow...")
|
||||||
|
|
||||||
# Create the tokens needed for the code challenge (PKCE) extension for OAuth2.
|
# Create the tokens needed for the code challenge (PKCE) extension for OAuth2.
|
||||||
|
@ -177,8 +191,9 @@ class AuthorizationService:
|
||||||
QDesktopServices.openUrl(QUrl("{}?{}".format(self._auth_url, query_string)))
|
QDesktopServices.openUrl(QUrl("{}?{}".format(self._auth_url, query_string)))
|
||||||
|
|
||||||
|
|
||||||
## Callback method for the authentication flow.
|
|
||||||
def _onAuthStateChanged(self, auth_response: AuthenticationResponse) -> None:
|
def _onAuthStateChanged(self, auth_response: AuthenticationResponse) -> None:
|
||||||
|
"""Callback method for the authentication flow."""
|
||||||
|
|
||||||
if auth_response.success:
|
if auth_response.success:
|
||||||
self._storeAuthData(auth_response)
|
self._storeAuthData(auth_response)
|
||||||
self.onAuthStateChanged.emit(logged_in = True)
|
self.onAuthStateChanged.emit(logged_in = True)
|
||||||
|
@ -186,8 +201,9 @@ class AuthorizationService:
|
||||||
self.onAuthenticationError.emit(logged_in = False, error_message = auth_response.err_message)
|
self.onAuthenticationError.emit(logged_in = False, error_message = auth_response.err_message)
|
||||||
self._server.stop() # Stop the web server at all times.
|
self._server.stop() # Stop the web server at all times.
|
||||||
|
|
||||||
## Load authentication data from preferences.
|
|
||||||
def loadAuthDataFromPreferences(self) -> None:
|
def loadAuthDataFromPreferences(self) -> None:
|
||||||
|
"""Load authentication data from preferences."""
|
||||||
|
|
||||||
if self._preferences is None:
|
if self._preferences is None:
|
||||||
Logger.log("e", "Unable to load authentication data, since no preference has been set!")
|
Logger.log("e", "Unable to load authentication data, since no preference has been set!")
|
||||||
return
|
return
|
||||||
|
@ -208,8 +224,9 @@ class AuthorizationService:
|
||||||
except ValueError:
|
except ValueError:
|
||||||
Logger.logException("w", "Could not load auth data from preferences")
|
Logger.logException("w", "Could not load auth data from preferences")
|
||||||
|
|
||||||
## Store authentication data in preferences.
|
|
||||||
def _storeAuthData(self, auth_data: Optional[AuthenticationResponse] = None) -> None:
|
def _storeAuthData(self, auth_data: Optional[AuthenticationResponse] = None) -> None:
|
||||||
|
"""Store authentication data in preferences."""
|
||||||
|
|
||||||
Logger.log("d", "Attempting to store the auth data")
|
Logger.log("d", "Attempting to store the auth data")
|
||||||
if self._preferences is None:
|
if self._preferences is None:
|
||||||
Logger.log("e", "Unable to save authentication data, since no preference has been set!")
|
Logger.log("e", "Unable to save authentication data, since no preference has been set!")
|
||||||
|
|
|
@ -20,18 +20,23 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
|
|
||||||
class LocalAuthorizationServer:
|
class LocalAuthorizationServer:
|
||||||
## The local LocalAuthorizationServer takes care of the oauth2 callbacks.
|
|
||||||
# Once the flow is completed, this server should be closed down again by
|
|
||||||
# calling stop()
|
|
||||||
# \param auth_helpers An instance of the authorization helpers class.
|
|
||||||
# \param auth_state_changed_callback A callback function to be called when
|
|
||||||
# the authorization state changes.
|
|
||||||
# \param daemon Whether the server thread should be run in daemon mode.
|
|
||||||
# Note: Daemon threads are abruptly stopped at shutdown. Their resources
|
|
||||||
# (e.g. open files) may never be released.
|
|
||||||
def __init__(self, auth_helpers: "AuthorizationHelpers",
|
def __init__(self, auth_helpers: "AuthorizationHelpers",
|
||||||
auth_state_changed_callback: Callable[["AuthenticationResponse"], Any],
|
auth_state_changed_callback: Callable[["AuthenticationResponse"], Any],
|
||||||
daemon: bool) -> None:
|
daemon: bool) -> None:
|
||||||
|
"""The local LocalAuthorizationServer takes care of the oauth2 callbacks.
|
||||||
|
|
||||||
|
Once the flow is completed, this server should be closed down again by calling
|
||||||
|
:py:meth:`cura.OAuth2.LocalAuthorizationServer.LocalAuthorizationServer.stop()`
|
||||||
|
|
||||||
|
:param auth_helpers: An instance of the authorization helpers class.
|
||||||
|
:param auth_state_changed_callback: A callback function to be called when the authorization state changes.
|
||||||
|
:param daemon: Whether the server thread should be run in daemon mode.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Daemon threads are abruptly stopped at shutdown. Their resources (e.g. open files) may never be released.
|
||||||
|
"""
|
||||||
|
|
||||||
self._web_server = None # type: Optional[AuthorizationRequestServer]
|
self._web_server = None # type: Optional[AuthorizationRequestServer]
|
||||||
self._web_server_thread = None # type: Optional[threading.Thread]
|
self._web_server_thread = None # type: Optional[threading.Thread]
|
||||||
self._web_server_port = auth_helpers.settings.CALLBACK_PORT
|
self._web_server_port = auth_helpers.settings.CALLBACK_PORT
|
||||||
|
@ -39,10 +44,13 @@ class LocalAuthorizationServer:
|
||||||
self._auth_state_changed_callback = auth_state_changed_callback
|
self._auth_state_changed_callback = auth_state_changed_callback
|
||||||
self._daemon = daemon
|
self._daemon = daemon
|
||||||
|
|
||||||
## Starts the local web server to handle the authorization callback.
|
|
||||||
# \param verification_code The verification code part of the OAuth2 client identification.
|
|
||||||
# \param state The unique state code (to ensure that the request we get back is really from the server.
|
|
||||||
def start(self, verification_code: str, state: str) -> None:
|
def start(self, verification_code: str, state: str) -> None:
|
||||||
|
"""Starts the local web server to handle the authorization callback.
|
||||||
|
|
||||||
|
:param verification_code: The verification code part of the OAuth2 client identification.
|
||||||
|
:param state: The unique state code (to ensure that the request we get back is really from the server.
|
||||||
|
"""
|
||||||
|
|
||||||
if self._web_server:
|
if self._web_server:
|
||||||
# If the server is already running (because of a previously aborted auth flow), we don't have to start it.
|
# If the server is already running (because of a previously aborted auth flow), we don't have to start it.
|
||||||
# We still inject the new verification code though.
|
# We still inject the new verification code though.
|
||||||
|
@ -66,8 +74,9 @@ class LocalAuthorizationServer:
|
||||||
self._web_server_thread = threading.Thread(None, self._web_server.serve_forever, daemon = self._daemon)
|
self._web_server_thread = threading.Thread(None, self._web_server.serve_forever, daemon = self._daemon)
|
||||||
self._web_server_thread.start()
|
self._web_server_thread.start()
|
||||||
|
|
||||||
## Stops the web server if it was running. It also does some cleanup.
|
|
||||||
def stop(self) -> None:
|
def stop(self) -> None:
|
||||||
|
"""Stops the web server if it was running. It also does some cleanup."""
|
||||||
|
|
||||||
Logger.log("d", "Stopping local oauth2 web server...")
|
Logger.log("d", "Stopping local oauth2 web server...")
|
||||||
|
|
||||||
if self._web_server:
|
if self._web_server:
|
||||||
|
|
|
@ -8,8 +8,9 @@ class BaseModel:
|
||||||
self.__dict__.update(kwargs)
|
self.__dict__.update(kwargs)
|
||||||
|
|
||||||
|
|
||||||
## OAuth OAuth2Settings data template.
|
|
||||||
class OAuth2Settings(BaseModel):
|
class OAuth2Settings(BaseModel):
|
||||||
|
"""OAuth OAuth2Settings data template."""
|
||||||
|
|
||||||
CALLBACK_PORT = None # type: Optional[int]
|
CALLBACK_PORT = None # type: Optional[int]
|
||||||
OAUTH_SERVER_URL = None # type: Optional[str]
|
OAUTH_SERVER_URL = None # type: Optional[str]
|
||||||
CLIENT_ID = None # type: Optional[str]
|
CLIENT_ID = None # type: Optional[str]
|
||||||
|
@ -20,16 +21,18 @@ class OAuth2Settings(BaseModel):
|
||||||
AUTH_FAILED_REDIRECT = "https://ultimaker.com" # type: str
|
AUTH_FAILED_REDIRECT = "https://ultimaker.com" # type: str
|
||||||
|
|
||||||
|
|
||||||
## User profile data template.
|
|
||||||
class UserProfile(BaseModel):
|
class UserProfile(BaseModel):
|
||||||
|
"""User profile data template."""
|
||||||
|
|
||||||
user_id = None # type: Optional[str]
|
user_id = None # type: Optional[str]
|
||||||
username = None # type: Optional[str]
|
username = None # type: Optional[str]
|
||||||
profile_image_url = None # type: Optional[str]
|
profile_image_url = None # type: Optional[str]
|
||||||
|
|
||||||
|
|
||||||
## Authentication data template.
|
|
||||||
class AuthenticationResponse(BaseModel):
|
class AuthenticationResponse(BaseModel):
|
||||||
"""Data comes from the token response with success flag and error message added."""
|
"""Authentication data template."""
|
||||||
|
|
||||||
|
# Data comes from the token response with success flag and error message added.
|
||||||
success = True # type: bool
|
success = True # type: bool
|
||||||
token_type = None # type: Optional[str]
|
token_type = None # type: Optional[str]
|
||||||
access_token = None # type: Optional[str]
|
access_token = None # type: Optional[str]
|
||||||
|
@ -40,22 +43,25 @@ class AuthenticationResponse(BaseModel):
|
||||||
received_at = None # type: Optional[str]
|
received_at = None # type: Optional[str]
|
||||||
|
|
||||||
|
|
||||||
## Response status template.
|
|
||||||
class ResponseStatus(BaseModel):
|
class ResponseStatus(BaseModel):
|
||||||
|
"""Response status template."""
|
||||||
|
|
||||||
code = 200 # type: int
|
code = 200 # type: int
|
||||||
message = "" # type: str
|
message = "" # type: str
|
||||||
|
|
||||||
|
|
||||||
## Response data template.
|
|
||||||
class ResponseData(BaseModel):
|
class ResponseData(BaseModel):
|
||||||
|
"""Response data template."""
|
||||||
|
|
||||||
status = None # type: ResponseStatus
|
status = None # type: ResponseStatus
|
||||||
data_stream = None # type: Optional[bytes]
|
data_stream = None # type: Optional[bytes]
|
||||||
redirect_uri = None # type: Optional[str]
|
redirect_uri = None # type: Optional[str]
|
||||||
content_type = "text/html" # type: str
|
content_type = "text/html" # type: str
|
||||||
|
|
||||||
|
|
||||||
## Possible HTTP responses.
|
|
||||||
HTTP_STATUS = {
|
HTTP_STATUS = {
|
||||||
|
"""Possible HTTP responses."""
|
||||||
|
|
||||||
"OK": ResponseStatus(code = 200, message = "OK"),
|
"OK": ResponseStatus(code = 200, message = "OK"),
|
||||||
"NOT_FOUND": ResponseStatus(code = 404, message = "NOT FOUND"),
|
"NOT_FOUND": ResponseStatus(code = 404, message = "NOT FOUND"),
|
||||||
"REDIRECT": ResponseStatus(code = 302, message = "REDIRECT")
|
"REDIRECT": ResponseStatus(code = 302, message = "REDIRECT")
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue