diff --git a/Package.resolved b/Package.resolved index 8a2d1a4ad..f99c535b3 100644 --- a/Package.resolved +++ b/Package.resolved @@ -23,8 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/duckduckgo-autofill.git", "state" : { - "revision" : "c8e895c8fd50dc76e8d8dc827a636ad77b7f46ff", - "version" : "9.0.0" + "revision" : "93677cc02cfe650ce7f417246afd0e8e972cd83e", + "version" : "10.0.0" } }, { diff --git a/Package.swift b/Package.swift index c2279e3bb..d48542daa 100644 --- a/Package.swift +++ b/Package.swift @@ -32,7 +32,7 @@ let package = Package( .library(name: "SecureStorage", targets: ["SecureStorage"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/duckduckgo-autofill.git", exact: "9.0.0"), + .package(url: "https://github.com/duckduckgo/duckduckgo-autofill.git", exact: "10.0.0"), .package(url: "https://github.com/duckduckgo/GRDB.swift.git", exact: "2.2.0"), .package(url: "https://github.com/duckduckgo/TrackerRadarKit", exact: "1.2.1"), .package(url: "https://github.com/duckduckgo/sync_crypto", exact: "0.2.0"), diff --git a/Sources/BrowserServicesKit/Autofill/AutofillUserScript+SecureVault.swift b/Sources/BrowserServicesKit/Autofill/AutofillUserScript+SecureVault.swift index 5399d41ac..2acd30a95 100644 --- a/Sources/BrowserServicesKit/Autofill/AutofillUserScript+SecureVault.swift +++ b/Sources/BrowserServicesKit/Autofill/AutofillUserScript+SecureVault.swift @@ -67,6 +67,9 @@ public protocol AutofillSecureVaultDelegate: AnyObject { func autofillUserScript(_: AutofillUserScript, didRequestCredentialsForDomain domain: String, completionHandler: @escaping ([SecureVaultModels.WebsiteCredentials], SecureVaultModels.CredentialsProvider) -> Void) + func autofillUserScript(_: AutofillUserScript, didRequestRuntimeConfigurationForDomain domain: String, + completionHandler: @escaping (String?) -> Void) + func autofillUserScriptDidOfferGeneratedPassword(_: AutofillUserScript, password: String, completionHandler: @escaping (Bool) -> Void) @@ -429,6 +432,14 @@ extension AutofillUserScript { // MARK: - Message Handlers + func getRuntimeConfiguration(_ message: UserScriptMessage, _ replyHandler: @escaping MessageReplyHandler) { + let domain = hostForMessage(message) + + vaultDelegate?.autofillUserScript(self, didRequestRuntimeConfigurationForDomain: domain, completionHandler: { response in + replyHandler(response) + }) + } + func getAvailableInputTypes(_ message: UserScriptMessage, _ replyHandler: @escaping MessageReplyHandler) { let domain = hostForMessage(message) let email = emailDelegate?.autofillUserScriptDidRequestSignedInStatus(self) ?? false diff --git a/Sources/BrowserServicesKit/Autofill/AutofillUserScript+SourceProvider.swift b/Sources/BrowserServicesKit/Autofill/AutofillUserScript+SourceProvider.swift index c4ae795a1..ed553daf7 100644 --- a/Sources/BrowserServicesKit/Autofill/AutofillUserScript+SourceProvider.swift +++ b/Sources/BrowserServicesKit/Autofill/AutofillUserScript+SourceProvider.swift @@ -25,40 +25,114 @@ public protocol AutofillUserScriptSourceProvider { } public class DefaultAutofillSourceProvider: AutofillUserScriptSourceProvider { - - private var sourceStr: String - + + private struct ProviderData { + var privacyConfig: Data + var userUnprotectedDomains: Data + var userPreferences: Data + } + + let privacyConfigurationManager: PrivacyConfigurationManaging + let properties: ContentScopeProperties + private var sourceStr: String = "" + public var source: String { return sourceStr } - + public init(privacyConfigurationManager: PrivacyConfigurationManaging, properties: ContentScopeProperties) { + self.privacyConfigurationManager = privacyConfigurationManager + self.properties = properties + } + + public func loadJS() { + guard let replacements = buildReplacementsString() else { + sourceStr = "" + return + } + sourceStr = AutofillUserScript.loadJS("assets/autofill", from: Autofill.bundle, withReplacements: replacements) + } + + public func buildRuntimeConfigResponse() -> String? { + guard let providerData = buildReplacementsData(), + let privacyConfigJson = String(data: providerData.privacyConfig, encoding: .utf8), + let userUnprotectedDomainsString = String(data: providerData.userUnprotectedDomains, encoding: .utf8), + let userPreferencesString = String(data: providerData.userPreferences, encoding: .utf8) else { + return nil + } + + return """ + { + "success": { + "contentScope": \(privacyConfigJson), + "userUnprotectedDomains": \(userUnprotectedDomainsString), + "userPreferences": \(userPreferencesString) + } + } + """ + } + + private func buildReplacementsString() -> [String: String]? { var replacements: [String: String] = [:] - #if os(macOS) - replacements["// INJECT isApp HERE"] = "isApp = true;" - #endif +#if os(macOS) + replacements["// INJECT isApp HERE"] = "isApp = true;" +#endif if #available(iOS 14, macOS 11, *) { replacements["// INJECT hasModernWebkitAPI HERE"] = "hasModernWebkitAPI = true;" - - #if os(macOS) - replacements["// INJECT supportsTopFrame HERE"] = "supportsTopFrame = true;" - #endif + +#if os(macOS) + replacements["// INJECT supportsTopFrame HERE"] = "supportsTopFrame = true;" +#endif } - - guard let privacyConfigJson = String(data: privacyConfigurationManager.currentConfig, encoding: .utf8), - let userUnprotectedDomains = try? JSONEncoder().encode(privacyConfigurationManager.privacyConfig.userUnprotectedDomains), - let userUnprotectedDomainsString = String(data: userUnprotectedDomains, encoding: .utf8), - let jsonProperties = try? JSONEncoder().encode(properties), - let jsonPropertiesString = String(data: jsonProperties, encoding: .utf8) - else { - sourceStr = "" - return + + guard let providerData = buildReplacementsData(), + let privacyConfigJson = String(data: providerData.privacyConfig, encoding: .utf8), + let userUnprotectedDomainsString = String(data: providerData.userUnprotectedDomains, encoding: .utf8), + let userPreferencesString = String(data: providerData.userPreferences, encoding: .utf8) else { + return nil } + replacements["// INJECT contentScope HERE"] = "contentScope = " + privacyConfigJson + ";" replacements["// INJECT userUnprotectedDomains HERE"] = "userUnprotectedDomains = " + userUnprotectedDomainsString + ";" - replacements["// INJECT userPreferences HERE"] = "userPreferences = " + jsonPropertiesString + ";" + replacements["// INJECT userPreferences HERE"] = "userPreferences = " + userPreferencesString + ";" + return replacements + } - sourceStr = AutofillUserScript.loadJS("assets/autofill", from: Autofill.bundle, withReplacements: replacements) + private func buildReplacementsData() -> ProviderData? { + guard let userUnprotectedDomains = try? JSONEncoder().encode(privacyConfigurationManager.privacyConfig.userUnprotectedDomains), + let jsonProperties = try? JSONEncoder().encode(properties) else { + return nil + } + return ProviderData(privacyConfig: privacyConfigurationManager.currentConfig, + userUnprotectedDomains: userUnprotectedDomains, + userPreferences: jsonProperties) + } + + public class Builder { + private var privacyConfigurationManager: PrivacyConfigurationManaging + private var properties: ContentScopeProperties + private var sourceStr: String = "" + private var shouldLoadJS: Bool = false + + public init(privacyConfigurationManager: PrivacyConfigurationManaging, properties: ContentScopeProperties) { + self.privacyConfigurationManager = privacyConfigurationManager + self.properties = properties + } + + public func build() -> DefaultAutofillSourceProvider { + let provider = DefaultAutofillSourceProvider(privacyConfigurationManager: privacyConfigurationManager, properties: properties) + + if shouldLoadJS { + provider.loadJS() + } + + return provider + } + + public func withJSLoading() -> Builder { + self.shouldLoadJS = true + return self + } } } diff --git a/Sources/BrowserServicesKit/Autofill/AutofillUserScript.swift b/Sources/BrowserServicesKit/Autofill/AutofillUserScript.swift index f9958171c..5b9ad133e 100644 --- a/Sources/BrowserServicesKit/Autofill/AutofillUserScript.swift +++ b/Sources/BrowserServicesKit/Autofill/AutofillUserScript.swift @@ -47,6 +47,7 @@ public class AutofillUserScript: NSObject, UserScript, UserScriptMessageEncrypti case pmHandlerOpenManageIdentities case pmHandlerOpenManagePasswords + case getRuntimeConfiguration case getAvailableInputTypes case getAutofillData case storeFormData @@ -68,6 +69,8 @@ public class AutofillUserScript: NSObject, UserScript, UserScriptMessageEncrypti /// once the user selects a field to open, we store field type and other contextual information to be initialized into the top autofill. public var serializedInputContext: String? + public var sessionKey: String? + public weak var emailDelegate: AutofillEmailDelegate? public weak var vaultDelegate: AutofillSecureVaultDelegate? @@ -131,7 +134,8 @@ public class AutofillUserScript: NSObject, UserScript, UserScriptMessageEncrypti case .emailHandlerCheckAppSignedInStatus: return emailCheckSignedInStatus case .pmHandlerGetAutofillInitData: return pmGetAutoFillInitData - + + case .getRuntimeConfiguration: return getRuntimeConfiguration case .getAvailableInputTypes: return getAvailableInputTypes case .getAutofillData: return getAutofillData case .storeFormData: return pmStoreData diff --git a/Sources/BrowserServicesKit/ContentScopeScript/ContentScopeUserScript.swift b/Sources/BrowserServicesKit/ContentScopeScript/ContentScopeUserScript.swift index a3dacf036..920b069b7 100644 --- a/Sources/BrowserServicesKit/ContentScopeScript/ContentScopeUserScript.swift +++ b/Sources/BrowserServicesKit/ContentScopeScript/ContentScopeUserScript.swift @@ -60,7 +60,7 @@ public struct ContentScopeFeatureToggles: Encodable { public let credentialsSaving: Bool - public let passwordGeneration: Bool + public var passwordGeneration: Bool public let inlineIconCredentials: Bool public let thirdPartyCredentialsProvider: Bool diff --git a/Sources/BrowserServicesKit/SecureVault/AutofillDatabaseProvider.swift b/Sources/BrowserServicesKit/SecureVault/AutofillDatabaseProvider.swift index aea2ce96f..f4ff9d6ab 100644 --- a/Sources/BrowserServicesKit/SecureVault/AutofillDatabaseProvider.swift +++ b/Sources/BrowserServicesKit/SecureVault/AutofillDatabaseProvider.swift @@ -35,6 +35,12 @@ public protocol AutofillDatabaseProvider: SecureStorageDatabaseProvider { func websiteAccountsForTopLevelDomain(_ eTLDplus1: String) throws -> [SecureVaultModels.WebsiteAccount] func deleteWebsiteCredentialsForAccountId(_ accountId: Int64) throws + func neverPromptWebsites() throws -> [SecureVaultModels.NeverPromptWebsites] + func hasNeverPromptWebsitesFor(domain: String) throws -> Bool + @discardableResult + func storeNeverPromptWebsite(_ neverPromptWebsite: SecureVaultModels.NeverPromptWebsites) throws -> Int64 + func deleteAllNeverPromptWebsites() throws + func notes() throws -> [SecureVaultModels.Note] func noteForNoteId(_ noteId: Int64) throws -> SecureVaultModels.Note? @discardableResult @@ -93,6 +99,7 @@ public final class DefaultAutofillDatabaseProvider: GRDBSecureStorageDatabasePro migrator.registerMigration("v9", migrate: Self.migrateV9(database:)) migrator.registerMigration("v10", migrate: Self.migrateV10(database:)) migrator.registerMigration("v11", migrate: Self.migrateV11(database:)) + migrator.registerMigration("v12", migrate: Self.migrateV12(database:)) } } } @@ -335,6 +342,54 @@ public final class DefaultAutofillDatabaseProvider: GRDBSecureStorageDatabasePro } } + // MARK: NeverPromptWebsites + + public func neverPromptWebsites() throws -> [SecureVaultModels.NeverPromptWebsites] { + try db.read { + try SecureVaultModels.NeverPromptWebsites.fetchAll($0) + } + } + + public func hasNeverPromptWebsitesFor(domain: String) throws -> Bool { + let neverPromptWebsite = try db.read { + try SecureVaultModels.NeverPromptWebsites + .filter(SecureVaultModels.NeverPromptWebsites.Columns.domain.like(domain)) + .fetchOne($0) + } + return neverPromptWebsite != nil + } + + public func storeNeverPromptWebsite(_ neverPromptWebsite: SecureVaultModels.NeverPromptWebsites) throws -> Int64 { + if let id = neverPromptWebsite.id { + try updateNeverPromptWebsite(neverPromptWebsite, usingId: id) + return id + } else { + return try insertNeverPromptWebsite(neverPromptWebsite) + } + } + + public func deleteAllNeverPromptWebsites() throws { + try db.write { + try $0.execute(sql: """ + DELETE FROM + \(SecureVaultModels.NeverPromptWebsites.databaseTableName) + """) + } + } + + func updateNeverPromptWebsite(_ neverPromptWebsite: SecureVaultModels.NeverPromptWebsites, usingId id: Int64) throws { + try db.write { + try neverPromptWebsite.update($0) + } + } + + func insertNeverPromptWebsite(_ neverPromptWebsite: SecureVaultModels.NeverPromptWebsites) throws -> Int64 { + try db.write { + try neverPromptWebsite.insert($0) + return $0.lastInsertedRowID + } + } + // MARK: Notes public func notes() throws -> [SecureVaultModels.Note] { @@ -886,6 +941,15 @@ extension DefaultAutofillDatabaseProvider { } } + static func migrateV12(database: Database) throws { + + try database.create(table: SecureVaultModels.NeverPromptWebsites.databaseTableName) { + $0.autoIncrementedPrimaryKey(SecureVaultModels.NeverPromptWebsites.Columns.id.name) + + $0.column(SecureVaultModels.NeverPromptWebsites.Columns.domain.name, .text) + } + } + // Refresh password comparison hashes static private func updatePasswordHashes(database: Database) throws { let accountRows = try Row.fetchCursor(database, sql: "SELECT * FROM \(SecureVaultModels.WebsiteAccount.databaseTableName)") @@ -1027,6 +1091,26 @@ extension SecureVaultModels.WebsiteCredentials { } +extension SecureVaultModels.NeverPromptWebsites: PersistableRecord, FetchableRecord { + + public enum Columns: String, ColumnExpression { + case id, domain + } + + public init(row: Row) { + id = row[Columns.id] + domain = row[Columns.domain] + } + + public func encode(to container: inout PersistenceContainer) { + container[Columns.id] = id + container[Columns.domain] = domain + } + + public static var databaseTableName: String = "never_prompt_websites" + +} + extension SecureVaultModels.CreditCard: PersistableRecord, FetchableRecord { enum Columns: String, ColumnExpression { diff --git a/Sources/BrowserServicesKit/SecureVault/AutofillSecureVault.swift b/Sources/BrowserServicesKit/SecureVault/AutofillSecureVault.swift index ee51040e8..a0a3256e4 100644 --- a/Sources/BrowserServicesKit/SecureVault/AutofillSecureVault.swift +++ b/Sources/BrowserServicesKit/SecureVault/AutofillSecureVault.swift @@ -65,6 +65,12 @@ public protocol AutofillSecureVault: SecureVault { func storeWebsiteCredentials(_ credentials: SecureVaultModels.WebsiteCredentials) throws -> Int64 func deleteWebsiteCredentialsFor(accountId: Int64) throws + func neverPromptWebsites() throws -> [SecureVaultModels.NeverPromptWebsites] + func hasNeverPromptWebsitesFor(domain: String) throws -> Bool + @discardableResult + func storeNeverPromptWebsites(_ neverPromptWebsite: SecureVaultModels.NeverPromptWebsites) throws -> Int64 + func deleteAllNeverPromptWebsites() throws + func notes() throws -> [SecureVaultModels.Note] func noteFor(id: Int64) throws -> SecureVaultModels.Note? @discardableResult @@ -361,6 +367,45 @@ public class DefaultAutofillSecureVault: AutofillSe } } + // MARK: NeverPromptWebsites + + public func neverPromptWebsites() throws -> [SecureVaultModels.NeverPromptWebsites] { + lock.lock() + defer { + lock.unlock() + } + + do { + return try self.providers.database.neverPromptWebsites() + } catch { + throw SecureStorageError.databaseError(cause: error) + } + } + + public func hasNeverPromptWebsitesFor(domain: String) throws -> Bool { + lock.lock() + defer { + lock.unlock() + } + do { + return try self.providers.database.hasNeverPromptWebsitesFor(domain: domain) + } catch { + throw SecureStorageError.databaseError(cause: error) + } + } + + public func storeNeverPromptWebsites(_ neverPromptWebsite: SecureVaultModels.NeverPromptWebsites) throws -> Int64 { + return try executeThrowingDatabaseOperation { + return try self.providers.database.storeNeverPromptWebsite(neverPromptWebsite) + } + } + + public func deleteAllNeverPromptWebsites() throws { + try executeThrowingDatabaseOperation { + try self.providers.database.deleteAllNeverPromptWebsites() + } + } + // MARK: - Notes public func notes() throws -> [SecureVaultModels.Note] { diff --git a/Sources/BrowserServicesKit/SecureVault/SecureVaultManager.swift b/Sources/BrowserServicesKit/SecureVault/SecureVaultManager.swift index b420ff4ca..3b7b44652 100644 --- a/Sources/BrowserServicesKit/SecureVault/SecureVaultManager.swift +++ b/Sources/BrowserServicesKit/SecureVault/SecureVaultManager.swift @@ -71,6 +71,10 @@ public protocol SecureVaultManagerDelegate: AnyObject, SecureVaultErrorReporting func secureVaultManager(_: SecureVaultManager, didRequestPasswordManagerForDomain domain: String) + func secureVaultManager(_: SecureVaultManager, + didRequestRuntimeConfigurationForDomain domain: String, + completionHandler: @escaping (String?) -> Void) + func secureVaultManager(_: SecureVaultManager, didReceivePixel: AutofillUserScript.JSPixel) } @@ -227,7 +231,12 @@ extension SecureVaultManager: AutofillSecureVaultDelegate { autosaveAccount = nil autosaveAccountCreatedInSession = false } - + + // Do not autosave anything if user has requested to never be prompted to save credentials for this domain + if let neverPrompt = try vault?.hasNeverPromptWebsitesFor(domain: domain), neverPrompt { + return + } + // Validate the existing account exists and matches the domain and fetch the credentials if let stringId = autosaveAccount?.id, let id = Int64(stringId), @@ -526,6 +535,14 @@ extension SecureVaultManager: AutofillSecureVaultDelegate { delegate?.secureVaultManager(self, didReceivePixel: pixel) } + public func autofillUserScript(_: AutofillUserScript, + didRequestRuntimeConfigurationForDomain domain: String, + completionHandler: @escaping (String?) -> Void) { + delegate?.secureVaultManager(self, didRequestRuntimeConfigurationForDomain: domain, completionHandler: { response in + completionHandler(response) + }) + } + /// Stores autogenerated credentials sent by the AutofillUserScript, or updates an existing row in the database if credentials already exist. func storeOrUpdateAutogeneratedCredentials(domain: String, autofillData: AutofillUserScript.DetectedAutofillData) throws { diff --git a/Sources/BrowserServicesKit/SecureVault/SecureVaultModels.swift b/Sources/BrowserServicesKit/SecureVault/SecureVaultModels.swift index 15b738ee7..d9a35ccce 100644 --- a/Sources/BrowserServicesKit/SecureVault/SecureVaultModels.swift +++ b/Sources/BrowserServicesKit/SecureVault/SecureVaultModels.swift @@ -162,6 +162,18 @@ public struct SecureVaultModels { } + public struct NeverPromptWebsites { + public var id: Int64? + public var domain: String + + public init(id: Int64? = nil, + domain: String) { + self.id = id + self.domain = domain + } + + } + public struct CreditCard { private enum Constants { diff --git a/Tests/BrowserServicesKitTests/Autofill/AutofillEmailUserScriptTests.swift b/Tests/BrowserServicesKitTests/Autofill/AutofillEmailUserScriptTests.swift index c174cd525..03363bafe 100644 --- a/Tests/BrowserServicesKitTests/Autofill/AutofillEmailUserScriptTests.swift +++ b/Tests/BrowserServicesKitTests/Autofill/AutofillEmailUserScriptTests.swift @@ -39,8 +39,10 @@ class AutofillEmailUserScriptTests: XCTestCase { """.data(using: .utf8)! let privacyConfig = AutofillTestHelper.preparePrivacyConfig(embeddedConfig: embeddedConfig) let properties = ContentScopeProperties(gpcEnabled: false, sessionKey: "1234", featureToggles: ContentScopeFeatureToggles.allTogglesOn) - let sourceProvider = DefaultAutofillSourceProvider(privacyConfigurationManager: privacyConfig, - properties: properties) + let sourceProvider = DefaultAutofillSourceProvider.Builder(privacyConfigurationManager: privacyConfig, + properties: properties) + .withJSLoading() + .build() return AutofillUserScript(scriptSourceProvider: sourceProvider, encrypter: MockEncrypter(), hostProvider: SecurityOriginHostProvider()) }() let userContentController = WKUserContentController() diff --git a/Tests/BrowserServicesKitTests/Autofill/AutofillUserScriptSourceProviderTests.swift b/Tests/BrowserServicesKitTests/Autofill/AutofillUserScriptSourceProviderTests.swift new file mode 100644 index 000000000..5f0fc7e57 --- /dev/null +++ b/Tests/BrowserServicesKitTests/Autofill/AutofillUserScriptSourceProviderTests.swift @@ -0,0 +1,57 @@ +// +// AutofillUserScriptSourceProviderTests.swift +// DuckDuckGo +// +// Copyright © 2023 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import XCTest +import BrowserServicesKit + +final class AutofillUserScriptSourceProviderTests: XCTestCase { + + let embeddedConfig = + """ + { + "features": { + "autofill": { + "status": "enabled", + "exceptions": [] + } + }, + "unprotectedTemporary": [] + } + """.data(using: .utf8)! + lazy var privacyConfig = AutofillTestHelper.preparePrivacyConfig(embeddedConfig: embeddedConfig) + let properties = ContentScopeProperties(gpcEnabled: false, sessionKey: "1234", featureToggles: ContentScopeFeatureToggles.allTogglesOn) + + func testWhenBuildWithLoadJSThenSourceStrIsBuilt() { + let autofillSourceProvider = DefaultAutofillSourceProvider.Builder(privacyConfigurationManager: privacyConfig, + properties: properties) + .withJSLoading() + .build() + XCTAssertFalse(autofillSourceProvider.source.isEmpty) + } + + func testWhenBuildRuntimeConfigurationThenConfigurationIsBuilt() { + let runtimeConfiguration = DefaultAutofillSourceProvider.Builder(privacyConfigurationManager: privacyConfig, + properties: properties) + .build() + .buildRuntimeConfigResponse() + + XCTAssertNotNil(runtimeConfiguration) + XCTAssertFalse(runtimeConfiguration!.isEmpty) + } +} diff --git a/Tests/BrowserServicesKitTests/Autofill/AutofillVaultUserScriptTests.swift b/Tests/BrowserServicesKitTests/Autofill/AutofillVaultUserScriptTests.swift index 68e3c1f8c..5f1e54f1e 100644 --- a/Tests/BrowserServicesKitTests/Autofill/AutofillVaultUserScriptTests.swift +++ b/Tests/BrowserServicesKitTests/Autofill/AutofillVaultUserScriptTests.swift @@ -447,6 +447,19 @@ class AutofillVaultUserScriptTests: XCTestCase { XCTAssertEqual(delegate.lastDomain, "example.com") } + + func testWhenGetRuntimeConfigurationIsCalled_ThenDelegateIsCalled() { + let delegate = MockSecureVaultDelegate() + userScript.vaultDelegate = delegate + + let mockWebView = MockWebView() + let message = MockUserScriptMessage(name: "getRuntimeConfiguration", body: encryptedMessagingParams, + host: "example.com", webView: mockWebView) + + userScript.processEncryptedMessage(message, from: userContentController) + + XCTAssertEqual(delegate.lastDomain, "example.com") + } func testWhenInitializingAutofillData_WhenCredentialsAreProvidedWithoutAUsername_ThenAutofillDataIsStillInitialized() { let password = "password" @@ -557,6 +570,7 @@ class MockSecureVaultDelegate: AutofillSecureVaultDelegate { case didRequestStoreDataForDomain case didRequestAccountsForDomain case didRequestCredentialsForDomain + case didRequestRuntimeConfigurationForDomain } var receivedCallbacks: [CallbackType] = [] @@ -638,6 +652,11 @@ class MockSecureVaultDelegate: AutofillSecureVaultDelegate { } + func autofillUserScript(_: BrowserServicesKit.AutofillUserScript, didRequestRuntimeConfigurationForDomain domain: String, completionHandler: @escaping (String?) -> Void) { + lastDomain = domain + receivedCallbacks.append(.didRequestRuntimeConfigurationForDomain) + } + func autofillUserScriptDidOfferGeneratedPassword(_: BrowserServicesKit.AutofillUserScript, password: String, completionHandler: @escaping (Bool) -> Void) { } diff --git a/Tests/BrowserServicesKitTests/SecureVault/MockAutofillDatabaseProvider.swift b/Tests/BrowserServicesKitTests/SecureVault/MockAutofillDatabaseProvider.swift index bfb5fa939..c4dd9956d 100644 --- a/Tests/BrowserServicesKitTests/SecureVault/MockAutofillDatabaseProvider.swift +++ b/Tests/BrowserServicesKitTests/SecureVault/MockAutofillDatabaseProvider.swift @@ -24,6 +24,7 @@ import GRDB internal class MockAutofillDatabaseProvider: AutofillDatabaseProvider { var _accounts = [SecureVaultModels.WebsiteAccount]() + var _neverPromptWebsites = [SecureVaultModels.NeverPromptWebsites]() var _notes = [SecureVaultModels.Note]() var _identities = [Int64: SecureVaultModels.Identity]() var _creditCards = [Int64: SecureVaultModels.CreditCard]() @@ -181,4 +182,31 @@ internal class MockAutofillDatabaseProvider: AutofillDatabaseProvider { [] } + func neverPromptWebsites() throws -> [SecureVaultModels.NeverPromptWebsites] { + return _neverPromptWebsites + } + + func hasNeverPromptWebsitesFor(domain: String) throws -> Bool { + return !_neverPromptWebsites.filter { $0.domain == domain }.isEmpty + } + + func storeNeverPromptWebsite(_ neverPromptWebsite: SecureVaultModels.NeverPromptWebsites) throws -> Int64 { + if let neverPromptWebsiteId = neverPromptWebsite.id { + _neverPromptWebsites.append(neverPromptWebsite) + return neverPromptWebsiteId + } else { + return -1 + } + } + + func deleteAllNeverPromptWebsites() throws { + _neverPromptWebsites.removeAll() + } + + func updateNeverPromptWebsite(_ neverPromptWebsite: SecureVaultModels.NeverPromptWebsites) throws { + } + + func insertNeverPromptWebsite(_ neverPromptWebsite: SecureVaultModels.NeverPromptWebsites) throws { + } + } diff --git a/Tests/BrowserServicesKitTests/SecureVault/SecureVaultManagerTests.swift b/Tests/BrowserServicesKitTests/SecureVault/SecureVaultManagerTests.swift index 88be89f21..d5b675fee 100644 --- a/Tests/BrowserServicesKitTests/SecureVault/SecureVaultManagerTests.swift +++ b/Tests/BrowserServicesKitTests/SecureVault/SecureVaultManagerTests.swift @@ -757,6 +757,8 @@ private class MockSecureVaultManagerDelegate: SecureVaultManagerDelegate { func secureVaultManager(_: SecureVaultManager, didRequestPasswordManagerForDomain domain: String) {} + func secureVaultManager(_: SecureVaultManager, didRequestRuntimeConfigurationForDomain domain: String, completionHandler: @escaping (String?) -> Void) {} + func secureVaultManager(_: SecureVaultManager, didReceivePixel: AutofillUserScript.JSPixel) {} } diff --git a/Tests/BrowserServicesKitTests/SecureVault/SecureVaultTests.swift b/Tests/BrowserServicesKitTests/SecureVault/SecureVaultTests.swift index 8c7e77de0..42d77442a 100644 --- a/Tests/BrowserServicesKitTests/SecureVault/SecureVaultTests.swift +++ b/Tests/BrowserServicesKitTests/SecureVault/SecureVaultTests.swift @@ -225,4 +225,24 @@ class SecureVaultTests: XCTestCase { } } + func testWhenRetrievingNeverPromptWebsites_ThenDatabaseIsCalled() throws { + mockDatabaseProvider._neverPromptWebsites = [ + .init(domain: "example.com") + ] + + let neverPromptWebsites = try testVault.neverPromptWebsites() + XCTAssertEqual(neverPromptWebsites.count, 1) + XCTAssertEqual(neverPromptWebsites.first?.domain, "example.com") + } + + func testWhenDeletingAllNeverPromptWebsites_ThenDatabaseIsCalled() throws { + mockDatabaseProvider._neverPromptWebsites = [ + .init(domain: "example.com") + ] + + try testVault.deleteAllNeverPromptWebsites() + + let neverPromptWebsites = try testVault.neverPromptWebsites() + XCTAssertEqual(neverPromptWebsites.count, 0) + } }