BIT-1148: Add storage for blocked autofill URIs (#657)

This commit is contained in:
Brian Yencho 2024-01-17 21:30:25 -06:00 committed by Álison Fernandes
parent a87bcd28ff
commit 840f675736
8 changed files with 163 additions and 0 deletions

View file

@ -102,4 +102,17 @@ interface SettingsDiskSource {
* Stores the given [isInlineAutofillEnabled] value for the given [userId]. * Stores the given [isInlineAutofillEnabled] value for the given [userId].
*/ */
fun storeInlineAutofillEnabled(userId: String, isInlineAutofillEnabled: Boolean?) fun storeInlineAutofillEnabled(userId: String, isInlineAutofillEnabled: Boolean?)
/**
* Gets a list of blocked autofill URI's for the given [userId].
*/
fun getBlockedAutofillUris(userId: String): List<String>?
/**
* Stores the list of [blockedAutofillUris] for the given [userId].
*/
fun storeBlockedAutofillUris(
userId: String,
blockedAutofillUris: List<String>?,
)
} }

View file

@ -9,11 +9,14 @@ import com.x8bit.bitwarden.ui.platform.feature.settings.appearance.model.AppThem
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.onSubscription import kotlinx.coroutines.flow.onSubscription
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
private const val APP_LANGUAGE_KEY = "$BASE_KEY:appLocale" private const val APP_LANGUAGE_KEY = "$BASE_KEY:appLocale"
private const val APP_THEME_KEY = "$BASE_KEY:theme" private const val APP_THEME_KEY = "$BASE_KEY:theme"
private const val PULL_TO_REFRESH_KEY = "$BASE_KEY:syncOnRefresh" private const val PULL_TO_REFRESH_KEY = "$BASE_KEY:syncOnRefresh"
private const val INLINE_AUTOFILL_ENABLED_KEY = "$BASE_KEY:inlineAutofillEnabled" private const val INLINE_AUTOFILL_ENABLED_KEY = "$BASE_KEY:inlineAutofillEnabled"
private const val BLOCKED_AUTOFILL_URIS_KEY = "$BASE_KEY:autofillBlacklistedUris"
private const val VAULT_TIMEOUT_ACTION_KEY = "$BASE_KEY:vaultTimeoutAction" private const val VAULT_TIMEOUT_ACTION_KEY = "$BASE_KEY:vaultTimeoutAction"
private const val VAULT_TIME_IN_MINUTES_KEY = "$BASE_KEY:vaultTimeout" private const val VAULT_TIME_IN_MINUTES_KEY = "$BASE_KEY:vaultTimeout"
private const val DISABLE_ICON_LOADING_KEY = "$BASE_KEY:disableFavicon" private const val DISABLE_ICON_LOADING_KEY = "$BASE_KEY:disableFavicon"
@ -24,6 +27,7 @@ private const val DISABLE_ICON_LOADING_KEY = "$BASE_KEY:disableFavicon"
@Suppress("TooManyFunctions") @Suppress("TooManyFunctions")
class SettingsDiskSourceImpl( class SettingsDiskSourceImpl(
val sharedPreferences: SharedPreferences, val sharedPreferences: SharedPreferences,
private val json: Json,
) : BaseDiskSource(sharedPreferences = sharedPreferences), ) : BaseDiskSource(sharedPreferences = sharedPreferences),
SettingsDiskSource { SettingsDiskSource {
private val mutableAppThemeFlow = private val mutableAppThemeFlow =
@ -87,6 +91,7 @@ class SettingsDiskSourceImpl(
storeVaultTimeoutAction(userId = userId, vaultTimeoutAction = null) storeVaultTimeoutAction(userId = userId, vaultTimeoutAction = null)
storePullToRefreshEnabled(userId = userId, isPullToRefreshEnabled = null) storePullToRefreshEnabled(userId = userId, isPullToRefreshEnabled = null)
storeInlineAutofillEnabled(userId = userId, isInlineAutofillEnabled = null) storeInlineAutofillEnabled(userId = userId, isInlineAutofillEnabled = null)
storeBlockedAutofillUris(userId = userId, blockedAutofillUris = null)
} }
override fun getVaultTimeoutInMinutes(userId: String): Int? = override fun getVaultTimeoutInMinutes(userId: String): Int? =
@ -152,6 +157,21 @@ class SettingsDiskSourceImpl(
) )
} }
override fun getBlockedAutofillUris(userId: String): List<String>? =
getString(key = "${BLOCKED_AUTOFILL_URIS_KEY}_$userId")?.let {
json.decodeFromString(it)
}
override fun storeBlockedAutofillUris(
userId: String,
blockedAutofillUris: List<String>?,
) {
putString(
key = "${BLOCKED_AUTOFILL_URIS_KEY}_$userId",
value = blockedAutofillUris?.let { json.encodeToString(it) },
)
}
private fun getMutableVaultTimeoutActionFlow( private fun getMutableVaultTimeoutActionFlow(
userId: String, userId: String,
): MutableSharedFlow<VaultTimeoutAction?> = ): MutableSharedFlow<VaultTimeoutAction?> =

