diff --git a/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/SettingsDiskSource.kt b/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/SettingsDiskSource.kt index ec3851980..705df855b 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/SettingsDiskSource.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/SettingsDiskSource.kt @@ -102,4 +102,17 @@ interface SettingsDiskSource { * Stores the given [isInlineAutofillEnabled] value for the given [userId]. */ fun storeInlineAutofillEnabled(userId: String, isInlineAutofillEnabled: Boolean?) + + /** + * Gets a list of blocked autofill URI's for the given [userId]. + */ + fun getBlockedAutofillUris(userId: String): List? + + /** + * Stores the list of [blockedAutofillUris] for the given [userId]. + */ + fun storeBlockedAutofillUris( + userId: String, + blockedAutofillUris: List?, + ) } diff --git a/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/SettingsDiskSourceImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/SettingsDiskSourceImpl.kt index bd4b4338d..d84de3862 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/SettingsDiskSourceImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/SettingsDiskSourceImpl.kt @@ -9,11 +9,14 @@ import com.x8bit.bitwarden.ui.platform.feature.settings.appearance.model.AppThem import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow 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_THEME_KEY = "$BASE_KEY:theme" private const val PULL_TO_REFRESH_KEY = "$BASE_KEY:syncOnRefresh" 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_TIME_IN_MINUTES_KEY = "$BASE_KEY:vaultTimeout" 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") class SettingsDiskSourceImpl( val sharedPreferences: SharedPreferences, + private val json: Json, ) : BaseDiskSource(sharedPreferences = sharedPreferences), SettingsDiskSource { private val mutableAppThemeFlow = @@ -87,6 +91,7 @@ class SettingsDiskSourceImpl( storeVaultTimeoutAction(userId = userId, vaultTimeoutAction = null) storePullToRefreshEnabled(userId = userId, isPullToRefreshEnabled = null) storeInlineAutofillEnabled(userId = userId, isInlineAutofillEnabled = null) + storeBlockedAutofillUris(userId = userId, blockedAutofillUris = null) } override fun getVaultTimeoutInMinutes(userId: String): Int? = @@ -152,6 +157,21 @@ class SettingsDiskSourceImpl( ) } + override fun getBlockedAutofillUris(userId: String): List? = + getString(key = "${BLOCKED_AUTOFILL_URIS_KEY}_$userId")?.let { + json.decodeFromString(it) + } + + override fun storeBlockedAutofillUris( + userId: String, + blockedAutofillUris: List?, + ) { + putString( + key = "${BLOCKED_AUTOFILL_URIS_KEY}_$userId", + value = blockedAutofillUris?.let { json.encodeToString(it) }, + ) + } + private fun getMutableVaultTimeoutActionFlow( userId: String, ): MutableSharedFlow = diff --git a/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/di/PlatformDiskModule.kt b/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/di/PlatformDiskModule.kt index e831a9711..ccc7efa44 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/di/PlatformDiskModule.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/disk/di/PlatformDiskModule.kt @@ -46,8 +46,10 @@ object PlatformDiskModule { @Singleton fun provideSettingsDiskSource( @UnencryptedPreferences sharedPreferences: SharedPreferences, + json: Json, ): SettingsDiskSource = SettingsDiskSourceImpl( sharedPreferences = sharedPreferences, + json = json, ) } diff --git a/app/src/main/java/com/x8bit/bitwarden/data/platform/repository/SettingsRepository.kt b/app/src/main/java/com/x8bit/bitwarden/data/platform/repository/SettingsRepository.kt index 50850fa2c..751160e11 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/platform/repository/SettingsRepository.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/platform/repository/SettingsRepository.kt @@ -57,6 +57,11 @@ interface SettingsRepository { */ var isInlineAutofillEnabled: Boolean + /** + * A list of blocked autofill URI's for the current user. + */ + var blockedAutofillUris: List + /** * Sets default values for various settings for the given [userId] if necessary. This is * typically used when logging into a new account. diff --git a/app/src/main/java/com/x8bit/bitwarden/data/platform/repository/SettingsRepositoryImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/platform/repository/SettingsRepositoryImpl.kt index 72c9a1f1a..d88481865 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/platform/repository/SettingsRepositoryImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/platform/repository/SettingsRepositoryImpl.kt @@ -109,6 +109,18 @@ class SettingsRepositoryImpl( ) } + override var blockedAutofillUris: List + 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) { // Set Vault Settings defaults if (!isVaultTimeoutActionSet(userId = userId)) { diff --git a/app/src/test/java/com/x8bit/bitwarden/data/platform/datasource/disk/SettingsDiskSourceTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/platform/datasource/disk/SettingsDiskSourceTest.kt index 317eded2b..003b0da99 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/platform/datasource/disk/SettingsDiskSourceTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/platform/datasource/disk/SettingsDiskSourceTest.kt @@ -3,6 +3,7 @@ package com.x8bit.bitwarden.data.platform.datasource.disk import androidx.core.content.edit import app.cash.turbine.test 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.ui.platform.feature.settings.appearance.model.AppLanguage import com.x8bit.bitwarden.ui.platform.feature.settings.appearance.model.AppTheme @@ -15,9 +16,11 @@ import org.junit.jupiter.api.Test class SettingsDiskSourceTest { private val fakeSharedPreferences = FakeSharedPreferences() + private val json = PlatformNetworkModule.providesJson() private val settingsDiskSource = SettingsDiskSourceImpl( sharedPreferences = fakeSharedPreferences, + json = json, ) @Test @@ -78,6 +81,10 @@ class SettingsDiskSourceTest { userId = userId, isInlineAutofillEnabled = true, ) + settingsDiskSource.storeBlockedAutofillUris( + userId = userId, + blockedAutofillUris = listOf("www.example.com"), + ) settingsDiskSource.clearData(userId = userId) @@ -85,6 +92,7 @@ class SettingsDiskSourceTest { assertNull(settingsDiskSource.getVaultTimeoutAction(userId = userId)) assertNull(settingsDiskSource.getPullToRefreshEnabled(userId = userId)) assertNull(settingsDiskSource.getInlineAutofillEnabled(userId = userId)) + assertNull(settingsDiskSource.getBlockedAutofillUris(userId = userId)) } @Test @@ -441,4 +449,62 @@ class SettingsDiskSourceTest { ) 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)), + ) + } } diff --git a/app/src/test/java/com/x8bit/bitwarden/data/platform/datasource/disk/util/FakeSettingsDiskSource.kt b/app/src/test/java/com/x8bit/bitwarden/data/platform/datasource/disk/util/FakeSettingsDiskSource.kt index 844f7d0f8..2ae6658f9 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/platform/datasource/disk/util/FakeSettingsDiskSource.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/platform/datasource/disk/util/FakeSettingsDiskSource.kt @@ -35,6 +35,7 @@ class FakeSettingsDiskSource : SettingsDiskSource { private val storedPullToRefreshEnabled = mutableMapOf() private val storedInlineAutofillEnabled = mutableMapOf() + private val storedBlockedAutofillUris = mutableMapOf?>() private var storedIsIconLoadingDisabled: Boolean? = null @@ -69,6 +70,7 @@ class FakeSettingsDiskSource : SettingsDiskSource { storedVaultTimeoutInMinutes.remove(userId) storedPullToRefreshEnabled.remove(userId) storedInlineAutofillEnabled.remove(userId) + storedBlockedAutofillUris.remove(userId) mutableVaultTimeoutActionsFlowMap.remove(userId) mutableVaultTimeoutInMinutesFlowMap.remove(userId) @@ -126,6 +128,16 @@ class FakeSettingsDiskSource : SettingsDiskSource { storedInlineAutofillEnabled[userId] = isInlineAutofillEnabled } + override fun getBlockedAutofillUris(userId: String): List? = + storedBlockedAutofillUris[userId] + + override fun storeBlockedAutofillUris( + userId: String, + blockedAutofillUris: List?, + ) { + storedBlockedAutofillUris[userId] = blockedAutofillUris + } + //region Private helper functions private fun getMutableVaultTimeoutActionsFlow( diff --git a/app/src/test/java/com/x8bit/bitwarden/data/platform/repository/SettingsRepositoryTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/platform/repository/SettingsRepositoryTest.kt index 7d75454c9..8c5ba19eb 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/platform/repository/SettingsRepositoryTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/platform/repository/SettingsRepositoryTest.kt @@ -356,6 +356,39 @@ class SettingsRepositoryTest { assertTrue(fakeSettingsDiskSource.getInlineAutofillEnabled(userId = userId)!!) } + @Test + fun `blockedAutofillUris should pull from and update SettingsDiskSource`() { + val userId = "userId" + fakeAuthDiskSource.userState = MOCK_USER_STATE + assertEquals( + emptyList(), + 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(), + fakeSettingsDiskSource.getBlockedAutofillUris(userId = userId), + ) + } + @Test fun `getPullToRefreshEnabledFlow should react to changes in SettingsDiskSource`() = runTest { val userId = "userId"