Skip to content

Commit 043c1c7

Browse files
[BITAU-152] Handle Vault Lock/Unlock with Sync (#998)
1 parent 77ec482 commit 043c1c7

File tree

5 files changed

+324
-119
lines changed

5 files changed

+324
-119
lines changed

BitwardenShared/Core/Platform/Services/AuthenticatorSyncService.swift

Lines changed: 47 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@ actor DefaultAuthenticatorSyncService: NSObject, AuthenticatorSyncService {
2525
private let authBridgeItemService: AuthenticatorBridgeItemService
2626

2727
/// The Tasks listening for Cipher updates (one for each user, indexed by the userId).
28-
private var cipherPublisherTasks = [String: Task<Void, Error>?]()
28+
private var cipherPublisherTasks = [String: Task<Void, Error>]()
2929

3030
/// The service used to manage syncing and updates to the user's ciphers.
31-
private let cipherService: CipherService
31+
private let cipherDataStore: CipherDataStore
3232

3333
/// The service that handles common client functionality such as encryption and decryption.
3434
private let clientService: ClientService
@@ -51,10 +51,6 @@ actor DefaultAuthenticatorSyncService: NSObject, AuthenticatorSyncService {
5151
/// The service used by the application to manage account state.
5252
private let stateService: StateService
5353

54-
/// a Task that subscribes to the sync setting publisher for accounts. This allows us to take action once
55-
/// a user opts-in to Authenticator sync.
56-
private var syncSettingSubscriberTask: Task<Void, Error>?
57-
5854
/// The service used by the application to manage vault access.
5955
private let vaultTimeoutService: VaultTimeoutService
6056

@@ -64,7 +60,7 @@ actor DefaultAuthenticatorSyncService: NSObject, AuthenticatorSyncService {
6460
///
6561
/// - Parameters:
6662
/// - authBridgeItemService: The service for managing sharing items to/from the Authenticator app.
67-
/// - cipherService: The service used to manage syncing and updates to the user's ciphers.
63+
/// - cipherDataStore: The service used to manage syncing and updates to the user's ciphers.
6864
/// - clientService: The service that handles common client functionality such as encryption and decryption.
6965
/// - configService: The service to get server-specified configuration.
7066
/// - errorReporter: The service used by the application to report non-fatal errors.\ organizations.
@@ -76,7 +72,7 @@ actor DefaultAuthenticatorSyncService: NSObject, AuthenticatorSyncService {
7672
///
7773
init(
7874
authBridgeItemService: AuthenticatorBridgeItemService,
79-
cipherService: CipherService,
75+
cipherDataStore: CipherDataStore,
8076
clientService: ClientService,
8177
configService: ConfigService,
8278
errorReporter: ErrorReporter,
@@ -86,7 +82,7 @@ actor DefaultAuthenticatorSyncService: NSObject, AuthenticatorSyncService {
8682
vaultTimeoutService: VaultTimeoutService
8783
) {
8884
self.authBridgeItemService = authBridgeItemService
89-
self.cipherService = cipherService
85+
self.cipherDataStore = cipherDataStore
9086
self.clientService = clientService
9187
self.configService = configService
9288
self.errorReporter = errorReporter
@@ -102,9 +98,33 @@ actor DefaultAuthenticatorSyncService: NSObject, AuthenticatorSyncService {
10298
public func start() async {
10399
guard !started else { return }
104100
started = true
105-
if await configService.getFeatureFlag(FeatureFlag.enableAuthenticatorSync,
106-
defaultValue: false) {
107-
subscribeToAppState()
101+
102+
guard await configService.getFeatureFlag(FeatureFlag.enableAuthenticatorSync,
103+
defaultValue: false) else {
104+
return
105+
}
106+
107+
Task {
108+
for await (userId, _) in await self.stateService.syncToAuthenticatorPublisher().values {
109+
guard let userId else { continue }
110+
111+
do {
112+
try await determineSyncForUserId(userId)
113+
} catch {
114+
errorReporter.log(error: error)
115+
}
116+
}
117+
}
118+
Task {
119+
for await vaultStatus in await self.vaultTimeoutService.vaultLockStatusPublisher().values {
120+
guard let vaultStatus else { continue }
121+
122+
do {
123+
try await determineSyncForUserId(vaultStatus.userId)
124+
} catch {
125+
errorReporter.log(error: error)
126+
}
127+
}
108128
}
109129
}
110130

@@ -138,10 +158,10 @@ actor DefaultAuthenticatorSyncService: NSObject, AuthenticatorSyncService {
138158
&& cipher.login?.totp != nil
139159
}
140160
let decryptedCiphers = try await totpCiphers.asyncMap { cipher in
141-
try await self.clientService.vault().ciphers().decrypt(cipher: cipher)
161+
try await self.clientService.vault(for: userId).ciphers().decrypt(cipher: cipher)
142162
}
143163
let account = try await stateService.getActiveAccount()
144-
let username = account.profile.name ?? account.profile.email
164+
let username = account.profile.email
145165

146166
return decryptedCiphers.map { cipher in
147167
AuthenticatorBridgeItemDataView(
@@ -154,43 +174,24 @@ actor DefaultAuthenticatorSyncService: NSObject, AuthenticatorSyncService {
154174
}
155175
}
156176

157-
/// This function handles the initial syncing with the Authenticator app as well as listening for updates
158-
/// when the user adds new items. This is called when the sync is turned on.
177+
/// Determine if the given userId has sync turned on and an unlocked vault. This method serves as the
178+
/// integration point of both the sync settings subscriber and the vault subscriber. When the user has sync turned
179+
/// on and the vault unlocked, we can proceed with the sync.
159180
///
160-
/// - Parameter userId: The userId of the user who has turned on sync.
181+
/// - Parameter userId: The userId of the user whose sync status is being determined.
161182
///
162-
private func handleSyncOnForUserId(_ userId: String) async {
163-
guard !vaultTimeoutService.isLocked(userId: userId) else {
183+
private func determineSyncForUserId(_ userId: String) async throws {
184+
guard try await stateService.getSyncToAuthenticator(userId: userId),
185+
!vaultTimeoutService.isLocked(userId: userId) else {
186+
cipherPublisherTasks[userId]?.cancel()
187+
cipherPublisherTasks.removeValue(forKey: userId)
164188
return
165189
}
166190

167-
do {
168-
try await createAuthenticatorKeyIfNeeded()
169-
} catch {
170-
errorReporter.log(error: error)
171-
}
191+
try await createAuthenticatorKeyIfNeeded()
172192
subscribeToCipherUpdates(userId: userId)
173193
}
174194

175-
/// This function handles stopping sync and cleaning up all sync-related items when a user has turned sync Off.
176-
///
177-
/// - Parameter userId: The userId of the user who has turned off sync.
178-
///
179-
private func handleSyncOffForUserId(_ userId: String) {
180-
cipherPublisherTasks[userId]??.cancel()
181-
cipherPublisherTasks[userId] = nil
182-
}
183-
184-
/// Subscribe to NotificationCenter updates about if the app is in the foreground vs. background.
185-
///
186-
private func subscribeToAppState() {
187-
Task {
188-
for await _ in notificationCenterService.willEnterForegroundPublisher() {
189-
subscribeToSyncToAuthenticatorSetting()
190-
}
191-
}
192-
}
193-
194195
/// Create a task for the given userId to listen for Cipher updates and sync to the Authenticator store.
195196
///
196197
/// - Parameter userId: The userId of the account to listen for.
@@ -200,7 +201,7 @@ actor DefaultAuthenticatorSyncService: NSObject, AuthenticatorSyncService {
200201

201202
cipherPublisherTasks[userId] = Task {
202203
do {
203-
for try await ciphers in try await self.cipherService.ciphersPublisher().values {
204+
for try await ciphers in self.cipherDataStore.cipherPublisher(userId: userId).values {
204205
try await writeCiphers(ciphers: ciphers, userId: userId)
205206
}
206207
} catch {
@@ -209,31 +210,15 @@ actor DefaultAuthenticatorSyncService: NSObject, AuthenticatorSyncService {
209210
}
210211
}
211212

212-
/// Subscribe to the Sync to Authenticator setting to handle when the user grants (or revokes)
213-
/// permission to sync items to the Authenticator app.
214-
///
215-
private func subscribeToSyncToAuthenticatorSetting() {
216-
syncSettingSubscriberTask?.cancel()
217-
syncSettingSubscriberTask = Task {
218-
for await (userId, shouldSync) in await self.stateService.syncToAuthenticatorPublisher().values {
219-
guard let userId else { continue }
220-
221-
if shouldSync {
222-
await handleSyncOnForUserId(userId)
223-
} else {
224-
handleSyncOffForUserId(userId)
225-
}
226-
}
227-
}
228-
}
229-
230213
/// Takes in a list of encrypted Ciphers, decrypts them, and writes ones with TOTP codes to the shared store.
231214
///
232215
/// - Parameters:
233216
/// - ciphers: The array of Ciphers belonging to a user to decrypt and store if necessary.
234217
/// - userId: The userId of the account to which the Ciphers belong.
235218
///
236219
private func writeCiphers(ciphers: [Cipher], userId: String) async throws {
220+
guard !vaultTimeoutService.isLocked(userId: userId) else { return }
221+
237222
let items = try await decryptTOTPs(ciphers, userId: userId)
238223
try await authBridgeItemService.replaceAllItems(with: items, forUserId: userId)
239224
}

0 commit comments

Comments
 (0)