From 00a4942fc99516c8d44ee6eea7dc9b7458b48c66 Mon Sep 17 00:00:00 2001 From: aj-rosado <109146700+aj-rosado@users.noreply.github.com> Date: Wed, 5 Aug 2026 17:38:54 +0100 Subject: [PATCH] [PM-39840] fix: Scope cookie bootstrap to the request's environment (#7243) --- .../network/NetworkCookieManagerImpl.kt | 42 +++++++++---------- .../network/NetworkCookieManagerTest.kt | 28 +++++++++++++ 2 files changed, 49 insertions(+), 21 deletions(-) diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/network/NetworkCookieManagerImpl.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/network/NetworkCookieManagerImpl.kt index 9828f71a71..2fa8a6ee00 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/network/NetworkCookieManagerImpl.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/manager/network/NetworkCookieManagerImpl.kt @@ -42,27 +42,12 @@ class NetworkCookieManagerImpl( ) override fun needsBootstrap(hostname: String): Boolean { - val result = configDiskSource - .serverConfig - ?.serverData - ?.communication - ?.bootstrap - ?.type - ?.let { bootstrapType -> - when (bootstrapType) { - BOOTSTRAP_TYPE_SSO_COOKIE_VENDOR -> { - val resolved = resolveHostname(hostname) - cookieDiskSource - .getCookieConfig(hostname = resolved) - ?.cookies - ?.none() != false - } - - else -> false - } - } - ?: false - Timber.d("needsBootstrap($hostname): $result (cookieDomain=$cookieDomain)") + val result = isCookieBootstrapConfigured(hostname = hostname) && + cookieDiskSource + .getCookieConfig(hostname = resolveHostname(hostname)) + ?.cookies + ?.none() != false + Timber.d("needsBootstrap($hostname): $result") return result } @@ -101,6 +86,21 @@ class NetworkCookieManagerImpl( ) } + private fun isCookieBootstrapConfigured(hostname: String): Boolean { + val bootstrap = configDiskSource + .serverConfig + ?.serverData + ?.communication + ?.bootstrap + ?.takeIf { it.type == BOOTSTRAP_TYPE_SSO_COOKIE_VENDOR } + ?: return false + val domain = bootstrap.cookieDomain + // No cookie domain configured: cannot scope to an environment, preserve default behavior. + val result = domain == null || hostname == domain || hostname.endsWith(".$domain") + Timber.d("isCookieBootstrapConfigured($hostname): $result (cookieDomain=$domain)") + return result + } + /** * Resolves the storage key for a given [hostname] by performing domain-suffix fallback. * diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/manager/network/NetworkCookieManagerTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/manager/network/NetworkCookieManagerTest.kt index db9ac04546..a67d34b4e0 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/manager/network/NetworkCookieManagerTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/manager/network/NetworkCookieManagerTest.kt @@ -200,6 +200,33 @@ class NetworkCookieManagerTest { assertTrue(result) } + @Suppress("MaxLineLength") + @Test + fun `needsBootstrap should return true when hostname equals the cookie domain and no cookies`() { + fakeConfigDiskSource.serverConfig = createServerConfig( + bootstrapType = BOOTSTRAP_TYPE_SSO, + cookieDomain = COOKIE_DOMAIN, + ) + every { mockCookieDiskSource.getCookieConfig(any()) } returns null + + val result = manager.needsBootstrap(COOKIE_DOMAIN) + + assertTrue(result) + } + + @Test + fun `needsBootstrap should return false when hostname is outside the cookie domain`() { + fakeConfigDiskSource.serverConfig = createServerConfig( + bootstrapType = BOOTSTRAP_TYPE_SSO, + cookieDomain = COOKIE_DOMAIN, + ) + + val result = manager.needsBootstrap(OTHER_ENVIRONMENT_HOSTNAME) + + assertFalse(result) + verify(exactly = 0) { mockCookieDiskSource.getCookieConfig(any()) } + } + @Test fun `storeCookies should store under cookieDomain when present`() { fakeConfigDiskSource.serverConfig = createServerConfig( @@ -259,6 +286,7 @@ class NetworkCookieManagerTest { private const val HOSTNAME = "vault.bitwarden.com" private const val COOKIE_DOMAIN = "bitwarden.com" private const val SUBDOMAIN_HOSTNAME = "api.bitwarden.com" +private const val OTHER_ENVIRONMENT_HOSTNAME = "vault.example.org" private const val BOOTSTRAP_TYPE_SSO = "ssoCookieVendor" private val DEFAULT_COOKIES = listOf(