Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cartfile.resolved
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
github "apple/swift-protobuf" "1.14.0"
github "apple/swift-protobuf" "1.18.0"
github "jrendel/SwiftKeychainWrapper" "4.0.1"
81 changes: 61 additions & 20 deletions components/logins/ios/Logins/LoginsStorage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import UIKit
#if canImport(MozillaRustComponents)
import MozillaRustComponents
#endif
#if canImport(Glean)
@_exported import Glean
#endif

typealias LoginsStoreError = LoginsStorageError

Expand Down Expand Up @@ -73,26 +76,6 @@ open class LoginsStorage {
}
}

open func migrateLogins(
newDbPath: String,
newDbEncKey: String,
sqlCipherDbPath: String,
sqlCipherEncKey: String,
sqlCipherSalt: String
) throws -> String {
return try queue.sync {
// last param is the "salt" which is only used on iOS.

return try self.migrateLogins(
newDbPath: newDbPath,
newDbEncKey: newDbEncKey,
sqlCipherDbPath: sqlCipherDbPath,
sqlCipherEncKey: sqlCipherEncKey,
sqlCipherSalt: sqlCipherSalt
)
}
}

/// Update `login` in the database. If `login.id` does not refer to a known
/// login, then this throws `LoginStoreError.NoSuchRecord`.
open func update(id: String, login: LoginEntry, encryptionKey: String) throws -> EncryptedLogin {
Expand Down Expand Up @@ -142,3 +125,61 @@ open class LoginsStorage {
}
}
}

public func migrateLoginsWithMetrics(
path: String,
newEncryptionKey: String,
sqlcipherPath: String,
sqlcipherKey: String,
salt: String
) -> Bool {
var didMigrationSucceed = false

do {
let metrics = try migrateLogins(
path: path,
newEncryptionKey: newEncryptionKey,
sqlcipherPath: sqlcipherPath,
sqlcipherKey: sqlcipherKey,
salt: salt
)
didMigrationSucceed = true

recordMigrationMetrics(jsonString: metrics)
} catch let err as NSError {
GleanMetrics.LoginsStoreMigration.errors.add(err.localizedDescription)
}
return didMigrationSucceed
}

func recordMigrationMetrics(jsonString: String) {
guard
let data = jsonString.data(using: .utf8),
let json = try? JSONSerialization.jsonObject(with: data, options: []),
let metrics = json as? [String: Any]
else {
return
}

if let processed = metrics["num_processed"] as? Int32 {
GleanMetrics.LoginsStoreMigration.numProcessed.add(processed)
}

if let succeeded = metrics["num_succeeded"] as? Int32 {
GleanMetrics.LoginsStoreMigration.numSucceeded.add(succeeded)
}

if let failed = metrics["num_failed"] as? Int32 {
GleanMetrics.LoginsStoreMigration.numFailed.add(failed)
}

if let duration = metrics["total_duration"] as? UInt64 {
GleanMetrics.LoginsStoreMigration.totalDuration.setRawNanos(duration * 1_000_000)
}

if let errors = metrics["errors"] as? [String] {
for error in errors {
GleanMetrics.LoginsStoreMigration.errors.add(error)
}
}
}
103 changes: 103 additions & 0 deletions components/logins/ios/metrics.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

# This file defines the metrics that will be gathered for the "logins"
# storage component.
# These are emitted for all users of the component. Additional metrics
# specific to the *syncing* of logins are defined in a separate "sync_ping"
# package.
#
# Changes to these metrics require data review, which should take into
# consideration the following known consumers of the logins component
# Android bindings:
#
# * Fenix for Andriod

---
$schema: moz://mozilla.org/schemas/glean/metrics/1-0-0

logins_store_migration:

# Migrations from sqlcipher to sqlite.
num_processed:
type: counter
description: >
The total number of login records processed by the migration
bugs:
- https://github.com/mozilla/application-services/issues/4064
- https://github.com/mozilla/application-services/issues/4102
data_reviews:
- https://github.com/mozilla/application-services/issues/4467
data_sensitivity:
- technical
- interaction
notification_emails:
- synced-client-integrations@mozilla.com
expires: 2022-06-30

