Skip to content
Open
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 @@ -801,6 +801,7 @@ public class ServiceContainer: Services { // swiftlint:disable:this type_body_le
cipherService: cipherService,
clientService: clientService,
errorReporter: errorReporter,
stateService: stateService,
syncService: syncService,
),
)
Expand All @@ -809,6 +810,7 @@ public class ServiceContainer: Services { // swiftlint:disable:this type_body_le
cipherService: cipherService,
clientService: clientService,
errorReporter: errorReporter,
stateService: stateService,
syncService: syncService,
)
#endif
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ final class Fido2CredentialStoreService: Fido2CredentialStore {
/// The service used by the application to report non-fatal errors.
private let errorReporter: ErrorReporter

/// The service used by the application to manage account state.
private let stateService: StateService

/// The service used to handle syncing vault data with the API
private let syncService: SyncService

Expand All @@ -26,16 +29,19 @@ final class Fido2CredentialStoreService: Fido2CredentialStore {
/// - cipherService: The service used to manage syncing and updates to the user's ciphers.
/// - clientService: The service that handles common client functionality such as encryption and decryption.
/// - errorReporter: The service used by the application to report non-fatal errors.
/// - stateService: The service used by the application to manage account state.
/// - syncService: The service used to handle syncing vault data with the API.
init(
cipherService: CipherService,
clientService: ClientService,
errorReporter: ErrorReporter,
stateService: StateService,
syncService: SyncService,
) {
self.cipherService = cipherService
self.clientService = clientService
self.errorReporter = errorReporter
self.stateService = stateService
self.syncService = syncService
}

Expand All @@ -55,18 +61,46 @@ final class Fido2CredentialStoreService: Fido2CredentialStore {
/// - ripId: The `ripId` to match the Fido2 credential `rpId`.
/// - Returns: All the ciphers that matches the filter.
func findCredentials(ids: [Data]?, ripId: String) async throws -> [BitwardenSdk.CipherView] {
do {
try await syncService.fetchSync(forceSync: false)
} catch {
errorReporter.log(error: error)
try await findCredentials(ids: ids, ripId: ripId, shouldCheckSync: true)
}

/// Saves a cipher credential that contains a Fido2 credential, either creating it or updating it to server.
/// - Parameter cred: Cipher/Credential to add/update.
func saveCredential(cred: BitwardenSdk.EncryptionContext) async throws {
if cred.cipher.id == nil {
try await cipherService.addCipherWithServer(cred.cipher, encryptedFor: cred.encryptedFor)
} else {
try await cipherService.updateCipherWithServer(cred.cipher, encryptedFor: cred.encryptedFor)
}
}

// MARK: Private methods

/// Finds active login ciphers that have Fido2 credentials, match the `ripId` and if `ids` is sent
/// then filters the one which the Fido2 `credentialId` matches some of the one in `ids`.
/// - Parameters:
/// - ids: An array of possible `credentialId` to filter credentials that matches one of them.
/// When `nil` the `credentialId` filter is not applied.
/// - ripId: The `ripId` to match the Fido2 credential `rpId`.
/// - shouldCheckSync: Whether it should check if sync is needed. This is particular useful to avoid
/// infinite loops by calling this method recursively.
/// - Returns: All the ciphers that matches the filter.
private func findCredentials(
ids: [Data]?,
ripId: String,
shouldCheckSync: Bool,
) async throws -> [BitwardenSdk.CipherView] {
let activeCiphersWithFido2Credentials = try await cipherService.fetchAllCiphers()
.filter(\.isActiveWithFido2Credentials)
.asyncMap { cipher in
try await self.clientService.vault().ciphers().decrypt(cipher: cipher)
}

var needsSync = false
if shouldCheckSync {
needsSync = await needsSyncCheckingLocally()
}

var result = [BitwardenSdk.CipherView]()
for cipherView in activeCiphersWithFido2Credentials {
let fido2CredentialAutofillViews = try await clientService.platform()
Expand All @@ -83,18 +117,32 @@ final class Fido2CredentialStoreService: Fido2CredentialStore {
continue
}

// Only perform sync if it's needed and there are Fido2 credentials with counter.
if needsSync, fido2CredentialAutofillViews.contains(where: \.hasCounter) {
do {
try await syncService.fetchSync(forceSync: false, isPeriodic: true)

// After sync re-call this function to find the up-to-date credentials.
return try await findCredentials(ids: ids, ripId: ripId, shouldCheckSync: false)
} catch {
errorReporter.log(error: error)
}
}

result.append(cipherView)
}
return result
}

/// Saves a cipher credential that contains a Fido2 credential, either creating it or updating it to server.
/// - Parameter cred: Cipher/Credential to add/update.
func saveCredential(cred: BitwardenSdk.EncryptionContext) async throws {
if cred.cipher.id == nil {
try await cipherService.addCipherWithServer(cred.cipher, encryptedFor: cred.encryptedFor)
} else {
try await cipherService.updateCipherWithServer(cred.cipher, encryptedFor: cred.encryptedFor)
/// Whether the current user needs to perform a sync. It only performs local verifications.
/// - Returns: `true` if needed, `false` otherwise.
private func needsSyncCheckingLocally() async -> Bool {
do {
let userId = try await stateService.getActiveAccountId()
return try await syncService.needsSync(for: userId, onlyCheckLocalData: true)
} catch {
errorReporter.log(error: error)
return false
}
}
}
Expand All @@ -111,7 +159,7 @@ private extension Cipher {
#if DEBUG

/// A wrapper of a `Fido2CredentialStore` which adds debugging info for the `Fido2DebugginReportBuilder`.
class DebuggingFido2CredentialStoreService: Fido2CredentialStore {
final class DebuggingFido2CredentialStoreService: Fido2CredentialStore {
let fido2CredentialStore: Fido2CredentialStore

init(fido2CredentialStore: Fido2CredentialStore) {
Expand Down
Loading
Loading