From 7b86a9717ed5872021caa5bc3ab1a95f171ad8c8 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 17 Oct 2025 14:30:26 -0400 Subject: [PATCH 1/9] DataConnectAuth.kt: Add authUid property --- .../dataconnect/core/DataConnectAppCheck.kt | 4 +-- .../dataconnect/core/DataConnectAuth.kt | 33 ++++++++++++++++--- .../DataConnectCredentialsTokenManager.kt | 25 +++++++++----- 3 files changed, 47 insertions(+), 15 deletions(-) diff --git a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectAppCheck.kt b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectAppCheck.kt index ce069f54a61..694140e46ac 100644 --- a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectAppCheck.kt +++ b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectAppCheck.kt @@ -42,10 +42,10 @@ internal class DataConnectAppCheck( private val appCheckTokenListener = AppCheckTokenListenerImpl(logger) @DeferredApi - override fun addTokenListener(provider: InteropAppCheckTokenProvider) = + override fun registerProvider(provider: InteropAppCheckTokenProvider) = provider.addAppCheckTokenListener(appCheckTokenListener) - override fun removeTokenListener(provider: InteropAppCheckTokenProvider) = + override fun unregisterProvider(provider: InteropAppCheckTokenProvider) = provider.removeAppCheckTokenListener(appCheckTokenListener) override suspend fun getToken(provider: InteropAppCheckTokenProvider, forceRefresh: Boolean) = diff --git a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectAuth.kt b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectAuth.kt index 59131a1ddad..5a2299d4e25 100644 --- a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectAuth.kt +++ b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectAuth.kt @@ -23,6 +23,8 @@ import com.google.firebase.dataconnect.core.DataConnectAuth.GetAuthTokenResult import com.google.firebase.dataconnect.core.Globals.toScrubbedAccessToken import com.google.firebase.dataconnect.core.LoggerGlobals.debug import com.google.firebase.internal.InternalTokenResult +import java.util.concurrent.locks.ReentrantLock +import kotlin.concurrent.withLock import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.tasks.await @@ -39,14 +41,37 @@ internal class DataConnectAuth( blockingDispatcher = blockingDispatcher, logger = logger, ) { - private val idTokenListener = IdTokenListenerImpl(logger) + + private data class ProviderIdTokenListenerPair( + val provider: InternalAuthProvider, + val idTokenListener: IdTokenListenerImpl, + ) + + private val providersLock = ReentrantLock() + private val providers = mutableListOf() + + /** + * The Firebase Auth UID of the current user, or `null` if Firebase Auth is not (yet) available or + * if there is no logged-in user. + */ + val authUid: String? + get() = providersLock.withLock { providers.lastOrNull()?.provider?.uid } @DeferredApi - override fun addTokenListener(provider: InternalAuthProvider) = + override fun registerProvider(provider: InternalAuthProvider) { + val idTokenListener = IdTokenListenerImpl(logger) provider.addIdTokenListener(idTokenListener) + providersLock.withLock { providers.add(ProviderIdTokenListenerPair(provider, idTokenListener)) } + } - override fun removeTokenListener(provider: InternalAuthProvider) = - provider.removeIdTokenListener(idTokenListener) + override fun unregisterProvider(provider: InternalAuthProvider) { + val idTokenListener = + providersLock.withLock { + val index = providers.indexOfLast { it.provider === provider } + if (index < 0) null else providers.removeAt(index).idTokenListener + } + idTokenListener?.let { provider.removeIdTokenListener(idTokenListener) } + } override suspend fun getToken(provider: InternalAuthProvider, forceRefresh: Boolean) = provider.getAccessToken(forceRefresh).await().let { GetAuthTokenResult(it.token) } diff --git a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectCredentialsTokenManager.kt b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectCredentialsTokenManager.kt index 90330820bfc..ae36a9c375b 100644 --- a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectCredentialsTokenManager.kt +++ b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectCredentialsTokenManager.kt @@ -123,18 +123,25 @@ internal sealed class DataConnectCredentialsTokenManager>(State.New) /** - * Adds the token listener to the given provider. + * Registers the given provider [T] instance if, and when, it becomes available. * - * @see removeTokenListener + * The implementation may want, or need, to register a listener of some sort with the provider in + * order to be notified of provider state changes, such as a user logging in or logging out. + * + * @see unregisterProvider */ - @DeferredApi protected abstract fun addTokenListener(provider: T) + @DeferredApi protected abstract fun registerProvider(provider: T) /** - * Removes the token listener from the given provider. + * Registers the given provider [T] instance, which can occur if a new provider becomes available + * of if [close] is called. + * + * The implementation should undo any work done by [registerProvider], such as unregistering + * listeners. * - * @see addTokenListener + * @see registerProvider */ - protected abstract fun removeTokenListener(provider: T) + protected abstract fun unregisterProvider(provider: T) /** * Starts an asynchronous task to get a new access token from the given provider, forcing a token @@ -192,7 +199,7 @@ internal sealed class DataConnectCredentialsTokenManager {} is State.Closed -> {} is State.StateWithProvider -> { - removeTokenListener(oldState.provider) + unregisterProvider(oldState.provider) } } } @@ -398,7 +405,7 @@ internal sealed class DataConnectCredentialsTokenManager @@ -422,7 +429,7 @@ internal sealed class DataConnectCredentialsTokenManager {} is State.Idle -> {} From 44bcda8f07800e1e088f0fe6d63a669ec13e2e56 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 17 Oct 2025 14:39:42 -0400 Subject: [PATCH 2/9] DataConnectAuth.kt: add authUid to GetAuthTokenResult This change extracts the `user_id` and `sub` claims from the Firebase Auth token and sends them to DataConnect as `authUids`. * DataConnectAuth.kt: * Update `getToken` to include `authUids` * Update `GetAuthTokenResult` to hold `authUids` * Add `getAuthUids()` to extract UIDs from token claims * arbs.kt: * Update `authTokenResult` to generate `authUids` --- .../dataconnect/core/DataConnectAuth.kt | 22 +++++++++++++++++-- .../testutil/property/arbitrary/arbs.kt | 8 ++++--- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectAuth.kt b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectAuth.kt index 5a2299d4e25..fd3e65d1f61 100644 --- a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectAuth.kt +++ b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectAuth.kt @@ -74,13 +74,31 @@ internal class DataConnectAuth( } override suspend fun getToken(provider: InternalAuthProvider, forceRefresh: Boolean) = - provider.getAccessToken(forceRefresh).await().let { GetAuthTokenResult(it.token) } + provider.getAccessToken(forceRefresh).await().let { + GetAuthTokenResult(it.token, it.getAuthUids()) + } - data class GetAuthTokenResult(override val token: String?) : GetTokenResult + data class GetAuthTokenResult(override val token: String?, val authUids: Set) : + GetTokenResult private class IdTokenListenerImpl(private val logger: Logger) : IdTokenListener { override fun onIdTokenChanged(tokenResult: InternalTokenResult) { logger.debug { "onIdTokenChanged(token=${tokenResult.token?.toScrubbedAccessToken()})" } } } + + private companion object { + + val authUidClaimNames = listOf("user_id", "sub") + + fun com.google.firebase.auth.GetTokenResult.getAuthUids(): Set = buildSet { + authUidClaimNames.forEach { claimName -> + claims[claimName]?.let { claimValue -> + if (claimValue is String) { + add(claimValue) + } + } + } + } + } } diff --git a/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/testutil/property/arbitrary/arbs.kt b/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/testutil/property/arbitrary/arbs.kt index 6d66f01fa88..91ecd70f013 100644 --- a/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/testutil/property/arbitrary/arbs.kt +++ b/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/testutil/property/arbitrary/arbs.kt @@ -51,6 +51,7 @@ import io.kotest.property.arbitrary.int import io.kotest.property.arbitrary.list import io.kotest.property.arbitrary.map import io.kotest.property.arbitrary.orNull +import io.kotest.property.arbitrary.set import io.kotest.property.arbitrary.string import io.mockk.coEvery import io.mockk.mockk @@ -333,9 +334,10 @@ internal inline fun DataConnectArb.operationRefConstru } internal fun DataConnectArb.authTokenResult( - accessToken: Arb = accessToken() -): Arb = accessToken.map { GetAuthTokenResult(it) } + accessToken: Arb = accessToken().orNull(nullProbability = 0.33), + authUids: Arb> = Arb.set(string(0..10, Codepoint.alphanumeric()), 0..10), +): Arb = Arb.bind(accessToken, authUids, ::GetAuthTokenResult) internal fun DataConnectArb.appCheckTokenResult( accessToken: Arb = accessToken() -): Arb = accessToken.map { GetAppCheckTokenResult(it) } +): Arb = accessToken.map(::GetAppCheckTokenResult) From 25677115d57e13896d46870d18e2d5431e07583a Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 17 Oct 2025 15:03:08 -0400 Subject: [PATCH 3/9] DataConnectAuthUnitTest.kt: Add tests for authUids population Adds tests to DataConnectAuthUnitTest to verify that `getToken()` correctly populates the `authUids` from the `user_id` and `sub` claims in the auth token. ### Changes * DataConnectAuthUnitTest.kt: * Add tests for `user_id` and `sub` claims * Overload `taskForToken` to accept claims --- .../core/DataConnectAuthUnitTest.kt | 77 ++++++++++++++++++- 1 file changed, 75 insertions(+), 2 deletions(-) diff --git a/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/core/DataConnectAuthUnitTest.kt b/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/core/DataConnectAuthUnitTest.kt index e3c9576f813..d2a029f390a 100644 --- a/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/core/DataConnectAuthUnitTest.kt +++ b/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/core/DataConnectAuthUnitTest.kt @@ -32,6 +32,7 @@ import com.google.firebase.dataconnect.testutil.UnavailableDeferred import com.google.firebase.dataconnect.testutil.newBackgroundScopeThatAdvancesLikeForeground import com.google.firebase.dataconnect.testutil.newMockLogger import com.google.firebase.dataconnect.testutil.property.arbitrary.dataConnect +import com.google.firebase.dataconnect.testutil.property.arbitrary.distinctPair import com.google.firebase.dataconnect.testutil.shouldContainWithNonAbuttingText import com.google.firebase.dataconnect.testutil.shouldContainWithNonAbuttingTextIgnoringCase import com.google.firebase.dataconnect.testutil.shouldHaveLoggedAtLeastOneMessageContaining @@ -46,15 +47,19 @@ import io.kotest.assertions.nondeterministic.eventually import io.kotest.assertions.nondeterministic.eventuallyConfig import io.kotest.assertions.throwables.shouldThrow import io.kotest.assertions.withClue +import io.kotest.matchers.collections.shouldBeEmpty import io.kotest.matchers.collections.shouldContain import io.kotest.matchers.collections.shouldContainExactly +import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder import io.kotest.matchers.nulls.shouldBeNull import io.kotest.matchers.nulls.shouldNotBeNull import io.kotest.matchers.shouldBe import io.kotest.matchers.types.shouldBeSameInstanceAs import io.kotest.property.Arb import io.kotest.property.RandomSource +import io.kotest.property.arbitrary.map import io.kotest.property.arbitrary.next +import io.kotest.property.arbs.products.brand import io.mockk.coEvery import io.mockk.confirmVerified import io.mockk.every @@ -311,6 +316,74 @@ class DataConnectAuthUnitTest { mockLogger.shouldNotHaveLoggedAnyMessagesContaining(accessToken) } + @Test + fun `getToken() should populate authUids from user_id claim`() = runTest { + val dataConnectAuth = newDataConnectAuth() + dataConnectAuth.initialize() + advanceUntilIdle() + val uid = Arb.brand().map { it.value }.next(rs) + coEvery { mockInternalAuthProvider.getAccessToken(any()) } returns + taskForToken(accessToken, mapOf("user_id" to uid)) + + val result = dataConnectAuth.getToken(requestId) + + result.shouldNotBeNull().authUids.shouldContainExactly(uid) + } + + @Test + fun `getToken() should populate authUids from sub claim`() = runTest { + val dataConnectAuth = newDataConnectAuth() + dataConnectAuth.initialize() + advanceUntilIdle() + val uid = Arb.brand().map { it.value }.next(rs) + coEvery { mockInternalAuthProvider.getAccessToken(any()) } returns + taskForToken(accessToken, mapOf("sub" to uid)) + + val result = dataConnectAuth.getToken(requestId) + + result.shouldNotBeNull().authUids.shouldContainExactly(uid) + } + + @Test + fun `getToken() should populate authUids from user_id and sub claims`() = runTest { + val dataConnectAuth = newDataConnectAuth() + dataConnectAuth.initialize() + advanceUntilIdle() + val (uid1, uid2) = Arb.brand().map { it.value }.distinctPair().next(rs) + coEvery { mockInternalAuthProvider.getAccessToken(any()) } returns + taskForToken(accessToken, mapOf("user_id" to uid1, "sub" to uid2)) + + val result = dataConnectAuth.getToken(requestId) + + result.shouldNotBeNull().authUids.shouldContainExactlyInAnyOrder(uid1, uid2) + } + + @Test + fun `getToken() should populate empty authUids if claims are missing`() = runTest { + val dataConnectAuth = newDataConnectAuth() + dataConnectAuth.initialize() + advanceUntilIdle() + coEvery { mockInternalAuthProvider.getAccessToken(any()) } returns + taskForToken(accessToken, emptyMap()) + + val result = dataConnectAuth.getToken(requestId) + + result.shouldNotBeNull().authUids.shouldBeEmpty() + } + + @Test + fun `getToken() should ignore non-string uid claims`() = runTest { + val dataConnectAuth = newDataConnectAuth() + dataConnectAuth.initialize() + advanceUntilIdle() + coEvery { mockInternalAuthProvider.getAccessToken(any()) } returns + taskForToken(accessToken, mapOf("user_id" to 123, "sub" to true)) + + val result = dataConnectAuth.getToken(requestId) + + result.shouldNotBeNull().authUids shouldBe emptySet() + } + @Test fun `getToken() should return re-throw the exception from the task returned from FirebaseAuth`() = runTest { @@ -613,7 +686,7 @@ class DataConnectAuthUnitTest { interval = 100.milliseconds } - fun taskForToken(token: String?): Task = - Tasks.forResult(mockk(relaxed = true) { every { getToken() } returns token }) + fun taskForToken(token: String?, claims: Map = emptyMap()): Task = + Tasks.forResult(GetTokenResult(token, claims)) } } From 5b851e793081c0a3e5cf5234b1c6757bd95da906 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 17 Oct 2025 15:07:36 -0400 Subject: [PATCH 4/9] CHANGELOG.md: add entry --- firebase-dataconnect/CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/firebase-dataconnect/CHANGELOG.md b/firebase-dataconnect/CHANGELOG.md index e6a6f7d636b..c4a40470658 100644 --- a/firebase-dataconnect/CHANGELOG.md +++ b/firebase-dataconnect/CHANGELOG.md @@ -1,7 +1,8 @@ # Unreleased - [changed] Internal refactor for managing Auth and App Check tokens - ([#7184](https://github.com/firebase/firebase-android-sdk/pull/7184)) + ([#7484](https://github.com/firebase/firebase-android-sdk/pull/7484), + [#7485](https://github.com/firebase/firebase-android-sdk/pull/7485)) # 17.1.0 From c2e6da13611d10ec7236ff873545af9235278d4e Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Wed, 22 Oct 2025 11:43:41 -0400 Subject: [PATCH 5/9] DataConnectAuth.kt: remove authUid property, since it's not needed --- .../dataconnect/core/DataConnectAppCheck.kt | 4 +-- .../dataconnect/core/DataConnectAuth.kt | 33 +++---------------- .../DataConnectCredentialsTokenManager.kt | 25 +++++--------- 3 files changed, 15 insertions(+), 47 deletions(-) diff --git a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectAppCheck.kt b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectAppCheck.kt index 694140e46ac..ce069f54a61 100644 --- a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectAppCheck.kt +++ b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectAppCheck.kt @@ -42,10 +42,10 @@ internal class DataConnectAppCheck( private val appCheckTokenListener = AppCheckTokenListenerImpl(logger) @DeferredApi - override fun registerProvider(provider: InteropAppCheckTokenProvider) = + override fun addTokenListener(provider: InteropAppCheckTokenProvider) = provider.addAppCheckTokenListener(appCheckTokenListener) - override fun unregisterProvider(provider: InteropAppCheckTokenProvider) = + override fun removeTokenListener(provider: InteropAppCheckTokenProvider) = provider.removeAppCheckTokenListener(appCheckTokenListener) override suspend fun getToken(provider: InteropAppCheckTokenProvider, forceRefresh: Boolean) = diff --git a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectAuth.kt b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectAuth.kt index fd3e65d1f61..8022ac95da8 100644 --- a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectAuth.kt +++ b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectAuth.kt @@ -23,8 +23,6 @@ import com.google.firebase.dataconnect.core.DataConnectAuth.GetAuthTokenResult import com.google.firebase.dataconnect.core.Globals.toScrubbedAccessToken import com.google.firebase.dataconnect.core.LoggerGlobals.debug import com.google.firebase.internal.InternalTokenResult -import java.util.concurrent.locks.ReentrantLock -import kotlin.concurrent.withLock import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.tasks.await @@ -41,37 +39,14 @@ internal class DataConnectAuth( blockingDispatcher = blockingDispatcher, logger = logger, ) { - - private data class ProviderIdTokenListenerPair( - val provider: InternalAuthProvider, - val idTokenListener: IdTokenListenerImpl, - ) - - private val providersLock = ReentrantLock() - private val providers = mutableListOf() - - /** - * The Firebase Auth UID of the current user, or `null` if Firebase Auth is not (yet) available or - * if there is no logged-in user. - */ - val authUid: String? - get() = providersLock.withLock { providers.lastOrNull()?.provider?.uid } + private val idTokenListener = IdTokenListenerImpl(logger) @DeferredApi - override fun registerProvider(provider: InternalAuthProvider) { - val idTokenListener = IdTokenListenerImpl(logger) + override fun addTokenListener(provider: InternalAuthProvider) = provider.addIdTokenListener(idTokenListener) - providersLock.withLock { providers.add(ProviderIdTokenListenerPair(provider, idTokenListener)) } - } - override fun unregisterProvider(provider: InternalAuthProvider) { - val idTokenListener = - providersLock.withLock { - val index = providers.indexOfLast { it.provider === provider } - if (index < 0) null else providers.removeAt(index).idTokenListener - } - idTokenListener?.let { provider.removeIdTokenListener(idTokenListener) } - } + override fun removeTokenListener(provider: InternalAuthProvider) = + provider.removeIdTokenListener(idTokenListener) override suspend fun getToken(provider: InternalAuthProvider, forceRefresh: Boolean) = provider.getAccessToken(forceRefresh).await().let { diff --git a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectCredentialsTokenManager.kt b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectCredentialsTokenManager.kt index ae36a9c375b..90330820bfc 100644 --- a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectCredentialsTokenManager.kt +++ b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectCredentialsTokenManager.kt @@ -123,25 +123,18 @@ internal sealed class DataConnectCredentialsTokenManager>(State.New) /** - * Registers the given provider [T] instance if, and when, it becomes available. + * Adds the token listener to the given provider. * - * The implementation may want, or need, to register a listener of some sort with the provider in - * order to be notified of provider state changes, such as a user logging in or logging out. - * - * @see unregisterProvider + * @see removeTokenListener */ - @DeferredApi protected abstract fun registerProvider(provider: T) + @DeferredApi protected abstract fun addTokenListener(provider: T) /** - * Registers the given provider [T] instance, which can occur if a new provider becomes available - * of if [close] is called. - * - * The implementation should undo any work done by [registerProvider], such as unregistering - * listeners. + * Removes the token listener from the given provider. * - * @see registerProvider + * @see addTokenListener */ - protected abstract fun unregisterProvider(provider: T) + protected abstract fun removeTokenListener(provider: T) /** * Starts an asynchronous task to get a new access token from the given provider, forcing a token @@ -199,7 +192,7 @@ internal sealed class DataConnectCredentialsTokenManager {} is State.Closed -> {} is State.StateWithProvider -> { - unregisterProvider(oldState.provider) + removeTokenListener(oldState.provider) } } } @@ -405,7 +398,7 @@ internal sealed class DataConnectCredentialsTokenManager @@ -429,7 +422,7 @@ internal sealed class DataConnectCredentialsTokenManager {} is State.Idle -> {} From eb214adb6295c89daa43b3f42f6425e3dd4ecc42 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Wed, 22 Oct 2025 11:50:59 -0400 Subject: [PATCH 6/9] DataConnectAuth.kt: just get the authUid from the sub claim, not the sub AND user_id claims --- .../dataconnect/core/DataConnectAuth.kt | 17 ++----- .../core/DataConnectAuthUnitTest.kt | 45 +++---------------- .../testutil/property/arbitrary/arbs.kt | 6 +-- 3 files changed, 13 insertions(+), 55 deletions(-) diff --git a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectAuth.kt b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectAuth.kt index 8022ac95da8..e984150b879 100644 --- a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectAuth.kt +++ b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectAuth.kt @@ -50,11 +50,10 @@ internal class DataConnectAuth( override suspend fun getToken(provider: InternalAuthProvider, forceRefresh: Boolean) = provider.getAccessToken(forceRefresh).await().let { - GetAuthTokenResult(it.token, it.getAuthUids()) + GetAuthTokenResult(it.token, it.getAuthUid()) } - data class GetAuthTokenResult(override val token: String?, val authUids: Set) : - GetTokenResult + data class GetAuthTokenResult(override val token: String?, val authUid: String?) : GetTokenResult private class IdTokenListenerImpl(private val logger: Logger) : IdTokenListener { override fun onIdTokenChanged(tokenResult: InternalTokenResult) { @@ -64,16 +63,6 @@ internal class DataConnectAuth( private companion object { - val authUidClaimNames = listOf("user_id", "sub") - - fun com.google.firebase.auth.GetTokenResult.getAuthUids(): Set = buildSet { - authUidClaimNames.forEach { claimName -> - claims[claimName]?.let { claimValue -> - if (claimValue is String) { - add(claimValue) - } - } - } - } + fun com.google.firebase.auth.GetTokenResult.getAuthUid(): String? = claims["sub"] as? String } } diff --git a/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/core/DataConnectAuthUnitTest.kt b/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/core/DataConnectAuthUnitTest.kt index d2a029f390a..af543389bf9 100644 --- a/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/core/DataConnectAuthUnitTest.kt +++ b/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/core/DataConnectAuthUnitTest.kt @@ -32,7 +32,6 @@ import com.google.firebase.dataconnect.testutil.UnavailableDeferred import com.google.firebase.dataconnect.testutil.newBackgroundScopeThatAdvancesLikeForeground import com.google.firebase.dataconnect.testutil.newMockLogger import com.google.firebase.dataconnect.testutil.property.arbitrary.dataConnect -import com.google.firebase.dataconnect.testutil.property.arbitrary.distinctPair import com.google.firebase.dataconnect.testutil.shouldContainWithNonAbuttingText import com.google.firebase.dataconnect.testutil.shouldContainWithNonAbuttingTextIgnoringCase import com.google.firebase.dataconnect.testutil.shouldHaveLoggedAtLeastOneMessageContaining @@ -47,10 +46,8 @@ import io.kotest.assertions.nondeterministic.eventually import io.kotest.assertions.nondeterministic.eventuallyConfig import io.kotest.assertions.throwables.shouldThrow import io.kotest.assertions.withClue -import io.kotest.matchers.collections.shouldBeEmpty import io.kotest.matchers.collections.shouldContain import io.kotest.matchers.collections.shouldContainExactly -import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder import io.kotest.matchers.nulls.shouldBeNull import io.kotest.matchers.nulls.shouldNotBeNull import io.kotest.matchers.shouldBe @@ -317,21 +314,7 @@ class DataConnectAuthUnitTest { } @Test - fun `getToken() should populate authUids from user_id claim`() = runTest { - val dataConnectAuth = newDataConnectAuth() - dataConnectAuth.initialize() - advanceUntilIdle() - val uid = Arb.brand().map { it.value }.next(rs) - coEvery { mockInternalAuthProvider.getAccessToken(any()) } returns - taskForToken(accessToken, mapOf("user_id" to uid)) - - val result = dataConnectAuth.getToken(requestId) - - result.shouldNotBeNull().authUids.shouldContainExactly(uid) - } - - @Test - fun `getToken() should populate authUids from sub claim`() = runTest { + fun `getToken() should populate authUid from sub claim`() = runTest { val dataConnectAuth = newDataConnectAuth() dataConnectAuth.initialize() advanceUntilIdle() @@ -341,25 +324,11 @@ class DataConnectAuthUnitTest { val result = dataConnectAuth.getToken(requestId) - result.shouldNotBeNull().authUids.shouldContainExactly(uid) - } - - @Test - fun `getToken() should populate authUids from user_id and sub claims`() = runTest { - val dataConnectAuth = newDataConnectAuth() - dataConnectAuth.initialize() - advanceUntilIdle() - val (uid1, uid2) = Arb.brand().map { it.value }.distinctPair().next(rs) - coEvery { mockInternalAuthProvider.getAccessToken(any()) } returns - taskForToken(accessToken, mapOf("user_id" to uid1, "sub" to uid2)) - - val result = dataConnectAuth.getToken(requestId) - - result.shouldNotBeNull().authUids.shouldContainExactlyInAnyOrder(uid1, uid2) + result.shouldNotBeNull().authUid shouldBe uid } @Test - fun `getToken() should populate empty authUids if claims are missing`() = runTest { + fun `getToken() should populate null authUid if sub claim is missing`() = runTest { val dataConnectAuth = newDataConnectAuth() dataConnectAuth.initialize() advanceUntilIdle() @@ -368,20 +337,20 @@ class DataConnectAuthUnitTest { val result = dataConnectAuth.getToken(requestId) - result.shouldNotBeNull().authUids.shouldBeEmpty() + result.shouldNotBeNull().authUid.shouldBeNull() } @Test - fun `getToken() should ignore non-string uid claims`() = runTest { + fun `getToken() should populate null authUid if sub claim is not a String`() = runTest { val dataConnectAuth = newDataConnectAuth() dataConnectAuth.initialize() advanceUntilIdle() coEvery { mockInternalAuthProvider.getAccessToken(any()) } returns - taskForToken(accessToken, mapOf("user_id" to 123, "sub" to true)) + taskForToken(accessToken, mapOf("sub" to 42)) val result = dataConnectAuth.getToken(requestId) - result.shouldNotBeNull().authUids shouldBe emptySet() + result.shouldNotBeNull().authUid.shouldBeNull() } @Test diff --git a/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/testutil/property/arbitrary/arbs.kt b/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/testutil/property/arbitrary/arbs.kt index 91ecd70f013..49f52994e5e 100644 --- a/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/testutil/property/arbitrary/arbs.kt +++ b/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/testutil/property/arbitrary/arbs.kt @@ -51,7 +51,6 @@ import io.kotest.property.arbitrary.int import io.kotest.property.arbitrary.list import io.kotest.property.arbitrary.map import io.kotest.property.arbitrary.orNull -import io.kotest.property.arbitrary.set import io.kotest.property.arbitrary.string import io.mockk.coEvery import io.mockk.mockk @@ -335,8 +334,9 @@ internal inline fun DataConnectArb.operationRefConstru internal fun DataConnectArb.authTokenResult( accessToken: Arb = accessToken().orNull(nullProbability = 0.33), - authUids: Arb> = Arb.set(string(0..10, Codepoint.alphanumeric()), 0..10), -): Arb = Arb.bind(accessToken, authUids, ::GetAuthTokenResult) + authUid: Arb = + Arb.string(0..10, Codepoint.alphanumeric()).orNull(nullProbability = 0.33), +): Arb = Arb.bind(accessToken, authUid, ::GetAuthTokenResult) internal fun DataConnectArb.appCheckTokenResult( accessToken: Arb = accessToken() From 386cfaaba19220ed2cd0526b54ff1da0ab6ffc8c Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Wed, 22 Oct 2025 12:20:06 -0400 Subject: [PATCH 7/9] DataConnectGrpcMetadataUnitTest.kt: fix for non-null access token result with null token. --- .../core/DataConnectGrpcMetadataUnitTest.kt | 69 ++++++++++++------- .../testutil/property/arbitrary/arbs.kt | 2 +- 2 files changed, 47 insertions(+), 24 deletions(-) diff --git a/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/core/DataConnectGrpcMetadataUnitTest.kt b/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/core/DataConnectGrpcMetadataUnitTest.kt index 772caf9af07..a7da2670489 100644 --- a/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/core/DataConnectGrpcMetadataUnitTest.kt +++ b/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/core/DataConnectGrpcMetadataUnitTest.kt @@ -19,6 +19,8 @@ import android.os.Build import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.firebase.dataconnect.BuildConfig import com.google.firebase.dataconnect.FirebaseDataConnect.CallerSdkType +import com.google.firebase.dataconnect.core.DataConnectAppCheck.GetAppCheckTokenResult +import com.google.firebase.dataconnect.core.DataConnectAuth.GetAuthTokenResult import com.google.firebase.dataconnect.testutil.FirebaseAppUnitTestingRule import com.google.firebase.dataconnect.testutil.property.arbitrary.appCheckTokenResult import com.google.firebase.dataconnect.testutil.property.arbitrary.authTokenResult @@ -31,9 +33,12 @@ import io.kotest.matchers.collections.shouldNotContain import io.kotest.matchers.shouldBe import io.kotest.matchers.types.shouldBeSameInstanceAs import io.kotest.property.Arb +import io.kotest.property.Exhaustive import io.kotest.property.arbitrary.constant import io.kotest.property.arbitrary.enum import io.kotest.property.arbitrary.next +import io.kotest.property.checkAll +import io.kotest.property.exhaustive.of import io.mockk.coEvery import io.mockk.mockk import kotlinx.coroutines.test.runTest @@ -160,24 +165,33 @@ class DataConnectGrpcMetadataUnitTest { @Test fun `should omit x-firebase-auth-token when the auth token is null`() = runTest { - val dataConnectAuth: DataConnectAuth = mockk() - coEvery { dataConnectAuth.getToken(any()) } returns null - val dataConnectGrpcMetadata = - Arb.dataConnect - .dataConnectGrpcMetadata(dataConnectAuth = Arb.constant(dataConnectAuth)) - .next() - val requestId = Arb.dataConnect.requestId().next() - val callerSdkType = Arb.enum().next() + val getAuthTokenResults: Exhaustive = + Exhaustive.of( + null, + Arb.dataConnect.authTokenResult(accessToken = Arb.constant(null)).next(), + ) - val metadata = dataConnectGrpcMetadata.get(requestId, callerSdkType) + checkAll(getAuthTokenResults) { getAuthTokenResult -> + val dataConnectAuth: DataConnectAuth = mockk() + coEvery { dataConnectAuth.getToken(any()) } returns getAuthTokenResult + val dataConnectGrpcMetadata = + Arb.dataConnect + .dataConnectGrpcMetadata(dataConnectAuth = Arb.constant(dataConnectAuth)) + .next() + val requestId = Arb.dataConnect.requestId().next() + val callerSdkType = Arb.enum().next() - metadata.asClue { it.keys() shouldNotContain "x-firebase-auth-token" } + val metadata = dataConnectGrpcMetadata.get(requestId, callerSdkType) + + metadata.asClue { it.keys() shouldNotContain "x-firebase-auth-token" } + } } @Test fun `should include x-firebase-auth-token when the auth token is not null`() = runTest { val dataConnectAuth: DataConnectAuth = mockk() - val authTokenResult = Arb.dataConnect.authTokenResult().next() + val authTokenResult = + Arb.dataConnect.authTokenResult(accessToken = Arb.dataConnect.accessToken()).next() coEvery { dataConnectAuth.getToken(any()) } returns authTokenResult val dataConnectGrpcMetadata = Arb.dataConnect @@ -197,24 +211,33 @@ class DataConnectGrpcMetadataUnitTest { @Test fun `should omit x-firebase-appcheck when the AppCheck token is null`() = runTest { - val dataConnectAppCheck: DataConnectAppCheck = mockk { - coEvery { getToken(any()) } returns null - } - val dataConnectGrpcMetadata = - Arb.dataConnect - .dataConnectGrpcMetadata(dataConnectAppCheck = Arb.constant(dataConnectAppCheck)) - .next() - val requestId = Arb.dataConnect.requestId().next() - val callerSdkType = Arb.enum().next() + val getAppCheckTokenResults: Exhaustive = + Exhaustive.of( + null, + Arb.dataConnect.appCheckTokenResult(accessToken = Arb.constant(null)).next(), + ) - val metadata = dataConnectGrpcMetadata.get(requestId, callerSdkType) + checkAll(getAppCheckTokenResults) { getAppCheckTokenResult -> + val dataConnectAppCheck: DataConnectAppCheck = mockk { + coEvery { getToken(any()) } returns getAppCheckTokenResult + } + val dataConnectGrpcMetadata = + Arb.dataConnect + .dataConnectGrpcMetadata(dataConnectAppCheck = Arb.constant(dataConnectAppCheck)) + .next() + val requestId = Arb.dataConnect.requestId().next() + val callerSdkType = Arb.enum().next() - metadata.asClue { it.keys() shouldNotContain "x-firebase-appcheck" } + val metadata = dataConnectGrpcMetadata.get(requestId, callerSdkType) + + metadata.asClue { it.keys() shouldNotContain "x-firebase-appcheck" } + } } @Test fun `should include x-firebase-appcheck when the AppCheck token is not null`() = runTest { - val appCheckTokenResult = Arb.dataConnect.appCheckTokenResult().next() + val appCheckTokenResult = + Arb.dataConnect.appCheckTokenResult(accessToken = Arb.dataConnect.accessToken()).next() val dataConnectAppCheck: DataConnectAppCheck = mockk { coEvery { getToken(any()) } returns appCheckTokenResult } diff --git a/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/testutil/property/arbitrary/arbs.kt b/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/testutil/property/arbitrary/arbs.kt index 49f52994e5e..04c5bd409d6 100644 --- a/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/testutil/property/arbitrary/arbs.kt +++ b/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/testutil/property/arbitrary/arbs.kt @@ -339,5 +339,5 @@ internal fun DataConnectArb.authTokenResult( ): Arb = Arb.bind(accessToken, authUid, ::GetAuthTokenResult) internal fun DataConnectArb.appCheckTokenResult( - accessToken: Arb = accessToken() + accessToken: Arb = accessToken().orNull(nullProbability = 0.33), ): Arb = accessToken.map(::GetAppCheckTokenResult) From c6e1a05a6ad9426edff9fea6864dc3aa64196afd Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Wed, 22 Oct 2025 13:03:30 -0400 Subject: [PATCH 8/9] DataConnectGrpcMetadataUnitTest.kt: fix tests for null access token. The tests for the `x-firebase-auth-token` and `x-firebase-appcheck` headers properly tested the case where getToken() would return null; however, they did not test for the case when getToken() returned non-null but the `token` property was null. This commit adds that necessary unit test coverage. --- .../core/DataConnectGrpcMetadataUnitTest.kt | 69 ++++++++++++------- .../testutil/property/arbitrary/arbs.kt | 4 +- 2 files changed, 48 insertions(+), 25 deletions(-) diff --git a/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/core/DataConnectGrpcMetadataUnitTest.kt b/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/core/DataConnectGrpcMetadataUnitTest.kt index 772caf9af07..a7da2670489 100644 --- a/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/core/DataConnectGrpcMetadataUnitTest.kt +++ b/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/core/DataConnectGrpcMetadataUnitTest.kt @@ -19,6 +19,8 @@ import android.os.Build import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.firebase.dataconnect.BuildConfig import com.google.firebase.dataconnect.FirebaseDataConnect.CallerSdkType +import com.google.firebase.dataconnect.core.DataConnectAppCheck.GetAppCheckTokenResult +import com.google.firebase.dataconnect.core.DataConnectAuth.GetAuthTokenResult import com.google.firebase.dataconnect.testutil.FirebaseAppUnitTestingRule import com.google.firebase.dataconnect.testutil.property.arbitrary.appCheckTokenResult import com.google.firebase.dataconnect.testutil.property.arbitrary.authTokenResult @@ -31,9 +33,12 @@ import io.kotest.matchers.collections.shouldNotContain import io.kotest.matchers.shouldBe import io.kotest.matchers.types.shouldBeSameInstanceAs import io.kotest.property.Arb +import io.kotest.property.Exhaustive import io.kotest.property.arbitrary.constant import io.kotest.property.arbitrary.enum import io.kotest.property.arbitrary.next +import io.kotest.property.checkAll +import io.kotest.property.exhaustive.of import io.mockk.coEvery import io.mockk.mockk import kotlinx.coroutines.test.runTest @@ -160,24 +165,33 @@ class DataConnectGrpcMetadataUnitTest { @Test fun `should omit x-firebase-auth-token when the auth token is null`() = runTest { - val dataConnectAuth: DataConnectAuth = mockk() - coEvery { dataConnectAuth.getToken(any()) } returns null - val dataConnectGrpcMetadata = - Arb.dataConnect - .dataConnectGrpcMetadata(dataConnectAuth = Arb.constant(dataConnectAuth)) - .next() - val requestId = Arb.dataConnect.requestId().next() - val callerSdkType = Arb.enum().next() + val getAuthTokenResults: Exhaustive = + Exhaustive.of( + null, + Arb.dataConnect.authTokenResult(accessToken = Arb.constant(null)).next(), + ) - val metadata = dataConnectGrpcMetadata.get(requestId, callerSdkType) + checkAll(getAuthTokenResults) { getAuthTokenResult -> + val dataConnectAuth: DataConnectAuth = mockk() + coEvery { dataConnectAuth.getToken(any()) } returns getAuthTokenResult + val dataConnectGrpcMetadata = + Arb.dataConnect + .dataConnectGrpcMetadata(dataConnectAuth = Arb.constant(dataConnectAuth)) + .next() + val requestId = Arb.dataConnect.requestId().next() + val callerSdkType = Arb.enum().next() - metadata.asClue { it.keys() shouldNotContain "x-firebase-auth-token" } + val metadata = dataConnectGrpcMetadata.get(requestId, callerSdkType) + + metadata.asClue { it.keys() shouldNotContain "x-firebase-auth-token" } + } } @Test fun `should include x-firebase-auth-token when the auth token is not null`() = runTest { val dataConnectAuth: DataConnectAuth = mockk() - val authTokenResult = Arb.dataConnect.authTokenResult().next() + val authTokenResult = + Arb.dataConnect.authTokenResult(accessToken = Arb.dataConnect.accessToken()).next() coEvery { dataConnectAuth.getToken(any()) } returns authTokenResult val dataConnectGrpcMetadata = Arb.dataConnect @@ -197,24 +211,33 @@ class DataConnectGrpcMetadataUnitTest { @Test fun `should omit x-firebase-appcheck when the AppCheck token is null`() = runTest { - val dataConnectAppCheck: DataConnectAppCheck = mockk { - coEvery { getToken(any()) } returns null - } - val dataConnectGrpcMetadata = - Arb.dataConnect - .dataConnectGrpcMetadata(dataConnectAppCheck = Arb.constant(dataConnectAppCheck)) - .next() - val requestId = Arb.dataConnect.requestId().next() - val callerSdkType = Arb.enum().next() + val getAppCheckTokenResults: Exhaustive = + Exhaustive.of( + null, + Arb.dataConnect.appCheckTokenResult(accessToken = Arb.constant(null)).next(), + ) - val metadata = dataConnectGrpcMetadata.get(requestId, callerSdkType) + checkAll(getAppCheckTokenResults) { getAppCheckTokenResult -> + val dataConnectAppCheck: DataConnectAppCheck = mockk { + coEvery { getToken(any()) } returns getAppCheckTokenResult + } + val dataConnectGrpcMetadata = + Arb.dataConnect + .dataConnectGrpcMetadata(dataConnectAppCheck = Arb.constant(dataConnectAppCheck)) + .next() + val requestId = Arb.dataConnect.requestId().next() + val callerSdkType = Arb.enum().next() - metadata.asClue { it.keys() shouldNotContain "x-firebase-appcheck" } + val metadata = dataConnectGrpcMetadata.get(requestId, callerSdkType) + + metadata.asClue { it.keys() shouldNotContain "x-firebase-appcheck" } + } } @Test fun `should include x-firebase-appcheck when the AppCheck token is not null`() = runTest { - val appCheckTokenResult = Arb.dataConnect.appCheckTokenResult().next() + val appCheckTokenResult = + Arb.dataConnect.appCheckTokenResult(accessToken = Arb.dataConnect.accessToken()).next() val dataConnectAppCheck: DataConnectAppCheck = mockk { coEvery { getToken(any()) } returns appCheckTokenResult } diff --git a/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/testutil/property/arbitrary/arbs.kt b/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/testutil/property/arbitrary/arbs.kt index 6d66f01fa88..dafcf7e7e21 100644 --- a/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/testutil/property/arbitrary/arbs.kt +++ b/firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/testutil/property/arbitrary/arbs.kt @@ -333,9 +333,9 @@ internal inline fun DataConnectArb.operationRefConstru } internal fun DataConnectArb.authTokenResult( - accessToken: Arb = accessToken() + accessToken: Arb = accessToken().orNull(nullProbability = 0.33), ): Arb = accessToken.map { GetAuthTokenResult(it) } internal fun DataConnectArb.appCheckTokenResult( - accessToken: Arb = accessToken() + accessToken: Arb = accessToken().orNull(nullProbability = 0.33), ): Arb = accessToken.map { GetAppCheckTokenResult(it) } From afb2b76f530e23edd2fa38cc3ad7fd724c684d5f Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 24 Oct 2025 15:04:28 -0400 Subject: [PATCH 9/9] DataConnectAuth.kt: add a link to the docs to justify using the "sub" claim. The unshortened URL is: https://firebase.google.com/docs/auth/admin/verify-id-tokens#verify_id_tokens_using_a_third-party_jwt_library Here is what the page looked like when viewed on Oct 24, 2025: https://github.com/user-attachments/assets/65f9d0ec-9067-40a7-a2d6-ce91e69d1dc4 diff --git a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectAuth.kt b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectAuth.kt index e984150b8..55bb959f1 100644 --- a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectAuth.kt +++ b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectAuth.kt @@ -63,6 +63,8 @@ internal class DataConnectAuth( private companion object { + // The "sub" claim is documented to be "a non-empty string and must be the uid of the user or + // device". See http://goo.gle/4oGjEQt for the relevant Firebase documentation. fun com.google.firebase.auth.GetTokenResult.getAuthUid(): String? = claims["sub"] as? String } } --- .../com/google/firebase/dataconnect/core/DataConnectAuth.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectAuth.kt b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectAuth.kt index e984150b879..55bb959f15e 100644 --- a/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectAuth.kt +++ b/firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectAuth.kt @@ -63,6 +63,8 @@ internal class DataConnectAuth( private companion object { + // The "sub" claim is documented to be "a non-empty string and must be the uid of the user or + // device". See http://goo.gle/4oGjEQt for the relevant Firebase documentation. fun com.google.firebase.auth.GetTokenResult.getAuthUid(): String? = claims["sub"] as? String } }