num_succeeded:
type: counter
description: >
The total number of login records successfully migrated
bugs:
- https://github.com/mozilla/application-services/issues/4064
- https://github.com/mozilla/application-services/issues/4102
data_reviews:
- https://github.com/mozilla/application-services/issues/4467
data_sensitivity:
- technical
- interaction
notification_emails:
- synced-client-integrations@mozilla.com
expires: 2022-06-30

num_failed:
type: counter
description: >
The total number of login records which failed to migrate
bugs:
- https://github.com/mozilla/application-services/issues/4064
- https://github.com/mozilla/application-services/issues/4102
data_reviews:
- https://github.com/mozilla/application-services/issues/4467
data_sensitivity:
- technical
- interaction
notification_emails:
- synced-client-integrations@mozilla.com
expires: 2022-06-30

total_duration:
type: timespan
time_unit: millisecond
description: >
How long the migration tool
bugs:
- https://github.com/mozilla/application-services/issues/4064
- https://github.com/mozilla/application-services/issues/4102
data_reviews:
- https://github.com/mozilla/application-services/issues/4467
data_sensitivity:
- technical
- interaction
notification_emails:
- synced-client-integrations@mozilla.com
expires: 2022-06-30

# Note glean limits this to 20 items each with a max length of 50 utf8 chars.
errors:
type: string_list
description: >
Errors discovered in the migration.
bugs:
- https://github.com/mozilla/application-services/issues/4064
- https://github.com/mozilla/application-services/issues/4102
data_reviews:
- https://github.com/mozilla/application-services/issues/4467
data_sensitivity:
- technical
- interaction
notification_emails:
- synced-client-integrations@mozilla.com
expires: 2022-06-30
2 changes: 1 addition & 1 deletion components/rc_log/ios/RustLog.swift
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ private class RustLogState {
}

func disable() {
guard let adapter = self.adapter else {
guard let adapter = adapter else {
return
}
self.adapter = nil
Expand Down
1 change: 1 addition & 0 deletions megazords/ios/MozillaAppServices.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -758,6 +758,7 @@
"$(SRCROOT)/../../components/external/glean/glean-core/metrics.yaml",
"$(SRCROOT)/../../components/external/glean/glean-core/pings.yaml",
"$(SRCROOT)/../../components/nimbus/metrics.yaml",
"$(SRCROOT)/../../components/logins/ios/metrics.yaml",
);
name = "Generate Glean metrics";
outputFileListPaths = (
Expand Down
33 changes: 32 additions & 1 deletion megazords/ios/MozillaAppServicesTests/LoginsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,36 @@ import XCTest
@testable import MozillaAppServices

class LoginsTests: XCTestCase {
// TODO: Add migration tests here
var storage: LoginsStorage!

override func setUp() {
Glean.shared.resetGlean(clearStores: true)
}

override func tearDown() {
// This method is called after the invocation of each test method in the class.
}

func testMigrationMetrics() throws {
let json = """
{"fixup_phase":{
"num_processed":0,"num_succeeded":0,"num_failed":0,"total_duration":0,"errors":[]
},
"insert_phase":{"num_processed":0,"num_succeeded":0,"num_failed":0,"total_duration":0,"errors":[]
},
"num_processed":3,"num_succeeded":1,"num_failed":2,"total_duration":53,"errors":[
"Invalid login: Login has illegal field: Origin is Malformed",
"Invalid login: Origin is empty"
]}
"""

recordMigrationMetrics(jsonString: json)
XCTAssertEqual(3, try GleanMetrics.LoginsStoreMigration.numProcessed.testGetValue())
XCTAssertEqual(2, try GleanMetrics.LoginsStoreMigration.numFailed.testGetValue())
XCTAssertEqual(1, try GleanMetrics.LoginsStoreMigration.numSucceeded.testGetValue())
XCTAssertEqual(53, try GleanMetrics.LoginsStoreMigration.totalDuration.testGetValue())

// Note the truncation of the first error string.
XCTAssertEqual(["Invalid login: Login has illegal field: Origin is ", "Invalid login: Origin is empty"], try GleanMetrics.LoginsStoreMigration.errors.testGetValue())
}
}