Bug 1583398 - Newly added logins should show breach notifications if they apply. r=MattN

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

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Jared Wein 2019-10-03 15:32:30 +00:00
parent f51a51c27d
commit c03d373e19
13 changed files with 134 additions and 15 deletions

View File

@ -166,6 +166,7 @@ let LEGACY_ACTORS = {
"AboutLogins:LoginRemoved",
"AboutLogins:MasterPasswordResponse",
"AboutLogins:SendFavicons",
"AboutLogins:SetBreaches",
"AboutLogins:Setup",
"AboutLogins:ShowLoginItemError",
"AboutLogins:SyncState",

View File

@ -219,6 +219,9 @@ class AboutLoginsChild extends ActorChild {
case "AboutLogins:SendFavicons":
this.sendToContent("SendFavicons", message.data);
break;
case "AboutLogins:SetBreaches":
this.sendToContent("SetBreaches", message.data);
break;
case "AboutLogins:Setup":
this.sendToContent("Setup", message.data);
Cu.waiveXrays(this.content).AboutLoginsUtils.masterPasswordEnabled =

View File

@ -242,7 +242,7 @@ var AboutLoginsParent = {
);
const messageManager = message.target.messageManager;
messageManager.sendAsyncMessage(
"AboutLogins:UpdateBreaches",
"AboutLogins:SetBreaches",
breachesByLoginGUID
);
break;
@ -513,6 +513,13 @@ var AboutLoginsParent = {
return;
}
this.messageSubscribers("AboutLogins:LoginAdded", login);
if (BREACH_ALERTS_ENABLED) {
this.messageSubscribers(
"AboutLogins:UpdateBreaches",
await LoginBreaches.getPotentialBreachesByLoginGUID([login])
);
}
break;
}
case "modifyLogin": {
@ -725,7 +732,7 @@ var AboutLoginsParent = {
};
if (BREACH_ALERTS_ENABLED) {
sendMessageFn(
"AboutLogins:UpdateBreaches",
"AboutLogins:SetBreaches",
await LoginBreaches.getPotentialBreachesByLoginGUID(logins)
);
}

View File

@ -69,6 +69,11 @@ window.addEventListener("AboutLoginsChromeToContent", event => {
gElements.loginList.addFavicons(event.detail.value);
break;
}
case "SetBreaches": {
gElements.loginList.setBreaches(event.detail.value);
gElements.loginItem.setBreaches(event.detail.value);
break;
}
case "Setup": {
handleAllLogins(event.detail.value.logins);
gElements.loginFooter.showStoreIconsForLocales(

View File

@ -176,11 +176,21 @@ export default class LoginItem extends HTMLElement {
this._updatePasswordRevealState();
}
updateBreaches(breachesByLoginGUID) {
setBreaches(breachesByLoginGUID) {
this._breachesMap = breachesByLoginGUID;
this.render();
}
updateBreaches(breachesByLoginGUID) {
if (!this._breachesMap) {
this._breachesMap = new Map();
}
for (const [guid, breach] of [...breachesByLoginGUID]) {
this._breachesMap.set(guid, breach);
}
this.setBreaches(this._breachesMap);
}
dismissBreachAlert() {
document.dispatchEvent(
new CustomEvent("AboutLoginsDismissBreachAlert", {

View File

@ -327,7 +327,7 @@ export default class LoginList extends HTMLElement {
* @param {Map} breachesByLoginGUID A Map of breaches by login GUIDs used
* for displaying breached login indicators.
*/
updateBreaches(breachesByLoginGUID) {
setBreaches(breachesByLoginGUID) {
this._breachesByLoginGUID = breachesByLoginGUID;
if (this._breachesByLoginGUID.size === 0) {
this.render();
@ -343,6 +343,21 @@ export default class LoginList extends HTMLElement {
this.render();
}
/**
* @param {Map} breachesByLoginGUID A Map of breaches by login GUIDs that
* should be added to the local cache of
* breaches.
*/
updateBreaches(breachesByLoginGUID) {
if (!this._breachesByLoginGUID) {
this._breachesByLoginGUID = new Map();
}
for (const [guid, breach] of [...breachesByLoginGUID]) {
this._breachesByLoginGUID.set(guid, breach);
}
this.setBreaches(this._breachesByLoginGUID);
}
/**
* @param {login} login A login that was added to storage.
*/

View File

@ -10,6 +10,7 @@ support-files =
skip-if = asan || debug
[browser_breachAlertDismissals.js]
skip-if = asan || debug || verify # bug 1574023
[browser_breachAlertShowingForAddedLogin.js]
[browser_confirmDeleteDialog.js]
[browser_contextmenuFillLogins.js]
[browser_copyToClipboardButton.js]

View File

@ -9,7 +9,7 @@ const TEST_BREACHES = [
{
AddedDate: "2019-12-20T23:56:26Z",
BreachDate: "2018-12-16",
Domain: "breached.com",
Domain: "breached.example.com",
Name: "Breached",
PwnCount: 1643100,
DataClasses: ["Email addresses", "Usernames", "Passwords", "IP addresses"],
@ -40,10 +40,10 @@ add_task(async function test_show_login() {
TEST_BREACHES
);
browser.messageManager.sendAsyncMessage(
"AboutLogins:UpdateBreaches",
"AboutLogins:SetBreaches",
testBreaches
);
await ContentTask.spawn(browser, TEST_LOGIN3, async () => {
await ContentTask.spawn(browser, null, async () => {
let loginItem = Cu.waiveXrays(content.document.querySelector("login-item"));
let breachAlert = loginItem.shadowRoot.querySelector(".breach-alert");
let breachAlertVisible = await ContentTaskUtils.waitForCondition(() => {

View File

@ -0,0 +1,77 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
let { LoginBreaches } = ChromeUtils.import(
"resource:///modules/LoginBreaches.jsm"
);
const TEST_BREACHES = [
{
AddedDate: "2019-12-20T23:56:26Z",
BreachDate: "2018-12-16",
Domain: "breached.example.com",
Name: "Breached",
PwnCount: 1643100,
DataClasses: ["Email addresses", "Usernames", "Passwords", "IP addresses"],
_status: "synced",
id: "047940fe-d2fd-4314-b636-b4a952ee0043",
last_modified: "1541615610052",
schema: "1541615609018",
},
];
add_task(async function setup() {
let oldGetPotentialBreachesByLoginGUID =
LoginBreaches.getPotentialBreachesByLoginGUID;
LoginBreaches.getPotentialBreachesByLoginGUID = logins => {
if (!logins.length) {
return new Map();
}
let breaches = new Map();
breaches.set(logins[0].guid, TEST_BREACHES[0]);
return breaches;
};
await BrowserTestUtils.openNewForegroundTab({
gBrowser,
url: "about:logins",
});
registerCleanupFunction(() => {
LoginBreaches.getPotentialBreachesByLoginGUID = oldGetPotentialBreachesByLoginGUID;
BrowserTestUtils.removeTab(gBrowser.selectedTab);
Services.logins.removeAllLogins();
});
});
add_task(async function test_added_login_shows_breach_warning() {
let browser = gBrowser.selectedBrowser;
await ContentTask.spawn(browser, null, async () => {
let loginList = Cu.waiveXrays(content.document.querySelector("login-list"));
is(
loginList._loginGuidsSortedOrder.length,
0,
"the login list should be empty"
);
});
TEST_LOGIN3 = await addLogin(TEST_LOGIN3);
await ContentTask.spawn(browser, TEST_LOGIN3.guid, async aTestLogin3Guid => {
let loginList = Cu.waiveXrays(content.document.querySelector("login-list"));
is(
loginList._loginGuidsSortedOrder.length,
1,
"one login should be in the list"
);
let breachedLoginListItems;
await ContentTaskUtils.waitForCondition(() => {
breachedLoginListItems = loginList._list.querySelectorAll(
".login-list-item[data-guid].breached"
);
return breachedLoginListItems.length == 1;
}, "waiting for the login to get marked as breached");
is(
breachedLoginListItems[0].dataset.guid,
aTestLogin3Guid,
"the breached login should be login3"
);
});
});

View File

@ -27,8 +27,8 @@ let TEST_LOGIN2 = new nsLoginInfo(
);
let TEST_LOGIN3 = new nsLoginInfo(
"https://breached.com",
"https://breached.com",
"https://breached.example.com",
"https://breached.example.com",
null,
"breachedLogin1",
"pass3",

View File

@ -139,7 +139,7 @@ add_task(async function test_set_login() {
add_task(async function test_update_breaches() {
gLoginItem.setLogin(TEST_LOGIN_1);
gLoginItem.updateBreaches(TEST_BREACHES_MAP);
gLoginItem.setBreaches(TEST_BREACHES_MAP);
await asyncElementRendered();
let correspondingBreach = TEST_BREACHES_MAP.get(gLoginItem._login.guid);
@ -150,7 +150,7 @@ add_task(async function test_update_breaches() {
add_task(async function test_breach_alert_is_correctly_hidden() {
gLoginItem.setLogin(TEST_LOGIN_2);
gLoginItem.updateBreaches(TEST_BREACHES_MAP);
gLoginItem.setBreaches(TEST_BREACHES_MAP);
await asyncElementRendered();
let breachAlert = gLoginItem.shadowRoot.querySelector(".breach-alert");

View File

@ -242,7 +242,7 @@ add_task(async function test_populated_list() {
});
add_task(async function test_breach_indicator() {
gLoginList.updateBreaches(TEST_BREACHES_MAP);
gLoginList.setBreaches(TEST_BREACHES_MAP);
let loginListItems = gLoginList.shadowRoot.querySelectorAll(".login-list-item:not(#new-login-list-item):not([hidden])");
let warningIcon = loginListItems[0].querySelector(".list-item-warning-icon");
let hiddenWarningIcon = loginListItems[1].querySelector(".list-item-warning-icon");
@ -459,7 +459,7 @@ add_task(async function test_sorted_list() {
is(pwChanged2 > pwChanged3, true, "Logins sorted by timePasswordChanged. Second: " + pwChanged2 + "; Third: " + pwChanged3);
// sort by breached when there are breached logins
gLoginList.updateBreaches(TEST_BREACHES_MAP);
gLoginList.setBreaches(TEST_BREACHES_MAP);
loginSort.selectedIndex = 3;
dispatchChangeEvent(loginSort);
loginListItems = gLoginList.shadowRoot.querySelectorAll(".login-list-item:not(#new-login-list-item):not([hidden])");
@ -467,7 +467,7 @@ add_task(async function test_sorted_list() {
is(!loginListItems[1].classList.contains("breached"), true, "Non-breached login should be displayed below breached");
// sort by name when there are no breached logins
gLoginList.updateBreaches(new Map());
gLoginList.setBreaches(new Map());
loginSort.selectedIndex = 3;
dispatchChangeEvent(loginSort);
loginListItems = gLoginList.shadowRoot.querySelectorAll(".login-list-item:not(#new-login-list-item):not([hidden])");

View File

@ -240,7 +240,7 @@ add_task(async function test_ExceptionsThrownByNonStandardURIsAreCaught() {
);
});
add_task(async function test_updateBreachesFromRemoteSettingsSync() {
add_task(async function test_setBreachesFromRemoteSettingsSync() {
const login = NOT_BREACHED_SUBDOMAIN_LOGIN;
const nowExampleIsInBreachedRecords = [
{