зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1486954 - Part I, Encrypt credit card numbers with OS key store. r=MattN
This patch morphs MasterPassword.jsm to OSKeyStore.jsm while keeping the same API, as an adaptor between the API and the native API exposed as nsIOSKeyStore.idl. Noted that OS Key Store has the concept of "recovery phrase" that we won't be adopting here. The recovery phrase, together with our label, allow the user to re-create the same key in OS key store. Test case changes are needed because we have started asking for login in places where we'll only do previously when "master password is enabled". This also made some "when master password is enabled" tests invalid because it is always considered enabled. Some more test changes are needed simply because they previously rely on the stable order of microtask resolutions (and the stable # of promises for a specific operation). That has certainly changed with OSKeyStore. The credit card form autofill is only enabled on Nightly. Differential Revision: https://phabricator.services.mozilla.com/D4498 --HG-- rename : browser/extensions/formautofill/MasterPassword.jsm => browser/extensions/formautofill/OSKeyStore.jsm rename : browser/extensions/formautofill/test/browser/browser_creditCard_fill_master_password.js => browser/extensions/formautofill/test/browser/browser_creditCard_fill_cancel_login.js extra : rebase_source : cabbd8cdec86e5b3965cf1c8b6e635b73b6c2095 extra : histedit_source : 65e71057104465553fefa1d0b293580efed53075
This commit is contained in:
Родитель
6013cf85d8
Коммит
b2023e958b
|
@ -21,8 +21,8 @@ ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
|
|||
|
||||
ChromeUtils.defineModuleGetter(this, "BrowserWindowTracker",
|
||||
"resource:///modules/BrowserWindowTracker.jsm");
|
||||
ChromeUtils.defineModuleGetter(this, "MasterPassword",
|
||||
"resource://formautofill/MasterPassword.jsm");
|
||||
ChromeUtils.defineModuleGetter(this, "OSKeyStore",
|
||||
"resource://formautofill/OSKeyStore.jsm");
|
||||
ChromeUtils.defineModuleGetter(this, "PrivateBrowsingUtils",
|
||||
"resource://gre/modules/PrivateBrowsingUtils.jsm");
|
||||
|
||||
|
@ -168,7 +168,7 @@ var paymentDialogWrapper = {
|
|||
|
||||
let cardNumber;
|
||||
try {
|
||||
cardNumber = await MasterPassword.decrypt(cardData["cc-number-encrypted"], true);
|
||||
cardNumber = await OSKeyStore.decrypt(cardData["cc-number-encrypted"], true);
|
||||
} catch (ex) {
|
||||
if (ex.result != Cr.NS_ERROR_ABORT) {
|
||||
throw ex;
|
||||
|
|
|
@ -21,6 +21,7 @@ const paymentUISrv = Cc["@mozilla.org/dom/payments/payment-ui-service;1"]
|
|||
const {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm", {});
|
||||
const {formAutofillStorage} = ChromeUtils.import(
|
||||
"resource://formautofill/FormAutofillStorage.jsm", {});
|
||||
const {OSKeyStoreTestUtils} = ChromeUtils.import("resource://testing-common/OSKeyStoreTestUtils.jsm", {});
|
||||
const {PaymentTestUtils: PTU} = ChromeUtils.import(
|
||||
"resource://testing-common/PaymentTestUtils.jsm", {});
|
||||
ChromeUtils.import("resource:///modules/BrowserWindowTracker.jsm");
|
||||
|
@ -362,10 +363,12 @@ add_task(async function setup_head() {
|
|||
}
|
||||
ok(false, msg.message || msg.errorMessage);
|
||||
});
|
||||
OSKeyStoreTestUtils.setup();
|
||||
await setupFormAutofillStorage();
|
||||
registerCleanupFunction(function cleanup() {
|
||||
registerCleanupFunction(async function cleanup() {
|
||||
paymentSrv.cleanup();
|
||||
cleanupFormAutofillStorage();
|
||||
await OSKeyStoreTestUtils.cleanup();
|
||||
Services.prefs.clearUserPref(RESPONSE_TIMEOUT_PREF);
|
||||
Services.prefs.clearUserPref(SAVE_CREDITCARD_DEFAULT_PREF);
|
||||
Services.prefs.clearUserPref(SAVE_ADDRESS_DEFAULT_PREF);
|
||||
|
|
|
@ -42,7 +42,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
|
|||
FormAutofillPreferences: "resource://formautofill/FormAutofillPreferences.jsm",
|
||||
FormAutofillDoorhanger: "resource://formautofill/FormAutofillDoorhanger.jsm",
|
||||
FormAutofillUtils: "resource://formautofill/FormAutofillUtils.jsm",
|
||||
MasterPassword: "resource://formautofill/MasterPassword.jsm",
|
||||
OSKeyStore: "resource://formautofill/OSKeyStore.jsm",
|
||||
});
|
||||
|
||||
this.log = null;
|
||||
|
@ -225,8 +225,8 @@ FormAutofillParent.prototype = {
|
|||
break;
|
||||
}
|
||||
case "FormAutofill:SaveCreditCard": {
|
||||
if (!await MasterPassword.ensureLoggedIn()) {
|
||||
log.warn("User canceled master password entry");
|
||||
if (!await OSKeyStore.ensureLoggedIn()) {
|
||||
log.warn("User canceled encryption login");
|
||||
return;
|
||||
}
|
||||
await this.formAutofillStorage.creditCards.add(data.creditcard);
|
||||
|
@ -253,12 +253,12 @@ FormAutofillParent.prototype = {
|
|||
let {cipherText, reauth} = data;
|
||||
let string;
|
||||
try {
|
||||
string = await MasterPassword.decrypt(cipherText, reauth);
|
||||
string = await OSKeyStore.decrypt(cipherText, reauth);
|
||||
} catch (e) {
|
||||
if (e.result != Cr.NS_ERROR_ABORT) {
|
||||
throw e;
|
||||
}
|
||||
log.warn("User canceled master password entry");
|
||||
log.warn("User canceled encryption login");
|
||||
}
|
||||
target.sendAsyncMessage("FormAutofill:DecryptedString", string);
|
||||
break;
|
||||
|
@ -292,7 +292,7 @@ FormAutofillParent.prototype = {
|
|||
/**
|
||||
* Get the records from profile store and return results back to content
|
||||
* process. It will decrypt the credit card number and append
|
||||
* "cc-number-decrypted" to each record if MasterPassword isn't set.
|
||||
* "cc-number-decrypted" to each record if OSKeyStore isn't set.
|
||||
*
|
||||
* @private
|
||||
* @param {string} data.collectionName
|
||||
|
@ -317,8 +317,8 @@ FormAutofillParent.prototype = {
|
|||
return;
|
||||
}
|
||||
|
||||
let isCCAndMPEnabled = collectionName == CREDITCARDS_COLLECTION_NAME && MasterPassword.isEnabled;
|
||||
// We don't filter "cc-number" when MasterPassword is set.
|
||||
let isCCAndMPEnabled = collectionName == CREDITCARDS_COLLECTION_NAME && OSKeyStore.isEnabled;
|
||||
// We don't filter "cc-number" when OSKeyStore is set.
|
||||
if (isCCAndMPEnabled && info.fieldName == "cc-number") {
|
||||
recordsInCollection = recordsInCollection.filter(record => !!record["cc-number"]);
|
||||
target.sendAsyncMessage("FormAutofill:Records", recordsInCollection);
|
||||
|
@ -335,9 +335,9 @@ FormAutofillParent.prototype = {
|
|||
}
|
||||
|
||||
// Cache the decrypted "cc-number" in each record for content to preview
|
||||
// when MasterPassword isn't set.
|
||||
// when OSKeyStore isn't set.
|
||||
if (!isCCAndMPEnabled && record["cc-number-encrypted"]) {
|
||||
record["cc-number-decrypted"] = await MasterPassword.decrypt(record["cc-number-encrypted"]);
|
||||
record["cc-number-decrypted"] = await OSKeyStore.decrypt(record["cc-number-encrypted"]);
|
||||
}
|
||||
|
||||
// Filter "cc-number" based on the decrypted one.
|
||||
|
@ -538,8 +538,8 @@ FormAutofillParent.prototype = {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!await MasterPassword.ensureLoggedIn()) {
|
||||
log.warn("User canceled master password entry");
|
||||
if (!await OSKeyStore.ensureLoggedIn()) {
|
||||
log.warn("User canceled encryption login");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -142,8 +142,8 @@ ChromeUtils.defineModuleGetter(this, "FormAutofillNameUtils",
|
|||
"resource://formautofill/FormAutofillNameUtils.jsm");
|
||||
ChromeUtils.defineModuleGetter(this, "FormAutofillUtils",
|
||||
"resource://formautofill/FormAutofillUtils.jsm");
|
||||
ChromeUtils.defineModuleGetter(this, "MasterPassword",
|
||||
"resource://formautofill/MasterPassword.jsm");
|
||||
ChromeUtils.defineModuleGetter(this, "OSKeyStore",
|
||||
"resource://formautofill/OSKeyStore.jsm");
|
||||
ChromeUtils.defineModuleGetter(this, "PhoneNumber",
|
||||
"resource://formautofill/phonenumberutils/PhoneNumber.jsm");
|
||||
|
||||
|
@ -1604,7 +1604,7 @@ class CreditCards extends AutofillRecords {
|
|||
if ("cc-number" in creditCard) {
|
||||
let ccNumber = creditCard["cc-number"];
|
||||
creditCard["cc-number"] = CreditCard.getLongMaskedNumber(ccNumber);
|
||||
creditCard["cc-number-encrypted"] = await MasterPassword.encrypt(ccNumber);
|
||||
creditCard["cc-number-encrypted"] = await OSKeyStore.encrypt(ccNumber);
|
||||
} else {
|
||||
creditCard["cc-number-encrypted"] = "";
|
||||
}
|
||||
|
@ -1615,7 +1615,7 @@ class CreditCards extends AutofillRecords {
|
|||
|
||||
async _stripComputedFields(creditCard) {
|
||||
if (creditCard["cc-number-encrypted"]) {
|
||||
creditCard["cc-number"] = await MasterPassword.decrypt(creditCard["cc-number-encrypted"]);
|
||||
creditCard["cc-number"] = await OSKeyStore.decrypt(creditCard["cc-number-encrypted"]);
|
||||
}
|
||||
await super._stripComputedFields(creditCard);
|
||||
}
|
||||
|
@ -1692,12 +1692,12 @@ class CreditCards extends AutofillRecords {
|
|||
return !creditCard[field];
|
||||
}
|
||||
if (field == "cc-number" && creditCard[field]) {
|
||||
if (MasterPassword.isEnabled) {
|
||||
// Compare the masked numbers instead when the master password is
|
||||
// enabled because we don't want to leak the credit card number.
|
||||
if (OSKeyStore.isEnabled) {
|
||||
// Compare the masked numbers instead when decryption requires a password
|
||||
// because we don't want to leak the credit card number.
|
||||
return CreditCard.getLongMaskedNumber(clonedTargetCreditCard[field]) == creditCard[field];
|
||||
}
|
||||
return (clonedTargetCreditCard[field] == await MasterPassword.decrypt(creditCard["cc-number-encrypted"]));
|
||||
return (clonedTargetCreditCard[field] == await OSKeyStore.decrypt(creditCard["cc-number-encrypted"]));
|
||||
}
|
||||
return clonedTargetCreditCard[field] == creditCard[field];
|
||||
})).then(fieldResults => fieldResults.every(result => result));
|
||||
|
|
|
@ -1,184 +0,0 @@
|
|||
/* 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/. */
|
||||
|
||||
/**
|
||||
* Helpers for the Master Password Dialog.
|
||||
* In the future the Master Password implementation may move here.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var EXPORTED_SYMBOLS = [
|
||||
"MasterPassword",
|
||||
];
|
||||
|
||||
ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "cryptoSDR",
|
||||
"@mozilla.org/login-manager/crypto/SDR;1",
|
||||
Ci.nsILoginManagerCrypto);
|
||||
|
||||
var MasterPassword = {
|
||||
get _token() {
|
||||
let tokendb = Cc["@mozilla.org/security/pk11tokendb;1"].createInstance(Ci.nsIPK11TokenDB);
|
||||
return tokendb.getInternalKeyToken();
|
||||
},
|
||||
|
||||
/**
|
||||
* @returns {boolean} True if a master password is set and false otherwise.
|
||||
*/
|
||||
get isEnabled() {
|
||||
return this._token.hasPassword;
|
||||
},
|
||||
|
||||
/**
|
||||
* @returns {boolean} True if master password is logged in and false if not.
|
||||
*/
|
||||
get isLoggedIn() {
|
||||
return Services.logins.isLoggedIn;
|
||||
},
|
||||
|
||||
/**
|
||||
* @returns {boolean} True if there is another master password login dialog
|
||||
* existing and false otherwise.
|
||||
*/
|
||||
get isUIBusy() {
|
||||
return Services.logins.uiBusy;
|
||||
},
|
||||
|
||||
/**
|
||||
* Ensure the master password is logged in. It will display the master password
|
||||
* login prompt or do nothing if it's logged in already. If an existing MP
|
||||
* prompt is already prompted, the result from it will be used instead.
|
||||
*
|
||||
* @param {boolean} reauth Prompt the login dialog no matter it's logged in
|
||||
* or not if it's set to true.
|
||||
* @returns {Promise<boolean>} True if it's logged in or no password is set
|
||||
* and false if it's still not logged in (prompt
|
||||
* canceled or other error).
|
||||
*/
|
||||
async ensureLoggedIn(reauth = false) {
|
||||
if (!this.isEnabled) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.isLoggedIn && !reauth) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If a prompt is already showing then wait for and focus it.
|
||||
if (this.isUIBusy) {
|
||||
return this.waitForExistingDialog();
|
||||
}
|
||||
|
||||
let token = this._token;
|
||||
try {
|
||||
// 'true' means always prompt for token password. User will be prompted until
|
||||
// clicking 'Cancel' or entering the correct password.
|
||||
token.login(true);
|
||||
} catch (e) {
|
||||
// An exception will be thrown if the user cancels the login prompt dialog.
|
||||
// User is also logged out.
|
||||
}
|
||||
|
||||
// If we triggered a master password prompt, notify observers.
|
||||
if (token.isLoggedIn()) {
|
||||
Services.obs.notifyObservers(null, "passwordmgr-crypto-login");
|
||||
} else {
|
||||
Services.obs.notifyObservers(null, "passwordmgr-crypto-loginCanceled");
|
||||
}
|
||||
|
||||
return token.isLoggedIn();
|
||||
},
|
||||
|
||||
/**
|
||||
* Decrypts cipherText.
|
||||
*
|
||||
* @param {string} cipherText Encrypted string including the algorithm details.
|
||||
* @param {boolean} reauth True if we want to force the prompt to show up
|
||||
* even if the user is already logged in.
|
||||
* @returns {Promise<string>} resolves to the decrypted string, or rejects otherwise.
|
||||
*/
|
||||
async decrypt(cipherText, reauth = false) {
|
||||
if (!await this.ensureLoggedIn(reauth)) {
|
||||
throw Components.Exception("User canceled master password entry", Cr.NS_ERROR_ABORT);
|
||||
}
|
||||
return cryptoSDR.decrypt(cipherText);
|
||||
},
|
||||
|
||||
/**
|
||||
* Encrypts a string and returns cipher text containing algorithm information used for decryption.
|
||||
*
|
||||
* @param {string} plainText Original string without encryption.
|
||||
* @returns {Promise<string>} resolves to the encrypted string (with algorithm), otherwise rejects.
|
||||
*/
|
||||
async encrypt(plainText) {
|
||||
if (!await this.ensureLoggedIn()) {
|
||||
throw Components.Exception("User canceled master password entry", Cr.NS_ERROR_ABORT);
|
||||
}
|
||||
|
||||
return cryptoSDR.encrypt(plainText);
|
||||
},
|
||||
|
||||
/**
|
||||
* Resolve when master password dialogs are closed, immediately if none are open.
|
||||
*
|
||||
* An existing MP dialog will be focused and will request attention.
|
||||
*
|
||||
* @returns {Promise<boolean>}
|
||||
* Resolves with whether the user is logged in to MP.
|
||||
*/
|
||||
async waitForExistingDialog() {
|
||||
if (!this.isUIBusy) {
|
||||
log.debug("waitForExistingDialog: Dialog isn't showing. isLoggedIn:", this.isLoggedIn);
|
||||
return this.isLoggedIn;
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
log.debug("waitForExistingDialog: Observing the open dialog");
|
||||
let observer = {
|
||||
QueryInterface: ChromeUtils.generateQI([
|
||||
Ci.nsIObserver,
|
||||
Ci.nsISupportsWeakReference,
|
||||
]),
|
||||
|
||||
observe(subject, topic, data) {
|
||||
log.debug("waitForExistingDialog: Got notification:", topic);
|
||||
// Only run observer once.
|
||||
Services.obs.removeObserver(this, "passwordmgr-crypto-login");
|
||||
Services.obs.removeObserver(this, "passwordmgr-crypto-loginCanceled");
|
||||
if (topic == "passwordmgr-crypto-loginCanceled") {
|
||||
resolve(false);
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(true);
|
||||
},
|
||||
};
|
||||
|
||||
// Possible leak: it's possible that neither of these notifications
|
||||
// will fire, and if that happens, we'll leak the observer (and
|
||||
// never return). We should guarantee that at least one of these
|
||||
// will fire.
|
||||
// See bug XXX.
|
||||
Services.obs.addObserver(observer, "passwordmgr-crypto-login");
|
||||
Services.obs.addObserver(observer, "passwordmgr-crypto-loginCanceled");
|
||||
|
||||
// Focus and draw attention to the existing master password dialog for the
|
||||
// occassions where it's not attached to the current window.
|
||||
let promptWin = Services.wm.getMostRecentWindow("prompt:promptPassword");
|
||||
promptWin.focus();
|
||||
promptWin.getAttention();
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "log", () => {
|
||||
let ConsoleAPI = ChromeUtils.import("resource://gre/modules/Console.jsm", {}).ConsoleAPI;
|
||||
return new ConsoleAPI({
|
||||
maxLogLevelPref: "masterPassword.loglevel",
|
||||
prefix: "Master Password",
|
||||
});
|
||||
});
|
|
@ -0,0 +1,262 @@
|
|||
/* 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/. */
|
||||
|
||||
/**
|
||||
* Helpers for using OS Key Store.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var EXPORTED_SYMBOLS = [
|
||||
"OSKeyStore",
|
||||
];
|
||||
|
||||
ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
ChromeUtils.defineModuleGetter(this, "AppConstants", "resource://gre/modules/AppConstants.jsm");
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "nativeOSKeyStore",
|
||||
"@mozilla.org/security/oskeystore;1", Ci.nsIOSKeyStore);
|
||||
|
||||
// Skip reauth during tests, only works in non-official builds.
|
||||
const TEST_ONLY_REAUTH = "extensions.formautofill.osKeyStore.unofficialBuildOnlyLogin";
|
||||
|
||||
var OSKeyStore = {
|
||||
/**
|
||||
* On macOS this becomes part of the name label visible on Keychain Acesss as
|
||||
* "org.mozilla.nss.keystore.firefox" (where "firefox" is the MOZ_APP_NAME).
|
||||
*/
|
||||
STORE_LABEL: AppConstants.MOZ_APP_NAME,
|
||||
|
||||
/**
|
||||
* Consider the module is initialized as locked. OS might unlock without a
|
||||
* prompt.
|
||||
* @type {Boolean}
|
||||
*/
|
||||
_isLocked: true,
|
||||
|
||||
_pendingUnlockPromise: null,
|
||||
|
||||
/**
|
||||
* @returns {boolean} Always considered enabled because OS store is always
|
||||
* protected via OS user login password.
|
||||
* TODO: Figure out if the affacted behaviors
|
||||
* (e.g. like bug 1486954 or confirming payment transaction)
|
||||
* is correct or not.
|
||||
*/
|
||||
get isEnabled() {
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* @returns {boolean} True if logged in (i.e. decrypt(reauth = false) will
|
||||
* not retrigger a dialog) and false if not.
|
||||
* User might log out elsewhere in the OS, so even if this
|
||||
* is true a prompt might still pop up.
|
||||
*/
|
||||
get isLoggedIn() {
|
||||
return !this._isLocked;
|
||||
},
|
||||
|
||||
/**
|
||||
* @returns {boolean} True if there is another login dialog existing and false
|
||||
* otherwise.
|
||||
*/
|
||||
get isUIBusy() {
|
||||
return !!this._pendingUnlockPromise;
|
||||
},
|
||||
|
||||
/**
|
||||
* If the test pref exist and applicable,
|
||||
* this method will dispatch a observer message and return
|
||||
* to simulate successful reauth, or throw to simulate
|
||||
* failed reauth.
|
||||
*
|
||||
* @returns {boolean} True when reauth should NOT be skipped,
|
||||
* false when reauth has been skipped.
|
||||
* @throws If it needs to simulate reauth login failure.
|
||||
*/
|
||||
_maybeSkipReauthForTest() {
|
||||
// Don't take test reauth pref in the following configurations.
|
||||
if (nativeOSKeyStore.isNSSKeyStore ||
|
||||
AppConstants.MOZILLA_OFFICIAL ||
|
||||
!this._testReauth) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Skip this reauth because there is no way to mock the
|
||||
// native dialog in the testing environment, for now.
|
||||
log.debug("_ensureReauth: _testReauth: ", this._testReauth);
|
||||
switch (this._testReauth) {
|
||||
case "pass":
|
||||
Services.obs.notifyObservers(null, "oskeystore-testonly-reauth", "pass");
|
||||
return false;
|
||||
case "cancel":
|
||||
Services.obs.notifyObservers(null, "oskeystore-testonly-reauth", "cancel");
|
||||
throw new Components.Exception("Simulating user cancelling login dialog", Cr.NS_ERROR_FAILURE);
|
||||
default:
|
||||
throw new Components.Exception("Unknown test pref value", Cr.NS_ERROR_FAILURE);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Ensure the store in use is logged in. It will display the OS login
|
||||
* login prompt or do nothing if it's logged in already. If an existing login
|
||||
* prompt is already prompted, the result from it will be used instead.
|
||||
*
|
||||
* Note: This method must set _pendingUnlockPromise before returning the
|
||||
* promise (i.e. the first |await|), otherwise we'll risk re-entry.
|
||||
* This is why there aren't an |await| in the method. The method is marked as
|
||||
* |async| to communicate that it's async.
|
||||
*
|
||||
* @param {boolean} reauth Prompt the login dialog no matter it's logged in
|
||||
* or not if it's set to true.
|
||||
* @returns {Promise<boolean>} True if it's logged in or no password is set
|
||||
* and false if it's still not logged in (prompt
|
||||
* canceled or other error).
|
||||
*/
|
||||
async ensureLoggedIn(reauth = false) {
|
||||
if (this._pendingUnlockPromise) {
|
||||
log.debug("ensureLoggedIn: Has a pending unlock operation");
|
||||
return this._pendingUnlockPromise;
|
||||
}
|
||||
log.debug("ensureLoggedIn: Creating new pending unlock promise. reauth: ", reauth);
|
||||
|
||||
// TODO: Implementing re-auth by passing this value to the native implementation
|
||||
// in some way. Set this to false for now to ignore the reauth request (bug 1429265).
|
||||
reauth = false;
|
||||
|
||||
let unlockPromise = Promise.resolve().then(async () => {
|
||||
if (reauth) {
|
||||
reauth = this._maybeSkipReauthForTest();
|
||||
}
|
||||
|
||||
if (!await nativeOSKeyStore.asyncSecretAvailable(this.STORE_LABEL)) {
|
||||
log.debug("ensureLoggedIn: Secret unavailable, attempt to generate new secret.");
|
||||
let recoveryPhrase = await nativeOSKeyStore.asyncGenerateSecret(this.STORE_LABEL);
|
||||
// TODO We should somehow have a dialog to ask the user to write this down,
|
||||
// and another dialog somewhere for the user to restore the secret with it.
|
||||
// (Intentionally not printing it out in the console)
|
||||
log.debug("ensureLoggedIn: Secret generated. Recovery phrase length: " + recoveryPhrase.length);
|
||||
}
|
||||
});
|
||||
|
||||
if (nativeOSKeyStore.isNSSKeyStore) {
|
||||
// Workaround bug 1492305: NSS-implemented methods don't reject when user cancels.
|
||||
unlockPromise = unlockPromise.then(() => {
|
||||
log.debug("ensureLoggedIn: isNSSKeyStore: ", reauth, Services.logins.isLoggedIn);
|
||||
// User has hit the cancel button on the master password prompt.
|
||||
// We must reject the promise chain here.
|
||||
if (!Services.logins.isLoggedIn) {
|
||||
throw Components.Exception("User canceled OS unlock entry (Workaround)", Cr.NS_ERROR_FAILURE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
unlockPromise = unlockPromise.then(() => {
|
||||
log.debug("ensureLoggedIn: Logged in");
|
||||
this._pendingUnlockPromise = null;
|
||||
this._isLocked = false;
|
||||
|
||||
return true;
|
||||
}, (err) => {
|
||||
log.debug("ensureLoggedIn: Not logged in", err);
|
||||
this._pendingUnlockPromise = null;
|
||||
this._isLocked = true;
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
this._pendingUnlockPromise = unlockPromise;
|
||||
|
||||
return this._pendingUnlockPromise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Decrypts cipherText.
|
||||
*
|
||||
* Note: In the event of an rejection, check the result property of the Exception
|
||||
* object. Handles NS_ERROR_ABORT as user has cancelled the action (e.g.,
|
||||
* don't show that dialog), apart from other errors (e.g., gracefully
|
||||
* recover from that and still shows the dialog.)
|
||||
*
|
||||
* @param {string} cipherText Encrypted string including the algorithm details.
|
||||
* @param {boolean} reauth True if we want to force the prompt to show up
|
||||
* even if the user is already logged in.
|
||||
* @returns {Promise<string>} resolves to the decrypted string, or rejects otherwise.
|
||||
*/
|
||||
async decrypt(cipherText, reauth = false) {
|
||||
if (!await this.ensureLoggedIn(reauth)) {
|
||||
throw Components.Exception("User canceled OS unlock entry", Cr.NS_ERROR_ABORT);
|
||||
}
|
||||
let bytes = await nativeOSKeyStore.asyncDecryptBytes(this.STORE_LABEL, cipherText);
|
||||
return String.fromCharCode.apply(String, bytes);
|
||||
},
|
||||
|
||||
/**
|
||||
* Encrypts a string and returns cipher text containing algorithm information used for decryption.
|
||||
*
|
||||
* @param {string} plainText Original string without encryption.
|
||||
* @returns {Promise<string>} resolves to the encrypted string (with algorithm), otherwise rejects.
|
||||
*/
|
||||
async encrypt(plainText) {
|
||||
if (!await this.ensureLoggedIn()) {
|
||||
throw Components.Exception("User canceled OS unlock entry", Cr.NS_ERROR_ABORT);
|
||||
}
|
||||
|
||||
// Convert plain text into a UTF-8 binary string
|
||||
plainText = unescape(encodeURIComponent(plainText));
|
||||
|
||||
// Convert it to an array
|
||||
let textArr = [];
|
||||
for (let char of plainText) {
|
||||
textArr.push(char.charCodeAt(0));
|
||||
}
|
||||
|
||||
let rawEncryptedText = await nativeOSKeyStore.asyncEncryptBytes(this.STORE_LABEL, textArr.length, textArr);
|
||||
|
||||
// Mark the output with a version number.
|
||||
return rawEncryptedText;
|
||||
},
|
||||
|
||||
/**
|
||||
* Resolve when the login dialogs are closed, immediately if none are open.
|
||||
*
|
||||
* An existing MP dialog will be focused and will request attention.
|
||||
*
|
||||
* @returns {Promise<boolean>}
|
||||
* Resolves with whether the user is logged in to MP.
|
||||
*/
|
||||
async waitForExistingDialog() {
|
||||
if (this.isUIBusy) {
|
||||
return this._pendingUnlockPromise;
|
||||
}
|
||||
return this.isLoggedIn;
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove the store. For tests.
|
||||
*/
|
||||
async cleanup() {
|
||||
return nativeOSKeyStore.asyncDeleteSecret(this.STORE_LABEL);
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if the implementation is using the NSS key store.
|
||||
* If so, tests will be able to handle the reauth dialog.
|
||||
*/
|
||||
get isNSSKeyStore() {
|
||||
return nativeOSKeyStore.isNSSKeyStore;
|
||||
},
|
||||
};
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "log", () => {
|
||||
let ConsoleAPI = ChromeUtils.import("resource://gre/modules/Console.jsm", {}).ConsoleAPI;
|
||||
return new ConsoleAPI({
|
||||
maxLogLevelPref: "extensions.formautofill.loglevel",
|
||||
prefix: "OSKeyStore",
|
||||
});
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyPreferenceGetter(OSKeyStore, "_testReauth", TEST_ONLY_REAUTH, "");
|
|
@ -19,8 +19,8 @@ ChromeUtils.defineModuleGetter(this, "formAutofillStorage",
|
|||
"resource://formautofill/FormAutofillStorage.jsm");
|
||||
ChromeUtils.defineModuleGetter(this, "FormAutofillUtils",
|
||||
"resource://formautofill/FormAutofillUtils.jsm");
|
||||
ChromeUtils.defineModuleGetter(this, "MasterPassword",
|
||||
"resource://formautofill/MasterPassword.jsm");
|
||||
ChromeUtils.defineModuleGetter(this, "OSKeyStore",
|
||||
"resource://formautofill/OSKeyStore.jsm");
|
||||
|
||||
this.log = null;
|
||||
FormAutofill.defineLazyLogGetter(this, "manageAddresses");
|
||||
|
@ -317,9 +317,9 @@ class ManageCreditCards extends ManageRecords {
|
|||
elements.add.setAttribute("searchkeywords", FormAutofillUtils.EDIT_CREDITCARD_KEYWORDS
|
||||
.map(key => FormAutofillUtils.stringBundle.GetStringFromName(key))
|
||||
.join("\n"));
|
||||
this._hasMasterPassword = MasterPassword.isEnabled;
|
||||
this._hasOSKeyStore = OSKeyStore.isEnabled;
|
||||
this._isDecrypted = false;
|
||||
if (this._hasMasterPassword) {
|
||||
if (this._hasOSKeyStore) {
|
||||
elements.showHideCreditCards.setAttribute("hidden", true);
|
||||
}
|
||||
}
|
||||
|
@ -330,12 +330,11 @@ class ManageCreditCards extends ManageRecords {
|
|||
* @param {object} creditCard [optional]
|
||||
*/
|
||||
async openEditDialog(creditCard) {
|
||||
// If master password is set, ask for password if user is trying to edit an
|
||||
// existing credit card.
|
||||
if (!creditCard || !this._hasMasterPassword || await MasterPassword.ensureLoggedIn(true)) {
|
||||
// Ask for reauth if user is trying to edit an existing credit card.
|
||||
if (!creditCard || !this._hasOSKeyStore || await OSKeyStore.ensureLoggedIn(true)) {
|
||||
let decryptedCCNumObj = {};
|
||||
if (creditCard) {
|
||||
decryptedCCNumObj["cc-number"] = await MasterPassword.decrypt(creditCard["cc-number-encrypted"]);
|
||||
decryptedCCNumObj["cc-number"] = await OSKeyStore.decrypt(creditCard["cc-number-encrypted"]);
|
||||
}
|
||||
let decryptedCreditCard = Object.assign({}, creditCard, decryptedCCNumObj);
|
||||
this.prefWin.gSubDialog.open(EDIT_CREDIT_CARD_URL, "resizable=no", {
|
||||
|
|
|
@ -32,6 +32,8 @@ elif CONFIG['OS_ARCH'] == 'WINNT':
|
|||
'skin/windows/editDialog.css',
|
||||
]
|
||||
|
||||
TESTING_JS_MODULES += ['test/fixtures/OSKeyStoreTestUtils.jsm']
|
||||
|
||||
BROWSER_CHROME_MANIFESTS += ['test/browser/browser.ini']
|
||||
|
||||
XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
|
||||
|
|
|
@ -15,7 +15,8 @@ skip-if = (verify && (os == 'win' || os == 'mac'))
|
|||
[browser_check_installed.js]
|
||||
[browser_creditCard_doorhanger.js]
|
||||
skip-if = (os == "linux") || (os == "mac" && debug) || (os == "win") # bug 1425884
|
||||
[browser_creditCard_fill_master_password.js]
|
||||
[browser_creditCard_fill_cancel_login.js]
|
||||
skip-if = true # Re-auth is not implemented, cannot cancel OS key store login (bug 1429265)
|
||||
[browser_dropdown_layout.js]
|
||||
[browser_editAddressDialog.js]
|
||||
[browser_editCreditCardDialog.js]
|
||||
|
|
|
@ -51,6 +51,7 @@ add_task(async function test_submit_creditCard_saved() {
|
|||
async function(browser) {
|
||||
let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
|
||||
"popupshown");
|
||||
let onChanged = TestUtils.topicObserved("formautofill-storage-changed");
|
||||
await ContentTask.spawn(browser, null, async function() {
|
||||
let form = content.document.getElementById("form");
|
||||
let name = form.querySelector("#cc-name");
|
||||
|
@ -69,6 +70,7 @@ add_task(async function test_submit_creditCard_saved() {
|
|||
|
||||
await promiseShown;
|
||||
await clickDoorhangerButton(MAIN_BUTTON);
|
||||
await onChanged;
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -82,6 +84,11 @@ add_task(async function test_submit_creditCard_saved() {
|
|||
});
|
||||
|
||||
add_task(async function test_submit_untouched_creditCard_form() {
|
||||
if (!OSKeyStoreTestUtils.canTestOSKeyStoreLogin()) {
|
||||
todo(OSKeyStoreTestUtils.canTestOSKeyStoreLogin(), "Cannot test OS key store login on official builds.");
|
||||
return;
|
||||
}
|
||||
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
"set": [
|
||||
[CREDITCARDS_USED_STATUS_PREF, 0],
|
||||
|
@ -90,11 +97,16 @@ add_task(async function test_submit_untouched_creditCard_form() {
|
|||
await saveCreditCard(TEST_CREDIT_CARD_1);
|
||||
let creditCards = await getCreditCards();
|
||||
is(creditCards.length, 1, "1 credit card in storage");
|
||||
|
||||
let osKeyStoreLoginShown = OSKeyStoreTestUtils.waitForOSKeyStoreLogin(true);
|
||||
let onUsed = TestUtils.topicObserved("formautofill-storage-changed",
|
||||
(subject, data) => data == "notifyUsed");
|
||||
await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
|
||||
async function(browser) {
|
||||
await openPopupOn(browser, "form #cc-name");
|
||||
await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
|
||||
await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, browser);
|
||||
await osKeyStoreLoginShown;
|
||||
await ContentTask.spawn(browser, null, async function() {
|
||||
let form = content.document.getElementById("form");
|
||||
|
||||
|
@ -107,6 +119,7 @@ add_task(async function test_submit_untouched_creditCard_form() {
|
|||
is(PopupNotifications.panel.state, "closed", "Doorhanger is hidden");
|
||||
}
|
||||
);
|
||||
await onUsed;
|
||||
|
||||
creditCards = await getCreditCards();
|
||||
is(creditCards.length, 1, "Still 1 credit card");
|
||||
|
@ -125,6 +138,9 @@ add_task(async function test_submit_changed_subset_creditCard_form() {
|
|||
await saveCreditCard(TEST_CREDIT_CARD_1);
|
||||
let creditCards = await getCreditCards();
|
||||
is(creditCards.length, 1, "1 credit card in storage");
|
||||
|
||||
let onUsed = TestUtils.topicObserved("formautofill-storage-changed",
|
||||
(subject, data) => data == "notifyUsed");
|
||||
await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
|
||||
async function(browser) {
|
||||
let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
|
||||
|
@ -149,6 +165,7 @@ add_task(async function test_submit_changed_subset_creditCard_form() {
|
|||
await clickDoorhangerButton(MAIN_BUTTON);
|
||||
}
|
||||
);
|
||||
await onUsed;
|
||||
|
||||
creditCards = await getCreditCards();
|
||||
is(creditCards.length, 1, "Still 1 credit card in storage");
|
||||
|
@ -274,78 +291,6 @@ add_task(async function test_submit_creditCard_never_save() {
|
|||
SpecialPowers.clearUserPref(ENABLED_AUTOFILL_CREDITCARDS_PREF);
|
||||
});
|
||||
|
||||
add_task(async function test_submit_creditCard_saved_with_mp_enabled() {
|
||||
LoginTestUtils.masterPassword.enable();
|
||||
// Login with the masterPassword in LoginTestUtils.
|
||||
let masterPasswordDialogShown = waitForMasterPasswordDialog(true);
|
||||
await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
|
||||
async function(browser) {
|
||||
let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
|
||||
"popupshown");
|
||||
await ContentTask.spawn(browser, null, async function() {
|
||||
let form = content.document.getElementById("form");
|
||||
let name = form.querySelector("#cc-name");
|
||||
name.focus();
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
name.setUserInput("User 0");
|
||||
|
||||
let number = form.querySelector("#cc-number");
|
||||
number.setUserInput("6387060366272981");
|
||||
|
||||
// Wait 1000ms before submission to make sure the input value applied
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
form.querySelector("input[type=submit]").click();
|
||||
});
|
||||
|
||||
await promiseShown;
|
||||
await clickDoorhangerButton(MAIN_BUTTON);
|
||||
await masterPasswordDialogShown;
|
||||
await TestUtils.topicObserved("formautofill-storage-changed");
|
||||
}
|
||||
);
|
||||
|
||||
let creditCards = await getCreditCards();
|
||||
is(creditCards.length, 1, "1 credit card in storage");
|
||||
is(creditCards[0]["cc-name"], "User 0", "Verify the name field");
|
||||
is(creditCards[0]["cc-number"], "************2981", "Verify the card number field");
|
||||
LoginTestUtils.masterPassword.disable();
|
||||
await removeAllRecords();
|
||||
});
|
||||
|
||||
add_task(async function test_submit_creditCard_saved_with_mp_enabled_but_canceled() {
|
||||
LoginTestUtils.masterPassword.enable();
|
||||
let masterPasswordDialogShown = waitForMasterPasswordDialog();
|
||||
await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
|
||||
async function(browser) {
|
||||
let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
|
||||
"popupshown");
|
||||
await ContentTask.spawn(browser, null, async function() {
|
||||
let form = content.document.getElementById("form");
|
||||
let name = form.querySelector("#cc-name");
|
||||
name.focus();
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
name.setUserInput("User 2");
|
||||
|
||||
let number = form.querySelector("#cc-number");
|
||||
number.setUserInput("5471839082338112");
|
||||
|
||||
// Wait 1000ms before submission to make sure the input value applied
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
form.querySelector("input[type=submit]").click();
|
||||
});
|
||||
|
||||
await promiseShown;
|
||||
await clickDoorhangerButton(MAIN_BUTTON);
|
||||
await masterPasswordDialogShown;
|
||||
}
|
||||
);
|
||||
|
||||
await sleep(1000);
|
||||
let creditCards = await getCreditCards();
|
||||
is(creditCards.length, 0, "No credit cards in storage");
|
||||
LoginTestUtils.masterPassword.disable();
|
||||
});
|
||||
|
||||
add_task(async function test_submit_creditCard_with_sync_account() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
"set": [
|
||||
|
@ -445,6 +390,8 @@ add_task(async function test_submit_manual_mergeable_creditCard_form() {
|
|||
await saveCreditCard(TEST_CREDIT_CARD_3);
|
||||
let creditCards = await getCreditCards();
|
||||
is(creditCards.length, 1, "1 credit card in storage");
|
||||
let onUsed = TestUtils.topicObserved("formautofill-storage-changed",
|
||||
(subject, data) => data == "notifyUsed");
|
||||
await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
|
||||
async function(browser) {
|
||||
let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
|
||||
|
@ -467,6 +414,7 @@ add_task(async function test_submit_manual_mergeable_creditCard_form() {
|
|||
await clickDoorhangerButton(MAIN_BUTTON);
|
||||
}
|
||||
);
|
||||
await onUsed;
|
||||
|
||||
creditCards = await getCreditCards();
|
||||
is(creditCards.length, 1, "Still 1 credit card in storage");
|
||||
|
@ -477,6 +425,11 @@ add_task(async function test_submit_manual_mergeable_creditCard_form() {
|
|||
});
|
||||
|
||||
add_task(async function test_update_autofill_form_name() {
|
||||
if (!OSKeyStoreTestUtils.canTestOSKeyStoreLogin()) {
|
||||
todo(OSKeyStoreTestUtils.canTestOSKeyStoreLogin(), "Cannot test OS key store login on official builds.");
|
||||
return;
|
||||
}
|
||||
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
"set": [
|
||||
[CREDITCARDS_USED_STATUS_PREF, 0],
|
||||
|
@ -485,6 +438,9 @@ add_task(async function test_update_autofill_form_name() {
|
|||
await saveCreditCard(TEST_CREDIT_CARD_1);
|
||||
let creditCards = await getCreditCards();
|
||||
is(creditCards.length, 1, "1 credit card in storage");
|
||||
let osKeyStoreLoginShown = OSKeyStoreTestUtils.waitForOSKeyStoreLogin(true);
|
||||
let onUsed = TestUtils.topicObserved("formautofill-storage-changed",
|
||||
(subject, data) => data == "notifyUsed");
|
||||
await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
|
||||
async function(browser) {
|
||||
let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
|
||||
|
@ -492,7 +448,13 @@ add_task(async function test_update_autofill_form_name() {
|
|||
await openPopupOn(browser, "form #cc-name");
|
||||
await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
|
||||
await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, browser);
|
||||
await osKeyStoreLoginShown;
|
||||
await ContentTask.spawn(browser, null, async function() {
|
||||
await ContentTaskUtils.waitForCondition(() => {
|
||||
let form = content.document.getElementById("form");
|
||||
let name = form.querySelector("#cc-name");
|
||||
return name.value == "John Doe";
|
||||
}, "Credit card detail never fills");
|
||||
let form = content.document.getElementById("form");
|
||||
let name = form.querySelector("#cc-name");
|
||||
name.setUserInput("User 1");
|
||||
|
@ -501,11 +463,11 @@ add_task(async function test_update_autofill_form_name() {
|
|||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
form.querySelector("input[type=submit]").click();
|
||||
});
|
||||
|
||||
await promiseShown;
|
||||
await clickDoorhangerButton(MAIN_BUTTON);
|
||||
}
|
||||
);
|
||||
await onUsed;
|
||||
|
||||
creditCards = await getCreditCards();
|
||||
is(creditCards.length, 1, "Still 1 credit card");
|
||||
|
@ -517,6 +479,11 @@ add_task(async function test_update_autofill_form_name() {
|
|||
});
|
||||
|
||||
add_task(async function test_update_autofill_form_exp_date() {
|
||||
if (!OSKeyStoreTestUtils.canTestOSKeyStoreLogin()) {
|
||||
todo(OSKeyStoreTestUtils.canTestOSKeyStoreLogin(), "Cannot test OS key store login on official builds.");
|
||||
return;
|
||||
}
|
||||
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
"set": [
|
||||
[CREDITCARDS_USED_STATUS_PREF, 0],
|
||||
|
@ -525,6 +492,9 @@ add_task(async function test_update_autofill_form_exp_date() {
|
|||
await saveCreditCard(TEST_CREDIT_CARD_1);
|
||||
let creditCards = await getCreditCards();
|
||||
is(creditCards.length, 1, "1 credit card in storage");
|
||||
let osKeyStoreLoginShown = OSKeyStoreTestUtils.waitForOSKeyStoreLogin(true);
|
||||
let onUsed = TestUtils.topicObserved("formautofill-storage-changed",
|
||||
(subject, data) => data == "notifyUsed");
|
||||
await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
|
||||
async function(browser) {
|
||||
let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
|
||||
|
@ -533,6 +503,11 @@ add_task(async function test_update_autofill_form_exp_date() {
|
|||
await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
|
||||
await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, browser);
|
||||
await ContentTask.spawn(browser, null, async function() {
|
||||
await ContentTaskUtils.waitForCondition(() => {
|
||||
let form = content.document.getElementById("form");
|
||||
let name = form.querySelector("#cc-name");
|
||||
return name.value == "John Doe";
|
||||
}, "Credit card detail never fills");
|
||||
let form = content.document.getElementById("form");
|
||||
let year = form.querySelector("#cc-exp-year");
|
||||
year.setUserInput("2020");
|
||||
|
@ -544,8 +519,10 @@ add_task(async function test_update_autofill_form_exp_date() {
|
|||
|
||||
await promiseShown;
|
||||
await clickDoorhangerButton(MAIN_BUTTON);
|
||||
await osKeyStoreLoginShown;
|
||||
}
|
||||
);
|
||||
await onUsed;
|
||||
|
||||
creditCards = await getCreditCards();
|
||||
is(creditCards.length, 1, "Still 1 credit card");
|
||||
|
@ -557,6 +534,11 @@ add_task(async function test_update_autofill_form_exp_date() {
|
|||
});
|
||||
|
||||
add_task(async function test_create_new_autofill_form() {
|
||||
if (!OSKeyStoreTestUtils.canTestOSKeyStoreLogin()) {
|
||||
todo(OSKeyStoreTestUtils.canTestOSKeyStoreLogin(), "Cannot test OS key store login on official builds.");
|
||||
return;
|
||||
}
|
||||
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
"set": [
|
||||
[CREDITCARDS_USED_STATUS_PREF, 0],
|
||||
|
@ -565,14 +547,21 @@ add_task(async function test_create_new_autofill_form() {
|
|||
await saveCreditCard(TEST_CREDIT_CARD_1);
|
||||
let creditCards = await getCreditCards();
|
||||
is(creditCards.length, 1, "1 credit card in storage");
|
||||
let osKeyStoreLoginShown = OSKeyStoreTestUtils.waitForOSKeyStoreLogin(true);
|
||||
await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
|
||||
async function(browser) {
|
||||
let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
|
||||
"popupshown");
|
||||
let onChanged = TestUtils.topicObserved("formautofill-storage-changed");
|
||||
await openPopupOn(browser, "form #cc-name");
|
||||
await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
|
||||
await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, browser);
|
||||
await ContentTask.spawn(browser, null, async function() {
|
||||
await ContentTaskUtils.waitForCondition(() => {
|
||||
let form = content.document.getElementById("form");
|
||||
let name = form.querySelector("#cc-name");
|
||||
return name.value == "John Doe";
|
||||
}, "Credit card detail never fills");
|
||||
let form = content.document.getElementById("form");
|
||||
let name = form.querySelector("#cc-name");
|
||||
name.setUserInput("User 1");
|
||||
|
@ -584,6 +573,8 @@ add_task(async function test_create_new_autofill_form() {
|
|||
|
||||
await promiseShown;
|
||||
await clickDoorhangerButton(SECONDARY_BUTTON);
|
||||
await osKeyStoreLoginShown;
|
||||
await onChanged;
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -598,6 +589,11 @@ add_task(async function test_create_new_autofill_form() {
|
|||
});
|
||||
|
||||
add_task(async function test_update_duplicate_autofill_form() {
|
||||
if (!OSKeyStoreTestUtils.canTestOSKeyStoreLogin()) {
|
||||
todo(OSKeyStoreTestUtils.canTestOSKeyStoreLogin(), "Cannot test OS key store login on official builds.");
|
||||
return;
|
||||
}
|
||||
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
"set": [
|
||||
[CREDITCARDS_USED_STATUS_PREF, 0],
|
||||
|
@ -611,16 +607,24 @@ add_task(async function test_update_duplicate_autofill_form() {
|
|||
});
|
||||
let creditCards = await getCreditCards();
|
||||
is(creditCards.length, 2, "2 credit card in storage");
|
||||
let osKeyStoreLoginShown = OSKeyStoreTestUtils.waitForOSKeyStoreLogin(true);
|
||||
let onUsed = TestUtils.topicObserved("formautofill-storage-changed",
|
||||
(subject, data) => data == "notifyUsed");
|
||||
await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
|
||||
async function(browser) {
|
||||
await openPopupOn(browser, "form #cc-number");
|
||||
await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
|
||||
await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, browser);
|
||||
await ContentTask.spawn(browser, null, async function() {
|
||||
await ContentTaskUtils.waitForCondition(() => {
|
||||
let form = content.document.getElementById("form");
|
||||
let number = form.querySelector("#cc-number");
|
||||
return number.value == "6387060366272981";
|
||||
}, "Should be the first credit card number");
|
||||
|
||||
// Change number to the second credit card number
|
||||
let form = content.document.getElementById("form");
|
||||
let number = form.querySelector("#cc-number");
|
||||
is(number.value, "6387060366272981", "Should be the first credit card number");
|
||||
// Change number to the second credit card number
|
||||
number.setUserInput("5038146897157463");
|
||||
|
||||
// Wait 1000ms before submission to make sure the input value applied
|
||||
|
@ -630,8 +634,10 @@ add_task(async function test_update_duplicate_autofill_form() {
|
|||
|
||||
await sleep(1000);
|
||||
is(PopupNotifications.panel.state, "closed", "Doorhanger is hidden");
|
||||
await osKeyStoreLoginShown;
|
||||
}
|
||||
);
|
||||
await onUsed;
|
||||
|
||||
creditCards = await getCreditCards();
|
||||
is(creditCards.length, 2, "Still 2 credit card");
|
||||
|
@ -646,6 +652,7 @@ add_task(async function test_submit_creditCard_with_invalid_network() {
|
|||
async function(browser) {
|
||||
let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
|
||||
"popupshown");
|
||||
let onChanged = TestUtils.topicObserved("formautofill-storage-changed");
|
||||
await ContentTask.spawn(browser, null, async function() {
|
||||
let form = content.document.getElementById("form");
|
||||
let name = form.querySelector("#cc-name");
|
||||
|
@ -664,6 +671,7 @@ add_task(async function test_submit_creditCard_with_invalid_network() {
|
|||
|
||||
await promiseShown;
|
||||
await clickDoorhangerButton(MAIN_BUTTON);
|
||||
await onChanged;
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
"use strict";
|
||||
|
||||
add_task(async function test_fill_creditCard_with_mp_enabled_but_canceled() {
|
||||
add_task(async function test_fill_creditCard_but_cancel_login() {
|
||||
if (!OSKeyStoreTestUtils.canTestOSKeyStoreLogin()) {
|
||||
todo(OSKeyStoreTestUtils.canTestOSKeyStoreLogin(), "Cannot test OS key store login on official builds.");
|
||||
return;
|
||||
}
|
||||
|
||||
await saveCreditCard(TEST_CREDIT_CARD_2);
|
||||
|
||||
LoginTestUtils.masterPassword.enable();
|
||||
registerCleanupFunction(() => {
|
||||
LoginTestUtils.masterPassword.disable();
|
||||
});
|
||||
|
||||
let masterPasswordDialogShown = waitForMasterPasswordDialog(false); // cancel
|
||||
let osKeyStoreLoginShown = OSKeyStoreTestUtils.waitForOSKeyStoreLogin(false); // cancel
|
||||
await BrowserTestUtils.withNewTab({gBrowser, url: CREDITCARD_FORM_URL},
|
||||
async function(browser) {
|
||||
await openPopupOn(browser, "#cc-name");
|
||||
const ccItem = getDisplayedPopupItems(browser)[0];
|
||||
await EventUtils.synthesizeMouseAtCenter(ccItem, {});
|
||||
await Promise.all([masterPasswordDialogShown, expectPopupClose(browser)]);
|
||||
await Promise.all([osKeyStoreLoginShown, expectPopupClose(browser)]);
|
||||
|
||||
await ContentTask.spawn(browser, {}, async function() {
|
||||
is(content.document.querySelector("#cc-name").value, "", "Check name");
|
|
@ -107,57 +107,6 @@ add_task(async function test_creditCardsDialogWatchesStorageChanges() {
|
|||
win.close();
|
||||
});
|
||||
|
||||
add_task(async function test_showCreditCards() {
|
||||
await SpecialPowers.pushPrefEnv({"set": [["privacy.reduceTimerPrecision", false]]});
|
||||
await saveCreditCard(TEST_CREDIT_CARD_1);
|
||||
await saveCreditCard(TEST_CREDIT_CARD_2);
|
||||
await saveCreditCard(TEST_CREDIT_CARD_3);
|
||||
|
||||
let win = window.openDialog(MANAGE_CREDIT_CARDS_DIALOG_URL, null, DIALOG_SIZE);
|
||||
await waitForFocusAndFormReady(win);
|
||||
|
||||
let selRecords = win.document.querySelector(TEST_SELECTORS.selRecords);
|
||||
let btnShowHideCreditCards = win.document.querySelector(TEST_SELECTORS.btnShowHideCreditCards);
|
||||
|
||||
is(btnShowHideCreditCards.disabled, false, "Show credit cards button enabled");
|
||||
is(btnShowHideCreditCards.textContent, "Show Credit Cards", "Label should be 'Show Credit Cards'");
|
||||
|
||||
// Show credit card numbers
|
||||
EventUtils.synthesizeMouseAtCenter(btnShowHideCreditCards, {}, win);
|
||||
await BrowserTestUtils.waitForEvent(selRecords, "LabelsUpdated");
|
||||
is(selRecords[0].text, "5103059495477870", "Decrypted credit card 3");
|
||||
is(selRecords[1].text, "4929001587121045, Timothy Berners-Lee", "Decrypted credit card 2");
|
||||
is(selRecords[2].text, "4111111111111111, John Doe", "Decrypted credit card 1");
|
||||
is(btnShowHideCreditCards.textContent, "Hide Credit Cards", "Label should be 'Hide Credit Cards'");
|
||||
|
||||
// Hide credit card numbers
|
||||
EventUtils.synthesizeMouseAtCenter(btnShowHideCreditCards, {}, win);
|
||||
await BrowserTestUtils.waitForEvent(selRecords, "LabelsUpdated");
|
||||
is(selRecords[0].text, "**** 7870", "Masked credit card 3");
|
||||
is(selRecords[1].text, "**** 1045, Timothy Berners-Lee", "Masked credit card 2");
|
||||
is(selRecords[2].text, "**** 1111, John Doe", "Masked credit card 1");
|
||||
is(btnShowHideCreditCards.textContent, "Show Credit Cards", "Label should be 'Show Credit Cards'");
|
||||
|
||||
// Show credit card numbers again to test if they revert back to masked form when reloaded
|
||||
EventUtils.synthesizeMouseAtCenter(btnShowHideCreditCards, {}, win);
|
||||
await BrowserTestUtils.waitForEvent(selRecords, "LabelsUpdated");
|
||||
// Ensure credit card numbers are shown again
|
||||
is(selRecords[0].text, "5103059495477870", "Decrypted credit card 3");
|
||||
// Remove a card to trigger reloading
|
||||
await removeCreditCards([selRecords.options[2].value]);
|
||||
await BrowserTestUtils.waitForEvent(selRecords, "RecordsLoaded");
|
||||
is(selRecords[0].text, "**** 7870", "Masked credit card 3");
|
||||
is(selRecords[1].text, "**** 1045, Timothy Berners-Lee", "Masked credit card 2");
|
||||
|
||||
// Remove the rest of the cards
|
||||
await removeCreditCards([selRecords.options[1].value]);
|
||||
await removeCreditCards([selRecords.options[0].value]);
|
||||
await BrowserTestUtils.waitForEvent(selRecords, "RecordsLoaded");
|
||||
is(btnShowHideCreditCards.disabled, true, "Show credit cards button is disabled when there is no card");
|
||||
|
||||
win.close();
|
||||
});
|
||||
|
||||
add_task(async function test_showCreditCardIcons() {
|
||||
await SpecialPowers.pushPrefEnv({"set": [["privacy.reduceTimerPrecision", false]]});
|
||||
await saveCreditCard(TEST_CREDIT_CARD_1);
|
||||
|
@ -193,10 +142,13 @@ add_task(async function test_showCreditCardIcons() {
|
|||
win.close();
|
||||
});
|
||||
|
||||
add_task(async function test_hasEditLoginPrompt() {
|
||||
if (!OSKeyStoreTestUtils.canTestOSKeyStoreLogin()) {
|
||||
todo(OSKeyStoreTestUtils.canTestOSKeyStoreLogin(), "Cannot test OS key store login on official builds.");
|
||||
return;
|
||||
}
|
||||
|
||||
add_task(async function test_hasMasterPassword() {
|
||||
await saveCreditCard(TEST_CREDIT_CARD_1);
|
||||
LoginTestUtils.masterPassword.enable();
|
||||
|
||||
let win = window.openDialog(MANAGE_CREDIT_CARDS_DIALOG_URL, null, DIALOG_SIZE);
|
||||
await waitForFocusAndFormReady(win);
|
||||
|
@ -205,23 +157,29 @@ add_task(async function test_hasMasterPassword() {
|
|||
let btnRemove = win.document.querySelector(TEST_SELECTORS.btnRemove);
|
||||
let btnShowHideCreditCards = win.document.querySelector(TEST_SELECTORS.btnShowHideCreditCards);
|
||||
let btnAdd = win.document.querySelector(TEST_SELECTORS.btnAdd);
|
||||
let btnEdit = win.document.querySelector(TEST_SELECTORS.btnEdit);
|
||||
let masterPasswordDialogShown = waitForMasterPasswordDialog();
|
||||
// let btnEdit = win.document.querySelector(TEST_SELECTORS.btnEdit);
|
||||
|
||||
is(btnShowHideCreditCards.hidden, true, "Show credit cards button is hidden");
|
||||
|
||||
// Master password dialog should show when trying to edit a credit card record.
|
||||
EventUtils.synthesizeMouseAtCenter(selRecords.children[0], {}, win);
|
||||
EventUtils.synthesizeMouseAtCenter(btnEdit, {}, win);
|
||||
await masterPasswordDialogShown;
|
||||
|
||||
// Master password is not required for removing credit cards.
|
||||
// Login dialog should show when trying to edit a credit card record.
|
||||
// TODO: test disabled because re-auth is not implemented yet (bug 1429265).
|
||||
/*
|
||||
let osKeyStoreLoginShown = OSKeyStoreTestUtils.waitForOSKeyStoreLogin(); // cancel
|
||||
EventUtils.synthesizeMouseAtCenter(btnEdit, {}, win);
|
||||
await osKeyStoreLoginShown;
|
||||
await new Promise(resolve => waitForFocus(resolve, win));
|
||||
await new Promise(resolve => executeSoon(resolve));
|
||||
*/
|
||||
|
||||
// Login is not required for removing credit cards.
|
||||
EventUtils.synthesizeMouseAtCenter(btnRemove, {}, win);
|
||||
await BrowserTestUtils.waitForEvent(selRecords, "RecordsRemoved");
|
||||
is(selRecords.length, 0, "Credit card is removed");
|
||||
|
||||
// gSubDialog.open should be called when trying to add a credit card,
|
||||
// no master password is required.
|
||||
// no OS login dialog is required.
|
||||
window.gSubDialog = {
|
||||
open: url => is(url, EDIT_CREDIT_CARD_DIALOG_URL, "Edit credit card dialog is called"),
|
||||
};
|
||||
|
|
|
@ -8,13 +8,13 @@
|
|||
DEFAULT_REGION_PREF,
|
||||
sleep, expectPopupOpen, openPopupOn, expectPopupClose, closePopup, clickDoorhangerButton,
|
||||
getAddresses, saveAddress, removeAddresses, saveCreditCard,
|
||||
getDisplayedPopupItems, getDoorhangerCheckbox, waitForMasterPasswordDialog,
|
||||
getDisplayedPopupItems, getDoorhangerCheckbox,
|
||||
getNotification, getDoorhangerButton, removeAllRecords, testDialog */
|
||||
|
||||
"use strict";
|
||||
|
||||
ChromeUtils.import("resource://testing-common/LoginTestUtils.jsm", this);
|
||||
ChromeUtils.import("resource://formautofill/MasterPassword.jsm", this);
|
||||
ChromeUtils.import("resource://formautofill/OSKeyStore.jsm", this);
|
||||
ChromeUtils.import("resource://testing-common/OSKeyStoreTestUtils.jsm", this);
|
||||
|
||||
const MANAGE_ADDRESSES_DIALOG_URL = "chrome://formautofill/content/manageAddresses.xhtml";
|
||||
const MANAGE_CREDIT_CARDS_DIALOG_URL = "chrome://formautofill/content/manageCreditCards.xhtml";
|
||||
|
@ -22,8 +22,7 @@ const EDIT_ADDRESS_DIALOG_URL = "chrome://formautofill/content/editAddress.xhtml
|
|||
const EDIT_CREDIT_CARD_DIALOG_URL = "chrome://formautofill/content/editCreditCard.xhtml";
|
||||
const BASE_URL = "http://mochi.test:8888/browser/browser/extensions/formautofill/test/browser/";
|
||||
const FORM_URL = "http://mochi.test:8888/browser/browser/extensions/formautofill/test/browser/autocomplete_basic.html";
|
||||
const CREDITCARD_FORM_URL =
|
||||
"https://example.org/browser/browser/extensions/formautofill/test/browser/autocomplete_creditcard_basic.html";
|
||||
const CREDITCARD_FORM_URL = "https://example.org/browser/browser/extensions/formautofill/test/browser/autocomplete_creditcard_basic.html";
|
||||
const FTU_PREF = "extensions.formautofill.firstTimeUse";
|
||||
const CREDITCARDS_USED_STATUS_PREF = "extensions.formautofill.creditCards.used";
|
||||
const ENABLED_AUTOFILL_ADDRESSES_PREF = "extensions.formautofill.addresses.enabled";
|
||||
|
@ -326,24 +325,6 @@ function getDoorhangerButton(button) {
|
|||
return getNotification()[button];
|
||||
}
|
||||
|
||||
|
||||
// Wait for the master password dialog to popup and enter the password to log in
|
||||
// if "login" is "true" or dismiss it directly if otherwise.
|
||||
function waitForMasterPasswordDialog(login = false) {
|
||||
info("expecting master password dialog loaded");
|
||||
let dialogShown = TestUtils.topicObserved("common-dialog-loaded");
|
||||
return dialogShown.then(([subject]) => {
|
||||
let dialog = subject.Dialog;
|
||||
is(dialog.args.title, "Password Required", "Master password dialog shown");
|
||||
if (login) {
|
||||
dialog.ui.password1Textbox.value = LoginTestUtils.masterPassword.masterPassword;
|
||||
dialog.ui.button0.click();
|
||||
} else {
|
||||
dialog.ui.button1.click();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function removeAllRecords() {
|
||||
let addresses = await getAddresses();
|
||||
if (addresses.length) {
|
||||
|
@ -366,7 +347,7 @@ async function waitForFocusAndFormReady(win) {
|
|||
async function testDialog(url, testFn, arg = undefined) {
|
||||
if (url == EDIT_CREDIT_CARD_DIALOG_URL && arg && arg.record) {
|
||||
arg.record = Object.assign({}, arg.record, {
|
||||
"cc-number": await MasterPassword.decrypt(arg.record["cc-number-encrypted"]),
|
||||
"cc-number": await OSKeyStore.decrypt(arg.record["cc-number-encrypted"]),
|
||||
});
|
||||
}
|
||||
let win = window.openDialog(url, null, "width=600,height=600", arg);
|
||||
|
@ -376,4 +357,11 @@ async function testDialog(url, testFn, arg = undefined) {
|
|||
return unloadPromise;
|
||||
}
|
||||
|
||||
add_task(function setup() {
|
||||
OSKeyStoreTestUtils.setup();
|
||||
});
|
||||
|
||||
registerCleanupFunction(removeAllRecords);
|
||||
registerCleanupFunction(async () => {
|
||||
await OSKeyStoreTestUtils.cleanup();
|
||||
});
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
var EXPORTED_SYMBOLS = [
|
||||
"OSKeyStoreTestUtils",
|
||||
];
|
||||
|
||||
ChromeUtils.import("resource://formautofill/OSKeyStore.jsm", this);
|
||||
// TODO: Consider AppConstants.MOZILLA_OFFICIAL to decide if we could test re-auth (bug 1429265).
|
||||
/*
|
||||
ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
|
||||
ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
*/
|
||||
ChromeUtils.import("resource://testing-common/LoginTestUtils.jsm", this);
|
||||
ChromeUtils.import("resource://testing-common/TestUtils.jsm");
|
||||
|
||||
var OSKeyStoreTestUtils = {
|
||||
/*
|
||||
TEST_ONLY_REAUTH: "extensions.formautofill.osKeyStore.unofficialBuildOnlyLogin",
|
||||
*/
|
||||
|
||||
setup() {
|
||||
// TODO: run tests with master password enabled to ensure NSS-implemented
|
||||
// key store prompts on re-auth (bug 1429265)
|
||||
/*
|
||||
LoginTestUtils.masterPassword.enable();
|
||||
*/
|
||||
|
||||
this.ORIGINAL_STORE_LABEL = OSKeyStore.STORE_LABEL;
|
||||
OSKeyStore.STORE_LABEL = "test-" + Math.random().toString(36).substr(2);
|
||||
},
|
||||
|
||||
async cleanup() {
|
||||
// TODO: run tests with master password enabled to ensure NSS-implemented
|
||||
// key store prompts on re-auth (bug 1429265)
|
||||
/*
|
||||
LoginTestUtils.masterPassword.disable();
|
||||
*/
|
||||
|
||||
await OSKeyStore.cleanup();
|
||||
OSKeyStore.STORE_LABEL = this.ORIGINAL_STORE_LABEL;
|
||||
},
|
||||
|
||||
canTestOSKeyStoreLogin() {
|
||||
// TODO: return true based on whether or not we could test the prompt on
|
||||
// the platform (bug 1429265).
|
||||
/*
|
||||
return OSKeyStore.isNSSKeyStore || !AppConstants.MOZILLA_OFFICIAL;
|
||||
*/
|
||||
return true;
|
||||
},
|
||||
|
||||
// Wait for the master password dialog to popup and enter the password to log in
|
||||
// if "login" is "true" or dismiss it directly if otherwise.
|
||||
async waitForOSKeyStoreLogin(login = false) {
|
||||
// TODO: Always resolves for now, because we are skipping re-auth on all
|
||||
// platforms (bug 1429265).
|
||||
/*
|
||||
if (OSKeyStore.isNSSKeyStore) {
|
||||
await this.waitForMasterPasswordDialog(login);
|
||||
return;
|
||||
}
|
||||
|
||||
const str = login ? "pass" : "cancel";
|
||||
|
||||
Services.prefs.setStringPref(this.TEST_ONLY_REAUTH, str);
|
||||
|
||||
await TestUtils.topicObserved("oskeystore-testonly-reauth",
|
||||
(subject, data) => data == str);
|
||||
|
||||
Services.prefs.setStringPref(this.TEST_ONLY_REAUTH, "");
|
||||
*/
|
||||
},
|
||||
|
||||
async waitForMasterPasswordDialog(login = false) {
|
||||
let [subject] = await TestUtils.topicObserved("common-dialog-loaded");
|
||||
|
||||
let dialog = subject.Dialog;
|
||||
if (dialog.args.title !== "Password Required") {
|
||||
throw new Error("Incorrect master password dialog title");
|
||||
}
|
||||
|
||||
if (login) {
|
||||
dialog.ui.password1Textbox.value = LoginTestUtils.masterPassword.masterPassword;
|
||||
dialog.ui.button0.click();
|
||||
} else {
|
||||
dialog.ui.button1.click();
|
||||
}
|
||||
await TestUtils.waitForTick();
|
||||
},
|
||||
};
|
|
@ -1,5 +1,6 @@
|
|||
/* import-globals-from ../../../../../testing/mochitest/tests/SimpleTest/SimpleTest.js */
|
||||
/* import-globals-from ../../../../../testing/mochitest/tests/SimpleTest/EventUtils.js */
|
||||
/* import-globals-from ../../../../../testing/mochitest/tests/SimpleTest/AddTask.js */
|
||||
/* import-globals-from ../../../../../toolkit/components/satchel/test/satchel_common.js */
|
||||
/* eslint-disable no-unused-vars */
|
||||
|
||||
|
@ -201,6 +202,15 @@ async function cleanUpStorage() {
|
|||
await cleanUpCreditCards();
|
||||
}
|
||||
|
||||
async function canTestOSKeyStoreLogin() {
|
||||
let {canTest} = await invokeAsyncChromeTask("FormAutofillTest:CanTestOSKeyStoreLogin", "FormAutofillTest:CanTestOSKeyStoreLoginResult");
|
||||
return canTest;
|
||||
}
|
||||
|
||||
async function waitForOSKeyStoreLogin(login = false) {
|
||||
await invokeAsyncChromeTask("FormAutofillTest:OSKeyStoreLogin", "FormAutofillTest:OSKeyStoreLoggedIn", {login});
|
||||
}
|
||||
|
||||
function patchRecordCCNumber(record) {
|
||||
const number = record["cc-number"];
|
||||
const ccNumberFmt = {
|
||||
|
@ -263,6 +273,12 @@ function formAutoFillCommonSetup() {
|
|||
}
|
||||
});
|
||||
|
||||
add_task(async function setup() {
|
||||
formFillChromeScript.sendAsyncMessage("setup");
|
||||
info(`expecting the storage setup`);
|
||||
await formFillChromeScript.promiseOneMessage("setup-finished");
|
||||
});
|
||||
|
||||
SimpleTest.registerCleanupFunction(async () => {
|
||||
formFillChromeScript.sendAsyncMessage("cleanup");
|
||||
info(`expecting the storage cleanup`);
|
||||
|
|
|
@ -4,8 +4,11 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
|
||||
ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
ChromeUtils.import("resource://formautofill/FormAutofillUtils.jsm");
|
||||
ChromeUtils.import("resource://formautofill/OSKeyStore.jsm");
|
||||
ChromeUtils.import("resource://testing-common/OSKeyStoreTestUtils.jsm");
|
||||
|
||||
let {formAutofillStorage} = ChromeUtils.import("resource://formautofill/FormAutofillStorage.jsm", {});
|
||||
|
||||
|
@ -114,9 +117,15 @@ var ParentUtils = {
|
|||
await this.operateCreditCard("remove", {guids}, "FormAutofillTest:CreditCardsCleanedUp");
|
||||
},
|
||||
|
||||
setup() {
|
||||
OSKeyStoreTestUtils.setup();
|
||||
},
|
||||
|
||||
async cleanup() {
|
||||
await this.cleanUpAddresses();
|
||||
await this.cleanUpCreditCards();
|
||||
await OSKeyStoreTestUtils.cleanup();
|
||||
|
||||
Services.obs.removeObserver(this, "formautofill-storage-changed");
|
||||
},
|
||||
|
||||
|
@ -215,8 +224,23 @@ addMessageListener("FormAutofillTest:CleanUpCreditCards", (msg) => {
|
|||
ParentUtils.cleanUpCreditCards();
|
||||
});
|
||||
|
||||
addMessageListener("cleanup", () => {
|
||||
ParentUtils.cleanup().then(() => {
|
||||
sendAsyncMessage("cleanup-finished", {});
|
||||
});
|
||||
addMessageListener("FormAutofillTest:CanTestOSKeyStoreLogin", (msg) => {
|
||||
sendAsyncMessage("FormAutofillTest:CanTestOSKeyStoreLoginResult",
|
||||
{canTest: OSKeyStore.isNSSKeyStore || !AppConstants.MOZILLA_OFFICIAL});
|
||||
});
|
||||
|
||||
addMessageListener("FormAutofillTest:OSKeyStoreLogin", async (msg) => {
|
||||
await OSKeyStoreTestUtils.waitForOSKeyStoreLogin(msg.login);
|
||||
sendAsyncMessage("FormAutofillTest:OSKeyStoreLoggedIn");
|
||||
});
|
||||
|
||||
addMessageListener("setup", async () => {
|
||||
ParentUtils.setup();
|
||||
sendAsyncMessage("setup-finished", {});
|
||||
});
|
||||
|
||||
addMessageListener("cleanup", async () => {
|
||||
await ParentUtils.cleanup();
|
||||
|
||||
sendAsyncMessage("cleanup-finished", {});
|
||||
});
|
||||
|
|
|
@ -150,8 +150,16 @@ add_task(async function check_search_result_for_pref_off() {
|
|||
await SpecialPowers.popPrefEnv();
|
||||
});
|
||||
|
||||
let canTest;
|
||||
|
||||
// Autofill the credit card from dropdown menu.
|
||||
add_task(async function check_fields_after_form_autofill() {
|
||||
canTest = await canTestOSKeyStoreLogin();
|
||||
if (!canTest) {
|
||||
todo(canTest, "Cannot test OS key store login on official builds.");
|
||||
return;
|
||||
}
|
||||
|
||||
await setInput("#cc-exp-year", 202);
|
||||
|
||||
synthesizeKey("KEY_ArrowDown");
|
||||
|
@ -162,11 +170,18 @@ add_task(async function check_fields_after_form_autofill() {
|
|||
})));
|
||||
|
||||
synthesizeKey("KEY_ArrowDown");
|
||||
let osKeyStoreLoginShown = waitForOSKeyStoreLogin(true);
|
||||
await new Promise(resolve => SimpleTest.executeSoon(resolve));
|
||||
await triggerAutofillAndCheckProfile(MOCK_STORAGE[1]);
|
||||
await osKeyStoreLoginShown;
|
||||
});
|
||||
|
||||
// Fallback to history search after autofill address.
|
||||
add_task(async function check_fallback_after_form_autofill() {
|
||||
if (!canTest) {
|
||||
return;
|
||||
}
|
||||
|
||||
await setInput("#cc-name", "", true);
|
||||
synthesizeKey("KEY_ArrowDown");
|
||||
await expectPopup();
|
||||
|
@ -175,6 +190,10 @@ add_task(async function check_fallback_after_form_autofill() {
|
|||
|
||||
// Resume form autofill once all the autofilled fileds are changed.
|
||||
add_task(async function check_form_autofill_resume() {
|
||||
if (!canTest) {
|
||||
return;
|
||||
}
|
||||
|
||||
document.querySelector("#cc-name").blur();
|
||||
document.querySelector("#form1").reset();
|
||||
|
||||
|
|
|
@ -103,9 +103,16 @@ add_task(async function clear_modified_form() {
|
|||
});
|
||||
|
||||
add_task(async function clear_distinct_section() {
|
||||
if (!(await canTestOSKeyStoreLogin())) {
|
||||
todo(false, "Cannot test OS key store login on official builds.");
|
||||
return;
|
||||
}
|
||||
|
||||
document.getElementById("form1").reset();
|
||||
await triggerPopupAndHoverItem("#cc-name", 0);
|
||||
let osKeyStoreLoginShown = waitForOSKeyStoreLogin(true);
|
||||
await triggerAutofillAndCheckProfile(MOCK_CC_STORAGE[0]);
|
||||
await osKeyStoreLoginShown;
|
||||
|
||||
await triggerPopupAndHoverItem("#organization", 0);
|
||||
await triggerAutofillAndCheckProfile(MOCK_ADDR_STORAGE[0]);
|
||||
|
|
|
@ -99,7 +99,7 @@ async function initProfileStorage(fileName, records, collectionName = "addresses
|
|||
subject.wrappedJSObject.collectionName == collectionName
|
||||
);
|
||||
for (let record of records) {
|
||||
Assert.ok(profileStorage[collectionName].add(record));
|
||||
Assert.ok(await profileStorage[collectionName].add(record));
|
||||
await onChanged;
|
||||
}
|
||||
await profileStorage._saveImmediately();
|
||||
|
@ -223,3 +223,13 @@ add_task(async function head_initialize() {
|
|||
|
||||
await loadExtension();
|
||||
});
|
||||
|
||||
let OSKeyStoreTestUtils;
|
||||
add_task(async function os_key_store_setup() {
|
||||
({OSKeyStoreTestUtils} =
|
||||
ChromeUtils.import("resource://testing-common/OSKeyStoreTestUtils.jsm", {}));
|
||||
OSKeyStoreTestUtils.setup();
|
||||
registerCleanupFunction(async function cleanup() {
|
||||
await OSKeyStoreTestUtils.cleanup();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,10 +5,9 @@
|
|||
|
||||
"use strict";
|
||||
|
||||
let MasterPassword;
|
||||
add_task(async function setup() {
|
||||
ChromeUtils.import("resource://formautofill/FormAutofillHandler.jsm");
|
||||
({MasterPassword} = ChromeUtils.import("resource://formautofill/MasterPassword.jsm", {}));
|
||||
ChromeUtils.import("resource://formautofill/OSKeyStore.jsm");
|
||||
});
|
||||
|
||||
const TESTCASES = [
|
||||
|
@ -477,7 +476,7 @@ function do_test(testcases, testFn) {
|
|||
info("Starting testcase: " + testcase.description);
|
||||
let ccNumber = testcase.profileData["cc-number"];
|
||||
if (ccNumber) {
|
||||
testcase.profileData["cc-number-encrypted"] = await MasterPassword.encrypt(ccNumber);
|
||||
testcase.profileData["cc-number-encrypted"] = await OSKeyStore.encrypt(ccNumber);
|
||||
delete testcase.profileData["cc-number"];
|
||||
}
|
||||
|
||||
|
@ -487,18 +486,11 @@ function do_test(testcases, testFn) {
|
|||
let formLike = FormLikeFactory.createFromForm(form);
|
||||
let handler = new FormAutofillHandler(formLike);
|
||||
let promises = [];
|
||||
// Replace the interal decrypt method with MasterPassword API
|
||||
// Replace the internal decrypt method with OSKeyStore API,
|
||||
// but don't pass the reauth parameter to avoid triggering
|
||||
// reauth login dialog in these tests.
|
||||
let decryptHelper = async (cipherText, reauth) => {
|
||||
let string;
|
||||
try {
|
||||
string = await MasterPassword.decrypt(cipherText, reauth);
|
||||
} catch (e) {
|
||||
if (e.result != Cr.NS_ERROR_ABORT) {
|
||||
throw e;
|
||||
}
|
||||
info("User canceled master password entry");
|
||||
}
|
||||
return string;
|
||||
return OSKeyStore.decrypt(cipherText, false);
|
||||
};
|
||||
|
||||
handler.collectFormFields();
|
||||
|
|
|
@ -655,18 +655,12 @@ add_task(async function test_getDuplicateGuid() {
|
|||
// This number differs from TEST_CREDIT_CARD_3 by swapping the order of the
|
||||
// 09 and 90 adjacent digits, which is still a valid credit card number.
|
||||
record["cc-number"] = "358999378390" + last4Digits;
|
||||
Assert.equal(await profileStorage.creditCards.getDuplicateGuid(record), null);
|
||||
|
||||
// ... However, we treat numbers with the same last 4 digits as a duplicate if
|
||||
// the master password is enabled.
|
||||
let tokendb = Cc["@mozilla.org/security/pk11tokendb;1"].createInstance(Ci.nsIPK11TokenDB);
|
||||
let token = tokendb.getInternalKeyToken();
|
||||
token.reset();
|
||||
token.initPassword("password");
|
||||
// We treat numbers with the same last 4 digits as a duplicate.
|
||||
Assert.equal(await profileStorage.creditCards.getDuplicateGuid(record), guid);
|
||||
|
||||
// ... Even though the master password is enabled and the last 4 digits are the
|
||||
// same, an invalid credit card number should never be treated as a duplicate.
|
||||
// Even though the last 4 digits are the same, an invalid credit card number
|
||||
// should never be treated as a duplicate.
|
||||
record["cc-number"] = "************" + last4Digits;
|
||||
Assert.equal(await profileStorage.creditCards.getDuplicateGuid(record), null);
|
||||
});
|
||||
|
|
|
@ -7,9 +7,10 @@
|
|||
ChromeUtils.import("resource://gre/modules/CreditCard.jsm");
|
||||
|
||||
let FormAutofillParent;
|
||||
let OSKeyStore;
|
||||
add_task(async function setup() {
|
||||
({FormAutofillParent} = ChromeUtils.import("resource://formautofill/FormAutofillParent.jsm", {}));
|
||||
ChromeUtils.import("resource://formautofill/MasterPassword.jsm");
|
||||
({OSKeyStore} = ChromeUtils.import("resource://formautofill/OSKeyStore.jsm", {}));
|
||||
});
|
||||
|
||||
const TEST_ADDRESS_1 = {
|
||||
|
@ -176,37 +177,24 @@ add_task(async function test_getRecords_creditCards() {
|
|||
let encryptedCCRecords = await Promise.all([TEST_CREDIT_CARD_1, TEST_CREDIT_CARD_2].map(async record => {
|
||||
let clonedRecord = Object.assign({}, record);
|
||||
clonedRecord["cc-number"] = CreditCard.getLongMaskedNumber(record["cc-number"]);
|
||||
clonedRecord["cc-number-encrypted"] = await MasterPassword.encrypt(record["cc-number"]);
|
||||
clonedRecord["cc-number-encrypted"] = await OSKeyStore.encrypt(record["cc-number"]);
|
||||
return clonedRecord;
|
||||
}));
|
||||
sinon.stub(collection, "getAll", () =>
|
||||
Promise.resolve([Object.assign({}, encryptedCCRecords[0]), Object.assign({}, encryptedCCRecords[1])]));
|
||||
let CreditCardsWithDecryptedNumber = [
|
||||
Object.assign({}, encryptedCCRecords[0], {"cc-number-decrypted": TEST_CREDIT_CARD_1["cc-number"]}),
|
||||
Object.assign({}, encryptedCCRecords[1], {"cc-number-decrypted": TEST_CREDIT_CARD_2["cc-number"]}),
|
||||
];
|
||||
|
||||
let testCases = [
|
||||
{
|
||||
description: "If the search string could match 1 creditCard (without masterpassword)",
|
||||
filter: {
|
||||
collectionName: "creditCards",
|
||||
info: {fieldName: "cc-name"},
|
||||
searchString: "John Doe",
|
||||
},
|
||||
expectedResult: CreditCardsWithDecryptedNumber.slice(0, 1),
|
||||
},
|
||||
{
|
||||
description: "If the search string could match multiple creditCards (without masterpassword)",
|
||||
description: "If the search string could match multiple creditCards",
|
||||
filter: {
|
||||
collectionName: "creditCards",
|
||||
info: {fieldName: "cc-name"},
|
||||
searchString: "John",
|
||||
},
|
||||
expectedResult: CreditCardsWithDecryptedNumber,
|
||||
expectedResult: encryptedCCRecords,
|
||||
},
|
||||
{
|
||||
description: "If the search string could not match any creditCard (without masterpassword)",
|
||||
description: "If the search string could not match any creditCard",
|
||||
filter: {
|
||||
collectionName: "creditCards",
|
||||
info: {fieldName: "cc-name"},
|
||||
|
@ -215,25 +203,17 @@ add_task(async function test_getRecords_creditCards() {
|
|||
expectedResult: [],
|
||||
},
|
||||
{
|
||||
description: "If the search number string could match 1 creditCard (without masterpassword)",
|
||||
filter: {
|
||||
collectionName: "creditCards",
|
||||
info: {fieldName: "cc-number"},
|
||||
searchString: "411",
|
||||
},
|
||||
expectedResult: CreditCardsWithDecryptedNumber.slice(0, 1),
|
||||
},
|
||||
{
|
||||
description: "If the search string could match multiple creditCards (without masterpassword)",
|
||||
description: "Return all creditCards if focused field is cc number; " +
|
||||
"if the search string could match multiple creditCards",
|
||||
filter: {
|
||||
collectionName: "creditCards",
|
||||
info: {fieldName: "cc-number"},
|
||||
searchString: "4",
|
||||
},
|
||||
expectedResult: CreditCardsWithDecryptedNumber,
|
||||
expectedResult: encryptedCCRecords,
|
||||
},
|
||||
{
|
||||
description: "If the search string could match 1 creditCard (with masterpassword)",
|
||||
description: "If the search string could match 1 creditCard",
|
||||
filter: {
|
||||
collectionName: "creditCards",
|
||||
info: {fieldName: "cc-name"},
|
||||
|
@ -243,7 +223,7 @@ add_task(async function test_getRecords_creditCards() {
|
|||
expectedResult: encryptedCCRecords.slice(0, 1),
|
||||
},
|
||||
{
|
||||
description: "Return all creditCards if focused field is cc number (with masterpassword)",
|
||||
description: "Return all creditCards if focused field is cc number",
|
||||
filter: {
|
||||
collectionName: "creditCards",
|
||||
info: {fieldName: "cc-number"},
|
||||
|
|
|
@ -1,119 +0,0 @@
|
|||
/**
|
||||
* Tests of MasterPassword.jsm
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
const {MockRegistrar} =
|
||||
ChromeUtils.import("resource://testing-common/MockRegistrar.jsm", {});
|
||||
|
||||
let MasterPassword;
|
||||
add_task(async function setup() {
|
||||
({MasterPassword} = ChromeUtils.import("resource://formautofill/MasterPassword.jsm", {}));
|
||||
});
|
||||
|
||||
const TESTCASES = [{
|
||||
description: "With master password set",
|
||||
masterPassword: "fakemp",
|
||||
mpEnabled: true,
|
||||
},
|
||||
{
|
||||
description: "Without master password set",
|
||||
masterPassword: "", // "" means no master password
|
||||
mpEnabled: false,
|
||||
}];
|
||||
|
||||
|
||||
// Tests that PSM can successfully ask for a password from the user and relay it
|
||||
// back to NSS. Does so by mocking out the actual dialog and "filling in" the
|
||||
// password. Also tests that providing an incorrect password will fail (well,
|
||||
// technically the user will just get prompted again, but if they then cancel
|
||||
// the dialog the overall operation will fail).
|
||||
|
||||
let gMockPrompter = {
|
||||
passwordToTry: null,
|
||||
numPrompts: 0,
|
||||
|
||||
// This intentionally does not use arrow function syntax to avoid an issue
|
||||
// where in the context of the arrow function, |this != gMockPrompter| due to
|
||||
// how objects get wrapped when going across xpcom boundaries.
|
||||
promptPassword(dialogTitle, text, password, checkMsg, checkValue) {
|
||||
this.numPrompts++;
|
||||
if (this.numPrompts > 1) { // don't keep retrying a bad password
|
||||
return false;
|
||||
}
|
||||
equal(text,
|
||||
"Please enter your master password.",
|
||||
"password prompt text should be as expected");
|
||||
equal(checkMsg, null, "checkMsg should be null");
|
||||
ok(this.passwordToTry, "passwordToTry should be non-null");
|
||||
password.value = this.passwordToTry;
|
||||
return true;
|
||||
},
|
||||
|
||||
QueryInterface: ChromeUtils.generateQI([Ci.nsIPrompt]),
|
||||
};
|
||||
|
||||
// Mock nsIWindowWatcher. PSM calls getNewPrompter on this to get an nsIPrompt
|
||||
// to call promptPassword. We return the mock one, above.
|
||||
let gWindowWatcher = {
|
||||
getNewPrompter: () => gMockPrompter,
|
||||
QueryInterface: ChromeUtils.generateQI([Ci.nsIWindowWatcher]),
|
||||
};
|
||||
|
||||
// Ensure that the appropriate initialization has happened.
|
||||
do_get_profile();
|
||||
|
||||
let windowWatcherCID =
|
||||
MockRegistrar.register("@mozilla.org/embedcomp/window-watcher;1",
|
||||
gWindowWatcher);
|
||||
registerCleanupFunction(() => {
|
||||
MockRegistrar.unregister(windowWatcherCID);
|
||||
});
|
||||
|
||||
TESTCASES.forEach(testcase => {
|
||||
add_task(async function test_encrypt_decrypt() {
|
||||
info("Starting testcase: " + testcase.description);
|
||||
|
||||
let token = MasterPassword._token;
|
||||
token.initPassword(testcase.masterPassword);
|
||||
|
||||
// Test only: Force the token login without asking for master password
|
||||
token.login(/* force */ false);
|
||||
Assert.equal(testcase.mpEnabled, token.isLoggedIn(), "Token should now be logged into");
|
||||
Assert.equal(MasterPassword.isEnabled, testcase.mpEnabled);
|
||||
|
||||
let testText = "test string";
|
||||
let cipherText = await MasterPassword.encrypt(testText);
|
||||
Assert.notEqual(testText, cipherText);
|
||||
let plainText = await MasterPassword.decrypt(cipherText);
|
||||
Assert.equal(testText, plainText);
|
||||
if (token.isLoggedIn()) {
|
||||
// Reset state.
|
||||
gMockPrompter.numPrompts = 0;
|
||||
token.logoutSimple();
|
||||
|
||||
ok(!token.isLoggedIn(),
|
||||
"Token should be logged out after calling logoutSimple()");
|
||||
|
||||
// Try with the correct password.
|
||||
gMockPrompter.passwordToTry = testcase.masterPassword;
|
||||
await MasterPassword.encrypt(testText);
|
||||
Assert.equal(gMockPrompter.numPrompts, 1, "should have prompted for encryption");
|
||||
|
||||
// Reset state.
|
||||
gMockPrompter.numPrompts = 0;
|
||||
token.logoutSimple();
|
||||
|
||||
try {
|
||||
// Try with the incorrect password.
|
||||
gMockPrompter.passwordToTry = "XXX";
|
||||
await MasterPassword.decrypt(cipherText);
|
||||
throw new Error("Not receiving canceled master password error");
|
||||
} catch (e) {
|
||||
Assert.equal(e.message, "User canceled master password entry");
|
||||
}
|
||||
}
|
||||
|
||||
token.reset();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,146 @@
|
|||
/**
|
||||
* Tests of OSKeyStore.jsm
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
|
||||
ChromeUtils.import("resource://testing-common/MockRegistrar.jsm");
|
||||
|
||||
let OSKeyStore;
|
||||
add_task(async function setup() {
|
||||
({OSKeyStore} = ChromeUtils.import("resource://formautofill/OSKeyStore.jsm", {}));
|
||||
});
|
||||
|
||||
// Ensure that the appropriate initialization has happened.
|
||||
do_get_profile();
|
||||
|
||||
// For NSS key store, mocking out the dialog and control it from here.
|
||||
let gMockPrompter = {
|
||||
passwordToTry: "hunter2",
|
||||
resolve: null,
|
||||
login: undefined,
|
||||
|
||||
// This intentionally does not use arrow function syntax to avoid an issue
|
||||
// where in the context of the arrow function, |this != gMockPrompter| due to
|
||||
// how objects get wrapped when going across xpcom boundaries.
|
||||
promptPassword(dialogTitle, text, password, checkMsg, checkValue) {
|
||||
equal(text,
|
||||
"Please enter your master password.",
|
||||
"password prompt text should be as expected");
|
||||
equal(checkMsg, null, "checkMsg should be null");
|
||||
if (this.login) {
|
||||
password.value = this.passwordToTry;
|
||||
}
|
||||
this.resolve();
|
||||
this.resolve = null;
|
||||
|
||||
return this.login;
|
||||
},
|
||||
|
||||
QueryInterface: ChromeUtils.generateQI([Ci.nsIPrompt]),
|
||||
};
|
||||
|
||||
// Mock nsIWindowWatcher. PSM calls getNewPrompter on this to get an nsIPrompt
|
||||
// to call promptPassword. We return the mock one, above.
|
||||
let gWindowWatcher = {
|
||||
getNewPrompter: () => gMockPrompter,
|
||||
QueryInterface: ChromeUtils.generateQI([Ci.nsIWindowWatcher]),
|
||||
};
|
||||
|
||||
let nssToken;
|
||||
|
||||
const TEST_ONLY_REAUTH = "extensions.formautofill.osKeyStore.unofficialBuildOnlyLogin";
|
||||
|
||||
async function waitForReauth(login = false) {
|
||||
if (OSKeyStore.isNSSKeyStore) {
|
||||
gMockPrompter.login = login;
|
||||
await new Promise(resolve => { gMockPrompter.resolve = resolve; });
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let value = login ? "pass" : "cancel";
|
||||
Services.prefs.setStringPref(TEST_ONLY_REAUTH, value);
|
||||
await TestUtils.topicObserved("oskeystore-testonly-reauth",
|
||||
(subject, data) => data == value);
|
||||
}
|
||||
|
||||
const testText = "test string";
|
||||
let cipherText;
|
||||
|
||||
add_task(async function test_encrypt_decrypt() {
|
||||
Assert.equal(OSKeyStore.isEnabled, true);
|
||||
|
||||
Assert.equal(await OSKeyStore.ensureLoggedIn(), true, "Started logged in.");
|
||||
|
||||
cipherText = await OSKeyStore.encrypt(testText);
|
||||
Assert.notEqual(testText, cipherText);
|
||||
|
||||
let plainText = await OSKeyStore.decrypt(cipherText);
|
||||
Assert.equal(testText, plainText);
|
||||
});
|
||||
|
||||
// TODO: skipped because re-auth is not implemented (bug 1429265).
|
||||
add_task(async function test_reauth() {
|
||||
let canTest = OSKeyStore.isNSSKeyStore || !AppConstants.MOZILLA_OFFICIAL;
|
||||
if (!canTest) {
|
||||
todo_check_false(canTest,
|
||||
"test_reauth: Cannot test OS key store login on official builds.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (OSKeyStore.isNSSKeyStore) {
|
||||
let windowWatcherCID;
|
||||
windowWatcherCID =
|
||||
MockRegistrar.register("@mozilla.org/embedcomp/window-watcher;1",
|
||||
gWindowWatcher);
|
||||
registerCleanupFunction(() => {
|
||||
MockRegistrar.unregister(windowWatcherCID);
|
||||
});
|
||||
|
||||
// If we use the NSS key store implementation test that everything works
|
||||
// when a master password is set.
|
||||
// Set an initial password.
|
||||
let tokenDB = Cc["@mozilla.org/security/pk11tokendb;1"]
|
||||
.getService(Ci.nsIPK11TokenDB);
|
||||
nssToken = tokenDB.getInternalKeyToken();
|
||||
nssToken.initPassword("hunter2");
|
||||
}
|
||||
|
||||
let reauthObserved = waitForReauth(false);
|
||||
await new Promise(resolve => TestUtils.executeSoon(resolve));
|
||||
try {
|
||||
await OSKeyStore.decrypt(cipherText, true);
|
||||
throw new Error("Not receiving canceled OS unlock error");
|
||||
} catch (ex) {
|
||||
Assert.equal(ex.message, "User canceled OS unlock entry");
|
||||
Assert.equal(ex.result, Cr.NS_ERROR_ABORT);
|
||||
}
|
||||
await reauthObserved;
|
||||
|
||||
reauthObserved = waitForReauth(false);
|
||||
await new Promise(resolve => TestUtils.executeSoon(resolve));
|
||||
Assert.equal(await OSKeyStore.ensureLoggedIn(true), false, "Reauth cancelled.");
|
||||
await reauthObserved;
|
||||
|
||||
reauthObserved = waitForReauth(true);
|
||||
await new Promise(resolve => TestUtils.executeSoon(resolve));
|
||||
let plainText2 = await OSKeyStore.decrypt(cipherText, true);
|
||||
await reauthObserved;
|
||||
Assert.equal(testText, plainText2);
|
||||
|
||||
reauthObserved = waitForReauth(true);
|
||||
await new Promise(resolve => TestUtils.executeSoon(resolve));
|
||||
Assert.equal(await OSKeyStore.ensureLoggedIn(true), true, "Reauth logged in.");
|
||||
await reauthObserved;
|
||||
}).skip();
|
||||
|
||||
add_task(async function test_decryption_failure() {
|
||||
try {
|
||||
await OSKeyStore.decrypt("Malformed cipher text");
|
||||
throw new Error("Not receiving decryption error");
|
||||
} catch (ex) {
|
||||
Assert.notEqual(ex.result, Cr.NS_ERROR_ABORT);
|
||||
}
|
||||
});
|
|
@ -38,10 +38,10 @@ support-files =
|
|||
[test_isCJKName.js]
|
||||
[test_isFieldEligibleForAutofill.js]
|
||||
[test_markAsAutofillField.js]
|
||||
[test_masterPassword.js]
|
||||
[test_migrateRecords.js]
|
||||
[test_nameUtils.js]
|
||||
[test_onFormSubmitted.js]
|
||||
[test_osKeyStore.js]
|
||||
[test_parseAddressFormat.js]
|
||||
[test_profileAutocompleteResult.js]
|
||||
[test_phoneNumber.js]
|
||||
|
|
|
@ -15,7 +15,7 @@ interface nsIOSKeyStore: nsISupports {
|
|||
* Usage:
|
||||
*
|
||||
* // obtain the singleton OSKeyStore instance
|
||||
* const oskeystore = Cc["@mozilla.org/oskeystore;1"].getService(Ci.nsIOSKeyStore);
|
||||
* const oskeystore = Cc["@mozilla.org/security/oskeystore;1"].getService(Ci.nsIOSKeyStore);
|
||||
*
|
||||
* const PASSWORD_LABEL = "mylabel1";
|
||||
* const COOKIE_LABEL = "mylabel2";
|
||||
|
|
|
@ -14,8 +14,8 @@ ChromeUtils.import("resource://tps/logger.jsm");
|
|||
ChromeUtils.defineModuleGetter(this, "formAutofillStorage",
|
||||
"resource://formautofill/FormAutofillStorage.jsm");
|
||||
|
||||
ChromeUtils.defineModuleGetter(this, "MasterPassword",
|
||||
"resource://formautofill/MasterPassword.jsm");
|
||||
ChromeUtils.defineModuleGetter(this, "OSKeyStore",
|
||||
"resource://formautofill/OSKeyStore.jsm");
|
||||
|
||||
class FormAutofillBase {
|
||||
constructor(props, subStorageName, fields) {
|
||||
|
@ -111,7 +111,7 @@ class CreditCard extends FormAutofillBase {
|
|||
async Find() {
|
||||
const storage = await this.getStorage();
|
||||
await Promise.all(storage._data.map(
|
||||
async entry => entry["cc-number"] = await MasterPassword.decrypt(entry["cc-number-encrypted"])));
|
||||
async entry => entry["cc-number"] = await OSKeyStore.decrypt(entry["cc-number-encrypted"])));
|
||||
return storage._data.find(entry => {
|
||||
return this._fields.every(field => entry[field] === this.props[field]);
|
||||
});
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
|
||||
var EXPORTED_SYMBOLS = ["CreditCard"];
|
||||
|
||||
ChromeUtils.defineModuleGetter(this, "MasterPassword",
|
||||
"resource://formautofill/MasterPassword.jsm");
|
||||
ChromeUtils.defineModuleGetter(this, "OSKeyStore",
|
||||
"resource://formautofill/OSKeyStore.jsm");
|
||||
|
||||
// The list of known and supported credit card network ids ("types")
|
||||
// This list mirrors the networks from dom/payments/BasicCardPayment.cpp
|
||||
|
@ -208,7 +208,7 @@ class CreditCard {
|
|||
|
||||
if (showNumbers) {
|
||||
if (this._encryptedNumber) {
|
||||
label = await MasterPassword.decrypt(this._encryptedNumber);
|
||||
label = await OSKeyStore.decrypt(this._encryptedNumber);
|
||||
} else {
|
||||
label = this._number;
|
||||
}
|
||||
|
|
|
@ -4,38 +4,39 @@
|
|||
"use strict";
|
||||
|
||||
ChromeUtils.import("resource://gre/modules/CreditCard.jsm");
|
||||
ChromeUtils.import("resource://formautofill/MasterPassword.jsm");
|
||||
ChromeUtils.import("resource://formautofill/OSKeyStore.jsm");
|
||||
ChromeUtils.import("resource://testing-common/OSKeyStoreTestUtils.jsm");
|
||||
|
||||
let oldGetters = {};
|
||||
let gFakeLoggedIn = true;
|
||||
|
||||
add_task(function setup() {
|
||||
oldGetters._token = Object.getOwnPropertyDescriptor(MasterPassword, "_token").get;
|
||||
oldGetters.isEnabled = Object.getOwnPropertyDescriptor(MasterPassword, "isEnabled").get;
|
||||
oldGetters.isLoggedIn = Object.getOwnPropertyDescriptor(MasterPassword, "isLoggedIn").get;
|
||||
MasterPassword.__defineGetter__("_token", () => { return {hasPassword: true}; });
|
||||
MasterPassword.__defineGetter__("isEnabled", () => true);
|
||||
MasterPassword.__defineGetter__("isLoggedIn", () => gFakeLoggedIn);
|
||||
registerCleanupFunction(() => {
|
||||
MasterPassword.__defineGetter__("_token", oldGetters._token);
|
||||
MasterPassword.__defineGetter__("isEnabled", oldGetters.isEnabled);
|
||||
MasterPassword.__defineGetter__("isLoggedIn", oldGetters.isLoggedIn);
|
||||
OSKeyStoreTestUtils.setup();
|
||||
oldGetters.isEnabled = Object.getOwnPropertyDescriptor(OSKeyStore, "isEnabled").get;
|
||||
oldGetters.isLoggedIn = Object.getOwnPropertyDescriptor(OSKeyStore, "isLoggedIn").get;
|
||||
OSKeyStore.__defineGetter__("isEnabled", () => true);
|
||||
OSKeyStore.__defineGetter__("isLoggedIn", () => gFakeLoggedIn);
|
||||
registerCleanupFunction(async () => {
|
||||
OSKeyStore.__defineGetter__("isEnabled", oldGetters.isEnabled);
|
||||
OSKeyStore.__defineGetter__("isLoggedIn", oldGetters.isLoggedIn);
|
||||
await OSKeyStoreTestUtils.cleanup();
|
||||
|
||||
// CreditCard.jsm and MasterPassword.jsm are imported into the global scope
|
||||
// -- the window -- above. If they're not deleted, they outlive the test and
|
||||
// are reported as a leak.
|
||||
delete window.MasterPassword;
|
||||
// CreditCard.jsm, OSKeyStore.jsm, and OSKeyStoreTestUtils.jsm are imported
|
||||
// into the global scope -- the window -- above. If they're not deleted,
|
||||
// they outlive the test and are reported as a leak.
|
||||
delete window.OSKeyStore;
|
||||
delete window.CreditCard;
|
||||
delete window.OSKeyStoreTestUtils;
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function test_getLabel_withMasterPassword() {
|
||||
ok(MasterPassword.isEnabled, "Confirm that MasterPassword is faked and thinks it is enabled");
|
||||
ok(MasterPassword.isLoggedIn, "Confirm that MasterPassword is faked and thinks it is logged in");
|
||||
add_task(async function test_getLabel_withOSKeyStore() {
|
||||
ok(OSKeyStore.isEnabled, "Confirm that OSKeyStore is faked and thinks it is enabled");
|
||||
ok(OSKeyStore.isLoggedIn, "Confirm that OSKeyStore is faked and thinks it is logged in");
|
||||
|
||||
const ccNumber = "4111111111111111";
|
||||
const encryptedNumber = await MasterPassword.encrypt(ccNumber);
|
||||
const decryptedNumber = await MasterPassword.decrypt(encryptedNumber);
|
||||
const encryptedNumber = await OSKeyStore.encrypt(ccNumber);
|
||||
const decryptedNumber = await OSKeyStore.decrypt(encryptedNumber);
|
||||
is(decryptedNumber, ccNumber, "Decrypted CC number should match original");
|
||||
|
||||
const name = "Foxkeh";
|
||||
|
|
Загрузка…
Ссылка в новой задаче