Skip to content

Commit

Permalink
Added new error types for CredentialManagerException
Browse files Browse the repository at this point in the history
  • Loading branch information
pmathew92 committed Dec 3, 2024
1 parent d5d2597 commit 9395de6
Show file tree
Hide file tree
Showing 6 changed files with 275 additions and 10 deletions.
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.SERVER_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,
SERVER_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 SERVER_ERROR: CredentialsManagerException =
CredentialsManagerException(Code.SERVER_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.SERVER_ERROR -> "An error occurred when trying to authenticate with the server."
}
}
}
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.SERVER_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 shouldGetAndFailToRenewExpiredCredentialsWhenServerErrorOccurs() {
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 when trying to authenticate with the server.")
)
}

@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 shouldGetAndFailToRenewExpiredCredentialsWhenServerErrorOccurs() {
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 when trying to authenticate with the server.")
)
}

/**
* 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>

0 comments on commit 9395de6

Please sign in to comment.