Bug 1259355 - Practice MozReview; r?KuoE0
MozReview-Commit-ID: 4XHXuezG7HY
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -17,16 +17,18 @@ Cu.import("resource://gre/modules/Inline
Cu.import("resource://gre/modules/Task.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
"resource://gre/modules/BrowserUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ContentLinkHandler",
"resource:///modules/ContentLinkHandler.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "LoginManagerContent",
"resource://gre/modules/LoginManagerContent.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "FormLikeFactory",
+ "resource://gre/modules/LoginManagerContent.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "InsecurePasswordUtils",
"resource://gre/modules/InsecurePasswordUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PluginContent",
"resource:///modules/PluginContent.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
"resource://gre/modules/PrivateBrowsingUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "FormSubmitObserver",
"resource:///modules/FormSubmitObserver.jsm");
@@ -52,20 +54,23 @@ addMessageListener("ContextMenu:DoCustom
PageMenuChild.executeMenu(message.data);
});
addMessageListener("RemoteLogins:fillForm", function(message) {
LoginManagerContent.receiveMessage(message, content);
});
addEventListener("DOMFormHasPassword", function(event) {
LoginManagerContent.onDOMFormHasPassword(event, content);
- InsecurePasswordUtils.checkForInsecurePasswords(event.target);
+ let formLike = FormLikeFactory.createFromForm(event.target);
+ InsecurePasswordUtils.checkForInsecurePasswords(formLike);
});
addEventListener("DOMInputPasswordAdded", function(event) {
LoginManagerContent.onDOMInputPasswordAdded(event, content);
+ let formLike = FormLikeFactory.createFromField(event.target);
+ InsecurePasswordUtils.checkForInsecurePasswords(formLike);
});
addEventListener("pageshow", function(event) {
LoginManagerContent.onPageShow(event, content);
});
addEventListener("DOMAutoComplete", function(event) {
LoginManagerContent.onUsernameInput(event);
});
addEventListener("blur", function(event) {
--- a/toolkit/components/passwordmgr/InsecurePasswordUtils.jsm
+++ b/toolkit/components/passwordmgr/InsecurePasswordUtils.jsm
@@ -17,16 +17,17 @@ XPCOMUtils.defineLazyModuleGetter(this,
XPCOMUtils.defineLazyGetter(this, "WebConsoleUtils", () => {
return this.devtools.require("devtools/shared/webconsole/utils").Utils;
});
XPCOMUtils.defineLazyGetter(this, "l10n", () => {
return new this.WebConsoleUtils.L10n(STRINGS_URI);
});
this.InsecurePasswordUtils = {
+ _formRootsWarned: new WeakMap(),
_sendWebConsoleMessage(messageTag, domDoc) {
let windowId = WebConsoleUtils.getInnerWindowId(domDoc.defaultView);
let category = "Insecure Password Field";
// All web console messages are warnings for now.
let flag = Ci.nsIScriptError.warningFlag;
let message = l10n.getStr(messageTag);
let consoleMsg = Cc["@mozilla.org/scripterror;1"].createInstance(Ci.nsIScriptError);
consoleMsg.initWithWindowID(message, "", 0, 0, 0, flag, category, windowId);
@@ -64,37 +65,47 @@ this.InsecurePasswordUtils = {
* Checks if there are insecure password fields present on the form's document
* i.e. passwords inside forms with http action, inside iframes with http src,
* or on insecure web pages. If insecure password fields are present,
* a log message is sent to the web console to warn developers.
*
* @param {FormLike} aForm A form-like object. @See {FormLikeFactory}
*/
checkForInsecurePasswords(aForm) {
+ if (this._formRootsWarned.has(aForm.rootElement) ||
+ this._formRootsWarned.get(aForm.rootElement)) {
+ return;
+ }
+
let domDoc = aForm.ownerDocument;
let topDocument = domDoc.defaultView.top.document;
let isSafePage = LoginManagerContent.isDocumentSecure(topDocument);
if (!isSafePage) {
this._sendWebConsoleMessage("InsecurePasswordsPresentOnPage", domDoc);
+ this._formRootsWarned.set(aForm.rootElement, true);
}
// Check if we are on an iframe with insecure src, or inside another
// insecure iframe or document.
if (this._checkForInsecureNestedDocuments(domDoc)) {
this._sendWebConsoleMessage("InsecurePasswordsPresentOnIframe", domDoc);
+ this._formRootsWarned.set(aForm.rootElement, true);
isSafePage = false;
}
let isFormSubmitHTTP = false, isFormSubmitHTTPS = false;
- if (aForm.action.match(/^http:\/\//)) {
- this._sendWebConsoleMessage("InsecureFormActionPasswordsPresent", domDoc);
- isFormSubmitHTTP = true;
- } else if (aForm.action.match(/^https:\/\//)) {
- isFormSubmitHTTPS = true;
+ if (aForm.rootElement instanceof Ci.nsIDOMHTMLFormElement) {
+ if (aForm.action.match(/^http:\/\//)) {
+ this._sendWebConsoleMessage("InsecureFormActionPasswordsPresent", domDoc);
+ this._formRootsWarned.set(aForm.rootElement, true);
+ isFormSubmitHTTP = true;
+ } else if (aForm.action.match(/^https:\/\//)) {
+ isFormSubmitHTTPS = true;
+ }
}
// The safety of a password field determined by the form action and the page protocol
let passwordSafety;
if (isSafePage) {
if (isFormSubmitHTTPS) {
passwordSafety = 0;
} else if (isFormSubmitHTTP) {
--- a/toolkit/components/passwordmgr/LoginManagerContent.jsm
+++ b/toolkit/components/passwordmgr/LoginManagerContent.jsm
@@ -1,15 +1,16 @@
/* 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/. */
"use strict";
this.EXPORTED_SYMBOLS = [ "LoginManagerContent",
+ "FormLikeFactory",
"UserAutoCompleteResult" ];
const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
const PASSWORD_INPUT_ADDED_COALESCING_THRESHOLD_MS = 1;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
--- a/toolkit/components/passwordmgr/moz.build
+++ b/toolkit/components/passwordmgr/moz.build
@@ -10,16 +10,17 @@ if CONFIG['MOZ_BUILD_APP'] == 'browser':
MOCHITEST_MANIFESTS += ['test/mochitest.ini', 'test/mochitest/mochitest.ini']
MOCHITEST_CHROME_MANIFESTS += ['test/chrome/chrome.ini']
BROWSER_CHROME_MANIFESTS += ['test/browser/browser.ini']
XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
TESTING_JS_MODULES += [
# Make this file available from the "resource:" URI of the test environment.
'test/browser/form_basic.html',
+ 'test/browser/formless_basic.html',
'test/LoginTestUtils.jsm',
]
XPIDL_SOURCES += [
'nsILoginInfo.idl',
'nsILoginManager.idl',
'nsILoginManagerCrypto.idl',
'nsILoginManagerPrompter.idl',
--- a/toolkit/components/passwordmgr/test/browser/browser.ini
+++ b/toolkit/components/passwordmgr/test/browser/browser.ini
@@ -1,22 +1,24 @@
[DEFAULT]
support-files =
authenticate.sjs
form_basic.html
+ formless_basic.html
insecure_test.html
insecure_test_subframe.html
multiple_forms.html
streamConverter_content.sjs
[browser_DOMFormHasPassword.js]
[browser_DOMInputPasswordAdded.js]
[browser_filldoorhanger.js]
[browser_hasInsecureLoginForms.js]
[browser_hasInsecureLoginForms_streamConverter.js]
+[browser_insecurePasswordWarning.js]
[browser_notifications.js]
skip-if = true # Intermittent failures: Bug 1182296, bug 1148771
[browser_passwordmgr_editing.js]
skip-if = os == "linux"
[browser_context_menu.js]
skip-if = os == "linux"
[browser_passwordmgr_contextmenu.js]
[browser_passwordmgr_fields.js]
new file mode 100644
--- /dev/null
+++ b/toolkit/components/passwordmgr/test/browser/browser_insecurePasswordWarning.js
@@ -0,0 +1,54 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+const TEST_URL_PATH = "/browser/toolkit/components/passwordmgr/test/browser/";
+
+const WARNING_PATTERN = {
+ // "INSECURE_FORM_ACTION"
+ '[JavaScript Warning: "Password fields present in a form with an insecure (http://) form action. This is a security risk that allows user login credentials to be stolen."]': "INSECURE_FORM_ACTION",
+
+ // "INSECURE_PAGE"
+ '[JavaScript Warning: "Password fields present on an insecure (http://) page. This is a security risk that allows user login credentials to be stolen."]': "INSECURE_PAGE"
+};
+
+add_task(function* testInsecurePasswordWarning() {
+ for (let [origin, testPattern, expectWarnings] of [
+ ["http://127.0.0.1", "form_basic.html", ["INSECURE_FORM_ACTION"]],
+ ["http://127.0.0.1", "formless_basic.html", []],
+ ["http://example.com", "form_basic.html", ["INSECURE_FORM_ACTION","INSECURE_PAGE"]],
+ ["http://example.com", "formless_basic.html", ["INSECURE_PAGE"]],
+ ["https://example.com", "form_basic.html", []],
+ ["https://example.com", "formless_basic.html", []],
+ ]) {
+ let testUrlPath = origin + TEST_URL_PATH + testPattern;
+ let tab = gBrowser.addTab(testUrlPath);
+ let browser = tab.linkedBrowser;
+ function listener(msg) {
+ // Only handle the insecure password related warning messages.
+ if (WARNING_PATTERN[msg.message]) {
+ var index = expectWarnings.indexOf(WARNING_PATTERN[msg.message]);
+
+ // Warning message found.
+ ok(index !== -1, "Warning message hits!");
+ if (index !== -1) {
+ // Remove the shown message.
+ expectWarnings[index] = null;
+ }
+ }
+ }
+ Services.console.registerListener(listener);
+
+ yield Promise.all([
+ BrowserTestUtils.switchTab(gBrowser, tab),
+ BrowserTestUtils.browserLoaded(browser),
+ ]);
+
+ // Verify if all warnings are shown.
+ for (let i = 0; i < expectWarnings.length; i++) {
+ ok(expectWarnings[i] === null, "All warnings are shown.");
+ }
+ Services.console.unregisterListener(listener);
+ gBrowser.removeTab(tab);
+ }
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/components/passwordmgr/test/browser/formless_basic.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html><html><head><meta charset="utf-8"></head><body>
+
+<!-- Simplest form with username and password fields. -->
+ <input id="form-basic-username" name="username">
+ <input id="form-basic-password" name="password" type="password">
+ <input id="form-basic-submit" type="submit">
+
+ <button id="add">Add input[type=password]</button>
+
+ <script>
+ document.getElementById('add').addEventListener('click', function () {
+ var node = document.createElement("input");
+ node.setAttribute('type', 'password');
+ document.querySelector('body').appendChild(node);
+ });
+ </script>
+
+</body></html>