Skip to content

Commit dbf5d01

Browse files
authored
dataconnect: auth code internal cleanup (#6836)
1 parent f826b40 commit dbf5d01

File tree

6 files changed

+109
-99
lines changed

6 files changed

+109
-99
lines changed

firebase-dataconnect/src/androidTest/kotlin/com/google/firebase/dataconnect/AuthIntegrationTest.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@
1717
package com.google.firebase.dataconnect
1818

1919
import com.google.firebase.auth.FirebaseAuth
20-
import com.google.firebase.dataconnect.core.FirebaseDataConnectInternal
2120
import com.google.firebase.dataconnect.testutil.DataConnectBackend
2221
import com.google.firebase.dataconnect.testutil.DataConnectIntegrationTestBase
2322
import com.google.firebase.dataconnect.testutil.InProcessDataConnectGrpcServer
23+
import com.google.firebase.dataconnect.testutil.awaitAuthReady
2424
import com.google.firebase.dataconnect.testutil.newInstance
2525
import com.google.firebase.dataconnect.testutil.property.arbitrary.dataConnect
2626
import com.google.firebase.dataconnect.testutil.schemas.PersonSchema
@@ -204,7 +204,7 @@ class AuthIntegrationTest : DataConnectIntegrationTestBase() {
204204
}
205205

206206
private suspend fun signIn() {
207-
(personSchema.dataConnect as FirebaseDataConnectInternal).awaitAuthReady()
207+
personSchema.dataConnect.awaitAuthReady()
208208
val authResult = auth.run { signInAnonymously().await() }
209209
withClue("authResult.user returned from signInAnonymously()") {
210210
authResult.user.shouldNotBeNull()

firebase-dataconnect/src/androidTest/kotlin/com/google/firebase/dataconnect/GrpcMetadataIntegrationTest.kt

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import com.google.android.gms.tasks.Tasks
2323
import com.google.firebase.appcheck.AppCheckProvider
2424
import com.google.firebase.appcheck.AppCheckProviderFactory
2525
import com.google.firebase.appcheck.FirebaseAppCheck
26-
import com.google.firebase.dataconnect.core.FirebaseDataConnectInternal
2726
import com.google.firebase.dataconnect.generated.GeneratedConnector
2827
import com.google.firebase.dataconnect.generated.GeneratedMutation
2928
import com.google.firebase.dataconnect.generated.GeneratedQuery
@@ -32,6 +31,8 @@ import com.google.firebase.dataconnect.testutil.DataConnectIntegrationTestBase
3231
import com.google.firebase.dataconnect.testutil.DataConnectTestAppCheckToken
3332
import com.google.firebase.dataconnect.testutil.FirebaseAuthBackend
3433
import com.google.firebase.dataconnect.testutil.InProcessDataConnectGrpcServer
34+
import com.google.firebase.dataconnect.testutil.awaitAppCheckReady
35+
import com.google.firebase.dataconnect.testutil.awaitAuthReady
3536
import com.google.firebase.dataconnect.testutil.getFirebaseAppIdFromStrings
3637
import com.google.firebase.dataconnect.testutil.newInstance
3738
import com.google.firebase.dataconnect.util.SuspendingLazy
@@ -138,7 +139,7 @@ class GrpcMetadataIntegrationTest : DataConnectIntegrationTestBase() {
138139
fun executeQueryShouldNotSendAuthMetadataWhenNotLoggedIn() = runTest {
139140
val grpcServer = inProcessDataConnectGrpcServer.newInstance()
140141
val dataConnect = dataConnectFactory.newInstance(grpcServer)
141-
(dataConnect as FirebaseDataConnectInternal).awaitAuthReady()
142+
dataConnect.awaitAuthReady()
142143
val queryRef = dataConnect.query("qryfyk7yfppfe", Unit, serializer<Unit>(), serializer<Unit>())
143144
val metadatasJob = async { grpcServer.metadatas.first() }
144145

@@ -151,7 +152,7 @@ class GrpcMetadataIntegrationTest : DataConnectIntegrationTestBase() {
151152
fun executeMutationShouldNotSendAuthMetadataWhenNotLoggedIn() = runTest {
152153
val grpcServer = inProcessDataConnectGrpcServer.newInstance()
153154
val dataConnect = dataConnectFactory.newInstance(grpcServer)
154-
(dataConnect as FirebaseDataConnectInternal).awaitAuthReady()
155+
dataConnect.awaitAuthReady()
155156
val mutationRef =
156157
dataConnect.mutation("mutckjpte9v9j", Unit, serializer<Unit>(), serializer<Unit>())
157158
val metadatasJob = async { grpcServer.metadatas.first() }
@@ -165,7 +166,7 @@ class GrpcMetadataIntegrationTest : DataConnectIntegrationTestBase() {
165166
fun executeQueryShouldSendAuthMetadataWhenLoggedIn() = runTest {
166167
val grpcServer = inProcessDataConnectGrpcServer.newInstance()
167168
val dataConnect = dataConnectFactory.newInstance(grpcServer)
168-
(dataConnect as FirebaseDataConnectInternal).awaitAuthReady()
169+
dataConnect.awaitAuthReady()
169170
val queryRef = dataConnect.query("qryyarwrxe2fv", Unit, serializer<Unit>(), serializer<Unit>())
170171
val metadatasJob = async { grpcServer.metadatas.first() }
171172
firebaseAuthSignIn(dataConnect)
@@ -179,7 +180,7 @@ class GrpcMetadataIntegrationTest : DataConnectIntegrationTestBase() {
179180
fun executeMutationShouldSendAuthMetadataWhenLoggedIn() = runTest {
180181
val grpcServer = inProcessDataConnectGrpcServer.newInstance()
181182
val dataConnect = dataConnectFactory.newInstance(grpcServer)
182-
(dataConnect as FirebaseDataConnectInternal).awaitAuthReady()
183+
dataConnect.awaitAuthReady()
183184
val mutationRef =
184185
dataConnect.mutation("mutayn7as5k7d", Unit, serializer<Unit>(), serializer<Unit>())
185186
val metadatasJob = async { grpcServer.metadatas.first() }
@@ -194,7 +195,7 @@ class GrpcMetadataIntegrationTest : DataConnectIntegrationTestBase() {
194195
fun executeQueryShouldNotSendAuthMetadataAfterLogout() = runTest {
195196
val grpcServer = inProcessDataConnectGrpcServer.newInstance()
196197
val dataConnect = dataConnectFactory.newInstance(grpcServer)
197-
(dataConnect as FirebaseDataConnectInternal).awaitAuthReady()
198+
dataConnect.awaitAuthReady()
198199
val queryRef = dataConnect.query("qryyarwrxe2fv", Unit, serializer<Unit>(), serializer<Unit>())
199200
val metadatasJob1 = async { grpcServer.metadatas.first() }
200201
val metadatasJob2 = async { grpcServer.metadatas.take(2).last() }
@@ -212,7 +213,7 @@ class GrpcMetadataIntegrationTest : DataConnectIntegrationTestBase() {
212213
fun executeMutationShouldNotSendAuthMetadataAfterLogout() = runTest {
213214
val grpcServer = inProcessDataConnectGrpcServer.newInstance()
214215
val dataConnect = dataConnectFactory.newInstance(grpcServer)
215-
(dataConnect as FirebaseDataConnectInternal).awaitAuthReady()
216+
dataConnect.awaitAuthReady()
216217
val mutationRef =
217218
dataConnect.mutation("mutvw945ag3vv", Unit, serializer<Unit>(), serializer<Unit>())
218219
val metadatasJob1 = async { grpcServer.metadatas.first() }
@@ -233,7 +234,7 @@ class GrpcMetadataIntegrationTest : DataConnectIntegrationTestBase() {
233234
// appcheck token is sent at all.
234235
val grpcServer = inProcessDataConnectGrpcServer.newInstance()
235236
val dataConnect = dataConnectFactory.newInstance(grpcServer)
236-
(dataConnect as FirebaseDataConnectInternal).awaitAppCheckReady()
237+
dataConnect.awaitAppCheckReady()
237238
val queryRef = dataConnect.query("qrybbeekpkkck", Unit, serializer<Unit>(), serializer<Unit>())
238239
val metadatasJob = async { grpcServer.metadatas.first() }
239240

@@ -248,7 +249,7 @@ class GrpcMetadataIntegrationTest : DataConnectIntegrationTestBase() {
248249
// appcheck token is sent at all.
249250
val grpcServer = inProcessDataConnectGrpcServer.newInstance()
250251
val dataConnect = dataConnectFactory.newInstance(grpcServer)
251-
(dataConnect as FirebaseDataConnectInternal).awaitAppCheckReady()
252+
dataConnect.awaitAppCheckReady()
252253
val mutationRef =
253254
dataConnect.mutation("mutbs7hhxk39c", Unit, serializer<Unit>(), serializer<Unit>())
254255
val metadatasJob = async { grpcServer.metadatas.first() }
@@ -262,7 +263,7 @@ class GrpcMetadataIntegrationTest : DataConnectIntegrationTestBase() {
262263
fun executeQueryShouldSendAppCheckMetadataWhenAppCheckIsEnabled() = runTest {
263264
val grpcServer = inProcessDataConnectGrpcServer.newInstance()
264265
val dataConnect = dataConnectFactory.newInstance(grpcServer)
265-
(dataConnect as FirebaseDataConnectInternal).awaitAppCheckReady()
266+
dataConnect.awaitAppCheckReady()
266267
val queryRef = dataConnect.query("qryyarwrxe2fv", Unit, serializer<Unit>(), serializer<Unit>())
267268
val metadatasJob = async { grpcServer.metadatas.first() }
268269
val appCheck = FirebaseAppCheck.getInstance(dataConnect.app)
@@ -277,7 +278,7 @@ class GrpcMetadataIntegrationTest : DataConnectIntegrationTestBase() {
277278
fun executeMutationShouldSendAppCheckMetadataWhenAppCheckIsEnabled() = runTest {
278279
val grpcServer = inProcessDataConnectGrpcServer.newInstance()
279280
val dataConnect = dataConnectFactory.newInstance(grpcServer)
280-
(dataConnect as FirebaseDataConnectInternal).awaitAppCheckReady()
281+
dataConnect.awaitAppCheckReady()
281282
val mutationRef =
282283
dataConnect.mutation("mutz4hzqzpgb4", Unit, serializer<Unit>(), serializer<Unit>())
283284
val metadatasJob = async { grpcServer.metadatas.first() }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright 2025 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.firebase.dataconnect.testutil
18+
19+
import com.google.firebase.dataconnect.FirebaseDataConnect
20+
import com.google.firebase.dataconnect.core.FirebaseDataConnectInternal
21+
22+
suspend fun FirebaseDataConnect.awaitAuthReady() =
23+
(this as FirebaseDataConnectInternal).awaitAuthReady()
24+
25+
suspend fun FirebaseDataConnect.awaitAppCheckReady() =
26+
(this as FirebaseDataConnectInternal).awaitAppCheckReady()

firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/core/DataConnectCredentialsTokenManager.kt

Lines changed: 66 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ import com.google.firebase.inject.Provider
3030
import com.google.firebase.internal.api.FirebaseNoSignedInUserException
3131
import com.google.firebase.util.nextAlphanumericString
3232
import java.lang.ref.WeakReference
33-
import java.util.concurrent.atomic.AtomicReference
3433
import kotlin.coroutines.coroutineContext
3534
import kotlin.random.Random
3635
import kotlinx.coroutines.CancellationException
@@ -46,10 +45,9 @@ import kotlinx.coroutines.async
4645
import kotlinx.coroutines.cancel
4746
import kotlinx.coroutines.ensureActive
4847
import kotlinx.coroutines.flow.MutableStateFlow
49-
import kotlinx.coroutines.flow.StateFlow
50-
import kotlinx.coroutines.flow.asStateFlow
48+
import kotlinx.coroutines.flow.filter
49+
import kotlinx.coroutines.flow.first
5150
import kotlinx.coroutines.launch
52-
import kotlinx.coroutines.yield
5351

5452
/** Base class that shares logic for managing the Auth token and AppCheck token. */
5553
internal sealed class DataConnectCredentialsTokenManager<T : Any>(
@@ -61,9 +59,6 @@ internal sealed class DataConnectCredentialsTokenManager<T : Any>(
6159
val instanceId: String
6260
get() = logger.nameWithId
6361

64-
private val _providerAvailable = MutableStateFlow(false)
65-
val providerAvailable: StateFlow<Boolean> = _providerAvailable.asStateFlow()
66-
6762
@Suppress("LeakingThis") private val weakThis = WeakReference(this)
6863

6964
private val coroutineScope =
@@ -87,49 +82,39 @@ internal sealed class DataConnectCredentialsTokenManager<T : Any>(
8782
}
8883
}
8984

90-
private interface ProviderProvider<T> {
91-
val provider: T?
92-
}
93-
9485
private sealed interface State<out T> {
9586

9687
/** State indicating that [close] has been invoked. */
9788
object Closed : State<Nothing>
9889

99-
/** State indicating that there is no outstanding "get token" request. */
100-
class Idle<T>(
101-
102-
/**
103-
* The [InternalAuthProvider] or [InteropAppCheckTokenProvider]; may be null if the deferred
104-
* has not yet given us a provider.
105-
*/
106-
override val provider: T?,
107-
90+
sealed interface StateWithForceTokenRefresh<out T> : State<T> {
10891
/** The value to specify for `forceRefresh` on the next invocation of [getToken]. */
10992
val forceTokenRefresh: Boolean
110-
) : State<T>, ProviderProvider<T>
93+
}
11194

112-
/** State indicating that there _is_ an outstanding "get token" request. */
113-
class Active<T>(
95+
/** State indicating that the token provider is not (yet?) available. */
96+
data class New(override val forceTokenRefresh: Boolean) : StateWithForceTokenRefresh<Nothing>
11497

115-
/**
116-
* The [InternalAuthProvider] or [InteropAppCheckTokenProvider] that is performing the "get
117-
* token" request.
118-
*/
98+
sealed interface StateWithProvider<out T> : State<T> {
99+
/** The token provider, [InternalAuthProvider] or [InteropAppCheckTokenProvider] */
100+
val provider: T
101+
}
102+
103+
/** State indicating that there is no outstanding "get token" request. */
104+
data class Idle<T>(override val provider: T, override val forceTokenRefresh: Boolean) :
105+
StateWithProvider<T>, StateWithForceTokenRefresh<T>
106+
107+
/** State indicating that there _is_ an outstanding "get token" request. */
108+
data class Active<out T>(
119109
override val provider: T,
120110

121111
/** The job that is performing the "get token" request. */
122112
val job: Deferred<SequencedReference<Result<GetTokenResult>>>
123-
) : State<T>, ProviderProvider<T>
113+
) : StateWithProvider<T>
124114
}
125115

126-
/**
127-
* The current state of this object. The value should only be changed in a compare-and-swap loop
128-
* in order to be thread-safe. Such a loop should call `yield()` on each iteration to allow other
129-
* coroutines to run on the thread.
130-
*/
131-
private val state =
132-
AtomicReference<State<T>>(State.Idle(provider = null, forceTokenRefresh = false))
116+
/** The current state of this object. */
117+
private val state = MutableStateFlow<State<T>>(State.New(forceTokenRefresh = false))
133118

134119
/**
135120
* Adds the token listener to the given provider.
@@ -168,19 +153,42 @@ internal sealed class DataConnectCredentialsTokenManager<T : Any>(
168153
setClosedState()
169154
}
170155

156+
/**
157+
* Suspends until the token provider becomes available to this object.
158+
*
159+
* If [close] has been invoked, or is invoked _before_ a token provider becomes available, then
160+
* this method returns normally, as if a token provider _had_ become available.
161+
*/
162+
suspend fun awaitTokenProvider() {
163+
logger.debug { "awaitTokenProvider() start" }
164+
val currentState =
165+
state
166+
.filter {
167+
when (it) {
168+
State.Closed -> true
169+
is State.New -> false
170+
is State.Idle -> true
171+
is State.Active -> true
172+
}
173+
}
174+
.first()
175+
logger.debug { "awaitTokenProvider() done: currentState=$currentState" }
176+
}
177+
171178
// This function must ONLY be called from close().
172179
private fun setClosedState() {
173180
while (true) {
174-
val oldState = state.get()
175-
val providerProvider: ProviderProvider<T> =
181+
val oldState = state.value
182+
val provider: T? =
176183
when (oldState) {
177184
is State.Closed -> return
178-
is State.Idle -> oldState
179-
is State.Active -> oldState
185+
is State.New -> null
186+
is State.Idle -> oldState.provider
187+
is State.Active -> oldState.provider
180188
}
181189

182190
if (state.compareAndSet(oldState, State.Closed)) {
183-
providerProvider.provider?.let { removeTokenListener(it) }
191+
provider?.let { removeTokenListener(it) }
184192
break
185193
}
186194
}
@@ -191,27 +199,28 @@ internal sealed class DataConnectCredentialsTokenManager<T : Any>(
191199
*
192200
* If [close] has been called, this method does nothing.
193201
*/
194-
suspend fun forceRefresh() {
202+
fun forceRefresh() {
195203
logger.debug { "forceRefresh()" }
196204
while (true) {
197-
val oldState = state.get()
198-
val oldStateProviderProvider =
205+
val oldState = state.value
206+
val newState: State.StateWithForceTokenRefresh<T> =
199207
when (oldState) {
200208
is State.Closed -> return
201-
is State.Idle -> oldState
209+
is State.New -> oldState.copy(forceTokenRefresh = true)
210+
is State.Idle -> oldState.copy(forceTokenRefresh = true)
202211
is State.Active -> {
203212
val message = "needs token refresh (wgrwbrvjxt)"
204213
oldState.job.cancel(message, ForceRefresh(message))
205-
oldState
214+
State.Idle(oldState.provider, forceTokenRefresh = true)
206215
}
207216
}
208217

209-
val newState = State.Idle(oldStateProviderProvider.provider, forceTokenRefresh = true)
218+
check(newState.forceTokenRefresh) {
219+
"newState.forceTokenRefresh should be true (error code gnvr2wx7nz)"
220+
}
210221
if (state.compareAndSet(oldState, newState)) {
211222
break
212223
}
213-
214-
yield()
215224
}
216225
}
217226

@@ -246,7 +255,7 @@ internal sealed class DataConnectCredentialsTokenManager<T : Any>(
246255
logger.debug { "$invocationId getToken(requestId=$requestId)" }
247256
while (true) {
248257
val attemptSequenceNumber = nextSequenceNumber()
249-
val oldState = state.get()
258+
val oldState = state.value
250259

251260
val newState: State.Active<T> =
252261
when (oldState) {
@@ -257,13 +266,13 @@ internal sealed class DataConnectCredentialsTokenManager<T : Any>(
257266
}
258267
throw CredentialsTokenManagerClosedException(this)
259268
}
260-
is State.Idle -> {
261-
if (oldState.provider === null) {
262-
logger.debug {
263-
"$invocationId getToken() returns null (token provider is not (yet?) available)"
264-
}
265-
return null
269+
is State.New -> {
270+
logger.debug {
271+
"$invocationId getToken() returns null (token provider is not (yet?) available)"
266272
}
273+
return null
274+
}
275+
is State.Idle -> {
267276
newActiveState(invocationId, oldState.provider, oldState.forceTokenRefresh)
268277
}
269278
is State.Active -> {
@@ -342,7 +351,7 @@ internal sealed class DataConnectCredentialsTokenManager<T : Any>(
342351
addTokenListener(newProvider)
343352

344353
while (true) {
345-
val oldState = state.get()
354+
val oldState = state.value
346355
val newState =
347356
when (oldState) {
348357
is State.Closed -> {
@@ -353,6 +362,7 @@ internal sealed class DataConnectCredentialsTokenManager<T : Any>(
353362
removeTokenListener(newProvider)
354363
break
355364
}
365+
is State.New -> State.Idle(newProvider, oldState.forceTokenRefresh)
356366
is State.Idle -> State.Idle(newProvider, oldState.forceTokenRefresh)
357367
is State.Active -> {
358368
val newProviderClassName = newProvider::class.qualifiedName
@@ -366,8 +376,6 @@ internal sealed class DataConnectCredentialsTokenManager<T : Any>(
366376
break
367377
}
368378
}
369-
370-
_providerAvailable.value = true
371379
}
372380

373381
/**

0 commit comments

Comments
 (0)