Bug 1564539 - Add breach alerts to login items r=jaws,fluent-reviewers,flod

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

--HG--
extra : moz-landing-system : lando
This commit is contained in:
lesleynorton 2019-07-29 17:54:23 +00:00
Родитель 8e83d4b8ba
Коммит 1b17257b95
11 изменённых файлов: 147 добавлений и 16 удалений

Просмотреть файл

@ -1757,6 +1757,9 @@ pref("signon.management.page.feedbackURL",
"https://www.surveygizmo.com/s3/5036102/Lockwise-feedback?ver=%VERSION%");
pref("signon.management.page.mobileAndroidURL", "https://app.adjust.com/6tteyjo?redirect=https%3A%2F%2Fplay.google.com%2Fstore%2Fapps%2Fdetails%3Fid%3Dmozilla.lockbox&utm_campaign=Desktop&utm_adgroup=InProduct&utm_creative=Elipsis_Menu");
pref("signon.management.page.mobileAppleURL", "https://app.adjust.com/6tteyjo?redirect=https%3A%2F%2Fitunes.apple.com%2Fapp%2Fid1314000270%3Fmt%3D8&utm_campaign=Desktop&utm_adgroup=InProduct&utm_creative=Elipsis_Menu");
pref("signon.management.page.breachAlertUrl",
"https://monitor.firefox.com/breach-details/");
// Enable the "Simplify Page" feature in Print Preview. This feature
// is disabled by default in toolkit.
pref("print.use_simplify_page", true);

Просмотреть файл

