Bug: Support translations for Cookie Acquisition error (#6917)

This commit is contained in:
David Perez
2026-05-13 15:05:53 -05:00
committed by GitHub
parent 8fe23ad275
commit e4f030d0e3
17 changed files with 94 additions and 31 deletions

View File

@@ -89,6 +89,7 @@ import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
import com.x8bit.bitwarden.data.vault.datasource.disk.VaultDiskSource
import com.x8bit.bitwarden.data.vault.manager.VaultLockManager
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
import com.x8bit.bitwarden.ui.platform.manager.resource.ResourceManager
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@@ -439,10 +440,12 @@ object PlatformManagerModule {
@Provides
@Singleton
fun provideNetworkCookieManager(
resourceManager: ResourceManager,
configDiskSource: ConfigDiskSource,
cookieDiskSource: CookieDiskSource,
cookieAcquisitionRequestManager: CookieAcquisitionRequestManager,
): NetworkCookieManager = NetworkCookieManagerImpl(
resourceManager = resourceManager,
configDiskSource = configDiskSource,
cookieDiskSource = cookieDiskSource,
cookieAcquisitionRequestManager = cookieAcquisitionRequestManager,

View File

@@ -2,11 +2,13 @@ package com.x8bit.bitwarden.data.platform.manager.network
import com.bitwarden.data.datasource.disk.ConfigDiskSource
import com.bitwarden.network.model.NetworkCookie
import com.bitwarden.ui.platform.resource.BitwardenString
import com.x8bit.bitwarden.data.platform.datasource.disk.CookieDiskSource
import com.x8bit.bitwarden.data.platform.datasource.disk.model.CookieConfigurationData
import com.x8bit.bitwarden.data.platform.manager.CookieAcquisitionRequestManager
import com.x8bit.bitwarden.data.platform.manager.model.CookieAcquisitionRequest
import com.x8bit.bitwarden.data.platform.manager.util.toNetworkCookieList
import com.x8bit.bitwarden.ui.platform.manager.resource.ResourceManager
import timber.log.Timber
private const val BOOTSTRAP_TYPE_SSO_COOKIE_VENDOR = "ssoCookieVendor"
@@ -15,6 +17,7 @@ private const val BOOTSTRAP_TYPE_SSO_COOKIE_VENDOR = "ssoCookieVendor"
* Default implementation of [NetworkCookieManager].
*/
class NetworkCookieManagerImpl(
private val resourceManager: ResourceManager,
private val configDiskSource: ConfigDiskSource,
private val cookieDiskSource: CookieDiskSource,
private val cookieAcquisitionRequestManager: CookieAcquisitionRequestManager,
@@ -32,6 +35,12 @@ class NetworkCookieManagerImpl(
?.takeIf { it.type == BOOTSTRAP_TYPE_SSO_COOKIE_VENDOR }
?.cookieDomain
override val errorMessageString: String
get() = resourceManager.getString(
resId = BitwardenString
.your_request_was_interrupted_because_the_app_needed_to_reauthenticate,
)
override fun needsBootstrap(hostname: String): Boolean {
val result = configDiskSource
.serverConfig

View File

@@ -43,6 +43,7 @@ class GlideCookieInterceptor(
response.close()
throw CookieRedirectException(
hostname = response.request.url.host,
message = cookieProvider.errorMessageString,
)
}
}

View File

@@ -4,10 +4,12 @@ import com.bitwarden.data.datasource.disk.model.ServerConfig
import com.bitwarden.data.datasource.disk.util.FakeConfigDiskSource
import com.bitwarden.network.model.ConfigResponseJson
import com.bitwarden.network.model.NetworkCookie
import com.bitwarden.ui.platform.resource.BitwardenString
import com.x8bit.bitwarden.data.platform.datasource.disk.CookieDiskSource
import com.x8bit.bitwarden.data.platform.datasource.disk.model.CookieConfigurationData
import com.x8bit.bitwarden.data.platform.manager.CookieAcquisitionRequestManager
import com.x8bit.bitwarden.data.platform.manager.model.CookieAcquisitionRequest
import com.x8bit.bitwarden.ui.platform.manager.resource.ResourceManager
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
@@ -20,6 +22,9 @@ import org.junit.jupiter.api.Test
class NetworkCookieManagerTest {
private val resourceManager: ResourceManager = mockk {
every { getString(resId = any()) } returns ERROR_MESSAGE
}
private val fakeConfigDiskSource = FakeConfigDiskSource()
private val mockCookieDiskSource: CookieDiskSource = mockk()
private val mockCookieAcquisitionRequestManager: CookieAcquisitionRequestManager =
@@ -28,11 +33,25 @@ class NetworkCookieManagerTest {
}
private val manager = NetworkCookieManagerImpl(
resourceManager = resourceManager,
configDiskSource = fakeConfigDiskSource,
cookieDiskSource = mockCookieDiskSource,
cookieAcquisitionRequestManager = mockCookieAcquisitionRequestManager,
)
@Test
fun `errorMessageString should return appropriate message`() {
val result = manager.errorMessageString
assertEquals(ERROR_MESSAGE, result)
verify(exactly = 1) {
resourceManager.getString(
resId = BitwardenString
.your_request_was_interrupted_because_the_app_needed_to_reauthenticate,
)
}
}
@Test
fun `needsBootstrap should return false when serverConfig is null`() {
fakeConfigDiskSource.serverConfig = null
@@ -284,3 +303,5 @@ private fun createCookieConfig(
hostname = hostname,
cookies = cookies,
)
private const val ERROR_MESSAGE: String = "Error Message"

View File

@@ -10,10 +10,14 @@ class ThrowableExtensionsTest {
@Test
fun `userFriendlyMessage should return message for CookieRedirectException`() {
val exception = CookieRedirectException(hostname = "example.com")
val message = "Your request was interrupted because the app needed to " +
"re-authenticate. Please try again."
val exception = CookieRedirectException(
hostname = "example.com",
message = message,
)
assertEquals(
"Your request was interrupted because the app needed to " +
"re-authenticate. Please try again.",
message,
exception.userFriendlyMessage,
)
}

View File

@@ -2941,7 +2941,9 @@ class CipherManagerTest {
runTest {
fakeAuthDiskSource.userState = MOCK_USER_STATE
val cipherId = "mockId-1"
val error = CookieRedirectException("test.host")
val message = "Your request was interrupted because " +
"the app needed to re-authenticate. Please try again."
val error = CookieRedirectException(hostname = "test.host", message = message)
coEvery {
ciphersService.hardDeleteCipher(cipherId = cipherId)
} returns error.asFailure()
@@ -2950,8 +2952,7 @@ class CipherManagerTest {
assertEquals(
DeleteCipherResult.Error(
errorMessage = "Your request was interrupted because " +
"the app needed to re-authenticate. Please try again.",
errorMessage = message,
error = error,
),
result,
@@ -2967,7 +2968,9 @@ class CipherManagerTest {
val cipherId = "mockId-1"
val cipherView = createMockCipherView(number = 1)
val encryptionContext = createMockEncryptionContext(number = 1)
val error = CookieRedirectException("test.host")
val message = "Your request was interrupted because " +
"the app needed to re-authenticate. Please try again."
val error = CookieRedirectException(hostname = "test.host", message = message)
coEvery {
vaultSdkSource.encryptCipher(userId = userId, cipherView = cipherView)
} returns encryptionContext.asSuccess()
@@ -2982,8 +2985,7 @@ class CipherManagerTest {
assertEquals(
DeleteCipherResult.Error(
errorMessage = "Your request was interrupted because " +
"the app needed to re-authenticate. Please try again.",
errorMessage = message,
error = error,
),
result,
@@ -2997,7 +2999,9 @@ class CipherManagerTest {
fakeAuthDiskSource.userState = MOCK_USER_STATE
val cipherId = "mockId-1"
val cipherView = createMockCipherView(number = 1)
val error = CookieRedirectException("test.host")
val message = "Your request was interrupted because " +
"the app needed to re-authenticate. Please try again."
val error = CookieRedirectException(hostname = "test.host", message = message)
coEvery {
ciphersService.restoreCipher(cipherId = cipherId)
} returns error.asFailure()
@@ -3009,8 +3013,7 @@ class CipherManagerTest {
assertEquals(
RestoreCipherResult.Error(
errorMessage = "Your request was interrupted because " +
"the app needed to re-authenticate. Please try again.",
errorMessage = message,
error = error,
),
result,
@@ -3023,7 +3026,9 @@ class CipherManagerTest {
runTest {
fakeAuthDiskSource.userState = MOCK_USER_STATE
val cipherId = "mockId-1"
val error = CookieRedirectException("test.host")
val message = "Your request was interrupted because " +
"the app needed to re-authenticate. Please try again."
val error = CookieRedirectException(hostname = "test.host", message = message)
coEvery {
ciphersService.archiveCipher(cipherId = cipherId)
} returns error.asFailure()
@@ -3035,8 +3040,7 @@ class CipherManagerTest {
assertEquals(
ArchiveCipherResult.Error(
errorMessage = "Your request was interrupted because " +
"the app needed to re-authenticate. Please try again.",
errorMessage = message,
error = error,
),
result,

View File

@@ -686,7 +686,9 @@ class SendManagerTest {
runTest {
fakeAuthDiskSource.userState = MOCK_USER_STATE
val sendId = "mockId-1"
val error = CookieRedirectException(hostname = "example.com")
val message = "Your request was interrupted because " +
"the app needed to re-authenticate. Please try again."
val error = CookieRedirectException(hostname = "test.host", message = message)
coEvery {
sendsService.deleteSend(sendId = sendId)
} returns error.asFailure()
@@ -695,7 +697,7 @@ class SendManagerTest {
assertEquals(
DeleteSendResult.Error(
errorMessage = error.message,
errorMessage = message,
error = error,
),
result,

View File

@@ -102,6 +102,7 @@ class GlideCookieInterceptorTest {
every {
mockCookieProvider.getCookies("vault.bitwarden.com")
} returns emptyList()
every { mockCookieProvider.errorMessageString } returns "Error"
val exception = assertThrows<CookieRedirectException> {
interceptor.intercept(chain)
@@ -134,6 +135,7 @@ class GlideCookieInterceptorTest {
} returns listOf(
NetworkCookie(name = "awselb", value = "session123"),
)
every { mockCookieProvider.errorMessageString } returns "Error"
val exception = assertThrows<CookieRedirectException> {
interceptor.intercept(chain)

View File

@@ -1760,9 +1760,11 @@ class VaultViewModelTest : BaseViewModelTest() {
@Test
fun `vaultDataStateFlow Error with CookieRedirectException should show user-friendly message`() =
runTest {
val message = "Your request was interrupted because " +
"the app needed to re-authenticate. Please try again."
mutableVaultDataStateFlow.tryEmit(
value = DataState.Error(
error = CookieRedirectException(hostname = "example.com"),
error = CookieRedirectException(hostname = "example.com", message = message),
),
)
@@ -1771,10 +1773,7 @@ class VaultViewModelTest : BaseViewModelTest() {
assertEquals(
createMockVaultState(
viewState = VaultState.ViewState.Error(
message = (
"Your request was interrupted because the app needed to " +
"re-authenticate. Please try again."
).asText(),
message = message.asText(),
),
),
viewModel.stateFlow.value,
@@ -1856,9 +1855,11 @@ class VaultViewModelTest : BaseViewModelTest() {
@Test
fun `vaultDataStateFlow Error with CookieRedirectException with items should show user-friendly error dialog`() =
runTest {
val message = "Your request was interrupted because " +
"the app needed to re-authenticate. Please try again."
mutableVaultDataStateFlow.tryEmit(
value = DataState.Error(
error = CookieRedirectException(hostname = "example.com"),
error = CookieRedirectException(hostname = "example.com", message = message),
data = VaultData(
decryptCipherListResult = createMockDecryptCipherListResult(
number = 1,