diff --git a/Package.resolved b/Package.resolved index e3b0845b1..7e774989e 100644 --- a/Package.resolved +++ b/Package.resolved @@ -15,8 +15,8 @@ "repositoryURL": "https://github.com/duckduckgo/duckduckgo-autofill.git", "state": { "branch": null, - "revision": "27a4cff621893ef9d1b96ec62c99b137a3a73450", - "version": "4.7.1" + "revision": "d29c5abc7f473b69f3d81d8a15e137ed3aab029d", + "version": "5.0.1" } }, { diff --git a/Package.swift b/Package.swift index 030fe135c..b0d10c022 100644 --- a/Package.swift +++ b/Package.swift @@ -14,7 +14,7 @@ let package = Package( .library(name: "BrowserServicesKit", targets: ["BrowserServicesKit"]) ], dependencies: [ - .package(name: "Autofill", url: "https://github.com/duckduckgo/duckduckgo-autofill.git", .exact("4.7.1")), + .package(name: "Autofill", url: "https://github.com/duckduckgo/duckduckgo-autofill.git", .exact("5.0.1")), .package(name: "GRDB", url: "https://github.com/duckduckgo/GRDB.swift.git", .exact("1.1.0")), .package(url: "https://github.com/duckduckgo/TrackerRadarKit", .exact("1.1.1")), .package(name: "Punycode", url: "https://github.com/gumob/PunycodeSwift.git", .exact("2.1.0")), diff --git a/Sources/BrowserServicesKit/Autofill/AutofillUserScript+SecureVault.swift b/Sources/BrowserServicesKit/Autofill/AutofillUserScript+SecureVault.swift index 4c6964da4..e9657a517 100644 --- a/Sources/BrowserServicesKit/Autofill/AutofillUserScript+SecureVault.swift +++ b/Sources/BrowserServicesKit/Autofill/AutofillUserScript+SecureVault.swift @@ -41,6 +41,7 @@ public protocol AutofillSecureVaultDelegate: AnyObject { completionHandler: @escaping ([SecureVaultModels.WebsiteAccount]) -> Void) func autofillUserScript(_: AutofillUserScript, didRequestCredentialsForDomain: String, subType: AutofillUserScript.GetAutofillDataSubType, + trigger: AutofillUserScript.GetTriggerType, completionHandler: @escaping (SecureVaultModels.WebsiteCredentials?, RequestVaultCredentialsAction) -> Void) func autofillUserScript(_: AutofillUserScript, didRequestCredentialsForAccount accountId: Int64, @@ -329,6 +330,7 @@ extension AutofillUserScript { struct GetAutofillDataRequest: Codable { let mainType: GetAutofillDataMainType let subType: GetAutofillDataSubType + let trigger: GetTriggerType } // https://github.com/duckduckgo/duckduckgo-autofill/blob/main/src/deviceApiCalls/schemas/getAutofillData.params.json @@ -342,6 +344,12 @@ extension AutofillUserScript { case username case password } + + // https://github.com/duckduckgo/duckduckgo-autofill/blob/main/src/deviceApiCalls/schemas/getAutofillData.params.json + public enum GetTriggerType: String, Codable { + case userInitiated + case autoprompt + } // https://github.com/duckduckgo/duckduckgo-autofill/blob/main/docs/runtime.ios.md#getautofilldatarequest func getAutofillData(_ message: AutofillMessage, _ replyHandler: @escaping MessageReplyHandler) { @@ -350,7 +358,7 @@ extension AutofillUserScript { } let domain = hostForMessage(message) - vaultDelegate?.autofillUserScript(self, didRequestCredentialsForDomain: domain, subType: request.subType) { credentials, action in + vaultDelegate?.autofillUserScript(self, didRequestCredentialsForDomain: domain, subType: request.subType, trigger: request.trigger) { credentials, action in let response = RequestVaultCredentialsForDomainResponse.responseFromSecureVaultWebsiteCredentials(credentials, action: action) if let json = try? JSONEncoder().encode(response), let jsonString = String(data: json, encoding: .utf8) { diff --git a/Sources/BrowserServicesKit/SecureVault/SecureVaultManager.swift b/Sources/BrowserServicesKit/SecureVault/SecureVaultManager.swift index 5adb964c1..cc2794017 100644 --- a/Sources/BrowserServicesKit/SecureVault/SecureVaultManager.swift +++ b/Sources/BrowserServicesKit/SecureVault/SecureVaultManager.swift @@ -41,6 +41,7 @@ public protocol SecureVaultManagerDelegate: SecureVaultErrorReporting { func secureVaultManager(_: SecureVaultManager, promptUserToAutofillCredentialsForDomain domain: String, withAccounts accounts: [SecureVaultModels.WebsiteAccount], + withTrigger trigger: AutofillUserScript.GetTriggerType, completionHandler: @escaping (SecureVaultModels.WebsiteAccount?) -> Void) func secureVaultManagerShouldAutomaticallyUpdateCredentialsWithoutUsername(_: SecureVaultManager) -> Bool @@ -127,6 +128,7 @@ extension SecureVaultManager: AutofillSecureVaultDelegate { public func autofillUserScript(_: AutofillUserScript, didRequestCredentialsForDomain domain: String, subType: AutofillUserScript.GetAutofillDataSubType, + trigger: AutofillUserScript.GetTriggerType, completionHandler: @escaping (SecureVaultModels.WebsiteCredentials?, RequestVaultCredentialsAction) -> Void) { do { let vault = try self.vault ?? SecureVaultFactory.default.makeVault(errorReporter: self.delegate) @@ -146,7 +148,7 @@ extension SecureVaultManager: AutofillSecureVaultDelegate { return } - delegate?.secureVaultManager(self, promptUserToAutofillCredentialsForDomain: domain, withAccounts: accounts) { account in + delegate?.secureVaultManager(self, promptUserToAutofillCredentialsForDomain: domain, withAccounts: accounts, withTrigger: trigger) { account in guard let accountID = account?.id else { completionHandler(nil, .none) diff --git a/Tests/BrowserServicesKitTests/Autofill/AutofillVaultUserScriptTests.swift b/Tests/BrowserServicesKitTests/Autofill/AutofillVaultUserScriptTests.swift index fa0b1377f..068a8664b 100644 --- a/Tests/BrowserServicesKitTests/Autofill/AutofillVaultUserScriptTests.swift +++ b/Tests/BrowserServicesKitTests/Autofill/AutofillVaultUserScriptTests.swift @@ -353,11 +353,21 @@ class AutofillVaultUserScriptTests: XCTestCase { var body = encryptedMessagingParams body["mainType"] = "credentials" body["subType"] = "username" + body["trigger"] = "userInitiated" let mockWebView = MockWebView() let message = MockAutofillMessage(name: "getAutofillData", body: body, host: "example.com", webView: mockWebView) userScript.processMessage(userContentController, didReceive: message) + + let predicate = NSPredicate(block: { _, _ -> Bool in + return !delegate.receivedCallbacks.isEmpty + }) + + let expectation = XCTNSPredicateExpectation(predicate: predicate, object: delegate.receivedCallbacks) + + wait(for: [expectation], timeout: 5) + XCTAssertEqual(delegate.lastSubtype, AutofillUserScript.GetAutofillDataSubType.username) } @@ -369,6 +379,7 @@ class AutofillVaultUserScriptTests: XCTestCase { var body = encryptedMessagingParams body["mainType"] = "creditCards" // <- unsupported main type body["subType"] = "username" + body["trigger"] = "userInitiated" let mockWebView = MockWebView() let message = MockAutofillMessage(name: "getAutofillData", body: body, host: "example.com", webView: mockWebView) @@ -385,6 +396,7 @@ class AutofillVaultUserScriptTests: XCTestCase { var body = encryptedMessagingParams body["mainType"] = "credentials" body["subType"] = "anything_else" + body["trigger"] = "userInitiated" let mockWebView = MockWebView() let message = MockAutofillMessage(name: "getAutofillData", body: body, host: "example.com", webView: mockWebView) @@ -396,6 +408,15 @@ class AutofillVaultUserScriptTests: XCTestCase { class MockSecureVaultDelegate: AutofillSecureVaultDelegate { + enum CallbackType { + case didRequestPasswordManagerForDomain + case didRequestStoreDataForDomain + case didRequestAccountsForDomain + case didRequestCredentialsForDomain + } + + var receivedCallbacks: [CallbackType] = [] + var lastDomain: String? var lastUsername: String? var lastPassword: String? @@ -403,28 +424,27 @@ class MockSecureVaultDelegate: AutofillSecureVaultDelegate { func autofillUserScript(_: AutofillUserScript, didRequestPasswordManagerForDomain domain: String) { lastDomain = domain + receivedCallbacks.append(.didRequestPasswordManagerForDomain) } func autofillUserScript(_: AutofillUserScript, didRequestStoreDataForDomain domain: String, data: AutofillUserScript.DetectedAutofillData) { lastDomain = domain lastUsername = data.credentials?.username lastPassword = data.credentials?.password + receivedCallbacks.append(.didRequestStoreDataForDomain) } func autofillUserScript(_: AutofillUserScript, didRequestAccountsForDomain domain: String, completionHandler: @escaping ([SecureVaultModels.WebsiteAccount]) -> Void) { lastDomain = domain + receivedCallbacks.append(.didRequestAccountsForDomain) } func autofillUserScript(_: AutofillUserScript, didRequestCredentialsForAccount accountId: Int64, completionHandler: @escaping (SecureVaultModels.WebsiteCredentials?) -> Void) { } - - func autofillUserScript(_: AutofillUserScript, didRequestCredentialsForDomain: String, - completionHandler: @escaping (SecureVaultModels.WebsiteCredentials?, RequestVaultCredentialsAction) -> Void) { - } func autofillUserScript(_: AutofillUserScript, didRequestAutoFillInitDataForDomain domain: String, @@ -446,8 +466,12 @@ class MockSecureVaultDelegate: AutofillSecureVaultDelegate { func autofillUserScript(_ script: AutofillUserScript, didRequestCredentialsForDomain: String, subType: AutofillUserScript.GetAutofillDataSubType, + trigger: AutofillUserScript.GetTriggerType, completionHandler: @escaping (SecureVaultModels.WebsiteCredentials?, RequestVaultCredentialsAction) -> Void) { lastSubtype = subType + receivedCallbacks.append(.didRequestCredentialsForDomain) + + completionHandler(nil, .none) } } diff --git a/Tests/BrowserServicesKitTests/SecureVault/SecureVaultManagerTests.swift b/Tests/BrowserServicesKitTests/SecureVault/SecureVaultManagerTests.swift index 1ea53c25f..13e1a5ec4 100644 --- a/Tests/BrowserServicesKitTests/SecureVault/SecureVaultManagerTests.swift +++ b/Tests/BrowserServicesKitTests/SecureVault/SecureVaultManagerTests.swift @@ -182,6 +182,8 @@ class SecureVaultManagerTests: XCTestCase { } func testWhenRequestingCredentialsWithEmptyUsername_ThenNonActionIsReturned() throws { + let triggerType = AutofillUserScript.GetTriggerType.userInitiated + // account let domain = "domain.com" let username = "" // <- this is a valid scenario @@ -194,7 +196,7 @@ class SecureVaultManagerTests: XCTestCase { let subType = AutofillUserScript.GetAutofillDataSubType.username let expect = expectation(description: #function) - manager.autofillUserScript(mockAutofillUserScript, didRequestCredentialsForDomain: domain, subType: subType) { credentials, action in + manager.autofillUserScript(mockAutofillUserScript, didRequestCredentialsForDomain: domain, subType: subType, trigger: triggerType) { credentials, action in XCTAssertEqual(action, .none) XCTAssertNil(credentials) expect.fulfill() @@ -207,6 +209,7 @@ class SecureVaultManagerTests: XCTestCase { override func secureVaultManager(_ manager: SecureVaultManager, promptUserToAutofillCredentialsForDomain domain: String, withAccounts accounts: [SecureVaultModels.WebsiteAccount], + withTrigger trigger: AutofillUserScript.GetTriggerType, completionHandler: @escaping (SecureVaultModels.WebsiteAccount?) -> Void) { XCTAssertEqual(accounts.count, 1, "The empty username should have been filtered so that it's not shown as an option") completionHandler(accounts[0]) @@ -215,6 +218,8 @@ class SecureVaultManagerTests: XCTestCase { self.secureVaultManagerDelegate = SecureVaultDelegate() self.manager.delegate = self.secureVaultManagerDelegate + + let triggerType = AutofillUserScript.GetTriggerType.userInitiated // account 1 (empty username) let domain = "domain.com" @@ -234,7 +239,7 @@ class SecureVaultManagerTests: XCTestCase { let subType = AutofillUserScript.GetAutofillDataSubType.username let expect = expectation(description: #function) - manager.autofillUserScript(mockAutofillUserScript, didRequestCredentialsForDomain: domain, subType: subType) { credentials, action in + manager.autofillUserScript(mockAutofillUserScript, didRequestCredentialsForDomain: domain, subType: subType, trigger: triggerType) { credentials, action in XCTAssertEqual(action, .fill) XCTAssertEqual(credentials!.password, "password".data(using: .utf8)!) XCTAssertEqual(credentials!.account.username, "dax2") @@ -248,6 +253,7 @@ class SecureVaultManagerTests: XCTestCase { override func secureVaultManager(_ manager: SecureVaultManager, promptUserToAutofillCredentialsForDomain domain: String, withAccounts accounts: [SecureVaultModels.WebsiteAccount], + withTrigger trigger: AutofillUserScript.GetTriggerType, completionHandler: @escaping (SecureVaultModels.WebsiteAccount?) -> Void) { XCTAssertEqual(accounts.count, 2, "Both accounts should be shown since the subType was `password`") completionHandler(accounts[1]) @@ -256,6 +262,8 @@ class SecureVaultManagerTests: XCTestCase { self.secureVaultManagerDelegate = SecureVaultDelegate() self.manager.delegate = self.secureVaultManagerDelegate + + let triggerType = AutofillUserScript.GetTriggerType.userInitiated // account 1 (empty username) let domain = "domain.com" @@ -275,7 +283,7 @@ class SecureVaultManagerTests: XCTestCase { let subType = AutofillUserScript.GetAutofillDataSubType.password let expect = expectation(description: #function) - manager.autofillUserScript(mockAutofillUserScript, didRequestCredentialsForDomain: domain, subType: subType) { credentials, action in + manager.autofillUserScript(mockAutofillUserScript, didRequestCredentialsForDomain: domain, subType: subType, trigger: triggerType) { credentials, action in XCTAssertEqual(action, .fill) XCTAssertEqual(credentials!.password, "password".data(using: .utf8)!) XCTAssertEqual(credentials!.account.username, "dax2") @@ -336,6 +344,7 @@ private class MockSecureVaultManagerDelegate: SecureVaultManagerDelegate { func secureVaultManager(_: SecureVaultManager, promptUserToAutofillCredentialsForDomain domain: String, withAccounts accounts: [SecureVaultModels.WebsiteAccount], + withTrigger trigger: AutofillUserScript.GetTriggerType, completionHandler: @escaping (SecureVaultModels.WebsiteAccount?) -> Void) {} func secureVaultManager(_: SecureVaultManager, didAutofill type: AutofillType, withObjectId objectId: Int64) {}