зеркало из 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",
|
XPCOMUtils.defineLazyModuleGetter(this, "devtools",
|
||||||
"resource://gre/modules/devtools/shared/Loader.jsm");
|
"resource://gre/modules/devtools/shared/Loader.jsm");
|
||||||
|
XPCOMUtils.defineLazyModuleGetter(this, "LoginManagerContent",
|
||||||
|
"resource://gre/modules/LoginManagerContent.jsm");
|
||||||
|
|
||||||
Object.defineProperty(this, "WebConsoleUtils", {
|
Object.defineProperty(this, "WebConsoleUtils", {
|
||||||
get: function() {
|
get: function() {
|
||||||
|
@ -47,50 +49,6 @@ this.InsecurePasswordUtils = {
|
||||||
Services.console.logMessage(consoleMsg);
|
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
|
* Checks whether the passed nested document is insecure
|
||||||
* or is inside an insecure parent document.
|
* or is inside an insecure parent document.
|
||||||
|
@ -109,7 +67,7 @@ this.InsecurePasswordUtils = {
|
||||||
// We are at the top, nothing to check here
|
// We are at the top, nothing to check here
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!this._checkIfURIisSecure(uri)) {
|
if (!LoginManagerContent.checkIfURIisSecure(uri)) {
|
||||||
// We are insecure
|
// We are insecure
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -127,7 +85,7 @@ this.InsecurePasswordUtils = {
|
||||||
checkForInsecurePasswords : function (aForm) {
|
checkForInsecurePasswords : function (aForm) {
|
||||||
var domDoc = aForm.ownerDocument;
|
var domDoc = aForm.ownerDocument;
|
||||||
let pageURI = domDoc.defaultView.top.document.documentURIObject;
|
let pageURI = domDoc.defaultView.top.document.documentURIObject;
|
||||||
let isSafePage = this._checkIfURIisSecure(pageURI);
|
let isSafePage = LoginManagerContent.checkIfURIisSecure(pageURI);
|
||||||
|
|
||||||
if (!isSafePage) {
|
if (!isSafePage) {
|
||||||
this._sendWebConsoleMessage("InsecurePasswordsPresentOnPage", domDoc);
|
this._sendWebConsoleMessage("InsecurePasswordsPresentOnPage", domDoc);
|
||||||
|
|
|
@ -414,6 +414,18 @@ var LoginManagerContent = {
|
||||||
return null;
|
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.
|
// Store the actual form to use on the state for the top-level document.
|
||||||
let topState = this.stateForDocument(topWindow.document);
|
let topState = this.stateForDocument(topWindow.document);
|
||||||
topState.loginFormForFill = getFirstLoginForm(topWindow);
|
topState.loginFormForFill = getFirstLoginForm(topWindow);
|
||||||
|
@ -423,6 +435,7 @@ var LoginManagerContent = {
|
||||||
messageManager.sendAsyncMessage("RemoteLogins:updateLoginFormPresence", {
|
messageManager.sendAsyncMessage("RemoteLogins:updateLoginFormPresence", {
|
||||||
loginFormOrigin,
|
loginFormOrigin,
|
||||||
loginFormPresent: !!topState.loginFormForFill,
|
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 = {
|
var LoginUtils = {
|
||||||
|
|
|
@ -569,12 +569,23 @@ var LoginManagerParent = {
|
||||||
return loginFormState;
|
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
|
* 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
|
* present or not. This is one of the factors used to control the visibility
|
||||||
* of the password fill doorhanger.
|
* of the password fill doorhanger.
|
||||||
*/
|
*/
|
||||||
updateLoginFormPresence(browser, { loginFormOrigin, loginFormPresent }) {
|
updateLoginFormPresence(browser, { loginFormOrigin, loginFormPresent,
|
||||||
|
hasInsecureLoginForms }) {
|
||||||
const ANCHOR_DELAY_MS = 200;
|
const ANCHOR_DELAY_MS = 200;
|
||||||
|
|
||||||
let state = this.stateForBrowser(browser);
|
let state = this.stateForBrowser(browser);
|
||||||
|
@ -583,8 +594,13 @@ var LoginManagerParent = {
|
||||||
// processed in order, this will always be the latest version to use.
|
// processed in order, this will always be the latest version to use.
|
||||||
state.loginFormOrigin = loginFormOrigin;
|
state.loginFormOrigin = loginFormOrigin;
|
||||||
state.loginFormPresent = loginFormPresent;
|
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) {
|
if (!state.anchorDeferredTask) {
|
||||||
state.anchorDeferredTask = new DeferredTask(
|
state.anchorDeferredTask = new DeferredTask(
|
||||||
() => this.updateLoginAnchor(browser),
|
() => this.updateLoginAnchor(browser),
|
||||||
|
|
|
@ -2,11 +2,14 @@
|
||||||
support-files =
|
support-files =
|
||||||
authenticate.sjs
|
authenticate.sjs
|
||||||
form_basic.html
|
form_basic.html
|
||||||
|
insecure_test.html
|
||||||
|
insecure_test_subframe.html
|
||||||
multiple_forms.html
|
multiple_forms.html
|
||||||
|
|
||||||
[browser_DOMFormHasPassword.js]
|
[browser_DOMFormHasPassword.js]
|
||||||
[browser_DOMInputPasswordAdded.js]
|
[browser_DOMInputPasswordAdded.js]
|
||||||
[browser_filldoorhanger.js]
|
[browser_filldoorhanger.js]
|
||||||
|
[browser_hasInsecureLoginForms.js]
|
||||||
[browser_notifications.js]
|
[browser_notifications.js]
|
||||||
skip-if = true # Intermittent failures: Bug 1182296, bug 1148771
|
skip-if = true # Intermittent failures: Bug 1182296, bug 1148771
|
||||||
[browser_passwordmgr_editing.js]
|
[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>
|
Загрузка…
Ссылка в новой задаче