Skip to content

Commit 6da628e

Browse files
fedemkrKatherineInCodematt-livefront2060k12aj-rosado
authored
Backport 'release/2024.10-rc1' (#1108)
Co-authored-by: Katherine Bertelsen <[email protected]> Co-authored-by: Matt Czech <[email protected]> Co-authored-by: Pranish <[email protected]> Co-authored-by: aj-rosado <[email protected]> Co-authored-by: Phil Cappelli <[email protected]> Co-authored-by: Álison Fernandes <[email protected]>
1 parent 91e8329 commit 6da628e

File tree

10 files changed

+91
-40
lines changed

10 files changed

+91
-40
lines changed

BitwardenShared/Core/Auth/Services/AuthService.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -407,11 +407,11 @@ class DefaultAuthService: AuthService { // swiftlint:disable:this type_body_leng
407407
minNumber: 1,
408408
minSpecial: 0
409409
)
410-
codeVerifier = try await clientService.generators().password(settings: passwordSettings)
410+
codeVerifier = try await clientService.generators(isPreAuth: true).password(settings: passwordSettings)
411411
let codeChallenge = Data(codeVerifier.utf8)
412412
.generatedHashBase64Encoded(using: SHA256.self)
413413
.urlEncoded()
414-
let state = try await clientService.generators().password(settings: passwordSettings)
414+
let state = try await clientService.generators(isPreAuth: true).password(settings: passwordSettings)
415415

416416
let queryItems = [
417417
URLQueryItem(name: "client_id", value: Constants.clientType),

BitwardenShared/Core/Platform/Services/ClientService.swift

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,13 @@ protocol ClientService {
3232

3333
/// Returns a `ClientGeneratorsProtocol` for generator data tasks.
3434
///
35-
/// - Parameter userId: The user ID mapped to the client instance.
35+
/// - Parameters:
36+
/// - userId: The user ID mapped to the client instance.
37+
/// - isPreAuth: Whether the client is being used for a user prior to authentication (when
38+
/// the user's ID doesn't yet exist).
3639
/// - Returns: A `ClientGeneratorsProtocol` for generator data tasks.
3740
///
38-
func generators(for userId: String?) async throws -> ClientGeneratorsProtocol
41+
func generators(for userId: String?, isPreAuth: Bool) async throws -> ClientGeneratorsProtocol
3942

4043
/// Returns a `ClientPlatformService` for client platform tasks.
4144
///
@@ -68,18 +71,12 @@ protocol ClientService {
6871
// MARK: Extension
6972

7073
extension ClientService {
71-
/// Returns a `ClientAuthProtocol` for auth data tasks.
72-
///
73-
func auth() async throws -> ClientAuthProtocol {
74-
try await auth(for: nil, isPreAuth: false)
75-
}
76-
7774
/// Returns a `ClientAuthProtocol` for auth data tasks.
7875
///
7976
/// - Parameter isPreAuth: Whether the client is being used for a user prior to authentication
8077
/// (when the user's ID doesn't yet exist).
8178
///
82-
func auth(isPreAuth: Bool) async throws -> ClientAuthProtocol {
79+
func auth(isPreAuth: Bool = false) async throws -> ClientAuthProtocol {
8380
try await auth(for: nil, isPreAuth: isPreAuth)
8481
}
8582

@@ -97,8 +94,11 @@ extension ClientService {
9794

9895
/// Returns a `ClientGeneratorsProtocol` for generator data tasks.
9996
///
100-
func generators() async throws -> ClientGeneratorsProtocol {
101-
try await generators(for: nil)
97+
/// - Parameter isPreAuth: Whether the client is being used for a user prior to authentication
98+
/// (when the user's ID doesn't yet exist). This primarily will happen in SSO flows.
99+
///
100+
func generators(isPreAuth: Bool = false) async throws -> ClientGeneratorsProtocol {
101+
try await generators(for: nil, isPreAuth: isPreAuth)
102102
}
103103

104104
/// Returns a `ClientPlatformService` for client platform tasks.
@@ -203,8 +203,8 @@ actor DefaultClientService: ClientService {
203203
try await client(for: userId).exporters()
204204
}
205205

206-
func generators(for userId: String?) async throws -> ClientGeneratorsProtocol {
207-
try await client(for: userId).generators()
206+
func generators(for userId: String?, isPreAuth: Bool = false) async throws -> ClientGeneratorsProtocol {
207+
try await client(for: userId, isPreAuth: isPreAuth).generators()
208208
}
209209

210210
func platform(for userId: String?) async throws -> ClientPlatformService {

BitwardenShared/Core/Platform/Services/ClientServiceTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@ final class ClientServiceTests: BitwardenTestCase { // swiftlint:disable:this ty
352352
func test_generators() async throws {
353353
stateService.activeAccount = .fixture(profile: .fixture(userId: "1"))
354354

355-
let generators = try await subject.generators()
355+
let generators = try await subject.generators(isPreAuth: false)
356356
XCTAssertIdentical(generators, clientBuilder.clients.first?.clientGenerators)
357357

358358
let user2Generators = try await subject.generators(for: "2")

BitwardenShared/Core/Platform/Services/TestHelpers/MockClientService.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ class MockClientService: ClientService {
99
var mockCrypto: MockClientCrypto
1010
var mockExporters: MockClientExporters
1111
var mockGenerators: MockClientGenerators
12+
var mockGeneratorsIsPreAuth = false
13+
var mockGeneratorsUserId: String?
1214
var mockPlatform: MockClientPlatformService
1315
var mockSends: MockClientSends
1416
var mockVault: MockClientVaultService
@@ -46,8 +48,10 @@ class MockClientService: ClientService {
4648
mockExporters
4749
}
4850

49-
func generators(for userId: String?) -> ClientGeneratorsProtocol {
50-
mockGenerators
51+
func generators(for userId: String?, isPreAuth: Bool) -> ClientGeneratorsProtocol {
52+
mockGeneratorsIsPreAuth = isPreAuth
53+
mockGeneratorsUserId = userId
54+
return mockGenerators
5155
}
5256

5357
func platform(for userId: String?) -> ClientPlatformService {

BitwardenShared/Core/Tools/Repositories/GeneratorRepository.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ extension DefaultGeneratorRepository: GeneratorRepository {
171171
// MARK: Generator
172172

173173
func generateMasterPassword() async throws -> String {
174-
try await clientService.generators().passphrase(
174+
try await clientService.generators(isPreAuth: false).passphrase(
175175
settings: PassphraseGeneratorRequest(
176176
numWords: 3,
177177
wordSeparator: "-",
@@ -182,15 +182,15 @@ extension DefaultGeneratorRepository: GeneratorRepository {
182182
}
183183

184184
func generatePassphrase(settings: PassphraseGeneratorRequest) async throws -> String {
185-
try await clientService.generators().passphrase(settings: settings)
185+
try await clientService.generators(isPreAuth: false).passphrase(settings: settings)
186186
}
187187

188188
func generatePassword(settings: PasswordGeneratorRequest) async throws -> String {
189-
try await clientService.generators().password(settings: settings)
189+
try await clientService.generators(isPreAuth: false).password(settings: settings)
190190
}
191191

192192
func generateUsername(settings: UsernameGeneratorRequest) async throws -> String {
193-
try await clientService.generators().username(settings: settings)
193+
try await clientService.generators(isPreAuth: false).username(settings: settings)
194194
}
195195

196196
func getPasswordGenerationOptions() async throws -> PasswordGenerationOptions {

BitwardenShared/Core/Vault/Repositories/VaultRepository.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1071,7 +1071,7 @@ extension DefaultVaultRepository: VaultRepository {
10711071

10721072
func needsSync() async throws -> Bool {
10731073
let userId = try await stateService.getActiveAccountId()
1074-
return try await syncService.needsSync(for: userId)
1074+
return try await syncService.needsSync(for: userId, onlyCheckLocalData: true)
10751075
}
10761076

10771077
func refreshTOTPCode(for key: TOTPKeyModel) async throws -> LoginTOTPState {

BitwardenShared/Core/Vault/Repositories/VaultRepositoryTests.swift

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -760,6 +760,33 @@ class VaultRepositoryTests: BitwardenTestCase { // swiftlint:disable:this type_b
760760
XCTAssertTrue(isDisabled)
761761
}
762762

763+
/// `needsSync()` Calls the sync service to check it.
764+
func test_needsSync() async throws {
765+
stateService.activeAccount = .fixture()
766+
syncService.needsSyncResult = .success(true)
767+
let needsSync = try await subject.needsSync()
768+
XCTAssertTrue(needsSync)
769+
XCTAssertTrue(syncService.needsSyncOnlyCheckLocalData)
770+
}
771+
772+
/// `needsSync()` throws when no active account.
773+
func test_needsSync_throwsNoActiveAccount() async throws {
774+
stateService.activeAccount = nil
775+
await assertAsyncThrows(error: StateServiceError.noActiveAccount) {
776+
_ = try await subject.needsSync()
777+
}
778+
}
779+
780+
/// `needsSync()` throws when sync service throws.
781+
func test_needsSync_throwsSyncService() async throws {
782+
stateService.activeAccount = .fixture()
783+
syncService.needsSyncResult = .failure(BitwardenTestError.example)
784+
await assertAsyncThrows(error: BitwardenTestError.example) {
785+
_ = try await subject.needsSync()
786+
}
787+
XCTAssertTrue(syncService.needsSyncOnlyCheckLocalData)
788+
}
789+
763790
/// `isVaultEmpty()` throws an error if one occurs.
764791
func test_isVaultEmpty_error() async {
765792
cipherService.cipherCountResult = .failure(BitwardenTestError.example)

BitwardenShared/Core/Vault/Services/SyncService.swift

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,13 @@ protocol SyncService: AnyObject {
5757
func fetchUpsertSyncSend(data: SyncSendNotification) async throws
5858

5959
/// Does a given account need a sync?
60+
/// - Parameters:
61+
/// - userId: The user id of the account
62+
/// - onlyCheckLocalData: If `true` it will only check local data to establish whether sync is needed.
63+
/// Otherwise, it can also perform requests to server to have additional data to check.
6064
///
61-
/// - Parameter userId: The user id of the account.
6265
/// - Returns: A bool indicating if the user needs a sync or not.
63-
///
64-
func needsSync(for userId: String) async throws -> Bool
66+
func needsSync(for userId: String, onlyCheckLocalData: Bool) async throws -> Bool
6567
}
6668

6769
// MARK: - SyncServiceDelegate
@@ -188,14 +190,8 @@ class DefaultSyncService: SyncService {
188190
self.timeProvider = timeProvider
189191
}
190192

191-
/// Determine if a full sync is necessary.
192-
///
193-
/// - Parameters:
194-
/// - userId: The user ID of the account to sync.
195-
/// - Returns: Whether a sync should be performed.
196-
///
197-
func needsSync(for userId: String) async throws -> Bool {
198-
try await needsSync(forceSync: false, userId: userId)
193+
func needsSync(for userId: String, onlyCheckLocalData: Bool) async throws -> Bool {
194+
try await needsSync(forceSync: false, onlyCheckLocalData: onlyCheckLocalData, userId: userId)
199195
}
200196

201197
// MARK: Private
@@ -205,13 +201,22 @@ class DefaultSyncService: SyncService {
205201
/// - Parameters:
206202
/// - forceSync: Whether syncing should be forced, bypassing the account revision and minimum
207203
/// sync interval checks.
204+
/// - onlyCheckLocalData: If `true` it will only check local data to establish whether sync is needed.
205+
/// Otherwise, it can also perform requests to server to have additional data to check.
208206
/// - userId: The user ID of the account to sync.
209207
/// - Returns: Whether a sync should be performed.
210208
///
211-
private func needsSync(forceSync: Bool, userId: String) async throws -> Bool {
212-
guard let lastSyncTime = try await stateService.getLastSyncTime(userId: userId), !forceSync else { return true }
213-
guard lastSyncTime.addingTimeInterval(Constants.minimumSyncInterval)
214-
< timeProvider.presentTime else { return false }
209+
private func needsSync(forceSync: Bool, onlyCheckLocalData: Bool = false, userId: String) async throws -> Bool {
210+
guard !forceSync, let lastSyncTime = try await stateService.getLastSyncTime(userId: userId) else {
211+
return true
212+
}
213+
guard lastSyncTime.addingTimeInterval(Constants.minimumSyncInterval) < timeProvider.presentTime else {
214+
return false
215+
}
216+
guard !onlyCheckLocalData else {
217+
return true
218+
}
219+
215220
do {
216221
guard let accountRevisionDate = try await accountAPIService.accountRevisionDate()
217222
else { return true }

BitwardenShared/Core/Vault/Services/SyncServiceTests.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -727,6 +727,19 @@ class SyncServiceTests: BitwardenTestCase {
727727
try await subject.fetchUpsertSyncSend(data: notification)
728728
XCTAssertEqual(sendService.syncSendWithServerId, "id")
729729
}
730+
731+
/// `needsSync(forceSync:onlyCheckLocalData:userId:)` returns `true` when
732+
/// only checking local data and not enough time hasn't passed since the last sync.
733+
func test_needsSync_onlyCheckLocalData() async throws {
734+
stateService.activeAccount = .fixture()
735+
let lastSync = timeProvider.presentTime.addingTimeInterval(-(Constants.minimumSyncInterval + 1))
736+
stateService.lastSyncTimeByUserId["1"] = try XCTUnwrap(
737+
lastSync
738+
)
739+
let needsSync = try await subject.needsSync(for: "1", onlyCheckLocalData: true)
740+
XCTAssertTrue(needsSync)
741+
XCTAssertTrue(client.requests.isEmpty)
742+
}
730743
}
731744

732745
class MockSyncServiceDelegate: SyncServiceDelegate {

BitwardenShared/Core/Vault/Services/TestHelpers/MockSyncService.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class MockSyncService: SyncService {
2727
var fetchUpsertSyncSendResult: Result<Void, Error> = .success(())
2828

2929
var needsSyncResult: Result<Bool, Error> = .success(false)
30+
var needsSyncOnlyCheckLocalData: Bool = false
3031

3132
func fetchSync(forceSync: Bool) async throws {
3233
didFetchSync = true
@@ -64,7 +65,8 @@ class MockSyncService: SyncService {
6465
return try fetchUpsertSyncSendResult.get()
6566
}
6667

67-
func needsSync(for userId: String) async throws -> Bool {
68-
try needsSyncResult.get()
68+
func needsSync(for userId: String, onlyCheckLocalData: Bool) async throws -> Bool {
69+
needsSyncOnlyCheckLocalData = onlyCheckLocalData
70+
return try needsSyncResult.get()
6971
}
7072
}

0 commit comments

Comments
 (0)