Add helper methods to disk source (#1311)

This commit is contained in:
David Perez 2024-04-26 11:46:37 -05:00 committed by Álison Fernandes
parent 12610f83eb
commit 70558499b4
8 changed files with 161 additions and 150 deletions

View file

@ -4,9 +4,7 @@ import android.content.SharedPreferences
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountTokensJson
import com.x8bit.bitwarden.data.auth.datasource.disk.model.PendingAuthRequestJson
import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson
import com.x8bit.bitwarden.data.platform.datasource.disk.BaseDiskSource.Companion.BASE_KEY
import com.x8bit.bitwarden.data.platform.datasource.disk.BaseEncryptedDiskSource
import com.x8bit.bitwarden.data.platform.datasource.disk.BaseEncryptedDiskSource.Companion.ENCRYPTED_BASE_KEY
import com.x8bit.bitwarden.data.platform.datasource.disk.legacy.LegacySecureStorageMigrator
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
import com.x8bit.bitwarden.data.platform.util.decodeFromStringOrNull
@ -18,28 +16,30 @@ import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import java.util.UUID
private const val ACCOUNT_TOKENS_KEY = "$ENCRYPTED_BASE_KEY:accountTokens"
private const val BIOMETRICS_UNLOCK_KEY = "$ENCRYPTED_BASE_KEY:userKeyBiometricUnlock"
private const val USER_AUTO_UNLOCK_KEY_KEY = "$ENCRYPTED_BASE_KEY:userKeyAutoUnlock"
private const val DEVICE_KEY_KEY = "$ENCRYPTED_BASE_KEY:deviceKey"
private const val PENDING_ADMIN_AUTH_REQUEST_KEY = "$ENCRYPTED_BASE_KEY:pendingAdminAuthRequest"
// These keys should be encrypted
private const val ACCOUNT_TOKENS_KEY = "accountTokens"
private const val BIOMETRICS_UNLOCK_KEY = "userKeyBiometricUnlock"
private const val USER_AUTO_UNLOCK_KEY_KEY = "userKeyAutoUnlock"
private const val DEVICE_KEY_KEY = "deviceKey"
private const val PENDING_ADMIN_AUTH_REQUEST_KEY = "pendingAdminAuthRequest"
private const val UNIQUE_APP_ID_KEY = "$BASE_KEY:appId"
private const val REMEMBERED_EMAIL_ADDRESS_KEY = "$BASE_KEY:rememberedEmail"
private const val REMEMBERED_ORG_IDENTIFIER_KEY = "$BASE_KEY:rememberedOrgIdentifier"
private const val STATE_KEY = "$BASE_KEY:state"
private const val LAST_ACTIVE_TIME_KEY = "$BASE_KEY:lastActiveTime"
private const val INVALID_UNLOCK_ATTEMPTS_KEY = "$BASE_KEY:invalidUnlockAttempts"
private const val MASTER_KEY_ENCRYPTION_USER_KEY = "$BASE_KEY:masterKeyEncryptedUserKey"
private const val MASTER_KEY_ENCRYPTION_PRIVATE_KEY = "$BASE_KEY:encPrivateKey"
private const val PIN_PROTECTED_USER_KEY_KEY = "$BASE_KEY:pinKeyEncryptedUserKey"
private const val ENCRYPTED_PIN_KEY = "$BASE_KEY:protectedPin"
private const val ORGANIZATIONS_KEY = "$BASE_KEY:organizations"
private const val ORGANIZATION_KEYS_KEY = "$BASE_KEY:encOrgKeys"
private const val TWO_FACTOR_TOKEN_KEY = "$BASE_KEY:twoFactorToken"
private const val MASTER_PASSWORD_HASH_KEY = "$BASE_KEY:keyHash"
private const val POLICIES_KEY = "$BASE_KEY:policies"
private const val SHOULD_TRUST_DEVICE_KEY = "$BASE_KEY:shouldTrustDevice"
// These keys should not be encrypted
private const val UNIQUE_APP_ID_KEY = "appId"
private const val REMEMBERED_EMAIL_ADDRESS_KEY = "rememberedEmail"
private const val REMEMBERED_ORG_IDENTIFIER_KEY = "rememberedOrgIdentifier"
private const val STATE_KEY = "state"
private const val LAST_ACTIVE_TIME_KEY = "lastActiveTime"
private const val INVALID_UNLOCK_ATTEMPTS_KEY = "invalidUnlockAttempts"
private const val MASTER_KEY_ENCRYPTION_USER_KEY = "masterKeyEncryptedUserKey"
private const val MASTER_KEY_ENCRYPTION_PRIVATE_KEY = "encPrivateKey"
private const val PIN_PROTECTED_USER_KEY_KEY = "pinKeyEncryptedUserKey"
private const val ENCRYPTED_PIN_KEY = "protectedPin"
private const val ORGANIZATIONS_KEY = "organizations"
private const val ORGANIZATION_KEYS_KEY = "encOrgKeys"
private const val TWO_FACTOR_TOKEN_KEY = "twoFactorToken"
private const val MASTER_PASSWORD_HASH_KEY = "keyHash"
private const val POLICIES_KEY = "policies"
private const val SHOULD_TRUST_DEVICE_KEY = "shouldTrustDevice"
/**
* Primary implementation of [AuthDiskSource].
@ -130,61 +130,63 @@ class AuthDiskSourceImpl(
}
override fun getShouldTrustDevice(userId: String): Boolean =
requireNotNull(getBoolean(key = "${SHOULD_TRUST_DEVICE_KEY}_$userId", default = false))
requireNotNull(
getBoolean(key = SHOULD_TRUST_DEVICE_KEY.appendIdentifier(userId), default = false),
)
override fun storeShouldTrustDevice(userId: String, shouldTrustDevice: Boolean?) {
putBoolean("${SHOULD_TRUST_DEVICE_KEY}_$userId", shouldTrustDevice)
putBoolean(SHOULD_TRUST_DEVICE_KEY.appendIdentifier(userId), shouldTrustDevice)
}
override fun getLastActiveTimeMillis(userId: String): Long? =
getLong(key = "${LAST_ACTIVE_TIME_KEY}_$userId")
getLong(key = LAST_ACTIVE_TIME_KEY.appendIdentifier(userId))
override fun storeLastActiveTimeMillis(
userId: String,
lastActiveTimeMillis: Long?,
) {
putLong(
key = "${LAST_ACTIVE_TIME_KEY}_$userId",
key = LAST_ACTIVE_TIME_KEY.appendIdentifier(userId),
value = lastActiveTimeMillis,
)
}
override fun getInvalidUnlockAttempts(userId: String): Int? =
getInt(key = "${INVALID_UNLOCK_ATTEMPTS_KEY}_$userId")
getInt(key = INVALID_UNLOCK_ATTEMPTS_KEY.appendIdentifier(userId))
override fun storeInvalidUnlockAttempts(
userId: String,
invalidUnlockAttempts: Int?,
) {
putInt(
key = "${INVALID_UNLOCK_ATTEMPTS_KEY}_$userId",
key = INVALID_UNLOCK_ATTEMPTS_KEY.appendIdentifier(userId),
value = invalidUnlockAttempts,
)
}
override fun getUserKey(userId: String): String? =
getString(key = "${MASTER_KEY_ENCRYPTION_USER_KEY}_$userId")
getString(key = MASTER_KEY_ENCRYPTION_USER_KEY.appendIdentifier(userId))
override fun storeUserKey(userId: String, userKey: String?) {
putString(
key = "${MASTER_KEY_ENCRYPTION_USER_KEY}_$userId",
key = MASTER_KEY_ENCRYPTION_USER_KEY.appendIdentifier(userId),
value = userKey,
)
}
override fun getPrivateKey(userId: String): String? =
getString(key = "${MASTER_KEY_ENCRYPTION_PRIVATE_KEY}_$userId")
getString(key = MASTER_KEY_ENCRYPTION_PRIVATE_KEY.appendIdentifier(userId))
override fun storePrivateKey(userId: String, privateKey: String?) {
putString(
key = "${MASTER_KEY_ENCRYPTION_PRIVATE_KEY}_$userId",
key = MASTER_KEY_ENCRYPTION_PRIVATE_KEY.appendIdentifier(userId),
value = privateKey,
)
}
override fun getUserAutoUnlockKey(userId: String): String? =
getEncryptedString(
key = "${USER_AUTO_UNLOCK_KEY_KEY}_$userId",
key = USER_AUTO_UNLOCK_KEY_KEY.appendIdentifier(userId),
default = null,
)
@ -193,26 +195,26 @@ class AuthDiskSourceImpl(
userAutoUnlockKey: String?,
) {
putEncryptedString(
key = "${USER_AUTO_UNLOCK_KEY_KEY}_$userId",
key = USER_AUTO_UNLOCK_KEY_KEY.appendIdentifier(userId),
value = userAutoUnlockKey,
)
}
override fun getDeviceKey(
userId: String,
): String? = getEncryptedString(key = "${DEVICE_KEY_KEY}_$userId")
): String? = getEncryptedString(key = DEVICE_KEY_KEY.appendIdentifier(userId))
override fun storeDeviceKey(
userId: String,
deviceKey: String?,
) {
putEncryptedString(key = "${DEVICE_KEY_KEY}_$userId", value = deviceKey)
putEncryptedString(key = DEVICE_KEY_KEY.appendIdentifier(userId), value = deviceKey)
}
override fun getPendingAuthRequest(
userId: String,
): PendingAuthRequestJson? =
getEncryptedString(key = "${PENDING_ADMIN_AUTH_REQUEST_KEY}_$userId")
getEncryptedString(key = PENDING_ADMIN_AUTH_REQUEST_KEY.appendIdentifier(userId))
?.let { json.decodeFromStringOrNull(it) }
override fun storePendingAuthRequest(
@ -220,27 +222,27 @@ class AuthDiskSourceImpl(
pendingAuthRequest: PendingAuthRequestJson?,
) {
putEncryptedString(
key = "${PENDING_ADMIN_AUTH_REQUEST_KEY}_$userId",
key = PENDING_ADMIN_AUTH_REQUEST_KEY.appendIdentifier(userId),
value = pendingAuthRequest?.let { json.encodeToString(it) },
)
}
override fun getUserBiometricUnlockKey(userId: String): String? =
getEncryptedString(key = "${BIOMETRICS_UNLOCK_KEY}_$userId")
getEncryptedString(key = BIOMETRICS_UNLOCK_KEY.appendIdentifier(userId))
override fun storeUserBiometricUnlockKey(
userId: String,
biometricsKey: String?,
) {
putEncryptedString(
key = "${BIOMETRICS_UNLOCK_KEY}_$userId",
key = BIOMETRICS_UNLOCK_KEY.appendIdentifier(userId),
value = biometricsKey,
)
}
override fun getPinProtectedUserKey(userId: String): String? =
inMemoryPinProtectedUserKeys[userId]
?: getString(key = "${PIN_PROTECTED_USER_KEY_KEY}_$userId")
?: getString(key = PIN_PROTECTED_USER_KEY_KEY.appendIdentifier(userId))
override fun storePinProtectedUserKey(
userId: String,
@ -250,36 +252,36 @@ class AuthDiskSourceImpl(
inMemoryPinProtectedUserKeys[userId] = pinProtectedUserKey
if (inMemoryOnly) return
putString(
key = "${PIN_PROTECTED_USER_KEY_KEY}_$userId",
key = PIN_PROTECTED_USER_KEY_KEY.appendIdentifier(userId),
value = pinProtectedUserKey,
)
}
override fun getTwoFactorToken(email: String): String? =
getString(key = "${TWO_FACTOR_TOKEN_KEY}_$email")
getString(key = TWO_FACTOR_TOKEN_KEY.appendIdentifier(email))
override fun storeTwoFactorToken(email: String, twoFactorToken: String?) {
putString(
key = "${TWO_FACTOR_TOKEN_KEY}_$email",
key = TWO_FACTOR_TOKEN_KEY.appendIdentifier(email),
value = twoFactorToken,
)
}
override fun getEncryptedPin(userId: String): String? =
getString(key = "${ENCRYPTED_PIN_KEY}_$userId")
getString(key = ENCRYPTED_PIN_KEY.appendIdentifier(userId))
override fun storeEncryptedPin(
userId: String,
encryptedPin: String?,
) {
putString(
key = "${ENCRYPTED_PIN_KEY}_$userId",
key = ENCRYPTED_PIN_KEY.appendIdentifier(userId),
value = encryptedPin,
)
}
override fun getOrganizationKeys(userId: String): Map<String, String>? =
getString(key = "${ORGANIZATION_KEYS_KEY}_$userId")
getString(key = ORGANIZATION_KEYS_KEY.appendIdentifier(userId))
?.let { json.decodeFromStringOrNull(it) }
override fun storeOrganizationKeys(
@ -287,7 +289,7 @@ class AuthDiskSourceImpl(
organizationKeys: Map<String, String>?,
) {
putString(
key = "${ORGANIZATION_KEYS_KEY}_$userId",
key = ORGANIZATION_KEYS_KEY.appendIdentifier(userId),
value = organizationKeys?.let { json.encodeToString(it) },
)
}
@ -295,7 +297,7 @@ class AuthDiskSourceImpl(
override fun getOrganizations(
userId: String,
): List<SyncResponseJson.Profile.Organization>? =
getString(key = "${ORGANIZATIONS_KEY}_$userId")
getString(key = ORGANIZATIONS_KEY.appendIdentifier(userId))
?.let {
// The organizations are stored as a map
val organizationMap: Map<String, SyncResponseJson.Profile.Organization>? =
@ -314,7 +316,7 @@ class AuthDiskSourceImpl(
organizations: List<SyncResponseJson.Profile.Organization>?,
) {
putString(
key = "${ORGANIZATIONS_KEY}_$userId",
key = ORGANIZATIONS_KEY.appendIdentifier(userId),
value = organizations?.let { nonNullOrganizations ->
// The organizations are stored as a map
val organizationsMap = nonNullOrganizations.associateBy { it.id }
@ -325,14 +327,14 @@ class AuthDiskSourceImpl(
}
override fun getMasterPasswordHash(userId: String): String? =
getString(key = "${MASTER_PASSWORD_HASH_KEY}_$userId")
getString(key = MASTER_PASSWORD_HASH_KEY.appendIdentifier(userId))
override fun storeMasterPasswordHash(userId: String, passwordHash: String?) {
putString(key = "${MASTER_PASSWORD_HASH_KEY}_$userId", value = passwordHash)
putString(key = MASTER_PASSWORD_HASH_KEY.appendIdentifier(userId), value = passwordHash)
}
override fun getPolicies(userId: String): List<SyncResponseJson.Policy>? =
getString(key = "${POLICIES_KEY}_$userId")
getString(key = POLICIES_KEY.appendIdentifier(userId))
?.let {
// The policies are stored as a map.
val policiesMap: Map<String, SyncResponseJson.Policy>? =
@ -348,7 +350,7 @@ class AuthDiskSourceImpl(
override fun storePolicies(userId: String, policies: List<SyncResponseJson.Policy>?) {
putString(
key = "${POLICIES_KEY}_$userId",
key = POLICIES_KEY.appendIdentifier(userId),
value = policies?.let { nonNullPolicies ->
// The policies are stored as a map.
val policiesMap = nonNullPolicies.associateBy { it.id }
@ -359,7 +361,7 @@ class AuthDiskSourceImpl(
}
override fun getAccountTokens(userId: String): AccountTokensJson? =
getEncryptedString(key = "${ACCOUNT_TOKENS_KEY}_$userId")
getEncryptedString(key = ACCOUNT_TOKENS_KEY.appendIdentifier(userId))
?.let { json.decodeFromStringOrNull(it) }
override fun getAccountTokensFlow(userId: String): Flow<AccountTokensJson?> =
@ -368,7 +370,7 @@ class AuthDiskSourceImpl(
override fun storeAccountTokens(userId: String, accountTokens: AccountTokensJson?) {
putEncryptedString(
key = "${ACCOUNT_TOKENS_KEY}_$userId",
key = ACCOUNT_TOKENS_KEY.appendIdentifier(userId),
value = accountTokens?.let { json.encodeToString(it) },
)
getMutableAccountTokensFlow(userId = userId).tryEmit(accountTokens)

View file

@ -18,8 +18,8 @@ abstract class BaseDiskSource(
key: String,
default: Boolean? = null,
): Boolean? =
if (sharedPreferences.contains(key)) {
sharedPreferences.getBoolean(key, false)
if (sharedPreferences.contains(key.withBase())) {
sharedPreferences.getBoolean(key.withBase(), false)
} else {
// Make sure we can return a null value as a default if necessary
default
@ -35,9 +35,9 @@ abstract class BaseDiskSource(
): Unit =
sharedPreferences.edit {
if (value != null) {
putBoolean(key, value)
putBoolean(key.withBase(), value)
} else {
remove(key)
remove(key.withBase())
}
}
@ -49,8 +49,8 @@ abstract class BaseDiskSource(
key: String,
default: Int? = null,
): Int? =
if (sharedPreferences.contains(key)) {
sharedPreferences.getInt(key, 0)
if (sharedPreferences.contains(key.withBase())) {
sharedPreferences.getInt(key.withBase(), 0)
} else {
// Make sure we can return a null value as a default if necessary
default
@ -66,9 +66,9 @@ abstract class BaseDiskSource(
): Unit =
sharedPreferences.edit {
if (value != null) {
putInt(key, value)
putInt(key.withBase(), value)
} else {
remove(key)
remove(key.withBase())
}
}
@ -80,8 +80,8 @@ abstract class BaseDiskSource(
key: String,
default: Long? = null,
): Long? =
if (sharedPreferences.contains(key)) {
sharedPreferences.getLong(key, 0)
if (sharedPreferences.contains(key.withBase())) {
sharedPreferences.getLong(key.withBase(), 0)
} else {
// Make sure we can return a null value as a default if necessary
default
@ -97,31 +97,34 @@ abstract class BaseDiskSource(
): Unit =
sharedPreferences.edit {
if (value != null) {
putLong(key, value)
putLong(key.withBase(), value)
} else {
remove(key)
remove(key.withBase())
}
}
protected fun getString(
key: String,
default: String? = null,
): String? = sharedPreferences.getString(key, default)
): String? = sharedPreferences.getString(key.withBase(), default)
protected fun putString(
key: String,
value: String?,
): Unit = sharedPreferences.edit { putString(key, value) }
): Unit = sharedPreferences.edit { putString(key.withBase(), value) }
protected fun removeWithPrefix(prefix: String) {
sharedPreferences
.all
.keys
.filter { it.startsWith(prefix) }
.filter { it.startsWith(prefix.withBase()) }
.forEach { sharedPreferences.edit { remove(it) } }
}
companion object {
const val BASE_KEY: String = "bwPreferencesStorage"
}
protected fun String.appendIdentifier(identifier: String): String = "${this}_$identifier"
}
/**
* Helper method for prepending the key with the appropriate base storage key.
*/
private fun String.withBase(): String = "bwPreferencesStorage:$this"

View file

@ -18,14 +18,15 @@ abstract class BaseEncryptedDiskSource(
protected fun getEncryptedString(
key: String,
default: String? = null,
): String? = encryptedSharedPreferences.getString(key, default)
): String? = encryptedSharedPreferences.getString(key.withBase(), default)
protected fun putEncryptedString(
key: String,
value: String?,
): Unit = encryptedSharedPreferences.edit { putString(key, value) }
companion object {
const val ENCRYPTED_BASE_KEY: String = "bwSecureStorage"
}
): Unit = encryptedSharedPreferences.edit { putString(key.withBase(), value) }
}
/**
* Helper method for prepending the key with the appropriate base storage key.
*/
private fun String.withBase(): String = "bwSecureStorage:$this"

