Bug 1185000 - Password manager should not offer to save credit card numbers. r=jaws

Password manager should not offer to save credit card numbers in certain straight-forward cases.

Differential Revision: https://phabricator.services.mozilla.com/D25485

--HG--
extra : moz-landing-system : lando
This commit is contained in:
prathiksha 2019-04-12 21:54:06 +00:00
Родитель cc5870448a
Коммит 497abd940b
7 изменённых файлов: 167 добавлений и 22 удалений

Просмотреть файл

@ -107,8 +107,8 @@ LoginManagerPrompter.prototype = {
* promptToSavePassword
*
*/
promptToSavePassword: function(aLogin) {
this._showSaveLoginNotification(aLogin);
promptToSavePassword: function(aLogin, dismissed) {
this._showSaveLoginNotification(aLogin, dismissed);
Services.telemetry.getHistogramById("PWMGR_PROMPT_REMEMBER_ACTION").add(PROMPT_DISPLAYED);
Services.obs.notifyObservers(aLogin, "passwordmgr-prompt-save");
},
@ -125,8 +125,10 @@ LoginManagerPrompter.prototype = {
* Username string used in creating a doorhanger action
* @param aPassword
* Password string used in creating a doorhanger action
* @param dismissed
* A boolean indicating if a prompt is dismissed by default.
*/
_showLoginNotification: function(aBody, aButtons, aUsername, aPassword) {
_showLoginNotification: function(aBody, aButtons, aUsername, aPassword, dismissed = false) {
let actionText = {
text: aUsername,
type: "EDIT",
@ -145,6 +147,7 @@ LoginManagerPrompter.prototype = {
persistWhileVisible: true,
timeout: Date.now() + 10000,
actionText: actionText,
dismissed,
};
let win = (this._browser && this._browser.contentWindow) || this._window;
@ -159,7 +162,7 @@ LoginManagerPrompter.prototype = {
* their login, and only save a login which they know worked.
*
*/
_showSaveLoginNotification: function(aLogin) {
_showSaveLoginNotification: function(aLogin, dismissed) {
let brandShortName = this._strBundle.brand.GetStringFromName("brandShortName");
let notificationText = this._getLocalizedString("saveLogin", [brandShortName]);
@ -191,7 +194,7 @@ LoginManagerPrompter.prototype = {
},
];
this._showLoginNotification(notificationText, buttons, aLogin.username, aLogin.password);
this._showLoginNotification(notificationText, buttons, aLogin.username, aLogin.password, dismissed);
},
/*
@ -202,7 +205,7 @@ LoginManagerPrompter.prototype = {
* fields.
*
*/
promptToChangePassword: function(aOldLogin, aNewLogin) {
promptToChangePassword: function(aOldLogin, aNewLogin, dismissed) {
this._showChangeLoginNotification(aOldLogin, aNewLogin.password);
Services.telemetry.getHistogramById("PWMGR_PROMPT_UPDATE_ACTION").add(PROMPT_DISPLAYED);
let oldGUID = aOldLogin.QueryInterface(Ci.nsILoginMetaInfo).guid;
@ -215,7 +218,7 @@ LoginManagerPrompter.prototype = {
* Shows the Change Password notification doorhanger.
*
*/
_showChangeLoginNotification: function(aOldLogin, aNewPassword) {
_showChangeLoginNotification: function(aOldLogin, aNewPassword, dismissed) {
var notificationText;
if (aOldLogin.username) {
let displayUser = this._sanitizeUsername(aOldLogin.username);
@ -247,7 +250,7 @@ LoginManagerPrompter.prototype = {
},
];
this._showLoginNotification(notificationText, buttons, aOldLogin.username, aNewPassword);
this._showLoginNotification(notificationText, buttons, aOldLogin.username, aNewPassword, dismissed);
},
/*

Просмотреть файл

@ -21,6 +21,7 @@ const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm")
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
const {PrivateBrowsingUtils} = ChromeUtils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
const {PromiseUtils} = ChromeUtils.import("resource://gre/modules/PromiseUtils.jsm");
const {CreditCard} = ChromeUtils.import("resource://gre/modules/CreditCard.jsm");
ChromeUtils.defineModuleGetter(this, "DeferredTask", "resource://gre/modules/DeferredTask.jsm");
ChromeUtils.defineModuleGetter(this, "FormLikeFactory",
@ -1139,6 +1140,17 @@ var LoginManagerContent = {
openerTopWindowID = win.opener.top.windowUtils.outerWindowID;
}
// Dismiss prompt if the username field is a credit card number AND
// if the password field is a three digit number. Also dismiss prompt if
// the password is a credit card number and the password field has attribute
// autocomplete="cc-number".
let dismissedPrompt = false;
let newPasswordFieldValue = newPasswordField.value;
if ((CreditCard.isValidNumber(usernameValue) && newPasswordFieldValue.trim().match(/^[0-9]{3}$/)) ||
(CreditCard.isValidNumber(newPasswordFieldValue) && newPasswordField.getAutocompleteInfo().fieldName == "cc-number")) {
dismissedPrompt = true;
}
let autoFilledLogin = this.stateForDocument(doc).fillsByRootElement.get(form.rootElement);
messageManager.sendAsyncMessage("PasswordManager:onFormSubmit",
{ hostname,
@ -1148,6 +1160,7 @@ var LoginManagerContent = {
newPasswordField: mockPassword,
oldPasswordField: mockOldPassword,
openerTopWindowID,
dismissedPrompt,
});
},

Просмотреть файл

@ -100,6 +100,7 @@ var LoginManagerParent = {
newPasswordField: data.newPasswordField,
oldPasswordField: data.oldPasswordField,
openerTopWindowID: data.openerTopWindowID,
dismissedPrompt: data.dismissedPrompt,
target: msg.target});
break;
}
@ -303,7 +304,7 @@ var LoginManagerParent = {
onFormSubmit({hostname, formSubmitURL, autoFilledLoginGuid,
usernameField, newPasswordField,
oldPasswordField, openerTopWindowID,
target}) {
dismissedPrompt, target}) {
function getPrompter() {
var prompterSvc = Cc["@mozilla.org/login-manager/prompter;1"].
createInstance(Ci.nsILoginManagerPrompter);
@ -391,7 +392,7 @@ var LoginManagerParent = {
formLogin.username = oldLogin.username;
formLogin.usernameField = oldLogin.usernameField;
prompter.promptToChangePassword(oldLogin, formLogin);
prompter.promptToChangePassword(oldLogin, formLogin, dismissedPrompt);
} else {
// Note: It's possible that that we already have the correct u+p saved
// but since we don't have the username, we don't know if the user is
@ -449,11 +450,11 @@ var LoginManagerParent = {
if (existingLogin.password != formLogin.password) {
log("...passwords differ, prompting to change.");
prompter = getPrompter();
prompter.promptToChangePassword(existingLogin, formLogin);
prompter.promptToChangePassword(existingLogin, formLogin, dismissedPrompt);
} else if (!existingLogin.username && formLogin.username) {
log("...empty username update, prompting to change.");
prompter = getPrompter();
prompter.promptToChangePassword(existingLogin, formLogin);
prompter.promptToChangePassword(existingLogin, formLogin, dismissedPrompt);
} else {
recordLoginUse(existingLogin);
}
@ -461,10 +462,9 @@ var LoginManagerParent = {
return;
}
// Prompt user to save login (via dialog or notification bar)
prompter = getPrompter();
prompter.promptToSavePassword(formLogin);
prompter.promptToSavePassword(formLogin, dismissedPrompt);
},
/**

Просмотреть файл

@ -812,12 +812,12 @@ LoginManagerPrompter.prototype = {
this._openerBrowser = aOpenerBrowser;
},
promptToSavePassword(aLogin) {
promptToSavePassword(aLogin, dismissed) {
this.log("promptToSavePassword");
var notifyObj = this._getPopupNote();
if (notifyObj) {
this._showLoginCaptureDoorhanger(aLogin, "password-save", {
dismissed: this._inPrivateBrowsing,
dismissed: this._inPrivateBrowsing || dismissed,
});
Services.obs.notifyObservers(aLogin, "passwordmgr-prompt-save");
} else {
@ -1176,14 +1176,17 @@ LoginManagerPrompter.prototype = {
* The old login we may want to update.
* @param {nsILoginInfo} aNewLogin
* The new login from the page form.
* @param dismissed
* A boolean indicating if the prompt should be automatically
* dismissed on being shown.
*/
promptToChangePassword(aOldLogin, aNewLogin) {
promptToChangePassword(aOldLogin, aNewLogin, dismissed) {
this.log("promptToChangePassword");
let notifyObj = this._getPopupNote();
if (notifyObj) {
this._showChangeLoginNotification(notifyObj, aOldLogin,
aNewLogin);
aNewLogin, dismissed);
} else {
this._showChangeLoginDialog(aOldLogin, aNewLogin);
}
@ -1200,13 +1203,16 @@ LoginManagerPrompter.prototype = {
*
* @param aNewLogin
* The login object with the changes we want to make.
* @param dismissed
* A boolean indicating if the prompt should be automatically
* dismissed on being shown.
*/
_showChangeLoginNotification(aNotifyObj, aOldLogin, aNewLogin) {
_showChangeLoginNotification(aNotifyObj, aOldLogin, aNewLogin, dismissed = false) {
aOldLogin.hostname = aNewLogin.hostname;
aOldLogin.formSubmitURL = aNewLogin.formSubmitURL;
aOldLogin.password = aNewLogin.password;
aOldLogin.username = aNewLogin.username;
this._showLoginCaptureDoorhanger(aOldLogin, "password-change");
this._showLoginCaptureDoorhanger(aOldLogin, "password-change", {dismissed});
let oldGUID = aOldLogin.QueryInterface(Ci.nsILoginMetaInfo).guid;
Services.obs.notifyObservers(aNewLogin, "passwordmgr-prompt-change", oldGUID);

Просмотреть файл

@ -49,8 +49,11 @@ interface nsILoginManagerPrompter : nsISupports {
*
* @param aLogin
* The login to be saved.
* @param dismissed
* A boolean value indicating whether the save logins doorhanger should
* be dismissed automatically when shown.
*/
void promptToSavePassword(in nsILoginInfo aLogin);
void promptToSavePassword(in nsILoginInfo aLogin, in boolean dismissed);
/**
* Ask the user if they want to change a login's password or username.
@ -60,9 +63,13 @@ interface nsILoginManagerPrompter : nsISupports {
* The existing login (with the old password).
* @param aNewLogin
* The new login.
* @param dismissed
* A boolean value indicating whether the save logins doorhanger should
* be dismissed automatically when shown.
*/
void promptToChangePassword(in nsILoginInfo aOldLogin,
in nsILoginInfo aNewLogin);
in nsILoginInfo aNewLogin,
in boolean dismissed);
/**
* Ask the user if they want to change the password for one of

Просмотреть файл

@ -49,6 +49,7 @@ skip-if = verify
[browser_username_select_dialog.js]
support-files =
subtst_notifications_change_p.html
[browser_doorhanger_dismissed_for_ccnumber.js]
[browser_DOMFormHasPassword.js]
[browser_DOMInputPasswordAdded.js]
skip-if = (os == "linux") || (os == "mac") # Bug 1337606

Просмотреть файл

@ -0,0 +1,115 @@
"use strict";
const TEST_HOSTNAME = "https://example.com";
const BASIC_FORM_PAGE_PATH = DIRECTORY_PATH + "form_basic.html";
function getSubmitMessage() {
info("getSubmitMessage");
return new Promise((resolve, reject) => {
Services.mm.addMessageListener("PasswordManager:onFormSubmit", function onFormSubmit() {
Services.mm.removeMessageListener("PasswordManager:onFormSubmit", onFormSubmit);
resolve();
});
});
}
add_task(async function test_doorhanger_dismissal_un() {
let url = TEST_HOSTNAME + BASIC_FORM_PAGE_PATH;
await BrowserTestUtils.withNewTab({
gBrowser,
url,
}, async function test_un_value_as_ccnumber(browser) {
// If the username field has a credit card number and if
// the password field is a three digit numberic value,
// we automatically dismiss the save logins prompt on submission.
let processedPromise = getSubmitMessage();
ContentTask.spawn(browser, null, async () => {
content.document.getElementById("form-basic-username").setUserInput("4111111111111111");
content.document.getElementById("form-basic-password").setUserInput("123");
content.document.getElementById("form-basic-submit").click();
});
await processedPromise;
let notif = getCaptureDoorhanger("password-save");
ok(notif, "got notification popup");
ok(notif.dismissed, "notification popup was automatically dismissed");
});
});
add_task(async function test_doorhanger_dismissal_pw() {
let url = TEST_HOSTNAME + BASIC_FORM_PAGE_PATH;
await BrowserTestUtils.withNewTab({
gBrowser,
url,
}, async function test_pw_value_as_ccnumber(browser) {
// If the password field has a credit card number and if
// the password field is also tagged autocomplete="cc-number",
// we automatically dismiss the save logins prompt on submission.
let processedPromise = getSubmitMessage();
ContentTask.spawn(browser, null, async () => {
content.document.getElementById("form-basic-username").setUserInput("aaa");
content.document.getElementById("form-basic-password").setUserInput("4111111111111111");
content.document.getElementById("form-basic-password").setAttribute("autocomplete", "cc-number");
content.document.getElementById("form-basic-submit").click();
});
await processedPromise;
let notif = getCaptureDoorhanger("password-save");
ok(notif, "got notification popup");
ok(notif.dismissed, "notification popup was automatically dismissed");
});
});
add_task(async function test_doorhanger_shown_on_un_with_invalid_ccnumber() {
let url = TEST_HOSTNAME + BASIC_FORM_PAGE_PATH;
await BrowserTestUtils.withNewTab({
gBrowser,
url,
}, async function test_un_with_invalid_cc_number(browser) {
// If the username field has a CC number that is invalid,
// we show the doorhanger to save logins like we usually do.
let processedPromise = getSubmitMessage();
ContentTask.spawn(browser, null, async () => {
content.document.getElementById("form-basic-username").value = "1234123412341234";
content.document.getElementById("form-basic-password").value = "411";
content.document.getElementById("form-basic-submit").click();
});
await processedPromise;
let notif = getCaptureDoorhanger("password-save");
ok(notif, "got notification popup");
ok(!notif.dismissed, "notification popup was not automatically dismissed");
});
});
add_task(async function test_doorhanger_dismissal_on_change() {
let url = TEST_HOSTNAME + BASIC_FORM_PAGE_PATH;
await BrowserTestUtils.withNewTab({
gBrowser,
url,
}, async function test_change_in_pw(browser) {
let nsLoginInfo = new Components.Constructor("@mozilla.org/login-manager/loginInfo;1",
Ci.nsILoginInfo, "init");
let login = new nsLoginInfo("https://example.org", "https://example.org", null,
"4111111111111111", "111", "form-basic-username", "form-basic-password");
Services.logins.addLogin(login);
let processedPromise = getSubmitMessage();
ContentTask.spawn(browser, null, async () => {
content.document.getElementById("form-basic-password").setUserInput("111");
content.document.getElementById("form-basic-submit").click();
});
await processedPromise;
let notif = getCaptureDoorhanger("password-save");
ok(notif, "got notification popup");
ok(notif.dismissed, "notification popup was automatically dismissed");
});
});