blob: 2efbc026bd2c6e83fff1e99df8b85e339d5a08fc [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/webui/settings/safety_check_handler.h"
#include <memory>
#include <string>
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/i18n/number_formatting.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/user_metrics.h"
#include "base/metrics/user_metrics_action.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/extensions/api/passwords_private/passwords_private_delegate_factory.h"
#include "chrome/browser/password_manager/bulk_leak_check_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/webui/version/version_ui.h"
#include "chrome/common/channel_info.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/extensions/api/passwords_private.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
#include "chrome/grit/branded_strings.h"
#include "chrome/grit/generated_resources.h"
#include "components/prefs/pref_service.h"
#include "components/safe_browsing/core/common/safe_browsing_prefs.h"
#include "components/strings/grit/components_strings.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/storage_partition.h"
#include "extensions/browser/blocklist_extension_prefs.h"
#include "extensions/browser/extension_prefs_factory.h"
#include "extensions/browser/extension_system.h"
#include "extensions/common/extension_id.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/strings/grit/ui_strings.h"
#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "ui/chromeos/devicetype_utils.h"
#endif
namespace {
// Constants for communication with JS.
constexpr char kParentEvent[] = "safety-check-parent-status-changed";
constexpr char kUpdatesEvent[] = "safety-check-updates-status-changed";
constexpr char kPasswordsEvent[] = "safety-check-passwords-status-changed";
constexpr char kSafeBrowsingEvent[] =
"safety-check-safe-browsing-status-changed";
constexpr char kExtensionsEvent[] = "safety-check-extensions-status-changed";
constexpr char kPerformSafetyCheck[] = "performSafetyCheck";
constexpr char kGetParentRanDisplayString[] = "getSafetyCheckRanDisplayString";
constexpr char kNewState[] = "newState";
constexpr char kDisplayString[] = "displayString";
// Converts the VersionUpdater::Status to the UpdateStatus enum to be passed
// to the safety check frontend. Note: if the VersionUpdater::Status gets
// changed, this will fail to compile. That is done intentionally to ensure
// that the states of the safety check are always in sync with the
// VersionUpdater ones.
SafetyCheckHandler::UpdateStatus ConvertToUpdateStatus(
VersionUpdater::Status status) {
switch (status) {
case VersionUpdater::CHECKING:
return SafetyCheckHandler::UpdateStatus::kChecking;
case VersionUpdater::UPDATED:
return SafetyCheckHandler::UpdateStatus::kUpdated;
case VersionUpdater::UPDATING:
return SafetyCheckHandler::UpdateStatus::kUpdating;
case VersionUpdater::DEFERRED:
case VersionUpdater::NEED_PERMISSION_TO_UPDATE:
case VersionUpdater::NEARLY_UPDATED:
return SafetyCheckHandler::UpdateStatus::kRelaunch;
case VersionUpdater::DISABLED_BY_ADMIN:
return SafetyCheckHandler::UpdateStatus::kDisabledByAdmin;
case VersionUpdater::UPDATE_TO_ROLLBACK_VERSION_DISALLOWED:
return SafetyCheckHandler::UpdateStatus::
kUpdateToRollbackVersionDisallowed;
// The disabled state can only be returned on non Chrome-branded browsers.
case VersionUpdater::DISABLED:
return SafetyCheckHandler::UpdateStatus::kUnknown;
case VersionUpdater::FAILED:
case VersionUpdater::FAILED_HTTP:
case VersionUpdater::FAILED_DOWNLOAD:
case VersionUpdater::FAILED_CONNECTION_TYPE_DISALLOWED:
return SafetyCheckHandler::UpdateStatus::kFailed;
case VersionUpdater::FAILED_OFFLINE:
return SafetyCheckHandler::UpdateStatus::kFailedOffline;
}
}
bool IsUnmutedCompromisedCredential(
const extensions::api::passwords_private::PasswordUiEntry& entry) {
DCHECK(entry.compromised_info);
if (entry.compromised_info->is_muted)
return false;
return base::ranges::any_of(
entry.compromised_info->compromise_types, [](auto type) {
return type == extensions::api::passwords_private::CompromiseType::
kLeaked ||
type ==
extensions::api::passwords_private::CompromiseType::kPhished;
});
}
bool IsCredentialWeak(
const extensions::api::passwords_private::PasswordUiEntry& entry) {
DCHECK(entry.compromised_info);
return base::ranges::any_of(
entry.compromised_info->compromise_types, [](auto type) {
return type ==
extensions::api::passwords_private::CompromiseType::kWeak;
});
}
bool IsCredentialReused(
const extensions::api::passwords_private::PasswordUiEntry& entry) {
DCHECK(entry.compromised_info);
return base::ranges::any_of(
entry.compromised_info->compromise_types, [](auto type) {
return type ==
extensions::api::passwords_private::CompromiseType::kReused;
});
}
} // namespace
base::Time TimestampDelegate::GetSystemTime() {
return base::Time::Now();
}
SafetyCheckHandler::SafetyCheckHandler() = default;
SafetyCheckHandler::~SafetyCheckHandler() = default;
void SafetyCheckHandler::SendSafetyCheckStartedWebUiUpdates() {
AllowJavascript();
// Ensure necessary delegates and helpers exist.
if (!timestamp_delegate_) {
timestamp_delegate_ = std::make_unique<TimestampDelegate>();
}
DCHECK(timestamp_delegate_);
// Reset status of parent and children, which might have been set from a
// previous run of safety check.
parent_status_ = ParentStatus::kChecking;
update_status_ = UpdateStatus::kChecking;
passwords_status_ = PasswordsStatus::kChecking;
safe_browsing_status_ = SafeBrowsingStatus::kChecking;
extensions_status_ = ExtensionsStatus::kChecking;
// Update WebUi.
FireBasicSafetyCheckWebUiListener(kUpdatesEvent,
static_cast<int>(update_status_),
GetStringForUpdates(update_status_));
FireBasicSafetyCheckWebUiListener(
kPasswordsEvent, static_cast<int>(passwords_status_),
GetStringForPasswords(passwords_status_, Compromised(0), Weak(0),
Reused(0), Done(0), Total(0)));
FireBasicSafetyCheckWebUiListener(
kSafeBrowsingEvent, static_cast<int>(safe_browsing_status_),
GetStringForSafeBrowsing(safe_browsing_status_));
FireBasicSafetyCheckWebUiListener(
kExtensionsEvent, static_cast<int>(extensions_status_),
GetStringForExtensions(extensions_status_, Blocklisted(0),
ReenabledUser(0), ReenabledAdmin(0)));
// Parent update is last as it reveals the children elements.
FireBasicSafetyCheckWebUiListener(kParentEvent,
static_cast<int>(parent_status_),
GetStringForParent(parent_status_));
}
void SafetyCheckHandler::PerformSafetyCheck() {
// Checks common to desktop, Android, and iOS are handled by
// safety_check::SafetyCheck.
safe_browsing_status_ =
safety_check::CheckSafeBrowsing(Profile::FromWebUI(web_ui())->GetPrefs());
if (safe_browsing_status_ != SafeBrowsingStatus::kChecking) {
base::UmaHistogramEnumeration("Settings.SafetyCheck.SafeBrowsingResult",
safe_browsing_status_);
}
FireBasicSafetyCheckWebUiListener(
kSafeBrowsingEvent, static_cast<int>(safe_browsing_status_),
GetStringForSafeBrowsing(safe_browsing_status_));
if (!version_updater_) {
version_updater_ = VersionUpdater::Create(web_ui()->GetWebContents());
}
DCHECK(version_updater_);
if (!update_helper_) {
update_helper_ = std::make_unique<safety_check::UpdateCheckHelper>(
Profile::FromWebUI(web_ui())
->GetDefaultStoragePartition()
->GetURLLoaderFactoryForBrowserProcess());
}
DCHECK(update_helper_);
CheckUpdates();
if (!leak_service_) {
leak_service_ = BulkLeakCheckServiceFactory::GetForProfile(
Profile::FromWebUI(web_ui()));
}
DCHECK(leak_service_);
if (!passwords_delegate_) {
passwords_delegate_ =
extensions::PasswordsPrivateDelegateFactory::GetForBrowserContext(
Profile::FromWebUI(web_ui()), true);
}
DCHECK(passwords_delegate_);
if (!insecure_credentials_manager_) {
insecure_credentials_manager_ =
passwords_delegate_->GetInsecureCredentialsManager();
}
DCHECK(insecure_credentials_manager_);
CheckPasswords();
if (!extension_prefs_) {
extension_prefs_ = extensions::ExtensionPrefsFactory::GetForBrowserContext(
Profile::FromWebUI(web_ui()));
}
DCHECK(extension_prefs_);
if (!extension_service_) {
extension_service_ =
extensions::ExtensionSystem::Get(Profile::FromWebUI(web_ui()))
->extension_service();
}
DCHECK(extension_service_);
CheckExtensions();
}
SafetyCheckHandler::SafetyCheckHandler(
std::unique_ptr<safety_check::UpdateCheckHelper> update_helper,
std::unique_ptr<VersionUpdater> version_updater,
password_manager::BulkLeakCheckService* leak_service,
extensions::PasswordsPrivateDelegate* passwords_delegate,
extensions::ExtensionPrefs* extension_prefs,
extensions::ExtensionServiceInterface* extension_service,
std::unique_ptr<TimestampDelegate> timestamp_delegate)
: update_helper_(std::move(update_helper)),
version_updater_(std::move(version_updater)),
leak_service_(leak_service),
passwords_delegate_(passwords_delegate),
extension_prefs_(extension_prefs),
extension_service_(extension_service),
timestamp_delegate_(std::move(timestamp_delegate)) {}
void SafetyCheckHandler::HandlePerformSafetyCheck(
const base::Value::List& args) {
SendSafetyCheckStartedWebUiUpdates();
// Run safety check after a delay. This ensures that the "running" state is
// visible to users for each safety check child, even if a child would
// otherwise complete in an instant.
base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&SafetyCheckHandler::PerformSafetyCheck,
weak_ptr_factory_.GetWeakPtr()),
base::Seconds(1));
}
void SafetyCheckHandler::HandleGetParentRanDisplayString(
const base::Value::List& args) {
const base::Value& callback_id = args[0];
// String update for the parent.
ResolveJavascriptCallback(
callback_id,
base::Value(GetStringForParentRan(safety_check_completion_time_)));
}
void SafetyCheckHandler::CheckUpdates() {
version_updater_->CheckForUpdate(
base::BindRepeating(&SafetyCheckHandler::OnVersionUpdaterResult,
weak_ptr_factory_.GetWeakPtr()),
VersionUpdater::PromoteCallback());
}
void SafetyCheckHandler::CheckPasswords() {
// Reset the tracking for callbacks with compromised passwords.
compromised_passwords_exist_ = false;
// Remove |this| as an existing observer for BulkLeakCheck if it is
// registered. This takes care of an edge case when safety check starts twice
// on the same page. Normally this should not happen, but if it does, the
// browser should not crash.
observed_leak_check_.Reset();
observed_leak_check_.Observe(leak_service_.get());
// Start observing the InsecureCredentialsManager.
observed_insecure_credentials_manager_.Reset();
observed_insecure_credentials_manager_.Observe(
insecure_credentials_manager_.get());
passwords_delegate_->StartPasswordCheck(base::BindOnce(
&SafetyCheckHandler::OnStateChanged, weak_ptr_factory_.GetWeakPtr()));
}
void SafetyCheckHandler::CheckExtensions() {
int blocklisted = 0;
int reenabled_by_user = 0;
int reenabled_by_admin = 0;
for (const auto& extension_id : extension_prefs_->GetExtensions()) {
extensions::BitMapBlocklistState state =
extensions::blocklist_prefs::GetExtensionBlocklistState(
extension_id, extension_prefs_);
if (state == extensions::BitMapBlocklistState::NOT_BLOCKLISTED) {
continue;
}
++blocklisted;
if (!extension_service_->IsExtensionEnabled(extension_id)) {
continue;
}
if (extension_service_->UserCanDisableInstalledExtension(extension_id)) {
++reenabled_by_user;
} else {
++reenabled_by_admin;
}
}
if (blocklisted == 0) {
OnExtensionsCheckResult(ExtensionsStatus::kNoneBlocklisted, Blocklisted(0),
ReenabledUser(0), ReenabledAdmin(0));
} else if (reenabled_by_user == 0 && reenabled_by_admin == 0) {
OnExtensionsCheckResult(ExtensionsStatus::kBlocklistedAllDisabled,
Blocklisted(blocklisted), ReenabledUser(0),
ReenabledAdmin(0));
} else if (reenabled_by_user > 0 && reenabled_by_admin == 0) {
OnExtensionsCheckResult(ExtensionsStatus::kBlocklistedReenabledAllByUser,
Blocklisted(blocklisted),
ReenabledUser(reenabled_by_user),
ReenabledAdmin(0));
} else if (reenabled_by_admin > 0 && reenabled_by_user == 0) {
OnExtensionsCheckResult(ExtensionsStatus::kBlocklistedReenabledAllByAdmin,
Blocklisted(blocklisted), ReenabledUser(0),
ReenabledAdmin(reenabled_by_admin));
} else {
OnExtensionsCheckResult(ExtensionsStatus::kBlocklistedReenabledSomeByUser,
Blocklisted(blocklisted),
ReenabledUser(reenabled_by_user),
ReenabledAdmin(reenabled_by_admin));
}
}
void SafetyCheckHandler::OnUpdateCheckResult(UpdateStatus status) {
update_status_ = status;
if (update_status_ != UpdateStatus::kChecking) {
base::UmaHistogramEnumeration("Settings.SafetyCheck.UpdatesResult",
update_status_);
}
// TODO(crbug.com/40127188): Since the UNKNOWN state is not present in JS in
// M83, use FAILED_OFFLINE, which uses the same icon.
FireBasicSafetyCheckWebUiListener(
kUpdatesEvent,
static_cast<int>(update_status_ != UpdateStatus::kUnknown
? update_status_
: UpdateStatus::kFailedOffline),
GetStringForUpdates(update_status_));
CompleteParentIfChildrenCompleted();
}
void SafetyCheckHandler::OnPasswordsCheckResult(PasswordsStatus status,
Compromised compromised,
Weak weak,
Reused reused,
Done done,
Total total) {
base::Value::Dict event;
event.Set(kNewState, static_cast<int>(status));
event.Set(kDisplayString, GetStringForPasswords(status, compromised, weak,
reused, done, total));
FireWebUIListener(kPasswordsEvent, event);
if (status != PasswordsStatus::kChecking) {
base::UmaHistogramEnumeration("Settings.SafetyCheck.PasswordsResult2",
status);
}
passwords_status_ = status;
CompleteParentIfChildrenCompleted();
}
void SafetyCheckHandler::OnExtensionsCheckResult(
ExtensionsStatus status,
Blocklisted blocklisted,
ReenabledUser reenabled_user,
ReenabledAdmin reenabled_admin) {
base::Value::Dict event;
event.Set(kNewState, static_cast<int>(status));
event.Set(kDisplayString,
GetStringForExtensions(status, Blocklisted(blocklisted),
reenabled_user, reenabled_admin));
FireWebUIListener(kExtensionsEvent, event);
extensions_status_ = status;
CompleteParentIfChildrenCompleted();
}
std::u16string SafetyCheckHandler::GetStringForParent(ParentStatus status) {
switch (status) {
case ParentStatus::kBefore:
return l10n_util::GetStringUTF16(
IDS_SETTINGS_SAFETY_CHECK_PARENT_PRIMARY_LABEL_BEFORE);
case ParentStatus::kChecking:
return l10n_util::GetStringUTF16(IDS_SETTINGS_SAFETY_CHECK_RUNNING);
case ParentStatus::kAfter:
return l10n_util::GetStringUTF16(
IDS_SETTINGS_SAFETY_CHECK_PARENT_PRIMARY_LABEL_AFTER);
}
}
std::u16string SafetyCheckHandler::GetStringForUpdates(UpdateStatus status) {
switch (status) {
case UpdateStatus::kChecking:
return u"";
case UpdateStatus::kUpdated:
#if BUILDFLAG(IS_CHROMEOS_ASH)
return ui::SubstituteChromeOSDeviceType(IDS_SETTINGS_UPGRADE_UP_TO_DATE);
#else
return l10n_util::GetStringUTF16(IDS_SETTINGS_UPGRADE_UP_TO_DATE);
#endif
case UpdateStatus::kUpdating:
return l10n_util::GetStringUTF16(IDS_SETTINGS_UPGRADE_UPDATING);
case UpdateStatus::kRelaunch:
return l10n_util::GetStringUTF16(
IDS_SETTINGS_UPGRADE_SUCCESSFUL_RELAUNCH);
case UpdateStatus::kDisabledByAdmin:
return l10n_util::GetStringFUTF16(
IDS_SETTINGS_SAFETY_CHECK_UPDATES_DISABLED_BY_ADMIN,
chrome::kWhoIsMyAdministratorHelpURL);
// This status is only used in ChromeOS.
case UpdateStatus::kUpdateToRollbackVersionDisallowed:
return l10n_util::GetStringUTF16(
IDS_SETTINGS_UPDATE_TO_ROLLBACK_VERSION_DISALLOWED);
case UpdateStatus::kFailedOffline:
return l10n_util::GetStringUTF16(
IDS_SETTINGS_SAFETY_CHECK_UPDATES_FAILED_OFFLINE);
case UpdateStatus::kFailed:
return l10n_util::GetStringFUTF16(
IDS_SETTINGS_SAFETY_CHECK_UPDATES_FAILED,
chrome::kChromeFixUpdateProblems);
case UpdateStatus::kUnknown:
return VersionUI::GetAnnotatedVersionStringForUi();
// This state is only used on Android for recording metrics. This codepath
// is unreachable.
case UpdateStatus::kOutdated:
return u"";
}
}
std::u16string SafetyCheckHandler::GetStringForSafeBrowsing(
SafeBrowsingStatus status) {
switch (status) {
case SafeBrowsingStatus::kChecking:
return u"";
case SafeBrowsingStatus::kEnabled:
case SafeBrowsingStatus::kEnabledStandard:
return l10n_util::GetStringUTF16(
IDS_SETTINGS_SAFETY_CHECK_SAFE_BROWSING_ENABLED_STANDARD);
case SafeBrowsingStatus::kEnabledEnhanced:
return l10n_util::GetStringUTF16(
IDS_SETTINGS_SAFETY_CHECK_SAFE_BROWSING_ENABLED_ENHANCED);
case SafeBrowsingStatus::kDisabled:
return l10n_util::GetStringUTF16(
IDS_SETTINGS_SAFETY_CHECK_SAFE_BROWSING_DISABLED);
case SafeBrowsingStatus::kDisabledByAdmin:
return l10n_util::GetStringFUTF16(
IDS_SETTINGS_SAFETY_CHECK_SAFE_BROWSING_DISABLED_BY_ADMIN,
chrome::kWhoIsMyAdministratorHelpURL);
case SafeBrowsingStatus::kDisabledByExtension:
return l10n_util::GetStringUTF16(
IDS_SETTINGS_SAFETY_CHECK_SAFE_BROWSING_DISABLED_BY_EXTENSION);
case SafeBrowsingStatus::kEnabledStandardAvailableEnhanced:
return l10n_util::GetStringUTF16(
IDS_SETTINGS_SAFETY_CHECK_SAFE_BROWSING_ENABLED_STANDARD_AVAILABLE_ENHANCED);
}
}
std::u16string SafetyCheckHandler::GetStringForPasswords(
PasswordsStatus status,
Compromised compromised,
Weak weak,
Reused reused,
Done done,
Total total) {
switch (status) {
case PasswordsStatus::kChecking: {
// Unable to get progress for some reason.
if (total.value() == 0) {
return u"";
}
return l10n_util::GetStringFUTF16(IDS_SETTINGS_CHECK_PASSWORDS_PROGRESS,
base::FormatNumber(done.value()),
base::FormatNumber(total.value()));
}
case PasswordsStatus::kSafe:
return l10n_util::GetPluralStringFUTF16(
IDS_SETTINGS_COMPROMISED_PASSWORDS_COUNT, 0);
case PasswordsStatus::kCompromisedExist:
case PasswordsStatus::kWeakPasswordsExist:
case PasswordsStatus::kReusedPasswordsExist:
case PasswordsStatus::kMutedCompromisedExist: {
// Keep the order since compromised issues should come first, then weak,
// then reused.
std::vector<std::u16string> issues;
if (compromised.value()) {
issues.push_back(l10n_util::GetPluralStringFUTF16(
IDS_SETTINGS_COMPROMISED_PASSWORDS_COUNT_SHORT,
compromised.value()));
}
if (weak.value()) {
issues.push_back(l10n_util::GetPluralStringFUTF16(
IDS_SETTINGS_WEAK_PASSWORDS_COUNT_SHORT, weak.value()));
}
if (reused.value()) {
issues.push_back(l10n_util::GetPluralStringFUTF16(
IDS_SETTINGS_REUSED_PASSWORDS_COUNT_SHORT, reused.value()));
}
CHECK(!issues.empty());
if (issues.size() == 1) {
return issues[0];
}
if (issues.size() == 2) {
return l10n_util::GetStringFUTF16(IDS_CONCAT_TWO_STRINGS_WITH_COMMA,
issues[0], issues[1]);
}
return l10n_util::GetStringFUTF16(IDS_CONCAT_THREE_STRINGS_WITH_COMMA,
issues[0], issues[1], issues[2]);
}
case PasswordsStatus::kOffline:
return l10n_util::GetStringUTF16(
IDS_SETTINGS_CHECK_PASSWORDS_ERROR_OFFLINE);
case PasswordsStatus::kNoPasswords:
return l10n_util::GetStringUTF16(
IDS_SETTINGS_CHECK_PASSWORDS_ERROR_NO_PASSWORDS);
case PasswordsStatus::kSignedOut:
return l10n_util::GetStringUTF16(
IDS_SETTINGS_SAFETY_CHECK_PASSWORDS_SIGNED_OUT);
case PasswordsStatus::kQuotaLimit:
return l10n_util::GetStringUTF16(
IDS_SETTINGS_CHECK_PASSWORDS_ERROR_QUOTA_LIMIT);
case PasswordsStatus::kError:
return l10n_util::GetStringUTF16(
IDS_SETTINGS_CHECK_PASSWORDS_ERROR_GENERIC);
case PasswordsStatus::kFeatureUnavailable:
return l10n_util::GetStringUTF16(
IDS_SETTINGS_SAFETY_CHECK_PASSWORDS_FEATURE_UNAVAILABLE);
}
}
std::u16string SafetyCheckHandler::GetStringForExtensions(
ExtensionsStatus status,
Blocklisted blocklisted,
ReenabledUser reenabled_user,
ReenabledAdmin reenabled_admin) {
switch (status) {
case ExtensionsStatus::kChecking:
return u"";
case ExtensionsStatus::kNoneBlocklisted:
return l10n_util::GetStringUTF16(
IDS_SETTINGS_SAFETY_CHECK_EXTENSIONS_SAFE);
case ExtensionsStatus::kBlocklistedAllDisabled:
return l10n_util::GetPluralStringFUTF16(
IDS_SETTINGS_SAFETY_CHECK_EXTENSIONS_BLOCKLISTED_OFF,
blocklisted.value());
case ExtensionsStatus::kBlocklistedReenabledAllByUser:
return l10n_util::GetPluralStringFUTF16(
IDS_SETTINGS_SAFETY_CHECK_EXTENSIONS_BLOCKLISTED_ON_USER,
reenabled_user.value());
case ExtensionsStatus::kBlocklistedReenabledSomeByUser:
return l10n_util::GetStringFUTF16(
IDS_CONCAT_TWO_STRINGS_WITH_PERIODS,
l10n_util::GetPluralStringFUTF16(
IDS_SETTINGS_SAFETY_CHECK_EXTENSIONS_BLOCKLISTED_ON_USER,
reenabled_user.value()),
l10n_util::GetPluralStringFUTF16(
IDS_SETTINGS_SAFETY_CHECK_EXTENSIONS_BLOCKLISTED_ON_ADMIN,
reenabled_admin.value()));
case ExtensionsStatus::kBlocklistedReenabledAllByAdmin:
return l10n_util::GetPluralStringFUTF16(
IDS_SETTINGS_SAFETY_CHECK_EXTENSIONS_BLOCKLISTED_ON_ADMIN,
reenabled_admin.value());
}
}
std::u16string SafetyCheckHandler::GetStringForTimePassed(
base::Time completion_timestamp,
base::Time system_time,
int less_than_one_minute_ago_message_id,
int minutes_ago_message_id,
int hours_ago_message_id,
int yesterday_message_id,
int days_ago_message_id) {
base::Time::Exploded completion_time_exploded;
completion_timestamp.LocalExplode(&completion_time_exploded);
base::Time::Exploded system_time_exploded;
system_time.LocalExplode(&system_time_exploded);
const base::Time time_yesterday = system_time - base::Days(1);
base::Time::Exploded time_yesterday_exploded;
time_yesterday.LocalExplode(&time_yesterday_exploded);
const auto time_diff = system_time - completion_timestamp;
if (completion_time_exploded.year == system_time_exploded.year &&
completion_time_exploded.month == system_time_exploded.month &&
completion_time_exploded.day_of_month ==
system_time_exploded.day_of_month) {
// The timestamp is today.
const int time_diff_in_mins = time_diff.InMinutes();
if (time_diff_in_mins == 0) {
return l10n_util::GetStringUTF16(less_than_one_minute_ago_message_id);
} else if (time_diff_in_mins < 60) {
return l10n_util::GetPluralStringFUTF16(minutes_ago_message_id,
time_diff_in_mins);
} else {
return l10n_util::GetPluralStringFUTF16(hours_ago_message_id,
time_diff_in_mins / 60);
}
} else if (completion_time_exploded.year == time_yesterday_exploded.year &&
completion_time_exploded.month == time_yesterday_exploded.month &&
completion_time_exploded.day_of_month ==
time_yesterday_exploded.day_of_month) {
// The timestamp was yesterday.
return l10n_util::GetStringUTF16(yesterday_message_id);
} else {
// The timestamp is longer ago than yesterday.
// TODO(crbug.com/40103878): While a minor issue, this is not be the ideal
// way to calculate the days passed since the timestamp. For example,
// <48 h might still be 2 days ago.
const int time_diff_in_days = time_diff.InDays();
return l10n_util::GetPluralStringFUTF16(days_ago_message_id,
time_diff_in_days);
}
}
std::u16string SafetyCheckHandler::GetStringForParentRan(
base::Time safety_check_completion_time,
base::Time system_time) {
return SafetyCheckHandler::GetStringForTimePassed(
safety_check_completion_time, system_time,
IDS_SETTINGS_SAFETY_CHECK_PARENT_PRIMARY_LABEL_AFTER,
IDS_SETTINGS_SAFETY_CHECK_PARENT_PRIMARY_LABEL_AFTER_MINS,
IDS_SETTINGS_SAFETY_CHECK_PARENT_PRIMARY_LABEL_AFTER_HOURS,
IDS_SETTINGS_SAFETY_CHECK_PARENT_PRIMARY_LABEL_AFTER_YESTERDAY,
IDS_SETTINGS_SAFETY_CHECK_PARENT_PRIMARY_LABEL_AFTER_DAYS);
}
std::u16string SafetyCheckHandler::GetStringForParentRan(
base::Time safety_check_completion_time) {
return SafetyCheckHandler::GetStringForParentRan(safety_check_completion_time,
base::Time::Now());
}
void SafetyCheckHandler::DetermineIfOfflineOrError(bool connected) {
OnUpdateCheckResult(connected ? UpdateStatus::kFailed
: UpdateStatus::kFailedOffline);
}
void SafetyCheckHandler::DetermineIfNoPasswordsOrSafe(
const std::vector<extensions::api::passwords_private::PasswordUiEntry>&
passwords) {
OnPasswordsCheckResult(passwords.empty() ? PasswordsStatus::kNoPasswords
: PasswordsStatus::kSafe,
Compromised(0), Weak(0), Reused(0), Done(0), Total(0));
}
void SafetyCheckHandler::UpdatePasswordsResultOnCheckIdle() {
auto insecure_credentials = passwords_delegate_->GetInsecureCredentials();
size_t num_compromised = base::ranges::count_if(
insecure_credentials, &IsUnmutedCompromisedCredential);
size_t num_weak =
base::ranges::count_if(insecure_credentials, &IsCredentialWeak);
size_t num_reused =
base::ranges::count_if(insecure_credentials, &IsCredentialReused);
if (num_compromised > 0) {
// At least one compromised password. Treat as compromises.
OnPasswordsCheckResult(PasswordsStatus::kCompromisedExist,
Compromised(num_compromised), Weak(num_weak),
Reused(num_reused), Done(0), Total(0));
} else if (num_weak > 0) {
// No compromised but weak passwords. Treat as weak passwords only.
OnPasswordsCheckResult(PasswordsStatus::kWeakPasswordsExist,
Compromised(num_compromised), Weak(num_weak),
Reused(num_reused), Done(0), Total(0));
} else if (num_reused > 0) {
// No weak or compromised but reused passwords.
OnPasswordsCheckResult(PasswordsStatus::kReusedPasswordsExist,
Compromised(num_compromised), Weak(num_weak),
Reused(num_reused), Done(0), Total(0));
} else {
// If there are no |OnCredentialDone| callbacks with is_leaked = true, no
// need to wait for InsecureCredentialsManager callbacks any longer, since
// there should be none for the current password check.
if (!compromised_passwords_exist_) {
observed_insecure_credentials_manager_.Reset();
}
passwords_delegate_->GetSavedPasswordsList(
base::BindOnce(&SafetyCheckHandler::DetermineIfNoPasswordsOrSafe,
base::Unretained(this)));
}
}
void SafetyCheckHandler::OnVersionUpdaterResult(VersionUpdater::Status status,
int progress,
bool rollback,
bool powerwash,
const std::string& version,
int64_t update_size,
const std::u16string& message) {
if (status == VersionUpdater::FAILED) {
update_helper_->CheckConnectivity(
base::BindOnce(&SafetyCheckHandler::DetermineIfOfflineOrError,
base::Unretained(this)));
return;
}
OnUpdateCheckResult(ConvertToUpdateStatus(status));
}
void SafetyCheckHandler::OnStateChanged(
password_manager::BulkLeakCheckService::State state) {
using password_manager::BulkLeakCheckService;
switch (state) {
case BulkLeakCheckService::State::kIdle:
case BulkLeakCheckService::State::kCanceled: {
UpdatePasswordsResultOnCheckIdle();
observed_leak_check_.Reset();
return;
}
case BulkLeakCheckService::State::kRunning:
OnPasswordsCheckResult(PasswordsStatus::kChecking, Compromised(0),
Weak(0), Reused(0), Done(0), Total(0));
// Non-terminal state, so nothing else needs to be done.
return;
case BulkLeakCheckService::State::kSignedOut:
OnPasswordsCheckResult(PasswordsStatus::kSignedOut, Compromised(0),
Weak(0), Reused(0), Done(0), Total(0));
break;
case BulkLeakCheckService::State::kNetworkError:
OnPasswordsCheckResult(PasswordsStatus::kOffline, Compromised(0), Weak(0),
Reused(0), Done(0), Total(0));
break;
case BulkLeakCheckService::State::kQuotaLimit:
OnPasswordsCheckResult(PasswordsStatus::kQuotaLimit, Compromised(0),
Weak(0), Reused(0), Done(0), Total(0));
break;
case BulkLeakCheckService::State::kTokenRequestFailure:
OnPasswordsCheckResult(PasswordsStatus::kFeatureUnavailable,
Compromised(0), Weak(0), Reused(0), Done(0),
Total(0));
break;
case BulkLeakCheckService::State::kHashingFailure:
case BulkLeakCheckService::State::kServiceError:
OnPasswordsCheckResult(PasswordsStatus::kError, Compromised(0), Weak(0),
Reused(0), Done(0), Total(0));
break;
}
// Stop observing the leak service and credentials manager in all non-idle
// states.
observed_leak_check_.Reset();
observed_insecure_credentials_manager_.Reset();
}
void SafetyCheckHandler::OnCredentialDone(
const password_manager::LeakCheckCredential& credential,
password_manager::IsLeaked is_leaked) {
// If a leaked credential is discovered, this is guaranteed to not be a safe
// state.
if (is_leaked) {
compromised_passwords_exist_ = true;
}
extensions::api::passwords_private::PasswordCheckStatus status =
passwords_delegate_->GetPasswordCheckStatus();
// Send progress updates only if the check is still running.
if (status.state ==
extensions::api::passwords_private::PasswordCheckState::kRunning &&
status.already_processed && status.remaining_in_queue) {
Done done = Done(*(status.already_processed));
Total total = Total(*(status.remaining_in_queue) + done.value());
OnPasswordsCheckResult(PasswordsStatus::kChecking, Compromised(0), Weak(0),
Reused(0), done, total);
}
}
void SafetyCheckHandler::OnInsecureCredentialsChanged() {
extensions::api::passwords_private::PasswordCheckStatus status =
passwords_delegate_->GetPasswordCheckStatus();
// Ignore the event, unless the password check is idle with no errors.
if (status.state !=
extensions::api::passwords_private::PasswordCheckState::kIdle) {
return;
}
UpdatePasswordsResultOnCheckIdle();
// Stop observing the manager to avoid dynamically updating the result.
observed_insecure_credentials_manager_.Reset();
}
void SafetyCheckHandler::OnJavascriptAllowed() {}
void SafetyCheckHandler::OnJavascriptDisallowed() {
// If the user refreshes the Settings tab in the delay between starting safety
// check and now, then the check should no longer be run. Invalidating the
// pointer prevents the callback from returning after the delay.
weak_ptr_factory_.InvalidateWeakPtrs();
// Remove |this| as an observer for BulkLeakCheck. This takes care of an edge
// case when the page is reloaded while the password check is in progress and
// another safety check is started. Otherwise |observed_leak_check_|
// automatically calls RemoveAll() on destruction.
observed_leak_check_.Reset();
// Remove |this| as an observer for InsecureCredentialsManager. This takes
// care of an edge case where an observation would happen when Javascript is
// already disabled. See crbug/1370719.
observed_insecure_credentials_manager_.Reset();
// Destroy the version updater to prevent getting a callback and firing a
// WebUI event, which would cause a crash.
version_updater_.reset();
}
void SafetyCheckHandler::RegisterMessages() {
// Usage of base::Unretained(this) is safe, because web_ui() owns `this` and
// won't release ownership until destruction.
web_ui()->RegisterMessageCallback(
kPerformSafetyCheck,
base::BindRepeating(&SafetyCheckHandler::HandlePerformSafetyCheck,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
kGetParentRanDisplayString,
base::BindRepeating(&SafetyCheckHandler::HandleGetParentRanDisplayString,
base::Unretained(this)));
}
void SafetyCheckHandler::CompleteParentIfChildrenCompleted() {
if (update_status_ == UpdateStatus::kChecking ||
passwords_status_ == PasswordsStatus::kChecking ||
safe_browsing_status_ == SafeBrowsingStatus::kChecking ||
extensions_status_ == ExtensionsStatus::kChecking) {
return;
}
// All children checks completed.
parent_status_ = ParentStatus::kAfter;
// Remember when safety check completed.
safety_check_completion_time_ = base::Time::Now();
// Update UI.
FireBasicSafetyCheckWebUiListener(kParentEvent,
static_cast<int>(parent_status_),
GetStringForParent(parent_status_));
}
void SafetyCheckHandler::FireBasicSafetyCheckWebUiListener(
const std::string& event_name,
int new_state,
const std::u16string& display_string) {
base::Value::Dict event;
event.Set(kNewState, new_state);
event.Set(kDisplayString, display_string);
FireWebUIListener(event_name, event);
}