mirror of
https://github.com/bitwarden/android.git
synced 2026-08-23 17:47:57 -05:00
Bug: Support translations for Cookie Acquisition error (#6917)
This commit is contained in:
+3
@@ -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,
|
||||
|
||||
+9
@@ -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
|
||||
|
||||
@@ -43,6 +43,7 @@ class GlideCookieInterceptor(
|
||||
response.close()
|
||||
throw CookieRedirectException(
|
||||
hostname = response.request.url.host,
|
||||
message = cookieProvider.errorMessageString,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+21
@@ -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"
|
||||
|
||||
+7
-3
@@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
+2
@@ -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)
|
||||
|
||||
+7
-6
@@ -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,
|
||||
|
||||
+2
@@ -75,6 +75,8 @@ object PlatformNetworkModule {
|
||||
override fun getPrivateKey(alias: String?): PrivateKey? = null
|
||||
},
|
||||
cookieProvider = object : CookieProvider {
|
||||
override val errorMessageString: String get() = "Error"
|
||||
|
||||
override fun needsBootstrap(hostname: String): Boolean = false
|
||||
|
||||
override fun getCookies(hostname: String): List<NetworkCookie> = emptyList()
|
||||
|
||||
@@ -13,6 +13,5 @@ import java.io.IOException
|
||||
*/
|
||||
class CookieRedirectException(
|
||||
val hostname: String,
|
||||
) : IOException(
|
||||
"Your request was interrupted because the app needed to re-authenticate. Please try again.",
|
||||
)
|
||||
message: String,
|
||||
) : IOException(message)
|
||||
|
||||
@@ -60,7 +60,10 @@ internal class CookieInterceptor(
|
||||
if (cookieProvider.needsBootstrap(hostname)) {
|
||||
Timber.d("Cookie bootstrap required for $hostname, triggering acquisition")
|
||||
cookieProvider.acquireCookies(hostname)
|
||||
throw CookieRedirectException(hostname = hostname)
|
||||
throw CookieRedirectException(
|
||||
hostname = hostname,
|
||||
message = cookieProvider.errorMessageString,
|
||||
)
|
||||
}
|
||||
|
||||
val request = originalRequest.withCookies(hostname)
|
||||
@@ -77,6 +80,7 @@ internal class CookieInterceptor(
|
||||
cookieProvider.acquireCookies(hostname)
|
||||
throw CookieRedirectException(
|
||||
hostname = hostname,
|
||||
message = cookieProvider.errorMessageString,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,11 @@ import com.bitwarden.network.model.NetworkCookie
|
||||
* Bitwarden API endpoints behind a load balancer.
|
||||
*/
|
||||
interface CookieProvider {
|
||||
/**
|
||||
* The translated human-readable string to be displayed when the missing cookie is the reason
|
||||
* for a request failure.
|
||||
*/
|
||||
val errorMessageString: String
|
||||
|
||||
/**
|
||||
* Determines if the given [hostname] requires cookie bootstrap.
|
||||
|
||||
@@ -66,6 +66,7 @@ class CookieInterceptorTest {
|
||||
val chain = FakeInterceptorChain(originalRequest)
|
||||
|
||||
every { mockCookieProvider.needsBootstrap("vault.bitwarden.com") } returns true
|
||||
every { mockCookieProvider.errorMessageString } returns "Error"
|
||||
|
||||
val exception = assertThrows<CookieRedirectException> {
|
||||
interceptor.intercept(chain)
|
||||
@@ -159,6 +160,7 @@ class CookieInterceptorTest {
|
||||
|
||||
every { mockCookieProvider.needsBootstrap("vault.bitwarden.com") } returns false
|
||||
every { mockCookieProvider.getCookies("vault.bitwarden.com") } returns emptyList()
|
||||
every { mockCookieProvider.errorMessageString } returns "Error"
|
||||
|
||||
val exception = assertThrows<CookieRedirectException> {
|
||||
interceptor.intercept(chain)
|
||||
@@ -224,6 +226,7 @@ class CookieInterceptorTest {
|
||||
|
||||
every { mockCookieProvider.needsBootstrap("vault.bitwarden.com") } returns false
|
||||
every { mockCookieProvider.getCookies("vault.bitwarden.com") } returns cookies
|
||||
every { mockCookieProvider.errorMessageString } returns "Error"
|
||||
|
||||
val exception = assertThrows<CookieRedirectException> {
|
||||
interceptor.intercept(chain)
|
||||
|
||||
@@ -13,7 +13,7 @@ class BitwardenErrorTest {
|
||||
|
||||
@Test
|
||||
fun `toBitwardenError with CookieRedirectException should return Http with status 400`() {
|
||||
val exception = CookieRedirectException(hostname = "example.com")
|
||||
val exception = CookieRedirectException(hostname = "example.com", message = "Fail!")
|
||||
|
||||
val result = exception.toBitwardenError()
|
||||
|
||||
@@ -24,13 +24,15 @@ class BitwardenErrorTest {
|
||||
|
||||
@Test
|
||||
fun `toBitwardenError with CookieRedirectException should include message in body`() {
|
||||
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)
|
||||
|
||||
val result = exception.toBitwardenError()
|
||||
|
||||
val httpError = result as BitwardenError.Http
|
||||
val body = httpError.responseBodyString
|
||||
assertTrue(body?.contains(exception.message.orEmpty()) == true)
|
||||
assertTrue(body?.contains(message) == true)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -24,7 +24,7 @@ class ExceptionExtensionsTest {
|
||||
fun `parseErrorBodyOrNull with CookieRedirectException should extract message`() {
|
||||
val expectedMessage = "Your request was interrupted because the app " +
|
||||
"needed to re-authenticate. Please try again."
|
||||
val error = CookieRedirectException(hostname = "example.com")
|
||||
val error = CookieRedirectException(hostname = "example.com", message = expectedMessage)
|
||||
.toBitwardenError()
|
||||
|
||||
val result = error.parseErrorBodyOrNull<CreateCipherResponseJson.Invalid>(
|
||||
|
||||
@@ -1359,4 +1359,5 @@ Do you want to switch to this account?</string>
|
||||
<string name="license_details">License details</string>
|
||||
<string name="licenses">Licenses</string>
|
||||
<string name="no_licenses">There are no licenses in your vault.</string>
|
||||
<string name="your_request_was_interrupted_because_the_app_needed_to_reauthenticate">Your request was interrupted because the app needed to re-authenticate. Please try again.</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user