зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1193341 - Detect presence of password fields in any subframe, flagging those on insecure connections. r=MattN
--HG-- extra : commitid : ADlptzF6ATB extra : rebase_source : dc352aa8cd85aec40d2fefbf0536d57c25ce316e
This commit is contained in:
Родитель
5a213c7dfc
Коммит
2654050231
|
@ -13,6 +13,8 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "devtools",
|
||||
"resource://gre/modules/devtools/shared/Loader.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "LoginManagerContent",
|
||||
"resource://gre/modules/LoginManagerContent.jsm");
|
||||
|
||||
Object.defineProperty(this, "WebConsoleUtils", {
|
||||
get: function() {
|
||||
|
@ -47,50 +49,6 @@ this.InsecurePasswordUtils = {
|
|||
Services.console.logMessage(consoleMsg);
|
||||
},
|
||||
|
||||
/*
|
||||
* Checks whether the passed uri is secure
|
||||
* Check Protocol Flags to determine if scheme is secure:
|
||||
* URI_DOES_NOT_RETURN_DATA - e.g.
|
||||
* "mailto"
|
||||
* URI_IS_LOCAL_RESOURCE - e.g.
|
||||
* "data",
|
||||
* "resource",
|
||||
* "moz-icon"
|
||||
* URI_INHERITS_SECURITY_CONTEXT - e.g.
|
||||
* "javascript"
|
||||
* URI_SAFE_TO_LOAD_IN_SECURE_CONTEXT - e.g.
|
||||
* "https",
|
||||
* "moz-safe-about"
|
||||
*
|
||||
* The use of this logic comes directly from nsMixedContentBlocker.cpp
|
||||
* At the time it was decided to include these protocols since a secure
|
||||
* uri for mixed content blocker means that the resource can't be
|
||||
* easily tampered with because 1) it is sent over an encrypted channel or
|
||||
* 2) it is a local resource that never hits the network
|
||||
* or 3) it is a request sent without any response that could alter
|
||||
* the behavior of the page. It was decided to include the same logic
|
||||
* here both to be consistent with MCB and to make sure we cover all
|
||||
* "safe" protocols. Eventually, the code here and the code in MCB
|
||||
* will be moved to a common location that will be referenced from
|
||||
* both places. Look at
|
||||
* https://bugzilla.mozilla.org/show_bug.cgi?id=899099 for more info.
|
||||
*/
|
||||
_checkIfURIisSecure : function(uri) {
|
||||
let isSafe = false;
|
||||
let netutil = Cc["@mozilla.org/network/util;1"].getService(Ci.nsINetUtil);
|
||||
let ph = Ci.nsIProtocolHandler;
|
||||
|
||||
if (netutil.URIChainHasFlags(uri, ph.URI_IS_LOCAL_RESOURCE) ||
|
||||
netutil.URIChainHasFlags(uri, ph.URI_DOES_NOT_RETURN_DATA) ||
|
||||
netutil.URIChainHasFlags(uri, ph.URI_INHERITS_SECURITY_CONTEXT) ||
|
||||
netutil.URIChainHasFlags(uri, ph.URI_SAFE_TO_LOAD_IN_SECURE_CONTEXT)) {
|
||||
|
||||
isSafe = true;
|
||||
}
|
||||
|
||||
return isSafe;
|
||||
},
|
||||
|
||||
/*
|
||||
* Checks whether the passed nested document is insecure
|
||||
* or is inside an insecure parent document.
|
||||
|
@ -109,7 +67,7 @@ this.InsecurePasswordUtils = {
|
|||
// We are at the top, nothing to check here
|
||||
return false;
|
||||
}
|
||||
if (!this._checkIfURIisSecure(uri)) {
|
||||
if (!LoginManagerContent.checkIfURIisSecure(uri)) {
|
||||
// We are insecure
|
||||
return true;
|
||||
}
|
||||
|
@ -127,7 +85,7 @@ this.InsecurePasswordUtils = {
|
|||
checkForInsecurePasswords : function (aForm) {
|
||||
var domDoc = aForm.ownerDocument;
|
||||
let pageURI = domDoc.defaultView.top.document.documentURIObject;
|
||||
let isSafePage = this._checkIfURIisSecure(pageURI);
|
||||
let isSafePage = LoginManagerContent.checkIfURIisSecure(pageURI);
|
||||
|
||||
if (!isSafePage) {
|
||||
this._sendWebConsoleMessage("InsecurePasswordsPresentOnPage", domDoc);
|
||||
|
|
|
@ -414,6 +414,18 @@ var LoginManagerContent = {
|
|||
return null;
|
||||
};
|
||||
|
||||
// Returns true if this window or any subframes have insecure login forms.
|
||||
let hasInsecureLoginForms = (thisWindow, parentIsInsecure) => {
|
||||
let doc = thisWindow.document;
|
||||
let isInsecure =
|
||||
parentIsInsecure ||
|
||||
!this.checkIfURIisSecure(doc.documentURIObject);
|
||||
let hasLoginForm = !!this.stateForDocument(doc).loginForm;
|
||||
return (hasLoginForm && isInsecure) ||
|
||||
Array.some(thisWindow.frames,
|
||||
frame => hasInsecureLoginForms(frame, isInsecure));
|
||||
};
|
||||
|
||||
// Store the actual form to use on the state for the top-level document.
|
||||
let topState = this.stateForDocument(topWindow.document);
|
||||
topState.loginFormForFill = getFirstLoginForm(topWindow);
|
||||
|
@ -423,6 +435,7 @@ var LoginManagerContent = {
|
|||
messageManager.sendAsyncMessage("RemoteLogins:updateLoginFormPresence", {
|
||||
loginFormOrigin,
|
||||
loginFormPresent: !!topState.loginFormForFill,
|
||||
hasInsecureLoginForms: hasInsecureLoginForms(topWindow, false),
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -1089,6 +1102,49 @@ var LoginManagerContent = {
|
|||
};
|
||||
},
|
||||
|
||||
/*
|
||||
* Checks whether the passed uri is secure
|
||||
* Check Protocol Flags to determine if scheme is secure:
|
||||
* URI_DOES_NOT_RETURN_DATA - e.g.
|
||||
* "mailto"
|
||||
* URI_IS_LOCAL_RESOURCE - e.g.
|
||||
* "data",
|
||||
* "resource",
|
||||
* "moz-icon"
|
||||
* URI_INHERITS_SECURITY_CONTEXT - e.g.
|
||||
* "javascript"
|
||||
* URI_SAFE_TO_LOAD_IN_SECURE_CONTEXT - e.g.
|
||||
* "https",
|
||||
* "moz-safe-about"
|
||||
*
|
||||
* The use of this logic comes directly from nsMixedContentBlocker.cpp
|
||||
* At the time it was decided to include these protocols since a secure
|
||||
* uri for mixed content blocker means that the resource can't be
|
||||
* easily tampered with because 1) it is sent over an encrypted channel or
|
||||
* 2) it is a local resource that never hits the network
|
||||
* or 3) it is a request sent without any response that could alter
|
||||
* the behavior of the page. It was decided to include the same logic
|
||||
* here both to be consistent with MCB and to make sure we cover all
|
||||
* "safe" protocols. Eventually, the code here and the code in MCB
|
||||
* will be moved to a common location that will be referenced from
|
||||
* both places. Look at
|
||||
* https://bugzilla.mozilla.org/show_bug.cgi?id=899099 for more info.
|
||||
*/
|
||||
checkIfURIisSecure : function(uri) {
|
||||
let isSafe = false;
|
||||
let netutil = Cc["@mozilla.org/network/util;1"].getService(Ci.nsINetUtil);
|
||||
let ph = Ci.nsIProtocolHandler;
|
||||
|
||||
if (netutil.URIChainHasFlags(uri, ph.URI_IS_LOCAL_RESOURCE) ||
|
||||
netutil.URIChainHasFlags(uri, ph.URI_DOES_NOT_RETURN_DATA) ||
|
||||
netutil.URIChainHasFlags(uri, ph.URI_INHERITS_SECURITY_CONTEXT) ||
|
||||
netutil.URIChainHasFlags(uri, ph.URI_SAFE_TO_LOAD_IN_SECURE_CONTEXT)) {
|
||||
|
||||
isSafe = true;
|
||||
}
|
||||
|
||||
return isSafe;
|
||||
},
|
||||
};
|
||||
|
||||
var LoginUtils = {
|
||||
|
|
|
@ -569,12 +569,23 @@ var LoginManagerParent = {
|
|||
return loginFormState;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns true if the page currently loaded in the given browser element has
|
||||
* insecure login forms. This state may be updated asynchronously, in which
|
||||
* case a custom event named InsecureLoginFormsStateChange will be dispatched
|
||||
* on the browser element.
|
||||
*/
|
||||
hasInsecureLoginForms(browser) {
|
||||
return !!this.stateForBrowser(browser).hasInsecureLoginForms;
|
||||
},
|
||||
|
||||
/**
|
||||
* Called to indicate whether a login form on the currently loaded page is
|
||||
* present or not. This is one of the factors used to control the visibility
|
||||
* of the password fill doorhanger.
|
||||
*/
|
||||
updateLoginFormPresence(browser, { loginFormOrigin, loginFormPresent }) {
|
||||
updateLoginFormPresence(browser, { loginFormOrigin, loginFormPresent,
|
||||
hasInsecureLoginForms }) {
|
||||
const ANCHOR_DELAY_MS = 200;
|
||||
|
||||
let state = this.stateForBrowser(browser);
|
||||
|
@ -583,8 +594,13 @@ var LoginManagerParent = {
|
|||
// processed in order, this will always be the latest version to use.
|
||||
state.loginFormOrigin = loginFormOrigin;
|
||||
state.loginFormPresent = loginFormPresent;
|
||||
state.hasInsecureLoginForms = hasInsecureLoginForms;
|
||||
|
||||
// Apply the data to the currently displayed icon later.
|
||||
// Report the insecure login form state immediately.
|
||||
browser.dispatchEvent(new browser.ownerDocument.defaultView
|
||||
.CustomEvent("InsecureLoginFormsStateChange"));
|
||||
|
||||
// Apply the data to the currently displayed login fill icon later.
|
||||
if (!state.anchorDeferredTask) {
|
||||
state.anchorDeferredTask = new DeferredTask(
|
||||
() => this.updateLoginAnchor(browser),
|
||||
|
|
|
@ -2,11 +2,14 @@
|
|||
support-files =
|
||||
authenticate.sjs
|
||||
form_basic.html
|
||||
insecure_test.html
|
||||
insecure_test_subframe.html
|
||||
multiple_forms.html
|
||||
|
||||
[browser_DOMFormHasPassword.js]
|
||||
[browser_DOMInputPasswordAdded.js]
|
||||
[browser_filldoorhanger.js]
|
||||
[browser_hasInsecureLoginForms.js]
|
||||
[browser_notifications.js]
|
||||
skip-if = true # Intermittent failures: Bug 1182296, bug 1148771
|
||||
[browser_passwordmgr_editing.js]
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
Cu.import("resource://gre/modules/LoginManagerParent.jsm", this);
|
||||
|
||||
const testUrlPath =
|
||||
"://example.com/browser/toolkit/components/passwordmgr/test/browser/";
|
||||
|
||||
/**
|
||||
* Waits for the given number of occurrences of InsecureLoginFormsStateChange
|
||||
* on the given browser element.
|
||||
*/
|
||||
function waitForInsecureLoginFormsStateChange(browser, count) {
|
||||
return BrowserTestUtils.waitForEvent(browser, "InsecureLoginFormsStateChange",
|
||||
false, () => --count == 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that hasInsecureLoginForms is true for a simple HTTP page and false
|
||||
* for a simple HTTPS page.
|
||||
*/
|
||||
add_task(function* test_simple() {
|
||||
for (let scheme of ["http", "https"]) {
|
||||
let tab = gBrowser.addTab(scheme + testUrlPath + "form_basic.html");
|
||||
let browser = tab.linkedBrowser;
|
||||
yield Promise.all([
|
||||
BrowserTestUtils.switchTab(gBrowser, tab),
|
||||
BrowserTestUtils.browserLoaded(browser),
|
||||
// One event is triggered by pageshow and one by DOMFormHasPassword.
|
||||
waitForInsecureLoginFormsStateChange(browser, 2),
|
||||
]);
|
||||
|
||||
Assert.equal(LoginManagerParent.hasInsecureLoginForms(browser),
|
||||
scheme == "http");
|
||||
|
||||
gBrowser.removeTab(tab);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Checks that hasInsecureLoginForms is true if a password field is present in
|
||||
* an HTTP page loaded as a subframe of a top-level HTTPS page, when mixed
|
||||
* active content blocking is disabled.
|
||||
*
|
||||
* When the subframe is navigated to an HTTPS page, hasInsecureLoginForms should
|
||||
* be set to false.
|
||||
*
|
||||
* Moving back in history should set hasInsecureLoginForms to true again.
|
||||
*/
|
||||
add_task(function* test_subframe_navigation() {
|
||||
yield new Promise(resolve => SpecialPowers.pushPrefEnv({
|
||||
"set": [["security.mixed_content.block_active_content", false]],
|
||||
}, resolve));
|
||||
|
||||
// Load the page with the subframe in a new tab.
|
||||
let tab = gBrowser.addTab("https" + testUrlPath + "insecure_test.html");
|
||||
let browser = tab.linkedBrowser;
|
||||
yield Promise.all([
|
||||
BrowserTestUtils.switchTab(gBrowser, tab),
|
||||
BrowserTestUtils.browserLoaded(browser),
|
||||
// Two events are triggered by pageshow and one by DOMFormHasPassword.
|
||||
waitForInsecureLoginFormsStateChange(browser, 3),
|
||||
]);
|
||||
|
||||
Assert.ok(LoginManagerParent.hasInsecureLoginForms(browser));
|
||||
|
||||
// Navigate the subframe to a secure page.
|
||||
let promiseSubframeReady = Promise.all([
|
||||
BrowserTestUtils.browserLoaded(browser, true),
|
||||
// One event is triggered by pageshow and one by DOMFormHasPassword.
|
||||
waitForInsecureLoginFormsStateChange(browser, 2),
|
||||
]);
|
||||
yield ContentTask.spawn(browser, null, function* () {
|
||||
content.document.getElementById("test-iframe")
|
||||
.contentDocument.getElementById("test-link").click();
|
||||
});
|
||||
yield promiseSubframeReady;
|
||||
|
||||
Assert.ok(!LoginManagerParent.hasInsecureLoginForms(browser));
|
||||
|
||||
// Navigate back to the insecure page. We only have to wait for the
|
||||
// InsecureLoginFormsStateChange event that is triggered by pageshow.
|
||||
let promise = waitForInsecureLoginFormsStateChange(browser, 1);
|
||||
yield ContentTask.spawn(browser, null, function* () {
|
||||
content.document.getElementById("test-iframe")
|
||||
.contentWindow.history.back();
|
||||
});
|
||||
yield promise;
|
||||
|
||||
Assert.ok(LoginManagerParent.hasInsecureLoginForms(browser));
|
||||
|
||||
gBrowser.removeTab(tab);
|
||||
});
|
|
@ -0,0 +1,9 @@
|
|||
<!DOCTYPE html><html><head><meta charset="utf-8"></head><body>
|
||||
<!-- Any copyright is dedicated to the Public Domain.
|
||||
- http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||
|
||||
<!-- This frame is initially loaded over HTTP. -->
|
||||
<iframe id="test-iframe"
|
||||
src="http://example.org/browser/toolkit/components/passwordmgr/test/browser/insecure_test_subframe.html"/>
|
||||
|
||||
</body></html>
|
|
@ -0,0 +1,13 @@
|
|||
<!DOCTYPE html><html><head><meta charset="utf-8"></head><body>
|
||||
<!-- Any copyright is dedicated to the Public Domain.
|
||||
- http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||
|
||||
<form>
|
||||
<input name="password" type="password">
|
||||
</form>
|
||||
|
||||
<!-- Link to reload this page over HTTPS. -->
|
||||
<a id="test-link"
|
||||
href="https://example.org/browser/toolkit/components/passwordmgr/test/browser/insecure_test_subframe.html">HTTPS</a>
|
||||
|
||||
</body></html>
|
Загрузка…
Ссылка в новой задаче