From 7b5611a1881132d7e93ee30df9857ea40e7ce895 Mon Sep 17 00:00:00 2001 From: Prince Mathew <17837162+pmathew92@users.noreply.github.com> Date: Thu, 19 Feb 2026 12:57:14 +0530 Subject: [PATCH 1/2] chore: updated the RL wrapper installation path (#917) --- .github/actions/rl-scanner/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/rl-scanner/action.yml b/.github/actions/rl-scanner/action.yml index b55195740..9fd39bc9f 100644 --- a/.github/actions/rl-scanner/action.yml +++ b/.github/actions/rl-scanner/action.yml @@ -33,7 +33,7 @@ runs: - name: Install RL Wrapper shell: bash run: | - pip install rl-wrapper>=1.0.0 --index-url "https://${{ env.PRODSEC_TOOLS_USER }}:${{ env.PRODSEC_TOOLS_TOKEN }}@a0us.jfrog.io/artifactory/api/pypi/python-local/simple" + pip install rl-wrapper --index-url "https://${{ env.PRODSEC_TOOLS_USER }}:${{ env.PRODSEC_TOOLS_TOKEN }}@a0us.jfrog.io/artifactory/api/pypi/python/simple" - name: Run RL Scanner shell: bash From 845f6ca6ffcc95806f8e7ed1bbe6dd8e5317e789 Mon Sep 17 00:00:00 2001 From: utkrishtS Date: Fri, 20 Feb 2026 13:24:55 +0530 Subject: [PATCH 2/2] Add DEFAULT_MIN_TTL boundary tests for CredentialsManager and SecureCredentialsManager --- .../storage/BaseCredentialsManager.kt | 15 ++- .../storage/CredentialsManager.kt | 6 +- .../storage/SecureCredentialsManager.kt | 6 +- .../storage/SharedPreferencesStorage.kt | 4 + .../android/authentication/storage/Storage.kt | 5 + .../storage/CredentialsManagerTest.kt | 103 +++++++++++++++++- .../storage/SecureCredentialsManagerTest.kt | 100 ++++++++++++++++- .../storage/SharedPreferencesStorageTest.java | 9 ++ .../android/provider/WebAuthProviderTest.kt | 44 +++++--- .../internal/ThreadSwitcherShadow.java | 5 + 10 files changed, 268 insertions(+), 29 deletions(-) diff --git a/auth0/src/main/java/com/auth0/android/authentication/storage/BaseCredentialsManager.kt b/auth0/src/main/java/com/auth0/android/authentication/storage/BaseCredentialsManager.kt index d3ac32d59..23e0c1b6a 100644 --- a/auth0/src/main/java/com/auth0/android/authentication/storage/BaseCredentialsManager.kt +++ b/auth0/src/main/java/com/auth0/android/authentication/storage/BaseCredentialsManager.kt @@ -22,6 +22,17 @@ public abstract class BaseCredentialsManager internal constructor( ) { private var _clock: Clock = ClockImpl() + public companion object { + /** + * Default minimum time to live (in seconds) for the access token. + * When retrieving credentials, if the access token has less than this amount of time + * remaining before expiration, it will be automatically renewed. + * This ensures the access token is valid for at least a short window after retrieval, + * preventing downstream API call failures from nearly-expired tokens. + */ + public const val DEFAULT_MIN_TTL: Int = 60 + } + /** * Updates the clock instance used for expiration verification purposes. * The use of this method can help on situations where the clock comes from an external synced source. @@ -83,7 +94,7 @@ public abstract class BaseCredentialsManager internal constructor( public abstract fun getApiCredentials( audience: String, scope: String? = null, - minTtl: Int = 0, + minTtl: Int = DEFAULT_MIN_TTL, parameters: Map = emptyMap(), headers: Map = emptyMap(), callback: Callback @@ -139,7 +150,7 @@ public abstract class BaseCredentialsManager internal constructor( public abstract suspend fun awaitApiCredentials( audience: String, scope: String? = null, - minTtl: Int = 0, + minTtl: Int = DEFAULT_MIN_TTL, parameters: Map = emptyMap(), headers: Map = emptyMap() ): APICredentials 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 31f8e62f4..0e967fa2f 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 @@ -244,7 +244,7 @@ public class CredentialsManager @VisibleForTesting(otherwise = VisibleForTesting @JvmSynthetic @Throws(CredentialsManagerException::class) override suspend fun awaitCredentials(): Credentials { - return awaitCredentials(null, 0) + return awaitCredentials(null, DEFAULT_MIN_TTL) } /** @@ -390,7 +390,7 @@ public class CredentialsManager @VisibleForTesting(otherwise = VisibleForTesting * @param callback the callback that will receive a valid [Credentials] or the [CredentialsManagerException]. */ override fun getCredentials(callback: Callback) { - getCredentials(null, 0, callback) + getCredentials(null, DEFAULT_MIN_TTL, callback) } /** @@ -702,7 +702,7 @@ public class CredentialsManager @VisibleForTesting(otherwise = VisibleForTesting * @return whether there are valid credentials stored on this manager. */ override fun hasValidCredentials(): Boolean { - return hasValidCredentials(0) + return hasValidCredentials(DEFAULT_MIN_TTL.toLong()) } /** 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 a6e86c492..14c6fc0f9 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 @@ -409,7 +409,7 @@ public class SecureCredentialsManager @VisibleForTesting(otherwise = VisibleForT @JvmSynthetic @Throws(CredentialsManagerException::class) override suspend fun awaitCredentials(): Credentials { - return awaitCredentials(null, 0) + return awaitCredentials(null, DEFAULT_MIN_TTL) } /** @@ -579,7 +579,7 @@ public class SecureCredentialsManager @VisibleForTesting(otherwise = VisibleForT override fun getCredentials( callback: Callback ) { - getCredentials(null, 0, callback) + getCredentials(null, DEFAULT_MIN_TTL, callback) } /** @@ -779,7 +779,7 @@ public class SecureCredentialsManager @VisibleForTesting(otherwise = VisibleForT * @return whether this manager contains a valid non-expired pair of credentials or not. */ override fun hasValidCredentials(): Boolean { - return hasValidCredentials(0) + return hasValidCredentials(DEFAULT_MIN_TTL.toLong()) } /** diff --git a/auth0/src/main/java/com/auth0/android/authentication/storage/SharedPreferencesStorage.kt b/auth0/src/main/java/com/auth0/android/authentication/storage/SharedPreferencesStorage.kt index b9c5bb8d4..72ebfebdc 100644 --- a/auth0/src/main/java/com/auth0/android/authentication/storage/SharedPreferencesStorage.kt +++ b/auth0/src/main/java/com/auth0/android/authentication/storage/SharedPreferencesStorage.kt @@ -75,6 +75,10 @@ public class SharedPreferencesStorage @JvmOverloads constructor( sp.edit().remove(name).apply() } + override fun removeAll() { + sp.edit().clear().apply() + } + private companion object { private const val SHARED_PREFERENCES_NAME = "com.auth0.authentication.storage" } diff --git a/auth0/src/main/java/com/auth0/android/authentication/storage/Storage.kt b/auth0/src/main/java/com/auth0/android/authentication/storage/Storage.kt index f699cd49e..4e0644045 100644 --- a/auth0/src/main/java/com/auth0/android/authentication/storage/Storage.kt +++ b/auth0/src/main/java/com/auth0/android/authentication/storage/Storage.kt @@ -75,4 +75,9 @@ public interface Storage { * @param name the name of the value to remove. */ public fun remove(name: String) + + /** + * Removes all values from the storage. + */ + public fun removeAll() } \ No newline at end of file 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 c69534fda..3423fc9de 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 @@ -3,6 +3,7 @@ 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.authentication.storage.BaseCredentialsManager.Companion.DEFAULT_MIN_TTL import com.auth0.android.callback.Callback import com.auth0.android.request.Request import com.auth0.android.request.internal.GsonProvider @@ -672,7 +673,7 @@ public class CredentialsManagerTest { Mockito.`when`( client.renewAuth("refresh_token", "audience") ).thenReturn(request) - val newDate = Date(CredentialsMock.CURRENT_TIME_MS + 1 * 1000) + val newDate = Date(CredentialsMock.CURRENT_TIME_MS + (DEFAULT_MIN_TTL + 10) * 1000L) val jwtMock = mock() Mockito.`when`(jwtMock.expiresAt).thenReturn(newDate) Mockito.`when`(jwtDecoder.decode("newId")).thenReturn(jwtMock) @@ -1770,6 +1771,103 @@ public class CredentialsManagerTest { MatcherAssert.assertThat(manager.hasValidCredentials(), Is.`is`(true)) } + @Test + public fun shouldRenewCredentialsViaCallbackWhenTokenExpiresWithinDefaultMinTtl() { + // Token expires in 30 seconds, which is within DEFAULT_MIN_TTL (60s) + val expirationTime = CredentialsMock.CURRENT_TIME_MS + 30 * 1000 + 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") + 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) + val newDate = Date(CredentialsMock.ONE_HOUR_AHEAD_MS) + val jwtMock = mock() + Mockito.`when`(jwtMock.expiresAt).thenReturn(newDate) + Mockito.`when`(jwtDecoder.decode("newId")).thenReturn(jwtMock) + + val renewedCredentials = + Credentials("newId", "newAccess", "newType", "refreshToken", newDate, "newScope") + Mockito.`when`(request.execute()).thenReturn(renewedCredentials) + // Use no-arg getCredentials which now uses DEFAULT_MIN_TTL + manager.getCredentials(callback) + verify(callback).onSuccess( + credentialsCaptor.capture() + ) + // Verify renewal was triggered (client.renewAuth was called) + verify(client).renewAuth("refreshToken") + val retrievedCredentials = credentialsCaptor.firstValue + MatcherAssert.assertThat(retrievedCredentials, Is.`is`(Matchers.notNullValue())) + MatcherAssert.assertThat(retrievedCredentials.idToken, Is.`is`("newId")) + MatcherAssert.assertThat(retrievedCredentials.accessToken, Is.`is`("newAccess")) + } + + @Test + @ExperimentalCoroutinesApi + public fun shouldAwaitRenewedCredentialsWhenTokenExpiresWithinDefaultMinTtl(): Unit = runTest { + // Token expires in 30 seconds, which is within DEFAULT_MIN_TTL (60s) + val expirationTime = CredentialsMock.CURRENT_TIME_MS + 30 * 1000 + 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") + 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) + val newDate = Date(CredentialsMock.ONE_HOUR_AHEAD_MS) + val jwtMock = mock() + Mockito.`when`(jwtMock.expiresAt).thenReturn(newDate) + Mockito.`when`(jwtDecoder.decode("newId")).thenReturn(jwtMock) + + val renewedCredentials = + Credentials("newId", "newAccess", "newType", "refreshToken", newDate, "newScope") + Mockito.`when`(request.execute()).thenReturn(renewedCredentials) + // Use no-arg awaitCredentials which now uses DEFAULT_MIN_TTL + val result = manager.awaitCredentials() + // Verify renewal was triggered + verify(client).renewAuth("refreshToken") + MatcherAssert.assertThat(result, Is.`is`(Matchers.notNullValue())) + MatcherAssert.assertThat(result.idToken, Is.`is`("newId")) + MatcherAssert.assertThat(result.accessToken, Is.`is`("newAccess")) + } + + @Test + public fun shouldNotHaveValidCredentialsWhenTokenExpiresWithinDefaultMinTtlAndNoRefreshToken() { + // Token expires in 30 seconds, within DEFAULT_MIN_TTL (60s), and no refresh token + val expirationTime = CredentialsMock.CURRENT_TIME_MS + 30 * 1000 + 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.refresh_token")).thenReturn(null) + Mockito.`when`(storage.retrieveString("com.auth0.id_token")).thenReturn("idToken") + Mockito.`when`(storage.retrieveString("com.auth0.access_token")).thenReturn("accessToken") + // No-arg hasValidCredentials now uses DEFAULT_MIN_TTL, so token expiring in 30s is invalid + Assert.assertFalse(manager.hasValidCredentials()) + } + + @Test + public fun shouldHaveValidCredentialsWhenTokenExpiresWithinDefaultMinTtlButRefreshTokenAvailable() { + // Token expires in 30 seconds, within DEFAULT_MIN_TTL (60s), but refresh token is available + val expirationTime = CredentialsMock.CURRENT_TIME_MS + 30 * 1000 + 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.refresh_token")).thenReturn("refreshToken") + Mockito.`when`(storage.retrieveString("com.auth0.id_token")).thenReturn("idToken") + Mockito.`when`(storage.retrieveString("com.auth0.access_token")).thenReturn("accessToken") + // Even though token expires within DEFAULT_MIN_TTL, refresh token makes it valid + MatcherAssert.assertThat(manager.hasValidCredentials(), Is.`is`(true)) + } + @Test public fun shouldNotHaveCredentialsWhenAccessTokenAndIdTokenAreMissing() { Mockito.`when`(storage.retrieveString("com.auth0.id_token")).thenReturn(null) @@ -1812,7 +1910,7 @@ public class CredentialsManagerTest { //now, update the clock and retry manager.setClock(object : Clock { override fun getCurrentTimeMillis(): Long { - return CredentialsMock.CURRENT_TIME_MS - 1000 + return CredentialsMock.CURRENT_TIME_MS - (DEFAULT_MIN_TTL * 1000 + 1000) } }) MatcherAssert.assertThat(manager.hasValidCredentials(), Is.`is`(true)) @@ -1829,7 +1927,6 @@ public class CredentialsManagerTest { }) } - @Test public fun shouldAddParametersToRequest() { Mockito.`when`(storage.retrieveString("com.auth0.id_token")).thenReturn("idToken") 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 45fb805b2..e6ff95f3b 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 @@ -9,6 +9,7 @@ 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.authentication.storage.BaseCredentialsManager.Companion.DEFAULT_MIN_TTL import com.auth0.android.callback.Callback import com.auth0.android.request.Request import com.auth0.android.request.internal.GsonProvider @@ -2501,6 +2502,103 @@ public class SecureCredentialsManagerTest { MatcherAssert.assertThat(manager.hasValidCredentials(), Is.`is`(true)) } + @Test + public fun shouldRenewCredentialsViaCallbackWhenTokenExpiresWithinDefaultMinTtl() { + Mockito.`when`(localAuthenticationManager.authenticate()).then { + localAuthenticationManager.resultCallback.onSuccess(true) + } + // Token expires in 30 seconds, which is within DEFAULT_MIN_TTL (60s) + val expiresAt = Date(CredentialsMock.CURRENT_TIME_MS + 30 * 1000) + insertTestCredentials(false, true, true, expiresAt, "scope") + Mockito.`when`(storage.retrieveLong("com.auth0.credentials_access_token_expires_at")) + .thenReturn(expiresAt.time) + val newDate = Date(CredentialsMock.ONE_HOUR_AHEAD_MS) + val jwtMock = mock() + Mockito.`when`(jwtMock.expiresAt).thenReturn(newDate) + Mockito.`when`(jwtDecoder.decode("newId")).thenReturn(jwtMock) + Mockito.`when`( + client.renewAuth("refreshToken") + ).thenReturn(request) + val expectedCredentials = + Credentials("newId", "newAccess", "newType", "refreshToken", newDate, "newScope") + Mockito.`when`(request.execute()).thenReturn(expectedCredentials) + val expectedJson = gson.toJson(expectedCredentials) + Mockito.`when`(crypto.encrypt(expectedJson.toByteArray())) + .thenReturn(expectedJson.toByteArray()) + // Use no-arg getCredentials which now uses DEFAULT_MIN_TTL + manager.getCredentials(callback) + verify(callback).onSuccess( + credentialsCaptor.capture() + ) + // Verify renewal was triggered + verify(client).renewAuth("refreshToken") + val retrievedCredentials = credentialsCaptor.firstValue + MatcherAssert.assertThat(retrievedCredentials, Is.`is`(Matchers.notNullValue())) + MatcherAssert.assertThat(retrievedCredentials.idToken, Is.`is`("newId")) + MatcherAssert.assertThat(retrievedCredentials.accessToken, Is.`is`("newAccess")) + } + + @Test + @ExperimentalCoroutinesApi + public fun shouldAwaitRenewedCredentialsWhenTokenExpiresWithinDefaultMinTtl(): Unit = runTest { + Mockito.`when`(localAuthenticationManager.authenticate()).then { + localAuthenticationManager.resultCallback.onSuccess(true) + } + // Token expires in 30 seconds, which is within DEFAULT_MIN_TTL (60s) + val expiresAt = Date(CredentialsMock.CURRENT_TIME_MS + 30 * 1000) + insertTestCredentials(false, true, true, expiresAt, "scope") + Mockito.`when`(storage.retrieveLong("com.auth0.credentials_access_token_expires_at")) + .thenReturn(expiresAt.time) + val newDate = Date(CredentialsMock.ONE_HOUR_AHEAD_MS) + val jwtMock = mock() + Mockito.`when`(jwtMock.expiresAt).thenReturn(newDate) + Mockito.`when`(jwtDecoder.decode("newId")).thenReturn(jwtMock) + Mockito.`when`( + client.renewAuth("refreshToken") + ).thenReturn(request) + val expectedCredentials = + Credentials("newId", "newAccess", "newType", "refreshToken", newDate, "newScope") + Mockito.`when`(request.execute()).thenReturn(expectedCredentials) + val expectedJson = gson.toJson(expectedCredentials) + Mockito.`when`(crypto.encrypt(expectedJson.toByteArray())) + .thenReturn(expectedJson.toByteArray()) + // Use no-arg awaitCredentials which now uses DEFAULT_MIN_TTL + val result = manager.awaitCredentials() + // Verify renewal was triggered + verify(client).renewAuth("refreshToken") + MatcherAssert.assertThat(result, Is.`is`(Matchers.notNullValue())) + MatcherAssert.assertThat(result.idToken, Is.`is`("newId")) + MatcherAssert.assertThat(result.accessToken, Is.`is`("newAccess")) + } + + @Test + public fun shouldNotHaveValidCredentialsWhenTokenExpiresWithinDefaultMinTtlAndNoRefreshToken() { + // Token expires in 30 seconds, within DEFAULT_MIN_TTL (60s), and no refresh token + val expirationTime = CredentialsMock.CURRENT_TIME_MS + 30 * 1000 + Mockito.`when`(storage.retrieveLong("com.auth0.credentials_access_token_expires_at")) + .thenReturn(expirationTime) + Mockito.`when`(storage.retrieveBoolean("com.auth0.credentials_can_refresh")) + .thenReturn(false) + Mockito.`when`(storage.retrieveString("com.auth0.credentials")) + .thenReturn("{\"access_token\":\"accessToken\"}") + // No-arg hasValidCredentials now uses DEFAULT_MIN_TTL, so token expiring in 30s is invalid + Assert.assertFalse(manager.hasValidCredentials()) + } + + @Test + public fun shouldHaveValidCredentialsWhenTokenExpiresWithinDefaultMinTtlButRefreshTokenAvailable() { + // Token expires in 30 seconds, within DEFAULT_MIN_TTL (60s), but refresh token is available + val expirationTime = CredentialsMock.CURRENT_TIME_MS + 30 * 1000 + Mockito.`when`(storage.retrieveLong("com.auth0.credentials_access_token_expires_at")) + .thenReturn(expirationTime) + Mockito.`when`(storage.retrieveBoolean("com.auth0.credentials_can_refresh")) + .thenReturn(true) + Mockito.`when`(storage.retrieveString("com.auth0.credentials")) + .thenReturn("{\"access_token\":\"accessToken\", \"refresh_token\":\"refreshToken\"}") + // Even though token expires within DEFAULT_MIN_TTL, refresh token makes it valid + MatcherAssert.assertThat(manager.hasValidCredentials(), Is.`is`(true)) + } + @Test public fun shouldHaveCredentialsWhenTheAliasUsedHasNotBeenMigratedYet() { val expirationTime = CredentialsMock.ONE_HOUR_AHEAD_MS @@ -3334,7 +3432,7 @@ public class SecureCredentialsManagerTest { //now, update the clock and retry manager.setClock(object : Clock { override fun getCurrentTimeMillis(): Long { - return CredentialsMock.CURRENT_TIME_MS - 1000 + return CredentialsMock.CURRENT_TIME_MS - (DEFAULT_MIN_TTL * 1000 + 1000) } }) MatcherAssert.assertThat(manager.hasValidCredentials(), Is.`is`(true)) diff --git a/auth0/src/test/java/com/auth0/android/authentication/storage/SharedPreferencesStorageTest.java b/auth0/src/test/java/com/auth0/android/authentication/storage/SharedPreferencesStorageTest.java index 82fdfc210..89c2c7983 100644 --- a/auth0/src/test/java/com/auth0/android/authentication/storage/SharedPreferencesStorageTest.java +++ b/auth0/src/test/java/com/auth0/android/authentication/storage/SharedPreferencesStorageTest.java @@ -221,4 +221,13 @@ public void shouldRemovePreferencesKey() { verify(sharedPreferencesEditor).apply(); } + @Test + public void shouldRemoveAllPreferencesKeys() { + when(sharedPreferencesEditor.clear()).thenReturn(sharedPreferencesEditor); + SharedPreferencesStorage storage = new SharedPreferencesStorage(context); + storage.removeAll(); + verify(sharedPreferencesEditor).clear(); + verify(sharedPreferencesEditor).apply(); + } + } \ No newline at end of file diff --git a/auth0/src/test/java/com/auth0/android/provider/WebAuthProviderTest.kt b/auth0/src/test/java/com/auth0/android/provider/WebAuthProviderTest.kt index 672aeb215..1939fdd78 100644 --- a/auth0/src/test/java/com/auth0/android/provider/WebAuthProviderTest.kt +++ b/auth0/src/test/java/com/auth0/android/provider/WebAuthProviderTest.kt @@ -45,7 +45,6 @@ import org.hamcrest.core.IsNot.not import org.hamcrest.core.IsNull.notNullValue import org.junit.Assert import org.junit.Before -import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers @@ -57,6 +56,7 @@ import org.mockito.MockitoAnnotations import org.robolectric.Robolectric import org.robolectric.RobolectricTestRunner import org.robolectric.annotation.Config +import org.robolectric.annotation.ConscryptMode import org.robolectric.shadows.ShadowLooper import java.io.ByteArrayInputStream import java.io.InputStream @@ -1539,18 +1539,17 @@ public class WebAuthProviderTest { } - // TODO: https://auth0team.atlassian.net/browse/SDK-7752 + @ConscryptMode(ConscryptMode.Mode.OFF) @Test - @Ignore("Fix these failing tests in CI once Roboelectric and other dependencies are updated") @Throws(Exception::class) public fun shouldFailToResumeLoginWhenRSAKeyIsMissingFromJWKSet() { val pkce = Mockito.mock(PKCE::class.java) `when`(pkce.codeChallenge).thenReturn("challenge") - val mockAPI = AuthenticationAPIMockServer() - mockAPI.willReturnEmptyJsonWebKeys() + val networkingClient: NetworkingClient = Mockito.spy(DefaultClient()) val authCallback = mock>() - val proxyAccount: Auth0 = Auth0.getInstance(JwtTestUtils.EXPECTED_AUDIENCE, mockAPI.domain) - proxyAccount.networkingClient = SSLTestUtils.testClient + val proxyAccount = + Auth0.getInstance(JwtTestUtils.EXPECTED_AUDIENCE, JwtTestUtils.EXPECTED_BASE_DOMAIN) + proxyAccount.networkingClient = networkingClient login(proxyAccount) .withState("1234567890") .withNonce(JwtTestUtils.EXPECTED_NONCE) @@ -1583,12 +1582,19 @@ public class WebAuthProviderTest { Date(), "codeScope" ) + // Mock JWKS response with empty keys (no matching RSA key for kid) + val emptyJwksJson = """{"keys": []}""" + val jwksInputStream: InputStream = ByteArrayInputStream(emptyJwksJson.toByteArray()) + val jwksResponse = ServerResponse(200, jwksInputStream, emptyMap()) + Mockito.doReturn(jwksResponse).`when`(networkingClient).load( + eq(proxyAccount.getDomainUrl() + ".well-known/jwks.json"), + any() + ) Mockito.doAnswer { callbackCaptor.firstValue.onSuccess(codeCredentials) null }.`when`(pkce).getToken(eq("1234"), callbackCaptor.capture()) Assert.assertTrue(resume(intent)) - mockAPI.takeRequest() ShadowLooper.idleMainLooper() verify(authCallback).onFailure(authExceptionCaptor.capture()) val error = authExceptionCaptor.firstValue @@ -1604,7 +1610,6 @@ public class WebAuthProviderTest { error.cause?.message, `is`("Could not find a public key for kid \"key123\"") ) - mockAPI.shutdown() } @Test @@ -1674,18 +1679,17 @@ public class WebAuthProviderTest { } - //TODO: https://auth0team.atlassian.net/browse/SDK-7752 + @ConscryptMode(ConscryptMode.Mode.OFF) @Test - @Ignore("Fix these failing tests in CI once Roboelectric and other dependencies are updated") @Throws(Exception::class) public fun shouldFailToResumeLoginWhenKeyIdIsMissingFromIdTokenHeader() { val pkce = Mockito.mock(PKCE::class.java) `when`(pkce.codeChallenge).thenReturn("challenge") - val mockAPI = AuthenticationAPIMockServer() - mockAPI.willReturnValidJsonWebKeys() + val networkingClient: NetworkingClient = Mockito.spy(DefaultClient()) val authCallback = mock>() - val proxyAccount: Auth0 = Auth0.getInstance(JwtTestUtils.EXPECTED_AUDIENCE, mockAPI.domain) - proxyAccount.networkingClient = SSLTestUtils.testClient + val proxyAccount = + Auth0.getInstance(JwtTestUtils.EXPECTED_AUDIENCE, JwtTestUtils.EXPECTED_BASE_DOMAIN) + proxyAccount.networkingClient = networkingClient login(proxyAccount) .withState("1234567890") .withNonce("abcdefg") @@ -1717,12 +1721,19 @@ public class WebAuthProviderTest { Date(), "codeScope" ) + // Mock JWKS response with valid keys + val encoded = Files.readAllBytes(Paths.get("src/test/resources/rsa_jwks.json")) + val jwksInputStream: InputStream = ByteArrayInputStream(encoded) + val jwksResponse = ServerResponse(200, jwksInputStream, emptyMap()) + Mockito.doReturn(jwksResponse).`when`(networkingClient).load( + eq(proxyAccount.getDomainUrl() + ".well-known/jwks.json"), + any() + ) Mockito.doAnswer { callbackCaptor.firstValue.onSuccess(codeCredentials) null }.`when`(pkce).getToken(eq("1234"), callbackCaptor.capture()) Assert.assertTrue(resume(intent)) - mockAPI.takeRequest() ShadowLooper.idleMainLooper() verify(authCallback).onFailure(authExceptionCaptor.capture()) val error = authExceptionCaptor.firstValue @@ -1738,7 +1749,6 @@ public class WebAuthProviderTest { error.cause?.message, `is`("Could not find a public key for kid \"null\"") ) - mockAPI.shutdown() } @Test diff --git a/auth0/src/test/java/com/auth0/android/request/internal/ThreadSwitcherShadow.java b/auth0/src/test/java/com/auth0/android/request/internal/ThreadSwitcherShadow.java index 0c096ba3f..17100bf9f 100644 --- a/auth0/src/test/java/com/auth0/android/request/internal/ThreadSwitcherShadow.java +++ b/auth0/src/test/java/com/auth0/android/request/internal/ThreadSwitcherShadow.java @@ -26,4 +26,9 @@ public ThreadSwitcherShadow() { public void backgroundThread(Runnable runnable) { executor.execute(runnable); } + + @Implementation + public void mainThread(Runnable runnable) { + executor.execute(runnable); + } }