blob: 8fc5da32bd47764122623ede846ed5b6c979a50a [file] [log] [blame]
// Copyright 2018 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/safe_browsing/advanced_protection_status_manager_desktop.h"
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/safe_browsing/advanced_protection_status_manager.h"
#include "chrome/browser/safe_browsing/advanced_protection_status_manager_factory.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "components/prefs/pref_service.h"
#include "components/safe_browsing/core/common/features.h"
#include "components/safe_browsing/core/common/safe_browsing_prefs.h"
#include "components/safe_browsing/core/common/safebrowsing_switches.h"
#include "components/signin/public/base/consent_level.h"
#include "components/signin/public/identity_manager/account_info.h"
#include "components/signin/public/identity_manager/accounts_mutator.h"
#include "components/signin/public/identity_manager/primary_account_access_token_fetcher.h"
#include "components/signin/public/identity_manager/scope_set.h"
#include "components/signin/public/identity_manager/tribool.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "google_apis/gaia/gaia_constants.h"
#include "google_apis/gaia/oauth2_id_token_decoder.h"
using content::BrowserThread;
namespace safe_browsing {
namespace {
const base::TimeDelta kRefreshAdvancedProtectionDelay = base::Days(1);
const base::TimeDelta kRetryDelay = base::Minutes(5);
const base::TimeDelta kMinimumRefreshDelay = base::Minutes(1);
void RecordStartupUma(bool is_under_advanced_protection) {
base::UmaHistogramBoolean("SafeBrowsing.Desktop.AdvancedProtection.Enabled",
is_under_advanced_protection);
}
} // namespace
////////////////////////////////////////////////////////////////////////////////
// AdvancedProtectionStatusManagerDesktop
////////////////////////////////////////////////////////////////////////////////
AdvancedProtectionStatusManagerDesktop::AdvancedProtectionStatusManagerDesktop(
PrefService* pref_service,
signin::IdentityManager* identity_manager)
: AdvancedProtectionStatusManagerDesktop(pref_service,
identity_manager,
kMinimumRefreshDelay) {}
void AdvancedProtectionStatusManagerDesktop::Initialize() {
SubscribeToSigninEvents();
}
void AdvancedProtectionStatusManagerDesktop::MaybeRefreshOnStartUp() {
// Retrieves advanced protection service status from primary account's info.
CoreAccountInfo core_info =
identity_manager_->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin);
if (core_info.account_id.empty()) {
RecordStartupUma(/*is_under_advanced_protection=*/false);
return;
}
is_under_advanced_protection_ = core_info.is_under_advanced_protection;
RecordStartupUma(is_under_advanced_protection_);
NotifyObserversStatusChanged();
if (pref_service_->HasPrefPath(prefs::kAdvancedProtectionLastRefreshInUs)) {
last_refreshed_ = base::Time::FromDeltaSinceWindowsEpoch(base::Microseconds(
pref_service_->GetInt64(prefs::kAdvancedProtectionLastRefreshInUs)));
if (is_under_advanced_protection_) {
ScheduleNextRefresh();
}
} else {
// User's advanced protection status is unknown, refresh in
// |minimum_delay_|.
timer_.Start(FROM_HERE, minimum_delay_, this,
&AdvancedProtectionStatusManagerDesktop::
RefreshAdvancedProtectionStatus);
}
}
void AdvancedProtectionStatusManagerDesktop::Shutdown() {
CancelFutureRefresh();
UnsubscribeFromSigninEvents();
}
AdvancedProtectionStatusManagerDesktop::
~AdvancedProtectionStatusManagerDesktop() = default;
bool AdvancedProtectionStatusManagerDesktop::IsUnderAdvancedProtection() const {
if (!pref_service_->GetBoolean(prefs::kAdvancedProtectionAllowed)) {
return false;
}
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kForceTreatUserAsAdvancedProtection)) {
return true;
}
return is_under_advanced_protection_;
}
void AdvancedProtectionStatusManagerDesktop::
SetAdvancedProtectionStatusForTesting(bool enrolled) {
is_under_advanced_protection_ = enrolled;
NotifyObserversStatusChanged();
}
void AdvancedProtectionStatusManagerDesktop::SubscribeToSigninEvents() {
identity_manager_->AddObserver(this);
}
void AdvancedProtectionStatusManagerDesktop::UnsubscribeFromSigninEvents() {
identity_manager_->RemoveObserver(this);
}
bool AdvancedProtectionStatusManagerDesktop::IsRefreshScheduled() {
return timer_.IsRunning();
}
void AdvancedProtectionStatusManagerDesktop::OnExtendedAccountInfoUpdated(
const AccountInfo& info) {
// Ignore update if the updated account is not the primary account.
if (!IsUnconsentedPrimaryAccount(info)) {
return;
}
if (info.is_under_advanced_protection) {
// User just enrolled into advanced protection.
OnAdvancedProtectionEnabled();
} else {
// User's no longer in advanced protection.
OnAdvancedProtectionDisabled();
}
}
void AdvancedProtectionStatusManagerDesktop::OnExtendedAccountInfoRemoved(
const AccountInfo& info) {
// If user signed out primary account, cancel refresh.
CoreAccountId unconsented_primary_account_id =
GetUnconsentedPrimaryAccountId();
if (!unconsented_primary_account_id.empty() &&
unconsented_primary_account_id == info.account_id) {
OnAdvancedProtectionDisabled();
}
}
void AdvancedProtectionStatusManagerDesktop::OnPrimaryAccountChanged(
const signin::PrimaryAccountChangeEvent& event) {
switch (event.GetEventTypeFor(signin::ConsentLevel::kSignin)) {
case signin::PrimaryAccountChangeEvent::Type::kSet: {
// TODO(crbug.com/41437854): remove IdentityManager ensures that primary
// account always has valid refresh token when it is set.
if (event.GetCurrentState()
.primary_account.is_under_advanced_protection) {
OnAdvancedProtectionEnabled();
} else {
OnAdvancedProtectionDisabled();
}
break;
}
case signin::PrimaryAccountChangeEvent::Type::kCleared:
OnAdvancedProtectionDisabled();
break;
case signin::PrimaryAccountChangeEvent::Type::kNone:
break;
}
}
void AdvancedProtectionStatusManagerDesktop::OnAdvancedProtectionEnabled() {
is_under_advanced_protection_ = true;
UpdateLastRefreshTime();
ScheduleNextRefresh();
NotifyObserversStatusChanged();
}
void AdvancedProtectionStatusManagerDesktop::OnAdvancedProtectionDisabled() {
is_under_advanced_protection_ = false;
UpdateLastRefreshTime();
CancelFutureRefresh();
NotifyObserversStatusChanged();
}
void AdvancedProtectionStatusManagerDesktop::OnAccessTokenFetchComplete(
CoreAccountId account_id,
GoogleServiceAuthError error,
signin::AccessTokenInfo token_info) {
DCHECK(access_token_fetcher_);
if (error.state() == GoogleServiceAuthError::NONE) {
OnGetIDToken(account_id, token_info.id_token);
}
access_token_fetcher_.reset();
// If failure is transient, we'll retry in 5 minutes.
if (error.IsTransientError()) {
timer_.Start(FROM_HERE, kRetryDelay, this,
&AdvancedProtectionStatusManagerDesktop::
RefreshAdvancedProtectionStatus);
}
}
void AdvancedProtectionStatusManagerDesktop::RefreshAdvancedProtectionStatus() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CoreAccountId unconsented_primary_account_id =
GetUnconsentedPrimaryAccountId();
if (!identity_manager_ || unconsented_primary_account_id.empty()) {
return;
}
// If there's already a request going on, do nothing.
if (access_token_fetcher_) {
return;
}
// Refresh OAuth access token. This class isn't actually interested in the
// access token itself, but the account's "under advanced protection" status
// can be determined from the "service flags" contained in the response.
// Note that the (quite powerful) `kOAuth1LoginScope` is required for the
// server to return the service flags.
signin::ScopeSet scopes;
scopes.insert(GaiaConstants::kOAuth1LoginScope);
access_token_fetcher_ = std::make_unique<
signin::PrimaryAccountAccessTokenFetcher>(
"advanced_protection_status_manager", identity_manager_, scopes,
base::BindOnce(
&AdvancedProtectionStatusManagerDesktop::OnAccessTokenFetchComplete,
base::Unretained(this), unconsented_primary_account_id),
signin::PrimaryAccountAccessTokenFetcher::Mode::kImmediate,
signin::ConsentLevel::kSignin);
}
void AdvancedProtectionStatusManagerDesktop::ScheduleNextRefresh() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CancelFutureRefresh();
base::Time now = base::Time::Now();
const base::TimeDelta time_since_last_refresh =
now > last_refreshed_ ? now - last_refreshed_ : base::TimeDelta::Max();
base::TimeDelta delay =
time_since_last_refresh > kRefreshAdvancedProtectionDelay
? minimum_delay_
: std::max(minimum_delay_,
kRefreshAdvancedProtectionDelay - time_since_last_refresh);
timer_.Start(
FROM_HERE, delay, this,
&AdvancedProtectionStatusManagerDesktop::RefreshAdvancedProtectionStatus);
}
void AdvancedProtectionStatusManagerDesktop::CancelFutureRefresh() {
if (timer_.IsRunning()) {
timer_.Stop();
}
}
void AdvancedProtectionStatusManagerDesktop::UpdateLastRefreshTime() {
last_refreshed_ = base::Time::Now();
pref_service_->SetInt64(
prefs::kAdvancedProtectionLastRefreshInUs,
last_refreshed_.ToDeltaSinceWindowsEpoch().InMicroseconds());
}
bool AdvancedProtectionStatusManagerDesktop::IsUnconsentedPrimaryAccount(
const CoreAccountInfo& account_info) {
return !account_info.account_id.empty() &&
account_info.account_id == GetUnconsentedPrimaryAccountId();
}
void AdvancedProtectionStatusManagerDesktop::OnGetIDToken(
const CoreAccountId& account_id,
const std::string& id_token) {
// Skips if the ID token is not for the primary account. Or user is no longer
// signed in.
CoreAccountId unconsented_primary_account_id =
GetUnconsentedPrimaryAccountId();
if (unconsented_primary_account_id.empty() ||
account_id != unconsented_primary_account_id) {
return;
}
gaia::TokenServiceFlags service_flags = gaia::ParseServiceFlags(id_token);
// If there's a change in advanced protection status, updates account info.
// This also triggers |OnAccountUpdated()|.
if (is_under_advanced_protection_ !=
service_flags.is_under_advanced_protection) {
identity_manager_->GetAccountsMutator()->UpdateAccountInfo(
GetUnconsentedPrimaryAccountId(),
/*is_child_account=*/signin::Tribool::kUnknown,
service_flags.is_under_advanced_protection ? signin::Tribool::kTrue
: signin::Tribool::kFalse);
} else if (service_flags.is_under_advanced_protection) {
OnAdvancedProtectionEnabled();
} else {
OnAdvancedProtectionDisabled();
}
}
AdvancedProtectionStatusManagerDesktop::AdvancedProtectionStatusManagerDesktop(
PrefService* pref_service,
signin::IdentityManager* identity_manager,
const base::TimeDelta& min_delay)
: pref_service_(pref_service),
identity_manager_(identity_manager),
minimum_delay_(min_delay) {
DCHECK(identity_manager_);
DCHECK(pref_service_);
Initialize();
MaybeRefreshOnStartUp();
}
CoreAccountId
AdvancedProtectionStatusManagerDesktop::GetUnconsentedPrimaryAccountId() const {
return identity_manager_ ? identity_manager_->GetPrimaryAccountId(
signin::ConsentLevel::kSignin)
: CoreAccountId();
}
} // namespace safe_browsing