def parseTokenResponse( token_response: requests.models.Response ) -> "AuthenticationResponse": token_data = None try: token_data = json.loads(token_response.text) except ValueError: Logger.log("w", "Could not parse token response data: %s", token_response.text) if not token_data: return AuthenticationResponse(success=False, err_message=catalog.i18nc( "@message", "Could not read response.")) if token_response.status_code not in (200, 201): return AuthenticationResponse( success=False, err_message=token_data["error_description"]) return AuthenticationResponse( success=True, token_type=token_data["token_type"], access_token=token_data["access_token"], refresh_token=token_data["refresh_token"], expires_in=token_data["expires_in"], scope=token_data["scope"], received_at=datetime.now().strftime(TOKEN_TIMESTAMP_FORMAT))
def _handleCallback( self, query: Dict[Any, List] ) -> Tuple[ResponseData, Optional[AuthenticationResponse]]: code = self._queryGet(query, "code") if code and self.authorization_helpers is not None and self.verification_code is not None: # If the code was returned we get the access token. token_response = self.authorization_helpers.getAccessTokenUsingAuthorizationCode( code, self.verification_code) elif self._queryGet(query, "error_code") == "user_denied": # Otherwise we show an error message (probably the user clicked "Deny" in the auth dialog). token_response = AuthenticationResponse( success=False, err_message= "Please give the required permissions when authorizing this application." ) else: # We don't know what went wrong here, so instruct the user to check the logs. token_response = AuthenticationResponse( success=False, error_message= "Something unexpected happened when trying to log in, please try again." ) if self.authorization_helpers is None: return ResponseData(), token_response return ResponseData(status=HTTP_STATUS["REDIRECT"], data_stream=b"Redirecting...", redirect_uri=self.authorization_helpers.settings. AUTH_SUCCESS_REDIRECT if token_response.success else self.authorization_helpers.settings. AUTH_FAILED_REDIRECT), token_response
def loadAuthDataFromPreferences(self) -> None: if self._preferences is None: Logger.log( "e", "Unable to load authentication data, since no preference has been set!" ) return try: preferences_data = json.loads( self._preferences.getValue( self._settings.AUTH_DATA_PREFERENCE_KEY)) if preferences_data: self._auth_data = AuthenticationResponse(**preferences_data) # Also check if we can actually get the user profile information. user_profile = self.getUserProfile() if user_profile is not None: self.onAuthStateChanged.emit(logged_in=True) else: if self._unable_to_get_data_message is not None: self._unable_to_get_data_message.hide() self._unable_to_get_data_message = Message( i18n_catalog.i18nc( "@info", "Unable to reach the Ultimaker account server."), title=i18n_catalog.i18nc("@info:title", "Warning")) self._unable_to_get_data_message.addAction( "retry", i18n_catalog.i18nc("@action:button", "Retry"), "[no_icon]", "[no_description]") self._unable_to_get_data_message.actionTriggered.connect( self._onMessageActionTriggered) self._unable_to_get_data_message.show() except ValueError: Logger.logException("w", "Could not load auth data from preferences")
def loadAuthDataFromPreferences(self) -> None: """Load authentication data from preferences.""" Logger.log("d", "Attempting to load the auth data from preferences.") if self._preferences is None: Logger.log("e", "Unable to load authentication data, since no preference has been set!") return try: preferences_data = json.loads(self._preferences.getValue(self._settings.AUTH_DATA_PREFERENCE_KEY)) if preferences_data: self._auth_data = AuthenticationResponse(**preferences_data) # Also check if we can actually get the user profile information. user_profile = self.getUserProfile() if user_profile is not None: self.onAuthStateChanged.emit(logged_in = True) Logger.log("d", "Auth data was successfully loaded") else: if self._unable_to_get_data_message is not None: self._unable_to_get_data_message.hide() self._unable_to_get_data_message = Message(i18n_catalog.i18nc("@info", "Unable to reach the Ultimaker account server."), title = i18n_catalog.i18nc("@info:title", "Warning"), message_type = Message.MessageType.ERROR) Logger.log("w", "Unable to load auth data from preferences") self._unable_to_get_data_message.show() except (ValueError, TypeError): Logger.logException("w", "Could not load auth data from preferences")
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 = { "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 "", "grant_type": "authorization_code", "code": authorization_code, "code_verifier": verification_code, "scope": self._settings.CLIENT_SCOPES if self._settings.CLIENT_SCOPES is not None else "", } try: return self.parseTokenResponse( requests.post(self._token_url, data=data)) # type: ignore except requests.exceptions.ConnectionError: return AuthenticationResponse( success=False, err_message="Unable to connect to remote server")
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.") data = { "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 "", "grant_type": "refresh_token", "refresh_token": refresh_token, "scope": self._settings.CLIENT_SCOPES if self._settings.CLIENT_SCOPES is not None else "", } try: return self.parseTokenResponse( requests.post(self._token_url, data=data)) # type: ignore except requests.exceptions.ConnectionError: return AuthenticationResponse( success=False, err_message="Unable to connect to remote server")
def getAccessTokenUsingAuthorizationCode( self, authorization_code: str, verification_code: str) -> "AuthenticationResponse": data = { "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 "", "grant_type": "authorization_code", "code": authorization_code, "code_verifier": verification_code, "scope": self._settings.CLIENT_SCOPES if self._settings.CLIENT_SCOPES is not None else "", } try: return self.parseTokenResponse( requests.post(self._token_url, data=data)) # type: ignore except requests.exceptions.ConnectionError: return AuthenticationResponse( success=False, err_message="Unable to connect to remote server")
def loadAuthDataFromPreferences(self) -> None: if self._preferences is None: Logger.log("e", "Unable to load authentication data, since no preference has been set!") return try: preferences_data = json.loads(self._preferences.getValue(self._settings.AUTH_DATA_PREFERENCE_KEY)) if preferences_data: self._auth_data = AuthenticationResponse(**preferences_data) self.onAuthStateChanged.emit(logged_in = True) except ValueError: Logger.logException("w", "Could not load auth data from preferences")
CALLBACK_PORT = 32118 OAUTH_ROOT = "https://account.ultimaker.com" CLOUD_API_ROOT = "https://api.ultimaker.com" OAUTH_SETTINGS = OAuth2Settings( OAUTH_SERVER_URL=OAUTH_ROOT, CALLBACK_PORT=CALLBACK_PORT, CALLBACK_URL="http://localhost:{}/callback".format(CALLBACK_PORT), CLIENT_ID="", CLIENT_SCOPES="", AUTH_DATA_PREFERENCE_KEY="test/auth_data", AUTH_SUCCESS_REDIRECT="{}/app/auth-success".format(OAUTH_ROOT), AUTH_FAILED_REDIRECT="{}/app/auth-error".format(OAUTH_ROOT)) FAILED_AUTH_RESPONSE = AuthenticationResponse(success=False, err_message="FAILURE!") SUCCESFULL_AUTH_RESPONSE = AuthenticationResponse(access_token="beep", refresh_token="beep?") MALFORMED_AUTH_RESPONSE = AuthenticationResponse() def test_cleanAuthService() -> None: # Ensure that when setting up an AuthorizationService, no data is set. authorization_service = AuthorizationService(OAUTH_SETTINGS, Preferences()) authorization_service.initialize() assert authorization_service.getUserProfile() is None assert authorization_service.getAccessToken() is None
CALLBACK_PORT = 32118 OAUTH_ROOT = "https://account.ultimaker.com" CLOUD_API_ROOT = "https://api.ultimaker.com" OAUTH_SETTINGS = OAuth2Settings( OAUTH_SERVER_URL=OAUTH_ROOT, CALLBACK_PORT=CALLBACK_PORT, CALLBACK_URL="http://localhost:{}/callback".format(CALLBACK_PORT), CLIENT_ID="", CLIENT_SCOPES="", AUTH_DATA_PREFERENCE_KEY="test/auth_data", AUTH_SUCCESS_REDIRECT="{}/app/auth-success".format(OAUTH_ROOT), AUTH_FAILED_REDIRECT="{}/app/auth-error".format(OAUTH_ROOT)) FAILED_AUTH_RESPONSE = AuthenticationResponse(success=False, err_message="FAILURE!") 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 success=True) NO_REFRESH_AUTH_RESPONSE = AuthenticationResponse( access_token="beep", received_at=datetime.now().strftime(TOKEN_TIMESTAMP_FORMAT), expires_in=300, # 5 minutes should be more than enough for testing success=True) MALFORMED_AUTH_RESPONSE = AuthenticationResponse(success=False)
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") state = self._queryGet(query, "state") if state != self.state: token_response = AuthenticationResponse( success=False, err_message=catalog.i18nc( "@message", "The provided state is not correct.")) elif code and self.authorization_helpers is not None and self.verification_code is not None: token_response = AuthenticationResponse( success=False, err_message=catalog.i18nc( "@message", "Timeout when authenticating with the account server.")) # If the code was returned we get the access token. lock = Lock() lock.acquire() def callback(response: AuthenticationResponse) -> None: nonlocal token_response token_response = response lock.release() self.authorization_helpers.getAccessTokenUsingAuthorizationCode( code, self.verification_code, callback) lock.acquire( timeout=60 ) # Block thread until request is completed (which releases the lock). If not acquired, the timeout message stays. elif self._queryGet(query, "error_code") == "user_denied": # Otherwise we show an error message (probably the user clicked "Deny" in the auth dialog). token_response = AuthenticationResponse( success=False, err_message=catalog.i18nc( "@message", "Please give the required permissions when authorizing this application." )) else: # We don't know what went wrong here, so instruct the user to check the logs. token_response = AuthenticationResponse( success=False, error_message=catalog.i18nc( "@message", "Something unexpected happened when trying to log in, please try again." )) if self.authorization_helpers is None: return ResponseData(), token_response return ResponseData(status=HTTP_STATUS["REDIRECT"], data_stream=b"Redirecting...", redirect_uri=self.authorization_helpers.settings. AUTH_SUCCESS_REDIRECT if token_response.success else self.authorization_helpers.settings. AUTH_FAILED_REDIRECT), token_response