From 7521d105003056924768f3a8c840c92fb11bf8e7 Mon Sep 17 00:00:00 2001 From: Nandan Prabhu Date: Tue, 10 Jun 2025 11:05:10 +0530 Subject: [PATCH 1/6] GH issue Allow accessing credentials without an attempt to refresh #378 --- .../storage/BaseCredentialsManager.kt | 8 ++++ .../storage/CredentialsManager.kt | 48 +++++++++++++++++++ .../storage/SecureCredentialsManager.kt | 48 +++++++++++++++++++ gradle/maven-publish.gradle | 5 -- sample/src/main/res/values/strings.xml | 4 +- 5 files changed, 106 insertions(+), 7 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 daf4cd00..09c86d03 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 @@ -3,11 +3,16 @@ package com.auth0.android.authentication.storage import androidx.annotation.VisibleForTesting import com.auth0.android.authentication.AuthenticationAPIClient import com.auth0.android.callback.Callback +import com.auth0.android.request.internal.GsonProvider +import com.auth0.android.request.internal.Jwt import com.auth0.android.result.APICredentials import com.auth0.android.result.Credentials import com.auth0.android.result.SSOCredentials +import com.auth0.android.result.UserProfile import com.auth0.android.util.Clock import java.util.* +import kotlin.collections.component1 +import kotlin.collections.component2 /** * Base class meant to abstract common logic across Credentials Manager implementations. @@ -38,6 +43,7 @@ public abstract class BaseCredentialsManager internal constructor( callback: Callback ) + public abstract fun getSsoCredentials( callback: Callback ) @@ -136,6 +142,8 @@ public abstract class BaseCredentialsManager internal constructor( headers: Map = emptyMap() ): APICredentials + public abstract val userProfile: UserProfile? + public abstract fun clearCredentials() public abstract fun clearApiCredentials(audience: String) public abstract fun hasValidCredentials(): Boolean 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 c9962e45..3c7fcefa 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,21 +1,28 @@ package com.auth0.android.authentication.storage import android.text.TextUtils +import android.util.Base64 import android.util.Log import androidx.annotation.VisibleForTesting import com.auth0.android.authentication.AuthenticationAPIClient import com.auth0.android.authentication.AuthenticationException +import com.auth0.android.authentication.storage.SecureCredentialsManager.Companion.KEY_CREDENTIALS import com.auth0.android.callback.Callback import com.auth0.android.request.internal.GsonProvider +import com.auth0.android.request.internal.Jwt import com.auth0.android.result.APICredentials import com.auth0.android.result.Credentials +import com.auth0.android.result.OptionalCredentials import com.auth0.android.result.SSOCredentials +import com.auth0.android.result.UserProfile import com.auth0.android.result.toAPICredentials import com.google.gson.Gson import kotlinx.coroutines.suspendCancellableCoroutine import java.util.* import java.util.concurrent.Executor import java.util.concurrent.Executors +import kotlin.collections.component1 +import kotlin.collections.component2 import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException @@ -44,6 +51,47 @@ public class CredentialsManager @VisibleForTesting(otherwise = VisibleForTesting Executors.newSingleThreadExecutor() ) + private fun retrieveCredentials() : Credentials? { + val encryptedEncoded = storage.retrieveString(KEY_CREDENTIALS) + if (encryptedEncoded.isNullOrBlank()) { + return null + } + val encrypted = android.util.Base64.decode(encryptedEncoded, Base64.DEFAULT) + val json: String + try { + json = String(crypto.decrypt(encrypted)) + } catch (e: IncompatibleDeviceException) { + return null + } catch (e: CryptoException) { + return null + } + val bridgeCredentials = gson.fromJson(json, OptionalCredentials::class.java)/* OPTIONAL CREDENTIALS + * This bridge is required to prevent users from being logged out when + * migrating from Credentials with optional Access Token and ID token + */ + val credentials = Credentials( + bridgeCredentials.idToken.orEmpty(), + bridgeCredentials.accessToken.orEmpty(), + bridgeCredentials.type.orEmpty(), + bridgeCredentials.refreshToken, + bridgeCredentials.expiresAt ?: Date(), + bridgeCredentials.scope + ) + return credentials + } + + public override val userProfile: UserProfile? + get() { + val credentials: Credentials? = retrieveCredentials() + // Handle null credentials gracefully + if (credentials == null) { + return null + } + val (_, payload) = Jwt.splitToken(credentials.idToken) + val gson = GsonProvider.gson + return gson.fromJson(Jwt.decodeBase64(payload), UserProfile::class.java) + } + /** * Stores the given credentials in the storage. Must have an access_token or id_token and a expires_in value. * 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 d3d4c311..2cd61aaa 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 @@ -11,16 +11,23 @@ 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.request.internal.Jwt import com.auth0.android.result.APICredentials import com.auth0.android.result.Credentials import com.auth0.android.result.OptionalCredentials import com.auth0.android.result.SSOCredentials +import com.auth0.android.result.UserProfile import com.auth0.android.result.toAPICredentials import com.google.gson.Gson +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch import kotlinx.coroutines.suspendCancellableCoroutine import java.lang.ref.WeakReference import java.util.* import java.util.concurrent.Executor +import kotlin.collections.component1 +import kotlin.collections.component2 import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException @@ -250,6 +257,47 @@ public class SecureCredentialsManager @VisibleForTesting(otherwise = VisibleForT } } + private fun retrieveCredentials() : Credentials? { + val encryptedEncoded = storage.retrieveString(KEY_CREDENTIALS) + if (encryptedEncoded.isNullOrBlank()) { + return null + } + val encrypted = Base64.decode(encryptedEncoded, Base64.DEFAULT) + val json: String + try { + json = String(crypto.decrypt(encrypted)) + } catch (e: IncompatibleDeviceException) { + return null + } catch (e: CryptoException) { + return null + } + val bridgeCredentials = gson.fromJson(json, OptionalCredentials::class.java)/* OPTIONAL CREDENTIALS + * This bridge is required to prevent users from being logged out when + * migrating from Credentials with optional Access Token and ID token + */ + val credentials = Credentials( + bridgeCredentials.idToken.orEmpty(), + bridgeCredentials.accessToken.orEmpty(), + bridgeCredentials.type.orEmpty(), + bridgeCredentials.refreshToken, + bridgeCredentials.expiresAt ?: Date(), + bridgeCredentials.scope + ) + return credentials + } + + public override val userProfile: UserProfile? + get() { + val credentials: Credentials? = retrieveCredentials() + // Handle null credentials gracefully + if (credentials == null) { + return null + } + val (_, payload) = Jwt.splitToken(credentials.idToken) + val gson = GsonProvider.gson + return gson.fromJson(Jwt.decodeBase64(payload), UserProfile::class.java) + } + /** * Creates a new request to exchange a refresh token for a session transfer token that can be used to perform web single sign-on. * diff --git a/gradle/maven-publish.gradle b/gradle/maven-publish.gradle index 567e224c..950b2a93 100644 --- a/gradle/maven-publish.gradle +++ b/gradle/maven-publish.gradle @@ -50,11 +50,6 @@ publishing { } } - scm { - url = POM_SCM_URL - connection = POM_SCM_CONNECTION - developerConnection = POM_SCM_DEV_CONNECTION - } withXml { def dependenciesNode = asNode().appendNode('dependencies') diff --git a/sample/src/main/res/values/strings.xml b/sample/src/main/res/values/strings.xml index 903e2a2e..25f8928b 100644 --- a/sample/src/main/res/values/strings.xml +++ b/sample/src/main/res/values/strings.xml @@ -1,6 +1,6 @@ Auth0 SDK Sample - YOUR_DOMAIN - YOUR_CLIENT_ID + int-dx-enterprise-test.us.auth0.com + GGUVoHL5nseaacSzqB810HWYGHZI34m8 demo \ No newline at end of file From 0ff769832320a8aa3833d8951d2daf3e83bb9ce7 Mon Sep 17 00:00:00 2001 From: Nandan Prabhu Date: Tue, 10 Jun 2025 11:09:40 +0530 Subject: [PATCH 2/6] fixes a build issue in CredentialsManager --- .../storage/CredentialsManager.kt | 37 ++----------------- 1 file changed, 4 insertions(+), 33 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 3c7fcefa..b60a6270 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 @@ -51,43 +51,14 @@ public class CredentialsManager @VisibleForTesting(otherwise = VisibleForTesting Executors.newSingleThreadExecutor() ) - private fun retrieveCredentials() : Credentials? { - val encryptedEncoded = storage.retrieveString(KEY_CREDENTIALS) - if (encryptedEncoded.isNullOrBlank()) { - return null - } - val encrypted = android.util.Base64.decode(encryptedEncoded, Base64.DEFAULT) - val json: String - try { - json = String(crypto.decrypt(encrypted)) - } catch (e: IncompatibleDeviceException) { - return null - } catch (e: CryptoException) { - return null - } - val bridgeCredentials = gson.fromJson(json, OptionalCredentials::class.java)/* OPTIONAL CREDENTIALS - * This bridge is required to prevent users from being logged out when - * migrating from Credentials with optional Access Token and ID token - */ - val credentials = Credentials( - bridgeCredentials.idToken.orEmpty(), - bridgeCredentials.accessToken.orEmpty(), - bridgeCredentials.type.orEmpty(), - bridgeCredentials.refreshToken, - bridgeCredentials.expiresAt ?: Date(), - bridgeCredentials.scope - ) - return credentials - } - public override val userProfile: UserProfile? get() { - val credentials: Credentials? = retrieveCredentials() - // Handle null credentials gracefully - if (credentials == null) { + val idToken = storage.retrieveString(KEY_ID_TOKEN) + + if (idToken.isNullOrBlank()) { return null } - val (_, payload) = Jwt.splitToken(credentials.idToken) + val (_, payload) = Jwt.splitToken(idToken) val gson = GsonProvider.gson return gson.fromJson(Jwt.decodeBase64(payload), UserProfile::class.java) } From 0a4c311b12a029cb2500b58ff66c7900623ab0ee Mon Sep 17 00:00:00 2001 From: Nandan Prabhu Date: Tue, 10 Jun 2025 12:30:30 +0530 Subject: [PATCH 3/6] reverts few changes --- .../android/authentication/storage/CredentialsManagerTest.kt | 5 +++++ .../authentication/storage/SecureCredentialsManagerTest.kt | 4 ++++ gradle/maven-publish.gradle | 5 +++++ sample/src/main/res/values/strings.xml | 4 ++-- 4 files changed, 16 insertions(+), 2 deletions(-) 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 c4397f8c..1c22dbef 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 @@ -721,6 +721,11 @@ public class CredentialsManagerTest { Assert.assertEquals(retrievedCredentials.accessToken, renewedCredentials.accessToken) } + @Test + public fun shouldFailOnUserProfileWhenCredentialsIsNotSaved() { + + } + @Test public fun shouldFailOnGetCredentialsWhenNoAccessTokenOrIdTokenWasSaved() { verifyNoMoreInteractions(client) 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 641fbf53..c861b470 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 @@ -174,6 +174,10 @@ public class SecureCredentialsManagerTest { MatcherAssert.assertThat(manager, Is.`is`(Matchers.notNullValue())) } + @Test + public fun shouldNotFailToRetrieveCredentials() { + + } /* * SAVE SSO credentials test diff --git a/gradle/maven-publish.gradle b/gradle/maven-publish.gradle index 950b2a93..567e224c 100644 --- a/gradle/maven-publish.gradle +++ b/gradle/maven-publish.gradle @@ -50,6 +50,11 @@ publishing { } } + scm { + url = POM_SCM_URL + connection = POM_SCM_CONNECTION + developerConnection = POM_SCM_DEV_CONNECTION + } withXml { def dependenciesNode = asNode().appendNode('dependencies') diff --git a/sample/src/main/res/values/strings.xml b/sample/src/main/res/values/strings.xml index 25f8928b..903e2a2e 100644 --- a/sample/src/main/res/values/strings.xml +++ b/sample/src/main/res/values/strings.xml @@ -1,6 +1,6 @@ Auth0 SDK Sample - int-dx-enterprise-test.us.auth0.com - GGUVoHL5nseaacSzqB810HWYGHZI34m8 + YOUR_DOMAIN + YOUR_CLIENT_ID demo \ No newline at end of file From e89b7557bd3568132e8774ce6c9e8aac400f3afc Mon Sep 17 00:00:00 2001 From: Nandan Prabhu Date: Mon, 16 Jun 2025 12:33:59 +0530 Subject: [PATCH 4/6] resolves PR comments --- .../storage/SecureCredentialsManager.kt | 35 ++----------------- .../storage/SecureCredentialsManagerTest.kt | 5 +++ gradle/jacoco.gradle | 8 ++--- 3 files changed, 11 insertions(+), 37 deletions(-) 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 2cd61aaa..f1f2c15a 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 @@ -257,45 +257,14 @@ public class SecureCredentialsManager @VisibleForTesting(otherwise = VisibleForT } } - private fun retrieveCredentials() : Credentials? { - val encryptedEncoded = storage.retrieveString(KEY_CREDENTIALS) - if (encryptedEncoded.isNullOrBlank()) { - return null - } - val encrypted = Base64.decode(encryptedEncoded, Base64.DEFAULT) - val json: String - try { - json = String(crypto.decrypt(encrypted)) - } catch (e: IncompatibleDeviceException) { - return null - } catch (e: CryptoException) { - return null - } - val bridgeCredentials = gson.fromJson(json, OptionalCredentials::class.java)/* OPTIONAL CREDENTIALS - * This bridge is required to prevent users from being logged out when - * migrating from Credentials with optional Access Token and ID token - */ - val credentials = Credentials( - bridgeCredentials.idToken.orEmpty(), - bridgeCredentials.accessToken.orEmpty(), - bridgeCredentials.type.orEmpty(), - bridgeCredentials.refreshToken, - bridgeCredentials.expiresAt ?: Date(), - bridgeCredentials.scope - ) - return credentials - } - public override val userProfile: UserProfile? get() { - val credentials: Credentials? = retrieveCredentials() + val credentials: Credentials? = getExistingCredentials() // Handle null credentials gracefully if (credentials == null) { return null } - val (_, payload) = Jwt.splitToken(credentials.idToken) - val gson = GsonProvider.gson - return gson.fromJson(Jwt.decodeBase64(payload), UserProfile::class.java) + return credentials.user } /** 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 c861b470..2bfdd0f0 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 @@ -832,6 +832,11 @@ public class SecureCredentialsManagerTest { manager.saveCredentials(credentials) } +// @Test +// public fun retrieveCredentials() { +// +// } + /* * GET Credentials tests */ diff --git a/gradle/jacoco.gradle b/gradle/jacoco.gradle index 5a1e3c32..e197c960 100644 --- a/gradle/jacoco.gradle +++ b/gradle/jacoco.gradle @@ -44,10 +44,10 @@ afterEvaluate { sourceDirectories.from = ['src/main/java'].plus(android.sourceSets[name].java.srcDirs) executionData.from = "${buildDir}/jacoco/${testTaskName}.exec" - reports { - xml.enabled = true - html.enabled = true - } +// reports { +// xml.enabled = true +// html.enabled = true +// } } jacocoTestReportTask.dependsOn reportTask } From c98cbdd344fb638ca9d2934dcb48d48f6d50f208 Mon Sep 17 00:00:00 2001 From: Nandan Prabhu Date: Tue, 17 Jun 2025 10:29:42 +0530 Subject: [PATCH 5/6] reverts changes --- .../authentication/storage/CredentialsManagerTest.kt | 5 ----- .../storage/SecureCredentialsManagerTest.kt | 5 ----- gradle/jacoco.gradle | 8 ++++---- 3 files changed, 4 insertions(+), 14 deletions(-) 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 1c22dbef..c4397f8c 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 @@ -721,11 +721,6 @@ public class CredentialsManagerTest { Assert.assertEquals(retrievedCredentials.accessToken, renewedCredentials.accessToken) } - @Test - public fun shouldFailOnUserProfileWhenCredentialsIsNotSaved() { - - } - @Test public fun shouldFailOnGetCredentialsWhenNoAccessTokenOrIdTokenWasSaved() { verifyNoMoreInteractions(client) 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 2bfdd0f0..c861b470 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 @@ -832,11 +832,6 @@ public class SecureCredentialsManagerTest { manager.saveCredentials(credentials) } -// @Test -// public fun retrieveCredentials() { -// -// } - /* * GET Credentials tests */ diff --git a/gradle/jacoco.gradle b/gradle/jacoco.gradle index e197c960..5a1e3c32 100644 --- a/gradle/jacoco.gradle +++ b/gradle/jacoco.gradle @@ -44,10 +44,10 @@ afterEvaluate { sourceDirectories.from = ['src/main/java'].plus(android.sourceSets[name].java.srcDirs) executionData.from = "${buildDir}/jacoco/${testTaskName}.exec" -// reports { -// xml.enabled = true -// html.enabled = true -// } + reports { + xml.enabled = true + html.enabled = true + } } jacocoTestReportTask.dependsOn reportTask } From 3a5ebdf7fc1c3470002879250f74169dda504d6c Mon Sep 17 00:00:00 2001 From: Nandan Prabhu Date: Mon, 23 Jun 2025 11:45:44 +0530 Subject: [PATCH 6/6] Address PR comments --- .../authentication/storage/SecureCredentialsManagerTest.kt | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) 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 c861b470..b91ebe55 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 @@ -173,12 +173,7 @@ public class SecureCredentialsManagerTest { ) MatcherAssert.assertThat(manager, Is.`is`(Matchers.notNullValue())) } - - @Test - public fun shouldNotFailToRetrieveCredentials() { - - } - + /* * SAVE SSO credentials test */