Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -110,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
)
)
}
}
}
Expand Down Expand Up @@ -442,6 +454,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
)
)
}
}
}
Expand Down Expand Up @@ -527,5 +553,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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ public class CredentialsManagerException :
NO_NETWORK,
API_ERROR,
SSO_EXCHANGE_FAILED,
UNKNOWN_ERROR
}

private var code: Code?
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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 fetching the token. Please check the error cause for more details."
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
)
}
}
}
Expand Down Expand Up @@ -680,6 +691,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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -1122,6 +1147,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 fetching the token. Please check the error cause for more details.")
)
}

@Test
public fun shouldClearCredentials() {
manager.clearCredentials()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down Expand Up @@ -1778,6 +1809,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 fetching 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.
*/
Expand Down