Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,9 @@ import com.x8bit.bitwarden.data.auth.repository.util.policyInformation
import com.x8bit.bitwarden.data.auth.repository.util.toAccountCryptographicState
import com.x8bit.bitwarden.data.auth.repository.util.toDeviceInfo
import com.x8bit.bitwarden.data.auth.repository.util.toOrganizations
import com.x8bit.bitwarden.data.auth.repository.util.toRemovedPasswordUserStateJson
import com.x8bit.bitwarden.data.auth.repository.util.toSdkParams
import com.x8bit.bitwarden.data.auth.repository.util.toUserState
import com.x8bit.bitwarden.data.auth.repository.util.toUserStateJsonWithPassword
import com.x8bit.bitwarden.data.auth.repository.util.updateMasterPasswordUnlock
import com.x8bit.bitwarden.data.auth.repository.util.userSwitchingChangesFlow
import com.x8bit.bitwarden.data.auth.util.KdfParamsConstants.DEFAULT_PBKDF2_ITERATIONS
import com.x8bit.bitwarden.data.auth.util.YubiKeyResult
Expand Down Expand Up @@ -1058,7 +1057,10 @@ class AuthRepositoryImpl(
MigrateExistingUserToKeyConnectorResult.Success -> {
authDiskSource.userState = authDiskSource
.userState
?.toRemovedPasswordUserStateJson(userId = userId)
?.updateMasterPasswordUnlock(
userId = userId,
masterPasswordUnlock = null,
)
vaultRepository.sync()
settingsRepository.setDefaultsIfNecessary(userId = userId)
RemovePasswordResult.Success
Expand Down Expand Up @@ -1180,7 +1182,8 @@ class AuthRepositoryImpl(
.map { response }
}
.onSuccess { response ->
authDiskSource.userState = authDiskSource.userState?.toUserStateJsonWithPassword(
authDiskSource.userState = authDiskSource.userState?.updateMasterPasswordUnlock(
userId = userId,
masterPasswordUnlock = MasterPasswordUnlockData(
kdf = profile.toSdkParams(),
masterKeyWrappedUserKey = response.newKey,
Expand Down Expand Up @@ -1241,7 +1244,8 @@ class AuthRepositoryImpl(
userId = userId,
accountCryptographicState = response.accountCryptographicState,
)
authDiskSource.userState = authDiskSource.userState?.toUserStateJsonWithPassword(
authDiskSource.userState = authDiskSource.userState?.updateMasterPasswordUnlock(
userId = userId,
masterPasswordUnlock = response.masterPasswordUnlock,
)
this.organizationIdentifier = null
Expand Down Expand Up @@ -1304,7 +1308,8 @@ class AuthRepositoryImpl(
)
authDiskSource.userState = authDiskSource
.userState
?.toUserStateJsonWithPassword(
?.updateMasterPasswordUnlock(
userId = userId,
masterPasswordUnlock = MasterPasswordUnlockData(
kdf = profile.toSdkParams(),
masterKeyWrappedUserKey = response.encryptedUserKey,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,36 +24,6 @@ import com.x8bit.bitwarden.data.platform.manager.model.FirstTimeState
import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockData
import com.x8bit.bitwarden.data.vault.repository.util.statusFor

/**
* Updates the given [UserStateJson] with the data to indicate that the password has been removed.
* The original will be returned if the [userId] does not match any accounts in the [UserStateJson].
*/
fun UserStateJson.toRemovedPasswordUserStateJson(
userId: String,
): UserStateJson {
val account = this.accounts[userId] ?: return this
val profile = account.profile
val updatedUserDecryptionOptions = profile
.userDecryptionOptions
?.copy(
hasMasterPassword = false,
masterPasswordUnlock = null,
)
?: UserDecryptionOptionsJson(
hasMasterPassword = false,
trustedDeviceUserDecryptionOptions = null,
keyConnectorUserDecryptionOptions = null,
masterPasswordUnlock = null,
)
val updatedProfile = profile.copy(userDecryptionOptions = updatedUserDecryptionOptions)
val updatedAccount = account.copy(profile = updatedProfile)
return this.copy(
accounts = accounts
.toMutableMap()
.apply { replace(userId, updatedAccount) },
)
}

/**
* Updates the given [UserStateJson] with the data from the [syncResponse] to return a new
* [UserStateJson]. The original will be returned if the sync response does not match any accounts
Expand Down Expand Up @@ -134,44 +104,43 @@ private fun SyncResponseJson.Profile.getForcePasswordResetReason(
}

/**
* Updates the [UserStateJson] to set the `hasMasterPassword` value to `true` after a user sets
* their password.
* Updates the [UserStateJson] by setting the `hasMasterPassword` and `masterPasswordUnlock` values
* for the given [userId]. If the user is not present in the `UserStateJson`, nothing is updated.
*/
fun UserStateJson.toUserStateJsonWithPassword(
masterPasswordUnlock: MasterPasswordUnlockData,
fun UserStateJson.updateMasterPasswordUnlock(
userId: String,
masterPasswordUnlock: MasterPasswordUnlockData?,
): UserStateJson {
val account = this.activeAccount
val account = accounts[userId] ?: return this
val profile = account.profile
val userDecryptionOptions = profile.userDecryptionOptions
val masterPasswordUnlockJson = MasterPasswordUnlockDataJson(
salt = masterPasswordUnlock.salt,
kdf = masterPasswordUnlock.kdf.toKdfRequestModel(),
masterKeyWrappedUserKey = masterPasswordUnlock.masterKeyWrappedUserKey,
)
val updatedProfile = profile
.copy(
forcePasswordResetReason = null,
userDecryptionOptions = userDecryptionOptions
?.copy(
hasMasterPassword = true,
masterPasswordUnlock = masterPasswordUnlockJson,
)
?: UserDecryptionOptionsJson(
hasMasterPassword = true,
keyConnectorUserDecryptionOptions = null,
trustedDeviceUserDecryptionOptions = null,
masterPasswordUnlock = masterPasswordUnlockJson,
),
val masterPasswordUnlockJson = masterPasswordUnlock?.let {
MasterPasswordUnlockDataJson(
salt = it.salt,
kdf = it.kdf.toKdfRequestModel(),
masterKeyWrappedUserKey = it.masterKeyWrappedUserKey,
)
}
val updatedProfile = profile.copy(
forcePasswordResetReason = null,
Comment thread
david-livefront marked this conversation as resolved.
userDecryptionOptions = userDecryptionOptions
?.copy(
hasMasterPassword = masterPasswordUnlockJson != null,
masterPasswordUnlock = masterPasswordUnlockJson,
)
?: UserDecryptionOptionsJson(
hasMasterPassword = masterPasswordUnlockJson != null,
keyConnectorUserDecryptionOptions = null,
trustedDeviceUserDecryptionOptions = null,
masterPasswordUnlock = masterPasswordUnlockJson,
),
)
val updatedAccount = account.copy(profile = updatedProfile)
return this
.copy(
accounts = accounts
.toMutableMap()
.apply {
replace(activeUserId, updatedAccount)
},
)
return this.copy(
accounts = accounts
.toMutableMap()
.apply { replace(userId, updatedAccount) },
)
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,9 +131,9 @@ import com.x8bit.bitwarden.data.auth.repository.util.CookieCallbackResult
import com.x8bit.bitwarden.data.auth.repository.util.DuoCallbackTokenResult
import com.x8bit.bitwarden.data.auth.repository.util.SsoCallbackResult
import com.x8bit.bitwarden.data.auth.repository.util.WebAuthResult
import com.x8bit.bitwarden.data.auth.repository.util.toRemovedPasswordUserStateJson
import com.x8bit.bitwarden.data.auth.repository.util.toSdkParams
import com.x8bit.bitwarden.data.auth.repository.util.toUserState
import com.x8bit.bitwarden.data.auth.repository.util.updateMasterPasswordUnlock
import com.x8bit.bitwarden.data.auth.util.YubiKeyResult
import com.x8bit.bitwarden.data.auth.util.toSdkParams
import com.x8bit.bitwarden.data.platform.datasource.disk.util.FakeSettingsDiskSource
Expand Down Expand Up @@ -330,7 +330,7 @@ class AuthRepositoryTest {
fun beforeEach() {
mockkStatic(
GetTokenResponseJson.Success::toUserState,
UserStateJson::toRemovedPasswordUserStateJson,
UserStateJson::updateMasterPasswordUnlock,
)
mockkConstructor(
NoActiveUserException::class,
Expand All @@ -348,7 +348,7 @@ class AuthRepositoryTest {
fun tearDown() {
unmockkStatic(
GetTokenResponseJson.Success::toUserState,
UserStateJson::toRemovedPasswordUserStateJson,
UserStateJson::updateMasterPasswordUnlock,
)
mockkConstructor(
NoActiveUserException::class,
Expand Down Expand Up @@ -5036,7 +5036,10 @@ class AuthRepositoryTest {
)
} returns MigrateExistingUserToKeyConnectorResult.Success.asSuccess()
every {
SINGLE_USER_STATE_1.toRemovedPasswordUserStateJson(userId = USER_ID_1)
SINGLE_USER_STATE_1.updateMasterPasswordUnlock(
userId = USER_ID_1,
masterPasswordUnlock = null,
)
} returns SINGLE_USER_STATE_1
every { vaultRepository.sync() } just runs
every { settingsRepository.setDefaultsIfNecessary(userId = USER_ID_1) } just runs
Expand All @@ -5045,7 +5048,10 @@ class AuthRepositoryTest {

assertEquals(RemovePasswordResult.Success, result)
verify(exactly = 1) {
SINGLE_USER_STATE_1.toRemovedPasswordUserStateJson(userId = USER_ID_1)
SINGLE_USER_STATE_1.updateMasterPasswordUnlock(
userId = USER_ID_1,
masterPasswordUnlock = null,
)
vaultRepository.sync()
settingsRepository.setDefaultsIfNecessary(userId = USER_ID_1)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,20 +59,23 @@ class UserStateJsonExtensionsTest {
}

@Test
fun `toRemovedPasswordUserStateJson should do nothing for a non-matching account`() {
fun `updateMasterPasswordUnlock should do nothing for a non-matching account`() {
val originalUserState = UserStateJson(
activeUserId = "activeUserId",
accounts = mapOf("activeUserId" to mockk()),
)
assertEquals(
originalUserState,
originalUserState.toRemovedPasswordUserStateJson(userId = "nonActiveUserId"),
originalUserState.updateMasterPasswordUnlock(
userId = "nonActiveUserId",
masterPasswordUnlock = null,
),
)
}

@Suppress("MaxLineLength")
@Test
fun `toRemovedPasswordUserStateJson should create user decryption options without a password if not present`() {
fun `updateMasterPasswordUnlock should create user decryption options without a password if not present`() {
val originalProfile = AccountJson.Profile(
userId = "activeUserId",
email = "email",
Expand Down Expand Up @@ -118,13 +121,16 @@ class UserStateJsonExtensionsTest {
),
),
),
originalUserState.toRemovedPasswordUserStateJson(userId = "activeUserId"),
originalUserState.updateMasterPasswordUnlock(
userId = "activeUserId",
masterPasswordUnlock = null,
),
)
}

@Suppress("MaxLineLength")
@Test
fun `toRemovedPasswordUserStateJson should update user decryption options to not have a password`() {
fun `updateMasterPasswordUnlock should update user decryption options to not have a password`() {
val originalProfile = AccountJson.Profile(
userId = "activeUserId",
email = "email",
Expand Down Expand Up @@ -179,7 +185,10 @@ class UserStateJsonExtensionsTest {
),
),
),
originalUserState.toRemovedPasswordUserStateJson(userId = "activeUserId"),
originalUserState.updateMasterPasswordUnlock(
userId = "activeUserId",
masterPasswordUnlock = null,
),
)
}

Expand Down Expand Up @@ -420,7 +429,7 @@ class UserStateJsonExtensionsTest {

@Suppress("MaxLineLength")
@Test
fun `toUserStateJsonWithPassword with masterPasswordUnlock should update active account to set hasMasterPassword and masterPasswordUnlock`() {
fun `updateMasterPasswordUnlock with masterPasswordUnlock should update account to set hasMasterPassword and masterPasswordUnlock`() {
val originalProfile = AccountJson.Profile(
userId = "activeUserId",
email = "email",
Expand Down Expand Up @@ -481,7 +490,10 @@ class UserStateJsonExtensionsTest {
"activeUserId" to originalAccount,
),
)
.toUserStateJsonWithPassword(masterPasswordUnlock = masterPasswordUnlock),
.updateMasterPasswordUnlock(
userId = "activeUserId",
masterPasswordUnlock = masterPasswordUnlock,
),
)
}

Expand Down
Loading