[BWA-182] Add mTLS support for Glide image loading (#6125)

Co-authored-by: David Perez <david@livefront.com>
This commit is contained in:
aj-rosado
2026-01-30 19:57:59 +00:00
committed by GitHub
parent 5531b478d3
commit 9f82b42e36
6 changed files with 178 additions and 32 deletions

View File

@@ -260,6 +260,8 @@ dependencies {
implementation(libs.androidx.work.runtime.ktx)
implementation(libs.bitwarden.sdk)
implementation(libs.bumptech.glide)
implementation(libs.bumptech.glide.okhttp)
ksp(libs.bumptech.glide.compiler)
implementation(libs.google.hilt.android)
ksp(libs.google.hilt.compiler)
implementation(libs.kotlinx.collections.immutable)

View File

@@ -0,0 +1,59 @@
package com.x8bit.bitwarden.ui.platform.glide
import android.content.Context
import com.bitwarden.network.ssl.createMtlsOkHttpClient
import com.bumptech.glide.Glide
import com.bumptech.glide.Registry
import com.bumptech.glide.annotation.GlideModule
import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader
import com.bumptech.glide.load.model.GlideUrl
import com.bumptech.glide.module.AppGlideModule
import com.x8bit.bitwarden.data.platform.manager.CertificateManager
import dagger.hilt.EntryPoint
import dagger.hilt.InstallIn
import dagger.hilt.android.EntryPointAccessors
import dagger.hilt.components.SingletonComponent
import java.io.InputStream
/**
* Custom Glide module for the Bitwarden app that configures Glide to use an OkHttpClient
* with mTLS (mutual TLS) support.
*
* This ensures that all icon/image loading requests through Glide present the client certificate
* for mutual TLS authentication, allowing them to pass through Cloudflare's mTLS checks.
*
* The configuration mirrors the SSL setup used in RetrofitsImpl for API calls.
*/
@GlideModule
class BitwardenAppGlideModule : AppGlideModule() {
/**
* Entry point to access Hilt-provided dependencies from non-Hilt managed classes.
*/
@EntryPoint
@InstallIn(SingletonComponent::class)
interface BitwardenGlideEntryPoint {
/**
* Provides access to the [CertificateManager] for mTLS certificate management.
*/
fun certificateManager(): CertificateManager
}
override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
// Get CertificateManager from Hilt
val entryPoint = EntryPointAccessors.fromApplication(
context = context.applicationContext,
entryPoint = BitwardenGlideEntryPoint::class.java,
)
val certificateManager = entryPoint.certificateManager()
// Register OkHttpUrlLoader that uses our mTLS OkHttpClient
registry.replace(
GlideUrl::class.java,
InputStream::class.java,
OkHttpUrlLoader.Factory(certificateManager.createMtlsOkHttpClient()),
)
}
override fun isManifestParsingEnabled(): Boolean = false
}

View File

@@ -0,0 +1,47 @@
package com.x8bit.bitwarden.ui.platform.glide
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
/**
* Test class for [BitwardenAppGlideModule] to verify mTLS configuration is properly applied
* to Glide without requiring a real mTLS server.
*
* These tests verify the module's structure and that it can be instantiated.
* Full integration testing requires running the app and checking logcat for
* "BitwardenGlide" logs when images are loaded.
*/
class BitwardenAppGlideModuleTest {
@Test
fun `BitwardenAppGlideModule should be instantiable`() {
// Verify the module can be created
val module = BitwardenAppGlideModule()
assertNotNull(module)
}
@Test
fun `BitwardenAppGlideModule should have EntryPoint interface for Hilt dependency injection`() {
// Verify the Hilt EntryPoint interface exists for accessing CertificateManager
val entryPointInterface = BitwardenAppGlideModule::class.java
.declaredClasses
.firstOrNull { it.simpleName == "BitwardenGlideEntryPoint" }
assertNotNull(entryPointInterface)
}
@Test
fun `BitwardenGlideEntryPoint should declare certificateManager method`() {
// Verify the EntryPoint has the required method to access CertificateManager
val entryPointInterface = BitwardenAppGlideModule::class.java
.declaredClasses
.firstOrNull { it.simpleName == "BitwardenGlideEntryPoint" }
val methods = requireNotNull(entryPointInterface).declaredMethods
val hasCertificateManagerMethod = methods.any { it.name == "certificateManager" }
assertTrue(hasCertificateManagerMethod)
}
}