Commit 215014dd authored by Friedrich Horschig's avatar Friedrich Horschig Committed by Commit Bot

[Passwords] Suppress retry leak checks for passphrases/non-sync users

With this CL, the error case "Too many passwords" will no longer offer
to check passwords in the account for users that are:
- not syncing
- syncing but use a passphrase.

If no check can be offered, always show the total count of found leaks.
screenshots can be found at https://crbug.com/1047726#c58

Translation screenshot for
IDS_SETTINGS_CHECK_PASSWORDS_ERROR_INTERRUPTED_TOO_MANY_PASSWORDS:
https://storage.cloud.google.com/chromium-translation-screenshots/cf5f1e0f138864731990c89272cb55030f2e380b

Bug: 1047726
Change-Id: I4142b1ef3912c15155484000ca305d488ded0dd1
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2096708
Commit-Queue: Friedrich [CET] <fhorschig@chromium.org>
Reviewed-by: default avatarVasilii Sukhanov <vasilii@chromium.org>
Reviewed-by: default avatarJan Wilken Dörrie <jdoerrie@chromium.org>
Cr-Commit-Position: refs/heads/master@{#749635}
parent 4915472c
...@@ -335,6 +335,9 @@ ...@@ -335,6 +335,9 @@
<message name="IDS_SETTINGS_CHECK_PASSWORDS_DESCRIPTION" desc="Explanation of the passwords bulk check feature found within the password settings."> <message name="IDS_SETTINGS_CHECK_PASSWORDS_DESCRIPTION" desc="Explanation of the passwords bulk check feature found within the password settings.">
Keep your passwords safe from data breaches and other security issues Keep your passwords safe from data breaches and other security issues
</message> </message>
<message name="IDS_SETTINGS_CHECK_PASSWORDS_ERROR_INTERRUPTED_TOO_MANY_PASSWORDS" desc="Error message when the password check was started and found some results but can't be completed since the user saved too many passwords.">
Chrome couldn't check all of your passwords because there are too many.
</message>
<message name="IDS_SETTINGS_CHECK_PASSWORDS_ERROR_OFFLINE" desc="Error message when the password check can't be completed because the user is offline."> <message name="IDS_SETTINGS_CHECK_PASSWORDS_ERROR_OFFLINE" desc="Error message when the password check can't be completed because the user is offline.">
Chrome can't check your passwords. Try checking your internet connection. Chrome can't check your passwords. Try checking your internet connection.
</message> </message>
......
...@@ -478,6 +478,7 @@ polymer_modulizer("password_check") { ...@@ -478,6 +478,7 @@ polymer_modulizer("password_check") {
html_type = "dom-module" html_type = "dom-module"
auto_imports = [ auto_imports = [
"chrome/browser/resources/settings/autofill_page/password_manager_proxy.html|PasswordManagerImpl,PasswordManagerProxy", "chrome/browser/resources/settings/autofill_page/password_manager_proxy.html|PasswordManagerImpl,PasswordManagerProxy",
"chrome/browser/resources/settings/people_page/sync_browser_proxy.html|SyncBrowserProxyImpl,SyncPrefs,SyncStatus",
"chrome/browser/resources/settings/plural_string_proxy.html|PluralStringProxyImpl", "chrome/browser/resources/settings/plural_string_proxy.html|PluralStringProxyImpl",
"ui/webui/resources/html/assert.html|assert", "ui/webui/resources/html/assert.html|assert",
] ]
......
...@@ -3,14 +3,16 @@ ...@@ -3,14 +3,16 @@
<link rel="import" href="chrome://resources/cr_elements/cr_action_menu/cr_action_menu.html"> <link rel="import" href="chrome://resources/cr_elements/cr_action_menu/cr_action_menu.html">
<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html"> <link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
<link rel="import" href="../i18n_setup.html"> <link rel="import" href="../i18n_setup.html">
<link rel="import" href="../people_page/sync_browser_proxy.html">
<link rel="import" href="../plural_string_proxy.html"> <link rel="import" href="../plural_string_proxy.html">
<link rel="import" href="../settings_shared_css.html"> <link rel="import" href="../settings_shared_css.html">
<link rel="import" href="chrome://resources/cr_elements/icons.html"> <link rel="import" href="chrome://resources/cr_elements/icons.html">
<link rel="import" href="chrome://resources/html/assert.html"> <link rel="import" href="chrome://resources/html/assert.html">
<link rel="import" href="chrome://resources/html/i18n_behavior.html">
<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
<link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html"> <link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
<link rel="import" href="chrome://resources/polymer/v1_0/iron-list/iron-list.html"> <link rel="import" href="chrome://resources/polymer/v1_0/iron-list/iron-list.html">
<link rel="import" href="chrome://resources/polymer/v1_0/paper-spinner/paper-spinner-lite.html"> <link rel="import" href="chrome://resources/polymer/v1_0/paper-spinner/paper-spinner-lite.html">
<link rel="import" href="chrome://resources/html/i18n_behavior.html">
<link rel="import" href="password_check_list_item.html"> <link rel="import" href="password_check_list_item.html">
<link rel="import" href="password_manager_proxy.html"> <link rel="import" href="password_manager_proxy.html">
...@@ -71,11 +73,11 @@ ...@@ -71,11 +73,11 @@
<div class="start settings-box-text"> <div class="start settings-box-text">
<div id="title"> <div id="title">
[[getTitle_(status_)]] [[getTitle_(status_, suppressCheckupLink_)]]
<a class="secondary inline" id="linkToGoogleAccount" <a class="secondary inline" id="linkToGoogleAccount"
href="[[passwordCheckUrl_]]" href="[[passwordCheckUrl_]]"
target="_blank" target="_blank"
hidden$="[[!hasTooManyPasswords_(status_)]]"> hidden$="[[suppressCheckupLink_]]">
$i18n{checkPasswordsAgainInAccount} $i18n{checkPasswordsAgainInAccount}
</a> </a>
<span class="secondary inline" id="lastCompletedCheck" <span class="secondary inline" id="lastCompletedCheck"
...@@ -84,7 +86,8 @@ ...@@ -84,7 +86,8 @@
</span> </span>
</div> </div>
<div class="secondary" id="subtitle" <div class="secondary" id="subtitle"
hidden$="[[!showsPasswordsCount_(status_, leakedPasswords)]]"> hidden$="[[!showsPasswordsCount_(status_, leakedPasswords,
suppressCheckupLink_)]]">
[[compromisedPasswordsCount_]] [[compromisedPasswordsCount_]]
</div> </div>
</div> </div>
......
...@@ -9,7 +9,10 @@ const CheckState = chrome.passwordsPrivate.PasswordCheckState; ...@@ -9,7 +9,10 @@ const CheckState = chrome.passwordsPrivate.PasswordCheckState;
Polymer({ Polymer({
is: 'settings-password-check', is: 'settings-password-check',
behaviors: [I18nBehavior], behaviors: [
I18nBehavior,
WebUIListenerBehavior,
],
properties: { properties: {
/** /**
...@@ -51,7 +54,19 @@ Polymer({ ...@@ -51,7 +54,19 @@ Polymer({
value: () => { value: () => {
return {state: CheckState.IDLE}; return {state: CheckState.IDLE};
}, },
} },
/** @private */
suppressCheckupLink_: {
type: Boolean,
computed: 'suppressesCheckupLink_(status_, syncPrefs_, syncStatus_)',
},
/** @private {settings.SyncPrefs} */
syncPrefs_: Object,
/** @private {settings.SyncStatus} */
syncStatus_: Object,
}, },
/** /**
...@@ -82,6 +97,7 @@ Polymer({ ...@@ -82,6 +97,7 @@ Polymer({
attached() { attached() {
// Set the manager. These can be overridden by tests. // Set the manager. These can be overridden by tests.
this.passwordManager_ = PasswordManagerImpl.getInstance(); this.passwordManager_ = PasswordManagerImpl.getInstance();
const syncBrowserProxy = settings.SyncBrowserProxyImpl.getInstance();
const statusChangeListener = status => this.status_ = status; const statusChangeListener = status => this.status_ = status;
const setLeakedCredentialsListener = compromisedCredentials => { const setLeakedCredentialsListener = compromisedCredentials => {
...@@ -93,6 +109,9 @@ Polymer({ ...@@ -93,6 +109,9 @@ Polymer({
this.compromisedPasswordsCount_ = count; this.compromisedPasswordsCount_ = count;
}); });
}; };
// TODO(crbug.com/1047726): Deduplicate with updates in password_section.js.
const syncStatusChanged = syncStatus => this.syncStatus_ = syncStatus;
const syncPrefsChanged = syncPrefs => this.syncPrefs_ = syncPrefs;
this.statusChangedListener_ = statusChangeListener; this.statusChangedListener_ = statusChangeListener;
this.leakedCredentialsListener_ = setLeakedCredentialsListener; this.leakedCredentialsListener_ = setLeakedCredentialsListener;
...@@ -102,12 +121,15 @@ Polymer({ ...@@ -102,12 +121,15 @@ Polymer({
this.statusChangedListener_); this.statusChangedListener_);
this.passwordManager_.getCompromisedCredentials().then( this.passwordManager_.getCompromisedCredentials().then(
this.leakedCredentialsListener_); this.leakedCredentialsListener_);
syncBrowserProxy.getSyncStatus().then(syncStatusChanged);
// Listen for changes. // Listen for changes.
this.passwordManager_.addPasswordCheckStatusListener( this.passwordManager_.addPasswordCheckStatusListener(
this.statusChangedListener_); this.statusChangedListener_);
this.passwordManager_.addCompromisedCredentialsListener( this.passwordManager_.addCompromisedCredentialsListener(
this.leakedCredentialsListener_); this.leakedCredentialsListener_);
this.addWebUIListener('sync-status-changed', syncStatusChanged);
this.addWebUIListener('sync-prefs-changed', syncPrefsChanged);
}, },
/** @override */ /** @override */
...@@ -222,20 +244,19 @@ Polymer({ ...@@ -222,20 +244,19 @@ Polymer({
/** /**
* Returns the title message indicating the state of the last/ongoing check. * Returns the title message indicating the state of the last/ongoing check.
* @param {!PasswordManagerProxy.PasswordCheckStatus} status
* @return {string} * @return {string}
* @private * @private
*/ */
getTitle_(status) { getTitle_() {
switch (status.state) { switch (this.status_.state) {
case CheckState.IDLE: case CheckState.IDLE:
return this.i18n('checkPasswords'); return this.i18n('checkPasswords');
case CheckState.CANCELED: case CheckState.CANCELED:
return this.i18n('checkPasswordsCanceled'); return this.i18n('checkPasswordsCanceled');
case CheckState.RUNNING: case CheckState.RUNNING:
return this.i18n( return this.i18n(
'checkPasswordsProgress', status.alreadyProcessed || 0, 'checkPasswordsProgress', this.status_.alreadyProcessed || 0,
status.remainingInQueue + status.alreadyProcessed); this.status_.remainingInQueue + this.status_.alreadyProcessed);
case CheckState.OFFLINE: case CheckState.OFFLINE:
return this.i18n('checkPasswordsErrorOffline'); return this.i18n('checkPasswordsErrorOffline');
case CheckState.SIGNED_OUT: case CheckState.SIGNED_OUT:
...@@ -243,13 +264,15 @@ Polymer({ ...@@ -243,13 +264,15 @@ Polymer({
case CheckState.NO_PASSWORDS: case CheckState.NO_PASSWORDS:
return this.i18n('checkPasswordsErrorNoPasswords'); return this.i18n('checkPasswordsErrorNoPasswords');
case CheckState.TOO_MANY_PASSWORDS: case CheckState.TOO_MANY_PASSWORDS:
return this.i18n('checkPasswordsErrorTooManyPasswords'); return this.suppressesCheckupLink_() ?
this.i18n('checkPasswordsErrorInterruptedTooManyPasswords') :
this.i18n('checkPasswordsErrorTooManyPasswords');
case CheckState.QUOTA_LIMIT: case CheckState.QUOTA_LIMIT:
return this.i18n('checkPasswordsErrorQuota'); return this.i18n('checkPasswordsErrorQuota');
case CheckState.OTHER_ERROR: case CheckState.OTHER_ERROR:
return this.i18n('checkPasswordsErrorGeneric'); return this.i18n('checkPasswordsErrorGeneric');
} }
throw 'Can\'t find a title for state: ' + status.state; assertNotReached('Can\'t find a title for state: ' + this.status_.state);
}, },
/** /**
...@@ -321,17 +344,6 @@ Polymer({ ...@@ -321,17 +344,6 @@ Polymer({
throw 'Can\'t determine button visibility for state: ' + status.state; throw 'Can\'t determine button visibility for state: ' + status.state;
}, },
/**
* Returns true iff the backend communicated that there are too many saved
* passwords to check them locally.
* @param {!PasswordManagerProxy.PasswordCheckStatus} status
* @return {boolean}
* @private
*/
hasTooManyPasswords_(status) {
return status.state == CheckState.TOO_MANY_PASSWORDS;
},
/** /**
* Returns the chrome:// address where the banner image is located. * Returns the chrome:// address where the banner image is located.
* @param {boolean} isDarkMode * @param {boolean} isDarkMode
...@@ -389,28 +401,50 @@ Polymer({ ...@@ -389,28 +401,50 @@ Polymer({
/** /**
* Returns true if there are leaked credentials or the status is unexpected * Returns true if there are leaked credentials or the status is unexpected
* for a regular password check. * for a regular password check.
* @param {!PasswordManagerProxy.PasswordCheckStatus} status
* @param {!Array<PasswordManagerProxy.CompromisedCredential>}
* leakedPasswords
* @return {boolean} * @return {boolean}
* @private * @private
*/ */
showsPasswordsCount_(status, leakedPasswords) { showsPasswordsCount_() {
switch (status.state) { if (this.hasLeakedCredentials_(this.leakedPasswords)) {
return true;
}
switch (this.status_.state) {
case CheckState.IDLE: case CheckState.IDLE:
return true; return true;
case CheckState.CANCELED: case CheckState.CANCELED:
case CheckState.RUNNING: case CheckState.RUNNING:
return this.hasLeakedCredentials_(leakedPasswords);
case CheckState.OFFLINE: case CheckState.OFFLINE:
case CheckState.SIGNED_OUT: case CheckState.SIGNED_OUT:
case CheckState.NO_PASSWORDS: case CheckState.NO_PASSWORDS:
case CheckState.TOO_MANY_PASSWORDS:
case CheckState.QUOTA_LIMIT: case CheckState.QUOTA_LIMIT:
case CheckState.OTHER_ERROR: case CheckState.OTHER_ERROR:
return false; return false;
case CheckState.TOO_MANY_PASSWORDS:
return this.suppressesCheckupLink_();
}
assertNotReached(
'Not specified whether to show passwords for state: ' +
this.status_.state);
},
/**
* Returns true iff the user cannot retry the password check in their account.
* Either because they are syncing or because they use a custom passphrase.
* @return {boolean}
* @private
*/
suppressesCheckupLink_() {
if (!this.isButtonHidden_(this.status_)) {
return true; // Never show the retry link alongside a button.
}
if (this.status_.state != CheckState.TOO_MANY_PASSWORDS) {
return true; // Never show the retry link for other states.
}
if (!this.syncStatus_ || !this.syncStatus_.signedIn) {
return true; // Never show the retry link for signed-out users.
} }
throw 'Not specified whether to show passwords for state: ' + status.state; // Show the retry link only, if user data is unencrypted.
return !!this.syncPrefs_ && !!this.syncPrefs_.encryptAllData;
}, },
/** /**
......
...@@ -733,6 +733,8 @@ void AddAutofillStrings(content::WebUIDataSource* html_source, ...@@ -733,6 +733,8 @@ void AddAutofillStrings(content::WebUIDataSource* html_source,
{"checkPasswordsCanceled", IDS_SETTINGS_CHECK_PASSWORDS_CANCELED}, {"checkPasswordsCanceled", IDS_SETTINGS_CHECK_PASSWORDS_CANCELED},
{"checkedPasswords", IDS_SETTINGS_CHECKED_PASSWORDS}, {"checkedPasswords", IDS_SETTINGS_CHECKED_PASSWORDS},
{"checkPasswordsDescription", IDS_SETTINGS_CHECK_PASSWORDS_DESCRIPTION}, {"checkPasswordsDescription", IDS_SETTINGS_CHECK_PASSWORDS_DESCRIPTION},
{"checkPasswordsErrorInterruptedTooManyPasswords",
IDS_SETTINGS_CHECK_PASSWORDS_ERROR_INTERRUPTED_TOO_MANY_PASSWORDS},
{"checkPasswordsErrorOffline", {"checkPasswordsErrorOffline",
IDS_SETTINGS_CHECK_PASSWORDS_ERROR_OFFLINE}, IDS_SETTINGS_CHECK_PASSWORDS_ERROR_OFFLINE},
{"checkPasswordsErrorSignedOut", {"checkPasswordsErrorSignedOut",
......
...@@ -360,6 +360,7 @@ CrSettingsPasswordsCheckTest.prototype = { ...@@ -360,6 +360,7 @@ CrSettingsPasswordsCheckTest.prototype = {
extraLibraries: CrSettingsBrowserTest.prototype.extraLibraries.concat([ extraLibraries: CrSettingsBrowserTest.prototype.extraLibraries.concat([
'../test_browser_proxy.js', '../test_browser_proxy.js',
'passwords_and_autofill_fake_data.js', 'passwords_and_autofill_fake_data.js',
'sync_test_util.js',
'test_password_manager_proxy.js', 'test_password_manager_proxy.js',
'password_check_test.js', 'password_check_test.js',
]), ]),
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
// clang-format off // clang-format off
// #import {PasswordManagerImpl} from 'chrome://settings/settings.js'; // #import {PasswordManagerImpl} from 'chrome://settings/settings.js';
// #import {makeCompromisedCredential, makePasswordCheckStatus} from 'chrome://test/settings/passwords_and_autofill_fake_data.m.js'; // #import {makeCompromisedCredential, makePasswordCheckStatus} from 'chrome://test/settings/passwords_and_autofill_fake_data.m.js';
// #import {getSyncAllPrefs,simulateSyncStatus} from 'chrome://test/settings/sync_test_util.m.js';
// #import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; // #import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
// #import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js'; // #import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
// #import {TestPasswordManagerProxy} from 'chrome://test/settings/test_password_manager_proxy.m.js'; // #import {TestPasswordManagerProxy} from 'chrome://test/settings/test_password_manager_proxy.m.js';
...@@ -154,17 +155,65 @@ cr.define('settings_passwords_check', function() { ...@@ -154,17 +155,65 @@ cr.define('settings_passwords_check', function() {
// Test verifies that 'Try again' is not visible if users have too many // Test verifies that 'Try again' is not visible if users have too many
// passwords. Instead, there should be a link to their Google Account. // passwords. Instead, there should be a link to their Google Account.
test('testNoRetryForTooManyPasswords', function() { test('testRetryInAccountForTooManyPasswordsWhenSyncing', function() {
passwordManager.data.checkStatus = passwordManager.data.checkStatus =
autofill_test_util.makePasswordCheckStatus( autofill_test_util.makePasswordCheckStatus(
/*state=*/ PasswordCheckState.TOO_MANY_PASSWORDS); /*state=*/ PasswordCheckState.TOO_MANY_PASSWORDS);
const section = createCheckPasswordSection(); const section = createCheckPasswordSection();
cr.webUIListenerCallback(
'sync-prefs-changed', sync_test_util.getSyncAllPrefs());
sync_test_util.simulateSyncStatus({signedIn: true});
return passwordManager.whenCalled('getPasswordCheckStatus').then(() => { return passwordManager.whenCalled('getPasswordCheckStatus').then(() => {
expectFalse(isElementVisible(section.$.controlPasswordCheckButton)); expectFalse(isElementVisible(section.$.controlPasswordCheckButton));
expectTrue(isElementVisible(section.$.linkToGoogleAccount)); expectTrue(isElementVisible(section.$.linkToGoogleAccount));
}); });
}); });
test('testNoRetryInAccountForTooManyPasswordsWhenSignedOut', function() {
passwordManager.data.checkStatus =
autofill_test_util.makePasswordCheckStatus(
PasswordCheckState.TOO_MANY_PASSWORDS);
const section = createCheckPasswordSection();
cr.webUIListenerCallback(
'sync-prefs-changed', sync_test_util.getSyncAllPrefs());
sync_test_util.simulateSyncStatus({signedIn: false});
return passwordManager.whenCalled('getPasswordCheckStatus').then(() => {
Polymer.dom.flush();
assertTrue(isElementVisible(section.$.title));
expectEquals(
section.i18n('checkPasswordsErrorInterruptedTooManyPasswords'),
section.$.title.innerText);
expectFalse(isElementVisible(section.$.linkToGoogleAccount));
expectTrue(isElementVisible(section.$.subtitle));
});
});
test('testNoRetryInAccountForTooManyPasswordsForEncryption', function() {
passwordManager.data.checkStatus =
autofill_test_util.makePasswordCheckStatus(
PasswordCheckState.TOO_MANY_PASSWORDS);
const section = createCheckPasswordSection();
const syncPrefs = sync_test_util.getSyncAllPrefs();
syncPrefs.encryptAllData = true;
cr.webUIListenerCallback('sync-prefs-changed', syncPrefs);
sync_test_util.simulateSyncStatus({signedIn: true});
return passwordManager.whenCalled('getPasswordCheckStatus').then(() => {
Polymer.dom.flush();
assertTrue(isElementVisible(section.$.title));
expectEquals(
section.i18n('checkPasswordsErrorInterruptedTooManyPasswords'),
section.$.title.innerText);
expectFalse(isElementVisible(section.$.linkToGoogleAccount));
expectTrue(isElementVisible(section.$.subtitle));
});
});
// Test verifies that 'Try again' visible and working when users encounter a // Test verifies that 'Try again' visible and working when users encounter a
// generic error. // generic error.
test('testShowRetryAfterGenericError', function() { test('testShowRetryAfterGenericError', function() {
...@@ -587,11 +636,14 @@ cr.define('settings_passwords_check', function() { ...@@ -587,11 +636,14 @@ cr.define('settings_passwords_check', function() {
PasswordCheckState.TOO_MANY_PASSWORDS); PasswordCheckState.TOO_MANY_PASSWORDS);
const section = createCheckPasswordSection(); const section = createCheckPasswordSection();
cr.webUIListenerCallback(
'sync-prefs-changed', sync_test_util.getSyncAllPrefs());
sync_test_util.simulateSyncStatus({signedIn: true});
return passwordManager.whenCalled('getPasswordCheckStatus').then(() => { return passwordManager.whenCalled('getPasswordCheckStatus').then(() => {
Polymer.dom.flush(); Polymer.dom.flush();
const title = section.$.title; const title = section.$.title;
assertTrue(isElementVisible(title)); assertTrue(isElementVisible(title));
// TODO(crbug.com/1047726): Check for account redirection.
expectEquals( expectEquals(
section.i18n('checkPasswordsErrorTooManyPasswords') + ' ' + section.i18n('checkPasswordsErrorTooManyPasswords') + ' ' +
section.i18n('checkPasswordsAgainInAccount'), section.i18n('checkPasswordsAgainInAccount'),
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment