PM-37879: feat: Add SDK state bridge - #7204
Conversation
🤖 Bitwarden Claude Code ReviewOverall Assessment: REQUEST CHANGES Re-reviewed at head Code Review DetailsStill open from prior review (existing thread — not re-posted):
Verified fixed / no longer applicable since the prior review:
Reviewed and found acceptable:
|
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #7204 +/- ##
==========================================
- Coverage 86.36% 86.16% -0.20%
==========================================
Files 916 893 -23
Lines 65833 64933 -900
Branches 9718 9719 +1
==========================================
- Hits 56854 55948 -906
- Misses 5509 5511 +2
- Partials 3470 3474 +4
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
4ee8300 to
b4ef4d7
Compare
c28fd0c to
a18aa60
Compare
| } | ||
|
|
||
| override suspend fun setEphemeralPinEnvelope(value: PasswordProtectedKeyEnvelope) { | ||
| authDiskSource.storePinProtectedUserKeyEnvelope( |
There was a problem hiding this comment.
@david-livefront Both ephemeral and persistent must be present at the same time / that is the expectation from the SDK side. How does this work in auth disk source? It seems the key here is the same? 🤔
There was a problem hiding this comment.
That is not how we treat the pin envelope on Android. We always store it in memory and optionally store it to disk based on the inMemoryOnly flag.
override fun storePinProtectedUserKeyEnvelope(
userId: String,
pinProtectedUserKeyEnvelope: String?,
inMemoryOnly: Boolean,
) {
inMemoryPinProtectedUserKeyEnvelopes[userId] = pinProtectedUserKeyEnvelope
if (inMemoryOnly) {
getMutablePinProtectedUserKeyEnvelopeFlow(userId).tryEmit(pinProtectedUserKeyEnvelope)
return
}
putString(
key = PIN_PROTECTED_USER_KEY_KEY_ENVELOPE.appendIdentifier(userId),
value = pinProtectedUserKeyEnvelope,
)
getMutablePinProtectedUserKeyEnvelopeFlow(userId).tryEmit(pinProtectedUserKeyEnvelope)
}and then we fetch whatever is available when trying to retrieve the data:
override fun getPinProtectedUserKeyEnvelope(userId: String): String? =
inMemoryPinProtectedUserKeyEnvelopes[userId]
?: getString(key = PIN_PROTECTED_USER_KEY_KEY_ENVELOPE.appendIdentifier(userId))There was a problem hiding this comment.
In a separate PR, I have fully separated the ephemeral and persistent inProtectedUserKeyEnvelope.
a18aa60 to
75e171a
Compare
75e171a to
cae440b
Compare
| override suspend fun setMasterpasswordUnlockData(value: MasterPasswordUnlockData) { | ||
| authDiskSource.userState = authDiskSource.userState?.updateMasterPasswordUnlock( | ||
| userId = userId, | ||
| masterPasswordUnlock = value, | ||
| ) | ||
| } |
There was a problem hiding this comment.
updateMasterPasswordUnlock also clears forcePasswordResetReason, so an SDK-driven unlock-data write silently drops a pending forced password reset.
Details and fix
UserStateJson.updateMasterPasswordUnlock unconditionally sets forcePasswordResetReason = null (UserStateJsonExtensions.kt:125). That is intended for its existing callers in AuthRepositoryImpl (set/change/remove password), but through this bridge the SDK can write unlock data while a reset is still pending — for example when the V2 upgrade re-wraps the user key during unlock for a user carrying ADMIN_FORCE_PASSWORD_RESET or WEAK_MASTER_PASSWORD_ON_LOGIN.
Nothing restores the flag afterwards: on sync, getForcePasswordResetReason only carries the previous value forward (?: previousForcePasswordResetReason), so needsPasswordReset stays false until the next login and the user is never prompted to reset.
Consider a helper that updates only masterPasswordUnlock/hasMasterPassword and leaves forcePasswordResetReason untouched for the bridge path.
Related: clearMasterpasswordUnlockData flips hasMasterPassword to false. Worth confirming the SDK only calls it when the master password is genuinely being removed, since the app uses that flag to route users into the set-password and TDE flows.
cae440b to
417d37e
Compare
417d37e to
160b93f
Compare
160b93f to
183c673
Compare
183c673 to
100cf78
Compare
100cf78 to
4b40dc9
Compare
🎟️ Tracking
PM-37879
📔 Objective
This PR is a first step in supporting V2 encryption.
V2UpgradeTokenis now supported in the SyncResponse and we store that to diskSdkStateBridgeis now in place to support communicating data between the SDK and the main appV2UpgradeTokenis passed into the SDK when unlocking in order to migrate users to V2 encryption