Bug 1527828 - Remove insecure password field detection code for the address bar. r=sfoster

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

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Matthew Noorenberghe 2019-10-14 20:03:19 +00:00
parent 2ebb010408
commit 87888ef235
12 changed files with 3 additions and 672 deletions

View File

@ -132,12 +132,8 @@ var gIdentityHandler = {
},
get _hasInsecureLoginForms() {
// checks if the page has been flagged for an insecure login. Also checks
// if the pref to degrade the UI is set to true
return (
LoginManagerParent.hasInsecureLoginForms(gBrowser.selectedBrowser) &&
Services.prefs.getBoolPref("security.insecure_password.ui.enabled")
);
// This function will be deleted in bug 1567827.
return false;
},
// smart getters

View File

@ -13,22 +13,13 @@ var { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
// BrowserChildGlobal
var global = this;
XPCOMUtils.defineLazyModuleGetters(this, {
ContentMetaHandler: "resource:///modules/ContentMetaHandler.jsm",
LoginFormFactory: "resource://gre/modules/LoginFormFactory.jsm",
LoginManagerContent: "resource://gre/modules/LoginManagerContent.jsm",
InsecurePasswordUtils: "resource://gre/modules/InsecurePasswordUtils.jsm",
});
XPCOMUtils.defineLazyGetter(this, "LoginManagerContent", () => {
let tmp = {};
ChromeUtils.import("resource://gre/modules/LoginManagerContent.jsm", tmp);
tmp.LoginManagerContent.setupEventListeners(global);
return tmp.LoginManagerContent;
});
// NOTE: Much of this logic is duplicated in BrowserCLH.js for Android.
addMessageListener("PasswordManager:fillForm", function(message) {
// intercept if ContextMenu.jsm had sent a plain object for remote targets

View File

@ -61,13 +61,6 @@ support-files =
skip-if = (fission && debug) || (os == "linux" && bits == 64) # Bug 1577395
[browser_identityPopup_custom_roots.js]
[browser_identityPopup_focus.js]
[browser_insecureLoginForms.js]
support-files =
insecure_opener.html
!/toolkit/components/passwordmgr/test/browser/form_basic.html
!/toolkit/components/passwordmgr/test/browser/insecure_test.html
!/toolkit/components/passwordmgr/test/browser/insecure_test_subframe.html
skip-if = fission
[browser_mcb_redirect.js]
tags = mcb
support-files =

View File

@ -1,306 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
// Load directly from the browser-chrome support files of login tests.
const TEST_URL_PATH = "/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 the insecure login forms logic for the identity block.
*/
add_task(async function test_simple() {
await SpecialPowers.pushPrefEnv({
set: [
["security.insecure_password.ui.enabled", true],
// By default, proxies don't apply to 127.0.0.1. We need them to for this test, though:
["network.proxy.allow_hijacking_localhost", true],
],
});
for (let [origin, expectWarning] of [
["http://example.com", true],
["http://127.0.0.1", false],
["https://example.com", false],
]) {
let testUrlPath = origin + TEST_URL_PATH;
let tab = BrowserTestUtils.addTab(
gBrowser,
testUrlPath + "form_basic.html"
);
let browser = tab.linkedBrowser;
await Promise.all([
BrowserTestUtils.switchTab(gBrowser, tab),
BrowserTestUtils.browserLoaded(browser),
// One event is triggered by pageshow and one by DOMFormHasPassword.
waitForInsecureLoginFormsStateChange(browser, 2),
]);
let { gIdentityHandler } = gBrowser.ownerGlobal;
let promisePanelOpen = BrowserTestUtils.waitForEvent(
gIdentityHandler._identityPopup,
"popupshown"
);
gIdentityHandler._identityBox.click();
await promisePanelOpen;
// Messages should be visible when the scheme is HTTP, and invisible when
// the scheme is HTTPS.
is(
Array.prototype.every.call(
document
.getElementById("identity-popup-mainView")
.querySelectorAll("[when-loginforms=insecure]"),
element => !BrowserTestUtils.is_hidden(element)
),
expectWarning,
"The relevant messages should be visible or hidden in the main view."
);
let promiseViewShown = BrowserTestUtils.waitForEvent(
gIdentityHandler._identityPopup,
"ViewShown"
);
document.getElementById("identity-popup-security-expander").click();
await promiseViewShown;
if (expectWarning) {
ok(
BrowserTestUtils.is_visible(document.getElementById("identity-icon")),
"Identity icon should be visible"
);
let identityIconImage = gBrowser.ownerGlobal
.getComputedStyle(document.getElementById("identity-icon"))
.getPropertyValue("list-style-image");
let securityViewBG = gBrowser.ownerGlobal
.getComputedStyle(
document
.getElementById("identity-popup-securityView")
.getElementsByClassName("identity-popup-security-connection")[0]
)
.getPropertyValue("background-image");
let securityContentBG = gBrowser.ownerGlobal
.getComputedStyle(
document
.getElementById("identity-popup-mainView")
.getElementsByClassName("identity-popup-security-connection")[0]
)
.getPropertyValue("background-image");
is(
identityIconImage,
'url("chrome://browser/skin/connection-mixed-active-loaded.svg")',
"Using expected icon image in the identity block"
);
is(
securityViewBG,
'url("chrome://browser/skin/controlcenter/mcb-disabled.svg")',
"Using expected icon image in the Control Center main view"
);
is(
securityContentBG,
'url("chrome://browser/skin/controlcenter/mcb-disabled.svg")',
"Using expected icon image in the Control Center subview"
);
ok(
!BrowserTestUtils.is_hidden(
document.getElementById(
"identity-popup-insecure-login-forms-learn-more"
)
),
"The 'Learn more' link should be visible."
);
}
// Messages should be visible when the scheme is HTTP, and invisible when
// the scheme is HTTPS.
is(
Array.prototype.every.call(
document
.getElementById("identity-popup-securityView")
.querySelectorAll("[when-loginforms=insecure]"),
element => !BrowserTestUtils.is_hidden(element)
),
expectWarning,
"The relevant messages should be visible or hidden in the security view."
);
if (gIdentityHandler._identityPopup.state != "closed") {
let hideEvent = BrowserTestUtils.waitForEvent(
gIdentityHandler._identityPopup,
"popuphidden"
);
info("hiding popup");
gIdentityHandler._identityPopup.hidePopup();
await hideEvent;
}
gBrowser.removeTab(tab);
}
});
/**
* Checks that the insecure login forms logic does not regress mixed content
* blocking messages when mixed active content is loaded.
*/
add_task(async function test_mixedcontent() {
await SpecialPowers.pushPrefEnv({
set: [["security.mixed_content.block_active_content", false]],
});
// Load the page with the subframe in a new tab.
let testUrlPath = "://example.com" + TEST_URL_PATH;
let tab = BrowserTestUtils.addTab(
gBrowser,
"https" + testUrlPath + "insecure_test.html"
);
let browser = tab.linkedBrowser;
await Promise.all([
BrowserTestUtils.switchTab(gBrowser, tab),
BrowserTestUtils.browserLoaded(browser),
// Two events are triggered by pageshow and one by DOMFormHasPassword.
waitForInsecureLoginFormsStateChange(browser, 3),
]);
await assertMixedContentBlockingState(browser, {
activeLoaded: true,
activeBlocked: false,
passiveLoaded: false,
});
gBrowser.removeTab(tab);
});
/**
* Checks that insecure window.opener does not trigger a warning.
*/
add_task(async function test_ignoring_window_opener() {
let path = getRootDirectory(gTestPath).replace(
"chrome://mochitests/content",
"http://example.com"
);
let url = path + "insecure_opener.html";
await BrowserTestUtils.withNewTab(url, async function(browser) {
// Clicking the link will spawn a new tab.
let stateChangePromise;
let tabOpenPromise = new Promise(resolve => {
gBrowser.tabContainer.addEventListener(
"TabOpen",
event => {
let tab = event.target;
let newTabBrowser = tab.linkedBrowser;
stateChangePromise = waitForInsecureLoginFormsStateChange(
newTabBrowser,
2
);
resolve(tab);
},
{ once: true }
);
});
await ContentTask.spawn(browser, {}, function() {
content.document.getElementById("link").click();
});
let tab = await tabOpenPromise;
await stateChangePromise;
// Open the identity popup.
let { gIdentityHandler } = gBrowser.ownerGlobal;
let promisePanelOpen = BrowserTestUtils.waitForEvent(
gIdentityHandler._identityPopup,
"popupshown"
);
gIdentityHandler._identityBox.click();
await promisePanelOpen;
ok(
Array.prototype.every.call(
document
.getElementById("identity-popup-mainView")
.querySelectorAll("[when-loginforms=insecure]"),
element => BrowserTestUtils.is_hidden(element)
),
"All messages should be hidden in the main view."
);
let promiseViewShown = BrowserTestUtils.waitForEvent(
gIdentityHandler._identityPopup,
"ViewShown"
);
document.getElementById("identity-popup-security-expander").click();
await promiseViewShown;
ok(
BrowserTestUtils.is_visible(document.getElementById("identity-icon")),
"Identity icon is visible"
);
// Assert that the identity indicators are still "secure".
let identityIconImage = gBrowser.ownerGlobal
.getComputedStyle(document.getElementById("identity-icon"))
.getPropertyValue("list-style-image");
let securityViewBG = gBrowser.ownerGlobal
.getComputedStyle(
document
.getElementById("identity-popup-securityView")
.getElementsByClassName("identity-popup-security-connection")[0]
)
.getPropertyValue("background-image");
let securityContentBG = gBrowser.ownerGlobal
.getComputedStyle(
document
.getElementById("identity-popup-mainView")
.getElementsByClassName("identity-popup-security-connection")[0]
)
.getPropertyValue("background-image");
is(
identityIconImage,
'url("chrome://browser/skin/connection-secure.svg")',
"Using expected icon image in the identity block"
);
is(
securityViewBG,
'url("chrome://browser/skin/connection-secure.svg")',
"Using expected icon image in the Control Center main view"
);
is(
securityContentBG,
'url("chrome://browser/skin/connection-secure.svg")',
"Using expected icon image in the Control Center subview"
);
ok(
Array.prototype.every.call(
document
.getElementById("identity-popup-securityView")
.querySelectorAll("[when-loginforms=insecure]"),
element => BrowserTestUtils.is_hidden(element)
),
"All messages should be hidden in the security view."
);
if (gIdentityHandler._identityPopup.state != "closed") {
info("hiding popup");
let hideEvent = BrowserTestUtils.waitForEvent(
gIdentityHandler._identityPopup,
"popuphidden"
);
gIdentityHandler._identityPopup.hidePopup();
await hideEvent;
}
BrowserTestUtils.removeTab(tab);
});
});

View File

@ -650,7 +650,6 @@ const listeners = {
"PasswordManager:onGeneratedPasswordFilledOrEdited": ["LoginManagerParent"],
"PasswordManager:autoCompleteLogins": ["LoginManagerParent"],
"PasswordManager:removeLogin": ["LoginManagerParent"],
"PasswordManager:insecureLoginFormPresent": ["LoginManagerParent"],
"PasswordManager:OpenPreferences": ["LoginManagerParent"],
// PLEASE KEEP THIS LIST IN SYNC WITH THE MOBILE LISTENERS IN BrowserCLH.js
"rtcpeer:CancelRequest": ["webrtcUI"],

View File

@ -83,7 +83,6 @@ BrowserCLH.prototype = {
"PasswordManager:onFormSubmit",
"PasswordManager:autoCompleteLogins",
"PasswordManager:removeLogin",
"PasswordManager:insecureLoginFormPresent",
// PLEASE KEEP THIS LIST IN SYNC WITH THE DESKTOP LIST IN
// BrowserGlue.jsm
],

View File

@ -616,12 +616,6 @@ this.LoginManagerContent = {
);
},
setupEventListeners(global) {
global.addEventListener("pageshow", event => {
this.onPageShow(event);
});
},
setupProgressListener(window) {
if (!LoginHelper.formlessCaptureEnabled) {
return;
@ -838,8 +832,6 @@ this.LoginManagerContent = {
*/
_fetchLoginsFromParentAndFillForm(form) {
let window = form.ownerDocument.defaultView;
this._detectInsecureFormLikes(window.top);
let messageManager = window.docShell.messageManager;
messageManager.sendAsyncMessage("LoginStats:LoginEncountered");
@ -852,11 +844,6 @@ this.LoginManagerContent = {
.catch(Cu.reportError);
},
onPageShow(event) {
let window = event.target.ownerGlobal;
this._detectInsecureFormLikes(window);
},
/**
* Maps all DOM content documents in this content process, including those in
* frames, to the current state used by the Login Manager.
@ -886,39 +873,6 @@ this.LoginManagerContent = {
return loginFormState;
},
/**
* Compute whether there is an insecure login form on any frame of the current page, and
* notify the parent process. This is used to control whether insecure password UI appears.
*/
_detectInsecureFormLikes(topWindow) {
log("_detectInsecureFormLikes", topWindow.location.href);
// Returns true if this window or any subframes have insecure login forms.
let hasInsecureLoginForms = thisWindow => {
let doc = thisWindow.document;
let rootElsWeakSet = LoginFormFactory.getRootElementsWeakSetForDocument(
doc
);
let hasLoginForm = !!ChromeUtils.nondeterministicGetWeakSetKeys(
rootElsWeakSet
).filter(el => el.isConnected).length;
return (
(hasLoginForm && !thisWindow.isSecureContext) ||
Array.prototype.some.call(thisWindow.frames, frame =>
hasInsecureLoginForms(frame)
)
);
};
let messageManager = topWindow.docShell.messageManager;
messageManager.sendAsyncMessage(
"PasswordManager:insecureLoginFormPresent",
{
hasInsecureLoginForms: hasInsecureLoginForms(topWindow),
}
);
},
/**
* Perform a password fill upon user request coming from the parent process.
* The fill will be in the form previously identified during page navigation.

View File

@ -169,11 +169,6 @@ this.LoginManagerParent = {
break;
}
case "PasswordManager:insecureLoginFormPresent": {
this.setHasInsecureLoginForms(msg.target, data.hasInsecureLoginForms);
break;
}
case "PasswordManager:autoCompleteLogins": {
this.doAutocompleteSearch(data, msg.target);
break;
@ -922,62 +917,6 @@ this.LoginManagerParent = {
shouldAutoSaveLogin // notifySaved
);
},
/**
* Maps all the <browser> elements for tabs in the parent process to the
* current state used to display tab-specific UI.
*
* This mapping is not updated in case a web page is moved to a different
* chrome window by the swapDocShells method. In this case, it is possible
* that a UI update just requested for the login fill doorhanger and then
* delayed by a few hundred milliseconds will be lost. Later requests would
* use the new browser reference instead.
*
* Given that the case above is rare, and it would not cause any origin
* mismatch at the time of filling because the origin is checked later in the
* content process, this case is left unhandled.
*/
loginFormStateByBrowser: new WeakMap(),
/**
* Retrieves a reference to the state object associated with the given
* browser. This is initialized to an empty object.
*/
stateForBrowser(browser) {
let loginFormState = this.loginFormStateByBrowser.get(browser);
if (!loginFormState) {
loginFormState = {};
this.loginFormStateByBrowser.set(browser, 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 an insecure password field is present so
* insecure password UI can know when to show.
*/
setHasInsecureLoginForms(browser, hasInsecureLoginForms) {
let state = this.stateForBrowser(browser);
// Update the data to use to the latest known values. Since messages are
// processed in order, this will always be the latest version to use.
state.hasInsecureLoginForms = hasInsecureLoginForms;
// Report the insecure login form state immediately.
browser.dispatchEvent(
new browser.ownerGlobal.CustomEvent("InsecureLoginFormsStateChange")
);
},
};
XPCOMUtils.defineLazyGetter(

View File

@ -10,10 +10,7 @@ support-files =
form_cross_origin_secure_action.html
form_cross_origin_insecure_action.html
head.js
insecure_test.html
insecure_test_subframe.html
multiple_forms.html
streamConverter_content.sjs
[browser_autocomplete_footer.js]
[browser_autocomplete_insecure_warning.js]
@ -65,10 +62,6 @@ support-files =
support-files =
form_password_change.html
[browser_generated_password_private_window.js]
[browser_hasInsecureLoginForms.js]
skip-if = fission || verify
[browser_hasInsecureLoginForms_streamConverter.js]
skip-if = fission
[browser_http_autofill.js]
fail-if = fission
skip-if = fission || verify

View File

@ -1,106 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
ChromeUtils.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(async function test_simple() {
for (let scheme of ["http", "https"]) {
let tab = BrowserTestUtils.addTab(
gBrowser,
scheme + testUrlPath + "form_basic.html"
);
let browser = tab.linkedBrowser;
await 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(async function test_subframe_navigation() {
await SpecialPowers.pushPrefEnv({
set: [["security.mixed_content.block_active_content", false]],
});
// Load the page with the subframe in a new tab.
let tab = BrowserTestUtils.addTab(
gBrowser,
"https" + testUrlPath + "insecure_test.html"
);
let browser = tab.linkedBrowser;
await 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),
]);
await ContentTask.spawn(browser, null, async function() {
content.document
.getElementById("test-iframe")
.contentDocument.getElementById("test-link")
.click();
});
await 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);
await ContentTask.spawn(browser, null, async function() {
content.document.getElementById("test-iframe").contentWindow.history.back();
});
await promise;
Assert.ok(LoginManagerParent.hasInsecureLoginForms(browser));
gBrowser.removeTab(tab);
});

View File

@ -1,115 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
ChromeUtils.import("resource://gre/modules/LoginManagerParent.jsm", this);
function registerConverter() {
ChromeUtils.import("resource://gre/modules/Services.jsm", this);
ChromeUtils.import("resource://gre/modules/NetUtil.jsm", this);
/**
* Converts the "test/content" MIME type, served by the test over HTTP, to an
* HTML viewer page containing the "form_basic.html" code. The viewer is
* served from a "resource:" URI while keeping the "resource:" principal.
*/
function TestStreamConverter() {}
TestStreamConverter.prototype = {
classID: Components.ID("{5f01d6ef-c090-45a4-b3e5-940d64713eb7}"),
contractID: "@mozilla.org/streamconv;1?from=test/content&to=*/*",
QueryInterface: ChromeUtils.generateQI([
Ci.nsIRequestObserver,
Ci.nsIStreamListener,
Ci.nsIStreamConverter,
]),
// nsIStreamConverter
convert() {},
// nsIStreamConverter
asyncConvertData(aFromType, aToType, aListener, aCtxt) {
this.listener = aListener;
},
// nsIRequestObserver
onStartRequest(aRequest) {
let channel = NetUtil.newChannel({
uri: "resource://testing-common/form_basic.html",
loadUsingSystemPrincipal: true,
});
channel.originalURI = aRequest.QueryInterface(Ci.nsIChannel).URI;
channel.loadGroup = aRequest.loadGroup;
channel.owner = Services.scriptSecurityManager.createContentPrincipal(
channel.URI,
{}
);
// In this test, we pass the new channel to the listener but don't fire a
// redirect notification, even if it would be required. This keeps the
// test code simpler and doesn't impact the principal check we're testing.
channel.asyncOpen(this.listener);
},
// nsIRequestObserver
onStopRequest() {},
// nsIStreamListener
onDataAvailable() {},
};
let factory = XPCOMUtils._getFactory(TestStreamConverter);
let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
registrar.registerFactory(
TestStreamConverter.prototype.classID,
"",
TestStreamConverter.prototype.contractID,
factory
);
this.cleanupFunction = function() {
registrar.unregisterFactory(TestStreamConverter.prototype.classID, factory);
};
}
/**
* 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 false for a viewer served internally
* using a "resource:" URI.
*/
add_task(async function test_streamConverter() {
let originalBrowser = gBrowser.selectedTab.linkedBrowser;
await ContentTask.spawn(originalBrowser, null, registerConverter);
let tab = BrowserTestUtils.addTab(
gBrowser,
"http://example.com/browser/toolkit/components/" +
"passwordmgr/test/browser/streamConverter_content.sjs",
{ sameProcessAsFrameLoader: originalBrowser.frameLoader }
);
let browser = tab.linkedBrowser;
await Promise.all([
BrowserTestUtils.switchTab(gBrowser, tab),
BrowserTestUtils.browserLoaded(browser),
// One event is triggered by pageshow and one by DOMFormHasPassword.
waitForInsecureLoginFormsStateChange(browser, 2),
]);
Assert.ok(!LoginManagerParent.hasInsecureLoginForms(browser));
BrowserTestUtils.removeTab(tab);
await ContentTask.spawn(originalBrowser, null, async function() {
this.cleanupFunction();
});
});

View File

@ -1,6 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
function handleRequest(request, response) {
response.setHeader("Content-Type", "test/content", false);
}