Skip to content

Commit 85ee4b4

Browse files
authored
dataconnect(chore): Create AuthUid value class to add some type safety compared to raw strings (#8198)
1 parent f343cd9 commit 85ee4b4

11 files changed

Lines changed: 83 additions & 55 deletions

File tree

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

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,13 @@ internal class DataConnectAuth(
5656
GetAuthTokenResult(it.token, it.getAuthUid())
5757
}
5858

59-
data class GetAuthTokenResult(override val token: String?, val authUid: String?) : GetTokenResult
59+
@JvmInline
60+
value class AuthUid(val string: String) {
61+
override fun toString() = "AuthUid($string)"
62+
}
63+
64+
data class GetAuthTokenResult(override val token: String?, val authUid: AuthUid?) :
65+
GetTokenResult
6066

6167
private class IdTokenListenerImpl(private val logger: Logger) : IdTokenListener {
6268
override fun onIdTokenChanged(tokenResult: InternalTokenResult) {
@@ -68,6 +74,9 @@ internal class DataConnectAuth(
6874

6975
// The "sub" claim is documented to be "a non-empty string and must be the uid of the user or
7076
// device". See http://goo.gle/4oGjEQt for the relevant Firebase documentation.
71-
fun com.google.firebase.auth.GetTokenResult.getAuthUid(): String? = claims["sub"] as? String
77+
fun com.google.firebase.auth.GetTokenResult.getAuthUid(): AuthUid? {
78+
val sub = claims["sub"] as? String
79+
return if (sub === null) null else AuthUid(sub)
80+
}
7281
}
7382
}

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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.google.firebase.dataconnect.core
1818

19+
import com.google.firebase.dataconnect.core.DataConnectAuth.AuthUid
1920
import com.google.firebase.dataconnect.core.LoggerGlobals.debug
2021
import com.google.firebase.dataconnect.util.CoroutineUtils.completedFlow
2122
import com.google.firebase.dataconnect.util.GrpcBidiFlow
@@ -72,7 +73,7 @@ import kotlinx.coroutines.launch
7273
* @param coroutineScope The [CoroutineScope] to whose lifetime this object belongs.
7374
*/
7475
internal class DataConnectBidiConnectStream(
75-
flow: Flow<GrpcBidiFlow.Event<StreamRequestProto, StreamResponseProto, String?>>,
76+
flow: Flow<GrpcBidiFlow.Event<StreamRequestProto, StreamResponseProto, AuthUid?>>,
7677
private val coroutineScope: CoroutineScope,
7778
private val logger: Logger,
7879
) {
@@ -278,11 +279,11 @@ internal class DataConnectBidiConnectStream(
278279

279280
class Connected(
280281
val connectionId: String,
281-
val authUid: String?,
282+
val authUid: AuthUid?,
282283
val outgoingRequests: SendChannel<StreamRequestProto>,
283284
) : Connection {
284285
constructor(
285-
event: GrpcBidiFlow.Event.ConnectionInfo<StreamRequestProto, String?>
286+
event: GrpcBidiFlow.Event.ConnectionInfo<StreamRequestProto, AuthUid?>
286287
) : this(event.connectionId, event.connectionCookie, event.outgoingRequests)
287288

288289
override fun toString() = "Connected(connectionId=$connectionId)"
@@ -342,7 +343,7 @@ internal class DataConnectBidiConnectStream(
342343
): Flow<SubscriptionEvent.Message> {
343344

344345
fun SendChannel<StreamRequestProto>.trySendOrThrow(
345-
authUid: String?,
346+
authUid: AuthUid?,
346347
request: StreamRequestProto
347348
) {
348349
val sendResult = trySend(request)
@@ -359,7 +360,7 @@ internal class DataConnectBidiConnectStream(
359360
}
360361

361362
suspend fun SendChannel<StreamRequestProto>.subscribeOrResumeLoop(
362-
authUid: String?,
363+
authUid: AuthUid?,
363364
subscribeOrResumeSignal: ConflatedSignal,
364365
) {
365366
var subscribed = false

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

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import android.os.Build
2020
import com.google.firebase.FirebaseApp
2121
import com.google.firebase.dataconnect.BuildConfig
2222
import com.google.firebase.dataconnect.FirebaseDataConnect
23+
import com.google.firebase.dataconnect.core.DataConnectAuth.AuthUid
2324
import com.google.firebase.dataconnect.core.Globals.toScrubbedAccessToken
2425
import com.google.firebase.dataconnect.core.LoggerGlobals.Logger
2526
import com.google.firebase.dataconnect.core.LoggerGlobals.debug
@@ -100,7 +101,7 @@ internal class DataConnectGrpcMetadata(
100101
companion object {
101102
// TODO: Move this to ProtoUtil.kt where it would live alongside other related methods.
102103
// NOTE: Keep the implementation of this method in parity with StructProtoBuilder.putHeaders().
103-
fun Metadata.toStructProto(authUid: String?): Struct = buildStructProto {
104+
fun Metadata.toStructProto(authUid: AuthUid?): Struct = buildStructProto {
104105
val keys: List<Metadata.Key<String>> = run {
105106
val keySet: MutableSet<String> = keys().toMutableSet()
106107
// Always explicitly include the auth header in the returned string, even if it is absent.
@@ -116,7 +117,8 @@ internal class DataConnectGrpcMetadata(
116117
else {
117118
values.map {
118119
when (key.name()) {
119-
firebaseAuthTokenHeader.name() -> it.toScrubbedAccessToken() + " (authUid=$authUid)"
120+
firebaseAuthTokenHeader.name() ->
121+
it.toScrubbedAccessToken() + " (authUid=${authUid?.string})"
120122
firebaseAppCheckTokenHeader.name() -> it.toScrubbedAccessToken()
121123
else -> it
122124
}
@@ -131,7 +133,11 @@ internal class DataConnectGrpcMetadata(
131133

132134
// TODO: Move this to ProtoUtil.kt where it would live alongside other related methods.
133135
// NOTE: Keep the implementation of this method in parity with Metadata.toStructProto().
134-
fun StructProtoBuilder.putHeaders(authUid: String?, key: String, headers: Map<String, String>) {
136+
fun StructProtoBuilder.putHeaders(
137+
authUid: AuthUid?,
138+
key: String,
139+
headers: Map<String, String>
140+
) {
135141
putStruct(key) {
136142
val keys: List<String> =
137143
buildSet {
@@ -148,7 +154,8 @@ internal class DataConnectGrpcMetadata(
148154
val scrubbedValue =
149155
value?.let {
150156
when (key) {
151-
firebaseAuthTokenHeader.name() -> it.toScrubbedAccessToken() + " (authUid=$authUid)"
157+
firebaseAuthTokenHeader.name() ->
158+
it.toScrubbedAccessToken() + " (authUid=${authUid?.string})"
152159
firebaseAppCheckTokenHeader.name() -> it.toScrubbedAccessToken()
153160
else -> it
154161
}

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import com.google.firebase.dataconnect.DataConnectPath
2424
import com.google.firebase.dataconnect.DataConnectPathSegment
2525
import com.google.firebase.dataconnect.FirebaseDataConnect
2626
import com.google.firebase.dataconnect.QueryRef.FetchPolicy
27+
import com.google.firebase.dataconnect.core.DataConnectAuth.AuthUid
2728
import com.google.firebase.dataconnect.core.DataConnectGrpcMetadata.Companion.toStructProto
2829
import com.google.firebase.dataconnect.core.LoggerGlobals.Logger
2930
import com.google.firebase.dataconnect.core.LoggerGlobals.debug
@@ -240,7 +241,7 @@ internal class DataConnectGrpcRPCs(
240241

241242
private class QueryCacheInfo(
242243
val cacheDb: DataConnectCacheDatabase,
243-
val authUid: String?,
244+
val authUid: AuthUid?,
244245
val queryId: ImmutableByteArray,
245246
val maxAge: DurationProto,
246247
)
@@ -427,7 +428,7 @@ internal class DataConnectGrpcRPCs(
427428

428429
private inner class ConnectGrpcBidiFlowListener(
429430
private val streamId: String,
430-
private val authUid: String?,
431+
private val authUid: AuthUid?,
431432
private val initRequest: StreamRequest,
432433
private val kotlinMethodName: String,
433434
) : GrpcBidiFlow.Listener<StreamRequest, StreamResponse> {
@@ -511,7 +512,7 @@ internal class DataConnectGrpcRPCs(
511512
}
512513

513514
@Suppress("unused")
514-
private class ConnectGrpcBidiFlowListenerFormatter(private val authUid: String?) :
515+
private class ConnectGrpcBidiFlowListenerFormatter(private val authUid: AuthUid?) :
515516
GrpcBidiFlowListenerMessageFormatter.Formatter<StreamRequest, StreamResponse>() {
516517
override fun connectionStartingHeaders(headers: Metadata?): String =
517518
headers?.toStructProto(authUid)?.toCompactString().toString()
@@ -682,7 +683,7 @@ internal class DataConnectGrpcRPCs(
682683
metadata: Metadata?,
683684
request: () -> Struct,
684685
requestTypeName: String,
685-
authUid: String?,
686+
authUid: AuthUid?,
686687
) = debug {
687688
val requestStruct = request()
688689
val struct = buildStructProto {

firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/sqlite/DataConnectCacheDatabase.kt

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package com.google.firebase.dataconnect.sqlite
1818

1919
import android.annotation.SuppressLint
2020
import android.database.sqlite.SQLiteDatabase
21+
import com.google.firebase.dataconnect.core.DataConnectAuth.AuthUid
2122
import com.google.firebase.dataconnect.core.Logger
2223
import com.google.firebase.dataconnect.core.LoggerGlobals.warn
2324
import com.google.firebase.dataconnect.sqlite.DataConnectCacheDatabase.GetQueryResultResult
@@ -198,17 +199,17 @@ internal class DataConnectCacheDatabase(
198199

199200
@JvmInline private value class SqliteEntityId(val sqliteRowId: Long)
200201

201-
private fun SQLiteDatabase.getOrInsertAuthUid(authUid: String?): SqliteUserId {
202-
execSQL(logger, "INSERT OR IGNORE INTO users (auth_uid) VALUES (?)", arrayOf(authUid))
202+
private fun SQLiteDatabase.getOrInsertAuthUid(authUid: AuthUid?): SqliteUserId {
203+
execSQL(logger, "INSERT OR IGNORE INTO users (auth_uid) VALUES (?)", arrayOf(authUid?.string))
203204
return rawQuery(
204205
logger,
205206
"SELECT id FROM users WHERE auth_uid ${if (authUid === null) "IS NULL" else "= ?"}",
206-
if (authUid === null) emptyArray() else arrayOf(authUid),
207+
if (authUid === null) emptyArray() else arrayOf(authUid.string),
207208
) { cursor ->
208209
if (cursor.moveToNext()) {
209210
SqliteUserId(cursor.getLong(0))
210211
} else {
211-
throw AuthUidNotFoundException("authUid=$authUid (internal error m5m52ahrxz)")
212+
throw AuthUidNotFoundException("authUid=${authUid?.string} (internal error m5m52ahrxz)")
212213
}
213214
}
214215
}
@@ -451,7 +452,7 @@ internal class DataConnectCacheDatabase(
451452
}
452453

453454
suspend fun getQueryResult(
454-
authUid: String?,
455+
authUid: AuthUid?,
455456
queryId: ImmutableByteArray,
456457
currentTimeMillis: Long,
457458
staleResult: KClass<out GetQueryResultResult>,
@@ -524,7 +525,7 @@ internal class DataConnectCacheDatabase(
524525
}
525526

526527
suspend fun insertQueryResult(
527-
authUid: String?,
528+
authUid: AuthUid?,
528529
queryId: ImmutableByteArray,
529530
queryData: Struct,
530531
maxAge: DurationProto,

firebase-dataconnect/src/main/kotlin/com/google/firebase/dataconnect/util/ProtoUtil.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package com.google.firebase.dataconnect.util
1818

1919
import com.google.firebase.dataconnect.DataConnectPath
2020
import com.google.firebase.dataconnect.DataConnectPathSegment
21+
import com.google.firebase.dataconnect.core.DataConnectAuth.AuthUid
2122
import com.google.firebase.dataconnect.core.DataConnectGrpcMetadata.Companion.putHeaders
2223
import com.google.firebase.dataconnect.core.DataConnectSerialization.Companion.toErrorInfoImpl
2324
import com.google.firebase.dataconnect.toPathString
@@ -297,11 +298,11 @@ internal object ProtoUtil {
297298
}
298299

299300
fun StreamRequest.toCompactString(
300-
authUid: String?,
301+
authUid: AuthUid?,
301302
keySortSelector: ((String) -> String)? = ::streamRequestKeySortSelector,
302303
): String = toStructProto(authUid).toCompactString(keySortSelector)
303304

304-
fun StreamRequest.toStructProto(authUid: String?): Struct = buildStructProto {
305+
fun StreamRequest.toStructProto(authUid: AuthUid?): Struct = buildStructProto {
305306
name.takeIf { it.isNotEmpty() }?.let { put("name", it) }
306307
requestId.takeIf { it.isNotEmpty() }?.let { put("requestId", it) }
307308
dataEtag.takeIf { it.isNotEmpty() }?.let { put("data_etag", it) }

firebase-dataconnect/src/test/kotlin/com/google/firebase/dataconnect/core/DataConnectAuthUnitTest.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import com.google.firebase.auth.GetTokenResult
2323
import com.google.firebase.auth.internal.IdTokenListener
2424
import com.google.firebase.auth.internal.InternalAuthProvider
2525
import com.google.firebase.dataconnect.DataConnectException
26+
import com.google.firebase.dataconnect.core.DataConnectAuth.AuthUid
2627
import com.google.firebase.dataconnect.core.Globals.toScrubbedAccessToken
2728
import com.google.firebase.dataconnect.testutil.DataConnectLogLevelRule
2829
import com.google.firebase.dataconnect.testutil.DelayedDeferred
@@ -326,7 +327,7 @@ class DataConnectAuthUnitTest {
326327

327328
val result = dataConnectAuth.getToken(requestId)
328329

329-
result.shouldNotBeNull().authUid shouldBe uid
330+
result.shouldNotBeNull().authUid shouldBe AuthUid(uid)
330331
}
331332

332333
@Test

0 commit comments

Comments
 (0)