From 8296744cceda269bf2b36cecf58e6fa03b6891c0 Mon Sep 17 00:00:00 2001 From: Prince Mathew Date: Wed, 23 Apr 2025 15:36:34 +0530 Subject: [PATCH 1/2] Added a new error type in the CredentialsManagerEXception class to handle any unknown error during token refresh --- .../storage/CredentialsManager.kt | 16 ++++++++ .../storage/CredentialsManagerException.kt | 4 ++ .../storage/SecureCredentialsManager.kt | 15 +++++++ .../storage/CredentialsManagerTest.kt | 40 +++++++++++++++++++ .../storage/SecureCredentialsManagerTest.kt | 37 +++++++++++++++++ 5 files changed, 112 insertions(+) diff --git a/auth0/src/main/java/com/auth0/android/authentication/storage/CredentialsManager.kt b/auth0/src/main/java/com/auth0/android/authentication/storage/CredentialsManager.kt index 3afaae7b1..ba9f7f841 100644 --- a/auth0/src/main/java/com/auth0/android/authentication/storage/CredentialsManager.kt +++ b/auth0/src/main/java/com/auth0/android/authentication/storage/CredentialsManager.kt @@ -1,6 +1,7 @@ package com.auth0.android.authentication.storage import android.text.TextUtils +import android.util.Log import androidx.annotation.VisibleForTesting import com.auth0.android.authentication.AuthenticationAPIClient import com.auth0.android.authentication.AuthenticationException @@ -442,6 +443,20 @@ public class CredentialsManager @VisibleForTesting(otherwise = VisibleForTesting exception, error ) ) + } catch (exception: RuntimeException) { + /** + * Catching any unexpected runtime errors in the token renewal flow + */ + Log.e( + TAG, + "Caught unexpected exceptions for token renewal ${exception.stackTraceToString()}" + ) + callback.onFailure( + CredentialsManagerException( + CredentialsManagerException.Code.UNKNOWN_ERROR, + exception + ) + ) } } } @@ -527,5 +542,6 @@ public class CredentialsManager @VisibleForTesting(otherwise = VisibleForTesting // This is no longer used as we get the credentials expiry from the access token only, // but we still store it so users can rollback to versions where it is required. private const val LEGACY_KEY_CACHE_EXPIRES_AT = "com.auth0.cache_expires_at" + private val TAG = CredentialsManager::class.java.simpleName } } \ No newline at end of file diff --git a/auth0/src/main/java/com/auth0/android/authentication/storage/CredentialsManagerException.kt b/auth0/src/main/java/com/auth0/android/authentication/storage/CredentialsManagerException.kt index 10210e286..1c2810b4f 100644 --- a/auth0/src/main/java/com/auth0/android/authentication/storage/CredentialsManagerException.kt +++ b/auth0/src/main/java/com/auth0/android/authentication/storage/CredentialsManagerException.kt @@ -46,6 +46,7 @@ public class CredentialsManagerException : NO_NETWORK, API_ERROR, SSO_EXCHANGE_FAILED, + UNKNOWN_ERROR } private var code: Code? @@ -146,6 +147,8 @@ public class CredentialsManagerException : public val SSO_EXCHANGE_FAILED: CredentialsManagerException = CredentialsManagerException(Code.SSO_EXCHANGE_FAILED) + public val UNKNOWN_ERROR: CredentialsManagerException = CredentialsManagerException(Code.UNKNOWN_ERROR) + private fun getMessage(code: Code): String { return when (code) { @@ -191,6 +194,7 @@ public class CredentialsManagerException : Code.NO_NETWORK -> "Failed to execute the network request." Code.API_ERROR -> "An error occurred while processing the request." Code.SSO_EXCHANGE_FAILED ->"The exchange of the refresh token for SSO credentials failed." + Code.UNKNOWN_ERROR -> "An unknown error has occurred while refreshing the token. Please check the error cause for more details." } } } diff --git a/auth0/src/main/java/com/auth0/android/authentication/storage/SecureCredentialsManager.kt b/auth0/src/main/java/com/auth0/android/authentication/storage/SecureCredentialsManager.kt index feaf17296..cb63fa64e 100644 --- a/auth0/src/main/java/com/auth0/android/authentication/storage/SecureCredentialsManager.kt +++ b/auth0/src/main/java/com/auth0/android/authentication/storage/SecureCredentialsManager.kt @@ -680,6 +680,21 @@ public class SecureCredentialsManager @VisibleForTesting(otherwise = VisibleForT ) ) return@execute + } catch (exception: RuntimeException) { + /** + * Catching any unexpected runtime errors in the token renewal flow + */ + Log.e( + TAG, + "Caught unexpected exceptions for token renewal ${exception.stackTraceToString()}" + ) + callback.onFailure( + CredentialsManagerException( + CredentialsManagerException.Code.UNKNOWN_ERROR, + exception + ) + ) + return@execute } try { diff --git a/auth0/src/test/java/com/auth0/android/authentication/storage/CredentialsManagerTest.kt b/auth0/src/test/java/com/auth0/android/authentication/storage/CredentialsManagerTest.kt index b12b23601..e54a9fb34 100644 --- a/auth0/src/test/java/com/auth0/android/authentication/storage/CredentialsManagerTest.kt +++ b/auth0/src/test/java/com/auth0/android/authentication/storage/CredentialsManagerTest.kt @@ -1122,6 +1122,46 @@ public class CredentialsManagerTest { ) } + @Test + public fun shouldGetAndFailToRenewExpiredCredentialsWhenAnyUnexpectedErrorOccurs() { + Mockito.`when`(storage.retrieveString("com.auth0.id_token")).thenReturn("idToken") + Mockito.`when`(storage.retrieveString("com.auth0.access_token")).thenReturn("accessToken") + Mockito.`when`(storage.retrieveString("com.auth0.refresh_token")).thenReturn("refreshToken") + Mockito.`when`(storage.retrieveString("com.auth0.token_type")).thenReturn("type") + val expirationTime = CredentialsMock.CURRENT_TIME_MS //Same as current time --> expired + Mockito.`when`(storage.retrieveLong("com.auth0.expires_at")).thenReturn(expirationTime) + Mockito.`when`(storage.retrieveLong("com.auth0.cache_expires_at")) + .thenReturn(expirationTime) + Mockito.`when`(storage.retrieveString("com.auth0.scope")).thenReturn("scope") + Mockito.`when`( + client.renewAuth("refreshToken") + ).thenReturn(request) + //Trigger failure + val runtimeException = NullPointerException("Something went wrong") + Mockito.`when`(request.execute()).thenThrow(runtimeException) + manager.getCredentials(callback) + verify(storage, never()) + .store(ArgumentMatchers.anyString(), ArgumentMatchers.anyInt()) + verify(storage, never()) + .store(ArgumentMatchers.anyString(), ArgumentMatchers.anyLong()) + verify(storage, never()) + .store(ArgumentMatchers.anyString(), ArgumentMatchers.anyString()) + verify(storage, never()) + .store(ArgumentMatchers.anyString(), ArgumentMatchers.anyBoolean()) + verify(storage, never()).remove(ArgumentMatchers.anyString()) + verify(callback).onFailure( + exceptionCaptor.capture() + ) + val exception = exceptionCaptor.firstValue + MatcherAssert.assertThat(exception, Is.`is`(Matchers.notNullValue())) + MatcherAssert.assertThat(exception, Is.`is`(CredentialsManagerException.UNKNOWN_ERROR)) + MatcherAssert.assertThat(exception.cause, Is.`is`(runtimeException)) + MatcherAssert.assertThat( + exception.message, + Is.`is`("An unknown error has occurred while refreshing the token. Please check the error cause for more details.") + ) + } + @Test public fun shouldClearCredentials() { manager.clearCredentials() diff --git a/auth0/src/test/java/com/auth0/android/authentication/storage/SecureCredentialsManagerTest.kt b/auth0/src/test/java/com/auth0/android/authentication/storage/SecureCredentialsManagerTest.kt index 42866e796..98e52c3f6 100644 --- a/auth0/src/test/java/com/auth0/android/authentication/storage/SecureCredentialsManagerTest.kt +++ b/auth0/src/test/java/com/auth0/android/authentication/storage/SecureCredentialsManagerTest.kt @@ -1778,6 +1778,43 @@ public class SecureCredentialsManagerTest { ) } + @Test + public fun shouldGetAndFailToRenewExpiredCredentialsWhenAnyUnexpectedErrorOccurs() { + Mockito.`when`(localAuthenticationManager.authenticate()).then { + localAuthenticationManager.resultCallback.onSuccess(true) + } + val expiresAt = Date(CredentialsMock.CURRENT_TIME_MS) + insertTestCredentials(false, true, true, expiresAt, "scope") + Mockito.`when`( + client.renewAuth("refreshToken") + ).thenReturn(request) + //Trigger failure + val runtimeError = + RuntimeException("Something went wrong") + Mockito.`when`(request.execute()).thenThrow(runtimeError) + manager.getCredentials(callback) + verify(callback).onFailure( + exceptionCaptor.capture() + ) + verify(storage, never()) + .store(anyString(), anyLong()) + verify(storage, never()) + .store(anyString(), anyInt()) + verify(storage, never()) + .store(anyString(), anyString()) + verify(storage, never()) + .store(anyString(), anyBoolean()) + verify(storage, never()).remove(anyString()) + val exception = exceptionCaptor.firstValue + MatcherAssert.assertThat(exception, Is.`is`(Matchers.notNullValue())) + MatcherAssert.assertThat(exception, Is.`is`(CredentialsManagerException.UNKNOWN_ERROR)) + MatcherAssert.assertThat(exception.cause, Is.`is`(runtimeError)) + MatcherAssert.assertThat( + exception.message, + Is.`is`("An unknown error has occurred while refreshing the token. Please check the error cause for more details.") + ) + } + /** * Testing that getCredentials execution from multiple threads via multiple instances of SecureCredentialsManager should trigger only one network request. */ From 264ce1d73037bd90de1d379fde85e4cb5d63a46e Mon Sep 17 00:00:00 2001 From: Prince Mathew Date: Fri, 25 Apr 2025 12:55:42 +0530 Subject: [PATCH 2/2] Added runtime exception handling to N2W token fetching --- .../storage/CredentialsManager.kt | 11 +++++++ .../storage/CredentialsManagerException.kt | 2 +- .../storage/SecureCredentialsManager.kt | 11 +++++++ .../storage/CredentialsManagerTest.kt | 27 ++++++++++++++- .../storage/SecureCredentialsManagerTest.kt | 33 ++++++++++++++++++- 5 files changed, 81 insertions(+), 3 deletions(-) diff --git a/auth0/src/main/java/com/auth0/android/authentication/storage/CredentialsManager.kt b/auth0/src/main/java/com/auth0/android/authentication/storage/CredentialsManager.kt index ba9f7f841..e56c2d87b 100644 --- a/auth0/src/main/java/com/auth0/android/authentication/storage/CredentialsManager.kt +++ b/auth0/src/main/java/com/auth0/android/authentication/storage/CredentialsManager.kt @@ -111,6 +111,17 @@ public class CredentialsManager @VisibleForTesting(otherwise = VisibleForTesting error ) ) + } catch (exception: RuntimeException) { + Log.e( + TAG, + "Caught unexpected exceptions while fetching sso token ${exception.stackTraceToString()}" + ) + callback.onFailure( + CredentialsManagerException( + CredentialsManagerException.Code.UNKNOWN_ERROR, + exception + ) + ) } } } diff --git a/auth0/src/main/java/com/auth0/android/authentication/storage/CredentialsManagerException.kt b/auth0/src/main/java/com/auth0/android/authentication/storage/CredentialsManagerException.kt index 1c2810b4f..8f8a981fb 100644 --- a/auth0/src/main/java/com/auth0/android/authentication/storage/CredentialsManagerException.kt +++ b/auth0/src/main/java/com/auth0/android/authentication/storage/CredentialsManagerException.kt @@ -194,7 +194,7 @@ public class CredentialsManagerException : Code.NO_NETWORK -> "Failed to execute the network request." Code.API_ERROR -> "An error occurred while processing the request." Code.SSO_EXCHANGE_FAILED ->"The exchange of the refresh token for SSO credentials failed." - Code.UNKNOWN_ERROR -> "An unknown error has occurred while refreshing the token. Please check the error cause for more details." + Code.UNKNOWN_ERROR -> "An unknown error has occurred while fetching the token. Please check the error cause for more details." } } } diff --git a/auth0/src/main/java/com/auth0/android/authentication/storage/SecureCredentialsManager.kt b/auth0/src/main/java/com/auth0/android/authentication/storage/SecureCredentialsManager.kt index cb63fa64e..6b4449e0d 100644 --- a/auth0/src/main/java/com/auth0/android/authentication/storage/SecureCredentialsManager.kt +++ b/auth0/src/main/java/com/auth0/android/authentication/storage/SecureCredentialsManager.kt @@ -192,6 +192,17 @@ public class SecureCredentialsManager @VisibleForTesting(otherwise = VisibleForT CredentialsManagerException.Code.STORE_FAILED, error ) callback.onFailure(exception) + } catch (exception: RuntimeException) { + Log.e( + TAG, + "Caught unexpected exceptions while fetching sso token ${exception.stackTraceToString()}" + ) + callback.onFailure( + CredentialsManagerException( + CredentialsManagerException.Code.UNKNOWN_ERROR, + exception + ) + ) } } } diff --git a/auth0/src/test/java/com/auth0/android/authentication/storage/CredentialsManagerTest.kt b/auth0/src/test/java/com/auth0/android/authentication/storage/CredentialsManagerTest.kt index e54a9fb34..5105c3142 100644 --- a/auth0/src/test/java/com/auth0/android/authentication/storage/CredentialsManagerTest.kt +++ b/auth0/src/test/java/com/auth0/android/authentication/storage/CredentialsManagerTest.kt @@ -328,6 +328,31 @@ public class CredentialsManagerTest { ) } + @Test + public fun shouldFailOnGetNewSSOCredentialsWhenUnexpectedErrorOccurs() { + Mockito.`when`(storage.retrieveString("com.auth0.refresh_token")).thenReturn("refreshToken") + Mockito.`when`( + client.ssoExchange("refreshToken") + ).thenReturn(SSOCredentialsRequest) + //Trigger failure + val runtimeException = RuntimeException( + "unexpected_error" + ) + Mockito.`when`(SSOCredentialsRequest.execute()).thenThrow(runtimeException) + manager.getSsoCredentials(ssoCallback) + verify(ssoCallback).onFailure( + exceptionCaptor.capture() + ) + val exception = exceptionCaptor.firstValue + MatcherAssert.assertThat(exception, Is.`is`(Matchers.notNullValue())) + MatcherAssert.assertThat(exception, Is.`is`(CredentialsManagerException.UNKNOWN_ERROR)) + MatcherAssert.assertThat(exception.cause, Is.`is`(runtimeException)) + MatcherAssert.assertThat( + exception.message, + Is.`is`("An unknown error has occurred while fetching the token. Please check the error cause for more details.") + ) + } + @Test @ExperimentalCoroutinesApi public fun shouldFailOnAwaitSSOCredentialsWhenNoRefreshTokenWasSaved(): Unit = runTest { @@ -1158,7 +1183,7 @@ public class CredentialsManagerTest { MatcherAssert.assertThat(exception.cause, Is.`is`(runtimeException)) MatcherAssert.assertThat( exception.message, - Is.`is`("An unknown error has occurred while refreshing the token. Please check the error cause for more details.") + Is.`is`("An unknown error has occurred while fetching the token. Please check the error cause for more details.") ) } diff --git a/auth0/src/test/java/com/auth0/android/authentication/storage/SecureCredentialsManagerTest.kt b/auth0/src/test/java/com/auth0/android/authentication/storage/SecureCredentialsManagerTest.kt index 98e52c3f6..19ad357bd 100644 --- a/auth0/src/test/java/com/auth0/android/authentication/storage/SecureCredentialsManagerTest.kt +++ b/auth0/src/test/java/com/auth0/android/authentication/storage/SecureCredentialsManagerTest.kt @@ -378,6 +378,37 @@ public class SecureCredentialsManagerTest { ) } + @Test + public fun shouldFailWhenUnexpectedErrorOccursOnGetSSOCredentials() { + val expiresAt = Date(CredentialsMock.ONE_HOUR_AHEAD_MS) + Mockito.`when`(client.ssoExchange("refreshToken")) + .thenReturn(SSOCredentialsRequest) + insertTestCredentials( + hasIdToken = true, + hasAccessToken = true, + hasRefreshToken = true, + willExpireAt = expiresAt, + scope = "scope" + ) + //Trigger failure + val runtimeException = RuntimeException( + "runtime exception" + ) + Mockito.`when`(SSOCredentialsRequest.execute()).thenThrow(runtimeException) + manager.getSsoCredentials(ssoCallback) + verify(ssoCallback).onFailure( + exceptionCaptor.capture() + ) + val exception = exceptionCaptor.firstValue + MatcherAssert.assertThat(exception, Is.`is`(Matchers.notNullValue())) + MatcherAssert.assertThat(exception.cause, Is.`is`(runtimeException)) + MatcherAssert.assertThat(exception, Is.`is`(CredentialsManagerException.UNKNOWN_ERROR)) + MatcherAssert.assertThat( + exception.message, + Is.`is`("An unknown error has occurred while fetching the token. Please check the error cause for more details.") + ) + } + /* * AWAIT SSO credentials test */ @@ -1811,7 +1842,7 @@ public class SecureCredentialsManagerTest { MatcherAssert.assertThat(exception.cause, Is.`is`(runtimeError)) MatcherAssert.assertThat( exception.message, - Is.`is`("An unknown error has occurred while refreshing the token. Please check the error cause for more details.") + Is.`is`("An unknown error has occurred while fetching the token. Please check the error cause for more details.") ) }