Skip to content

Commit 7ed2f16

Browse files
PM-39006: Update the setPassword flow for TDE users
1 parent 74f7dea commit 7ed2f16

4 files changed

Lines changed: 373 additions & 295 deletions

File tree

app/src/main/kotlin/com/x8bit/bitwarden/data/auth/repository/AuthRepositoryImpl.kt

Lines changed: 31 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.x8bit.bitwarden.data.auth.repository
22

33
import com.bitwarden.core.AuthRequestMethod
44
import com.bitwarden.core.InitUserCryptoMethod
5+
import com.bitwarden.core.MasterPasswordUnlockData
56
import com.bitwarden.core.RegisterTdeKeyResponse
67
import com.bitwarden.core.WrappedAccountCryptographicState
78
import com.bitwarden.core.data.manager.dispatcher.DispatcherManager
@@ -1145,10 +1146,10 @@ class AuthRepositoryImpl(
11451146
organizationIdentifier: String,
11461147
password: String,
11471148
passwordHint: String?,
1148-
): SetPasswordResult {
1149+
): SetPasswordResult = userStateManager.userStateTransaction {
11491150
val profile = authDiskSource.userState?.activeAccount?.profile
1150-
?: return SetPasswordResult.Error(error = NoActiveUserException())
1151-
return when (profile.forcePasswordResetReason) {
1151+
?: return@userStateTransaction SetPasswordResult.Error(error = NoActiveUserException())
1152+
return@userStateTransaction when (profile.forcePasswordResetReason) {
11521153
ForcePasswordResetReason.TDE_USER_WITHOUT_PASSWORD_HAS_PASSWORD_RESET_PERMISSION -> {
11531154
setUpdatedPassword(
11541155
profile = profile,
@@ -1196,30 +1197,25 @@ class AuthRepositoryImpl(
11961197
keys = null,
11971198
),
11981199
)
1199-
.map { response.passwordHash }
1200+
.map { response }
12001201
}
1201-
.flatMap { masterPasswordHash ->
1202-
when (val result = vaultRepository.unlockVaultWithMasterPassword(password)) {
1203-
is VaultUnlockResult.Success -> {
1204-
enrollUserInPasswordReset(
1205-
userId = userId,
1206-
organizationIdentifier = organizationIdentifier,
1207-
passwordHash = masterPasswordHash,
1208-
)
1209-
}
1210-
1211-
is VaultUnlockError -> {
1212-
(result.error ?: IllegalStateException("Failed to unlock vault"))
1213-
.asFailure()
1214-
}
1215-
}
1216-
}
1217-
.onSuccess {
1202+
.onSuccess { response ->
12181203
authDiskSource.userState = authDiskSource.userState?.toUserStateJsonWithPassword(
1219-
masterPasswordUnlock = null,
1204+
masterPasswordUnlock = MasterPasswordUnlockData(
1205+
kdf = profile.toSdkParams(),
1206+
masterKeyWrappedUserKey = response.newKey,
1207+
salt = profile.email,
1208+
),
12201209
)
12211210
this.organizationIdentifier = null
12221211
}
1212+
.flatMap { response ->
1213+
enrollUserInPasswordReset(
1214+
userId = userId,
1215+
organizationIdentifier = organizationIdentifier,
1216+
passwordHash = response.passwordHash,
1217+
)
1218+
}
12231219
.fold(
12241220
onFailure = { SetPasswordResult.Error(error = it) },
12251221
onSuccess = { SetPasswordResult.Success },
@@ -1326,16 +1322,26 @@ class AuthRepositoryImpl(
13261322
privateKey = response.keys.private,
13271323
),
13281324
)
1325+
authDiskSource.userState = authDiskSource
1326+
.userState
1327+
?.toUserStateJsonWithPassword(
1328+
masterPasswordUnlock = MasterPasswordUnlockData(
1329+
kdf = profile.toSdkParams(),
1330+
masterKeyWrappedUserKey = response.encryptedUserKey,
1331+
salt = profile.email,
1332+
),
1333+
)
1334+
this.organizationIdentifier = null
13291335
}
1330-
.map { response.masterPasswordHash }
1336+
.map { response }
13311337
}
1332-
.flatMap { masterPasswordHash ->
1338+
.flatMap { response ->
13331339
when (val result = vaultRepository.unlockVaultWithMasterPassword(password)) {
13341340
is VaultUnlockResult.Success -> {
13351341
enrollUserInPasswordReset(
13361342
userId = userId,
13371343
organizationIdentifier = organizationIdentifier,
1338-
passwordHash = masterPasswordHash,
1344+
passwordHash = response.masterPasswordHash,
13391345
)
13401346
}
13411347

@@ -1345,12 +1351,6 @@ class AuthRepositoryImpl(
13451351
}
13461352
}
13471353
}
1348-
.onSuccess {
1349-
authDiskSource.userState = authDiskSource.userState?.toUserStateJsonWithPassword(
1350-
masterPasswordUnlock = null,
1351-
)
1352-
this.organizationIdentifier = null
1353-
}
13541354
.fold(
13551355
onFailure = { SetPasswordResult.Error(error = it) },
13561356
onSuccess = { SetPasswordResult.Success },

app/src/main/kotlin/com/x8bit/bitwarden/data/auth/repository/util/UserStateJsonExtensions.kt

Lines changed: 52 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import com.bitwarden.network.model.UserDecryptionOptionsJson
1010
import com.bitwarden.policies.PolicyType
1111
import com.bitwarden.policies.PolicyView
1212
import com.bitwarden.ui.platform.base.util.toHexColorRepresentation
13+
import com.x8bit.bitwarden.data.auth.datasource.disk.model.ForcePasswordResetReason
1314
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
1415
import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson
1516
import com.x8bit.bitwarden.data.auth.datasource.sdk.util.toKdfRequestModel
@@ -84,56 +85,69 @@ fun UserStateJson.toUpdatedUserStateJson(
8485
}
8586
?: profile
8687
.userDecryptionOptions
87-
?.copy(masterPasswordUnlock = null)
88-
89-
val updatedProfile = profile
90-
.copy(
91-
avatarColorHex = syncProfile.avatarColor,
92-
stamp = syncProfile.securityStamp,
93-
hasPremiumPersonally = syncProfile.isPremium,
94-
hasPremiumFromOrganization = syncProfile.isPremiumFromOrganization,
95-
isTwoFactorEnabled = syncProfile.isTwoFactorEnabled,
96-
creationDate = syncProfile.creationDate,
97-
userDecryptionOptions = userDecryptionOptions,
98-
kdfType = masterPasswordUnlockKdf?.kdfType
99-
?: profile.kdfType,
100-
kdfIterations = masterPasswordUnlockKdf?.iterations
101-
?: profile.kdfIterations,
102-
kdfMemory = masterPasswordUnlockKdf?.memory
103-
?: profile.kdfMemory,
104-
kdfParallelism = masterPasswordUnlockKdf?.parallelism
105-
?: profile.kdfParallelism,
106-
)
88+
?.copy(
89+
hasMasterPassword = false,
90+
masterPasswordUnlock = null,
91+
)
92+
val forcePasswordResetReason = syncProfile.getForcePasswordResetReason(
93+
userDecryptionOptions = userDecryptionOptions,
94+
previousForcePasswordResetReason = profile.forcePasswordResetReason,
95+
)
96+
val updatedProfile = profile.copy(
97+
forcePasswordResetReason = forcePasswordResetReason,
98+
avatarColorHex = syncProfile.avatarColor,
99+
stamp = syncProfile.securityStamp,
100+
hasPremiumPersonally = syncProfile.isPremium,
101+
hasPremiumFromOrganization = syncProfile.isPremiumFromOrganization,
102+
isTwoFactorEnabled = syncProfile.isTwoFactorEnabled,
103+
creationDate = syncProfile.creationDate,
104+
userDecryptionOptions = userDecryptionOptions,
105+
kdfType = masterPasswordUnlockKdf?.kdfType ?: profile.kdfType,
106+
kdfIterations = masterPasswordUnlockKdf?.iterations ?: profile.kdfIterations,
107+
kdfMemory = masterPasswordUnlockKdf?.memory ?: profile.kdfMemory,
108+
kdfParallelism = masterPasswordUnlockKdf?.parallelism ?: profile.kdfParallelism,
109+
)
107110
val updatedAccount = account.copy(profile = updatedProfile)
108-
return this
109-
.copy(
110-
accounts = accounts
111-
.toMutableMap()
112-
.apply {
113-
replace(userId, updatedAccount)
114-
},
115-
)
111+
return this.copy(
112+
accounts = accounts
113+
.toMutableMap()
114+
.apply { replace(userId, updatedAccount) },
115+
)
116+
}
117+
118+
private fun SyncResponseJson.Profile.getForcePasswordResetReason(
119+
userDecryptionOptions: UserDecryptionOptionsJson?,
120+
previousForcePasswordResetReason: ForcePasswordResetReason?,
121+
): ForcePasswordResetReason? {
122+
val hasManageResetPasswordPermission = this.organizations.orEmpty().any {
123+
it.type == OrganizationType.OWNER ||
124+
it.type == OrganizationType.ADMIN ||
125+
it.permissions.shouldManageResetPassword
126+
}
127+
return ForcePasswordResetReason
128+
.TDE_USER_WITHOUT_PASSWORD_HAS_PASSWORD_RESET_PERMISSION
129+
.takeIf {
130+
userDecryptionOptions?.hasMasterPassword == false &&
131+
hasManageResetPasswordPermission
132+
}
133+
?: previousForcePasswordResetReason
116134
}
117135

118136
/**
119137
* Updates the [UserStateJson] to set the `hasMasterPassword` value to `true` after a user sets
120138
* their password.
121139
*/
122140
fun UserStateJson.toUserStateJsonWithPassword(
123-
masterPasswordUnlock: MasterPasswordUnlockData?,
141+
masterPasswordUnlock: MasterPasswordUnlockData,
124142
): UserStateJson {
125143
val account = this.activeAccount
126144
val profile = account.profile
127145
val userDecryptionOptions = profile.userDecryptionOptions
128-
val masterPasswordUnlockJson = masterPasswordUnlock
129-
?.let {
130-
MasterPasswordUnlockDataJson(
131-
salt = it.salt,
132-
kdf = it.kdf.toKdfRequestModel(),
133-
masterKeyWrappedUserKey = it.masterKeyWrappedUserKey,
134-
)
135-
}
136-
?: userDecryptionOptions?.masterPasswordUnlock
146+
val masterPasswordUnlockJson = MasterPasswordUnlockDataJson(
147+
salt = masterPasswordUnlock.salt,
148+
kdf = masterPasswordUnlock.kdf.toKdfRequestModel(),
149+
masterKeyWrappedUserKey = masterPasswordUnlock.masterKeyWrappedUserKey,
150+
)
137151
val updatedProfile = profile
138152
.copy(
139153
forcePasswordResetReason = null,

app/src/test/kotlin/com/x8bit/bitwarden/data/auth/repository/AuthRepositoryTest.kt

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5795,9 +5795,6 @@ class AuthRepositoryTest {
57955795
userId = profile.userId,
57965796
)
57975797
} returns resetPasswordKey.asSuccess()
5798-
coEvery {
5799-
vaultRepository.unlockVaultWithMasterPassword(password)
5800-
} returns VaultUnlockResult.Success
58015798

58025799
val result = repository.setPassword(
58035800
organizationIdentifier = organizationIdentifier,
@@ -5822,7 +5819,6 @@ class AuthRepositoryTest {
58225819
passwordHash = passwordHash,
58235820
resetPasswordKey = resetPasswordKey,
58245821
)
5825-
vaultRepository.unlockVaultWithMasterPassword(password)
58265822
vaultSdkSource.getResetPasswordKey(
58275823
orgPublicKey = publicOrgKey,
58285824
userId = profile.userId,
@@ -7481,7 +7477,7 @@ class AuthRepositoryTest {
74817477
masterPasswordUnlock = MasterPasswordUnlockDataJson(
74827478
kdf = BASE_PROFILE_1.toSdkParams().toKdfRequestModel(),
74837479
masterKeyWrappedUserKey = ENCRYPTED_USER_KEY,
7484-
salt = "mockSalt",
7480+
salt = EMAIL,
74857481
),
74867482
),
74877483
)
@@ -7533,7 +7529,7 @@ class AuthRepositoryTest {
75337529
masterPasswordUnlock = MasterPasswordUnlockDataJson(
75347530
kdf = BASE_PROFILE_1.toSdkParams().toKdfRequestModel(),
75357531
masterKeyWrappedUserKey = ENCRYPTED_USER_KEY,
7536-
salt = "mockSalt",
7532+
salt = EMAIL,
75377533
),
75387534
),
75397535
),

0 commit comments

Comments
 (0)