mirror of
https://github.com/bitwarden/android.git
synced 2026-04-29 04:18:52 -05:00
[PM-31982] Add CookieDiskSource for cookie persistence (#6504)
Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: Patrick Honkonen <SaintPatrck@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
package com.x8bit.bitwarden.data.platform.datasource.disk
|
||||
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.model.CookieConfigurationData
|
||||
|
||||
/**
|
||||
* Disk source for cookie persistence.
|
||||
*/
|
||||
interface CookieDiskSource {
|
||||
|
||||
/**
|
||||
* Gets cookie configuration for a specific [hostname].
|
||||
*
|
||||
* @param hostname The server hostname to retrieve configuration for.
|
||||
* @return The [CookieConfigurationData] if found, or null if no cookies stored.
|
||||
*/
|
||||
fun getCookieConfig(hostname: String): CookieConfigurationData?
|
||||
|
||||
/**
|
||||
* Stores cookie [config] for the given [hostname].
|
||||
*
|
||||
* @param hostname The server hostname to associate with this configuration.
|
||||
* @param config The [CookieConfigurationData] to persist.
|
||||
*/
|
||||
fun storeCookieConfig(hostname: String, config: CookieConfigurationData)
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.x8bit.bitwarden.data.platform.datasource.disk
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import com.bitwarden.core.data.util.decodeFromStringOrNull
|
||||
import com.bitwarden.data.datasource.disk.BaseEncryptedDiskSource
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.model.CookieConfigurationData
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
private const val CONFIG_PREFIX = "elb_cookie_config"
|
||||
|
||||
/**
|
||||
* Implementation of [CookieDiskSource] using encrypted SharedPreferences.
|
||||
*
|
||||
* Simple storage layer for cookies.
|
||||
*/
|
||||
class CookieDiskSourceImpl(
|
||||
sharedPreferences: SharedPreferences,
|
||||
encryptedSharedPreferences: SharedPreferences,
|
||||
private val json: Json,
|
||||
) : CookieDiskSource,
|
||||
BaseEncryptedDiskSource(
|
||||
sharedPreferences = sharedPreferences,
|
||||
encryptedSharedPreferences = encryptedSharedPreferences,
|
||||
) {
|
||||
|
||||
override fun getCookieConfig(hostname: String): CookieConfigurationData? {
|
||||
val key = CONFIG_PREFIX.appendIdentifier(hostname)
|
||||
return getEncryptedString(key)
|
||||
?.let { json.decodeFromStringOrNull<CookieConfigurationData>(it) }
|
||||
}
|
||||
|
||||
override fun storeCookieConfig(hostname: String, config: CookieConfigurationData) {
|
||||
val key = CONFIG_PREFIX.appendIdentifier(hostname)
|
||||
putEncryptedString(key, json.encodeToString(config))
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,8 @@ import com.bitwarden.core.data.manager.dispatcher.DispatcherManager
|
||||
import com.bitwarden.data.datasource.disk.FlightRecorderDiskSource
|
||||
import com.bitwarden.data.datasource.disk.di.EncryptedPreferences
|
||||
import com.bitwarden.data.datasource.disk.di.UnencryptedPreferences
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.CookieDiskSource
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.CookieDiskSourceImpl
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.EnvironmentDiskSource
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.EnvironmentDiskSourceImpl
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.EventDiskSource
|
||||
@@ -155,4 +157,16 @@ object PlatformDiskModule {
|
||||
): FeatureFlagOverrideDiskSource = FeatureFlagOverrideDiskSourceImpl(
|
||||
sharedPreferences = sharedPreferences,
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideCookieDiskSource(
|
||||
@UnencryptedPreferences sharedPreferences: SharedPreferences,
|
||||
@EncryptedPreferences encryptedSharedPreferences: SharedPreferences,
|
||||
json: Json,
|
||||
): CookieDiskSource = CookieDiskSourceImpl(
|
||||
sharedPreferences = sharedPreferences,
|
||||
encryptedSharedPreferences = encryptedSharedPreferences,
|
||||
json = json,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.x8bit.bitwarden.data.platform.datasource.disk.model
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Simple domain model for cookie storage.
|
||||
*
|
||||
* @property hostname The server hostname this configuration applies to.
|
||||
* @property cookies The list of cookies for this server configuration.
|
||||
*/
|
||||
@Serializable
|
||||
data class CookieConfigurationData(
|
||||
val hostname: String,
|
||||
val cookies: List<Cookie>,
|
||||
) {
|
||||
/**
|
||||
* Simple domain model for a cookie.
|
||||
*
|
||||
* @property name The cookie name.
|
||||
* @property value The cookie value.
|
||||
*/
|
||||
@Serializable
|
||||
data class Cookie(
|
||||
val name: String,
|
||||
val value: String,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
package com.x8bit.bitwarden.data.platform.datasource.disk
|
||||
|
||||
import com.bitwarden.core.di.CoreModule
|
||||
import com.bitwarden.data.datasource.disk.base.FakeSharedPreferences
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.model.CookieConfigurationData
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertNull
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class CookieDiskSourceTest {
|
||||
private val fakeEncryptedSharedPreferences = FakeSharedPreferences()
|
||||
private val fakeSharedPreferences = FakeSharedPreferences()
|
||||
private val json = CoreModule.providesJson()
|
||||
|
||||
private val cookieDiskSource: CookieDiskSource = CookieDiskSourceImpl(
|
||||
sharedPreferences = fakeSharedPreferences,
|
||||
encryptedSharedPreferences = fakeEncryptedSharedPreferences,
|
||||
json = json,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `getCookieConfig should return null when no config exists`() {
|
||||
assertNull(cookieDiskSource.getCookieConfig("example.com"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `storeCookieConfig should persist config and getCookieConfig should retrieve it`() {
|
||||
val hostname = "vault.bitwarden.com"
|
||||
val config = CookieConfigurationData(
|
||||
hostname = hostname,
|
||||
cookies = listOf(
|
||||
CookieConfigurationData.Cookie(
|
||||
name = "BW_SESSION",
|
||||
value = "encrypted_cookie_value",
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
cookieDiskSource.storeCookieConfig(hostname, config)
|
||||
|
||||
val retrieved = cookieDiskSource.getCookieConfig(hostname)
|
||||
assertEquals(config, retrieved)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `storeCookieConfig should update existing config`() {
|
||||
val hostname = "vault.bitwarden.com"
|
||||
val initialConfig = CookieConfigurationData(
|
||||
hostname = hostname,
|
||||
cookies = listOf(
|
||||
CookieConfigurationData.Cookie(
|
||||
name = "SESSION",
|
||||
value = "initial_value",
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
val updatedConfig = CookieConfigurationData(
|
||||
hostname = hostname,
|
||||
cookies = listOf(
|
||||
CookieConfigurationData.Cookie(
|
||||
name = "SESSION",
|
||||
value = "updated_value",
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
cookieDiskSource.storeCookieConfig(hostname, initialConfig)
|
||||
cookieDiskSource.storeCookieConfig(hostname, updatedConfig)
|
||||
|
||||
val retrieved = cookieDiskSource.getCookieConfig(hostname)
|
||||
assertEquals(updatedConfig, retrieved)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `storage should handle cookies with multiple values`() {
|
||||
val hostname = "vault.bitwarden.com"
|
||||
val config = CookieConfigurationData(
|
||||
hostname = hostname,
|
||||
cookies = listOf(
|
||||
CookieConfigurationData.Cookie(
|
||||
name = "BW_SESSION",
|
||||
value = "session_value",
|
||||
),
|
||||
CookieConfigurationData.Cookie(
|
||||
name = "BW_REFRESH",
|
||||
value = "refresh_value",
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
cookieDiskSource.storeCookieConfig(hostname, config)
|
||||
|
||||
assertEquals(config, cookieDiskSource.getCookieConfig(hostname))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `storage should isolate configs by hostname`() {
|
||||
val hostname1 = "vault.bitwarden.com"
|
||||
val hostname2 = "other.bitwarden.com"
|
||||
val config1 = CookieConfigurationData(
|
||||
hostname = hostname1,
|
||||
cookies = listOf(
|
||||
CookieConfigurationData.Cookie(
|
||||
name = "A",
|
||||
value = "1",
|
||||
),
|
||||
),
|
||||
)
|
||||
val config2 = CookieConfigurationData(
|
||||
hostname = hostname2,
|
||||
cookies = listOf(
|
||||
CookieConfigurationData.Cookie(
|
||||
name = "B",
|
||||
value = "2",
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
cookieDiskSource.storeCookieConfig(hostname1, config1)
|
||||
cookieDiskSource.storeCookieConfig(hostname2, config2)
|
||||
|
||||
assertEquals(config1, cookieDiskSource.getCookieConfig(hostname1))
|
||||
assertEquals(config2, cookieDiskSource.getCookieConfig(hostname2))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user