@ -195,7 +195,6 @@ var AboutLoginsParent = {
const logins = await this.getAllLogins();
try {
messageManager.sendAsyncMessage("AboutLogins:AllLogins", logins);
if (!BREACH_ALERTS_ENABLED) {
return;
}

Просмотреть файл

@ -82,6 +82,10 @@
<link rel="stylesheet" href="chrome://global/skin/in-content/common.css">
<link rel="stylesheet" href="chrome://browser/content/aboutlogins/common.css">
<link rel="stylesheet" href="chrome://browser/content/aboutlogins/components/login-item.css">
<div class="breach-alert">
<span class="breach-alert-text" data-l10n-id="breach-alert-text"></span>
<a class="breach-alert-link" data-l10n-id="breach-alert-link" rel="noopener noreferer" target="_blank"></a>
</div>
<div class="header">
<h2 class="title">
<span class="login-item-title"></span>

Просмотреть файл

@ -33,6 +33,7 @@ window.addEventListener("AboutLoginsChromeToContent", event => {
}
case "UpdateBreaches": {
gElements.loginList.updateBreaches(event.detail.value);
gElements.loginItem.updateBreaches(event.detail.value);
break;
}
case "LoginAdded": {

Просмотреть файл

@ -11,17 +11,6 @@
--success-color: #00c100;
}
@supports -moz-bool-pref("browser.in-content.dark-mode") {
@media (prefers-color-scheme: dark) {
:host {
--reveal-checkbox-opacity: .8;
--reveal-checkbox-opacity-hover: 1;
--reveal-checkbox-opacity-active: .6;
--success-color: #86DE74;
}
}
}
:host([data-editing]) .edit-button,
:host([data-editing]) .copy-button,
:host([data-editing]) .open-site-button,
@ -200,3 +189,48 @@
-moz-outline-radius: 3px;
box-shadow: 0 0 0 4px var(--in-content-border-active-shadow);
}
.breach-alert {
border-radius: 8px;
border: 1px solid var(--in-content-border-color);
background-color: #FFFF98;
background-image: url("chrome://global/skin/icons/warning.svg");
background-repeat: no-repeat;
background-position: left 10px top 10px;
-moz-context-properties: fill;
fill: var(--red-90);
box-shadow: 0 2px 8px 0 rgba(12,12,13,0.1);
font-size: .9em;
font-weight: 300;
line-height: 1.4;
padding-block: 12px;
padding-inline-start: 36px;
padding-inline-end: 92px;
margin-block-end: 40px;
}
.breach-alert:dir(rtl) {
background-position: right 10px top 10px;
}
a.breach-alert-link {
color: inherit;
text-decoration: underline;
font-weight: 500;
}
@supports -moz-bool-pref("browser.in-content.dark-mode") {
@media (prefers-color-scheme: dark) {
:host {
--reveal-checkbox-opacity: .8;
--reveal-checkbox-opacity-hover: 1;
--reveal-checkbox-opacity-active: .6;
--success-color: #86DE74;
}
.breach-alert {
box-shadow: 0 2px 8px 0 rgba(249,249,250,0.1);
color: #0C0C0D;
}
}
}

Просмотреть файл

@ -58,6 +58,7 @@ export default class LoginItem extends HTMLElement {
this._timeCreated = this.shadowRoot.querySelector(".time-created");
this._timeChanged = this.shadowRoot.querySelector(".time-changed");
this._timeUsed = this.shadowRoot.querySelector(".time-used");
this._breachAlert = this.shadowRoot.querySelector(".breach-alert");
this.render();
@ -77,6 +78,15 @@ export default class LoginItem extends HTMLElement {
}
render() {
this._breachAlert.hidden = true;
if (this._breachesMap && this._breachesMap.has(this._login.guid)) {
const breachDetails = this._breachesMap.get(this._login.guid);
const breachAlertLink = this._breachAlert.querySelector(
".breach-alert-link"
);
breachAlertLink.href = breachDetails.breachAlertURL;
this._breachAlert.hidden = false;
}
document.l10n.setAttributes(this._timeCreated, "login-item-time-created", {
timeCreated: this._login.timeCreated || "",
});
@ -100,6 +110,11 @@ export default class LoginItem extends HTMLElement {
this._updatePasswordRevealState();
}
updateBreaches(breachesByLoginGUID) {
this._breachesMap = breachesByLoginGUID;
this.render();
}
handleEvent(event) {
switch (event.type) {
case "AboutLoginsCreateLogin": {

Просмотреть файл

@ -79,10 +79,6 @@ ol {
background-color: var(--in-content-box-background-hover);
}
.login-list-item.breached {
border-inline-start-color: var(--in-content-border-invalid);
}
.login-list-item.selected .title {
font-weight: 600;
}
@ -99,3 +95,24 @@ ol {
font-size: 0.85em;
color: var(--in-content-deemphasized-text);
}
.login-list-item.breached {
padding-inline-end: 48px;
background-image: url("chrome://global/skin/icons/warning.svg");
background-position: right 24px center;
background-repeat: no-repeat;
-moz-context-properties: fill;
fill: var(--red-90);
}
.login-list-item.breached:dir(rtl) {
background-position: left 24px center;
}
@supports -moz-bool-pref("browser.in-content.dark-mode") {
@media (prefers-color-scheme: dark) {
.login-list-item.breached {
fill: var(--red-60);
}
}
}

Просмотреть файл

@ -46,6 +46,14 @@ const TEST_LOGIN_2 = {
timeLastUsed: "8000",
};
const TEST_BREACH = {
Name: "Test-Breach",
breachAlertURL: "https://monitor.firefox.com/breach-details/Test-Breach",
};
const TEST_BREACHES_MAP = new Map();
TEST_BREACHES_MAP.set(TEST_LOGIN_1.guid, TEST_BREACH);
add_task(async function setup() {
let templateFrame = document.getElementById("templateFrame");
let displayEl = document.getElementById("display");
@ -81,6 +89,26 @@ add_task(async function test_set_login() {
ok(copyButtons.every(button => !isHidden(button)), "The copy buttons should be visible when viewing a login");
});
add_task(async function test_update_breaches() {
gLoginItem.setLogin(TEST_LOGIN_1);
gLoginItem.updateBreaches(TEST_BREACHES_MAP);
await asyncElementRendered();
let correspondingBreach = TEST_BREACHES_MAP.get(gLoginItem._login.guid);
let breachAlert = gLoginItem.shadowRoot.querySelector(".breach-alert");
ok(!breachAlert.hidden, "Breach alert should be visible");
is(breachAlert.querySelector(".breach-alert-link").href, correspondingBreach.breachAlertURL, "Breach alert link should be equal to the correspondingBreach.breachAlertURL.");
});
add_task(async function test_breach_alert_is_correctly_hidden() {
gLoginItem.setLogin(TEST_LOGIN_2);
gLoginItem.updateBreaches(TEST_BREACHES_MAP);
await asyncElementRendered();
let breachAlert = gLoginItem.shadowRoot.querySelector(".breach-alert");
ok(breachAlert.hidden, "Breach alert should not be visible on login without an associated breach.");
});
add_task(async function test_edit_login() {
gLoginItem.setLogin(TEST_LOGIN_1);
gLoginItem.shadowRoot.querySelector(".edit-button").click();

Просмотреть файл

@ -57,6 +57,14 @@ const TEST_LOGIN_3 = {
timePasswordChanged: 1559361600000,
};
const TEST_BREACH = {
Name: "Test-Breach",
breachAlertURL: "https://monitor.firefox.com/breach-details/Test-Breach",
};
const TEST_BREACHES_MAP = new Map();
TEST_BREACHES_MAP.set(TEST_LOGIN_1.guid, TEST_BREACH);
add_task(async function setup() {
let templateFrame = document.getElementById("templateFrame");
let displayEl = document.getElementById("display");
@ -149,6 +157,13 @@ add_task(async function test_populated_list() {
ok(!loginListItems[1].classList.contains("selected"), "The second item should still not be selected");
});
add_task(async function test_breach_indicator() {
gLoginList.updateBreaches(TEST_BREACHES_MAP);
let loginListItems = gLoginList.shadowRoot.querySelectorAll(".login-list-item");
ok(loginListItems[0].classList.contains("breached"), "The first login should have the .breached class.");
ok(!loginListItems[1].classList.contains("breached"), "The second login should not have the .breached class");
});
add_task(async function test_filtered_list() {
is(gLoginList.shadowRoot.querySelectorAll(".login-list-item:not([hidden])").length, 2, "Both logins should be visible");
let countSpan = gLoginList.shadowRoot.querySelector(".count");

Просмотреть файл

@ -10,6 +10,7 @@ login-filter =
create-login-button = Create New Login
## The ⋯ menu that is in the top corner of the page
menu =
.title = Open menu
# This menuitem is only visible on Windows
@ -25,6 +26,7 @@ menu-menuitem-download-android = Lockwise for Android
menu-menuitem-download-iphone = Lockwise for iPhone and iPad
## Login List
login-list =
.aria-label = Logins matching search query
login-list-count =
@ -41,6 +43,7 @@ login-list-item-subtitle-new-login = Enter your login credentials
login-list-item-subtitle-missing-username = (no username)
## Login
login-item-new-login-title = Create New Login
login-item-edit-button = Edit
login-item-delete-button = Delete
@ -68,6 +71,7 @@ login-item-time-created = Created: { DATETIME($timeCreated, day: "numeric", mont
login-item-time-used = Last used: { DATETIME($timeUsed, day: "numeric", month: "long", year: "numeric") }
## Master Password notification
master-password-notification-message = Please enter your master password to view saved logins & passwords
master-password-reload-button =
.label = Log in
@ -79,3 +83,8 @@ confirm-delete-dialog-dismiss-button =
.title = Cancel
confirm-delete-dialog-cancel-button = Cancel
confirm-delete-dialog-confirm-button = Delete
## Breach Alert notification
breach-alert-text = Passwords were leaked or stolen from this website since you last updated your login details. Change your password to protect your account.
breach-alert-link = Learn more about this breach.

Просмотреть файл

@ -1110,6 +1110,10 @@ this.LoginHelper = {
if (!breaches) {
breaches = await RemoteSettings("fxmonitor-breaches").get();
}
const BREACH_ALERT_URL = Services.prefs.getStringPref(
"signon.management.page.breachAlertUrl"
);
const baseBreachAlertURL = new URL(BREACH_ALERT_URL);
// Determine potentially breached logins by checking their origin and the last time
// they were changed. It's important to note here that we are NOT considering the
@ -1125,6 +1129,8 @@ this.LoginHelper = {
Services.eTLD.hasRootDomain(loginURI.host, breach.Domain) &&
login.timePasswordChanged < new Date(breach.BreachDate).getTime()
) {
let breachAlertURL = new URL(breach.Name, baseBreachAlertURL);
breach.breachAlertURL = breachAlertURL.href;
breachesByLoginGUID.set(login.guid, breach);
}
}