| // Copyright 2018 The Chromium Authors. All rights reserved. |
| // 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.h" |
| |
| #include "base/bind.h" |
| #include "base/command_line.h" |
| #include "base/feature_list.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "chrome/browser/profiles/profile.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/signin/public/identity_manager/account_info.h" |
| #include "components/signin/public/identity_manager/accounts_mutator.h" |
| #include "components/signin/public/identity_manager/consent_level.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); |
| |
| const char kForceTreatUserAsAdvancedProtection[] = |
| "safe-browsing-treat-user-as-advanced-protection"; |
| |
| } // namespace |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // AdvancedProtectionStatusManager |
| //////////////////////////////////////////////////////////////////////////////// |
| AdvancedProtectionStatusManager::AdvancedProtectionStatusManager( |
| PrefService* pref_service, |
| signin::IdentityManager* identity_manager) |
| : AdvancedProtectionStatusManager(pref_service, |
| identity_manager, |
| kMinimumRefreshDelay) {} |
| |
| void AdvancedProtectionStatusManager::Initialize() { |
| SubscribeToSigninEvents(); |
| } |
| |
| void AdvancedProtectionStatusManager::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()) |
| return; |
| |
| is_under_advanced_protection_ = core_info.is_under_advanced_protection; |
| |
| 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, |
| &AdvancedProtectionStatusManager::RefreshAdvancedProtectionStatus); |
| } |
| } |
| |
| void AdvancedProtectionStatusManager::Shutdown() { |
| CancelFutureRefresh(); |
| UnsubscribeFromSigninEvents(); |
| } |
| |
| AdvancedProtectionStatusManager::~AdvancedProtectionStatusManager() {} |
| |
| void AdvancedProtectionStatusManager::SubscribeToSigninEvents() { |
| identity_manager_->AddObserver(this); |
| } |
| |
| void AdvancedProtectionStatusManager::UnsubscribeFromSigninEvents() { |
| identity_manager_->RemoveObserver(this); |
| } |
| |
| bool AdvancedProtectionStatusManager::IsRefreshScheduled() { |
| return timer_.IsRunning(); |
| } |
| |
| void AdvancedProtectionStatusManager::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 AdvancedProtectionStatusManager::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) { |
| is_under_advanced_protection_ = false; |
| OnAdvancedProtectionDisabled(); |
| } |
| } |
| |
| void AdvancedProtectionStatusManager::OnPrimaryAccountChanged( |
| const signin::PrimaryAccountChangeEvent& event) { |
| switch (event.GetEventTypeFor(signin::ConsentLevel::kSignin)) { |
| case signin::PrimaryAccountChangeEvent::Type::kSet: { |
| // TODO(crbug.com/926204): 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 AdvancedProtectionStatusManager::OnAdvancedProtectionEnabled() { |
| is_under_advanced_protection_ = true; |
| UpdateLastRefreshTime(); |
| ScheduleNextRefresh(); |
| } |
| |
| void AdvancedProtectionStatusManager::OnAdvancedProtectionDisabled() { |
| is_under_advanced_protection_ = false; |
| UpdateLastRefreshTime(); |
| CancelFutureRefresh(); |
| } |
| |
| void AdvancedProtectionStatusManager::OnAccessTokenFetchComplete( |
| CoreAccountId account_id, |
| GoogleServiceAuthError error, |
| signin::AccessTokenInfo token_info) { |
| DCHECK(access_token_fetcher_); |
| |
| if (is_under_advanced_protection_) { |
| // Those already known to be under AP should have much lower error rates. |
| UMA_HISTOGRAM_ENUMERATION( |
| "SafeBrowsing.AdvancedProtection.APTokenFetchStatus", error.state(), |
| GoogleServiceAuthError::NUM_STATES); |
| } |
| |
| if (error.state() == GoogleServiceAuthError::NONE) |
| OnGetIDToken(account_id, token_info.id_token); |
| |
| UMA_HISTOGRAM_ENUMERATION("SafeBrowsing.AdvancedProtection.TokenFetchStatus", |
| error.state(), GoogleServiceAuthError::NUM_STATES); |
| |
| access_token_fetcher_.reset(); |
| |
| // If failure is transient, we'll retry in 5 minutes. |
| if (error.IsTransientError()) { |
| timer_.Start( |
| FROM_HERE, kRetryDelay, this, |
| &AdvancedProtectionStatusManager::RefreshAdvancedProtectionStatus); |
| } |
| } |
| |
| void AdvancedProtectionStatusManager::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. |
| signin::ScopeSet scopes; |
| scopes.insert(GaiaConstants::kOAuth1LoginScope); |
| |
| access_token_fetcher_ = |
| std::make_unique<signin::PrimaryAccountAccessTokenFetcher>( |
| "advanced_protection_status_manager", identity_manager_, scopes, |
| base::BindOnce( |
| &AdvancedProtectionStatusManager::OnAccessTokenFetchComplete, |
| base::Unretained(this), unconsented_primary_account_id), |
| signin::PrimaryAccountAccessTokenFetcher::Mode::kImmediate); |
| } |
| |
| void AdvancedProtectionStatusManager::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, |
| &AdvancedProtectionStatusManager::RefreshAdvancedProtectionStatus); |
| } |
| void AdvancedProtectionStatusManager::CancelFutureRefresh() { |
| if (timer_.IsRunning()) |
| timer_.Stop(); |
| } |
| |
| void AdvancedProtectionStatusManager::UpdateLastRefreshTime() { |
| last_refreshed_ = base::Time::Now(); |
| pref_service_->SetInt64( |
| prefs::kAdvancedProtectionLastRefreshInUs, |
| last_refreshed_.ToDeltaSinceWindowsEpoch().InMicroseconds()); |
| } |
| |
| bool AdvancedProtectionStatusManager::IsUnderAdvancedProtection() const { |
| if (!pref_service_->GetBoolean(prefs::kAdvancedProtectionAllowed)) |
| return false; |
| |
| if (base::CommandLine::ForCurrentProcess()->HasSwitch( |
| kForceTreatUserAsAdvancedProtection)) |
| return true; |
| |
| return is_under_advanced_protection_; |
| } |
| |
| bool AdvancedProtectionStatusManager::IsUnconsentedPrimaryAccount( |
| const CoreAccountInfo& account_info) { |
| return !account_info.account_id.empty() && |
| account_info.account_id == GetUnconsentedPrimaryAccountId(); |
| } |
| |
| void AdvancedProtectionStatusManager::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(); |
| } |
| } |
| |
| AdvancedProtectionStatusManager::AdvancedProtectionStatusManager( |
| PrefService* pref_service, |
| signin::IdentityManager* identity_manager, |
| const base::TimeDelta& min_delay) |
| : pref_service_(pref_service), |
| identity_manager_(identity_manager), |
| is_under_advanced_protection_(false), |
| minimum_delay_(min_delay) { |
| DCHECK(identity_manager_); |
| DCHECK(pref_service_); |
| |
| Initialize(); |
| MaybeRefreshOnStartUp(); |
| } |
| |
| CoreAccountId AdvancedProtectionStatusManager::GetUnconsentedPrimaryAccountId() |
| const { |
| return identity_manager_ ? identity_manager_->GetPrimaryAccountId( |
| signin::ConsentLevel::kSignin) |
| : CoreAccountId(); |
| } |
| |
| void AdvancedProtectionStatusManager::SetAdvancedProtectionStatusForTesting( |
| bool enrolled) { |
| is_under_advanced_protection_ = enrolled; |
| } |
| |
| } // namespace safe_browsing |