Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added new error types for CredentialsManagerException #783

Merged
merged 2 commits into from
Dec 6, 2024
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
Expand Up @@ -325,9 +325,15 @@ public class CredentialsManager @VisibleForTesting(otherwise = VisibleForTesting
saveCredentials(credentials)
callback.onSuccess(credentials)
} catch (error: AuthenticationException) {
val exception = when {
error.isRefreshTokenDeleted ||
error.isInvalidRefreshToken -> CredentialsManagerException.Code.RENEW_FAILED
error.isNetworkError -> CredentialsManagerException.Code.NO_NETWORK
else -> CredentialsManagerException.Code.API_ERROR
}
callback.onFailure(
CredentialsManagerException(
CredentialsManagerException.Code.RENEW_FAILED,
exception,
error
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ public class CredentialsManagerException :
BIOMETRIC_ERROR_UNABLE_TO_PROCESS,
BIOMETRICS_INVALID_USER,
BIOMETRIC_AUTHENTICATION_FAILED,
NO_NETWORK,
API_ERROR
}

private var code: Code?
Expand Down Expand Up @@ -135,6 +137,12 @@ public class CredentialsManagerException :
public val BIOMETRICS_INVALID_USER: CredentialsManagerException =
CredentialsManagerException(Code.BIOMETRICS_INVALID_USER)

//Exceptions thrown when making api calls for access token renewal
public val NO_NETWORK: CredentialsManagerException =
CredentialsManagerException(Code.NO_NETWORK)
public val API_ERROR: CredentialsManagerException =
CredentialsManagerException(Code.API_ERROR)


private fun getMessage(code: Code): String {
return when (code) {
Expand Down Expand Up @@ -177,6 +185,8 @@ public class CredentialsManagerException :
Code.BIOMETRIC_ERROR_UNABLE_TO_PROCESS -> "Failed to authenticate because the sensor was unable to process the current image."
Code.BIOMETRICS_INVALID_USER -> "The user didn't pass the authentication challenge."
Code.BIOMETRIC_AUTHENTICATION_FAILED -> "Biometric authentication failed."
Code.NO_NETWORK -> "Failed to execute the network request."
Code.API_ERROR -> "An error occurred while processing the request."
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import android.util.Log
import androidx.annotation.VisibleForTesting
import androidx.fragment.app.FragmentActivity
import com.auth0.android.Auth0
import com.auth0.android.Auth0Exception
import com.auth0.android.authentication.AuthenticationAPIClient
import com.auth0.android.authentication.AuthenticationException
import com.auth0.android.callback.Callback
import com.auth0.android.request.internal.GsonProvider
import com.auth0.android.result.Credentials
Expand Down Expand Up @@ -585,10 +585,16 @@ public class SecureCredentialsManager @VisibleForTesting(otherwise = VisibleForT
fresh.expiresAt,
fresh.scope
)
} catch (error: Auth0Exception) {
} catch (error: AuthenticationException) {
val exception = when {
error.isRefreshTokenDeleted ||
error.isInvalidRefreshToken -> CredentialsManagerException.Code.RENEW_FAILED
error.isNetworkError -> CredentialsManagerException.Code.NO_NETWORK
else -> CredentialsManagerException.Code.API_ERROR
}
callback.onFailure(
CredentialsManagerException(
CredentialsManagerException.Code.RENEW_FAILED,
exception,
error
)
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.auth0.android.authentication.storage

import com.auth0.android.NetworkErrorException
import com.auth0.android.authentication.AuthenticationAPIClient
import com.auth0.android.authentication.AuthenticationException
import com.auth0.android.callback.Callback
Expand Down Expand Up @@ -788,7 +789,7 @@ public class CredentialsManagerTest {
}

@Test
public fun shouldGetAndFailToRenewExpiredCredentials() {
public fun shouldGetAndFailToRenewExpiredCredentialsWhenRefreshTokenExpired() {
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")
Expand All @@ -802,8 +803,9 @@ public class CredentialsManagerTest {
client.renewAuth("refreshToken")
).thenReturn(request)
//Trigger failure
val authenticationException = Mockito.mock(
AuthenticationException::class.java
val authenticationException = AuthenticationException(
"invalid_grant",
"Unknown or invalid refresh token."
)
Mockito.`when`(request.execute()).thenThrow(authenticationException)
manager.getCredentials(callback)
Expand All @@ -828,6 +830,130 @@ public class CredentialsManagerTest {
)
}

@Test
public fun shouldGetAndFailToRenewExpiredCredentialsWhenUserIsDeleted() {
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 authenticationException = AuthenticationException(
mapOf(
"error" to "invalid_grant",
"error_description" to "The refresh_token was generated for a user who doesn't exist anymore."
), 403
)
Mockito.`when`(request.execute()).thenThrow(authenticationException)
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.cause, Is.`is`(authenticationException))
MatcherAssert.assertThat(
exception.message,
Is.`is`("An error occurred while trying to use the Refresh Token to renew the Credentials.")
)
}

@Test
public fun shouldGetAndFailToRenewExpiredCredentialsWhenNetworkIsNotAvailable() {
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 authenticationException = AuthenticationException(
"Failed to execute the network request", NetworkErrorException(mock())
)
Mockito.`when`(request.execute()).thenThrow(authenticationException)
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.cause, Is.`is`(authenticationException))
MatcherAssert.assertThat(
exception.message,
Is.`is`("Failed to execute the network request.")
)
}

@Test
public fun shouldGetAndFailToRenewExpiredCredentialsWhenApiErrorOccurs() {
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 authenticationException = AuthenticationException("Something went wrong", mock<Exception>())
Mockito.`when`(request.execute()).thenThrow(authenticationException)
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.cause, Is.`is`(authenticationException))
MatcherAssert.assertThat(
exception.message,
Is.`is`("An error occurred while processing the request.")
)
}

@Test
public fun shouldClearCredentials() {
manager.clearCredentials()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import android.content.Context
import android.util.Base64
import androidx.fragment.app.FragmentActivity
import com.auth0.android.Auth0
import com.auth0.android.NetworkErrorException
import com.auth0.android.authentication.AuthenticationAPIClient
import com.auth0.android.authentication.AuthenticationException
import com.auth0.android.callback.Callback
Expand Down Expand Up @@ -1259,7 +1260,7 @@ public class SecureCredentialsManagerTest {
}

@Test
public fun shouldGetAndFailToRenewExpiredCredentials() {
public fun shouldGetAndFailToRenewExpiredCredentialsWhenRefreshTokenExpired() {
Mockito.`when`(localAuthenticationManager.authenticate()).then {
localAuthenticationManager.resultCallback.onSuccess(true)
}
Expand All @@ -1269,7 +1270,50 @@ public class SecureCredentialsManagerTest {
client.renewAuth("refreshToken")
).thenReturn(request)
//Trigger failure
val authenticationException = mock<AuthenticationException>()
val authenticationException = AuthenticationException(
"invalid_grant",
"Unknown or invalid refresh token."
)
Mockito.`when`(request.execute()).thenThrow(authenticationException)
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.cause, Is.`is`(authenticationException))
MatcherAssert.assertThat(
exception.message,
Is.`is`("An error occurred while trying to use the Refresh Token to renew the Credentials.")
)
}

@Test
public fun shouldGetAndFailToRenewExpiredCredentialsWhenUserIsDeleted() {
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 authenticationException = AuthenticationException(
mapOf(
"error" to "invalid_grant",
"error_description" to "The refresh_token was generated for a user who doesn't exist anymore."
), 403
)
Mockito.`when`(request.execute()).thenThrow(authenticationException)
manager.getCredentials(callback)
verify(callback).onFailure(
Expand All @@ -1293,6 +1337,79 @@ public class SecureCredentialsManagerTest {
)
}

@Test
public fun shouldGetAndFailToRenewExpiredCredentialsWhenNetworkIsNotAvailable() {
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 authenticationException = AuthenticationException(
"Failed to execute the network request", NetworkErrorException(mock())
)
Mockito.`when`(request.execute()).thenThrow(authenticationException)
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.cause, Is.`is`(authenticationException))
MatcherAssert.assertThat(
exception.message,
Is.`is`("Failed to execute the network request.")
)
}

@Test
public fun shouldGetAndFailToRenewExpiredCredentialsWhenApiErrorOccurs() {
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 authenticationException =
AuthenticationException("Something went wrong", mock<Exception>())
Mockito.`when`(request.execute()).thenThrow(authenticationException)
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.cause, Is.`is`(authenticationException))
MatcherAssert.assertThat(
exception.message,
Is.`is`("An error occurred while processing the request.")
)
}

/**
* Testing that getCredentials execution from multiple threads via multiple instances of SecureCredentialsManager should trigger only one network request.
*/
Expand Down
2 changes: 1 addition & 1 deletion sample/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<resources>
<string name="app_name">Auth0 SDK Sample</string>
<string name="com_auth0_domain">mathewp.acmetest.org</string>
<string name="com_auth0_domain">p-mathew.us.auth0.com</string>
<string name="com_auth0_client_id">gkba7X6OJM2b0cdlUlTCqXD7AwT3FYVV</string>
<string name="com_auth0_scheme">demo</string>
</resources>
Loading