mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-02 10:00:54 +00:00
Bug 1393332 - Add infrastructure for importing payment methods from Chrome-based browsers r=mconley,fluent-reviewers,flod
This tries to retrieve the credit card data from Chrome-based browsers using SQLite queries and then inserts them into the Firefox database. Differential Revision: https://phabricator.services.mozilla.com/D168434
This commit is contained in:
parent
a50a2052fe
commit
8b433920dc
@ -131,7 +131,8 @@ export class ChromeProfileMigrator extends MigratorBase {
|
||||
];
|
||||
if (lazy.ChromeMigrationUtils.supportsLoginsForPlatform) {
|
||||
possibleResourcePromises.push(
|
||||
this._GetPasswordsResource(profileFolder)
|
||||
this._GetPasswordsResource(profileFolder),
|
||||
this._GetPaymentMethodsResource(profileFolder)
|
||||
);
|
||||
}
|
||||
let possibleResources = await Promise.all(possibleResourcePromises);
|
||||
@ -380,6 +381,84 @@ export class ChromeProfileMigrator extends MigratorBase {
|
||||
},
|
||||
};
|
||||
}
|
||||
async _GetPaymentMethodsResource(aProfileFolder) {
|
||||
let paymentMethodsPath = PathUtils.join(aProfileFolder, "Web Data");
|
||||
|
||||
if (!(await IOUtils.exists(paymentMethodsPath))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let rows = await MigrationUtils.getRowsFromDBWithoutLocks(
|
||||
paymentMethodsPath,
|
||||
"Chrome Credit Cards",
|
||||
"SELECT name_on_card, card_number_encrypted, expiration_month, expiration_year FROM credit_cards"
|
||||
).catch(ex => {
|
||||
console.error(ex);
|
||||
});
|
||||
|
||||
if (!rows?.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let {
|
||||
_chromeUserDataPathSuffix,
|
||||
_keychainServiceName,
|
||||
_keychainAccountName,
|
||||
_keychainMockPassphrase = null,
|
||||
} = this;
|
||||
|
||||
return {
|
||||
type: MigrationUtils.resourceTypes.PAYMENT_METHODS,
|
||||
|
||||
async migrate(aCallback) {
|
||||
let crypto;
|
||||
try {
|
||||
if (AppConstants.platform == "win") {
|
||||
let { ChromeWindowsLoginCrypto } = ChromeUtils.importESModule(
|
||||
"resource:///modules/ChromeWindowsLoginCrypto.sys.mjs"
|
||||
);
|
||||
crypto = new ChromeWindowsLoginCrypto(_chromeUserDataPathSuffix);
|
||||
} else if (AppConstants.platform == "macosx") {
|
||||
let { ChromeMacOSLoginCrypto } = ChromeUtils.importESModule(
|
||||
"resource:///modules/ChromeMacOSLoginCrypto.sys.mjs"
|
||||
);
|
||||
crypto = new ChromeMacOSLoginCrypto(
|
||||
_keychainServiceName,
|
||||
_keychainAccountName,
|
||||
_keychainMockPassphrase
|
||||
);
|
||||
} else {
|
||||
aCallback(false);
|
||||
return;
|
||||
}
|
||||
} catch (ex) {
|
||||
// Handle the user canceling Keychain access or other OSCrypto errors.
|
||||
console.error(ex);
|
||||
aCallback(false);
|
||||
return;
|
||||
}
|
||||
|
||||
let cards = [];
|
||||
for (let row of rows) {
|
||||
cards.push({
|
||||
"cc-name": row.getResultByName("name_on_card"),
|
||||
"cc-number": await crypto.decryptData(
|
||||
row.getResultByName("card_number_encrypted"),
|
||||
null
|
||||
),
|
||||
"cc-exp-month": parseInt(
|
||||
row.getResultByName("expiration_month"),
|
||||
10
|
||||
),
|
||||
"cc-exp-year": parseInt(row.getResultByName("expiration_year"), 10),
|
||||
});
|
||||
}
|
||||
|
||||
await MigrationUtils.insertCreditCardsWrapper(cards);
|
||||
aCallback(true);
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async function GetBookmarksResource(aProfileFolder, aBrowserKey) {
|
||||
|
@ -139,6 +139,7 @@ class MigrationUtils {
|
||||
BOOKMARKS: 0x0020,
|
||||
OTHERDATA: 0x0040,
|
||||
SESSION: 0x0080,
|
||||
PAYMENT_METHODS: 0x0100,
|
||||
});
|
||||
|
||||
/**
|
||||
@ -767,6 +768,7 @@ class MigrationUtils {
|
||||
bookmarks: 0,
|
||||
logins: 0,
|
||||
history: 0,
|
||||
cards: 0,
|
||||
};
|
||||
|
||||
getImportedCount(type) {
|
||||
@ -895,6 +897,18 @@ class MigrationUtils {
|
||||
}
|
||||
}
|
||||
|
||||
async insertCreditCardsWrapper(cards) {
|
||||
this._importQuantities.cards += cards.length;
|
||||
let { formAutofillStorage } = ChromeUtils.import(
|
||||
"resource://autofill/FormAutofillStorage.jsm"
|
||||
);
|
||||
|
||||
await formAutofillStorage.initialize();
|
||||
for (let card of cards) {
|
||||
await formAutofillStorage.creditCards.add(card);
|
||||
}
|
||||
}
|
||||
|
||||
initializeUndoData() {
|
||||
gKeepUndoData = true;
|
||||
gUndoData = new Map([
|
||||
|
@ -29,6 +29,7 @@ const kDataToStringMap = new Map([
|
||||
["bookmarks", "browser-data-bookmarks"],
|
||||
["otherdata", "browser-data-otherdata"],
|
||||
["session", "browser-data-session"],
|
||||
["payment_methods", "browser-data-payment-methods"],
|
||||
]);
|
||||
|
||||
var MigrationWizard = {
|
||||
|
Binary file not shown.
@ -0,0 +1,205 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
/* global structuredClone */
|
||||
|
||||
const PROFILE = {
|
||||
id: "Default",
|
||||
name: "Default",
|
||||
};
|
||||
|
||||
const PAYMENT_METHODS = [
|
||||
{
|
||||
name_on_card: "Name Name",
|
||||
card_number: "4532962432748929", // Visa
|
||||
expiration_month: 3,
|
||||
expiration_year: 2027,
|
||||
},
|
||||
{
|
||||
name_on_card: "Name Name Name",
|
||||
card_number: "5359908373796416", // Mastercard
|
||||
expiration_month: 5,
|
||||
expiration_year: 2028,
|
||||
},
|
||||
{
|
||||
name_on_card: "Name",
|
||||
card_number: "346624461807588", // AMEX
|
||||
expiration_month: 4,
|
||||
expiration_year: 2026,
|
||||
},
|
||||
];
|
||||
|
||||
let OSKeyStoreTestUtils;
|
||||
add_setup(async function os_key_store_setup() {
|
||||
({ OSKeyStoreTestUtils } = ChromeUtils.importESModule(
|
||||
"resource://testing-common/OSKeyStoreTestUtils.sys.mjs"
|
||||
));
|
||||
OSKeyStoreTestUtils.setup();
|
||||
registerCleanupFunction(async function cleanup() {
|
||||
await OSKeyStoreTestUtils.cleanup();
|
||||
});
|
||||
});
|
||||
|
||||
let rootDir = do_get_file("chromefiles/", true);
|
||||
|
||||
function checkCardsAreEqual(importedCard, testCard, id) {
|
||||
const CC_NUMBER_RE = /^(\*+)(.{4})$/;
|
||||
|
||||
Assert.equal(
|
||||
importedCard["cc-name"],
|
||||
testCard.name_on_card,
|
||||
"The two logins ID " + id + " have the same name on card"
|
||||
);
|
||||
|
||||
let matches = CC_NUMBER_RE.exec(importedCard["cc-number"]);
|
||||
Assert.notEqual(matches, null);
|
||||
Assert.equal(importedCard["cc-number"].length, testCard.card_number.length);
|
||||
Assert.equal(testCard.card_number.endsWith(matches[2]), true);
|
||||
Assert.notEqual(importedCard["cc-number-encrypted"], "");
|
||||
|
||||
Assert.equal(
|
||||
importedCard["cc-exp-month"],
|
||||
testCard.expiration_month,
|
||||
"The two logins ID " + id + " have the same expiration month"
|
||||
);
|
||||
Assert.equal(
|
||||
importedCard["cc-exp-year"],
|
||||
testCard.expiration_year,
|
||||
"The two logins ID " + id + " have the same expiration year"
|
||||
);
|
||||
}
|
||||
|
||||
add_task(async function setup_fakePaths() {
|
||||
let pathId;
|
||||
if (AppConstants.platform == "macosx") {
|
||||
pathId = "ULibDir";
|
||||
} else if (AppConstants.platform == "win") {
|
||||
pathId = "LocalAppData";
|
||||
} else {
|
||||
pathId = "Home";
|
||||
}
|
||||
registerFakePath(pathId, rootDir);
|
||||
});
|
||||
|
||||
add_task(async function test_credit_cards() {
|
||||
let loginCrypto;
|
||||
let profilePathSegments;
|
||||
|
||||
let mockMacOSKeychain = {
|
||||
passphrase: "bW96aWxsYWZpcmVmb3g=",
|
||||
serviceName: "TESTING Chrome Safe Storage",
|
||||
accountName: "TESTING Chrome",
|
||||
};
|
||||
if (AppConstants.platform == "macosx") {
|
||||
let { ChromeMacOSLoginCrypto } = ChromeUtils.importESModule(
|
||||
"resource:///modules/ChromeMacOSLoginCrypto.sys.mjs"
|
||||
);
|
||||
loginCrypto = new ChromeMacOSLoginCrypto(
|
||||
mockMacOSKeychain.serviceName,
|
||||
mockMacOSKeychain.accountName,
|
||||
mockMacOSKeychain.passphrase
|
||||
);
|
||||
profilePathSegments = [
|
||||
"Application Support",
|
||||
"Google",
|
||||
"Chrome",
|
||||
"Default",
|
||||
];
|
||||
} else if (AppConstants.platform == "win") {
|
||||
let { ChromeWindowsLoginCrypto } = ChromeUtils.importESModule(
|
||||
"resource:///modules/ChromeWindowsLoginCrypto.sys.mjs"
|
||||
);
|
||||
loginCrypto = new ChromeWindowsLoginCrypto("Chrome");
|
||||
profilePathSegments = ["Google", "Chrome", "User Data", "Default"];
|
||||
} else {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
let target = rootDir.clone();
|
||||
let defaultFolderPath = PathUtils.join(target.path, ...profilePathSegments);
|
||||
let webDataPath = PathUtils.join(defaultFolderPath, "Web Data");
|
||||
let localStatePath = defaultFolderPath.replace("Default", "");
|
||||
|
||||
await IOUtils.makeDirectory(defaultFolderPath, {
|
||||
createAncestor: true,
|
||||
ignoreExisting: true,
|
||||
});
|
||||
|
||||
// Copy Web Data database into Default profile
|
||||
const sourcePathWebData = do_get_file(
|
||||
"AppData/Local/Google/Chrome/User Data/Default/Web Data"
|
||||
).path;
|
||||
await IOUtils.copy(sourcePathWebData, webDataPath);
|
||||
|
||||
const sourcePathLocalState = do_get_file(
|
||||
"AppData/Local/Google/Chrome/User Data/Local State"
|
||||
).path;
|
||||
await IOUtils.copy(sourcePathLocalState, localStatePath);
|
||||
|
||||
let dbConn = await Sqlite.openConnection({ path: webDataPath });
|
||||
|
||||
for (let card of PAYMENT_METHODS) {
|
||||
let encryptedCardNumber = await loginCrypto.encryptData(card.card_number);
|
||||
let cardNumberEncryptedValue = new Uint8Array(
|
||||
loginCrypto.stringToArray(encryptedCardNumber)
|
||||
);
|
||||
|
||||
let cardCopy = structuredClone(card);
|
||||
|
||||
cardCopy.card_number_encrypted = cardNumberEncryptedValue;
|
||||
delete cardCopy.card_number;
|
||||
|
||||
await dbConn.execute(
|
||||
`INSERT INTO credit_cards
|
||||
(name_on_card, card_number_encrypted, expiration_month, expiration_year)
|
||||
VALUES (:name_on_card, :card_number_encrypted, :expiration_month, :expiration_year)
|
||||
`,
|
||||
cardCopy
|
||||
);
|
||||
}
|
||||
|
||||
await dbConn.close();
|
||||
|
||||
let migrator = await MigrationUtils.getMigrator("chrome");
|
||||
if (AppConstants.platform == "macosx") {
|
||||
Object.assign(migrator, {
|
||||
_keychainServiceName: mockMacOSKeychain.serviceName,
|
||||
_keychainAccountName: mockMacOSKeychain.accountName,
|
||||
_keychainMockPassphrase: mockMacOSKeychain.passphrase,
|
||||
});
|
||||
}
|
||||
Assert.ok(
|
||||
await migrator.isSourceAvailable(),
|
||||
"Sanity check the source exists"
|
||||
);
|
||||
|
||||
let { formAutofillStorage } = ChromeUtils.importESModule(
|
||||
"resource://autofill/FormAutofillStorage.sys.mjs"
|
||||
);
|
||||
await formAutofillStorage.initialize();
|
||||
|
||||
await promiseMigration(
|
||||
migrator,
|
||||
MigrationUtils.resourceTypes.PAYMENT_METHODS,
|
||||
PROFILE
|
||||
);
|
||||
|
||||
let cards = await formAutofillStorage.creditCards.getAll();
|
||||
|
||||
Assert.equal(
|
||||
cards.length,
|
||||
PAYMENT_METHODS.length,
|
||||
"Check there are still the same number of credit cards after re-importing the data"
|
||||
);
|
||||
Assert.equal(
|
||||
cards.length,
|
||||
MigrationUtils._importQuantities.cards,
|
||||
"Check telemetry matches the actual import."
|
||||
);
|
||||
|
||||
for (let i = 0; i < PAYMENT_METHODS.length; i++) {
|
||||
checkCardsAreEqual(cards[i], PAYMENT_METHODS[i], i + 1);
|
||||
}
|
||||
});
|
@ -16,6 +16,9 @@ run-if = os == "win"
|
||||
[test_ChromeMigrationUtils_path_chromium_snap.js]
|
||||
run-if = os == "linux"
|
||||
[test_Chrome_bookmarks.js]
|
||||
[test_Chrome_credit_cards.js]
|
||||
skip-if = os != "win" # Disabled on macOS for perma failures (bug 1833460)
|
||||
condprof # bug 1769154 - not realistic for condprof
|
||||
[test_Chrome_formdata.js]
|
||||
[test_Chrome_history.js]
|
||||
skip-if = os != "mac" # Relies on ULibDir
|
||||
|
@ -163,3 +163,8 @@ browser-data-session-checkbox =
|
||||
.label = Windows and Tabs
|
||||
browser-data-session-label =
|
||||
.value = Windows and Tabs
|
||||
|
||||
browser-data-payment-methods-checkbox =
|
||||
.label = Payment methods
|
||||
browser-data-payment-methods-label =
|
||||
.value = Payment methods
|
||||
|
Loading…
Reference in New Issue
Block a user