Skip to content

Commit

Permalink
fix: Limit the precision of Instant to 6 digits after seconds #792
Browse files Browse the repository at this point in the history
  • Loading branch information
AChep committed Jan 26, 2025
1 parent f6855b1 commit b72e2e7
Show file tree
Hide file tree
Showing 8 changed files with 78 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,22 @@ package com.artemchep.keyguard.common.util
import kotlinx.datetime.Instant

val Instant.millis get() = toEpochMilliseconds()

// See:
// https://github.com/dani-garcia/vaultwarden/pull/4386#issuecomment-2614321170
fun Instant.isOver6DigitsNanosOfSecond(): Boolean =
nanosecondsOfSecond != convertNanosOfSecondTo6Digits(nanosecondsOfSecond)

// See:
// https://github.com/dani-garcia/vaultwarden/pull/4386#issuecomment-2614321170
fun Instant.to6DigitsNanosOfSecond(): Instant {
val nanos = convertNanosOfSecondTo6Digits(nanosecondsOfSecond)
return Instant.fromEpochSeconds(epochSeconds, nanos)
}

private fun convertNanosOfSecondTo6Digits(nanos: Int): Int {
val multiplier = 1000
return nanos
.div(multiplier)
.times(multiplier)
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import com.artemchep.keyguard.common.service.crypto.CryptoGenerator
import com.artemchep.keyguard.common.service.logging.LogRepository
import com.artemchep.keyguard.common.service.text.Base64Service
import com.artemchep.keyguard.common.usecase.GetPasswordStrength
import com.artemchep.keyguard.common.util.isOver6DigitsNanosOfSecond
import com.artemchep.keyguard.core.store.DatabaseManager
import com.artemchep.keyguard.core.store.DatabaseSyncer
import com.artemchep.keyguard.core.store.bitwarden.BitwardenCipher
Expand Down Expand Up @@ -63,7 +64,6 @@ import io.ktor.client.HttpClient
import io.ktor.client.call.NoTransformationFoundException
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlin.RuntimeException
import kotlin.String
Expand Down Expand Up @@ -712,7 +712,7 @@ class SyncEngine(
}
}
},
shouldOverwrite = { local, remote ->
shouldOverwriteLocal = { local, remote ->
val remoteAttachments = remote.attachments
.orEmpty()
// fast path:
Expand All @@ -735,6 +735,26 @@ class SyncEngine(

false
},
shouldOverwriteRemote = { local, remote ->
// Auto-correct precision for different revision
// properties.
// See:
// https://github.com/dani-garcia/vaultwarden/pull/4386#issuecomment-2614321170
val passwordRevision = local.login?.passwordRevisionDate
?.isOver6DigitsNanosOfSecond() == true
val passwordHistory = local.login?.passwordHistory.orEmpty()
.any { it.lastUsedDate?.isOver6DigitsNanosOfSecond() == true }
if (passwordRevision || passwordHistory) {
return@syncX true
}
val passkeyCreatedAt = local.login?.fido2Credentials.orEmpty()
.any { it.creationDate.isOver6DigitsNanosOfSecond() }
if (passkeyCreatedAt) {
return@syncX true
}

false
},
remoteItems = response.ciphers.orEmpty(),
remoteLens = cipherRemoteLens,
remoteDecoder = { remote, local ->
Expand Down Expand Up @@ -822,7 +842,8 @@ class SyncEngine(
val isTrashed = r.source.deletedDate != null
val wasTrashed = r.source.service.remote?.deletedDate != null
val hasChanged =
r.source.service.remote?.revisionDate != r.source.revisionDate
r.source.service.remote?.revisionDate != r.source.revisionDate ||
force
if (isTrashed != wasTrashed || hasChanged) {
// Due to Bitwarden API restrictions you can not modify
// trashed items: first we have to restore them.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ suspend fun merge(
}

interface RemotePutScope<Remote> {
val force: Boolean

fun updateRemoteModel(remote: Remote)
}

Expand All @@ -124,7 +126,8 @@ suspend fun <
localDecodedToString: (LocalDecoded) -> String = { it.toString() },
localDeleteById: suspend (List<String>) -> Unit,
localPut: suspend (List<RemoteDecoded>) -> Unit,
shouldOverwrite: (Local, Remote) -> Boolean = { _, _ -> false },
shouldOverwriteLocal: (Local, Remote) -> Boolean = { _, _ -> false },
shouldOverwriteRemote: (Local, Remote) -> Boolean = { _, _ -> false },
remoteItems: Collection<Remote>,
remoteLens: SyncManager.Lens<Remote>,
remoteDecoder: suspend (Remote, Local?) -> RemoteDecoded,
Expand All @@ -147,7 +150,8 @@ suspend fun <
).df(
localItems = localItems,
remoteItems = remoteItems,
shouldOverwrite = shouldOverwrite,
shouldOverwriteLocal = shouldOverwriteLocal,
shouldOverwriteRemote = shouldOverwriteRemote,
)
onLog(
"[Start] Starting to sync the $name: " +
Expand Down Expand Up @@ -290,6 +294,9 @@ suspend fun <
.flatMap { localDecoded ->
var lastRemote: Remote? = null
val scope = object : RemotePutScope<Remote> {
override val force: Boolean
get() = entry.force

override fun updateRemoteModel(remote: Remote) {
lastRemote = remote
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.artemchep.keyguard.provider.bitwarden.entity.api

import com.artemchep.keyguard.common.util.to6DigitsNanosOfSecond
import com.artemchep.keyguard.core.store.bitwarden.BitwardenCipher
import kotlinx.datetime.Instant
import kotlinx.serialization.SerialName
Expand Down Expand Up @@ -53,6 +54,7 @@ fun LoginFido2CredentialsRequest.Companion.of(
userName = model.userName,
userDisplayName = model.userDisplayName,
discoverable = model.discoverable,
creationDate = model.creationDate,
creationDate = model.creationDate
.to6DigitsNanosOfSecond(),
)
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.artemchep.keyguard.provider.bitwarden.entity.api

import com.artemchep.keyguard.common.util.to6DigitsNanosOfSecond
import com.artemchep.keyguard.core.store.bitwarden.BitwardenCipher
import kotlinx.datetime.Instant
import kotlinx.serialization.SerialName
Expand Down Expand Up @@ -37,7 +38,8 @@ fun LoginRequest.Companion.of(
uris = urisRequests,
username = model.username,
password = model.password,
passwordRevisionDate = model.passwordRevisionDate,
passwordRevisionDate = model.passwordRevisionDate
?.to6DigitsNanosOfSecond(),
totp = model.totp,
)
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.artemchep.keyguard.provider.bitwarden.entity.request

import com.artemchep.keyguard.common.util.to6DigitsNanosOfSecond
import com.artemchep.keyguard.core.store.bitwarden.BitwardenCipher
import kotlinx.datetime.Instant
import kotlinx.serialization.SerialName
Expand All @@ -17,6 +18,7 @@ fun PasswordHistoryRequest.Companion.of(
model: BitwardenCipher.Login.PasswordHistory,
) = kotlin.run {
val lastUsedDate = model.lastUsedDate
?.to6DigitsNanosOfSecond()
// Bitwarden forces us to have a last used date for
// the password history item. It still allows for existing
// items to have it as null tho.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.artemchep.keyguard.provider.bitwarden.entity.request

import com.artemchep.keyguard.common.service.crypto.CryptoGenerator
import com.artemchep.keyguard.common.service.text.Base64Service
import com.artemchep.keyguard.common.util.to6DigitsNanosOfSecond
import com.artemchep.keyguard.core.store.bitwarden.BitwardenOptionalStringNullable
import com.artemchep.keyguard.core.store.bitwarden.BitwardenSend
import com.artemchep.keyguard.provider.bitwarden.entity.SendTypeEntity
Expand Down Expand Up @@ -106,8 +107,10 @@ fun SendRequest.Companion.of(
password = passwordHashBase64,
disabled = model.disabled,
hideEmail = model.hideEmail == true,
deletionDate = deletionDate,
expirationDate = model.expirationDate,
deletionDate = deletionDate
.to6DigitsNanosOfSecond(),
expirationDate = model.expirationDate
?.to6DigitsNanosOfSecond(),
maxAccessCount = model.maxAccessCount,
text = text,
file = file,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class SyncManager<Local : BitwardenService.Has<Local>, Remote : Any>(
data class Ite<Local, Remote>(
val local: Local,
val remote: Remote,
val force: Boolean = false,
)
}

Expand Down Expand Up @@ -60,7 +61,8 @@ class SyncManager<Local : BitwardenService.Has<Local>, Remote : Any>(
fun df(
localItems: Collection<Local>,
remoteItems: Collection<Remote>,
shouldOverwrite: (Local, Remote) -> Boolean,
shouldOverwriteLocal: (Local, Remote) -> Boolean,
shouldOverwriteRemote: (Local, Remote) -> Boolean,
): Df<Local, Remote> {
// Delete items
val remoteDeletedCipherIds = mutableListOf<Df.Ite<Local, Remote>>()
Expand Down Expand Up @@ -156,11 +158,18 @@ class SyncManager<Local : BitwardenService.Has<Local>, Remote : Any>(
if (dateRoundingError || localItem.service.error != null) {
localPutCipher += Df.Ite(localItem, remoteItem)
} else {
val hasChanged = shouldOverwrite(localItem, remoteItem)
if (hasChanged) {
val overwriteLocal = shouldOverwriteLocal(localItem, remoteItem)
if (overwriteLocal) {
localPutCipher += Df.Ite(localItem, remoteItem)
} else {
// Up to date.
val overwriteRemote = shouldOverwriteRemote(localItem, remoteItem)
if (overwriteRemote) {
// We have to force that in, because the revision
// date has not been changed.
remotePutCipher += Df.Ite(localItem, remoteItem, force = true)
} else {
// Up to date.
}
}
}
}
Expand Down

0 comments on commit b72e2e7

Please sign in to comment.