def test_loginAndLogout() -> None: preferences = Preferences() authorization_service = AuthorizationService(OAUTH_SETTINGS, preferences) authorization_service.onAuthenticationError.emit = MagicMock() authorization_service.onAuthStateChanged.emit = MagicMock() authorization_service.initialize() # Let the service think there was a succesfull response with patch.object(AuthorizationHelpers, "parseJWT", return_value=UserProfile()): authorization_service._onAuthStateChanged(SUCCESFULL_AUTH_RESPONSE) # Ensure that the error signal was not triggered assert authorization_service.onAuthenticationError.emit.call_count == 0 # Since we said that it went right this time, validate that we got a signal. assert authorization_service.onAuthStateChanged.emit.call_count == 1 assert authorization_service.getUserProfile() is not None assert authorization_service.getAccessToken() == "beep" # Check that we stored the authentication data, so next time the user won't have to log in again. assert preferences.getValue("test/auth_data") is not None # We're logged in now, also check if logging out works authorization_service.deleteAuthData() assert authorization_service.onAuthStateChanged.emit.call_count == 2 assert authorization_service.getUserProfile() is None # Ensure the data is gone after we logged out. assert preferences.getValue("test/auth_data") == "{}"
def test__parseJWTNoRefreshToken(): authorization_service = AuthorizationService(OAUTH_SETTINGS, Preferences()) with patch.object(AuthorizationService, "getUserProfile", return_value=UserProfile()): authorization_service._storeAuthData(NO_REFRESH_AUTH_RESPONSE) assert authorization_service._parseJWT() is None
def test__parseJWTNoRefreshToken(): """ Tests parsing the user profile if there is no refresh token stored, but there is a normal authentication token. The request for the user profile using the authentication token should still work normally. """ authorization_service = AuthorizationService(OAUTH_SETTINGS, Preferences()) with patch.object(AuthorizationService, "getUserProfile", return_value=UserProfile()): authorization_service._storeAuthData(NO_REFRESH_AUTH_RESPONSE) mock_callback = Mock() # To log the final profile response. mock_reply = Mock( ) # The user profile that the service should respond with. mock_reply.error = Mock(return_value=QNetworkReply.NetworkError.NoError) http_mock = Mock() http_mock.get = lambda url, headers_dict, callback, error_callback: callback( mock_reply) http_mock.readJSON = Mock(return_value={ "data": { "user_id": "id_ego_or_superego", "username": "******" } }) with patch( "UM.TaskManagement.HttpRequestManager.HttpRequestManager.getInstance", MagicMock(return_value=http_mock)): authorization_service._parseJWT(mock_callback) mock_callback.assert_called_once() profile_reply = mock_callback.call_args_list[0][0][0] assert profile_reply.user_id == "id_ego_or_superego" assert profile_reply.username == "Ghostkeeper"
def test__parseJWTFailOnRefresh(): """ Tries to refresh the authentication token using an invalid refresh token. The request should fail. """ authorization_service = AuthorizationService(OAUTH_SETTINGS, Preferences()) with patch.object(AuthorizationService, "getUserProfile", return_value=UserProfile()): authorization_service._storeAuthData(SUCCESSFUL_AUTH_RESPONSE) mock_callback = Mock() # To log the final profile response. mock_reply = Mock( ) # The response that the request should give, containing an error about it failing to authenticate. mock_reply.error = Mock( return_value=QNetworkReply.NetworkError.AuthenticationRequiredError ) # The reply is 403: Authentication required, meaning the server responded with a "Can't do that, Dave". http_mock = Mock() http_mock.get = lambda url, headers_dict, callback, error_callback: callback( mock_reply) http_mock.post = lambda url, data, headers_dict, callback, error_callback: callback( mock_reply) with patch( "UM.TaskManagement.HttpRequestManager.HttpRequestManager.readJSON", Mock( return_value={"error_description": "Mock a failed request!"})): with patch( "UM.TaskManagement.HttpRequestManager.HttpRequestManager.getInstance", MagicMock(return_value=http_mock)): authorization_service._parseJWT(mock_callback) mock_callback.assert_called_once_with(None)
def parseJWT(self, access_token: str) -> Optional["UserProfile"]: try: token_request = requests.get( "{}/check-token".format(self._settings.OAUTH_SERVER_URL), headers={"Authorization": "Bearer {}".format(access_token)}) except requests.exceptions.ConnectionError: # Connection was suddenly dropped. Nothing we can do about that. Logger.log( "w", "Something failed while attempting to parse the JWT token") return None if token_request.status_code not in (200, 201): Logger.log("w", "Could not retrieve token data from auth server: %s", token_request.text) return None user_data = token_request.json().get("data") if not user_data or not isinstance(user_data, dict): Logger.log("w", "Could not parse user data from token: %s", user_data) return None return UserProfile(user_id=user_data["user_id"], username=user_data["username"], profile_image_url=user_data.get( "profile_image_url", ""))
def test_wrongServerResponses() -> None: authorization_service = AuthorizationService(OAUTH_SETTINGS, Preferences()) authorization_service.initialize() with patch.object(AuthorizationHelpers, "parseJWT", return_value=UserProfile()): authorization_service._onAuthStateChanged(MALFORMED_AUTH_RESPONSE) assert authorization_service.getUserProfile() is None
def test__parseJWTFailOnRefresh(): authorization_service = AuthorizationService(OAUTH_SETTINGS, Preferences()) with patch.object(AuthorizationService, "getUserProfile", return_value=UserProfile()): authorization_service._storeAuthData(SUCCESSFUL_AUTH_RESPONSE) with patch.object(AuthorizationHelpers, "getAccessTokenUsingRefreshToken", return_value=FAILED_AUTH_RESPONSE): assert authorization_service._parseJWT() is None
def test_refreshAccessTokenFailed(): authorization_service = AuthorizationService(OAUTH_SETTINGS, Preferences()) authorization_service.initialize() with patch.object(AuthorizationService, "getUserProfile", return_value=UserProfile()): authorization_service._storeAuthData(SUCCESSFUL_AUTH_RESPONSE) authorization_service.onAuthStateChanged.emit = MagicMock() with patch.object(AuthorizationHelpers, "getAccessTokenUsingRefreshToken", return_value=FAILED_AUTH_RESPONSE): authorization_service.refreshAccessToken() assert authorization_service.onAuthStateChanged.emit.called_with(False)
def test__parseJWTSucceedOnRefresh(): """ Tries to refresh the authentication token using a valid refresh token. The request should succeed. """ authorization_service = AuthorizationService(OAUTH_SETTINGS, Preferences()) authorization_service.initialize() with patch.object(AuthorizationService, "getUserProfile", return_value=UserProfile()): authorization_service._storeAuthData(EXPIRED_AUTH_RESPONSE) mock_callback = Mock() # To log the final profile response. mock_reply_success = Mock( ) # The reply should be a failure when using the expired access token, but succeed when using the refresh token. mock_reply_success.error = Mock( return_value=QNetworkReply.NetworkError.NoError) mock_reply_failure = Mock() mock_reply_failure.error = Mock( return_value=QNetworkReply.NetworkError.AuthenticationRequiredError) http_mock = Mock() def mock_get(url, headers_dict, callback, error_callback): if (headers_dict == {"Authorization": "Bearer beep"}): callback(mock_reply_success) else: callback(mock_reply_failure) http_mock.get = mock_get http_mock.readJSON = Mock(return_value={ "data": { "user_id": "user_idea", "username": "******" } }) def mock_refresh(self, refresh_token, callback): # Refreshing gives a valid token. callback(SUCCESSFUL_AUTH_RESPONSE) with patch( "cura.OAuth2.AuthorizationHelpers.AuthorizationHelpers.getAccessTokenUsingRefreshToken", mock_refresh): with patch( "UM.TaskManagement.HttpRequestManager.HttpRequestManager.getInstance", MagicMock(return_value=http_mock)): authorization_service._parseJWT(mock_callback) mock_callback.assert_called_once() profile_reply = mock_callback.call_args_list[0][0][0] assert profile_reply.user_id == "user_idea" assert profile_reply.username == "Ghostkeeper"
def test__parseJWTSucceedOnRefresh(): authorization_service = AuthorizationService(OAUTH_SETTINGS, Preferences()) authorization_service.initialize() with patch.object(AuthorizationService, "getUserProfile", return_value=UserProfile()): authorization_service._storeAuthData(SUCCESSFUL_AUTH_RESPONSE) with patch.object(AuthorizationHelpers, "getAccessTokenUsingRefreshToken", return_value=SUCCESSFUL_AUTH_RESPONSE): with patch.object(AuthorizationHelpers, "parseJWT", MagicMock(return_value=None)) as mocked_parseJWT: authorization_service._parseJWT() mocked_parseJWT.assert_called_with("beep")
def parseJWT(self, access_token: str) -> Optional["UserProfile"]: token_request = requests.get( "{}/check-token".format(self._settings.OAUTH_SERVER_URL), headers={"Authorization": "Bearer {}".format(access_token)}) if token_request.status_code not in (200, 201): Logger.log("w", "Could not retrieve token data from auth server: %s", token_request.text) return None user_data = token_request.json().get("data") if not user_data or not isinstance(user_data, dict): Logger.log("w", "Could not parse user data from token: %s", user_data) return None return UserProfile(user_id=user_data["user_id"], username=user_data["username"], profile_image_url=user_data.get( "profile_image_url", ""))
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: check_token_url = "{}/check-token".format( self._settings.OAUTH_SERVER_URL) Logger.log("d", "Checking the access token for [%s]", check_token_url) token_request = requests.get( check_token_url, headers={"Authorization": "Bearer {}".format(access_token)}) except (requests.exceptions.ConnectionError, requests.exceptions.Timeout): # Connection was suddenly dropped. Nothing we can do about that. Logger.logException( "w", "Something failed while attempting to parse the JWT token") return None if token_request.status_code not in (200, 201): Logger.log("w", "Could not retrieve token data from auth server: %s", token_request.text) return None user_data = token_request.json().get("data") if not user_data or not isinstance(user_data, dict): Logger.log("w", "Could not parse user data from token: %s", user_data) return None return UserProfile( user_id=user_data["user_id"], username=user_data["username"], profile_image_url=user_data.get("profile_image_url", ""), organization_id=user_data.get("organization", {}).get("organization_id"), subscriptions=user_data.get("subscriptions", []))
def user_profile(): result = UserProfile() result.username = "******" result.profile_image_url = "profile_image_url!" result.user_id = "user_id!" return result
authorization_service._onAuthStateChanged(FAILED_AUTH_RESPONSE) # Check that the error signal was triggered assert authorization_service.onAuthenticationError.emit.call_count == 1 # Since nothing changed, this should still be 0. assert authorization_service.onAuthStateChanged.emit.call_count == 0 # Validate that there is no user profile or token assert authorization_service.getUserProfile() is None assert authorization_service.getAccessToken() is None @patch.object(AuthorizationService, "getUserProfile", return_value=UserProfile()) def test_storeAuthData(get_user_profile) -> None: preferences = Preferences() authorization_service = AuthorizationService(OAUTH_SETTINGS, preferences) authorization_service.initialize() # Write stuff to the preferences. authorization_service._storeAuthData(SUCCESFULL_AUTH_RESPONSE) preference_value = preferences.getValue( OAUTH_SETTINGS.AUTH_DATA_PREFERENCE_KEY) # Check that something was actually put in the preferences assert preference_value is not None and preference_value != {} # Create a second auth service, so we can load the data. second_auth_service = AuthorizationService(OAUTH_SETTINGS, preferences) second_auth_service.initialize()
# Let the service think there was a failed response authorization_service._onAuthStateChanged(FAILED_AUTH_RESPONSE) # Check that the error signal was triggered assert authorization_service.onAuthenticationError.emit.call_count == 1 # Since nothing changed, this should still be 0. assert authorization_service.onAuthStateChanged.emit.call_count == 0 # Validate that there is no user profile or token assert authorization_service.getUserProfile() is None assert authorization_service.getAccessToken() is None @patch.object(AuthorizationService, "getUserProfile", return_value=UserProfile()) def test_storeAuthData(get_user_profile) -> None: preferences = Preferences() authorization_service = AuthorizationService(OAUTH_SETTINGS, preferences) authorization_service.initialize() # Write stuff to the preferences. authorization_service._storeAuthData(SUCCESSFUL_AUTH_RESPONSE) preference_value = preferences.getValue(OAUTH_SETTINGS.AUTH_DATA_PREFERENCE_KEY) # Check that something was actually put in the preferences assert preference_value is not None and preference_value != {} # Create a second auth service, so we can load the data. second_auth_service = AuthorizationService(OAUTH_SETTINGS, preferences) second_auth_service.initialize() second_auth_service.loadAuthDataFromPreferences()