Skip to content

Commit ac74ea3

Browse files
committed
Fixed WebDAV account loading and vault deletion issues (#415)
1 parent d579ed4 commit ac74ea3

File tree

5 files changed

+108
-9
lines changed

5 files changed

+108
-9
lines changed

Cryptomator/Common/CloudAccountList/AccountListViewModel.swift

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,23 @@ class AccountListViewModel: AccountListViewModelProtocol {
4343

4444
func refreshItems() throws {
4545
let refreshedAccountInfos = try dbManager.getAllAccounts(for: cloudProviderType)
46-
let refreshedAccounts = try refreshedAccountInfos.map { try createAccountCellContent(from: $0) }
47-
accountInfos = refreshedAccountInfos
46+
var refreshedAccounts: [AccountCellContent] = []
47+
var validAccountInfos: [AccountInfo] = []
48+
49+
for accountInfo in refreshedAccountInfos {
50+
do {
51+
let accountContent = try createAccountCellContent(from: accountInfo)
52+
refreshedAccounts.append(accountContent)
53+
validAccountInfos.append(accountInfo)
54+
} catch CloudProviderAccountError.accountNotFoundError {
55+
DDLogError("Account \(accountInfo.accountUID) not found in keychain, skipping")
56+
} catch {
57+
DDLogError("Error creating account cell content for \(accountInfo.accountUID): \(error)")
58+
throw error
59+
}
60+
}
61+
62+
accountInfos = validAccountInfos
4863
accounts = refreshedAccounts
4964
}
5065

Cryptomator/Common/CloudAuthenticator.swift

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -139,15 +139,18 @@ class CloudAuthenticator {
139139
try WebDAVCredentialManager.shared.removeCredentialFromKeychain(with: account.accountUID)
140140
}
141141
let correspondingVaults = try vaultAccountManager.getAllAccounts().filter { $0.delegateAccountUID == account.accountUID }
142-
_ = Promise<Void>(on: .global()) { fulfill, _ in
143-
for correspondingVault in correspondingVaults {
144-
do {
145-
try awaitPromise(self.vaultManager.removeVault(withUID: correspondingVault.vaultUID))
146-
} catch {
147-
DDLogError("Remove corresponding vault: \(correspondingVault.vaultName) after deauthenticated account: \(account) - failed with error: \(error)")
142+
let vaultUIDs = correspondingVaults.map { $0.vaultUID }
143+
144+
_ = Promise<Void>(on: .global()) { fulfill, reject in
145+
do {
146+
if !vaultUIDs.isEmpty {
147+
try awaitPromise(self.vaultManager.removeVaults(withUIDs: vaultUIDs))
148+
DDLogInfo("Removed \(vaultUIDs.count) vaults for deauthenticated account: \(account)")
148149
}
150+
fulfill(())
151+
} catch {
152+
reject(error)
149153
}
150-
fulfill(())
151154
}.then {
152155
try self.accountManager.removeAccount(with: account.accountUID)
153156
}.catch { error in

CryptomatorCommon/Sources/CryptomatorCommonCore/Manager/VaultAccountDBManager.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
// Copyright © 2020 Skymatic GmbH. All rights reserved.
77
//
88

9+
import CocoaLumberjackSwift
910
import CryptomatorCloudAccessCore
1011
import Dependencies
1112
import Foundation
@@ -46,6 +47,7 @@ extension VaultAccount: PersistableRecord {
4647
public protocol VaultAccountManager {
4748
func saveNewAccount(_ account: VaultAccount) throws
4849
func removeAccount(with vaultUID: String) throws
50+
func removeAccounts(with vaultUIDs: [String]) throws
4951
func getAccount(with vaultUID: String) throws -> VaultAccount
5052
func getAllAccounts() throws -> [VaultAccount]
5153
func updateAccount(_ account: VaultAccount) throws
@@ -77,6 +79,18 @@ public class VaultAccountDBManager: VaultAccountManager {
7779
}
7880
}
7981

82+
public func removeAccounts(with vaultUIDs: [String]) throws {
83+
guard !vaultUIDs.isEmpty else {
84+
return
85+
}
86+
let deletedCount = try database.write { db in
87+
try VaultAccount.deleteAll(db, keys: vaultUIDs)
88+
}
89+
if deletedCount != vaultUIDs.count {
90+
DDLogError("Expected to delete \(vaultUIDs.count) vault accounts, but only deleted \(deletedCount)")
91+
}
92+
}
93+
8094
public func getAccount(with vaultUID: String) throws -> VaultAccount {
8195
let fetchedAccount = try database.read { db in
8296
return try VaultAccount.fetchOne(db, key: vaultUID)

CryptomatorCommon/Sources/CryptomatorCommonCore/Manager/VaultDBManager.swift

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ public protocol VaultManager {
3131
func manualUnlockVault(withUID vaultUID: String, kek: [UInt8]) throws -> CloudProvider
3232
func createVaultProvider(withUID vaultUID: String, masterkey: Masterkey) throws -> CloudProvider
3333
func removeVault(withUID vaultUID: String) throws -> Promise<Void>
34+
func removeVaults(withUIDs vaultUIDs: [String]) throws -> Promise<Void>
3435
func removeAllUnusedFileProviderDomains() -> Promise<Void>
3536
func moveVault(account: VaultAccount, to targetVaultPath: CloudPath) -> Promise<Void>
3637
func changePassphrase(oldPassphrase: String, newPassphrase: String, forVaultUID vaultUID: String) -> Promise<Void>
@@ -406,6 +407,49 @@ public class VaultDBManager: VaultManager {
406407
}
407408
}
408409

410+
/**
411+
Removes multiple vaults at once, handling database operations in a single transaction to avoid constraint violations.
412+
- Precondition: `VaultAccount`s for the `vaultUIDs` exist in the database.
413+
- Postcondition: All specified vaults are removed from the database, keychain, and FileProvider.
414+
*/
415+
public func removeVaults(withUIDs vaultUIDs: [String]) throws -> Promise<Void> {
416+
guard !vaultUIDs.isEmpty else {
417+
return Promise(())
418+
}
419+
420+
return Promise<Void>(on: .global()) { fulfill, reject in
421+
do {
422+
// Remove passwords and cached masterkeys for all vaults
423+
for vaultUID in vaultUIDs {
424+
do {
425+
try self.passwordManager.removePassword(forVaultUID: vaultUID)
426+
try self.masterkeyCacheManager.removeCachedMasterkey(forVaultUID: vaultUID)
427+
} catch {
428+
DDLogError("Failed to remove password/masterkey for vault \(vaultUID): \(error)")
429+
}
430+
}
431+
432+
// Remove all FileProvider domains
433+
for vaultUID in vaultUIDs {
434+
do {
435+
try awaitPromise(self.removeFileProviderDomain(withVaultUID: vaultUID))
436+
} catch {
437+
DDLogError("Failed to remove FileProvider domain for vault \(vaultUID): \(error)")
438+
}
439+
}
440+
441+
// Remove all vault accounts in a single transaction
442+
try self.vaultAccountManager.removeAccounts(with: vaultUIDs)
443+
DDLogInfo("Removed \(vaultUIDs.count) vaults")
444+
445+
fulfill(())
446+
} catch {
447+
DDLogError("Removing vaults failed with error: \(error)")
448+
reject(error)
449+
}
450+
}
451+
}
452+
409453
/**
410454
Removes all unused FileProvider domains.
411455

CryptomatorCommon/Sources/CryptomatorCommonCore/Mocks/VaultManagerMock.swift

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,29 @@ final class VaultManagerMock: VaultManager {
156156
return try removeVaultWithUIDClosure.map({ try $0(vaultUID) }) ?? removeVaultWithUIDReturnValue
157157
}
158158

159+
// MARK: - removeVaults
160+
161+
var removeVaultsWithUIDsThrowableError: Error?
162+
var removeVaultsWithUIDsCallsCount = 0
163+
var removeVaultsWithUIDsCalled: Bool {
164+
removeVaultsWithUIDsCallsCount > 0
165+
}
166+
167+
var removeVaultsWithUIDsReceivedVaultUIDs: [String]?
168+
var removeVaultsWithUIDsReceivedInvocations: [[String]] = []
169+
var removeVaultsWithUIDsReturnValue: Promise<Void>!
170+
var removeVaultsWithUIDsClosure: (([String]) throws -> Promise<Void>)?
171+
172+
func removeVaults(withUIDs vaultUIDs: [String]) throws -> Promise<Void> {
173+
if let error = removeVaultsWithUIDsThrowableError {
174+
throw error
175+
}
176+
removeVaultsWithUIDsCallsCount += 1
177+
removeVaultsWithUIDsReceivedVaultUIDs = vaultUIDs
178+
removeVaultsWithUIDsReceivedInvocations.append(vaultUIDs)
179+
return try removeVaultsWithUIDsClosure.map({ try $0(vaultUIDs) }) ?? removeVaultsWithUIDsReturnValue
180+
}
181+
159182
// MARK: - removeAllUnusedFileProviderDomains
160183

161184
var removeAllUnusedFileProviderDomainsThrowableError: Error?

0 commit comments

Comments
 (0)