View file

@ -46,8 +46,10 @@ object PlatformDiskModule {
@Singleton @Singleton
fun provideSettingsDiskSource( fun provideSettingsDiskSource(
@UnencryptedPreferences sharedPreferences: SharedPreferences, @UnencryptedPreferences sharedPreferences: SharedPreferences,
json: Json,
): SettingsDiskSource = ): SettingsDiskSource =
SettingsDiskSourceImpl( SettingsDiskSourceImpl(
sharedPreferences = sharedPreferences, sharedPreferences = sharedPreferences,
json = json,
) )
} }

View file

@ -57,6 +57,11 @@ interface SettingsRepository {
*/ */
var isInlineAutofillEnabled: Boolean var isInlineAutofillEnabled: Boolean
/**
* A list of blocked autofill URI's for the current user.
*/
var blockedAutofillUris: List<String>
/** /**
* Sets default values for various settings for the given [userId] if necessary. This is * Sets default values for various settings for the given [userId] if necessary. This is
* typically used when logging into a new account. * typically used when logging into a new account.

View file

@ -109,6 +109,18 @@ class SettingsRepositoryImpl(
) )
} }
override var blockedAutofillUris: List<String>
get() = activeUserId
?.let { settingsDiskSource.getBlockedAutofillUris(userId = it) }
?: emptyList()
set(value) {
val userId = activeUserId ?: return
settingsDiskSource.storeBlockedAutofillUris(
userId = userId,
blockedAutofillUris = value,
)
}
override fun setDefaultsIfNecessary(userId: String) { override fun setDefaultsIfNecessary(userId: String) {
// Set Vault Settings defaults // Set Vault Settings defaults
if (!isVaultTimeoutActionSet(userId = userId)) { if (!isVaultTimeoutActionSet(userId = userId)) {

View file

@ -3,6 +3,7 @@ package com.x8bit.bitwarden.data.platform.datasource.disk
import androidx.core.content.edit import androidx.core.content.edit
import app.cash.turbine.test import app.cash.turbine.test
import com.x8bit.bitwarden.data.platform.base.FakeSharedPreferences import com.x8bit.bitwarden.data.platform.base.FakeSharedPreferences
import com.x8bit.bitwarden.data.platform.datasource.network.di.PlatformNetworkModule
import com.x8bit.bitwarden.data.platform.repository.model.VaultTimeoutAction import com.x8bit.bitwarden.data.platform.repository.model.VaultTimeoutAction
import com.x8bit.bitwarden.ui.platform.feature.settings.appearance.model.AppLanguage import com.x8bit.bitwarden.ui.platform.feature.settings.appearance.model.AppLanguage
import com.x8bit.bitwarden.ui.platform.feature.settings.appearance.model.AppTheme import com.x8bit.bitwarden.ui.platform.feature.settings.appearance.model.AppTheme
@ -15,9 +16,11 @@ import org.junit.jupiter.api.Test
class SettingsDiskSourceTest { class SettingsDiskSourceTest {
private val fakeSharedPreferences = FakeSharedPreferences() private val fakeSharedPreferences = FakeSharedPreferences()
private val json = PlatformNetworkModule.providesJson()
private val settingsDiskSource = SettingsDiskSourceImpl( private val settingsDiskSource = SettingsDiskSourceImpl(
sharedPreferences = fakeSharedPreferences, sharedPreferences = fakeSharedPreferences,
json = json,
) )
@Test @Test
@ -78,6 +81,10 @@ class SettingsDiskSourceTest {
userId = userId, userId = userId,
isInlineAutofillEnabled = true, isInlineAutofillEnabled = true,
) )
settingsDiskSource.storeBlockedAutofillUris(
userId = userId,
blockedAutofillUris = listOf("www.example.com"),
)
settingsDiskSource.clearData(userId = userId) settingsDiskSource.clearData(userId = userId)
@ -85,6 +92,7 @@ class SettingsDiskSourceTest {
assertNull(settingsDiskSource.getVaultTimeoutAction(userId = userId)) assertNull(settingsDiskSource.getVaultTimeoutAction(userId = userId))
assertNull(settingsDiskSource.getPullToRefreshEnabled(userId = userId)) assertNull(settingsDiskSource.getPullToRefreshEnabled(userId = userId))
assertNull(settingsDiskSource.getInlineAutofillEnabled(userId = userId)) assertNull(settingsDiskSource.getInlineAutofillEnabled(userId = userId))
assertNull(settingsDiskSource.getBlockedAutofillUris(userId = userId))
} }
@Test @Test
@ -441,4 +449,62 @@ class SettingsDiskSourceTest {
) )
assertFalse(fakeSharedPreferences.contains(inlineAutofillEnabledKey)) assertFalse(fakeSharedPreferences.contains(inlineAutofillEnabledKey))
} }
@Test
fun `getBlockedAutofillUris should pull from SharedPreferences`() {
val blockedAutofillUrisBaseKey = "bwPreferencesStorage:autofillBlacklistedUris"
val mockUserId = "mockUserId"
val mockBlockedAutofillUris = listOf(
"https://www.example1.com",
"https://www.example2.com",
)
fakeSharedPreferences
.edit {
putString(
"${blockedAutofillUrisBaseKey}_$mockUserId",
"""
[
"https://www.example1.com",
"https://www.example2.com"
]
"""
.trimIndent(),
)
}
val actual = settingsDiskSource.getBlockedAutofillUris(userId = mockUserId)
assertEquals(
mockBlockedAutofillUris,
actual,
)
}
@Test
fun `storeBlockedAutofillUris should update SharedPreferences`() {
val blockedAutofillUrisBaseKey = "bwPreferencesStorage:autofillBlacklistedUris"
val mockUserId = "mockUserId"
val mockBlockedAutofillUris = listOf(
"https://www.example1.com",
"https://www.example2.com",
)
settingsDiskSource.storeBlockedAutofillUris(
userId = mockUserId,
blockedAutofillUris = mockBlockedAutofillUris,
)
val actual = fakeSharedPreferences.getString(
"${blockedAutofillUrisBaseKey}_$mockUserId",
null,
)
assertEquals(
json.parseToJsonElement(
"""
[
"https://www.example1.com",
"https://www.example2.com"
]
"""
.trimIndent(),
),
json.parseToJsonElement(requireNotNull(actual)),
)
}
} }

View file

@ -35,6 +35,7 @@ class FakeSettingsDiskSource : SettingsDiskSource {
private val storedPullToRefreshEnabled = mutableMapOf<String, Boolean?>() private val storedPullToRefreshEnabled = mutableMapOf<String, Boolean?>()
private val storedInlineAutofillEnabled = mutableMapOf<String, Boolean?>() private val storedInlineAutofillEnabled = mutableMapOf<String, Boolean?>()
private val storedBlockedAutofillUris = mutableMapOf<String, List<String>?>()
private var storedIsIconLoadingDisabled: Boolean? = null private var storedIsIconLoadingDisabled: Boolean? = null
@ -69,6 +70,7 @@ class FakeSettingsDiskSource : SettingsDiskSource {
storedVaultTimeoutInMinutes.remove(userId) storedVaultTimeoutInMinutes.remove(userId)
storedPullToRefreshEnabled.remove(userId) storedPullToRefreshEnabled.remove(userId)
storedInlineAutofillEnabled.remove(userId) storedInlineAutofillEnabled.remove(userId)
storedBlockedAutofillUris.remove(userId)
mutableVaultTimeoutActionsFlowMap.remove(userId) mutableVaultTimeoutActionsFlowMap.remove(userId)
mutableVaultTimeoutInMinutesFlowMap.remove(userId) mutableVaultTimeoutInMinutesFlowMap.remove(userId)
@ -126,6 +128,16 @@ class FakeSettingsDiskSource : SettingsDiskSource {
storedInlineAutofillEnabled[userId] = isInlineAutofillEnabled storedInlineAutofillEnabled[userId] = isInlineAutofillEnabled
} }
override fun getBlockedAutofillUris(userId: String): List<String>? =
storedBlockedAutofillUris[userId]
override fun storeBlockedAutofillUris(
userId: String,
blockedAutofillUris: List<String>?,
) {
storedBlockedAutofillUris[userId] = blockedAutofillUris
}
//region Private helper functions //region Private helper functions
private fun getMutableVaultTimeoutActionsFlow( private fun getMutableVaultTimeoutActionsFlow(

View file

@ -356,6 +356,39 @@ class SettingsRepositoryTest {
assertTrue(fakeSettingsDiskSource.getInlineAutofillEnabled(userId = userId)!!) assertTrue(fakeSettingsDiskSource.getInlineAutofillEnabled(userId = userId)!!)
} }
@Test
fun `blockedAutofillUris should pull from and update SettingsDiskSource`() {
val userId = "userId"
fakeAuthDiskSource.userState = MOCK_USER_STATE
assertEquals(
emptyList<String>(),
settingsRepository.blockedAutofillUris,
)
// Updates to the disk source change the repository value.
fakeSettingsDiskSource.storeBlockedAutofillUris(
userId = userId,
blockedAutofillUris = listOf(
"https://www.example1.com",
"https://www.example2.com",
),
)
assertEquals(
listOf(
"https://www.example1.com",
"https://www.example2.com",
),
settingsRepository.blockedAutofillUris,
)
// Updates to the repository change the disk source value
settingsRepository.blockedAutofillUris = emptyList()
assertEquals(
emptyList<String>(),
fakeSettingsDiskSource.getBlockedAutofillUris(userId = userId),
)
}
@Test @Test
fun `getPullToRefreshEnabledFlow should react to changes in SettingsDiskSource`() = runTest { fun `getPullToRefreshEnabledFlow should react to changes in SettingsDiskSource`() = runTest {
val userId = "userId" val userId = "userId"