View file

@ -2,7 +2,6 @@ package com.x8bit.bitwarden.data.platform.datasource.disk
import android.content.SharedPreferences
import com.x8bit.bitwarden.data.auth.datasource.disk.model.EnvironmentUrlDataJson
import com.x8bit.bitwarden.data.platform.datasource.disk.BaseDiskSource.Companion.BASE_KEY
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
import com.x8bit.bitwarden.data.platform.util.decodeFromStringOrNull
import kotlinx.coroutines.flow.Flow
@ -10,7 +9,7 @@ import kotlinx.coroutines.flow.onSubscription
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
private const val PRE_AUTH_URLS_KEY = "$BASE_KEY:preAuthEnvironmentUrls"
private const val PRE_AUTH_URLS_KEY = "preAuthEnvironmentUrls"
/**
* Primary implementation of [EnvironmentDiskSource].

View file

@ -1,14 +1,13 @@
package com.x8bit.bitwarden.data.platform.datasource.disk
import android.content.SharedPreferences
import com.x8bit.bitwarden.data.platform.datasource.disk.BaseDiskSource.Companion.BASE_KEY
import com.x8bit.bitwarden.data.platform.util.getBinaryLongFromZoneDateTime
import com.x8bit.bitwarden.data.platform.util.getZoneDateTimeFromBinaryLong
import java.time.ZonedDateTime
private const val CURRENT_PUSH_TOKEN_KEY = "${BASE_KEY}:pushCurrentToken"
private const val LAST_REGISTRATION_DATE_KEY = "${BASE_KEY}:pushLastRegistrationDate"
private const val REGISTERED_PUSH_TOKEN_KEY = "${BASE_KEY}:pushRegisteredToken"
private const val CURRENT_PUSH_TOKEN_KEY = "pushCurrentToken"
private const val LAST_REGISTRATION_DATE_KEY = "pushLastRegistrationDate"
private const val REGISTERED_PUSH_TOKEN_KEY = "pushRegisteredToken"
/**
* Primary implementation of [PushDiskSource].
@ -32,17 +31,17 @@ class PushDiskSourceImpl(
}
override fun getCurrentPushToken(userId: String): String? {
return getString("${CURRENT_PUSH_TOKEN_KEY}_$userId")
return getString(CURRENT_PUSH_TOKEN_KEY.appendIdentifier(userId))
}
override fun getLastPushTokenRegistrationDate(userId: String): ZonedDateTime? {
return getLong("${LAST_REGISTRATION_DATE_KEY}_$userId", null)
return getLong(LAST_REGISTRATION_DATE_KEY.appendIdentifier(userId), null)
?.let { getZoneDateTimeFromBinaryLong(it) }
}
override fun storeCurrentPushToken(userId: String, pushToken: String?) {
putString(
key = "${CURRENT_PUSH_TOKEN_KEY}_$userId",
key = CURRENT_PUSH_TOKEN_KEY.appendIdentifier(userId),
value = pushToken,
)
}
@ -52,7 +51,7 @@ class PushDiskSourceImpl(
registrationDate: ZonedDateTime?,
) {
putLong(
key = "${LAST_REGISTRATION_DATE_KEY}_$userId",
key = LAST_REGISTRATION_DATE_KEY.appendIdentifier(userId),
value = registrationDate?.let { getBinaryLongFromZoneDateTime(registrationDate) },
)
}

View file

@ -1,7 +1,6 @@
package com.x8bit.bitwarden.data.platform.datasource.disk
import android.content.SharedPreferences
import com.x8bit.bitwarden.data.platform.datasource.disk.BaseDiskSource.Companion.BASE_KEY
import com.x8bit.bitwarden.data.platform.repository.model.UriMatchType
import com.x8bit.bitwarden.data.platform.repository.model.VaultTimeoutAction
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
@ -15,25 +14,25 @@ import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import java.time.Instant
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_LAST_SYNC_TIME = "$BASE_KEY:vaultLastSyncTime"
private const val VAULT_TIMEOUT_ACTION_KEY = "$BASE_KEY:vaultTimeoutAction"
private const val VAULT_TIME_IN_MINUTES_KEY = "$BASE_KEY:vaultTimeout"
private const val DEFAULT_URI_MATCH_TYPE_KEY = "$BASE_KEY:defaultUriMatch"
private const val DISABLE_AUTO_TOTP_COPY_KEY = "$BASE_KEY:disableAutoTotpCopy"
private const val DISABLE_AUTOFILL_SAVE_PROMPT_KEY = "$BASE_KEY:autofillDisableSavePrompt"
private const val DISABLE_ICON_LOADING_KEY = "$BASE_KEY:disableFavicon"
private const val APPROVE_PASSWORDLESS_LOGINS_KEY = "$BASE_KEY:approvePasswordlessLogins"
private const val SCREEN_CAPTURE_ALLOW_KEY = "$BASE_KEY:screenCaptureAllowed"
private const val SYSTEM_BIOMETRIC_INTEGRITY_SOURCE_KEY = "$BASE_KEY:biometricIntegritySource"
private const val ACCOUNT_BIOMETRIC_INTEGRITY_VALID_KEY = "$BASE_KEY:accountBiometricIntegrityValid"
private const val CRASH_LOGGING_ENABLED_KEY = "$BASE_KEY:crashLoggingEnabled"
private const val CLEAR_CLIPBOARD_INTERVAL_KEY = "$BASE_KEY:clearClipboard"
private const val INITIAL_AUTOFILL_DIALOG_SHOWN = "$BASE_KEY:addSitePromptShown"
private const val APP_LANGUAGE_KEY = "appLocale"
private const val APP_THEME_KEY = "theme"
private const val PULL_TO_REFRESH_KEY = "syncOnRefresh"
private const val INLINE_AUTOFILL_ENABLED_KEY = "inlineAutofillEnabled"
private const val BLOCKED_AUTOFILL_URIS_KEY = "autofillBlacklistedUris"
private const val VAULT_LAST_SYNC_TIME = "vaultLastSyncTime"
private const val VAULT_TIMEOUT_ACTION_KEY = "vaultTimeoutAction"
private const val VAULT_TIME_IN_MINUTES_KEY = "vaultTimeout"
private const val DEFAULT_URI_MATCH_TYPE_KEY = "defaultUriMatch"
private const val DISABLE_AUTO_TOTP_COPY_KEY = "disableAutoTotpCopy"
private const val DISABLE_AUTOFILL_SAVE_PROMPT_KEY = "autofillDisableSavePrompt"
private const val DISABLE_ICON_LOADING_KEY = "disableFavicon"
private const val APPROVE_PASSWORDLESS_LOGINS_KEY = "approvePasswordlessLogins"
private const val SCREEN_CAPTURE_ALLOW_KEY = "screenCaptureAllowed"
private const val SYSTEM_BIOMETRIC_INTEGRITY_SOURCE_KEY = "biometricIntegritySource"
private const val ACCOUNT_BIOMETRIC_INTEGRITY_VALID_KEY = "accountBiometricIntegrityValid"
private const val CRASH_LOGGING_ENABLED_KEY = "crashLoggingEnabled"
private const val CLEAR_CLIPBOARD_INTERVAL_KEY = "clearClipboard"
private const val INITIAL_AUTOFILL_DIALOG_SHOWN = "addSitePromptShown"
/**
* Primary implementation of [SettingsDiskSource].
@ -44,8 +43,7 @@ class SettingsDiskSourceImpl(
private val json: Json,
) : BaseDiskSource(sharedPreferences = sharedPreferences),
SettingsDiskSource {
private val mutableAppThemeFlow =
bufferedMutableSharedFlow<AppTheme>(replay = 1)
private val mutableAppThemeFlow = bufferedMutableSharedFlow<AppTheme>(replay = 1)
private val mutableLastSyncFlowMap = mutableMapOf<String, MutableSharedFlow<Instant?>>()
@ -58,11 +56,9 @@ class SettingsDiskSourceImpl(
private val mutablePullToRefreshEnabledFlowMap =
mutableMapOf<String, MutableSharedFlow<Boolean?>>()
private val mutableIsIconLoadingDisabledFlow =
bufferedMutableSharedFlow<Boolean?>()
private val mutableIsIconLoadingDisabledFlow = bufferedMutableSharedFlow<Boolean?>()
private val mutableIsCrashLoggingEnabledFlow =
bufferedMutableSharedFlow<Boolean?>()
private val mutableIsCrashLoggingEnabledFlow = bufferedMutableSharedFlow<Boolean?>()
private val mutableScreenCaptureAllowedFlowMap =
mutableMapOf<String, MutableSharedFlow<Boolean?>>()
@ -149,7 +145,7 @@ class SettingsDiskSourceImpl(
)
storeLastSyncTime(userId = userId, lastSyncTime = null)
storeClearClipboardFrequencySeconds(userId = userId, frequency = null)
removeWithPrefix(prefix = "${ACCOUNT_BIOMETRIC_INTEGRITY_VALID_KEY}_$userId")
removeWithPrefix(prefix = ACCOUNT_BIOMETRIC_INTEGRITY_VALID_KEY.appendIdentifier(userId))
// The following are intentionally not cleared so they can be
// restored after logging out and back in:
@ -161,7 +157,9 @@ class SettingsDiskSourceImpl(
systemBioIntegrityState: String,
): Boolean? =
getBoolean(
key = "${ACCOUNT_BIOMETRIC_INTEGRITY_VALID_KEY}_${userId}_$systemBioIntegrityState",
key = ACCOUNT_BIOMETRIC_INTEGRITY_VALID_KEY
.appendIdentifier(userId)
.appendIdentifier(systemBioIntegrityState),
)
override fun storeAccountBiometricIntegrityValidity(
@ -170,30 +168,33 @@ class SettingsDiskSourceImpl(
value: Boolean?,
) {
putBoolean(
key = "${ACCOUNT_BIOMETRIC_INTEGRITY_VALID_KEY}_${userId}_$systemBioIntegrityState",
key = ACCOUNT_BIOMETRIC_INTEGRITY_VALID_KEY
.appendIdentifier(userId)
.appendIdentifier(systemBioIntegrityState),
value = value,
)
}
override fun getAutoCopyTotpDisabled(userId: String): Boolean? =
getBoolean(key = "${DISABLE_AUTO_TOTP_COPY_KEY}_$userId")
getBoolean(key = DISABLE_AUTO_TOTP_COPY_KEY.appendIdentifier(userId))
override fun storeAutoCopyTotpDisabled(
userId: String,
isAutomaticallyCopyTotpDisabled: Boolean?,
) {
putBoolean(
key = "${DISABLE_AUTO_TOTP_COPY_KEY}_$userId",
key = DISABLE_AUTO_TOTP_COPY_KEY.appendIdentifier(userId),
value = isAutomaticallyCopyTotpDisabled,
)
}
override fun getLastSyncTime(userId: String): Instant? =
getLong(key = "${VAULT_LAST_SYNC_TIME}_$userId")?.let { Instant.ofEpochMilli(it) }
getLong(key = VAULT_LAST_SYNC_TIME.appendIdentifier(userId))
?.let { Instant.ofEpochMilli(it) }
override fun storeLastSyncTime(userId: String, lastSyncTime: Instant?) {
putLong(
key = "${VAULT_LAST_SYNC_TIME}_$userId",
key = VAULT_LAST_SYNC_TIME.appendIdentifier(userId),
value = lastSyncTime?.toEpochMilli(),
)
getMutableLastSyncFlow(userId = userId).tryEmit(lastSyncTime)
@ -204,7 +205,7 @@ class SettingsDiskSourceImpl(
.onSubscription { emit(getLastSyncTime(userId = userId)) }
override fun getVaultTimeoutInMinutes(userId: String): Int? =
getInt(key = "${VAULT_TIME_IN_MINUTES_KEY}_$userId")
getInt(key = VAULT_TIME_IN_MINUTES_KEY.appendIdentifier(userId))
override fun getVaultTimeoutInMinutesFlow(userId: String): Flow<Int?> =
getMutableVaultTimeoutInMinutesFlow(userId = userId)
@ -215,24 +216,24 @@ class SettingsDiskSourceImpl(
vaultTimeoutInMinutes: Int?,
) {
putInt(
key = "${VAULT_TIME_IN_MINUTES_KEY}_$userId",
key = VAULT_TIME_IN_MINUTES_KEY.appendIdentifier(userId),
value = vaultTimeoutInMinutes,
)
getMutableVaultTimeoutInMinutesFlow(userId = userId).tryEmit(vaultTimeoutInMinutes)
}
override fun getClearClipboardFrequencySeconds(userId: String): Int? =
getInt(key = "${CLEAR_CLIPBOARD_INTERVAL_KEY}_$userId")
getInt(key = CLEAR_CLIPBOARD_INTERVAL_KEY.appendIdentifier(userId))
override fun storeClearClipboardFrequencySeconds(userId: String, frequency: Int?) {
putInt(
key = "${CLEAR_CLIPBOARD_INTERVAL_KEY}_$userId",
key = CLEAR_CLIPBOARD_INTERVAL_KEY.appendIdentifier(userId),
value = frequency,
)
}
override fun getVaultTimeoutAction(userId: String): VaultTimeoutAction? =
getString(key = "${VAULT_TIMEOUT_ACTION_KEY}_$userId")?.let { storedValue ->
getString(key = VAULT_TIMEOUT_ACTION_KEY.appendIdentifier(userId))?.let { storedValue ->
VaultTimeoutAction.entries.firstOrNull { storedValue == it.value }
}
@ -245,14 +246,14 @@ class SettingsDiskSourceImpl(
vaultTimeoutAction: VaultTimeoutAction?,
) {
putString(
key = "${VAULT_TIMEOUT_ACTION_KEY}_$userId",
key = VAULT_TIMEOUT_ACTION_KEY.appendIdentifier(userId),
value = vaultTimeoutAction?.value,
)
getMutableVaultTimeoutActionFlow(userId = userId).tryEmit(vaultTimeoutAction)
}
override fun getDefaultUriMatchType(userId: String): UriMatchType? =
getInt(key = "${DEFAULT_URI_MATCH_TYPE_KEY}_$userId")?.let { storedValue ->
getInt(key = DEFAULT_URI_MATCH_TYPE_KEY.appendIdentifier(userId))?.let { storedValue ->
UriMatchType.entries.find { it.value == storedValue }
}
@ -261,51 +262,54 @@ class SettingsDiskSourceImpl(
uriMatchType: UriMatchType?,
) {
putInt(
key = "${DEFAULT_URI_MATCH_TYPE_KEY}_$userId",
key = DEFAULT_URI_MATCH_TYPE_KEY.appendIdentifier(userId),
value = uriMatchType?.value,
)
}
override fun getAutofillSavePromptDisabled(userId: String): Boolean? =
getBoolean(key = "${DISABLE_AUTOFILL_SAVE_PROMPT_KEY}_$userId")
getBoolean(key = DISABLE_AUTOFILL_SAVE_PROMPT_KEY.appendIdentifier(userId))
override fun storeAutofillSavePromptDisabled(
userId: String,
isAutofillSavePromptDisabled: Boolean?,
) {
putBoolean(
key = "${DISABLE_AUTOFILL_SAVE_PROMPT_KEY}_$userId",
key = DISABLE_AUTOFILL_SAVE_PROMPT_KEY.appendIdentifier(userId),
value = isAutofillSavePromptDisabled,
)
}
override fun getPullToRefreshEnabled(userId: String): Boolean? =
getBoolean(key = "${PULL_TO_REFRESH_KEY}_$userId")
getBoolean(key = PULL_TO_REFRESH_KEY.appendIdentifier(userId))
override fun getPullToRefreshEnabledFlow(userId: String): Flow<Boolean?> =
getMutablePullToRefreshEnabledFlowMap(userId = userId)
.onSubscription { emit(getPullToRefreshEnabled(userId = userId)) }
override fun storePullToRefreshEnabled(userId: String, isPullToRefreshEnabled: Boolean?) {
putBoolean(key = "${PULL_TO_REFRESH_KEY}_$userId", value = isPullToRefreshEnabled)
putBoolean(
key = PULL_TO_REFRESH_KEY.appendIdentifier(userId),
value = isPullToRefreshEnabled,
)
getMutablePullToRefreshEnabledFlowMap(userId = userId).tryEmit(isPullToRefreshEnabled)
}
override fun getInlineAutofillEnabled(userId: String): Boolean? =
getBoolean(key = "${INLINE_AUTOFILL_ENABLED_KEY}_$userId")
getBoolean(key = INLINE_AUTOFILL_ENABLED_KEY.appendIdentifier(userId))
override fun storeInlineAutofillEnabled(
userId: String,
isInlineAutofillEnabled: Boolean?,
) {
putBoolean(
key = "${INLINE_AUTOFILL_ENABLED_KEY}_$userId",
key = INLINE_AUTOFILL_ENABLED_KEY.appendIdentifier(userId),
value = isInlineAutofillEnabled,
)
}
override fun getBlockedAutofillUris(userId: String): List<String>? =
getString(key = "${BLOCKED_AUTOFILL_URIS_KEY}_$userId")?.let {
getString(key = BLOCKED_AUTOFILL_URIS_KEY.appendIdentifier(userId))?.let {
json.decodeFromStringOrNull(it)
}
@ -314,7 +318,7 @@ class SettingsDiskSourceImpl(
blockedAutofillUris: List<String>?,
) {
putString(
key = "${BLOCKED_AUTOFILL_URIS_KEY}_$userId",
key = BLOCKED_AUTOFILL_URIS_KEY.appendIdentifier(userId),
value = blockedAutofillUris?.let { json.encodeToString(it) },
)
}
@ -353,7 +357,7 @@ class SettingsDiskSourceImpl(
}
override fun getApprovePasswordlessLoginsEnabled(userId: String): Boolean? {
return getBoolean(key = "${APPROVE_PASSWORDLESS_LOGINS_KEY}_$userId")
return getBoolean(key = APPROVE_PASSWORDLESS_LOGINS_KEY.appendIdentifier(userId))
}
override fun storeApprovePasswordlessLoginsEnabled(
@ -361,13 +365,13 @@ class SettingsDiskSourceImpl(
isApprovePasswordlessLoginsEnabled: Boolean?,
) {
putBoolean(
key = "${APPROVE_PASSWORDLESS_LOGINS_KEY}_$userId",
key = APPROVE_PASSWORDLESS_LOGINS_KEY.appendIdentifier(userId),
value = isApprovePasswordlessLoginsEnabled,
)
}
override fun getScreenCaptureAllowed(userId: String): Boolean? {
return getBoolean(key = "${SCREEN_CAPTURE_ALLOW_KEY}_$userId")
return getBoolean(key = SCREEN_CAPTURE_ALLOW_KEY.appendIdentifier(userId))
}
override fun getScreenCaptureAllowedFlow(userId: String): Flow<Boolean?> =
@ -379,7 +383,7 @@ class SettingsDiskSourceImpl(
isScreenCaptureAllowed: Boolean?,
) {
putBoolean(
key = "${SCREEN_CAPTURE_ALLOW_KEY}_$userId",
key = SCREEN_CAPTURE_ALLOW_KEY.appendIdentifier(userId),
value = isScreenCaptureAllowed,
)
getMutableScreenCaptureAllowedFlow(userId).tryEmit(isScreenCaptureAllowed)

View file

@ -2,7 +2,6 @@ package com.x8bit.bitwarden.data.platform.datasource.disk.legacy
import android.content.SharedPreferences
import androidx.core.content.edit
import com.x8bit.bitwarden.data.platform.datasource.disk.BaseEncryptedDiskSource
/**
* Primary implementation of [LegacySecureStorageMigrator].
@ -21,7 +20,7 @@ class LegacySecureStorageMigratorImpl(
// which will all start with "bwSecureStorage". Hashing only occurred on devices with
// SDK <23.
val plaintextKeys = keys.filter {
it.startsWith(BaseEncryptedDiskSource.ENCRYPTED_BASE_KEY)
it.startsWith(ENCRYPTED_BASE_KEY)
}
plaintextKeys.forEach { unhashedKey ->
@ -32,4 +31,8 @@ class LegacySecureStorageMigratorImpl(
legacySecureStorage.remove(unhashedKey)
}
}
companion object {
private const val ENCRYPTED_BASE_KEY: String = "bwSecureStorage"
}
}

View file

@ -26,7 +26,7 @@ class GeneratorDiskSourceImpl(
}
override fun getPasscodeGenerationOptions(userId: String): PasscodeGenerationOptions? =
getString("${BASE_KEY}:${PASSWORD_GENERATION_OPTIONS_KEY}_$userId")
getString(PASSWORD_GENERATION_OPTIONS_KEY.appendIdentifier(userId))
?.let { json.decodeFromStringOrNull(it) }
override fun storePasscodeGenerationOptions(
@ -34,13 +34,13 @@ class GeneratorDiskSourceImpl(
options: PasscodeGenerationOptions?,
) {
putString(
"${BASE_KEY}:${PASSWORD_GENERATION_OPTIONS_KEY}_$userId",
PASSWORD_GENERATION_OPTIONS_KEY.appendIdentifier(userId),
options?.let { json.encodeToString(options) },
)
}
override fun getUsernameGenerationOptions(userId: String): UsernameGenerationOptions? =
getString("${BASE_KEY}:${USERNAME_GENERATION_OPTIONS_KEY}_$userId")
getString(USERNAME_GENERATION_OPTIONS_KEY.appendIdentifier(userId))
?.let { json.decodeFromStringOrNull(it) }
override fun storeUsernameGenerationOptions(
@ -48,7 +48,7 @@ class GeneratorDiskSourceImpl(
options: UsernameGenerationOptions?,
) {
putString(
"${BASE_KEY}:${USERNAME_GENERATION_OPTIONS_KEY}_$userId",
USERNAME_GENERATION_OPTIONS_KEY.appendIdentifier(userId),
options?.let { json.encodeToString(it) },
)
}