From 200794cd2b299c177d115c7ad233523af2ffdf17 Mon Sep 17 00:00:00 2001 From: aj-rosado <109146700+aj-rosado@users.noreply.github.com> Date: Fri, 3 Jul 2026 13:04:33 +0100 Subject: [PATCH] [PM-39840] fix: Use dedicated OkHttpClient for fill-assist to prevent load-balancer prompt (#7131) --- .../network/retrofit/RetrofitsImpl.kt | 25 ++++++++++++++++- .../network/retrofit/RetrofitsTest.kt | 27 ++++++++++++++++++- 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/network/src/main/kotlin/com/bitwarden/network/retrofit/RetrofitsImpl.kt b/network/src/main/kotlin/com/bitwarden/network/retrofit/RetrofitsImpl.kt index 525cad6b96..626d7cf75d 100644 --- a/network/src/main/kotlin/com/bitwarden/network/retrofit/RetrofitsImpl.kt +++ b/network/src/main/kotlin/com/bitwarden/network/retrofit/RetrofitsImpl.kt @@ -67,7 +67,7 @@ internal class RetrofitsImpl( //region Fill-Assist Retrofit override val fillAssistRetrofit: Retrofit by lazy { - createUnauthenticatedRetrofit( + createExternalRetrofit( baseUrlInterceptor = baseUrlInterceptors.fillAssistInterceptor, ) } @@ -111,6 +111,14 @@ internal class RetrofitsImpl( .configureSsl(certificateProvider = certificateProvider) .build() + // For requests to external (non-Bitwarden) URLs. CookieInterceptor must be excluded because + // it treats all 302s as Bitwarden load-balancer auth redirects, which is only correct for + // Bitwarden's own infrastructure. + private val externalOkHttpClient: OkHttpClient = OkHttpClient.Builder() + .addInterceptor(headersInterceptor) + .configureSsl(certificateProvider = certificateProvider) + .build() + private val authenticatedOkHttpClient: OkHttpClient by lazy { baseOkHttpClient .newBuilder() @@ -162,5 +170,20 @@ internal class RetrofitsImpl( ) .build() + private fun createExternalRetrofit( + baseUrlInterceptor: BaseUrlInterceptor, + ): Retrofit = + baseRetrofit + .newBuilder() + .client( + externalOkHttpClient + .newBuilder() + .addInterceptor(baseUrlInterceptor) + .addInterceptor(loggingInterceptor) + .addInterceptor(permissionInterceptor) + .build(), + ) + .build() + //endregion Helper properties and functions } diff --git a/network/src/test/kotlin/com/bitwarden/network/retrofit/RetrofitsTest.kt b/network/src/test/kotlin/com/bitwarden/network/retrofit/RetrofitsTest.kt index 35772ceb8e..145784da10 100644 --- a/network/src/test/kotlin/com/bitwarden/network/retrofit/RetrofitsTest.kt +++ b/network/src/test/kotlin/com/bitwarden/network/retrofit/RetrofitsTest.kt @@ -47,6 +47,9 @@ class RetrofitsTest { every { eventsInterceptor } returns mockk { mockIntercept { isEventsInterceptorCalled = true } } + every { fillAssistInterceptor } returns mockk { + mockIntercept { isFillAssistInterceptorCalled = true } + } } private val cookieInterceptor = mockk { mockIntercept { isCookieInterceptorCalled = true } @@ -82,6 +85,7 @@ class RetrofitsTest { private var isHeadersInterceptorCalled = false private var isIdentityInterceptorCalled = false private var isEventsInterceptorCalled = false + private var isFillAssistInterceptorCalled = false private var isRefreshAuthenticatorCalled = false @Before @@ -248,6 +252,27 @@ class RetrofitsTest { assertFalse(isEventsInterceptorCalled) } + @Test + fun `fillAssistRetrofit should invoke the correct interceptors`() = runBlocking { + val testApi = retrofits + .fillAssistRetrofit + .createMockRetrofit() + .create() + + server.enqueue(MockResponse().setBody("""{}""")) + + testApi.test() + + assertFalse(isAuthInterceptorCalled) + assertFalse(isApiInterceptorCalled) + assertFalse(isCookieInterceptorCalled) + assertTrue(isPermissionInterceptorCalled) + assertTrue(isHeadersInterceptorCalled) + assertFalse(isIdentityInterceptorCalled) + assertFalse(isEventsInterceptorCalled) + assertTrue(isFillAssistInterceptorCalled) + } + @Test fun `createStaticRetrofit when authenticated should invoke the correct interceptors`() = runBlocking { @@ -312,7 +337,7 @@ class RetrofitsTest { retrofits.createStaticRetrofit() - verify(exactly = 1) { + verify(exactly = 2) { anyConstructed().sslSocketFactory(any(), any()) } }