Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions firebase-ai/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
configured.
* [changed] Added a `dilation` parameter to `ImagenMaskReference.generateMaskAndPadForOutpainting`
(#7260)
* [feature] Added a new configuration option to enable limited-use App Check tokens for attesting
Firebase AI Logic requests. This enhances security against replay attacks. To use this feature,
configure it explicitly via the new `useLimitedUseAppCheckTokens` parameter when initializing
`FirebaseAI`. We recommend migrating to limited-use tokens now, so your app will be ready to take
advantage of replay protection when it becomes available for Firebase AI Logic.

# 17.1.0
=======
Expand Down
4 changes: 3 additions & 1 deletion firebase-ai/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ package com.google.firebase.ai {
method public static com.google.firebase.ai.FirebaseAI getInstance(com.google.firebase.ai.type.GenerativeBackend backend);
method public static com.google.firebase.ai.FirebaseAI getInstance(com.google.firebase.FirebaseApp app);
method public static com.google.firebase.ai.FirebaseAI getInstance(com.google.firebase.FirebaseApp app = Firebase.app, com.google.firebase.ai.type.GenerativeBackend backend);
method public static com.google.firebase.ai.FirebaseAI getInstance(com.google.firebase.FirebaseApp app = Firebase.app, com.google.firebase.ai.type.GenerativeBackend backend, boolean useLimitedUseAppCheckTokens = false);
method @com.google.firebase.ai.type.PublicPreviewAPI public com.google.firebase.ai.ImagenModel imagenModel(String modelName);
method @com.google.firebase.ai.type.PublicPreviewAPI public com.google.firebase.ai.ImagenModel imagenModel(String modelName, com.google.firebase.ai.type.ImagenGenerationConfig? generationConfig = null);
method @com.google.firebase.ai.type.PublicPreviewAPI public com.google.firebase.ai.ImagenModel imagenModel(String modelName, com.google.firebase.ai.type.ImagenGenerationConfig? generationConfig = null, com.google.firebase.ai.type.ImagenSafetySettings? safetySettings = null);
Expand All @@ -43,11 +44,12 @@ package com.google.firebase.ai {
method public com.google.firebase.ai.FirebaseAI getInstance(com.google.firebase.ai.type.GenerativeBackend backend);
method public com.google.firebase.ai.FirebaseAI getInstance(com.google.firebase.FirebaseApp app);
method public com.google.firebase.ai.FirebaseAI getInstance(com.google.firebase.FirebaseApp app = Firebase.app, com.google.firebase.ai.type.GenerativeBackend backend);
method public com.google.firebase.ai.FirebaseAI getInstance(com.google.firebase.FirebaseApp app = Firebase.app, com.google.firebase.ai.type.GenerativeBackend backend, boolean useLimitedUseAppCheckTokens = false);
property public final com.google.firebase.ai.FirebaseAI instance;
}

public final class FirebaseAIKt {
method public static com.google.firebase.ai.FirebaseAI ai(com.google.firebase.Firebase, com.google.firebase.FirebaseApp app = Firebase.app, com.google.firebase.ai.type.GenerativeBackend backend = GenerativeBackend.googleAI());
method public static com.google.firebase.ai.FirebaseAI ai(com.google.firebase.Firebase, com.google.firebase.FirebaseApp app = Firebase.app, com.google.firebase.ai.type.GenerativeBackend backend = GenerativeBackend.googleAI(), boolean useLimitedUseAppCheckTokens = false);
method public static com.google.firebase.ai.FirebaseAI getAi(com.google.firebase.Firebase);
}

Expand Down
33 changes: 28 additions & 5 deletions firebase-ai/src/main/kotlin/com/google/firebase/ai/FirebaseAI.kt
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ internal constructor(
@Blocking private val blockingDispatcher: CoroutineContext,
private val appCheckProvider: Provider<InteropAppCheckTokenProvider>,
private val internalAuthProvider: Provider<InternalAuthProvider>,
private val useLimitedUseAppCheckTokens: Boolean
) {

/**
Expand Down Expand Up @@ -92,6 +93,7 @@ internal constructor(
modelUri,
firebaseApp.options.apiKey,
firebaseApp,
useLimitedUseAppCheckTokens,
generationConfig,
safetySettings,
tools,
Expand Down Expand Up @@ -152,7 +154,8 @@ internal constructor(
requestOptions,
appCheckProvider.get(),
internalAuthProvider.get(),
backend
backend,
useLimitedUseAppCheckTokens,
)
}

Expand Down Expand Up @@ -194,6 +197,7 @@ internal constructor(
modelUri,
firebaseApp.options.apiKey,
firebaseApp,
useLimitedUseAppCheckTokens,
generationConfig,
safetySettings,
requestOptions,
Expand All @@ -212,15 +216,31 @@ internal constructor(
* Returns the [FirebaseAI] instance for the provided [FirebaseApp] and [backend].
*
* @param backend the backend reference to make generative AI requests to.
* @param useLimitedUseAppCheckTokens when sending tokens to the backend, this option enables
* the usage of App Check's limited-use tokens instead of the standard cached tokens.
*
* A new limited-use tokens will be generated for each request; providing a smaller attack
* surface for malicious parties to hijack tokens. When used alongside replay protection,
* limited-use tokens are also _consumed_ after each request, ensuring they can't be used again.
*
* _This flag is set to `false` by default._
*
* **Important:** Replay protection is not currently supported for the FirebaseAI backend. While
* this feature is being developed, you can still migrate to using limited-use tokens. Because
* limited-use tokens are backwards compatible, you can still use them without replay
* protection. Due to their shorter TTL over standard App Check tokens, they still provide a
* security benefit. Migrating to limited-use tokens sooner minimizes disruption when support
* for replay protection is added.
*/
@JvmStatic
@JvmOverloads
public fun getInstance(
app: FirebaseApp = Firebase.app,
backend: GenerativeBackend
backend: GenerativeBackend,
useLimitedUseAppCheckTokens: Boolean = false,
): FirebaseAI {
val multiResourceComponent = app[FirebaseAIMultiResourceComponent::class.java]
return multiResourceComponent.get(backend)
return multiResourceComponent.get(InstanceKey(backend, useLimitedUseAppCheckTokens))
}

/** The [FirebaseAI] instance for the provided [FirebaseApp] using the Google AI Backend. */
Expand All @@ -244,8 +264,11 @@ public val Firebase.ai: FirebaseAI
* Returns the [FirebaseAI] instance for the provided [FirebaseApp] and [backend].
*
* @param backend the backend reference to make generative AI requests to.
* @param useLimitedUseAppCheckTokens use App Check's limited-use tokens when sending requests to
* the backend. To learn more about what this means, see the full docs on [FirebaseAI.getInstance].
*/
public fun Firebase.ai(
app: FirebaseApp = Firebase.app,
backend: GenerativeBackend = GenerativeBackend.googleAI()
): FirebaseAI = FirebaseAI.getInstance(app, backend)
backend: GenerativeBackend = GenerativeBackend.googleAI(),
useLimitedUseAppCheckTokens: Boolean = false
): FirebaseAI = FirebaseAI.getInstance(app, backend, useLimitedUseAppCheckTokens)
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,24 @@ internal class FirebaseAIMultiResourceComponent(
private val internalAuthProvider: Provider<InternalAuthProvider>,
) {

@GuardedBy("this") private val instances: MutableMap<String, FirebaseAI> = mutableMapOf()
@GuardedBy("this") private val instances: MutableMap<InstanceKey, FirebaseAI> = mutableMapOf()

fun get(backend: GenerativeBackend): FirebaseAI =
fun get(key: InstanceKey): FirebaseAI =
synchronized(this) {
instances.getOrPut(backend.location) {
instances.getOrPut(key) {
FirebaseAI(
app,
backend,
key.backend,
blockingDispatcher,
appCheckProvider,
internalAuthProvider,
key.useLimitedUseAppCheckTokens
)
}
}
}

internal data class InstanceKey(
val backend: GenerativeBackend,
val useLimitedUseAppCheckTokens: Boolean
)
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ internal constructor(
modelName: String,
apiKey: String,
firebaseApp: FirebaseApp,
useLimitedUseAppCheckTokens: Boolean,
generationConfig: GenerationConfig? = null,
safetySettings: List<SafetySetting>? = null,
tools: List<Tool>? = null,
Expand All @@ -73,7 +74,7 @@ internal constructor(
requestOptions: RequestOptions = RequestOptions(),
generativeBackend: GenerativeBackend,
appCheckTokenProvider: InteropAppCheckTokenProvider? = null,
internalAuthProvider: InternalAuthProvider? = null,
internalAuthProvider: InternalAuthProvider? = null
) : this(
modelName,
generationConfig,
Expand All @@ -88,7 +89,12 @@ internal constructor(
requestOptions,
"gl-kotlin/${KotlinVersion.CURRENT}-ai fire/${BuildConfig.VERSION_NAME}",
firebaseApp,
AppCheckHeaderProvider(TAG, appCheckTokenProvider, internalAuthProvider),
AppCheckHeaderProvider(
TAG,
useLimitedUseAppCheckTokens,
appCheckTokenProvider,
internalAuthProvider
),
),
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ internal constructor(
modelName: String,
apiKey: String,
firebaseApp: FirebaseApp,
useLimitedUseAppCheckTokens: Boolean,
generationConfig: ImagenGenerationConfig? = null,
safetySettings: ImagenSafetySettings? = null,
requestOptions: RequestOptions = RequestOptions(),
Expand All @@ -73,7 +74,12 @@ internal constructor(
requestOptions,
"gl-kotlin/${KotlinVersion.CURRENT}-ai fire/${BuildConfig.VERSION_NAME}",
firebaseApp,
AppCheckHeaderProvider(TAG, appCheckTokenProvider, internalAuthProvider),
AppCheckHeaderProvider(
TAG,
useLimitedUseAppCheckTokens,
appCheckTokenProvider,
internalAuthProvider
),
),
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ internal constructor(
appCheckTokenProvider: InteropAppCheckTokenProvider? = null,
internalAuthProvider: InternalAuthProvider? = null,
generativeBackend: GenerativeBackend,
useLimitedUseAppCheckTokens: Boolean,
) : this(
modelName,
blockingDispatcher,
Expand All @@ -83,7 +84,12 @@ internal constructor(
requestOptions,
"gl-kotlin/${KotlinVersion.CURRENT}-ai fire/${BuildConfig.VERSION_NAME}",
firebaseApp,
AppCheckHeaderProvider(TAG, appCheckTokenProvider, internalAuthProvider),
AppCheckHeaderProvider(
TAG,
useLimitedUseAppCheckTokens,
appCheckTokenProvider,
internalAuthProvider
),
generativeBackend
),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import kotlinx.coroutines.tasks.await

internal class AppCheckHeaderProvider(
private val logTag: String,
private val useLimitedUseAppCheckTokens: Boolean,
private val appCheckTokenProvider: InteropAppCheckTokenProvider? = null,
private val internalAuthProvider: InternalAuthProvider? = null,
) : HeaderProvider {
Expand All @@ -36,7 +37,14 @@ internal class AppCheckHeaderProvider(
if (appCheckTokenProvider == null) {
Log.w(logTag, "AppCheck not registered, skipping")
} else {
val token = appCheckTokenProvider.getToken(false).await()
val result =
if (useLimitedUseAppCheckTokens) {
appCheckTokenProvider.limitedUseToken
} else {
appCheckTokenProvider.getToken(false)
}

val token = result.await()

if (token.error != null) {
Log.w(logTag, "Error obtaining AppCheck token", token.error)
Expand Down
Loading