Skip to content

Commit 3de0343

Browse files
authored
[PM-9849] Fido2 creation list selection (#763)
1 parent 5d78b16 commit 3de0343

23 files changed

+1147
-55
lines changed

BitwardenShared/Core/Vault/Extensions/BitwardenSdk+Vault.swift

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,51 @@ extension BitwardenSdk.Cipher {
317317
}
318318

319319
extension BitwardenSdk.CipherListView: Identifiable {}
320-
extension BitwardenSdk.CipherView: Identifiable {}
320+
321+
extension BitwardenSdk.CipherView: Identifiable {
322+
/// Initializes a new `CipherView` based on a `Fido2CredentialNewView`
323+
/// - Parameters:
324+
/// - fido2CredentialNewView: The `Fido2CredentialNewView` for the Fido2 creation flow
325+
/// - timeProvider: The time provider.
326+
init(fido2CredentialNewView: Fido2CredentialNewView, timeProvider: TimeProvider) {
327+
self = CipherView(
328+
id: nil,
329+
organizationId: nil,
330+
folderId: nil,
331+
collectionIds: [],
332+
key: nil,
333+
name: fido2CredentialNewView.rpName ?? fido2CredentialNewView.rpId,
334+
notes: nil,
335+
type: .login,
336+
login: BitwardenSdk.LoginView(
337+
username: fido2CredentialNewView.userName ?? "",
338+
password: nil,
339+
passwordRevisionDate: nil,
340+
uris: [
341+
LoginUriView(uri: fido2CredentialNewView.rpId, match: nil, uriChecksum: nil),
342+
],
343+
totp: nil,
344+
autofillOnPageLoad: nil,
345+
fido2Credentials: nil
346+
),
347+
identity: nil,
348+
card: nil,
349+
secureNote: nil,
350+
favorite: false,
351+
reprompt: .none,
352+
organizationUseTotp: false,
353+
edit: false,
354+
viewPassword: true,
355+
localData: nil,
356+
attachments: nil,
357+
fields: nil,
358+
passwordHistory: nil,
359+
creationDate: timeProvider.presentTime,
360+
deletedDate: nil,
361+
revisionDate: timeProvider.presentTime
362+
)
363+
}
364+
}
321365

322366
extension BitwardenSdk.CipherType {
323367
init(_ cipherType: CipherType) {
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
// swiftlint:disable:this file_name
2+
3+
import BitwardenSdk
4+
import XCTest
5+
6+
@testable import BitwardenShared
7+
8+
// MARK: - CipherView
9+
10+
class CipherViewTests: BitwardenTestCase {
11+
// MARK: Properties
12+
13+
var timeProvider: MockTimeProvider!
14+
15+
// MARK: Setup & Teardown
16+
17+
override func setUp() {
18+
super.setUp()
19+
20+
timeProvider = MockTimeProvider(.mockTime(Date(year: 2024, month: 6, day: 28)))
21+
}
22+
23+
override func tearDown() {
24+
super.tearDown()
25+
26+
timeProvider = nil
27+
}
28+
29+
// MARK: Tests
30+
31+
/// `init(fido2CredentialNewView:timeProvider:)` initializes correctly
32+
func test_init_fido2CredentialNewView_defaultValues() {
33+
let fido2CredentialNewView = Fido2CredentialNewView.fixture()
34+
let subject = CipherView(fido2CredentialNewView: fido2CredentialNewView, timeProvider: timeProvider)
35+
XCTAssertEqual(
36+
subject,
37+
CipherView(
38+
id: nil,
39+
organizationId: nil,
40+
folderId: nil,
41+
collectionIds: [],
42+
key: nil,
43+
name: "myApp.com",
44+
notes: nil,
45+
type: .login,
46+
login: BitwardenSdk.LoginView(
47+
username: "",
48+
password: nil,
49+
passwordRevisionDate: nil,
50+
uris: [
51+
LoginUriView(uri: "myApp.com", match: nil, uriChecksum: nil),
52+
],
53+
totp: nil,
54+
autofillOnPageLoad: nil,
55+
fido2Credentials: nil
56+
),
57+
identity: nil,
58+
card: nil,
59+
secureNote: nil,
60+
favorite: false,
61+
reprompt: .none,
62+
organizationUseTotp: false,
63+
edit: false,
64+
viewPassword: true,
65+
localData: nil,
66+
attachments: nil,
67+
fields: nil,
68+
passwordHistory: nil,
69+
creationDate: timeProvider.presentTime,
70+
deletedDate: nil,
71+
revisionDate: timeProvider.presentTime
72+
)
73+
)
74+
}
75+
76+
/// `init(fido2CredentialNewView:timeProvider:)` initializes correctly with rpName and username
77+
func test_init_fido2CredentialNewView_rpNameUsername() {
78+
let fido2CredentialNewView = Fido2CredentialNewView.fixture(
79+
userName: "username",
80+
rpName: "MyApp"
81+
)
82+
let subject = CipherView(fido2CredentialNewView: fido2CredentialNewView, timeProvider: timeProvider)
83+
XCTAssertEqual(
84+
subject,
85+
CipherView(
86+
id: nil,
87+
organizationId: nil,
88+
folderId: nil,
89+
collectionIds: [],
90+
key: nil,
91+
name: "MyApp",
92+
notes: nil,
93+
type: .login,
94+
login: BitwardenSdk.LoginView(
95+
username: "username",
96+
password: nil,
97+
passwordRevisionDate: nil,
98+
uris: [
99+
LoginUriView(uri: "myApp.com", match: nil, uriChecksum: nil),
100+
],
101+
totp: nil,
102+
autofillOnPageLoad: nil,
103+
fido2Credentials: nil
104+
),
105+
identity: nil,
106+
card: nil,
107+
secureNote: nil,
108+
favorite: false,
109+
reprompt: .none,
110+
organizationUseTotp: false,
111+
edit: false,
112+
viewPassword: true,
113+
localData: nil,
114+
attachments: nil,
115+
fields: nil,
116+
passwordHistory: nil,
117+
creationDate: timeProvider.presentTime,
118+
deletedDate: nil,
119+
revisionDate: timeProvider.presentTime
120+
)
121+
)
122+
}
123+
}

BitwardenShared/Core/Vault/Repositories/TestHelpers/MockVaultRepository.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ class MockVaultRepository: VaultRepository {
1010
var addCipherCiphers = [CipherView]()
1111
var addCipherResult: Result<Void, Error> = .success(())
1212

13+
var ciphersAutofillPublisherUriCalled: String?
1314
var ciphersSubject = CurrentValueSubject<[CipherListView], Error>([])
1415
var ciphersAutofillSubject = CurrentValueSubject<[BitwardenShared.VaultListSection], Error>([])
1516
var cipherDetailsSubject = CurrentValueSubject<CipherView?, Error>(.fixture())
@@ -126,7 +127,8 @@ class MockVaultRepository: VaultRepository {
126127
rpID: String?,
127128
uri: String?
128129
) async throws -> AsyncThrowingPublisher<AnyPublisher<[BitwardenShared.VaultListSection], Error>> {
129-
ciphersAutofillSubject.eraseToAnyPublisher().values
130+
ciphersAutofillPublisherUriCalled = uri
131+
return ciphersAutofillSubject.eraseToAnyPublisher().values
130132
}
131133

132134
func clearTemporaryDownloads() {

BitwardenShared/Core/Vault/Repositories/VaultRepository.swift

Lines changed: 52 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1355,6 +1355,15 @@ extension DefaultVaultRepository: VaultRepository {
13551355
rpID: String?,
13561356
searchText: String?
13571357
) async throws -> [VaultListSection] {
1358+
guard mode != .combinedSingleSection else {
1359+
guard !ciphers.isEmpty else {
1360+
return []
1361+
}
1362+
1363+
let section = try await createAutofillListCombinedSingleSection(from: ciphers)
1364+
return [section]
1365+
}
1366+
13581367
var sections = [VaultListSection]()
13591368
if #available(iOSApplicationExtension 17.0, *),
13601369
let fido2Section = try await loadAutofillFido2Section(
@@ -1385,6 +1394,48 @@ extension DefaultVaultRepository: VaultRepository {
13851394
return sections
13861395
}
13871396

1397+
/// Creates the single vault list section for passwords + Fido2 credentials.
1398+
/// - Parameter ciphers: Ciphers to load.
1399+
/// - Returns: The section to display passwords + Fido2 credentials.
1400+
private func createAutofillListCombinedSingleSection(
1401+
from ciphers: [CipherView]
1402+
) async throws -> VaultListSection {
1403+
let vaultItems = try await ciphers
1404+
.asyncMap { cipher in
1405+
guard cipher.hasFido2Credentials else {
1406+
return VaultListItem(cipherView: cipher)
1407+
}
1408+
return try await createFido2VaultListItem(from: cipher)
1409+
}
1410+
.compactMap { $0 }
1411+
1412+
return VaultListSection(
1413+
id: Localizations.chooseALoginToSaveThisPasskeyTo,
1414+
items: vaultItems,
1415+
name: Localizations.chooseALoginToSaveThisPasskeyTo
1416+
)
1417+
}
1418+
1419+
/// Creates a `VaultListItem` from a `CipherView` with Fido2 credentials.
1420+
/// - Parameter cipher: Cipher from which create the item.
1421+
/// - Returns: The `VaultListItem` with the cipher and Fido2 credentials.
1422+
func createFido2VaultListItem(from cipher: CipherView) async throws -> VaultListItem? {
1423+
let decryptedFido2Credentials = try await clientService
1424+
.platform()
1425+
.fido2()
1426+
.decryptFido2AutofillCredentials(cipherView: cipher)
1427+
1428+
guard let fido2CredentialAutofillView = decryptedFido2Credentials.first else {
1429+
errorReporter.log(error: Fido2Error.decryptFido2AutofillCredentialsEmpty)
1430+
return nil
1431+
}
1432+
1433+
return VaultListItem(
1434+
cipherView: cipher,
1435+
fido2CredentialAutofillView: fido2CredentialAutofillView
1436+
)
1437+
}
1438+
13881439
/// Gets the passwords vault list section name depending on the context.
13891440
///
13901441
/// - Parameters:
@@ -1447,20 +1498,7 @@ extension DefaultVaultRepository: VaultRepository {
14471498

14481499
let fido2ListItems: [VaultListItem?] = try await filteredFido2Credentials
14491500
.asyncMap { cipher in
1450-
let decryptedFido2Credentials = try await self.clientService
1451-
.platform()
1452-
.fido2()
1453-
.decryptFido2AutofillCredentials(cipherView: cipher)
1454-
1455-
guard let fido2CredentialAutofillView = decryptedFido2Credentials.first else {
1456-
errorReporter.log(error: Fido2Error.decryptFido2AutofillCredentialsEmpty)
1457-
return nil
1458-
}
1459-
1460-
return VaultListItem(
1461-
cipherView: cipher,
1462-
fido2CredentialAutofillView: fido2CredentialAutofillView
1463-
)
1501+
try await createFido2VaultListItem(from: cipher)
14641502
}
14651503

14661504
return VaultListSection(

0 commit comments

Comments
 (0)