Skip to content

Commit

Permalink
Merge branch 'main' into sam/update-last-version-run-store
Browse files Browse the repository at this point in the history
* main:
  Entitlements cache fixes (#736)
  Surface the last-used password first in autofill prompt when filling passwords (#731)
  RMF update adding build number to app version check (#708)
  VPN UI improvements (#722)
  Adds Privacy Pro Feature Flags (#717)
  macOS: Display error messaging for cancelled subscriptions  (#714)
  Handle subscription-related iOS use cases (#728)
  Remove hardcoded NetP endpoint (#734)
  Use History in Suggestions on iOS (#705)
  • Loading branch information
samsymons committed Mar 21, 2024
2 parents f18f6c8 + 1ba3e36 commit bff5b95
Show file tree
Hide file tree
Showing 47 changed files with 724 additions and 166 deletions.
26 changes: 26 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ let package = Package(
.library(name: "SecureStorage", targets: ["SecureStorage"]),
.library(name: "Subscription", targets: ["Subscription"]),
.library(name: "History", targets: ["History"]),
.library(name: "Suggestions", targets: ["Suggestions"]),
],
dependencies: [
.package(url: "https://github.com/duckduckgo/duckduckgo-autofill.git", exact: "10.1.0"),
Expand Down Expand Up @@ -112,6 +113,16 @@ let package = Package(
],
plugins: [swiftlintPlugin]
),
.target(
name: "Suggestions",
dependencies: [
"Common"
],
swiftSettings: [
.define("DEBUG", .when(configuration: .debug))
],
plugins: [swiftlintPlugin]
),
.executableTarget(
name: "BookmarksTestDBBuilder",
dependencies: [
Expand Down Expand Up @@ -325,6 +336,7 @@ let package = Package(
.target(
name: "Subscription",
dependencies: [
"BrowserServicesKit",
"Common",
],
swiftSettings: [
Expand All @@ -341,6 +353,13 @@ let package = Package(
],
plugins: [swiftlintPlugin]
),
.testTarget(
name: "SuggestionsTests",
dependencies: [
"Suggestions",
],
plugins: [swiftlintPlugin]
),
.testTarget(
name: "BookmarksTests",
dependencies: [
Expand Down Expand Up @@ -490,6 +509,13 @@ let package = Package(
],
plugins: [swiftlintPlugin]
),
.testTarget(
name: "SubscriptionTests",
dependencies: [
"Subscription",
],
plugins: [swiftlintPlugin]
),
],
cxxLanguageStandard: .cxx11
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public enum PrivacyFeature: String {
case sync
case privacyDashboard
case history
case privacyPro
}

/// An abstraction to be implemented by any "subfeature" of a given `PrivacyConfiguration` feature.
Expand Down Expand Up @@ -112,3 +113,14 @@ public enum AutoconsentSubfeature: String, PrivacySubfeature {

case onByDefault
}

public enum PrivacyProSubfeature: String, Equatable, PrivacySubfeature {
public var parent: PrivacyFeature { .privacyPro }

case isLaunched
case isLaunchedStripe
case allowPurchase
case allowPurchaseStripe
case isLaunchedOverride
case isLaunchedOverrideStripe
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public protocol AutofillDatabaseProvider: SecureStorageDatabaseProvider {
func websiteCredentialsForAccountId(_ accountId: Int64) throws -> SecureVaultModels.WebsiteCredentials?
func websiteAccountsForDomain(_ domain: String) throws -> [SecureVaultModels.WebsiteAccount]
func websiteAccountsForTopLevelDomain(_ eTLDplus1: String) throws -> [SecureVaultModels.WebsiteAccount]
func updateLastUsedForAccountId(_ accountId: Int64) throws
func deleteWebsiteCredentialsForAccountId(_ accountId: Int64) throws
func deleteAllWebsiteCredentials() throws

Expand Down Expand Up @@ -100,6 +101,7 @@ public final class DefaultAutofillDatabaseProvider: GRDBSecureStorageDatabasePro
migrator.registerMigration("v10", migrate: Self.migrateV10(database:))
migrator.registerMigration("v11", migrate: Self.migrateV11(database:))
migrator.registerMigration("v12", migrate: Self.migrateV12(database:))
migrator.registerMigration("v13", migrate: Self.migrateV13(database:))
}
}
}
Expand Down Expand Up @@ -190,6 +192,26 @@ public final class DefaultAutofillDatabaseProvider: GRDBSecureStorageDatabasePro
}
}

public func updateLastUsedForAccountId(_ accountId: Int64) throws {
try db.write {
try updateLastUsed(in: $0, usingId: accountId)
}
}

private func updateLastUsed(in database: Database,
usingId id: Int64) throws {
assert(database.isInsideTransaction)

try database.execute(sql: """
UPDATE
\(SecureVaultModels.WebsiteAccount.databaseTableName)
SET
\(SecureVaultModels.WebsiteAccount.Columns.lastUsed.name) = ?
WHERE
\(SecureVaultModels.WebsiteAccount.Columns.id.name) = ?
""", arguments: [Date(), id])
}

public func deleteSyncableCredentials(_ syncableCredentials: SecureVaultModels.SyncableCredentials, in database: Database) throws {
assert(database.isInsideTransaction)

Expand Down Expand Up @@ -268,6 +290,7 @@ public final class DefaultAutofillDatabaseProvider: GRDBSecureStorageDatabasePro
do {
var account = credentials.account
account.title = account.patternMatchedTitle()
account.lastUsed = Date()

try account.insert(database)
let id = database.lastInsertedRowID
Expand Down Expand Up @@ -972,6 +995,12 @@ extension DefaultAutofillDatabaseProvider {
}
}

static func migrateV13(database: Database) throws {
try database.alter(table: SecureVaultModels.WebsiteAccount.databaseTableName) {
$0.add(column: SecureVaultModels.WebsiteAccount.Columns.lastUsed.name, .date)
}
}

// Refresh password comparison hashes
static private func updatePasswordHashes(database: Database) throws {
let accountRows = try Row.fetchCursor(database, sql: "SELECT * FROM \(SecureVaultModels.WebsiteAccount.databaseTableName)")
Expand Down Expand Up @@ -1074,7 +1103,7 @@ struct MigrationUtility {
extension SecureVaultModels.WebsiteAccount: PersistableRecord, FetchableRecord {

public enum Columns: String, ColumnExpression {
case id, title, username, domain, signature, notes, created, lastUpdated
case id, title, username, domain, signature, notes, created, lastUpdated, lastUsed
}

public init(row: Row) {
Expand All @@ -1086,6 +1115,7 @@ extension SecureVaultModels.WebsiteAccount: PersistableRecord, FetchableRecord {
notes = row[Columns.notes]
created = row[Columns.created]
lastUpdated = row[Columns.lastUpdated]
lastUsed = row[Columns.lastUsed]
}

public func encode(to container: inout PersistenceContainer) {
Expand All @@ -1097,6 +1127,7 @@ extension SecureVaultModels.WebsiteAccount: PersistableRecord, FetchableRecord {
container[Columns.notes] = notes
container[Columns.created] = created
container[Columns.lastUpdated] = Date()
container[Columns.lastUsed] = lastUsed
}

public static var databaseTableName: String = "website_accounts"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ public protocol AutofillSecureVault: SecureVault {
func accountsFor(domain: String) throws -> [SecureVaultModels.WebsiteAccount]
func accountsWithPartialMatchesFor(eTLDplus1: String) throws -> [SecureVaultModels.WebsiteAccount]
func hasAccountFor(username: String?, domain: String?) throws -> Bool
func updateLastUsedFor(accountId: Int64) throws

func websiteCredentialsFor(accountId: Int64) throws -> SecureVaultModels.WebsiteCredentials?
@discardableResult
Expand Down Expand Up @@ -271,6 +272,12 @@ public class DefaultAutofillSecureVault<T: AutofillDatabaseProvider>: AutofillSe
}
}

public func updateLastUsedFor(accountId: Int64) throws {
try executeThrowingDatabaseOperation {
try self.providers.database.updateLastUsedForAccountId(accountId)
}
}

// MARK: - Credentials

public func websiteCredentialsFor(accountId: Int64) throws -> SecureVaultModels.WebsiteCredentials? {
Expand Down
27 changes: 24 additions & 3 deletions Sources/BrowserServicesKit/SecureVault/SecureVaultManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public protocol SecureVaultManagerDelegate: AnyObject, SecureVaultErrorReporting
promptUserToAutofillCredentialsForDomain domain: String,
withAccounts accounts: [SecureVaultModels.WebsiteAccount],
withTrigger trigger: AutofillUserScript.GetTriggerType,
onAccountSelected account: @escaping (SecureVaultModels.WebsiteAccount?) -> Void,
completionHandler: @escaping (SecureVaultModels.WebsiteAccount?) -> Void)

func secureVaultManager(_: SecureVaultManager,
Expand Down Expand Up @@ -342,6 +343,7 @@ extension SecureVaultManager: AutofillSecureVaultDelegate {

}

// swiftlint:disable function_body_length
public func autofillUserScript(_: AutofillUserScript,
didRequestCredentialsForDomain domain: String,
subType: AutofillUserScript.GetAutofillDataSubType,
Expand Down Expand Up @@ -378,7 +380,13 @@ extension SecureVaultManager: AutofillSecureVaultDelegate {

self.delegate?.secureVaultManager(self, promptUserToAutofillCredentialsForDomain: domain,
withAccounts: accounts,
withTrigger: trigger) { [weak self] account in
withTrigger: trigger,
onAccountSelected: { [weak self] account in
guard let accountID = account?.id else {
return
}
self?.updateLastUsedDate(for: accountID, vault: vault)
}, completionHandler: { [weak self] account in
guard let self = self else { return }
guard let accountID = account?.id else {
completionHandler(nil, self.credentialsProvider, .none)
Expand All @@ -394,13 +402,14 @@ extension SecureVaultManager: AutofillSecureVaultDelegate {
completionHandler(credentials, self.credentialsProvider, .fill)
}
}
}
})
}
} catch {
os_log(.error, "Error requesting accounts: %{public}@", error.localizedDescription)
completionHandler(nil, credentialsProvider, .none)
}
}
// swiftlint:enable function_body_length

public func autofillUserScript(_: AutofillUserScript,
didRequestCredentialsForAccount accountId: String,
Expand All @@ -410,6 +419,8 @@ extension SecureVaultManager: AutofillSecureVaultDelegate {
do {
let vault = try self.vault ?? AutofillSecureVaultFactory.makeVault(errorReporter: self.delegate)

updateLastUsedDate(for: accountId, vault: vault)

self.delegate?.secureVaultManager(self, isAuthenticatedFor: .password, completionHandler: { result in
if result == true {
self.getCredentials(for: accountId, from: vault, or: self.passwordManager) { [weak self] credentials, error in
Expand Down Expand Up @@ -608,12 +619,22 @@ extension SecureVaultManager: AutofillSecureVaultDelegate {

private func createAccount(username: String, password: Data, domain: String) -> SecureVaultModels.WebsiteAccount {
let vault = try? self.vault ?? AutofillSecureVaultFactory.makeVault(errorReporter: self.delegate)
var account = SecureVaultModels.WebsiteAccount(username: username, domain: domain)
var account = SecureVaultModels.WebsiteAccount(username: username, domain: domain, lastUsed: Date())
let credentials = try? vault?.storeWebsiteCredentials(SecureVaultModels.WebsiteCredentials(account: account, password: password))
account.id = String(credentials ?? -1)
return account
}

private func updateLastUsedDate(for accountId: String, vault: any AutofillSecureVault) {
guard credentialsProvider.name == .duckduckgo else {
return
}

if let accountIdInt = Int64(accountId) {
try? vault.updateLastUsedFor(accountId: accountIdInt)
}
}

func existingEntries(for domain: String,
autofillData: AutofillUserScript.DetectedAutofillData
) throws -> AutofillData {
Expand Down
47 changes: 37 additions & 10 deletions Sources/BrowserServicesKit/SecureVault/SecureVaultModels.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ public struct SecureVaultModels {
public var notes: String?
public let created: Date
public let lastUpdated: Date
public var lastUsed: Date?

public enum CommonTitlePatterns: String, CaseIterable {
/*
Expand All @@ -64,7 +65,7 @@ public struct SecureVaultModels {
case hostFromTitle = #"^(?:https?:\/\/?)?(?:www\.)?([^\s\/\?]+?\.[^\s\/\?]+)(?=\s*\(|\s*\/|\s*\?|$)"#
}

public init(title: String? = nil, username: String?, domain: String?, signature: String? = nil, notes: String? = nil) {
public init(title: String? = nil, username: String?, domain: String?, signature: String? = nil, notes: String? = nil, lastUsed: Date? = nil) {
self.id = nil
self.title = title
self.username = username
Expand All @@ -73,6 +74,7 @@ public struct SecureVaultModels {
self.notes = notes
self.created = Date()
self.lastUpdated = self.created
self.lastUsed = lastUsed
}

public init(id: String,
Expand All @@ -82,7 +84,8 @@ public struct SecureVaultModels {
signature: String? = nil,
notes: String? = nil,
created: Date,
lastUpdated: Date) {
lastUpdated: Date,
lastUsed: Date? = nil) {
self.id = id
self.title = title
self.username = username
Expand All @@ -91,6 +94,7 @@ public struct SecureVaultModels {
self.notes = notes
self.created = created
self.lastUpdated = lastUpdated
self.lastUsed = lastUsed
}

private var tld: String {
Expand Down Expand Up @@ -571,7 +575,7 @@ extension Array where Element == SecureVaultModels.WebsiteAccount {
return urlComponents.eTLDplus1(tld: tld)
}

// Last Updated > Alphabetical Domain > Alphabetical Username > Empty Usernames
// Last Used > Last Updated > Alphabetical Domain > Alphabetical Username > Empty Usernames
private func compareAccount(_ account1: SecureVaultModels.WebsiteAccount, _ account2: SecureVaultModels.WebsiteAccount) -> Bool {
let username1 = account1.username ?? ""
let username2 = account2.username ?? ""
Expand All @@ -584,13 +588,40 @@ extension Array where Element == SecureVaultModels.WebsiteAccount {
return false
}

if let lastUsedComparisonResult = compareByLastUsed(account1: account1, account2: account2) {
return lastUsedComparisonResult
}

if account1.lastUpdated.withoutTime != account2.lastUpdated.withoutTime {
return account1.lastUpdated.withoutTime > account2.lastUpdated.withoutTime
}

let domain1 = account1.domain ?? ""
let domain2 = account2.domain ?? ""
if let domainComparisonResult = compareByDomain(domain1: account1.domain ?? "", domain2: account2.domain ?? "") {
return domainComparisonResult
}

if !username1.isEmpty && !username2.isEmpty {
return username1 < username2
}

return false
}

private func compareByLastUsed(account1: SecureVaultModels.WebsiteAccount, account2: SecureVaultModels.WebsiteAccount) -> Bool? {
if account1.lastUsed != nil && account2.lastUsed == nil {
return true
} else if account1.lastUsed == nil && account2.lastUsed != nil {
return false
} else if let lastUsed1 = account1.lastUsed, let lastUsed2 = account2.lastUsed {
if lastUsed1 != lastUsed2 {
return lastUsed1 > lastUsed2
}
}

return nil
}

private func compareByDomain(domain1: String, domain2: String) -> Bool? {
if !domain1.isEmpty && domain2.isEmpty {
return true
}
Expand All @@ -603,11 +634,7 @@ extension Array where Element == SecureVaultModels.WebsiteAccount {
return domain1 < domain2
}

if !username1.isEmpty && !username2.isEmpty {
return username1 < username2
}

return false
return nil
}

// Receives a sorted Array, and removes duplicate based signatures
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public actor NetworkProtectionEntitlementMonitor {

// MARK: - Init & deinit

init() {
public init() {
os_log("[+] %{public}@", log: .networkProtectionMemoryLog, type: .debug, String(describing: self))
}

Expand Down
Loading

0 comments on commit bff5b95

Please sign in to comment.