diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2019-02-13 16:23:34 +0100 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2019-02-14 10:37:21 +0000 |
commit | 38a9a29f4f9436cace7f0e7abf9c586057df8a4e (patch) | |
tree | c4e8c458dc595bc0ddb435708fa2229edfd00bd4 /chromium/components/signin | |
parent | e684a3455bcc29a6e3e66a004e352dea4e1141e7 (diff) | |
download | qtwebengine-chromium-38a9a29f4f9436cace7f0e7abf9c586057df8a4e.tar.gz |
BASELINE: Update Chromium to 73.0.3683.37
Change-Id: I08c9af2948b645f671e5d933aca1f7a90ea372f2
Reviewed-by: Michael BrĂ¼ning <michael.bruning@qt.io>
Diffstat (limited to 'chromium/components/signin')
90 files changed, 6988 insertions, 2508 deletions
diff --git a/chromium/components/signin/core/browser/BUILD.gn b/chromium/components/signin/core/browser/BUILD.gn index ad4cd91b5c2..d85aaee4e6e 100644 --- a/chromium/components/signin/core/browser/BUILD.gn +++ b/chromium/components/signin/core/browser/BUILD.gn @@ -24,6 +24,10 @@ static_library("shared") { sources = [ "account_info.cc", "account_info.h", + "account_info_util.cc", + "account_info_util.h", + "avatar_icon_util.cc", + "avatar_icon_util.h", "identity_utils.cc", "identity_utils.h", "signin_metrics.cc", @@ -32,12 +36,17 @@ static_library("shared") { "signin_pref_names.h", "signin_switches.cc", "signin_switches.h", + "ubertoken_fetcher.cc", + "ubertoken_fetcher.h", ] deps = [ ":signin_buildflags", "//components/account_id", "//components/prefs:prefs", "//third_party/icu:icui18n", + "//third_party/re2", + "//ui/gfx", + "//url", ] public_deps = [ "//base", @@ -57,8 +66,14 @@ static_library("internals") { sources = [ "account_consistency_method.cc", "account_consistency_method.h", + "account_fetcher_service.cc", + "account_fetcher_service.h", + "account_info_fetcher.cc", + "account_info_fetcher.h", "account_tracker_service.cc", "account_tracker_service.h", + "child_account_info_fetcher_android.cc", + "child_account_info_fetcher_android.h", "device_id_helper.cc", "device_id_helper.h", "gaia_cookie_manager_service.cc", @@ -67,14 +82,14 @@ static_library("internals") { "profile_oauth2_token_service.h", "signin_client.cc", "signin_client.h", - "signin_error_controller.cc", - "signin_error_controller.h", "signin_internals_util.cc", "signin_internals_util.h", "signin_manager.cc", "signin_manager.h", "signin_manager_base.cc", "signin_manager_base.h", + "ubertoken_fetcher_impl.cc", + "ubertoken_fetcher_impl.h", ] if (is_chromeos) { @@ -83,8 +98,10 @@ static_library("internals") { deps = [ ":shared", + ":signin_buildflags", "//base", "//components/data_use_measurement/core", + "//components/image_fetcher/core", "//components/keyed_service/core", "//components/prefs", "//google_apis", @@ -93,30 +110,22 @@ static_library("internals") { "//services/network/public/mojom", "//ui/gfx", ] + + if (is_android) { + deps += [ "android:jni_headers" ] + } } static_library("browser") { sources = [ "about_signin_internals.cc", "about_signin_internals.h", - "account_fetcher_service.cc", - "account_fetcher_service.h", - "account_info_fetcher.cc", - "account_info_fetcher.h", "account_investigator.cc", "account_investigator.h", "account_reconcilor.cc", "account_reconcilor.h", "account_reconcilor_delegate.cc", "account_reconcilor_delegate.h", - "avatar_icon_util.cc", - "avatar_icon_util.h", - "child_account_info_fetcher.cc", - "child_account_info_fetcher.h", - "child_account_info_fetcher_android.cc", - "child_account_info_fetcher_android.h", - "child_account_info_fetcher_impl.cc", - "child_account_info_fetcher_impl.h", "chrome_connected_header_helper.cc", "chrome_connected_header_helper.h", "cookie_settings_util.cc", @@ -127,6 +136,12 @@ static_library("browser") { "dice_header_helper.h", "mirror_account_reconcilor_delegate.cc", "mirror_account_reconcilor_delegate.h", + "mutable_profile_oauth2_token_service_delegate.cc", + "mutable_profile_oauth2_token_service_delegate.h", + "oauth2_token_service_delegate_android.cc", + "oauth2_token_service_delegate_android.h", + "signin_error_controller.cc", + "signin_error_controller.h", "signin_header_helper.cc", "signin_header_helper.h", "signin_investigator.cc", @@ -168,7 +183,6 @@ static_library("browser") { "//base:i18n", "//components/data_use_measurement/core", "//components/google/core/browser", - "//components/image_fetcher/core", "//components/metrics", "//components/os_crypt", "//components/webdata/common", @@ -178,7 +192,6 @@ static_library("browser") { "//skia", "//sql", "//third_party/icu", - "//third_party/re2", ] if (is_chromeos) { @@ -198,10 +211,6 @@ static_library("browser") { } if (is_android) { - sources -= [ - "child_account_info_fetcher_impl.cc", - "child_account_info_fetcher_impl.h", - ] deps += [ "android:jni_headers" ] } } @@ -214,20 +223,29 @@ static_library("browser") { static_library("internals_test_support") { testonly = true sources = [ + "fake_account_fetcher_service.cc", + "fake_account_fetcher_service.h", "fake_gaia_cookie_manager_service.cc", "fake_gaia_cookie_manager_service.h", "fake_profile_oauth2_token_service.cc", "fake_profile_oauth2_token_service.h", "fake_signin_manager.cc", "fake_signin_manager.h", + + # TODO(https://crbug.com/907782): Move list_accounts_test_utils to + # //services/identity/public/cpp once FakeGCMS no longer depends on it. + "list_accounts_test_utils.cc", + "list_accounts_test_utils.h", "test_signin_client.cc", "test_signin_client.h", ] deps = [ "//base/test:test_support", + "//components/image_fetcher/core", "//components/prefs", "//google_apis:test_support", + "//ui/gfx:test_support", ] public_deps = [ @@ -238,36 +256,11 @@ static_library("internals_test_support") { ] } -static_library("test_support") { - testonly = true - sources = [ - "fake_account_fetcher_service.cc", - "fake_account_fetcher_service.h", - "fake_auth_status_provider.cc", - "fake_auth_status_provider.h", - ] - - deps = [ - "//ui/gfx:test_support", - ] - - public_deps = [ - ":browser", - - # This public_dep is present to avoid requiring all consumers of this - # target to also depend on the below target. Once there are no direct - # consumers of the internals_test_support target, this dep should be - # removed. See https://crbug.com/796544. - ":internals_test_support", - "//base", - "//components/image_fetcher/core/", - ] -} - source_set("unit_tests") { testonly = true sources = [ "account_info_unittest.cc", + "account_info_util_unittest.cc", "account_investigator_unittest.cc", "account_reconcilor_delegate_unittest.cc", "account_reconcilor_unittest.cc", @@ -277,6 +270,7 @@ source_set("unit_tests") { "dice_account_reconcilor_delegate_unittest.cc", "gaia_cookie_manager_service_unittest.cc", "identity_utils_unittest.cc", + "mutable_profile_oauth2_token_service_delegate_unittest.cc", "signin_error_controller_unittest.cc", "signin_header_helper_unittest.cc", "signin_investigator_unittest.cc", @@ -284,14 +278,17 @@ source_set("unit_tests") { "signin_metrics_unittest.cc", "signin_status_metrics_provider_unittest.cc", "signin_tracker_unittest.cc", + "ubertoken_fetcher_impl_unittest.cc", "webdata/token_service_table_unittest.cc", ] deps = [ + ":browser", + ":internals_test_support", ":signin_buildflags", - ":test_support", "//base/test:test_support", "//components/content_settings/core/browser", + "//components/image_fetcher/core", "//components/os_crypt:test_support", "//components/prefs", "//components/prefs:test_support", diff --git a/chromium/components/signin/core/browser/about_signin_internals.cc b/chromium/components/signin/core/browser/about_signin_internals.cc index 2139af774d6..ad8dd0679f8 100644 --- a/chromium/components/signin/core/browser/about_signin_internals.cc +++ b/chromium/components/signin/core/browser/about_signin_internals.cc @@ -25,7 +25,6 @@ #include "components/signin/core/browser/profile_oauth2_token_service.h" #include "components/signin/core/browser/signin_client.h" #include "components/signin/core/browser/signin_internals_util.h" -#include "components/signin/core/browser/signin_manager.h" #include "components/signin/core/browser/signin_switches.h" #include "google_apis/gaia/oauth2_token_service_delegate.h" #include "net/base/backoff_entry.h" @@ -125,10 +124,6 @@ std::string SigninStatusFieldToLabel( return "Gaia Authentication Result"; case signin_internals_util::REFRESH_TOKEN_RECEIVED: return "RefreshToken Received"; - case signin_internals_util::SIGNIN_STARTED: - return "SigninManager Started"; - case signin_internals_util::SIGNIN_COMPLETED: - return "SigninManager Completed"; case signin_internals_util::TIMED_FIELDS_END: NOTREACHED(); return "Error"; @@ -173,8 +168,6 @@ std::string GetAccountConsistencyDescription( return "None"; case signin::AccountConsistencyMethod::kMirror: return "Mirror"; - case signin::AccountConsistencyMethod::kDiceFixAuthErrors: - return "DICE fixing auth errors"; case signin::AccountConsistencyMethod::kDiceMigration: return "DICE migration"; case signin::AccountConsistencyMethod::kDice: @@ -190,14 +183,12 @@ AboutSigninInternals::AboutSigninInternals( ProfileOAuth2TokenService* token_service, AccountTrackerService* account_tracker, identity::IdentityManager* identity_manager, - SigninManagerBase* signin_manager, SigninErrorController* signin_error_controller, GaiaCookieManagerService* cookie_manager_service, signin::AccountConsistencyMethod account_consistency) : token_service_(token_service), account_tracker_(account_tracker), identity_manager_(identity_manager), - signin_manager_(signin_manager), client_(nullptr), signin_error_controller_(signin_error_controller), cookie_manager_service_(cookie_manager_service), @@ -252,7 +243,7 @@ void AboutSigninInternals::RemoveSigninObserver( signin_observers_.RemoveObserver(observer); } -void AboutSigninInternals::NotifySigninValueChanged( +void AboutSigninInternals::NotifyTimedSigninFieldValueChanged( const signin_internals_util::TimedSigninStatusField& field, const std::string& value) { unsigned int field_index = field - signin_internals_util::TIMED_FIELDS_BEGIN; @@ -274,8 +265,6 @@ void AboutSigninInternals::NotifySigninValueChanged( if (field == signin_internals_util::AUTHENTICATION_RESULT_RECEIVED) { ClearPref(client_->GetPrefs(), signin_internals_util::REFRESH_TOKEN_RECEIVED); - ClearPref(client_->GetPrefs(), signin_internals_util::SIGNIN_STARTED); - ClearPref(client_->GetPrefs(), signin_internals_util::SIGNIN_COMPLETED); } NotifyObservers(); @@ -313,8 +302,6 @@ void AboutSigninInternals::Initialize(SigninClient* client) { signin_error_controller_->AddObserver(this); identity_manager_->AddObserver(this); identity_manager_->AddDiagnosticsObserver(this); - signin_manager_->AddSigninDiagnosticsObserver(this); - token_service_->AddObserver(this); token_service_->AddDiagnosticsObserver(this); cookie_manager_service_->AddObserver(this); } @@ -323,8 +310,6 @@ void AboutSigninInternals::Shutdown() { signin_error_controller_->RemoveObserver(this); identity_manager_->RemoveObserver(this); identity_manager_->RemoveDiagnosticsObserver(this); - signin_manager_->RemoveSigninDiagnosticsObserver(this); - token_service_->RemoveObserver(this); token_service_->RemoveDiagnosticsObserver(this); cookie_manager_service_->RemoveObserver(this); } @@ -415,7 +400,7 @@ void AboutSigninInternals::OnRefreshTokensLoaded() { NotifyObservers(); } -void AboutSigninInternals::OnEndBatchChanges() { +void AboutSigninInternals::OnEndBatchOfRefreshTokenStateChanges() { NotifyObservers(); } @@ -431,13 +416,13 @@ void AboutSigninInternals::OnAccessTokenRemoved( } void AboutSigninInternals::OnRefreshTokenReceived(const std::string& status) { - NotifySigninValueChanged(signin_internals_util::REFRESH_TOKEN_RECEIVED, - status); + NotifyTimedSigninFieldValueChanged( + signin_internals_util::REFRESH_TOKEN_RECEIVED, status); } void AboutSigninInternals::OnAuthenticationResultReceived( const std::string& status) { - NotifySigninValueChanged( + NotifyTimedSigninFieldValueChanged( signin_internals_util::AUTHENTICATION_RESULT_RECEIVED, status); } diff --git a/chromium/components/signin/core/browser/about_signin_internals.h b/chromium/components/signin/core/browser/about_signin_internals.h index 23aa4fd8973..5ce029fbc09 100644 --- a/chromium/components/signin/core/browser/about_signin_internals.h +++ b/chromium/components/signin/core/browser/about_signin_internals.h @@ -17,8 +17,6 @@ #include "components/signin/core/browser/gaia_cookie_manager_service.h" #include "components/signin/core/browser/signin_client.h" #include "components/signin/core/browser/signin_error_controller.h" -#include "components/signin/core/browser/signin_internals_util.h" -#include "components/signin/core/browser/signin_manager.h" #include "google_apis/gaia/oauth2_token_service.h" #include "services/identity/public/cpp/identity_manager.h" @@ -39,8 +37,6 @@ using TimedSigninStatusValue = std::pair<std::string, std::string>; // to propagate to about:signin-internals via SigninInternalsUI. class AboutSigninInternals : public KeyedService, - public signin_internals_util::SigninDiagnosticsObserver, - public OAuth2TokenService::Observer, public OAuth2TokenService::DiagnosticsObserver, public GaiaCookieManagerService::Observer, SigninErrorController::Observer, @@ -60,7 +56,6 @@ class AboutSigninInternals AboutSigninInternals(ProfileOAuth2TokenService* token_service, AccountTrackerService* account_tracker, identity::IdentityManager* identity_manager, - SigninManagerBase* signin_manager, SigninErrorController* signin_error_controller, GaiaCookieManagerService* cookie_manager_service, signin::AccountConsistencyMethod account_consistency); @@ -196,12 +191,7 @@ class AboutSigninInternals signin::AccountConsistencyMethod account_consistency); }; - // SigninManager::SigninDiagnosticsObserver implementation. - void NotifySigninValueChanged( - const signin_internals_util::TimedSigninStatusField& field, - const std::string& value) override; - - // IdentityMager::DiagnosticsObserver implementations. + // IdentityManager::DiagnosticsObserver implementations. void OnAccessTokenRequested(const std::string& account_id, const std::string& consumer_id, const identity::ScopeSet& scopes) override; @@ -221,17 +211,19 @@ class AboutSigninInternals void OnRefreshTokenRevokedFromSource(const std::string& account_id, const std::string& source) override; - // OAuth2TokenServiceDelegate::Observer implementations. - void OnRefreshTokensLoaded() override; - void OnEndBatchChanges() override; - // IdentityManager::Observer implementations. + void OnRefreshTokensLoaded() override; + void OnEndBatchOfRefreshTokenStateChanges() override; void OnPrimaryAccountSigninFailed( const GoogleServiceAuthError& error) override; void OnPrimaryAccountSet(const AccountInfo& primary_account_info) override; void OnPrimaryAccountCleared( const AccountInfo& primary_account_info) override; + void NotifyTimedSigninFieldValueChanged( + const signin_internals_util::TimedSigninStatusField& field, + const std::string& value); + void NotifyObservers(); // SigninErrorController::Observer implementation @@ -246,9 +238,6 @@ class AboutSigninInternals // Weak pointer to the identity manager. identity::IdentityManager* identity_manager_; - // Weak pointer to the signin manager. - SigninManagerBase* signin_manager_; - // Weak pointer to the client. SigninClient* client_; diff --git a/chromium/components/signin/core/browser/account_consistency_method.h b/chromium/components/signin/core/browser/account_consistency_method.h index f1d1b38026c..bee4dc36835 100644 --- a/chromium/components/signin/core/browser/account_consistency_method.h +++ b/chromium/components/signin/core/browser/account_consistency_method.h @@ -21,9 +21,6 @@ enum class AccountConsistencyMethod : int { // Account management UI in the avatar bubble. kMirror, - // No account consistency, but Dice fixes authentication errors. - kDiceFixAuthErrors, - // Account management UI on Gaia webpages is enabled once the accounts become // consistent. kDiceMigration, diff --git a/chromium/components/signin/core/browser/account_fetcher_service.cc b/chromium/components/signin/core/browser/account_fetcher_service.cc index 5377efb4acc..23a8aa36d01 100644 --- a/chromium/components/signin/core/browser/account_fetcher_service.cc +++ b/chromium/components/signin/core/browser/account_fetcher_service.cc @@ -17,11 +17,14 @@ #include "components/signin/core/browser/account_info_fetcher.h" #include "components/signin/core/browser/account_tracker_service.h" #include "components/signin/core/browser/avatar_icon_util.h" -#include "components/signin/core/browser/child_account_info_fetcher.h" #include "components/signin/core/browser/signin_client.h" #include "components/signin/core/browser/signin_switches.h" #include "services/network/public/cpp/shared_url_loader_factory.h" +#if defined(OS_ANDROID) +#include "components/signin/core/browser/child_account_info_fetcher_android.h" +#endif + namespace { const base::TimeDelta kRefreshFromTokenServiceDelay = @@ -53,8 +56,11 @@ AccountFetcherService::AccountFetcherService() profile_loaded_(false), refresh_tokens_loaded_(false), shutdown_called_(false), - scheduled_refresh_enabled_(true), - child_info_request_(nullptr) {} +#if defined(OS_ANDROID) + child_info_request_(nullptr), +#endif + scheduled_refresh_enabled_(true) { +} AccountFetcherService::~AccountFetcherService() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); @@ -91,9 +97,11 @@ void AccountFetcherService::Initialize( void AccountFetcherService::Shutdown() { token_service_->RemoveObserver(this); +#if defined(OS_ANDROID) // child_info_request_ is an invalidation handler and needs to be // unregistered during the lifetime of the invalidation service. child_info_request_.reset(); +#endif invalidation_service_ = nullptr; shutdown_called_ = true; } @@ -113,7 +121,9 @@ void AccountFetcherService::SetupInvalidationsOnProfileLoad( DCHECK(!invalidation_service_); DCHECK(!profile_loaded_); DCHECK(!network_fetches_enabled_); +#if defined(OS_ANDROID) DCHECK(!child_info_request_); +#endif invalidation_service_ = invalidation_service; profile_loaded_ = true; MaybeEnableNetworkFetches(); @@ -215,12 +225,12 @@ void AccountFetcherService::StartFetchingUserInfo( } } +#if defined(OS_ANDROID) // Starts fetching whether this is a child account. Handles refresh internally. void AccountFetcherService::StartFetchingChildInfo( const std::string& account_id) { - child_info_request_ = ChildAccountInfoFetcher::CreateFrom( - child_request_account_id_, this, token_service_, - signin_client_->GetURLLoaderFactory(), invalidation_service_); + child_info_request_ = + ChildAccountInfoFetcherAndroid::Create(this, child_request_account_id_); } void AccountFetcherService::ResetChildInfo() { @@ -230,6 +240,13 @@ void AccountFetcherService::ResetChildInfo() { child_info_request_.reset(); } +void AccountFetcherService::SetIsChildAccount(const std::string& account_id, + bool is_child_account) { + if (child_request_account_id_ == account_id) + account_tracker_service_->SetIsChildAccount(account_id, is_child_account); +} +#endif + void AccountFetcherService::RefreshAccountInfo(const std::string& account_id, bool only_fetch_if_invalid) { DCHECK(network_fetches_enabled_); @@ -253,8 +270,8 @@ void AccountFetcherService::RefreshAccountInfo(const std::string& account_id, void AccountFetcherService::OnUserInfoFetchSuccess( const std::string& account_id, std::unique_ptr<base::DictionaryValue> user_info) { - account_tracker_service_->SetAccountStateFromUserInfo(account_id, - user_info.get()); + account_tracker_service_->SetAccountInfoFromUserInfo(account_id, + user_info.get()); FetchAccountImage(account_id); user_info_requests_.erase(account_id); } @@ -309,12 +326,6 @@ void AccountFetcherService::FetchAccountImage(const std::string& account_id) { callback, traffic_annotation); } -void AccountFetcherService::SetIsChildAccount(const std::string& account_id, - bool is_child_account) { - if (child_request_account_id_ == account_id) - account_tracker_service_->SetIsChildAccount(account_id, is_child_account); -} - void AccountFetcherService::OnUserInfoFetchFailure( const std::string& account_id) { LOG(WARNING) << "Failed to get UserInfo for " << account_id; diff --git a/chromium/components/signin/core/browser/account_fetcher_service.h b/chromium/components/signin/core/browser/account_fetcher_service.h index 77eb45911f7..7ced83d8402 100644 --- a/chromium/components/signin/core/browser/account_fetcher_service.h +++ b/chromium/components/signin/core/browser/account_fetcher_service.h @@ -13,17 +13,21 @@ #include "base/macros.h" #include "base/sequence_checker.h" #include "base/timer/timer.h" +#include "build/build_config.h" #include "components/keyed_service/core/keyed_service.h" #include "google_apis/gaia/oauth2_token_service.h" #include "ui/gfx/image/image.h" class AccountInfoFetcher; class AccountTrackerService; -class ChildAccountInfoFetcher; class OAuth2TokenService; class PrefRegistrySimple; class SigninClient; +#if defined(OS_ANDROID) +class ChildAccountInfoFetcherAndroid; +#endif + namespace base { class DictionaryValue; } @@ -38,9 +42,6 @@ namespace invalidation { class InvalidationService; } -// TODO(maroun): Protect with macro for Android only everything that is related -// to child account info fetching. - class AccountFetcherService : public KeyedService, public OAuth2TokenService::Observer { public: @@ -83,8 +84,10 @@ class AccountFetcherService : public KeyedService, void EnableNetworkFetchesForTest(); - // Called by ChildAccountInfoFetcher. +#if defined(OS_ANDROID) + // Called by ChildAccountInfoFetcherAndroid. void SetIsChildAccount(const std::string& account_id, bool is_child_account); +#endif // OAuth2TokenService::Observer implementation. void OnRefreshTokenAvailable(const std::string& account_id) override; @@ -93,15 +96,16 @@ class AccountFetcherService : public KeyedService, private: friend class AccountInfoFetcher; - friend class ChildAccountInfoFetcherImpl; void RefreshAllAccountInfo(bool only_fetch_if_invalid); void RefreshAllAccountsAndScheduleNext(); void ScheduleNextRefresh(); +#if defined(OS_ANDROID) // Called on all account state changes. Decides whether to fetch new child // status information or reset old values that aren't valid now. void UpdateChildInfo(); +#endif void MaybeEnableNetworkFetches(); @@ -109,11 +113,13 @@ class AccountFetcherService : public KeyedService, // Further the two fetches are managed by a different refresh logic and // thus, can not be combined. virtual void StartFetchingUserInfo(const std::string& account_id); +#if defined(OS_ANDROID) virtual void StartFetchingChildInfo(const std::string& account_id); // If there is more than one account in a profile, we forcibly reset the // child status for an account to be false. void ResetChildInfo(); +#endif // Refreshes the AccountInfo associated with |account_id|. void RefreshAccountInfo(const std::string& account_id, @@ -143,11 +149,14 @@ class AccountFetcherService : public KeyedService, base::Time last_updated_; base::OneShotTimer timer_; bool shutdown_called_; - // Only disabled in tests. - bool scheduled_refresh_enabled_; +#if defined(OS_ANDROID) std::string child_request_account_id_; - std::unique_ptr<ChildAccountInfoFetcher> child_info_request_; + std::unique_ptr<ChildAccountInfoFetcherAndroid> child_info_request_; +#endif + + // Only disabled in tests. + bool scheduled_refresh_enabled_; // Holds references to account info fetchers keyed by account_id. std::unordered_map<std::string, std::unique_ptr<AccountInfoFetcher>> diff --git a/chromium/components/signin/core/browser/account_info.cc b/chromium/components/signin/core/browser/account_info.cc index b795bccd57d..8fc78378066 100644 --- a/chromium/components/signin/core/browser/account_info.cc +++ b/chromium/components/signin/core/browser/account_info.cc @@ -6,22 +6,40 @@ namespace { -bool UpdateField(std::string* field, const std::string& new_value) { - bool should_update = field->empty() && !new_value.empty(); - if (should_update) - *field = new_value; - return should_update; +// Updates |field| with |new_value| if non-empty and different; if |new_value| +// is equal to |default_value| then it won't override |field| unless it is not +// set. Returns whether |field| was changed. +bool UpdateField(std::string* field, + const std::string& new_value, + const char* default_value) { + if (*field == new_value || new_value.empty()) + return false; + + if (!field->empty() && default_value && new_value == default_value) + return false; + + *field = new_value; + return true; } +// Updates |field| with |new_value| if true. Returns whether |field| was +// changed. bool UpdateField(bool* field, bool new_value) { - bool should_update = !*field && new_value; - if (should_update) - *field = new_value; - return should_update; + if (*field == new_value || !new_value) + return false; + + *field = new_value; + return true; } } // namespace +// This must be a string which can never be a valid domain. +const char kNoHostedDomainFound[] = "NO_HOSTED_DOMAIN"; + +// This must be a string which can never be a valid picture URL. +const char kNoPictureURLFound[] = "NO_PICTURE_URL"; + AccountInfo::AccountInfo() = default; AccountInfo::~AccountInfo() = default; @@ -52,13 +70,15 @@ bool AccountInfo::UpdateWith(const AccountInfo& other) { return false; } - bool modified = UpdateField(&gaia, other.gaia); - modified |= UpdateField(&email, other.email); - modified |= UpdateField(&full_name, other.full_name); - modified |= UpdateField(&given_name, other.given_name); - modified |= UpdateField(&hosted_domain, other.hosted_domain); - modified |= UpdateField(&locale, other.locale); - modified |= UpdateField(&picture_url, other.picture_url); + bool modified = false; + modified |= UpdateField(&gaia, other.gaia, nullptr); + modified |= UpdateField(&email, other.email, nullptr); + modified |= UpdateField(&full_name, other.full_name, nullptr); + modified |= UpdateField(&given_name, other.given_name, nullptr); + modified |= + UpdateField(&hosted_domain, other.hosted_domain, kNoHostedDomainFound); + modified |= UpdateField(&locale, other.locale, nullptr); + modified |= UpdateField(&picture_url, other.picture_url, kNoPictureURLFound); modified |= UpdateField(&is_child_account, other.is_child_account); modified |= UpdateField(&is_under_advanced_protection, other.is_under_advanced_protection); diff --git a/chromium/components/signin/core/browser/account_info.h b/chromium/components/signin/core/browser/account_info.h index 4856bee87a0..9be08af8f5f 100644 --- a/chromium/components/signin/core/browser/account_info.h +++ b/chromium/components/signin/core/browser/account_info.h @@ -8,6 +8,13 @@ #include <string> #include "components/account_id/account_id.h" +#include "ui/gfx/image/image.h" + +// Value representing no hosted domain associated with an account. +extern const char kNoHostedDomainFound[]; + +// Value representing no picture URL associated with an account. +extern const char kNoPictureURLFound[]; // Information about a specific account. struct AccountInfo { @@ -30,6 +37,7 @@ struct AccountInfo { std::string hosted_domain; std::string locale; std::string picture_url; + gfx::Image account_image; bool is_child_account = false; bool is_under_advanced_protection = false; diff --git a/chromium/components/signin/core/browser/account_info_unittest.cc b/chromium/components/signin/core/browser/account_info_unittest.cc index 844632d7134..2329095b45a 100644 --- a/chromium/components/signin/core/browser/account_info_unittest.cc +++ b/chromium/components/signin/core/browser/account_info_unittest.cc @@ -47,8 +47,8 @@ TEST_F(AccountInfoTest, IsValid) { EXPECT_TRUE(info.IsValid()); } -// Tests that UpdateWith() correctly ignores parameters with a different account -// id. +// Tests that UpdateWith() correctly ignores parameters with a different +// account / id. TEST_F(AccountInfoTest, UpdateWithDifferentAccountId) { AccountInfo info; info.account_id = "test_id"; @@ -61,7 +61,8 @@ TEST_F(AccountInfoTest, UpdateWithDifferentAccountId) { EXPECT_TRUE(info.email.empty()); } -// Tests that UpdateWith() doesn't update the fields that were already set. +// Tests that UpdateWith() doesn't update the fields that were already set +// to the correct value. TEST_F(AccountInfoTest, UpdateWithNoModification) { AccountInfo info; info.account_id = info.gaia = info.email = "test_id"; @@ -69,7 +70,7 @@ TEST_F(AccountInfoTest, UpdateWithNoModification) { AccountInfo other; other.account_id = "test_id"; - other.gaia = other.email = "test_other_id"; + other.gaia = other.email = "test_id"; other.is_child_account = false; EXPECT_FALSE(info.UpdateWith(other)); @@ -95,3 +96,37 @@ TEST_F(AccountInfoTest, UpdateWithSuccessfulUpdate) { EXPECT_EQ("test_name", info.given_name); EXPECT_TRUE(info.is_child_account); } + +// Tests that UpdateWith() sets default values for hosted_domain and +// picture_url if the properties are unset. +TEST_F(AccountInfoTest, UpdateWithDefaultValues) { + AccountInfo info; + info.account_id = info.gaia = info.email = "test_id"; + + AccountInfo other; + other.account_id = "test_id"; + other.hosted_domain = kNoHostedDomainFound; + other.picture_url = kNoPictureURLFound; + + EXPECT_TRUE(info.UpdateWith(other)); + EXPECT_EQ(kNoHostedDomainFound, info.hosted_domain); + EXPECT_EQ(kNoPictureURLFound, info.picture_url); +} + +// Tests that UpdateWith() ignores default values for hosted_domain and +// picture_url if they are already set. +TEST_F(AccountInfoTest, UpdateWithDefaultValuesNoOverride) { + AccountInfo info; + info.account_id = info.gaia = info.email = "test_id"; + info.hosted_domain = "test_domain"; + info.picture_url = "test_url"; + + AccountInfo other; + other.account_id = "test_id"; + other.hosted_domain = kNoHostedDomainFound; + other.picture_url = kNoPictureURLFound; + + EXPECT_FALSE(info.UpdateWith(other)); + EXPECT_EQ("test_domain", info.hosted_domain); + EXPECT_EQ("test_url", info.picture_url); +} diff --git a/chromium/components/signin/core/browser/account_info_util.cc b/chromium/components/signin/core/browser/account_info_util.cc new file mode 100644 index 00000000000..878adeb5f62 --- /dev/null +++ b/chromium/components/signin/core/browser/account_info_util.cc @@ -0,0 +1,73 @@ +// Copyright 2019 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 "components/signin/core/browser/account_info_util.h" + +#include "components/signin/core/browser/account_info.h" + +namespace { +// Keys used to store the different values in the JSON dictionary received +// from gaia server. +const char kGaiaIdKey[] = "id"; +const char kEmailKey[] = "email"; +const char kHostedDomainKey[] = "hd"; +const char kFullNameKey[] = "name"; +const char kGivenNameKey[] = "given_name"; +const char kLocaleKey[] = "locale"; +const char kPictureUrlKey[] = "picture"; +} // namespace + +base::Optional<AccountInfo> AccountInfoFromUserInfo( + const base::Value& user_info) { + if (!user_info.is_dict()) + return base::nullopt; + + // Both |gaia_id| and |email| are required value in the JSON reply, so + // return empty result if any is missing. + const base::Value* gaia_id_value = + user_info.FindKeyOfType(kGaiaIdKey, base::Value::Type::STRING); + if (!gaia_id_value) + return base::nullopt; + + const base::Value* email_value = + user_info.FindKeyOfType(kEmailKey, base::Value::Type::STRING); + if (!email_value) + return base::nullopt; + + AccountInfo account_info; + account_info.email = email_value->GetString(); + account_info.gaia = gaia_id_value->GetString(); + + // All other fields are optional, some with default values. + const base::Value* hosted_domain_value = + user_info.FindKeyOfType(kHostedDomainKey, base::Value::Type::STRING); + if (hosted_domain_value && !hosted_domain_value->GetString().empty()) + account_info.hosted_domain = hosted_domain_value->GetString(); + else + account_info.hosted_domain = kNoHostedDomainFound; + + const base::Value* full_name_value = + user_info.FindKeyOfType(kFullNameKey, base::Value::Type::STRING); + if (full_name_value) + account_info.full_name = full_name_value->GetString(); + + const base::Value* given_name_value = + user_info.FindKeyOfType(kGivenNameKey, base::Value::Type::STRING); + if (given_name_value) + account_info.given_name = given_name_value->GetString(); + + const base::Value* locale_value = + user_info.FindKeyOfType(kLocaleKey, base::Value::Type::STRING); + if (locale_value) + account_info.locale = locale_value->GetString(); + + const base::Value* picture_url_value = + user_info.FindKeyOfType(kPictureUrlKey, base::Value::Type::STRING); + if (picture_url_value && !picture_url_value->GetString().empty()) + account_info.picture_url = picture_url_value->GetString(); + else + account_info.picture_url = kNoPictureURLFound; + + return account_info; +} diff --git a/chromium/components/signin/core/browser/account_info_util.h b/chromium/components/signin/core/browser/account_info_util.h new file mode 100644 index 00000000000..fb2058a6674 --- /dev/null +++ b/chromium/components/signin/core/browser/account_info_util.h @@ -0,0 +1,17 @@ +// Copyright 2019 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. + +#ifndef COMPONENTS_SIGNIN_CORE_BROWSER_ACCOUNT_INFO_UTIL_H_ +#define COMPONENTS_SIGNIN_CORE_BROWSER_ACCOUNT_INFO_UTIL_H_ + +#include "base/optional.h" +#include "base/values.h" +#include "components/signin/core/browser/account_info.h" + +// Builds an AccountInfo from the JSON data returned by the gaia servers (the +// data should have been converted to base::Value), if possible. +base::Optional<AccountInfo> AccountInfoFromUserInfo( + const base::Value& user_info); + +#endif // COMPONENTS_SIGNIN_CORE_BROWSER_ACCOUNT_INFO_UTIL_H_ diff --git a/chromium/components/signin/core/browser/account_info_util_unittest.cc b/chromium/components/signin/core/browser/account_info_util_unittest.cc new file mode 100644 index 00000000000..da0c76f172f --- /dev/null +++ b/chromium/components/signin/core/browser/account_info_util_unittest.cc @@ -0,0 +1,158 @@ +// Copyright 2019 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 "components/signin/core/browser/account_info_util.h" + +#include "components/signin/core/browser/account_info.h" +#include "testing/platform_test.h" + +namespace { +// Returns a base::Value corresponding to the user info as would be returned +// by gaia server with provided values (if null is passed for a value, it will +// not be set in the returned user_info object). +base::Value CreateUserInfoWithValues(const char* email, + const char* gaia, + const char* hosted_domain, + const char* full_name, + const char* given_name, + const char* locale, + const char* picture_url) { + base::Value user_info(base::Value::Type::DICTIONARY); + if (email) + user_info.SetKey("email", base::Value(email)); + + if (gaia) + user_info.SetKey("id", base::Value(gaia)); + + if (hosted_domain) + user_info.SetKey("hd", base::Value(hosted_domain)); + + if (full_name) + user_info.SetKey("name", base::Value(full_name)); + + if (given_name) + user_info.SetKey("given_name", base::Value(given_name)); + + if (locale) + user_info.SetKey("locale", base::Value(locale)); + + if (picture_url) + user_info.SetKey("picture", base::Value(picture_url)); + + return user_info; +} +} // namespace + +using AccountInfoUtilTest = PlatformTest; + +// Tests that AccountInfoFromUserInfo returns an AccountInfo with the value +// extracted from the passed base::Value. +TEST_F(AccountInfoUtilTest, FromUserInfo) { + base::Optional<AccountInfo> maybe_account_info = + AccountInfoFromUserInfo(CreateUserInfoWithValues( + /*email=*/"user@example.com", /*gaia=*/"gaia_id_user_example_com", + /*hosted_domain=*/"example.com", /*full_name=*/"full name", + /*given_name=*/"given name", /*locale=*/"locale", + /*picture_url=*/"https://example.com/picture/user")); + + ASSERT_TRUE(maybe_account_info.has_value()); + + AccountInfo& account_info = maybe_account_info.value(); + ASSERT_EQ(account_info.email, "user@example.com"); + ASSERT_EQ(account_info.gaia, "gaia_id_user_example_com"); + ASSERT_EQ(account_info.hosted_domain, "example.com"); + ASSERT_EQ(account_info.full_name, "full name"); + ASSERT_EQ(account_info.given_name, "given name"); + ASSERT_EQ(account_info.locale, "locale"); + ASSERT_EQ(account_info.picture_url, "https://example.com/picture/user"); +} + +// Tests that AccountInfoFromUserInfo returns an AccountInfo with empty or +// default values if no fields are set in the user_info. +TEST_F(AccountInfoUtilTest, FromUserInfo_EmptyValues) { + base::Optional<AccountInfo> maybe_account_info = + AccountInfoFromUserInfo(CreateUserInfoWithValues( + /*email=*/"", /*gaia=*/"", /*hosted_domain=*/"", /*full_name=*/"", + /*given_name=*/"", /*locale=*/"", /*picture_url=*/"")); + + ASSERT_TRUE(maybe_account_info.has_value()); + + AccountInfo& account_info = maybe_account_info.value(); + ASSERT_EQ(account_info.email, std::string()); + ASSERT_EQ(account_info.gaia, std::string()); + ASSERT_EQ(account_info.hosted_domain, kNoHostedDomainFound); + ASSERT_EQ(account_info.full_name, std::string()); + ASSERT_EQ(account_info.given_name, std::string()); + ASSERT_EQ(account_info.locale, std::string()); + ASSERT_EQ(account_info.picture_url, kNoPictureURLFound); +} + +// Tests that AccountInfoFromUserInfo returns an AccountInfo with the value +// extracted from the passed base::Value, with default value for |hosted_domain| +// if missing. +TEST_F(AccountInfoUtilTest, FromUserInfo_NoHostedDomain) { + base::Optional<AccountInfo> maybe_account_info = + AccountInfoFromUserInfo(CreateUserInfoWithValues( + /*email=*/"user@example.com", /*gaia=*/"gaia_id_user_example_com", + /*hosted_domain=*/nullptr, /*full_name=*/"full name", + /*given_name=*/"given name", /*locale=*/"locale", + /*picture_url=*/"https://example.com/picture/user")); + + ASSERT_TRUE(maybe_account_info.has_value()); + + AccountInfo& account_info = maybe_account_info.value(); + ASSERT_EQ(account_info.hosted_domain, kNoHostedDomainFound); +} + +// Tests that AccountInfoFromUserInfo returns an AccountInfo with the value +// extracted from the passed base::Value, with default value for |picture_url| +// if missing. +TEST_F(AccountInfoUtilTest, FromUserInfo_NoPictureUrl) { + base::Optional<AccountInfo> maybe_account_info = + AccountInfoFromUserInfo(CreateUserInfoWithValues( + /*email=*/"user@example.com", /*gaia=*/"gaia_id_user_example_com", + /*hosted_domain=*/"example.com", /*full_name=*/"full name", + /*given_name=*/"given name", /*locale=*/"locale", + /*picture_url=*/nullptr)); + + ASSERT_TRUE(maybe_account_info.has_value()); + + AccountInfo& account_info = maybe_account_info.value(); + ASSERT_EQ(account_info.picture_url, kNoPictureURLFound); +} + +// Tests that if AccountInfoFromUserInfo fails if the value passed has no +// value for |email|. +TEST_F(AccountInfoUtilTest, FromUserInfo_NoEmail) { + base::Optional<AccountInfo> maybe_account_info = + AccountInfoFromUserInfo(CreateUserInfoWithValues( + /*email=*/nullptr, /*gaia=*/"gaia_id_user_example_com", + /*hosted_domain=*/"example.com", /*full_name=*/"full name", + /*given_name=*/"given name", /*locale=*/"locale", + /*picture_url=*/"https://example.com/picture/user")); + + EXPECT_FALSE(maybe_account_info.has_value()); +} + +// Tests that if AccountInfoFromUserInfo fails if the value passed has no +// value for |gaia|. +TEST_F(AccountInfoUtilTest, FromUserInfo_NoGaiaId) { + base::Optional<AccountInfo> maybe_account_info = + AccountInfoFromUserInfo(CreateUserInfoWithValues( + /*email=*/"user@example.com", /*gaia=*/nullptr, + /*hosted_domain=*/"example.com", /*full_name=*/"full name", + /*given_name=*/"given name", /*locale=*/"locale", + /*picture_url=*/"https://example.com/picture/user")); + + EXPECT_FALSE(maybe_account_info.has_value()); +} + +// Tests that if AccountInfoFromUserInfo fails if the value passed is not a +// dictionary. +TEST_F(AccountInfoUtilTest, FromUserInfo_NotADictionary) { + base::Optional<AccountInfo> maybe_account_info = + AccountInfoFromUserInfo(base::Value("not a dictionary")); + + EXPECT_FALSE(maybe_account_info.has_value()); +} diff --git a/chromium/components/signin/core/browser/account_investigator.cc b/chromium/components/signin/core/browser/account_investigator.cc index eb3d15fec8b..239b372330e 100644 --- a/chromium/components/signin/core/browser/account_investigator.cc +++ b/chromium/components/signin/core/browser/account_investigator.cc @@ -17,7 +17,7 @@ #include "components/signin/core/browser/signin_pref_names.h" #include "google_apis/gaia/gaia_auth_util.h" #include "google_apis/gaia/google_service_auth_error.h" -#include "services/identity/public/cpp/identity_manager.h" +#include "services/identity/public/cpp/accounts_in_cookie_jar_info.h" using base::Time; using base::TimeDelta; @@ -42,12 +42,9 @@ const TimeDelta AccountInvestigator::kPeriodicReportingInterval = TimeDelta::FromDays(1); AccountInvestigator::AccountInvestigator( - GaiaCookieManagerService* cookie_service, PrefService* pref_service, identity::IdentityManager* identity_manager) - : cookie_service_(cookie_service), - pref_service_(pref_service), - identity_manager_(identity_manager) {} + : pref_service_(pref_service), identity_manager_(identity_manager) {} AccountInvestigator::~AccountInvestigator() {} @@ -59,7 +56,7 @@ void AccountInvestigator::RegisterPrefs(PrefRegistrySimple* registry) { } void AccountInvestigator::Initialize() { - cookie_service_->AddObserver(this); + identity_manager_->AddObserver(this); previously_authenticated_ = identity_manager_->HasPrimaryAccount(); Time previous = Time::FromDoubleT( @@ -72,7 +69,7 @@ void AccountInvestigator::Initialize() { } void AccountInvestigator::Shutdown() { - cookie_service_->RemoveObserver(this); + identity_manager_->RemoveObserver(this); timer_.Stop(); } @@ -84,6 +81,14 @@ void AccountInvestigator::OnAddAccountToCookieCompleted( // called serveral times. } +void AccountInvestigator::OnAccountsInCookieUpdated( + const identity::AccountsInCookieJarInfo& accounts_in_cookie_jar_info, + const GoogleServiceAuthError& error) { + OnGaiaAccountsInCookieUpdated(accounts_in_cookie_jar_info.signed_in_accounts, + accounts_in_cookie_jar_info.signed_out_accounts, + error); +} + void AccountInvestigator::OnGaiaAccountsInCookieUpdated( const std::vector<ListedAccount>& signed_in_accounts, const std::vector<ListedAccount>& signed_out_accounts, @@ -186,8 +191,8 @@ AccountRelation AccountInvestigator::DiscernRelation( } else if (signed_out_match_iter != signed_out_accounts.end()) { if (signed_in_accounts.empty()) { return signed_out_accounts.size() == 1 - ? AccountRelation::NO_SIGNED_IN_SINGLE_SIGNED_OUT_MATCH - : AccountRelation::NO_SIGNED_IN_ONE_OF_SIGNED_OUT_MATCH; + ? AccountRelation::NO_SIGNED_IN_SINGLE_SIGNED_OUT_MATCH + : AccountRelation::NO_SIGNED_IN_ONE_OF_SIGNED_OUT_MATCH; } else { return AccountRelation::WITH_SIGNED_IN_ONE_OF_SIGNED_OUT_MATCH; } @@ -199,10 +204,11 @@ AccountRelation AccountInvestigator::DiscernRelation( } void AccountInvestigator::TryPeriodicReport() { - std::vector<ListedAccount> signed_in_accounts, signed_out_accounts; - if (cookie_service_->ListAccounts(&signed_in_accounts, - &signed_out_accounts)) { - DoPeriodicReport(signed_in_accounts, signed_out_accounts); + auto accounts_in_cookie_jar_info = + identity_manager_->GetAccountsInCookieJar(); + if (accounts_in_cookie_jar_info.accounts_are_fresh) { + DoPeriodicReport(accounts_in_cookie_jar_info.signed_in_accounts, + accounts_in_cookie_jar_info.signed_out_accounts); } else { periodic_pending_ = true; } @@ -235,8 +241,7 @@ void AccountInvestigator::SharedCookieJarReport( int signed_in_count = signed_in_accounts.size(); int signed_out_count = signed_out_accounts.size(); - signin_metrics::LogCookieJarCounts(signed_in_count, - signed_out_count, + signin_metrics::LogCookieJarCounts(signed_in_count, signed_out_count, signed_in_count + signed_out_count, type); if (identity_manager_->HasPrimaryAccount()) { diff --git a/chromium/components/signin/core/browser/account_investigator.h b/chromium/components/signin/core/browser/account_investigator.h index 6fb94997788..dd6b6661f7a 100644 --- a/chromium/components/signin/core/browser/account_investigator.h +++ b/chromium/components/signin/core/browser/account_investigator.h @@ -11,10 +11,9 @@ #include "base/macros.h" #include "base/timer/timer.h" #include "components/keyed_service/core/keyed_service.h" -#include "components/signin/core/browser/gaia_cookie_manager_service.h" +#include "services/identity/public/cpp/identity_manager.h" struct AccountInfo; -class GaiaCookieManagerService; class PrefRegistrySimple; class PrefService; @@ -23,8 +22,8 @@ class Time; } // namespace base namespace identity { -class IdentityManager; -} +struct AccountsInCookieJarInfo; +} // namespace identity namespace signin_metrics { enum class AccountRelation; @@ -36,15 +35,14 @@ enum class ReportingType; // is to watch for changes in relation between Chrome and content area accounts // and emit metrics about their relation. class AccountInvestigator : public KeyedService, - public GaiaCookieManagerService::Observer { + public identity::IdentityManager::Observer { public: // The targeted interval to perform periodic reporting. If chrome is not // active at the end of an interval, reporting will be done as soon as // possible. static const base::TimeDelta kPeriodicReportingInterval; - AccountInvestigator(GaiaCookieManagerService* cookie_service, - PrefService* pref_service, + AccountInvestigator(PrefService* pref_service, identity::IdentityManager* identity_manager); ~AccountInvestigator() override; @@ -56,14 +54,20 @@ class AccountInvestigator : public KeyedService, // KeyedService: void Shutdown() override; - // GaiaCookieManagerService::Observer: + // identity::IdentityManager::Observer: void OnAddAccountToCookieCompleted( const std::string& account_id, const GoogleServiceAuthError& error) override; + void OnAccountsInCookieUpdated( + const identity::AccountsInCookieJarInfo& accounts_in_cookie_jar_info, + const GoogleServiceAuthError& error) override; + + // Internal implementation of OnAccountsInCookieUpdated. It is public given + // that it is called directly by unittests. void OnGaiaAccountsInCookieUpdated( const std::vector<gaia::ListedAccount>& signed_in_accounts, const std::vector<gaia::ListedAccount>& signed_out_accounts, - const GoogleServiceAuthError& error) override; + const GoogleServiceAuthError& error); private: friend class AccountInvestigatorTest; @@ -114,7 +118,6 @@ class AccountInvestigator : public KeyedService, const std::vector<gaia::ListedAccount>& signed_out_accounts, signin_metrics::ReportingType type); - GaiaCookieManagerService* cookie_service_; PrefService* pref_service_; identity::IdentityManager* identity_manager_; diff --git a/chromium/components/signin/core/browser/account_investigator_unittest.cc b/chromium/components/signin/core/browser/account_investigator_unittest.cc index b72205b02c9..d8eaca6d908 100644 --- a/chromium/components/signin/core/browser/account_investigator_unittest.cc +++ b/chromium/components/signin/core/browser/account_investigator_unittest.cc @@ -8,20 +8,14 @@ #include <string> #include <vector> -#include "base/message_loop/message_loop.h" #include "base/run_loop.h" #include "base/test/metrics/histogram_tester.h" +#include "base/test/scoped_task_environment.h" #include "base/timer/timer.h" #include "components/prefs/pref_registry_simple.h" -#include "components/signin/core/browser/account_tracker_service.h" -#include "components/signin/core/browser/fake_gaia_cookie_manager_service.h" -#include "components/signin/core/browser/fake_signin_manager.h" -#include "components/signin/core/browser/profile_oauth2_token_service.h" #include "components/signin/core/browser/signin_metrics.h" #include "components/signin/core/browser/signin_pref_names.h" -#include "components/signin/core/browser/test_signin_client.h" #include "components/sync_preferences/testing_pref_service_syncable.h" -#include "google_apis/gaia/fake_oauth2_token_service_delegate.h" #include "google_apis/gaia/gaia_auth_util.h" #include "google_apis/gaia/google_service_auth_error.h" #include "services/identity/public/cpp/identity_test_environment.h" @@ -37,38 +31,18 @@ using signin_metrics::ReportingType; class AccountInvestigatorTest : public testing::Test { protected: AccountInvestigatorTest() - : signin_client_(&prefs_), - token_service_(&prefs_, - std::make_unique<FakeOAuth2TokenServiceDelegate>()), - signin_manager_(&signin_client_, - &token_service_, - &account_tracker_service_, - nullptr), - gaia_cookie_manager_service_(&token_service_, &signin_client_), - identity_test_env_(&account_tracker_service_, - &token_service_, - &signin_manager_, - &gaia_cookie_manager_service_), - investigator_(&gaia_cookie_manager_service_, - &prefs_, - identity_test_env_.identity_manager()) { - AccountTrackerService::RegisterPrefs(prefs_.registry()); + : identity_test_env_(&test_url_loader_factory_, &prefs_), + investigator_(&prefs_, identity_test_env_.identity_manager()) { AccountInvestigator::RegisterPrefs(prefs_.registry()); - SigninManagerBase::RegisterProfilePrefs(prefs_.registry()); - account_tracker_service_.Initialize(&prefs_, base::FilePath()); } ~AccountInvestigatorTest() override { investigator_.Shutdown(); } - FakeSigninManager* signin_manager() { return &signin_manager_; } identity::IdentityTestEnvironment* identity_test_env() { return &identity_test_env_; } PrefService* pref_service() { return &prefs_; } AccountInvestigator* investigator() { return &investigator_; } - FakeGaiaCookieManagerService* cookie_manager_service() { - return &gaia_cookie_manager_service_; - } // Wrappers to invoke private methods through friend class. TimeDelta Delay(const Time previous, @@ -82,15 +56,14 @@ class AccountInvestigatorTest : public testing::Test { signed_out_accounts); } AccountRelation Relation( - const AccountInfo& info, + const AccountInfo& account_info, const std::vector<ListedAccount>& signed_in_accounts, const std::vector<ListedAccount>& signed_out_accounts) { - return AccountInvestigator::DiscernRelation(info, - signed_in_accounts, - signed_out_accounts); + return AccountInvestigator::DiscernRelation( + account_info, signed_in_accounts, signed_out_accounts); } - void SharedReport(const std::vector<gaia::ListedAccount>& signed_in_accounts, - const std::vector<gaia::ListedAccount>& signed_out_accounts, + void SharedReport(const std::vector<ListedAccount>& signed_in_accounts, + const std::vector<ListedAccount>& signed_out_accounts, const Time now, const ReportingType type) { investigator_.SharedCookieJarReport(signed_in_accounts, signed_out_accounts, @@ -110,8 +83,7 @@ class AccountInvestigatorTest : public testing::Test { const AccountRelation expected) { HistogramTester histogram_tester; investigator_.SignedInAccountRelationReport(signed_in_accounts, - signed_out_accounts, - type); + signed_out_accounts, type); ExpectRelationReport(type, histogram_tester, expected); } @@ -162,13 +134,9 @@ class AccountInvestigatorTest : public testing::Test { private: // Timer needs a message loop. - base::MessageLoop message_loop_; + base::test::ScopedTaskEnvironment task_environment_; sync_preferences::TestingPrefServiceSyncable prefs_; - AccountTrackerService account_tracker_service_; - TestSigninClient signin_client_; - FakeProfileOAuth2TokenService token_service_; - FakeSigninManager signin_manager_; - FakeGaiaCookieManagerService gaia_cookie_manager_service_; + network::TestURLLoaderFactory test_url_loader_factory_; identity::IdentityTestEnvironment identity_test_env_; AccountInvestigator investigator_; std::map<ReportingType, std::string> suffix_ = { @@ -184,25 +152,30 @@ ListedAccount Account(const std::string& id) { return account; } -AccountInfo Info(const std::string& id) { - AccountInfo info; - info.account_id = id; - return info; +AccountInfo ToAccountInfo(ListedAccount account) { + AccountInfo account_info; + account_info.account_id = account.id; + account_info.gaia = account.gaia_id; + account_info.email = account.email; + return account_info; } // NOTE: IdentityTestEnvironment uses a prefix for generating gaia IDs: // "gaia_id_for_". For this reason, the tests prefix expected account IDs // used so that there is a match. -const std::vector<ListedAccount> no_accounts{}; -const std::vector<ListedAccount> just_one{Account("gaia_id_for_1_mail.com")}; -const std::vector<ListedAccount> just_two{Account("gaia_id_for_2_mail.com")}; -const std::vector<ListedAccount> both{Account("gaia_id_for_1_mail.com"), - Account("gaia_id_for_2_mail.com")}; -const std::vector<ListedAccount> both_reversed{ - Account("gaia_id_for_2_mail.com"), Account("gaia_id_for_1_mail.com")}; +const std::string kGaiaId1 = identity::GetTestGaiaIdForEmail("1@mail.com"); +const std::string kGaiaId2 = identity::GetTestGaiaIdForEmail("2@mail.com"); +const std::string kGaiaId3 = identity::GetTestGaiaIdForEmail("3@mail.com"); -const AccountInfo one(Info("gaia_id_for_1_mail.com")); -const AccountInfo three(Info("gaia_id_for_3_mail.com")); +const ListedAccount one(Account(kGaiaId1)); +const ListedAccount two(Account(kGaiaId2)); +const ListedAccount three(Account(kGaiaId3)); + +const std::vector<ListedAccount> no_accounts{}; +const std::vector<ListedAccount> just_one{one}; +const std::vector<ListedAccount> just_two{two}; +const std::vector<ListedAccount> both{one, two}; +const std::vector<ListedAccount> both_reversed{two, one}; TEST_F(AccountInvestigatorTest, CalculatePeriodicDelay) { const Time epoch; @@ -233,25 +206,25 @@ TEST_F(AccountInvestigatorTest, HashAccounts) { TEST_F(AccountInvestigatorTest, DiscernRelation) { EXPECT_EQ(AccountRelation::EMPTY_COOKIE_JAR, - Relation(one, no_accounts, no_accounts)); + Relation(ToAccountInfo(one), no_accounts, no_accounts)); EXPECT_EQ(AccountRelation::SINGLE_SIGNED_IN_MATCH_NO_SIGNED_OUT, - Relation(one, just_one, no_accounts)); + Relation(ToAccountInfo(one), just_one, no_accounts)); EXPECT_EQ(AccountRelation::SINGLE_SINGED_IN_MATCH_WITH_SIGNED_OUT, - Relation(one, just_one, just_two)); + Relation(ToAccountInfo(one), just_one, just_two)); EXPECT_EQ(AccountRelation::WITH_SIGNED_IN_NO_MATCH, - Relation(one, just_two, no_accounts)); + Relation(ToAccountInfo(one), just_two, no_accounts)); EXPECT_EQ(AccountRelation::ONE_OF_SIGNED_IN_MATCH_ANY_SIGNED_OUT, - Relation(one, both, just_one)); + Relation(ToAccountInfo(one), both, just_one)); EXPECT_EQ(AccountRelation::ONE_OF_SIGNED_IN_MATCH_ANY_SIGNED_OUT, - Relation(one, both, no_accounts)); + Relation(ToAccountInfo(one), both, no_accounts)); EXPECT_EQ(AccountRelation::NO_SIGNED_IN_ONE_OF_SIGNED_OUT_MATCH, - Relation(one, no_accounts, both)); + Relation(ToAccountInfo(one), no_accounts, both)); EXPECT_EQ(AccountRelation::NO_SIGNED_IN_SINGLE_SIGNED_OUT_MATCH, - Relation(one, no_accounts, just_one)); + Relation(ToAccountInfo(one), no_accounts, just_one)); EXPECT_EQ(AccountRelation::WITH_SIGNED_IN_ONE_OF_SIGNED_OUT_MATCH, - Relation(one, just_two, just_one)); + Relation(ToAccountInfo(one), just_two, just_one)); EXPECT_EQ(AccountRelation::NO_SIGNED_IN_WITH_SIGNED_OUT_NO_MATCH, - Relation(three, no_accounts, both)); + Relation(ToAccountInfo(three), no_accounts, both)); } TEST_F(AccountInvestigatorTest, SignedInAccountRelationReport) { @@ -330,7 +303,7 @@ TEST_F(AccountInvestigatorTest, // Simulate a sign out of the content area. const HistogramTester histogram_tester2; investigator()->OnGaiaAccountsInCookieUpdated( - no_accounts, just_one, GoogleServiceAuthError::AuthErrorNone()); + no_accounts, just_one, GoogleServiceAuthError::AuthErrorNone()); const AccountRelation expected_relation = AccountRelation::NO_SIGNED_IN_SINGLE_SIGNED_OUT_MATCH; ExpectSharedReportHistograms(ReportingType::ON_CHANGE, histogram_tester2, @@ -359,22 +332,23 @@ TEST_F(AccountInvestigatorTest, InitializeSignedIn) { TEST_F(AccountInvestigatorTest, TryPeriodicReportStale) { investigator()->Initialize(); - cookie_manager_service()->set_list_accounts_stale_for_testing(true); - cookie_manager_service()->SetListAccountsResponseOneAccount("f@bar.com", "1"); const HistogramTester histogram_tester; TryPeriodicReport(); EXPECT_TRUE(*periodic_pending()); EXPECT_EQ(0u, histogram_tester.GetTotalCountsForPrefix("Signin.").size()); - base::RunLoop().RunUntilIdle(); + std::string email("f@bar.com"); + identity_test_env()->SetCookieAccounts( + {{email, identity::GetTestGaiaIdForEmail(email)}}); + EXPECT_FALSE(*periodic_pending()); ExpectSharedReportHistograms(ReportingType::PERIODIC, histogram_tester, nullptr, 1, 0, 1, nullptr, false); } TEST_F(AccountInvestigatorTest, TryPeriodicReportEmpty) { - cookie_manager_service()->set_list_accounts_stale_for_testing(false); + identity_test_env()->SetFreshnessOfAccountsInGaiaCookie(false); const HistogramTester histogram_tester; TryPeriodicReport(); diff --git a/chromium/components/signin/core/browser/account_reconcilor.cc b/chromium/components/signin/core/browser/account_reconcilor.cc index 7113c3b1898..0393eb4ddda 100644 --- a/chromium/components/signin/core/browser/account_reconcilor.cc +++ b/chromium/components/signin/core/browser/account_reconcilor.cc @@ -21,14 +21,13 @@ #include "build/build_config.h" #include "components/signin/core/browser/account_consistency_method.h" #include "components/signin/core/browser/account_reconcilor_delegate.h" -#include "components/signin/core/browser/profile_oauth2_token_service.h" #include "components/signin/core/browser/signin_buildflags.h" #include "components/signin/core/browser/signin_client.h" #include "components/signin/core/browser/signin_metrics.h" #include "google_apis/gaia/gaia_auth_util.h" #include "google_apis/gaia/gaia_urls.h" #include "google_apis/gaia/google_service_auth_error.h" -#include "services/identity/public/cpp/identity_manager.h" +#include "services/identity/public/cpp/accounts_mutator.h" using signin::AccountReconcilorDelegate; @@ -72,7 +71,7 @@ std::vector<gaia::ListedAccount> FilterUnverifiedAccounts( // Revokes tokens for all accounts in chrome_accounts but the primary account. // Returns true if tokens were revoked, and false if the function did nothing. bool RevokeAllSecondaryTokens( - ProfileOAuth2TokenService* token_service, + identity::IdentityManager* identity_manager, signin::AccountReconcilorDelegate::RevokeTokenOption revoke_option, const std::string& primary_account, bool is_account_consistency_enforced, @@ -81,13 +80,16 @@ bool RevokeAllSecondaryTokens( if (revoke_option == AccountReconcilorDelegate::RevokeTokenOption::kDoNotRevoke) return false; - for (const std::string& account : token_service->GetAccounts()) { + for (const AccountInfo& account_info : + identity_manager->GetAccountsWithRefreshTokens()) { + std::string account(account_info.account_id); if (account == primary_account) continue; bool should_revoke = false; switch (revoke_option) { case AccountReconcilorDelegate::RevokeTokenOption::kRevokeIfInError: - if (token_service->RefreshTokenHasError(account)) { + if (identity_manager->HasAccountWithRefreshTokenInPersistentErrorState( + account)) { VLOG(1) << "Revoke token for " << account; should_revoke = true; } @@ -105,8 +107,10 @@ bool RevokeAllSecondaryTokens( if (should_revoke) { token_revoked = true; VLOG(1) << "Revoke token for " << account; - if (is_account_consistency_enforced) - token_service->RevokeCredentials(account, source); + if (is_account_consistency_enforced) { + auto* accounts_mutator = identity_manager->GetAccountsMutator(); + accounts_mutator->RemoveAccount(account, source); + } } } return token_revoked; @@ -154,14 +158,15 @@ std::string PickFirstGaiaAccount( } // namespace AccountReconcilor::Lock::Lock(AccountReconcilor* reconcilor) - : reconcilor_(reconcilor) { + : reconcilor_(reconcilor->weak_factory_.GetWeakPtr()) { DCHECK(reconcilor_); reconcilor_->IncrementLockCount(); } AccountReconcilor::Lock::~Lock() { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); - reconcilor_->DecrementLockCount(); + if (reconcilor_) + reconcilor_->DecrementLockCount(); } AccountReconcilor::ScopedSyncedDataDeletion::ScopedSyncedDataDeletion( @@ -180,23 +185,22 @@ AccountReconcilor::ScopedSyncedDataDeletion::~ScopedSyncedDataDeletion() { } AccountReconcilor::AccountReconcilor( - ProfileOAuth2TokenService* token_service, identity::IdentityManager* identity_manager, SigninClient* client, GaiaCookieManagerService* cookie_manager_service, std::unique_ptr<signin::AccountReconcilorDelegate> delegate) : delegate_(std::move(delegate)), - token_service_(token_service), identity_manager_(identity_manager), client_(client), cookie_manager_service_(cookie_manager_service), - registered_with_token_service_(false), + registered_with_identity_manager_(false), registered_with_cookie_manager_service_(false), registered_with_content_settings_(false), is_reconcile_started_(false), first_execution_(true), error_during_last_reconcile_(GoogleServiceAuthError::AuthErrorNone()), reconcile_is_noop_(true), + set_accounts_in_progress_(false), chrome_accounts_changed_(false), account_reconcilor_lock_count_(0), reconcile_on_unblock_(false), @@ -211,7 +215,7 @@ AccountReconcilor::AccountReconcilor( AccountReconcilor::~AccountReconcilor() { VLOG(1) << "AccountReconcilor::~AccountReconcilor"; // Make sure shutdown was called first. - DCHECK(!registered_with_token_service_); + DCHECK(!registered_with_identity_manager_); DCHECK(!registered_with_cookie_manager_service_); } @@ -221,7 +225,7 @@ void AccountReconcilor::Initialize(bool start_reconcile_if_tokens_available) { EnableReconcile(); // Start a reconcile if the tokens are already loaded. - if (start_reconcile_if_tokens_available && IsTokenServiceReady()) + if (start_reconcile_if_tokens_available && IsIdentityManagerReady()) StartReconcile(); } } @@ -236,13 +240,13 @@ void AccountReconcilor::EnableReconcile() { DCHECK(delegate_->IsReconcileEnabled()); RegisterWithCookieManagerService(); RegisterWithContentSettings(); - RegisterWithTokenService(); + RegisterWithIdentityManager(); } void AccountReconcilor::DisableReconcile(bool logout_all_accounts) { AbortReconcile(); UnregisterWithCookieManagerService(); - UnregisterWithTokenService(); + UnregisterWithIdentityManager(); UnregisterWithContentSettings(); if (logout_all_accounts) @@ -276,25 +280,25 @@ void AccountReconcilor::UnregisterWithContentSettings() { registered_with_content_settings_ = false; } -void AccountReconcilor::RegisterWithTokenService() { - VLOG(1) << "AccountReconcilor::RegisterWithTokenService"; +void AccountReconcilor::RegisterWithIdentityManager() { + VLOG(1) << "AccountReconcilor::RegisterWithIdentityManager"; // During re-auth, the reconcilor will get a callback about successful signin // even when the profile is already connected. Avoid re-registering // with the token service since this will DCHECK. - if (registered_with_token_service_) + if (registered_with_identity_manager_) return; - token_service_->AddObserver(this); - registered_with_token_service_ = true; + identity_manager_->AddObserver(this); + registered_with_identity_manager_ = true; } -void AccountReconcilor::UnregisterWithTokenService() { - VLOG(1) << "AccountReconcilor::UnregisterWithTokenService"; - if (!registered_with_token_service_) +void AccountReconcilor::UnregisterWithIdentityManager() { + VLOG(1) << "AccountReconcilor::UnregisterWithIdentityManager"; + if (!registered_with_identity_manager_) return; - token_service_->RemoveObserver(this); - registered_with_token_service_ = false; + identity_manager_->RemoveObserver(this); + registered_with_identity_manager_ = false; } void AccountReconcilor::RegisterWithCookieManagerService() { @@ -363,8 +367,8 @@ void AccountReconcilor::OnContentSettingChanged( StartReconcile(); } -void AccountReconcilor::OnEndBatchChanges() { - VLOG(1) << "AccountReconcilor::OnEndBatchChanges. " +void AccountReconcilor::OnEndBatchOfRefreshTokenStateChanges() { + VLOG(1) << "AccountReconcilor::OnEndBatchOfRefreshTokenStateChanges. " << "Reconcilor state: " << is_reconcile_started_; // Remember that accounts have changed if a reconcile is already started. chrome_accounts_changed_ = is_reconcile_started_; @@ -375,8 +379,8 @@ void AccountReconcilor::OnRefreshTokensLoaded() { StartReconcile(); } -void AccountReconcilor::OnAuthErrorChanged( - const std::string& account_id, +void AccountReconcilor::OnErrorStateOfRefreshTokenUpdatedForAccount( + const AccountInfo& account_info, const GoogleServiceAuthError& error) { // Gaia cookies may be invalidated server-side and the client does not get any // notification when this happens. @@ -403,7 +407,6 @@ void AccountReconcilor::PerformMergeAction(const std::string& account_id) { void AccountReconcilor::PerformSetCookiesAction( const signin::MultiloginParameters& parameters) { reconcile_is_noop_ = false; - is_reconcile_started_ = true; VLOG(1) << "AccountReconcilor::PerformSetCookiesAction: " << base::JoinString(parameters.accounts_to_send, " "); // TODO (https://crbug.com/890321): pass mode to GaiaCookieManagerService. @@ -437,7 +440,7 @@ void AccountReconcilor::StartReconcile() { } // Do not reconcile if tokens are not loaded yet. - if (!IsTokenServiceReady()) { + if (!IsIdentityManagerReady()) { VLOG(1) << "AccountReconcilor::StartReconcile: token service *not* ready yet."; return; @@ -453,18 +456,18 @@ void AccountReconcilor::StartReconcile() { reconcile_is_noop_ = true; if (!timeout_.is_max()) { - // Keep using base::Bind() until base::OnceCallback get supported by - // base::OneShotTimer. timer_->Start(FROM_HERE, timeout_, base::BindOnce(&AccountReconcilor::HandleReconcileTimeout, base::Unretained(this))); } const std::string& account_id = identity_manager_->GetPrimaryAccountId(); - if (token_service_->RefreshTokenHasError(account_id) && + if (identity_manager_->HasAccountWithRefreshTokenInPersistentErrorState( + account_id) && delegate_->ShouldAbortReconcileIfPrimaryHasError()) { VLOG(1) << "AccountReconcilor::StartReconcile: primary has error, abort."; - error_during_last_reconcile_ = token_service_->GetAuthError(account_id); + error_during_last_reconcile_ = + identity_manager_->GetErrorStateOfRefreshTokenForAccount(account_id); AbortReconcile(); return; } @@ -483,9 +486,11 @@ void AccountReconcilor::FinishReconcileWithMultiloginEndpoint( const std::vector<std::string>& chrome_accounts, std::vector<gaia::ListedAccount>&& gaia_accounts) { DCHECK(base::FeatureList::IsEnabled(kUseMultiloginEndpoint)); + DCHECK(!set_accounts_in_progress_); bool primary_has_error = - token_service_->RefreshTokenHasError(primary_account); + identity_manager_->HasAccountWithRefreshTokenInPersistentErrorState( + primary_account); const signin::MultiloginParameters parameters_for_multilogin = delegate_->CalculateParametersForMultilogin( @@ -498,7 +503,9 @@ void AccountReconcilor::FinishReconcileWithMultiloginEndpoint( // and any StartReconcile() calls that are made in the meantime will be // aborted until OnSetAccountsInCookieCompleted is called and // is_reconcile_started_ is set to false. + set_accounts_in_progress_ = true; PerformSetCookiesAction(parameters_for_multilogin); + DCHECK(is_reconcile_started_); } else { OnSetAccountsInCookieCompleted(GoogleServiceAuthError::AuthErrorNone()); DCHECK(!is_reconcile_started_); @@ -531,6 +538,18 @@ void AccountReconcilor::OnGaiaAccountsInCookieUpdated( << "Reconcilor's state is " << is_reconcile_started_ << ", " << "Error was " << error.ToString(); + // If cookies change while the reconcilor is running, ignore the changes and + // let it complete. Adding accounts to the cookie will trigger new + // notifications anyway, and these will be handled in a new reconciliation + // cycle. See https://crbug.com/923716 + if (IsMultiloginEndpointEnabled()) { + if (set_accounts_in_progress_) + return; + } else { + if (!add_to_cookie_.empty()) + return; + } + if (error.state() != GoogleServiceAuthError::NONE) { // We may have seen a series of errors during reconciliation. Delegates may // rely on the severity of the last seen error (see |OnReconcileError|) and @@ -562,12 +581,14 @@ void AccountReconcilor::OnGaiaAccountsInCookieUpdated( AccountReconcilorDelegate::RevokeTokenOption revoke_option = delegate_->ShouldRevokeSecondaryTokensBeforeReconcile( verified_gaia_accounts); - RevokeAllSecondaryTokens(token_service_, revoke_option, primary_account, true, + RevokeAllSecondaryTokens(identity_manager_, revoke_option, primary_account, + true, signin_metrics::SourceForRefreshTokenOperation:: kAccountReconcilor_GaiaCookiesUpdated); if (delegate_->ShouldAbortReconcileIfPrimaryHasError() && - token_service_->RefreshTokenHasError(primary_account)) { + identity_manager_->HasAccountWithRefreshTokenInPersistentErrorState( + primary_account)) { VLOG(1) << "Primary account has error, abort."; DCHECK(is_reconcile_started_); AbortReconcile(); @@ -591,17 +612,18 @@ void AccountReconcilor::OnGaiaCookieDeletedByUserAction() { const std::string& primary_account = identity_manager_->GetPrimaryAccountId(); // Revoke secondary tokens. RevokeAllSecondaryTokens( - token_service_, AccountReconcilorDelegate::RevokeTokenOption::kRevoke, + identity_manager_, AccountReconcilorDelegate::RevokeTokenOption::kRevoke, primary_account, /*account_consistency_enforced=*/true, signin_metrics::SourceForRefreshTokenOperation:: kAccountReconcilor_GaiaCookiesDeletedByUser); if (primary_account.empty()) return; - if (token_service_->RefreshTokenHasError(primary_account) || + if (identity_manager_->HasAccountWithRefreshTokenInPersistentErrorState( + primary_account) || synced_data_deletion_in_progress_count_ == 0) { // Invalidate the primary token, but do not revoke it. - token_service_->UpdateCredentials( - primary_account, OAuth2TokenServiceDelegate::kInvalidRefreshToken, + auto* accounts_mutator = identity_manager_->GetAccountsMutator(); + accounts_mutator->InvalidateRefreshTokenForPrimaryAccount( signin_metrics::SourceForRefreshTokenOperation:: kAccountReconcilor_GaiaCookiesDeletedByUser); } @@ -609,25 +631,30 @@ void AccountReconcilor::OnGaiaCookieDeletedByUserAction() { std::vector<std::string> AccountReconcilor::LoadValidAccountsFromTokenService() const { - std::vector<std::string> chrome_accounts = token_service_->GetAccounts(); + auto chrome_accounts_with_refresh_tokens = + identity_manager_->GetAccountsWithRefreshTokens(); + + std::vector<std::string> chrome_account_ids; // Remove any accounts that have an error. There is no point in trying to // reconcile them, since it won't work anyway. If the list ends up being // empty then don't reconcile any accounts. - for (auto i = chrome_accounts.begin(); i != chrome_accounts.end(); ++i) { - if (token_service_->RefreshTokenHasError(*i)) { - VLOG(1) << "AccountReconcilor::ValidateAccountsFromTokenService: " << *i + for (const auto& chrome_account_with_refresh_tokens : + chrome_accounts_with_refresh_tokens) { + if (identity_manager_->HasAccountWithRefreshTokenInPersistentErrorState( + chrome_account_with_refresh_tokens.account_id)) { + VLOG(1) << "AccountReconcilor::ValidateAccountsFromTokenService: " + << chrome_account_with_refresh_tokens.account_id << " has error, don't reconcile"; - i->clear(); + continue; } + chrome_account_ids.push_back(chrome_account_with_refresh_tokens.account_id); } - base::Erase(chrome_accounts, std::string()); - VLOG(1) << "AccountReconcilor::ValidateAccountsFromTokenService: " - << "Chrome " << chrome_accounts.size() << " accounts"; + << "Chrome " << chrome_account_ids.size() << " accounts"; - return chrome_accounts; + return chrome_account_ids; } void AccountReconcilor::OnReceivedManageAccountsResponse( @@ -676,8 +703,9 @@ void AccountReconcilor::FinishReconcile( if (first_account.empty()) { DCHECK(!delegate_->ShouldAbortReconcileIfPrimaryHasError()); reconcile_is_noop_ = !RevokeAllSecondaryTokens( - token_service_, AccountReconcilorDelegate::RevokeTokenOption::kRevoke, - primary_account, delegate_->IsAccountConsistencyEnforced(), + identity_manager_, + AccountReconcilorDelegate::RevokeTokenOption::kRevoke, primary_account, + delegate_->IsAccountConsistencyEnforced(), signin_metrics::SourceForRefreshTokenOperation:: kAccountReconcilor_Reconcile); } else { @@ -796,16 +824,16 @@ bool AccountReconcilor::MarkAccountAsAddedToCookie( return false; } -bool AccountReconcilor::IsTokenServiceReady() { +bool AccountReconcilor::IsIdentityManagerReady() { #if defined(OS_CHROMEOS) // TODO(droger): ChromeOS should use the same logic as other platforms. See // https://crbug.com/749535 - // On ChromeOS, there are cases where the token service is never fully + // On ChromeOS, there are cases where the IdentityManager is never fully // initialized and AreAllCredentialsLoaded() always return false. - return token_service_->AreAllCredentialsLoaded() || - (token_service_->GetAccounts().size() > 0); + return identity_manager_->AreRefreshTokensLoaded() || + (identity_manager_->GetAccountsWithRefreshTokens().size() > 0); #else - return token_service_->AreAllCredentialsLoaded(); + return identity_manager_->AreRefreshTokensLoaded(); #endif } @@ -819,6 +847,8 @@ void AccountReconcilor::OnSetAccountsInCookieCompleted( error_during_last_reconcile_ = error; delegate_->OnReconcileError(error_during_last_reconcile_); } + + set_accounts_in_progress_ = false; is_reconcile_started_ = false; timer_->Stop(); diff --git a/chromium/components/signin/core/browser/account_reconcilor.h b/chromium/components/signin/core/browser/account_reconcilor.h index c13ed7f9786..6cf7cbd91ce 100644 --- a/chromium/components/signin/core/browser/account_reconcilor.h +++ b/chromium/components/signin/core/browser/account_reconcilor.h @@ -28,26 +28,21 @@ #include "components/signin/core/browser/signin_header_helper.h" #include "components/signin/core/browser/signin_metrics.h" #include "google_apis/gaia/google_service_auth_error.h" -#include "google_apis/gaia/oauth2_token_service.h" +#include "services/identity/public/cpp/identity_manager.h" // Enables usage of Gaia Auth Multilogin endpoint for identity consistency. extern const base::Feature kUseMultiloginEndpoint; -namespace identity { -class IdentityManager; -} - namespace signin { class AccountReconcilorDelegate; } -class ProfileOAuth2TokenService; class SigninClient; class AccountReconcilor : public KeyedService, public content_settings::Observer, public GaiaCookieManagerService::Observer, - public OAuth2TokenService::Observer { + public identity::IdentityManager::Observer { public: // When an instance of this class exists, the account reconcilor is suspended. // It will automatically restart when all instances of Lock have been @@ -58,7 +53,7 @@ class AccountReconcilor : public KeyedService, ~Lock(); private: - AccountReconcilor* reconcilor_; + base::WeakPtr<AccountReconcilor> reconcilor_; THREAD_CHECKER(thread_checker_); DISALLOW_COPY_AND_ASSIGN(Lock); }; @@ -99,7 +94,6 @@ class AccountReconcilor : public KeyedService, }; AccountReconcilor( - ProfileOAuth2TokenService* token_service, identity::IdentityManager* identity_manager, SigninClient* client, GaiaCookieManagerService* cookie_manager_service, @@ -196,6 +190,8 @@ class AccountReconcilor : public KeyedService, FRIEND_TEST_ALL_PREFIXES(AccountReconcilorMirrorEndpointParamTest, GetAccountsFromCookieFailure); FRIEND_TEST_ALL_PREFIXES(AccountReconcilorMirrorEndpointParamTest, + ExtraCookieChangeNotification); + FRIEND_TEST_ALL_PREFIXES(AccountReconcilorMirrorEndpointParamTest, StartReconcileNoop); FRIEND_TEST_ALL_PREFIXES(AccountReconcilorMirrorEndpointParamTest, StartReconcileNoopWithDots); @@ -233,13 +229,13 @@ class AccountReconcilor : public KeyedService, void set_timer_for_testing(std::unique_ptr<base::OneShotTimer> timer); - bool IsRegisteredWithTokenService() const { - return registered_with_token_service_; + bool IsRegisteredWithIdentityManager() const { + return registered_with_identity_manager_; } // Register and unregister with dependent services. - void RegisterWithTokenService(); - void UnregisterWithTokenService(); + void RegisterWithIdentityManager(); + void UnregisterWithIdentityManager(); void RegisterWithCookieManagerService(); void UnregisterWithCookieManagerService(); void RegisterWithContentSettings(); @@ -269,15 +265,15 @@ class AccountReconcilor : public KeyedService, bool MarkAccountAsAddedToCookie(const std::string& account_id); // The reconcilor only starts when the token service is ready. - bool IsTokenServiceReady(); + bool IsIdentityManagerReady(); - // Overriden from content_settings::Observer. + // Overridden from content_settings::Observer. void OnContentSettingChanged(const ContentSettingsPattern& primary_pattern, const ContentSettingsPattern& secondary_pattern, ContentSettingsType content_type, const std::string& resource_identifier) override; - // Overriden from GaiaGookieManagerService::Observer. + // Overridden from GaiaGookieManagerService::Observer. void OnAddAccountToCookieCompleted( const std::string& account_id, const GoogleServiceAuthError& error) override; @@ -289,11 +285,12 @@ class AccountReconcilor : public KeyedService, const GoogleServiceAuthError& error) override; void OnGaiaCookieDeletedByUserAction() override; - // Overriden from OAuth2TokenService::Observer. - void OnEndBatchChanges() override; + // Overridden from identity::IdentityManager::Observer. + void OnEndBatchOfRefreshTokenStateChanges() override; void OnRefreshTokensLoaded() override; - void OnAuthErrorChanged(const std::string& account_id, - const GoogleServiceAuthError& error) override; + void OnErrorStateOfRefreshTokenUpdatedForAccount( + const AccountInfo& account_info, + const GoogleServiceAuthError& error) override; void FinishReconcileWithMultiloginEndpoint( const std::string& primary_account, @@ -314,9 +311,6 @@ class AccountReconcilor : public KeyedService, std::unique_ptr<signin::AccountReconcilorDelegate> delegate_; - // The ProfileOAuth2TokenService associated with this reconcilor. - ProfileOAuth2TokenService* token_service_; - // The IdentityManager associated with this reconcilor. identity::IdentityManager* identity_manager_; @@ -326,7 +320,7 @@ class AccountReconcilor : public KeyedService, // The GaiaCookieManagerService associated with this reconcilor. GaiaCookieManagerService* cookie_manager_service_; - bool registered_with_token_service_; + bool registered_with_identity_manager_; bool registered_with_cookie_manager_service_; bool registered_with_content_settings_; @@ -353,8 +347,8 @@ class AccountReconcilor : public KeyedService, bool reconcile_is_noop_; // Used during reconcile action. - // These members are used to validate the tokens in OAuth2TokenService. - std::vector<std::string> add_to_cookie_; + std::vector<std::string> add_to_cookie_; // Progress of AddAccount calls. + bool set_accounts_in_progress_; // Progress of SetAccounts calls. bool chrome_accounts_changed_; // Used for the Lock. diff --git a/chromium/components/signin/core/browser/account_reconcilor_unittest.cc b/chromium/components/signin/core/browser/account_reconcilor_unittest.cc index b4128da739d..0ac578b3a55 100644 --- a/chromium/components/signin/core/browser/account_reconcilor_unittest.cc +++ b/chromium/components/signin/core/browser/account_reconcilor_unittest.cc @@ -10,12 +10,12 @@ #include <utility> #include "base/macros.h" -#include "base/message_loop/message_loop.h" #include "base/run_loop.h" #include "base/scoped_observer.h" #include "base/strings/utf_string_conversions.h" #include "base/test/metrics/histogram_tester.h" #include "base/test/scoped_feature_list.h" +#include "base/test/scoped_task_environment.h" #include "base/time/time.h" #include "base/timer/mock_timer.h" #include "build/build_config.h" @@ -40,6 +40,8 @@ #include "google_apis/gaia/google_service_auth_error.h" #include "net/url_request/test_url_fetcher_factory.h" #include "services/identity/public/cpp/identity_test_environment.h" +#include "services/identity/public/cpp/identity_test_utils.h" +#include "services/identity/public/cpp/primary_account_mutator.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -118,13 +120,11 @@ class SpyReconcilorDelegate : public signin::AccountReconcilorDelegate { class DummyAccountReconcilorWithDelegate : public AccountReconcilor { public: DummyAccountReconcilorWithDelegate( - ProfileOAuth2TokenService* token_service, identity::IdentityManager* identity_manager, SigninClient* client, GaiaCookieManagerService* cookie_manager_service, signin::AccountConsistencyMethod account_consistency) : AccountReconcilor( - token_service, identity_manager, client, cookie_manager_service, @@ -140,13 +140,11 @@ class DummyAccountReconcilorWithDelegate : public AccountReconcilor { // Takes ownership of |delegate|. // gmock can't work with move only parameters. DummyAccountReconcilorWithDelegate( - ProfileOAuth2TokenService* token_service, identity::IdentityManager* identity_manager, SigninClient* client, GaiaCookieManagerService* cookie_manager_service, signin::AccountReconcilorDelegate* delegate) : AccountReconcilor( - token_service, identity_manager, client, cookie_manager_service, @@ -167,7 +165,6 @@ class DummyAccountReconcilorWithDelegate : public AccountReconcilor { return std::make_unique<signin::MirrorAccountReconcilorDelegate>( identity_manager); case signin::AccountConsistencyMethod::kDisabled: - case signin::AccountConsistencyMethod::kDiceFixAuthErrors: return std::make_unique<signin::AccountReconcilorDelegate>(); case signin::AccountConsistencyMethod::kDiceMigration: case signin::AccountConsistencyMethod::kDice: @@ -188,14 +185,12 @@ class MockAccountReconcilor : public testing::StrictMock<DummyAccountReconcilorWithDelegate> { public: explicit MockAccountReconcilor( - ProfileOAuth2TokenService* token_service, identity::IdentityManager* identity_manager, SigninClient* client, GaiaCookieManagerService* cookie_manager_service, signin::AccountConsistencyMethod account_consistency); explicit MockAccountReconcilor( - ProfileOAuth2TokenService* token_service, identity::IdentityManager* identity_manager, SigninClient* client, GaiaCookieManagerService* cookie_manager_service, @@ -208,26 +203,22 @@ class MockAccountReconcilor }; MockAccountReconcilor::MockAccountReconcilor( - ProfileOAuth2TokenService* token_service, identity::IdentityManager* identity_manager, SigninClient* client, GaiaCookieManagerService* cookie_manager_service, signin::AccountConsistencyMethod account_consistency) : testing::StrictMock<DummyAccountReconcilorWithDelegate>( - token_service, identity_manager, client, cookie_manager_service, account_consistency) {} MockAccountReconcilor::MockAccountReconcilor( - ProfileOAuth2TokenService* token_service, identity::IdentityManager* identity_manager, SigninClient* client, GaiaCookieManagerService* cookie_manager_service, std::unique_ptr<signin::AccountReconcilorDelegate> delegate) : testing::StrictMock<DummyAccountReconcilorWithDelegate>( - token_service, identity_manager, client, cookie_manager_service, @@ -242,6 +233,21 @@ struct Cookie { } }; +// Converts CookieParams to ListedAccounts. +gaia::ListedAccount ListedAccounfFromCookieParams( + const signin::CookieParams& params, + const std::string& account_id) { + gaia::ListedAccount listed_account; + listed_account.id = account_id; + listed_account.email = params.email; + listed_account.gaia_id = params.gaia_id; + listed_account.raw_email = params.email; + listed_account.valid = params.valid; + listed_account.signed_out = params.signed_out; + listed_account.verified = params.verified; + return listed_account; +} + } // namespace class AccountReconcilorTest : public ::testing::Test { @@ -249,8 +255,9 @@ class AccountReconcilorTest : public ::testing::Test { AccountReconcilorTest(); ~AccountReconcilorTest() override; - FakeSigninManagerForTesting* signin_manager() { return &signin_manager_; } - FakeProfileOAuth2TokenService* token_service() { return &token_service_; } + identity::IdentityTestEnvironment* identity_test_env() { + return &identity_test_env_; + } DiceTestSigninClient* test_signin_client() { return &test_signin_client_; } AccountTrackerService* account_tracker() { return &account_tracker_; } FakeGaiaCookieManagerService* cookie_manager_service() { @@ -262,8 +269,7 @@ class AccountReconcilorTest : public ::testing::Test { MockAccountReconcilor* GetMockReconcilor( std::unique_ptr<signin::AccountReconcilorDelegate> delegate); - std::string ConnectProfileToAccount(const std::string& gaia_id, - const std::string& username); + AccountInfo ConnectProfileToAccount(const std::string& email); std::string PickAccountIdForAccount(const std::string& gaia_id, const std::string& username); @@ -290,12 +296,13 @@ class AccountReconcilorTest : public ::testing::Test { void DeleteReconcilor() { mock_reconcilor_.reset(); } private: - base::MessageLoop loop; + base::test::ScopedTaskEnvironment task_environment_; signin::AccountConsistencyMethod account_consistency_; sync_preferences::TestingPrefServiceSyncable pref_service_; DiceTestSigninClient test_signin_client_; FakeProfileOAuth2TokenService token_service_; AccountTrackerService account_tracker_; + network::TestURLLoaderFactory test_url_loader_factory_; FakeGaiaCookieManagerService cookie_manager_service_; FakeSigninManagerForTesting signin_manager_; identity::IdentityTestEnvironment identity_test_env_; @@ -347,9 +354,10 @@ AccountReconcilorTest::AccountReconcilorTest() test_signin_client_(&pref_service_), token_service_(&pref_service_), cookie_manager_service_(&token_service_, - &test_signin_client_), + &test_signin_client_, + &test_url_loader_factory_), #if defined(OS_CHROMEOS) - signin_manager_(&test_signin_client_, &account_tracker_), + signin_manager_(&test_signin_client_, &token_service_, &account_tracker_), #else signin_manager_(&test_signin_client_, &token_service_, @@ -379,8 +387,8 @@ AccountReconcilorTest::AccountReconcilorTest() MockAccountReconcilor* AccountReconcilorTest::GetMockReconcilor() { if (!mock_reconcilor_) { mock_reconcilor_ = std::make_unique<MockAccountReconcilor>( - &token_service_, identity_test_env_.identity_manager(), - &test_signin_client_, &cookie_manager_service_, account_consistency_); + identity_test_env_.identity_manager(), &test_signin_client_, + &cookie_manager_service_, account_consistency_); } return mock_reconcilor_.get(); @@ -389,8 +397,8 @@ MockAccountReconcilor* AccountReconcilorTest::GetMockReconcilor() { MockAccountReconcilor* AccountReconcilorTest::GetMockReconcilor( std::unique_ptr<signin::AccountReconcilorDelegate> delegate) { mock_reconcilor_ = std::make_unique<MockAccountReconcilor>( - &token_service_, identity_test_env_.identity_manager(), - &test_signin_client_, &cookie_manager_service_, std::move(delegate)); + identity_test_env_.identity_manager(), &test_signin_client_, + &cookie_manager_service_, std::move(delegate)); return mock_reconcilor_.get(); } @@ -405,16 +413,11 @@ AccountReconcilorTest::~AccountReconcilorTest() { token_service_.Shutdown(); } -std::string AccountReconcilorTest::ConnectProfileToAccount( - const std::string& gaia_id, - const std::string& username) { - const std::string account_id = SeedAccountInfo(gaia_id, username); -#if !defined(OS_CHROMEOS) - signin_manager()->set_password("password"); -#endif - signin_manager()->SetAuthenticatedAccountInfo(gaia_id, username); - token_service()->UpdateCredentials(account_id, "refresh_token"); - return account_id; +AccountInfo AccountReconcilorTest::ConnectProfileToAccount( + const std::string& email) { + AccountInfo account_info = + identity_test_env()->MakePrimaryAccountAvailable(email); + return account_info; } std::string AccountReconcilorTest::PickAccountIdForAccount( @@ -522,9 +525,12 @@ class AccountReconcilorTestTable }; AccountReconcilorTestTable() { - accounts_['A'] = {"a@gmail.com", "A"}; - accounts_['B'] = {"b@gmail.com", "B"}; - accounts_['C'] = {"c@gmail.com", "C"}; + accounts_['A'] = {"a@gmail.com", + identity::GetTestGaiaIdForEmail("a@gmail.com")}; + accounts_['B'] = {"b@gmail.com", + identity::GetTestGaiaIdForEmail("b@gmail.com")}; + accounts_['C'] = {"c@gmail.com", + identity::GetTestGaiaIdForEmail("c@gmail.com")}; } // Build Tokens from string. @@ -569,21 +575,26 @@ class AccountReconcilorTestTable // Checks that the tokens in the TokenService match the tokens. void VerifyCurrentTokens(const std::vector<Token>& tokens) { - EXPECT_EQ(token_service()->GetAccounts().size(), tokens.size()); + auto* identity_manager = identity_test_env()->identity_manager(); + EXPECT_EQ(identity_manager->GetAccountsWithRefreshTokens().size(), + tokens.size()); + bool authenticated_account_found = false; for (const Token& token : tokens) { std::string account_id = PickAccountIdForAccount(token.gaia_id, token.email); - EXPECT_TRUE(token_service()->RefreshTokenIsAvailable(account_id)); - EXPECT_EQ(token.has_error, - token_service()->RefreshTokenHasError(account_id)); + EXPECT_TRUE(identity_manager->HasAccountWithRefreshToken(account_id)); + EXPECT_EQ( + token.has_error, + identity_manager->HasAccountWithRefreshTokenInPersistentErrorState( + account_id)); if (token.is_authenticated) { - EXPECT_EQ(account_id, signin_manager()->GetAuthenticatedAccountId()); + EXPECT_EQ(account_id, identity_manager->GetPrimaryAccountId()); authenticated_account_found = true; } } if (!authenticated_account_found) - EXPECT_EQ("", signin_manager()->GetAuthenticatedAccountId()); + EXPECT_EQ("", identity_manager->GetPrimaryAccountId()); } // Checks that reconcile is idempotent. @@ -620,10 +631,22 @@ class AccountReconcilorTestTable } void ConfigureCookieManagerService(const std::vector<Cookie>& cookies) { - std::vector<FakeGaiaCookieManagerService::CookieParams> cookie_params; + std::vector<signin::CookieParams> cookie_params; for (const auto& cookie : cookies) { std::string gaia_id = cookie.gaia_id; - cookie_params.push_back({accounts_[gaia_id[0]].email, gaia_id, + + // Figure the account token of this specific account id, + // ie 'A', 'B', or 'C'. + char account_key = '\0'; + for (const auto& account : accounts_) { + if (account.second.gaia_id == gaia_id) { + account_key = account.first; + break; + } + } + ASSERT_NE(account_key, '\0'); + + cookie_params.push_back({accounts_[account_key].email, gaia_id, cookie.is_valid, false /* signed_out */, true /* verified */}); } @@ -631,6 +654,10 @@ class AccountReconcilorTestTable cookie_manager_service()->set_list_accounts_stale_for_testing(true); } + std::string GaiaIdForAccountKey(char account_key) { + return accounts_[account_key].gaia_id; + } + std::map<char, Account> accounts_; }; @@ -643,17 +670,15 @@ class AccountReconcilorTestTable TEST_P(AccountReconcilorMirrorEndpointParamTest, SigninManagerRegistration) { AccountReconcilor* reconcilor = GetMockReconcilor(); ASSERT_TRUE(reconcilor); - ASSERT_FALSE(reconcilor->IsRegisteredWithTokenService()); + ASSERT_FALSE(reconcilor->IsRegisteredWithIdentityManager()); - account_tracker()->SeedAccountInfo("12345", "user@gmail.com"); - signin_manager()->SignIn("12345", "user@gmail.com", "password"); - ASSERT_TRUE(reconcilor->IsRegisteredWithTokenService()); + identity_test_env()->MakePrimaryAccountAvailable("user@gmail.com"); + ASSERT_TRUE(reconcilor->IsRegisteredWithIdentityManager()); EXPECT_CALL(*GetMockReconcilor(), PerformLogoutAllAccountsAction()); - signin_manager()->SignOut(signin_metrics::SIGNOUT_TEST, - signin_metrics::SignoutDelete::IGNORE_METRIC); - ASSERT_FALSE(reconcilor->IsRegisteredWithTokenService()); + identity_test_env()->ClearPrimaryAccount(); + ASSERT_FALSE(reconcilor->IsRegisteredWithIdentityManager()); } // This method requires the use of the |TestSigninClient| to be created from the @@ -662,25 +687,29 @@ TEST_P(AccountReconcilorMirrorEndpointParamTest, SigninManagerRegistration) { // causes the try_bots to time out. TEST_P(AccountReconcilorMirrorEndpointParamTest, Reauth) { const std::string email = "user@gmail.com"; - const std::string account_id = ConnectProfileToAccount("12345", email); + AccountInfo account_info = ConnectProfileToAccount(email); AccountReconcilor* reconcilor = GetMockReconcilor(); ASSERT_TRUE(reconcilor); - ASSERT_TRUE(reconcilor->IsRegisteredWithTokenService()); + ASSERT_TRUE(reconcilor->IsRegisteredWithIdentityManager()); // Simulate reauth. The state of the reconcilor should not change. - signin_manager()->OnExternalSigninCompleted(email); - ASSERT_TRUE(reconcilor->IsRegisteredWithTokenService()); + auto* account_mutator = + identity_test_env()->identity_manager()->GetPrimaryAccountMutator(); + DCHECK(account_mutator); + account_mutator->SetPrimaryAccount(account_info.account_id); + + ASSERT_TRUE(reconcilor->IsRegisteredWithIdentityManager()); } #endif // !defined(OS_CHROMEOS) TEST_P(AccountReconcilorMirrorEndpointParamTest, ProfileAlreadyConnected) { - ConnectProfileToAccount("12345", "user@gmail.com"); + ConnectProfileToAccount("user@gmail.com"); AccountReconcilor* reconcilor = GetMockReconcilor(); ASSERT_TRUE(reconcilor); - ASSERT_TRUE(reconcilor->IsRegisteredWithTokenService()); + ASSERT_TRUE(reconcilor->IsRegisteredWithIdentityManager()); } #if BUILDFLAG(ENABLE_DICE_SUPPORT) @@ -901,15 +930,18 @@ TEST_P(AccountReconcilorTestTable, TableRowTest) { std::vector<Token> tokens_before_reconcile = ParseTokenString(GetParam().tokens); for (const Token& token : tokens_before_reconcile) { - std::string account_id = SeedAccountInfo(token.gaia_id, token.email); - if (token.is_authenticated) - ConnectProfileToAccount(token.gaia_id, token.email); - else - token_service()->UpdateCredentials(account_id, "refresh_token"); + std::string account_id; + if (token.is_authenticated) { + account_id = ConnectProfileToAccount(token.email).account_id; + } else { + account_id = SeedAccountInfo(token.gaia_id, token.email); + identity_test_env()->SetRefreshTokenForAccount(account_id); + } if (token.has_error) { - token_service()->UpdateAuthErrorForTesting( - account_id, GoogleServiceAuthError( - GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS)); + identity::UpdatePersistentErrorOfRefreshTokenForAccount( + identity_test_env()->identity_manager(), account_id, + GoogleServiceAuthError( + GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS)); } } VerifyCurrentTokens(tokens_before_reconcile); @@ -934,12 +966,14 @@ TEST_P(AccountReconcilorTestTable, TableRowTest) { continue; } std::string cookie(1, GetParam().gaia_api_calls[i]); - EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(cookie)).Times(1); + std::string account_id_for_cookie = GaiaIdForAccountKey(cookie[0]); + EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(account_id_for_cookie)) + .Times(1); // MergeSession fixes an existing cookie or appends it at the end. auto it = std::find(cookies.begin(), cookies.end(), - Cookie{cookie, false /* is_valid */}); + Cookie{account_id_for_cookie, false /* is_valid */}); if (it == cookies.end()) - cookies.push_back({cookie, true}); + cookies.push_back({account_id_for_cookie, true}); else it->is_valid = true; } @@ -1015,17 +1049,18 @@ TEST_P(AccountReconcilorTestDiceMultilogin, TableRowTest) { ParseTokenString(GetParam().tokens); Token primary_account; for (const Token& token : tokens_before_reconcile) { - std::string account_id = SeedAccountInfo(token.gaia_id, token.email); + std::string account_id; if (token.is_authenticated) { - primary_account = token; - ConnectProfileToAccount(token.gaia_id, token.email); + account_id = ConnectProfileToAccount(token.email).account_id; } else { - token_service()->UpdateCredentials(account_id, "refresh_token"); + account_id = SeedAccountInfo(token.gaia_id, token.email); + identity_test_env()->SetRefreshTokenForAccount(account_id); } if (token.has_error) { - token_service()->UpdateAuthErrorForTesting( - account_id, GoogleServiceAuthError( - GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS)); + identity::UpdatePersistentErrorOfRefreshTokenForAccount( + identity_test_env()->identity_manager(), account_id, + GoogleServiceAuthError( + GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS)); } } VerifyCurrentTokens(tokens_before_reconcile); @@ -1114,11 +1149,10 @@ class AccountReconcilorDiceEndpointParamTest TEST_P(AccountReconcilorDiceEndpointParamTest, DiceTokenServiceRegistration) { AccountReconcilor* reconcilor = GetMockReconcilor(); ASSERT_TRUE(reconcilor); - ASSERT_TRUE(reconcilor->IsRegisteredWithTokenService()); + ASSERT_TRUE(reconcilor->IsRegisteredWithIdentityManager()); - account_tracker()->SeedAccountInfo("12345", "user@gmail.com"); - signin_manager()->SignIn("12345", "user@gmail.com", "password"); - ASSERT_TRUE(reconcilor->IsRegisteredWithTokenService()); + identity_test_env()->MakePrimaryAccountAvailable("user@gmail.com"); + ASSERT_TRUE(reconcilor->IsRegisteredWithIdentityManager()); // Reconcilor should not logout all accounts from the cookies when // SigninManager signs out. @@ -1126,16 +1160,15 @@ TEST_P(AccountReconcilorDiceEndpointParamTest, DiceTokenServiceRegistration) { EXPECT_CALL(*GetMockReconcilor(), PerformSetCookiesAction(::testing::_)) .Times(0); - signin_manager()->SignOut(signin_metrics::SIGNOUT_TEST, - signin_metrics::SignoutDelete::IGNORE_METRIC); - ASSERT_TRUE(reconcilor->IsRegisteredWithTokenService()); + identity_test_env()->ClearPrimaryAccount(); + ASSERT_TRUE(reconcilor->IsRegisteredWithIdentityManager()); } // Tests that reconcile starts even when Sync is not enabled. TEST_P(AccountReconcilorDiceEndpointParamTest, DiceReconcileWithoutSignin) { // Add a token in Chrome but do not sign in. - const std::string account_id = SeedAccountInfo("12345", "user@gmail.com"); - token_service()->UpdateCredentials(account_id, "refresh_token"); + const std::string account_id = + identity_test_env()->MakeAccountAvailable("user@gmail.com").account_id; cookie_manager_service()->SetListAccountsResponseNoAccounts(); if (!IsMultiloginEnabled()) { @@ -1186,19 +1219,24 @@ TEST_P(AccountReconcilorDiceEndpointParamTest, DiceReconcileNoop) { TEST_P(AccountReconcilorDiceEndpointParamTest, DiceReconcileReuseGaiaFirstAccount) { // Add accounts 1 and 2 to the token service. - const std::string account_id_1 = SeedAccountInfo("12345", "user@gmail.com"); - const std::string account_id_2 = SeedAccountInfo("67890", "other@gmail.com"); - token_service()->UpdateCredentials(account_id_1, "refresh_token"); - token_service()->UpdateCredentials(account_id_2, "refresh_token"); - - ASSERT_EQ(2u, token_service()->GetAccounts().size()); - ASSERT_EQ(account_id_1, token_service()->GetAccounts()[0]); - ASSERT_EQ(account_id_2, token_service()->GetAccounts()[1]); + const AccountInfo account_info_1 = + identity_test_env()->MakeAccountAvailable("user@gmail.com"); + const std::string account_id_1 = account_info_1.account_id; + const AccountInfo account_info_2 = + identity_test_env()->MakeAccountAvailable("other@gmail.com"); + const std::string account_id_2 = account_info_2.account_id; + + auto* identity_manager = identity_test_env()->identity_manager(); + std::vector<AccountInfo> accounts = + identity_manager->GetAccountsWithRefreshTokens(); + ASSERT_EQ(2u, accounts.size()); + ASSERT_TRUE(identity_manager->HasAccountWithRefreshToken(account_id_1)); + ASSERT_TRUE(identity_manager->HasAccountWithRefreshToken(account_id_2)); // Add accounts 2 and 3 to the Gaia cookie. const std::string account_id_3 = SeedAccountInfo("9999", "foo@gmail.com"); cookie_manager_service()->SetListAccountsResponseTwoAccounts( - "other@gmail.com", "67890", "foo@gmail.com", "9999"); + account_info_2.email, account_info_2.gaia, "foo@gmail.com", "9999"); if (!IsMultiloginEnabled()) { testing::InSequence mock_sequence; @@ -1207,7 +1245,7 @@ TEST_P(AccountReconcilorDiceEndpointParamTest, EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(account_id_2)); EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(account_id_1)); } else { - std::vector<std::string> accounts_to_send = {account_id_1, account_id_2}; + std::vector<std::string> accounts_to_send = {account_id_2, account_id_1}; // Send accounts to Gaia in any order, it will determine the order itself in // PRESERVE order. const signin::MultiloginParameters params( @@ -1237,16 +1275,22 @@ TEST_P(AccountReconcilorDiceEndpointParamTest, // lost. TEST_P(AccountReconcilorDiceEndpointParamTest, DiceLastKnownFirstAccount) { // Add accounts to the token service and the Gaia cookie in a different order. - const std::string account_id_1 = SeedAccountInfo("12345", "user@gmail.com"); - const std::string account_id_2 = SeedAccountInfo("67890", "other@gmail.com"); + AccountInfo account_info_1 = + identity_test_env()->MakeAccountAvailable("user@gmail.com"); + const std::string account_id_1 = account_info_1.account_id; + AccountInfo account_info_2 = + identity_test_env()->MakeAccountAvailable("other@gmail.com"); + const std::string account_id_2 = account_info_2.account_id; cookie_manager_service()->SetListAccountsResponseTwoAccounts( - "other@gmail.com", "67890", "user@gmail.com", "12345"); - token_service()->UpdateCredentials(account_id_1, "refresh_token"); - token_service()->UpdateCredentials(account_id_2, "refresh_token"); + account_info_2.email, account_info_2.gaia, account_info_1.email, + account_info_1.gaia); - ASSERT_EQ(2u, token_service()->GetAccounts().size()); - ASSERT_EQ(account_id_1, token_service()->GetAccounts()[0]); - ASSERT_EQ(account_id_2, token_service()->GetAccounts()[1]); + auto* identity_manager = identity_test_env()->identity_manager(); + std::vector<AccountInfo> accounts = + identity_manager->GetAccountsWithRefreshTokens(); + ASSERT_EQ(2u, accounts.size()); + ASSERT_TRUE(identity_manager->HasAccountWithRefreshToken(account_id_1)); + ASSERT_TRUE(identity_manager->HasAccountWithRefreshToken(account_id_2)); // Do one reconcile. It should do nothing but to populating the last known // account. @@ -1337,8 +1381,7 @@ TEST_P(AccountReconcilorDiceEndpointParamTest, UnverifiedAccountMerge) { // Add a token to Chrome. const std::string chrome_account_id = - SeedAccountInfo("67890", "other@gmail.com"); - token_service()->UpdateCredentials(chrome_account_id, "refresh_token"); + identity_test_env()->MakeAccountAvailable("other@gmail.com").account_id; if (!IsMultiloginEnabled()) { // Check that the Chrome account is merged and the unverified account is not @@ -1379,10 +1422,10 @@ TEST_P(AccountReconcilorDiceEndpointParamTest, DiceMigrationAfterNoop) { pref_service()->SetBoolean(prefs::kTokenServiceDiceCompatible, true); // Chrome account is consistent with the cookie. - const std::string account_id = SeedAccountInfo("12345", "user@gmail.com"); - token_service()->UpdateCredentials(account_id, "refresh_token"); - cookie_manager_service()->SetListAccountsResponseOneAccount("user@gmail.com", - "12345"); + AccountInfo account_info = + identity_test_env()->MakeAccountAvailable("user@gmail.com"); + cookie_manager_service()->SetListAccountsResponseOneAccount( + account_info.email, account_info.gaia); AccountReconcilor* reconcilor = GetMockReconcilor(); // Dice is not enabled by default. EXPECT_FALSE(reconcilor->delegate_->IsAccountConsistencyEnforced()); @@ -1409,10 +1452,10 @@ TEST_P(AccountReconcilorDiceEndpointParamTest, SetAccountConsistency(signin::AccountConsistencyMethod::kDiceMigration); // Chrome account is consistent with the cookie. - const std::string account_id = SeedAccountInfo("12345", "user@gmail.com"); - token_service()->UpdateCredentials(account_id, "refresh_token"); - cookie_manager_service()->SetListAccountsResponseOneAccount("user@gmail.com", - "12345"); + AccountInfo account_info = + identity_test_env()->MakeAccountAvailable("user@gmail.com"); + cookie_manager_service()->SetListAccountsResponseOneAccount( + account_info.email, account_info.gaia); AccountReconcilor* reconcilor = GetMockReconcilor(); // Dice is not enabled by default. EXPECT_FALSE(reconcilor->delegate_->IsAccountConsistencyEnforced()); @@ -1440,7 +1483,7 @@ TEST_P(AccountReconcilorDiceEndpointParamTest, DiceNoMigrationAfterReconcile) { // Add a token in Chrome. const std::string account_id = - ConnectProfileToAccount("12345", "user@gmail.com"); + ConnectProfileToAccount("user@gmail.com").account_id; cookie_manager_service()->SetListAccountsResponseNoAccounts(); AccountReconcilor* reconcilor = GetMockReconcilor(); @@ -1480,12 +1523,14 @@ TEST_P(AccountReconcilorDiceEndpointParamTest, MigrationClearSecondaryTokens) { // Add a tokens in Chrome, signin to Sync, but no Gaia cookies. const std::string account_id_1 = - ConnectProfileToAccount("12345", "user@gmail.com"); - const std::string account_id_2 = SeedAccountInfo("67890", "other@gmail.com"); - token_service()->UpdateCredentials(account_id_2, "refresh_token"); + ConnectProfileToAccount("user@gmail.com").account_id; + const std::string account_id_2 = + identity_test_env()->MakeAccountAvailable("other@gmail.com").account_id; cookie_manager_service()->SetListAccountsResponseNoAccounts(); - ASSERT_TRUE(token_service()->RefreshTokenIsAvailable(account_id_1)); - ASSERT_TRUE(token_service()->RefreshTokenIsAvailable(account_id_2)); + + auto* identity_manager = identity_test_env()->identity_manager(); + ASSERT_TRUE(identity_manager->HasAccountWithRefreshToken(account_id_1)); + ASSERT_TRUE(identity_manager->HasAccountWithRefreshToken(account_id_2)); // Reconcile should revoke the secondary account. AccountReconcilor* reconcilor = GetMockReconcilor(); @@ -1512,8 +1557,8 @@ TEST_P(AccountReconcilorDiceEndpointParamTest, MigrationClearSecondaryTokens) { ASSERT_FALSE(reconcilor->is_reconcile_started_); ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_OK, reconcilor->GetState()); - EXPECT_TRUE(token_service()->RefreshTokenIsAvailable(account_id_1)); - EXPECT_FALSE(token_service()->RefreshTokenIsAvailable(account_id_2)); + EXPECT_TRUE(identity_manager->HasAccountWithRefreshToken(account_id_1)); + EXPECT_FALSE(identity_manager->HasAccountWithRefreshToken(account_id_2)); // Profile was not migrated. EXPECT_FALSE(test_signin_client()->is_ready_for_dice_migration()); @@ -1527,13 +1572,15 @@ TEST_P(AccountReconcilorDiceEndpointParamTest, MigrationClearAllTokens) { pref_service()->SetBoolean(prefs::kTokenServiceDiceCompatible, true); // Add a tokens in Chrome but no Gaia cookies. - const std::string account_id_1 = SeedAccountInfo("12345", "user@gmail.com"); - const std::string account_id_2 = SeedAccountInfo("67890", "other@gmail.com"); - token_service()->UpdateCredentials(account_id_1, "refresh_token"); - token_service()->UpdateCredentials(account_id_2, "refresh_token"); + const std::string account_id_1 = + identity_test_env()->MakeAccountAvailable("user@gmail.com").account_id; + const std::string account_id_2 = + identity_test_env()->MakeAccountAvailable("other@gmail.com").account_id; cookie_manager_service()->SetListAccountsResponseNoAccounts(); - ASSERT_TRUE(token_service()->RefreshTokenIsAvailable(account_id_1)); - ASSERT_TRUE(token_service()->RefreshTokenIsAvailable(account_id_2)); + + auto* identity_manager = identity_test_env()->identity_manager(); + ASSERT_TRUE(identity_manager->HasAccountWithRefreshToken(account_id_1)); + ASSERT_TRUE(identity_manager->HasAccountWithRefreshToken(account_id_2)); // Reconcile should revoke the secondary account. AccountReconcilor* reconcilor = GetMockReconcilor(); @@ -1543,8 +1590,8 @@ TEST_P(AccountReconcilorDiceEndpointParamTest, MigrationClearAllTokens) { ASSERT_FALSE(reconcilor->is_reconcile_started_); ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_OK, reconcilor->GetState()); - EXPECT_FALSE(token_service()->RefreshTokenIsAvailable(account_id_1)); - EXPECT_FALSE(token_service()->RefreshTokenIsAvailable(account_id_2)); + EXPECT_FALSE(identity_manager->HasAccountWithRefreshToken(account_id_1)); + EXPECT_FALSE(identity_manager->HasAccountWithRefreshToken(account_id_2)); // Profile is now ready for migration on next startup. base::RunLoop().RunUntilIdle(); @@ -1559,17 +1606,22 @@ TEST_F(AccountReconcilorTest, DiceDeleteCookie) { SetAccountConsistency(signin::AccountConsistencyMethod::kDice); const std::string primary_account_id = - account_tracker()->SeedAccountInfo("12345", "user@gmail.com"); - token_service()->UpdateCredentials(primary_account_id, "refresh_token"); - signin_manager()->SignIn("12345", "user@gmail.com", "password"); + identity_test_env() + ->MakePrimaryAccountAvailable("user@gmail.com") + .account_id; const std::string secondary_account_id = - SeedAccountInfo("67890", "other@gmail.com"); - token_service()->UpdateCredentials(secondary_account_id, "refresh_token"); + identity_test_env()->MakeAccountAvailable("other@gmail.com").account_id; - ASSERT_TRUE(token_service()->RefreshTokenIsAvailable(primary_account_id)); - ASSERT_FALSE(token_service()->RefreshTokenHasError(primary_account_id)); - ASSERT_TRUE(token_service()->RefreshTokenIsAvailable(secondary_account_id)); - ASSERT_FALSE(token_service()->RefreshTokenHasError(secondary_account_id)); + auto* identity_manager = identity_test_env()->identity_manager(); + ASSERT_TRUE(identity_manager->HasAccountWithRefreshToken(primary_account_id)); + ASSERT_FALSE( + identity_manager->HasAccountWithRefreshTokenInPersistentErrorState( + primary_account_id)); + ASSERT_TRUE( + identity_manager->HasAccountWithRefreshToken(secondary_account_id)); + ASSERT_FALSE( + identity_manager->HasAccountWithRefreshTokenInPersistentErrorState( + secondary_account_id)); AccountReconcilor* reconcilor = GetMockReconcilor(); @@ -1578,30 +1630,38 @@ TEST_F(AccountReconcilorTest, DiceDeleteCookie) { std::unique_ptr<AccountReconcilor::ScopedSyncedDataDeletion> deletion = reconcilor->GetScopedSyncDataDeletion(); reconcilor->OnGaiaCookieDeletedByUserAction(); - EXPECT_TRUE(token_service()->RefreshTokenIsAvailable(primary_account_id)); - EXPECT_FALSE(token_service()->RefreshTokenHasError(primary_account_id)); + EXPECT_TRUE( + identity_manager->HasAccountWithRefreshToken(primary_account_id)); + EXPECT_FALSE( + identity_manager->HasAccountWithRefreshTokenInPersistentErrorState( + primary_account_id)); EXPECT_FALSE( - token_service()->RefreshTokenIsAvailable(secondary_account_id)); + identity_manager->HasAccountWithRefreshToken(secondary_account_id)); } - token_service()->UpdateCredentials(secondary_account_id, "refresh_token"); + identity_test_env()->SetRefreshTokenForAccount(secondary_account_id); reconcilor->OnGaiaCookieDeletedByUserAction(); // Without scoped deletion, the primary token is also invalidated. - EXPECT_TRUE(token_service()->RefreshTokenIsAvailable(primary_account_id)); - EXPECT_TRUE(token_service()->RefreshTokenHasError(primary_account_id)); + EXPECT_TRUE(identity_manager->HasAccountWithRefreshToken(primary_account_id)); + EXPECT_TRUE( + identity_manager->HasAccountWithRefreshTokenInPersistentErrorState( + primary_account_id)); EXPECT_EQ(GoogleServiceAuthError::InvalidGaiaCredentialsReason:: CREDENTIALS_REJECTED_BY_CLIENT, - token_service() - ->GetAuthError(primary_account_id) + identity_manager + ->GetErrorStateOfRefreshTokenForAccount(primary_account_id) .GetInvalidGaiaCredentialsReason()); - EXPECT_FALSE(token_service()->RefreshTokenIsAvailable(secondary_account_id)); + EXPECT_FALSE( + identity_manager->HasAccountWithRefreshToken(secondary_account_id)); // If the primary account has an error, always revoke it. - token_service()->UpdateCredentials(primary_account_id, "refresh_token"); - ASSERT_FALSE(token_service()->RefreshTokenHasError(primary_account_id)); - token_service()->UpdateAuthErrorForTesting( - primary_account_id, + identity_test_env()->SetRefreshTokenForAccount(primary_account_id); + EXPECT_FALSE( + identity_manager->HasAccountWithRefreshTokenInPersistentErrorState( + primary_account_id)); + identity::UpdatePersistentErrorOfRefreshTokenForAccount( + identity_test_env()->identity_manager(), primary_account_id, GoogleServiceAuthError::FromInvalidGaiaCredentialsReason( GoogleServiceAuthError::InvalidGaiaCredentialsReason:: CREDENTIALS_REJECTED_BY_SERVER)); @@ -1611,8 +1671,8 @@ TEST_F(AccountReconcilorTest, DiceDeleteCookie) { reconcilor->OnGaiaCookieDeletedByUserAction(); EXPECT_EQ(GoogleServiceAuthError::InvalidGaiaCredentialsReason:: CREDENTIALS_REJECTED_BY_CLIENT, - token_service() - ->GetAuthError(primary_account_id) + identity_manager + ->GetErrorStateOfRefreshTokenForAccount(primary_account_id) .GetInvalidGaiaCredentialsReason()); } } @@ -1687,15 +1747,18 @@ TEST_P(AccountReconcilorTestMirrorMultilogin, TableRowTest) { std::vector<Token> tokens_before_reconcile = ParseTokenString(GetParam().tokens); for (const Token& token : tokens_before_reconcile) { - std::string account_id = SeedAccountInfo(token.gaia_id, token.email); - if (token.is_authenticated) - ConnectProfileToAccount(token.gaia_id, token.email); - else - token_service()->UpdateCredentials(account_id, "refresh_token"); + std::string account_id; + if (token.is_authenticated) { + account_id = ConnectProfileToAccount(token.email).account_id; + } else { + account_id = SeedAccountInfo(token.gaia_id, token.email); + identity_test_env()->SetRefreshTokenForAccount(account_id); + } if (token.has_error) { - token_service()->UpdateAuthErrorForTesting( - account_id, GoogleServiceAuthError( - GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS)); + identity::UpdatePersistentErrorOfRefreshTokenForAccount( + identity_test_env()->identity_manager(), account_id, + GoogleServiceAuthError( + GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS)); } } VerifyCurrentTokens(tokens_before_reconcile); @@ -1715,8 +1778,8 @@ TEST_P(AccountReconcilorTestMirrorMultilogin, TableRowTest) { if (GetParam().gaia_api_calls[i] == 'M') { std::vector<std::string> accounts_to_send; for (int i = 0; GetParam().cookies_after_reconcile[i] != '\0'; ++i) { - std::string account_to_send = - std::string(1, GetParam().cookies_after_reconcile[i]); + char cookie = GetParam().cookies_after_reconcile[i]; + std::string account_to_send = GaiaIdForAccountKey(cookie); accounts_to_send.push_back(PickAccountIdForAccount( account_to_send, accounts_[GetParam().cookies_after_reconcile[i]].email)); @@ -1769,9 +1832,9 @@ INSTANTIATE_TEST_CASE_P( // automatically started when tokens are loaded. TEST_P(AccountReconcilorMirrorEndpointParamTest, TokensNotLoaded) { const std::string account_id = - ConnectProfileToAccount("12345", "user@gmail.com"); + ConnectProfileToAccount("user@gmail.com").account_id; cookie_manager_service()->SetListAccountsResponseNoAccounts(); - token_service()->set_all_credentials_loaded_for_testing(false); + identity_test_env()->ResetToAccountsNotYetLoadedFromDiskState(); AccountReconcilor* reconcilor = GetMockReconcilor(); reconcilor->StartReconcile(); @@ -1781,7 +1844,7 @@ TEST_P(AccountReconcilorMirrorEndpointParamTest, TokensNotLoaded) { // can start as long as the token service is not empty. ASSERT_FALSE(reconcilor->is_reconcile_started_); // When tokens are loaded, reconcile starts automatically. - token_service()->LoadCredentials(account_id); + identity_test_env()->identity_manager()->LegacyLoadCredentials(account_id); #endif if (!IsMultiloginEnabled()) { @@ -1807,11 +1870,11 @@ TEST_P(AccountReconcilorMirrorEndpointParamTest, TokensNotLoaded) { } TEST_P(AccountReconcilorMirrorEndpointParamTest, GetAccountsFromCookieSuccess) { - const std::string account_id = - ConnectProfileToAccount("12345", "user@gmail.com"); + AccountInfo account_info = ConnectProfileToAccount("user@gmail.com"); + const std::string account_id = account_info.account_id; cookie_manager_service()->SetListAccountsResponseOneAccountWithParams( - {"user@gmail.com", "12345", false /* valid */, false /* signed_out */, - true /* verified */}); + {account_info.email, account_info.gaia, false /* valid */, + false /* signed_out */, true /* verified */}); if (!IsMultiloginEnabled()) { EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(account_id)); @@ -1842,8 +1905,7 @@ TEST_P(AccountReconcilorMirrorEndpointParamTest, GetAccountsFromCookieSuccess) { } TEST_P(AccountReconcilorMirrorEndpointParamTest, GetAccountsFromCookieFailure) { - ; - ConnectProfileToAccount("12345", "user@gmail.com"); + ConnectProfileToAccount("user@gmail.com"); cookie_manager_service()->SetListAccountsResponseWebLoginRequired(); AccountReconcilor* reconcilor = GetMockReconcilor(); @@ -1865,15 +1927,62 @@ TEST_P(AccountReconcilorMirrorEndpointParamTest, GetAccountsFromCookieFailure) { ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_ERROR, reconcilor->GetState()); } +// Regression test for https://crbug.com/923716 +TEST_P(AccountReconcilorMirrorEndpointParamTest, + ExtraCookieChangeNotification) { + AccountInfo account_info = ConnectProfileToAccount("user@gmail.com"); + const std::string account_id = account_info.account_id; + signin::CookieParams cookie_params = { + account_info.email, account_info.gaia, false /* valid */, + false /* signed_out */, true /* verified */}; + + cookie_manager_service()->SetListAccountsResponseOneAccountWithParams( + cookie_params); + + if (!IsMultiloginEnabled()) { + EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(account_id)); + } else { + std::vector<std::string> accounts_to_send = {account_id}; + const signin::MultiloginParameters params( + gaia::MultiloginMode::MULTILOGIN_UPDATE_COOKIE_ACCOUNTS_ORDER, + accounts_to_send); + EXPECT_CALL(*GetMockReconcilor(), PerformSetCookiesAction(params)); + } + + AccountReconcilor* reconcilor = GetMockReconcilor(); + ASSERT_TRUE(reconcilor); + + ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_OK, reconcilor->GetState()); + reconcilor->StartReconcile(); + ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_RUNNING, reconcilor->GetState()); + + // Add extra cookie change notification. Reconcilor should ignore it. + gaia::ListedAccount listed_account = + ListedAccounfFromCookieParams(cookie_params, account_id); + reconcilor->OnGaiaAccountsInCookieUpdated( + {listed_account}, {}, GoogleServiceAuthError::AuthErrorNone()); + + base::RunLoop().RunUntilIdle(); + + if (!IsMultiloginEnabled()) { + SimulateAddAccountToCookieCompleted( + reconcilor, account_id, GoogleServiceAuthError::AuthErrorNone()); + } else { + SimulateSetAccountsInCookieCompleted( + reconcilor, GoogleServiceAuthError::AuthErrorNone()); + } + ASSERT_FALSE(reconcilor->is_reconcile_started_); + ASSERT_EQ(signin_metrics::ACCOUNT_RECONCILOR_OK, reconcilor->GetState()); +} + TEST_P(AccountReconcilorMirrorEndpointParamTest, StartReconcileNoop) { - const std::string account_id = - ConnectProfileToAccount("12345", "user@gmail.com"); + AccountInfo account_info = ConnectProfileToAccount("user@gmail.com"); AccountReconcilor* reconcilor = GetMockReconcilor(); ASSERT_TRUE(reconcilor); - cookie_manager_service()->SetListAccountsResponseOneAccount("user@gmail.com", - "12345"); + cookie_manager_service()->SetListAccountsResponseOneAccount( + account_info.email, account_info.gaia); reconcilor->StartReconcile(); ASSERT_TRUE(reconcilor->is_reconcile_started_); @@ -1893,8 +2002,8 @@ TEST_P(AccountReconcilorMirrorEndpointParamTest, StartReconcileNoop) { TEST_P(AccountReconcilorMirrorEndpointParamTest, StartReconcileCookiesDisabled) { const std::string account_id = - ConnectProfileToAccount("12345", "user@gmail.com"); - token_service()->UpdateCredentials(account_id, "refresh_token"); + ConnectProfileToAccount("user@gmail.com").account_id; + identity_test_env()->SetRefreshTokenForAccount(account_id); test_signin_client()->set_are_signin_cookies_allowed(false); AccountReconcilor* reconcilor = GetMockReconcilor(); @@ -1913,8 +2022,8 @@ TEST_P(AccountReconcilorMirrorEndpointParamTest, TEST_P(AccountReconcilorMirrorEndpointParamTest, StartReconcileContentSettings) { const std::string account_id = - ConnectProfileToAccount("12345", "user@gmail.com"); - token_service()->UpdateCredentials(account_id, "refresh_token"); + ConnectProfileToAccount("user@gmail.com").account_id; + identity_test_env()->SetRefreshTokenForAccount(account_id); AccountReconcilor* reconcilor = GetMockReconcilor(); ASSERT_TRUE(reconcilor); @@ -1933,8 +2042,8 @@ TEST_P(AccountReconcilorMirrorEndpointParamTest, TEST_P(AccountReconcilorMirrorEndpointParamTest, StartReconcileContentSettingsGaiaUrl) { const std::string account_id = - ConnectProfileToAccount("12345", "user@gmail.com"); - token_service()->UpdateCredentials(account_id, "refresh_token"); + ConnectProfileToAccount("user@gmail.com").account_id; + identity_test_env()->SetRefreshTokenForAccount(account_id); AccountReconcilor* reconcilor = GetMockReconcilor(); ASSERT_TRUE(reconcilor); @@ -1948,8 +2057,8 @@ TEST_P(AccountReconcilorMirrorEndpointParamTest, TEST_P(AccountReconcilorMirrorEndpointParamTest, StartReconcileContentSettingsNonGaiaUrl) { const std::string account_id = - ConnectProfileToAccount("12345", "user@gmail.com"); - token_service()->UpdateCredentials(account_id, "refresh_token"); + ConnectProfileToAccount("user@gmail.com").account_id; + identity_test_env()->SetRefreshTokenForAccount(account_id); AccountReconcilor* reconcilor = GetMockReconcilor(); ASSERT_TRUE(reconcilor); @@ -1963,8 +2072,8 @@ TEST_P(AccountReconcilorMirrorEndpointParamTest, TEST_P(AccountReconcilorMirrorEndpointParamTest, StartReconcileContentSettingsInvalidPattern) { const std::string account_id = - ConnectProfileToAccount("12345", "user@gmail.com"); - token_service()->UpdateCredentials(account_id, "refresh_token"); + ConnectProfileToAccount("user@gmail.com").account_id; + identity_test_env()->SetRefreshTokenForAccount(account_id); AccountReconcilor* reconcilor = GetMockReconcilor(); ASSERT_TRUE(reconcilor); @@ -1991,10 +2100,9 @@ TEST_P(AccountReconcilorMirrorEndpointParamTest, StartReconcileNoopWithDots) { return; } - const std::string account_id = - ConnectProfileToAccount("12345", "Dot.S@gmail.com"); - cookie_manager_service()->SetListAccountsResponseOneAccount("dot.s@gmail.com", - "12345"); + AccountInfo account_info = ConnectProfileToAccount("Dot.S@gmail.com"); + cookie_manager_service()->SetListAccountsResponseOneAccount( + account_info.email, account_info.gaia); AccountReconcilor* reconcilor = GetMockReconcilor(); ASSERT_TRUE(reconcilor); @@ -2010,12 +2118,12 @@ TEST_P(AccountReconcilorMirrorEndpointParamTest, StartReconcileNoopWithDots) { } TEST_P(AccountReconcilorMirrorEndpointParamTest, StartReconcileNoopMultiple) { - const std::string account_id = - ConnectProfileToAccount("67890", "user@gmail.com"); - const std::string account_id2 = SeedAccountInfo("12345", "other@gmail.com"); + AccountInfo account_info = ConnectProfileToAccount("user@gmail.com"); + AccountInfo account_info_2 = + identity_test_env()->MakeAccountAvailable("other@gmail.com"); cookie_manager_service()->SetListAccountsResponseTwoAccounts( - "user@gmail.com", "67890", "other@gmail.com", "12345"); - token_service()->UpdateCredentials(account_id2, "refresh_token"); + account_info.email, account_info.gaia, account_info_2.email, + account_info_2.gaia); AccountReconcilor* reconcilor = GetMockReconcilor(); ASSERT_TRUE(reconcilor); @@ -2034,14 +2142,14 @@ TEST_P(AccountReconcilorMirrorEndpointParamTest, StartReconcileNoopMultiple) { } TEST_P(AccountReconcilorMirrorEndpointParamTest, StartReconcileAddToCookie) { - const std::string account_id = - ConnectProfileToAccount("67890", "user@gmail.com"); - token_service()->UpdateCredentials(account_id, "refresh_token"); - cookie_manager_service()->SetListAccountsResponseOneAccount("user@gmail.com", - "67890"); + AccountInfo account_info = ConnectProfileToAccount("user@gmail.com"); + const std::string account_id = account_info.account_id; + identity_test_env()->SetRefreshTokenForAccount(account_id); + cookie_manager_service()->SetListAccountsResponseOneAccount( + account_info.email, account_info.gaia); - const std::string account_id2 = SeedAccountInfo("12345", "other@gmail.com"); - token_service()->UpdateCredentials(account_id2, "refresh_token"); + const std::string account_id2 = + identity_test_env()->MakeAccountAvailable("other@gmail.com").account_id; if (!IsMultiloginEnabled()) { EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(account_id2)); @@ -2108,16 +2216,16 @@ TEST_F(AccountReconcilorTest, AuthErrorTriggersListAccount) { #endif // Add one account to Chrome and instantiate the reconcilor. - const std::string account_id = - ConnectProfileToAccount("12345", "user@gmail.com"); - token_service()->UpdateCredentials(account_id, "refresh_token"); + AccountInfo account_info = ConnectProfileToAccount("user@gmail.com"); + const std::string account_id = account_info.account_id; + identity_test_env()->SetRefreshTokenForAccount(account_id); TestGaiaCookieObserver observer; cookie_manager_service()->AddObserver(&observer); AccountReconcilor* reconcilor = GetMockReconcilor(); base::RunLoop().RunUntilIdle(); ASSERT_FALSE(reconcilor->is_reconcile_started_); - cookie_manager_service()->SetListAccountsResponseOneAccount("user@gmail.com", - "12345"); + cookie_manager_service()->SetListAccountsResponseOneAccount( + account_info.email, account_info.gaia); if (account_consistency == signin::AccountConsistencyMethod::kDice) { EXPECT_CALL(*GetMockReconcilor(), PerformLogoutAllAccountsAction()) .Times(1); @@ -2125,10 +2233,11 @@ TEST_F(AccountReconcilorTest, AuthErrorTriggersListAccount) { // Set an authentication error. ASSERT_FALSE(observer.cookies_updated_); - token_service()->UpdateAuthErrorForTesting( - account_id, GoogleServiceAuthError::FromInvalidGaiaCredentialsReason( - GoogleServiceAuthError::InvalidGaiaCredentialsReason:: - CREDENTIALS_REJECTED_BY_SERVER)); + identity::UpdatePersistentErrorOfRefreshTokenForAccount( + identity_test_env()->identity_manager(), account_id, + GoogleServiceAuthError::FromInvalidGaiaCredentialsReason( + GoogleServiceAuthError::InvalidGaiaCredentialsReason:: + CREDENTIALS_REJECTED_BY_SERVER)); base::RunLoop().RunUntilIdle(); // Check that a call to ListAccount was triggered. @@ -2144,14 +2253,14 @@ TEST_F(AccountReconcilorTest, AuthErrorTriggersListAccount) { TEST_P(AccountReconcilorMirrorEndpointParamTest, SignoutAfterErrorDoesNotRecordUma) { - const std::string account_id = - ConnectProfileToAccount("12345", "user@gmail.com"); - token_service()->UpdateCredentials(account_id, "refresh_token"); - cookie_manager_service()->SetListAccountsResponseOneAccount("user@gmail.com", - "12345"); + AccountInfo account_info = ConnectProfileToAccount("user@gmail.com"); + const std::string account_id = account_info.account_id; + identity_test_env()->SetRefreshTokenForAccount(account_id); + cookie_manager_service()->SetListAccountsResponseOneAccount( + account_info.email, account_info.gaia); - const std::string account_id2 = SeedAccountInfo("67890", "other@gmail.com"); - token_service()->UpdateCredentials(account_id2, "refresh_token"); + const std::string account_id2 = + identity_test_env()->MakeAccountAvailable("other@gmail.com").account_id; if (!IsMultiloginEnabled()) { EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(account_id2)); @@ -2178,8 +2287,7 @@ TEST_P(AccountReconcilorMirrorEndpointParamTest, ASSERT_FALSE(reconcilor->is_reconcile_started_); EXPECT_CALL(*GetMockReconcilor(), PerformLogoutAllAccountsAction()); - signin_manager()->SignOut(signin_metrics::SIGNOUT_TEST, - signin_metrics::SignoutDelete::IGNORE_METRIC); + identity_test_env()->ClearPrimaryAccount(); base::HistogramTester::CountsMap expected_counts; expected_counts["Signin.Reconciler.Duration.Failure"] = 1; @@ -2194,11 +2302,11 @@ TEST_P(AccountReconcilorMirrorEndpointParamTest, TEST_P(AccountReconcilorMirrorEndpointParamTest, StartReconcileRemoveFromCookie) { - const std::string account_id = - ConnectProfileToAccount("67890", "user@gmail.com"); - token_service()->UpdateCredentials(account_id, "refresh_token"); + AccountInfo account_info = ConnectProfileToAccount("user@gmail.com"); + const std::string account_id = account_info.account_id; + identity_test_env()->SetRefreshTokenForAccount(account_id); cookie_manager_service()->SetListAccountsResponseTwoAccounts( - "user@gmail.com", "67890", "other@gmail.com", "12345"); + account_info.email, account_info.gaia, "other@gmail.com", "12345"); if (!IsMultiloginEnabled()) { EXPECT_CALL(*GetMockReconcilor(), PerformLogoutAllAccountsAction()); @@ -2239,14 +2347,13 @@ TEST_P(AccountReconcilorMirrorEndpointParamTest, // Check that reconcile is aborted if there is token error on primary account. TEST_P(AccountReconcilorMirrorEndpointParamTest, TokenErrorOnPrimary) { - const std::string account_id = - ConnectProfileToAccount("12345", "user@gmail.com"); - token_service()->UpdateAuthErrorForTesting( - account_id, + AccountInfo account_info = ConnectProfileToAccount("user@gmail.com"); + identity::UpdatePersistentErrorOfRefreshTokenForAccount( + identity_test_env()->identity_manager(), account_info.account_id, GoogleServiceAuthError(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS)); cookie_manager_service()->SetListAccountsResponseTwoAccounts( - "user@gmail.com", "12345", "other@gmail.com", "67890"); + account_info.email, account_info.gaia, "other@gmail.com", "67890"); AccountReconcilor* reconcilor = GetMockReconcilor(); reconcilor->StartReconcile(); @@ -2257,14 +2364,15 @@ TEST_P(AccountReconcilorMirrorEndpointParamTest, TokenErrorOnPrimary) { TEST_P(AccountReconcilorMirrorEndpointParamTest, StartReconcileAddToCookieTwice) { - const std::string account_id = - ConnectProfileToAccount("67890", "user@gmail.com"); - const std::string account_id2 = SeedAccountInfo("12345", "other@gmail.com"); + AccountInfo account_info = ConnectProfileToAccount("user@gmail.com"); + const std::string account_id = account_info.account_id; + AccountInfo account_info2 = + identity_test_env()->MakeAccountAvailable("other@gmail.com"); + const std::string account_id2 = account_info2.account_id; const std::string account_id3 = SeedAccountInfo("34567", "third@gmail.com"); - cookie_manager_service()->SetListAccountsResponseOneAccount("user@gmail.com", - "67890"); - token_service()->UpdateCredentials(account_id2, "refresh_token"); + cookie_manager_service()->SetListAccountsResponseOneAccount( + account_info.email, account_info.gaia); if (!IsMultiloginEnabled()) { EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(account_id2)); @@ -2303,11 +2411,12 @@ TEST_P(AccountReconcilorMirrorEndpointParamTest, // Do another pass after I've added a third account to the token service cookie_manager_service()->SetListAccountsResponseTwoAccounts( - "user@gmail.com", "67890", "other@gmail.com", "12345"); + account_info.email, account_info.gaia, account_info2.email, + account_info2.gaia); cookie_manager_service()->set_list_accounts_stale_for_testing(true); // This will cause the reconcilor to fire. - token_service()->UpdateCredentials(account_id3, "refresh_token"); + identity_test_env()->SetRefreshTokenForAccount(account_id3); if (IsMultiloginEnabled()) { std::vector<std::string> accounts_to_send = {account_id, account_id2, account_id3}; @@ -2347,13 +2456,15 @@ TEST_P(AccountReconcilorMirrorEndpointParamTest, } TEST_P(AccountReconcilorMirrorEndpointParamTest, StartReconcileBadPrimary) { - const std::string account_id = - ConnectProfileToAccount("12345", "user@gmail.com"); - const std::string account_id2 = SeedAccountInfo("67890", "other@gmail.com"); + AccountInfo account_info = ConnectProfileToAccount("user@gmail.com"); + const std::string account_id = account_info.account_id; - token_service()->UpdateCredentials(account_id2, "refresh_token"); + AccountInfo account_info2 = + identity_test_env()->MakeAccountAvailable("other@gmail.com"); + const std::string account_id2 = account_info2.account_id; cookie_manager_service()->SetListAccountsResponseTwoAccounts( - "other@gmail.com", "67890", "user@gmail.com", "12345"); + account_info2.email, account_info2.gaia, account_info.email, + account_info.gaia); if (!IsMultiloginEnabled()) { EXPECT_CALL(*GetMockReconcilor(), PerformLogoutAllAccountsAction()); @@ -2396,10 +2507,9 @@ TEST_P(AccountReconcilorMirrorEndpointParamTest, StartReconcileBadPrimary) { } TEST_P(AccountReconcilorMirrorEndpointParamTest, StartReconcileOnlyOnce) { - const std::string account_id = - ConnectProfileToAccount("12345", "user@gmail.com"); - cookie_manager_service()->SetListAccountsResponseOneAccount("user@gmail.com", - "12345"); + AccountInfo account_info = ConnectProfileToAccount("user@gmail.com"); + cookie_manager_service()->SetListAccountsResponseOneAccount( + account_info.email, account_info.gaia); AccountReconcilor* reconcilor = GetMockReconcilor(); ASSERT_TRUE(reconcilor); @@ -2413,10 +2523,10 @@ TEST_P(AccountReconcilorMirrorEndpointParamTest, StartReconcileOnlyOnce) { } TEST_P(AccountReconcilorMirrorEndpointParamTest, Lock) { - const std::string account_id = - ConnectProfileToAccount("12345", "user@gmail.com"); - cookie_manager_service()->SetListAccountsResponseOneAccount("user@gmail.com", - "12345"); + AccountInfo account_info = ConnectProfileToAccount("user@gmail.com"); + cookie_manager_service()->SetListAccountsResponseOneAccount( + account_info.email, account_info.gaia); + AccountReconcilor* reconcilor = GetMockReconcilor(); ASSERT_TRUE(reconcilor); ASSERT_FALSE(reconcilor->is_reconcile_started_); @@ -2487,15 +2597,16 @@ TEST_P(AccountReconcilorMirrorEndpointParamTest, Lock) { TEST_P(AccountReconcilorMethodParamTest, StartReconcileWithSessionInfoExpiredDefault) { SetAccountConsistency(GetParam()); - const std::string account_id = - ConnectProfileToAccount("12345", "user@gmail.com"); - const std::string account_id2 = SeedAccountInfo("67890", "other@gmail.com"); - token_service()->UpdateCredentials(account_id2, "refresh_token"); + AccountInfo account_info = ConnectProfileToAccount("user@gmail.com"); + const std::string account_id = account_info.account_id; + AccountInfo account_info2 = + identity_test_env()->MakeAccountAvailable("other@gmail.com"); + const std::string account_id2 = account_info2.account_id; cookie_manager_service()->SetListAccountsResponseWithParams( - {{"user@gmail.com", "12345", false /* valid */, false /* signed_out */, - true /* verified */}, - {"other@gmail.com", "67890", true /* valid */, false /* signed_out */, - true /* verified */}}); + {{account_info.email, account_info.gaia, false /* valid */, + false /* signed_out */, true /* verified */}, + {account_info2.email, account_info2.gaia, true /* valid */, + false /* signed_out */, true /* verified */}}); EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(account_id)); @@ -2514,11 +2625,11 @@ TEST_P(AccountReconcilorMethodParamTest, TEST_P(AccountReconcilorMirrorEndpointParamTest, AddAccountToCookieCompletedWithBogusAccount) { - const std::string account_id = - ConnectProfileToAccount("12345", "user@gmail.com"); + AccountInfo account_info = ConnectProfileToAccount("user@gmail.com"); + const std::string account_id = account_info.account_id; cookie_manager_service()->SetListAccountsResponseOneAccountWithParams( - {"user@gmail.com", "12345", false /* valid */, false /* signed_out */, - true /* verified */}); + {account_info.email, account_info.gaia, false /* valid */, + false /* signed_out */, true /* verified */}); if (!IsMultiloginEnabled()) { EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction(account_id)); @@ -2553,10 +2664,11 @@ TEST_P(AccountReconcilorMirrorEndpointParamTest, TEST_P(AccountReconcilorMirrorEndpointParamTest, NoLoopWithBadPrimary) { // Connect profile to a primary account and then add a secondary account. - const std::string account_id1 = - ConnectProfileToAccount("12345", "user@gmail.com"); - const std::string account_id2 = SeedAccountInfo("67890", "other@gmail.com"); - token_service()->UpdateCredentials(account_id2, "refresh_token"); + AccountInfo account_info = ConnectProfileToAccount("user@gmail.com"); + const std::string account_id1 = account_info.account_id; + AccountInfo account_info2 = + identity_test_env()->MakeAccountAvailable("other@gmail.com"); + const std::string account_id2 = account_info2.account_id; if (!IsMultiloginEnabled()) { EXPECT_CALL(*GetMockReconcilor(), PerformLogoutAllAccountsAction()); @@ -2571,8 +2683,8 @@ TEST_P(AccountReconcilorMirrorEndpointParamTest, NoLoopWithBadPrimary) { } // The primary account is in auth error, so it is not in the cookie. cookie_manager_service()->SetListAccountsResponseOneAccountWithParams( - {"other@gmail.com", "67890", false /* valid */, false /* signed_out */, - true /* verified */}); + {account_info2.email, account_info2.gaia, false /* valid */, + false /* signed_out */, true /* verified */}); AccountReconcilor* reconcilor = GetMockReconcilor(); ASSERT_TRUE(reconcilor); @@ -2600,7 +2712,8 @@ TEST_P(AccountReconcilorMirrorEndpointParamTest, NoLoopWithBadPrimary) { // Now that we've tried once, the token service knows that the primary // account has an auth error. - token_service()->UpdateAuthErrorForTesting(account_id1, error); + identity::UpdatePersistentErrorOfRefreshTokenForAccount( + identity_test_env()->identity_manager(), account_id1, error); // A second attempt to reconcile should be a noop. reconcilor->StartReconcile(); @@ -2612,13 +2725,13 @@ TEST_P(AccountReconcilorMirrorEndpointParamTest, NoLoopWithBadPrimary) { TEST_P(AccountReconcilorMirrorEndpointParamTest, WontMergeAccountsWithError) { // Connect profile to a primary account and then add a secondary account. const std::string account_id1 = - ConnectProfileToAccount("12345", "user@gmail.com"); - const std::string account_id2 = SeedAccountInfo("67890", "other@gmail.com"); - token_service()->UpdateCredentials(account_id2, "refresh_token"); + ConnectProfileToAccount("user@gmail.com").account_id; + const std::string account_id2 = + identity_test_env()->MakeAccountAvailable("other@gmail.com").account_id; // Mark the secondary account in auth error state. - token_service()->UpdateAuthErrorForTesting( - account_id2, + identity::UpdatePersistentErrorOfRefreshTokenForAccount( + identity_test_env()->identity_manager(), account_id2, GoogleServiceAuthError(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS)); // The cookie starts empty. @@ -2660,8 +2773,7 @@ TEST_P(AccountReconcilorMirrorEndpointParamTest, WontMergeAccountsWithError) { // Test that delegate timeout is called when the delegate offers a valid // timeout. TEST_F(AccountReconcilorTest, DelegateTimeoutIsCalled) { - const std::string account_id = - ConnectProfileToAccount("12345", "user@gmail.com"); + AccountInfo account_info = ConnectProfileToAccount("user@gmail.com"); auto spy_delegate0 = std::make_unique<SpyReconcilorDelegate>(); SpyReconcilorDelegate* spy_delegate = spy_delegate0.get(); AccountReconcilor* reconcilor = GetMockReconcilor(std::move(spy_delegate0)); @@ -2684,9 +2796,9 @@ TEST_F(AccountReconcilorTest, DelegateTimeoutIsCalled) { // Test that delegate timeout is not called when the delegate does not offer a // valid timeout. TEST_P(AccountReconcilorMirrorEndpointParamTest, DelegateTimeoutIsNotCalled) { - ConnectProfileToAccount("12345", "user@gmail.com"); - cookie_manager_service()->SetListAccountsResponseOneAccount("user@gmail.com", - "12345"); + AccountInfo account_info = ConnectProfileToAccount("user@gmail.com"); + cookie_manager_service()->SetListAccountsResponseOneAccount( + account_info.email, account_info.gaia); AccountReconcilor* reconcilor = GetMockReconcilor(); ASSERT_TRUE(reconcilor); auto timer0 = std::make_unique<base::MockOneShotTimer>(); @@ -2703,9 +2815,9 @@ INSTANTIATE_TEST_CASE_P(TestMirrorEndpoint, ::testing::ValuesIn({false, true})); TEST_F(AccountReconcilorTest, DelegateTimeoutIsNotCalledIfTimeoutIsNotReached) { - ConnectProfileToAccount("12345", "user@gmail.com"); - cookie_manager_service()->SetListAccountsResponseOneAccount("user@gmail.com", - "12345"); + AccountInfo account_info = ConnectProfileToAccount("user@gmail.com"); + cookie_manager_service()->SetListAccountsResponseOneAccount( + account_info.email, account_info.gaia); auto spy_delegate0 = std::make_unique<SpyReconcilorDelegate>(); SpyReconcilorDelegate* spy_delegate = spy_delegate0.get(); AccountReconcilor* reconcilor = GetMockReconcilor(std::move(spy_delegate0)); @@ -2732,3 +2844,10 @@ TEST_F(AccountReconcilorTest, ScopedSyncedDataDeletionDestructionOrder) { DeleteReconcilor(); // data_deletion is destroyed after the reconcilor, this should not crash. } + +TEST_F(AccountReconcilorTest, LockDestructionOrder) { + AccountReconcilor* reconcilor = GetMockReconcilor(); + AccountReconcilor::Lock lock(reconcilor); + DeleteReconcilor(); + // |lock| is destroyed after the reconcilor, this should not crash. +} diff --git a/chromium/components/signin/core/browser/account_tracker_service.cc b/chromium/components/signin/core/browser/account_tracker_service.cc index 51c45a8888f..0526b0e94c6 100644 --- a/chromium/components/signin/core/browser/account_tracker_service.cc +++ b/chromium/components/signin/core/browser/account_tracker_service.cc @@ -22,9 +22,15 @@ #include "build/build_config.h" #include "components/prefs/pref_registry_simple.h" #include "components/prefs/scoped_user_pref_update.h" +#include "components/signin/core/browser/account_info_util.h" #include "components/signin/core/browser/signin_manager.h" #include "components/signin/core/browser/signin_pref_names.h" +#if defined(OS_ANDROID) +#include "base/android/jni_array.h" +#include "jni/AccountTrackerService_jni.h" +#endif + namespace { const char kAccountKeyPath[] = "account_id"; @@ -39,6 +45,10 @@ const char kAccountChildAccountStatusPath[] = "is_child_account"; const char kAdvancedProtectionAccountStatusPath[] = "is_under_advanced_protection"; +// TODO(knn): Remove once deprecated service flags have been migrated from +// preferences. +const char kChildAccountServiceFlag[] = "uca"; + // Account folders used for storing account related data at disk. const base::FilePath::CharType kAccountsFolder[] = FILE_PATH_LITERAL("Accounts"); @@ -49,7 +59,7 @@ const base::FilePath::CharType kAvatarImagesFolder[] = const char kAccountServiceFlagsPath[] = "service_flags"; void RemoveDeprecatedServiceFlags(PrefService* pref_service) { - ListPrefUpdate update(pref_service, AccountTrackerService::kAccountInfoPref); + ListPrefUpdate update(pref_service, prefs::kAccountInfo); for (size_t i = 0; i < update->GetSize(); ++i) { base::DictionaryValue* dict = nullptr; if (update->GetDictionary(i, &dict)) @@ -97,17 +107,14 @@ void RemoveImage(const base::FilePath& image_path) { } // namespace -const char AccountTrackerService::kAccountInfoPref[] = "account_info"; - -const char AccountTrackerService::kChildAccountServiceFlag[] = "uca"; - -// This must be a string which can never be a valid domain. -const char AccountTrackerService::kNoHostedDomainFound[] = "NO_HOSTED_DOMAIN"; - -// This must be a string which can never be a valid picture URL. -const char AccountTrackerService::kNoPictureURLFound[] = "NO_PICTURE_URL"; - -AccountTrackerService::AccountTrackerService() : weak_factory_(this) {} +AccountTrackerService::AccountTrackerService() : weak_factory_(this) { +#if defined(OS_ANDROID) + JNIEnv* env = base::android::AttachCurrentThread(); + base::android::ScopedJavaLocalRef<jobject> java_ref = + Java_AccountTrackerService_create(env, reinterpret_cast<intptr_t>(this)); + java_ref_.Reset(env, java_ref.obj()); +#endif +} AccountTrackerService::~AccountTrackerService() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); @@ -115,7 +122,7 @@ AccountTrackerService::~AccountTrackerService() { // static void AccountTrackerService::RegisterPrefs(PrefRegistrySimple* registry) { - registry->RegisterListPref(AccountTrackerService::kAccountInfoPref); + registry->RegisterListPref(prefs::kAccountInfo); registry->RegisterIntegerPref(prefs::kAccountIdMigrationState, AccountTrackerService::MIGRATION_NOT_STARTED); } @@ -153,7 +160,7 @@ void AccountTrackerService::RemoveObserver(Observer* observer) { std::vector<AccountInfo> AccountTrackerService::GetAccounts() const { std::vector<AccountInfo> accounts; for (const auto& pair : accounts_) { - accounts.push_back(pair.second.info); + accounts.push_back(pair.second); } return accounts; } @@ -162,7 +169,7 @@ AccountInfo AccountTrackerService::GetAccountInfo( const std::string& account_id) const { const auto iterator = accounts_.find(account_id); if (iterator != accounts_.end()) - return iterator->second.info; + return iterator->second; return AccountInfo(); } @@ -171,11 +178,10 @@ AccountInfo AccountTrackerService::FindAccountInfoByGaiaId( const std::string& gaia_id) const { if (!gaia_id.empty()) { const auto iterator = std::find_if( - accounts_.begin(), accounts_.end(), [&gaia_id](const auto& pair) { - return pair.second.info.gaia == gaia_id; - }); + accounts_.begin(), accounts_.end(), + [&gaia_id](const auto& pair) { return pair.second.gaia == gaia_id; }); if (iterator != accounts_.end()) - return iterator->second.info; + return iterator->second; } return AccountInfo(); @@ -186,24 +192,15 @@ AccountInfo AccountTrackerService::FindAccountInfoByEmail( if (!email.empty()) { const auto iterator = std::find_if( accounts_.begin(), accounts_.end(), [&email](const auto& pair) { - return gaia::AreEmailsSame(pair.second.info.email, email); + return gaia::AreEmailsSame(pair.second.email, email); }); if (iterator != accounts_.end()) - return iterator->second.info; + return iterator->second; } return AccountInfo(); } -gfx::Image AccountTrackerService::GetAccountImage( - const std::string& account_id) { - const auto iterator = accounts_.find(account_id); - if (iterator != accounts_.end()) - return iterator->second.image; - - return gfx::Image(); -} - // static bool AccountTrackerService::IsMigrationSupported() { #if defined(OS_CHROMEOS) @@ -222,17 +219,11 @@ void AccountTrackerService::SetMigrationDone() { SetMigrationState(MIGRATION_DONE); } -void AccountTrackerService::NotifyAccountUpdated(const AccountState& state) { - DCHECK(!state.info.gaia.empty()); - for (auto& observer : observer_list_) - observer.OnAccountUpdated(state.info); -} - -void AccountTrackerService::NotifyAccountImageUpdated( - const std::string& account_id, - const gfx::Image& image) { +void AccountTrackerService::NotifyAccountUpdated( + const AccountInfo& account_info) { + DCHECK(!account_info.gaia.empty()); for (auto& observer : observer_list_) - observer.OnAccountImageUpdated(account_id, image); + observer.OnAccountUpdated(account_info); } void AccountTrackerService::NotifyAccountUpdateFailed( @@ -241,113 +232,103 @@ void AccountTrackerService::NotifyAccountUpdateFailed( observer.OnAccountUpdateFailed(account_id); } -void AccountTrackerService::NotifyAccountRemoved(const AccountState& state) { - DCHECK(!state.info.gaia.empty()); +void AccountTrackerService::NotifyAccountRemoved( + const AccountInfo& account_info) { + DCHECK(!account_info.gaia.empty()); for (auto& observer : observer_list_) - observer.OnAccountRemoved(state.info); + observer.OnAccountRemoved(account_info); } void AccountTrackerService::StartTrackingAccount( const std::string& account_id) { if (!base::ContainsKey(accounts_, account_id)) { DVLOG(1) << "StartTracking " << account_id; - AccountState state; - state.info.account_id = account_id; - state.info.is_child_account = false; - accounts_.insert(make_pair(account_id, state)); + AccountInfo account_info; + account_info.account_id = account_id; + account_info.is_child_account = false; + accounts_.insert(make_pair(account_id, account_info)); } } void AccountTrackerService::StopTrackingAccount(const std::string& account_id) { DVLOG(1) << "StopTracking " << account_id; if (base::ContainsKey(accounts_, account_id)) { - AccountState state = std::move(accounts_[account_id]); - RemoveFromPrefs(state); + AccountInfo account_info = std::move(accounts_[account_id]); + RemoveFromPrefs(account_info); RemoveAccountImageFromDisk(account_id); accounts_.erase(account_id); - if (!state.info.gaia.empty()) - NotifyAccountRemoved(state); + if (!account_info.gaia.empty()) + NotifyAccountRemoved(account_info); } } -void AccountTrackerService::SetAccountStateFromUserInfo( +void AccountTrackerService::SetAccountInfoFromUserInfo( const std::string& account_id, const base::DictionaryValue* user_info) { DCHECK(base::ContainsKey(accounts_, account_id)); - AccountState& state = accounts_[account_id]; - - std::string gaia_id; - std::string email; - if (user_info->GetString("id", &gaia_id) && - user_info->GetString("email", &email)) { - state.info.gaia = gaia_id; - state.info.email = email; - - std::string hosted_domain; - if (user_info->GetString("hd", &hosted_domain) && !hosted_domain.empty()) { - state.info.hosted_domain = hosted_domain; - } else { - state.info.hosted_domain = kNoHostedDomainFound; - } - - user_info->GetString("name", &state.info.full_name); - user_info->GetString("given_name", &state.info.given_name); - user_info->GetString("locale", &state.info.locale); - - std::string picture_url; - if (user_info->GetString("picture", &picture_url)) { - state.info.picture_url = picture_url; - } else { - state.info.picture_url = kNoPictureURLFound; - } + AccountInfo& account_info = accounts_[account_id]; + + base::Optional<AccountInfo> maybe_account_info = + AccountInfoFromUserInfo(*user_info); + if (maybe_account_info) { + // Should we DCHECK that the account stored in |accounts_| has the same + // value for |gaia_id| and |email| as the value loaded from |user_info|? + // DCHECK(account_info.gaia.empty() + // || account_info.gaia == maybe_account_info.value().gaia); + // DCHECK(account_info.email.empty() + // || account_info.email == maybe_account_info.value().email); + maybe_account_info.value().account_id = account_id; + account_info.UpdateWith(maybe_account_info.value()); } - if (!state.info.gaia.empty()) - NotifyAccountUpdated(state); - SaveToPrefs(state); + + if (!account_info.gaia.empty()) + NotifyAccountUpdated(account_info); + SaveToPrefs(account_info); } void AccountTrackerService::SetAccountImage(const std::string& account_id, const gfx::Image& image) { if (!base::ContainsKey(accounts_, account_id)) return; - accounts_[account_id].image = image; + AccountInfo& account_info = accounts_[account_id]; + account_info.account_image = image; SaveAccountImageToDisk(account_id, image); - NotifyAccountImageUpdated(account_id, image); + NotifyAccountUpdated(account_info); } void AccountTrackerService::SetIsChildAccount(const std::string& account_id, bool is_child_account) { DCHECK(base::ContainsKey(accounts_, account_id)); - AccountState& state = accounts_[account_id]; - if (state.info.is_child_account == is_child_account) + AccountInfo& account_info = accounts_[account_id]; + if (account_info.is_child_account == is_child_account) return; - state.info.is_child_account = is_child_account; - if (!state.info.gaia.empty()) - NotifyAccountUpdated(state); - SaveToPrefs(state); + account_info.is_child_account = is_child_account; + if (!account_info.gaia.empty()) + NotifyAccountUpdated(account_info); + SaveToPrefs(account_info); } void AccountTrackerService::SetIsAdvancedProtectionAccount( const std::string& account_id, bool is_under_advanced_protection) { DCHECK(base::ContainsKey(accounts_, account_id)); - AccountState& state = accounts_[account_id]; - if (state.info.is_under_advanced_protection == is_under_advanced_protection) + AccountInfo& account_info = accounts_[account_id]; + if (account_info.is_under_advanced_protection == is_under_advanced_protection) return; - state.info.is_under_advanced_protection = is_under_advanced_protection; - if (!state.info.gaia.empty()) - NotifyAccountUpdated(state); - SaveToPrefs(state); + account_info.is_under_advanced_protection = is_under_advanced_protection; + if (!account_info.gaia.empty()) + NotifyAccountUpdated(account_info); + SaveToPrefs(account_info); } void AccountTrackerService::MigrateToGaiaId() { DCHECK_EQ(GetMigrationState(), MIGRATION_IN_PROGRESS); std::vector<std::string> to_remove; - std::vector<AccountState> migrated_accounts; + std::vector<AccountInfo> migrated_accounts; for (const auto& pair : accounts_) { - const std::string& new_account_id = pair.second.info.gaia; + const std::string& new_account_id = pair.second.gaia; if (pair.first == new_account_id) continue; @@ -359,28 +340,28 @@ void AccountTrackerService::MigrateToGaiaId() { if (base::ContainsKey(accounts_, new_account_id)) continue; - AccountState new_state = pair.second; - new_state.info.account_id = new_account_id; - SaveToPrefs(new_state); - migrated_accounts.emplace_back(std::move(new_state)); + AccountInfo new_account_info = pair.second; + new_account_info.account_id = new_account_id; + SaveToPrefs(new_account_info); + migrated_accounts.emplace_back(std::move(new_account_info)); } // Insert the new migrated accounts. - for (AccountState& new_state : migrated_accounts) { - // Copy the AccountState |gaia| member field so that it is not left in + for (AccountInfo& new_account_info : migrated_accounts) { + // Copy the AccountInfo |gaia| member field so that it is not left in // an undeterminate state in the structure after std::map::emplace call. - std::string account_id = new_state.info.gaia; - SaveToPrefs(new_state); + std::string account_id = new_account_info.gaia; + SaveToPrefs(new_account_info); - accounts_.emplace(std::move(account_id), std::move(new_state)); + accounts_.emplace(std::move(account_id), std::move(new_account_info)); } // Remove any obsolete account. for (const auto& account_id : to_remove) { DCHECK(base::ContainsKey(accounts_, account_id)); - AccountState& state = accounts_[account_id]; + AccountInfo& account_info = accounts_[account_id]; RemoveAccountImageFromDisk(account_id); - RemoveFromPrefs(state); + RemoveFromPrefs(account_info); accounts_.erase(account_id); } } @@ -390,7 +371,7 @@ bool AccountTrackerService::IsMigrationDone() const { return false; for (const auto& pair : accounts_) { - if (pair.first != pair.second.info.gaia) + if (pair.first != pair.second.gaia) return false; } @@ -406,12 +387,12 @@ AccountTrackerService::ComputeNewMigrationState() const { bool migration_required = false; for (const auto& pair : accounts_) { // If there is any non-migratable account, skip migration. - if (pair.first.empty() || pair.second.info.gaia.empty()) + if (pair.first.empty() || pair.second.gaia.empty()) return MIGRATION_NOT_STARTED; // Migration is required if at least one account is not keyed to its // gaia id. - migration_required |= (pair.first != pair.second.info.gaia); + migration_required |= (pair.first != pair.second.gaia); } return migration_required ? MIGRATION_IN_PROGRESS : MIGRATION_DONE; @@ -439,9 +420,10 @@ base::FilePath AccountTrackerService::GetImagePathFor( void AccountTrackerService::OnAccountImageLoaded(const std::string& account_id, gfx::Image image) { if (base::ContainsKey(accounts_, account_id) && - accounts_[account_id].image.IsEmpty()) { - accounts_[account_id].image = image; - NotifyAccountImageUpdated(account_id, image); + accounts_[account_id].account_image.IsEmpty()) { + AccountInfo& account_info = accounts_[account_id]; + account_info.account_image = image; + NotifyAccountUpdated(account_info); } } @@ -449,7 +431,7 @@ void AccountTrackerService::LoadAccountImagesFromDisk() { if (!image_storage_task_runner_) return; for (const auto& pair : accounts_) { - const std::string& account_id = pair.second.info.account_id; + const std::string& account_id = pair.second.account_id; PostTaskAndReplyWithResult( image_storage_task_runner_.get(), FROM_HERE, base::BindOnce(&ReadImage, GetImagePathFor(account_id)), @@ -477,7 +459,7 @@ void AccountTrackerService::RemoveAccountImageFromDisk( } void AccountTrackerService::LoadFromPrefs() { - const base::ListValue* list = pref_service_->GetList(kAccountInfoPref); + const base::ListValue* list = pref_service_->GetList(prefs::kAccountInfo); std::set<std::string> to_remove; bool contains_deprecated_service_flags = false; for (size_t i = 0; i < list->GetSize(); ++i) { @@ -495,22 +477,22 @@ void AccountTrackerService::LoadFromPrefs() { } StartTrackingAccount(account_id); - AccountState& state = accounts_[account_id]; + AccountInfo& account_info = accounts_[account_id]; if (dict->GetString(kAccountGaiaPath, &value)) - state.info.gaia = base::UTF16ToUTF8(value); + account_info.gaia = base::UTF16ToUTF8(value); if (dict->GetString(kAccountEmailPath, &value)) - state.info.email = base::UTF16ToUTF8(value); + account_info.email = base::UTF16ToUTF8(value); if (dict->GetString(kAccountHostedDomainPath, &value)) - state.info.hosted_domain = base::UTF16ToUTF8(value); + account_info.hosted_domain = base::UTF16ToUTF8(value); if (dict->GetString(kAccountFullNamePath, &value)) - state.info.full_name = base::UTF16ToUTF8(value); + account_info.full_name = base::UTF16ToUTF8(value); if (dict->GetString(kAccountGivenNamePath, &value)) - state.info.given_name = base::UTF16ToUTF8(value); + account_info.given_name = base::UTF16ToUTF8(value); if (dict->GetString(kAccountLocalePath, &value)) - state.info.locale = base::UTF16ToUTF8(value); + account_info.locale = base::UTF16ToUTF8(value); if (dict->GetString(kAccountPictureURLPath, &value)) - state.info.picture_url = base::UTF16ToUTF8(value); + account_info.picture_url = base::UTF16ToUTF8(value); bool is_child_account = false; // Migrate deprecated service flag preference. @@ -525,20 +507,20 @@ void AccountTrackerService::LoadFromPrefs() { break; } } - state.info.is_child_account = is_child_account; + account_info.is_child_account = is_child_account; } if (dict->GetBoolean(kAccountChildAccountStatusPath, &is_child_account)) - state.info.is_child_account = is_child_account; + account_info.is_child_account = is_child_account; bool is_under_advanced_protection = false; if (dict->GetBoolean(kAdvancedProtectionAccountStatusPath, &is_under_advanced_protection)) { - state.info.is_under_advanced_protection = + account_info.is_under_advanced_protection = is_under_advanced_protection; } - if (!state.info.gaia.empty()) - NotifyAccountUpdated(state); + if (!account_info.gaia.empty()) + NotifyAccountUpdated(account_info); } } } @@ -551,9 +533,9 @@ void AccountTrackerService::LoadFromPrefs() { // Remove any obsolete prefs. for (auto account_id : to_remove) { - AccountState state; - state.info.account_id = account_id; - RemoveFromPrefs(state); + AccountInfo account_info; + account_info.account_id = account_id; + RemoveFromPrefs(account_info); RemoveAccountImageFromDisk(account_id); } @@ -582,13 +564,13 @@ void AccountTrackerService::LoadFromPrefs() { accounts_.size()); } -void AccountTrackerService::SaveToPrefs(const AccountState& state) { +void AccountTrackerService::SaveToPrefs(const AccountInfo& account_info) { if (!pref_service_) return; base::DictionaryValue* dict = nullptr; - base::string16 account_id_16 = base::UTF8ToUTF16(state.info.account_id); - ListPrefUpdate update(pref_service_, kAccountInfoPref); + base::string16 account_id_16 = base::UTF8ToUTF16(account_info.account_id); + ListPrefUpdate update(pref_service_, prefs::kAccountInfo); for (size_t i = 0; i < update->GetSize(); ++i, dict = nullptr) { if (update->GetDictionary(i, &dict)) { base::string16 value; @@ -605,24 +587,25 @@ void AccountTrackerService::SaveToPrefs(const AccountState& state) { dict->SetString(kAccountKeyPath, account_id_16); } - dict->SetString(kAccountEmailPath, state.info.email); - dict->SetString(kAccountGaiaPath, state.info.gaia); - dict->SetString(kAccountHostedDomainPath, state.info.hosted_domain); - dict->SetString(kAccountFullNamePath, state.info.full_name); - dict->SetString(kAccountGivenNamePath, state.info.given_name); - dict->SetString(kAccountLocalePath, state.info.locale); - dict->SetString(kAccountPictureURLPath, state.info.picture_url); - dict->SetBoolean(kAccountChildAccountStatusPath, state.info.is_child_account); + dict->SetString(kAccountEmailPath, account_info.email); + dict->SetString(kAccountGaiaPath, account_info.gaia); + dict->SetString(kAccountHostedDomainPath, account_info.hosted_domain); + dict->SetString(kAccountFullNamePath, account_info.full_name); + dict->SetString(kAccountGivenNamePath, account_info.given_name); + dict->SetString(kAccountLocalePath, account_info.locale); + dict->SetString(kAccountPictureURLPath, account_info.picture_url); + dict->SetBoolean(kAccountChildAccountStatusPath, + account_info.is_child_account); dict->SetBoolean(kAdvancedProtectionAccountStatusPath, - state.info.is_under_advanced_protection); + account_info.is_under_advanced_protection); } -void AccountTrackerService::RemoveFromPrefs(const AccountState& state) { +void AccountTrackerService::RemoveFromPrefs(const AccountInfo& account_info) { if (!pref_service_) return; - base::string16 account_id_16 = base::UTF8ToUTF16(state.info.account_id); - ListPrefUpdate update(pref_service_, kAccountInfoPref); + base::string16 account_id_16 = base::UTF8ToUTF16(account_info.account_id); + ListPrefUpdate update(pref_service_, prefs::kAccountInfo); for (size_t i = 0; i < update->GetSize(); ++i) { base::DictionaryValue* dict = nullptr; if (update->GetDictionary(i, &dict)) { @@ -670,11 +653,12 @@ std::string AccountTrackerService::SeedAccountInfo(const std::string& gaia, const std::string account_id = PickAccountIdForAccount(gaia, email); const bool already_exists = base::ContainsKey(accounts_, account_id); StartTrackingAccount(account_id); - AccountState& state = accounts_[account_id]; - DCHECK(!already_exists || state.info.gaia.empty() || state.info.gaia == gaia); - state.info.gaia = gaia; - state.info.email = email; - SaveToPrefs(state); + AccountInfo& account_info = accounts_[account_id]; + DCHECK(!already_exists || account_info.gaia.empty() || + account_info.gaia == gaia); + account_info.gaia = gaia; + account_info.email = email; + SaveToPrefs(account_info); DVLOG(1) << "AccountTrackerService::SeedAccountInfo" << " account_id=" << account_id << " gaia_id=" << gaia @@ -690,13 +674,13 @@ std::string AccountTrackerService::SeedAccountInfo(AccountInfo info) { StartTrackingAccount(info.account_id); } - AccountState& state = accounts_[info.account_id]; - // Update the missing fields in |state.info| with |info|. - if (state.info.UpdateWith(info)) { - if (!state.info.gaia.empty()) - NotifyAccountUpdated(state); + AccountInfo& account_info = accounts_[info.account_id]; + // Update the missing fields in |account_info| with |info|. + if (account_info.UpdateWith(info)) { + if (!account_info.gaia.empty()) + NotifyAccountUpdated(account_info); - SaveToPrefs(state); + SaveToPrefs(account_info); } return info.account_id; } @@ -704,3 +688,61 @@ std::string AccountTrackerService::SeedAccountInfo(AccountInfo info) { void AccountTrackerService::RemoveAccount(const std::string& account_id) { StopTrackingAccount(account_id); } + +#if defined(OS_ANDROID) +base::android::ScopedJavaLocalRef<jobject> +AccountTrackerService::GetJavaObject() { + return base::android::ScopedJavaLocalRef<jobject>(java_ref_); +} + +void JNI_AccountTrackerService_SeedAccountsInfo( + JNIEnv* env, + jlong nativeAccountTrackerService, + const base::android::JavaParamRef<jobjectArray>& gaiaIds, + const base::android::JavaParamRef<jobjectArray>& accountNames) { + AccountTrackerService* service = + reinterpret_cast<AccountTrackerService*>(nativeAccountTrackerService); + DCHECK(service); + + std::vector<std::string> gaia_ids; + std::vector<std::string> account_names; + base::android::AppendJavaStringArrayToStringVector(env, gaiaIds, &gaia_ids); + base::android::AppendJavaStringArrayToStringVector(env, accountNames, + &account_names); + DCHECK_EQ(gaia_ids.size(), account_names.size()); + + DVLOG(1) << "AccountTrackerService.SeedAccountsInfo: " + << " number of accounts " << gaia_ids.size(); + for (size_t i = 0; i < gaia_ids.size(); ++i) { + service->SeedAccountInfo(gaia_ids[i], account_names[i]); + } +} + +jboolean JNI_AccountTrackerService_AreAccountsSeeded( + JNIEnv* env, + jlong nativeAccountTrackerService, + const base::android::JavaParamRef<jobjectArray>& accountNames) { + AccountTrackerService* service = + reinterpret_cast<AccountTrackerService*>(nativeAccountTrackerService); + DCHECK(service); + + std::vector<std::string> account_names; + base::android::AppendJavaStringArrayToStringVector(env, accountNames, + &account_names); + + bool migrated = + service->GetMigrationState() == + AccountTrackerService::AccountIdMigrationState::MIGRATION_DONE; + + for (const auto& account_name : account_names) { + AccountInfo info = service->FindAccountInfoByEmail(account_name); + if (info.account_id.empty()) { + return false; + } + if (migrated && info.gaia.empty()) { + return false; + } + } + return true; +} +#endif diff --git a/chromium/components/signin/core/browser/account_tracker_service.h b/chromium/components/signin/core/browser/account_tracker_service.h index caf69224f86..c60a555a0ba 100644 --- a/chromium/components/signin/core/browser/account_tracker_service.h +++ b/chromium/components/signin/core/browser/account_tracker_service.h @@ -16,11 +16,16 @@ #include "base/observer_list.h" #include "base/sequence_checker.h" #include "base/timer/timer.h" +#include "build/build_config.h" #include "components/keyed_service/core/keyed_service.h" #include "components/signin/core/browser/account_info.h" #include "google_apis/gaia/gaia_auth_util.h" #include "ui/gfx/image/image.h" +#if defined(OS_ANDROID) +#include "base/android/scoped_java_ref.h" +#endif + class PrefRegistrySimple; class PrefService; @@ -32,30 +37,12 @@ class DictionaryValue; // information about Google Accounts. class AccountTrackerService : public KeyedService { public: - // Name of the preference property that persists the account information - // tracked by this service. - static const char kAccountInfoPref[]; - - // Value representing no hosted domain in the kGoogleServicesHostedDomain - // preference. - static const char kNoHostedDomainFound[]; - - // Value representing no picture URL associated with an account. - static const char kNoPictureURLFound[]; - - // TODO(knn): Move to ChildAccountInfoFetcher once deprecated service flags - // have been migrated from preferences. - // Child account service flag name. - static const char kChildAccountServiceFlag[]; - // Clients of AccountTrackerService can implement this interface and register // with AddObserver() to learn about account information changes. class Observer { public: virtual ~Observer() {} virtual void OnAccountUpdated(const AccountInfo& info) {} - virtual void OnAccountImageUpdated(const std::string& account_id, - const gfx::Image& image) {} virtual void OnAccountUpdateFailed(const std::string& account_id) {} virtual void OnAccountRemoved(const AccountInfo& info) {} }; @@ -95,10 +82,6 @@ class AccountTrackerService : public KeyedService { AccountInfo FindAccountInfoByGaiaId(const std::string& gaia_id) const; AccountInfo FindAccountInfoByEmail(const std::string& email) const; - // Returns the account image associated to the account id |account_id|. - // If the account id is not known an empty image is returned. - gfx::Image GetAccountImage(const std::string& account_id); - // Picks the correct account_id for the specified account depending on the // migration state. std::string PickAccountIdForAccount(const std::string& gaia, @@ -134,10 +117,15 @@ class AccountTrackerService : public KeyedService { AccountIdMigrationState GetMigrationState() const; void SetMigrationDone(); +#if defined(OS_ANDROID) + // Returns a reference to the corresponding Java AccountTrackerService object. + base::android::ScopedJavaLocalRef<jobject> GetJavaObject(); +#endif + protected: // Available to be called in tests. - void SetAccountStateFromUserInfo(const std::string& account_id, - const base::DictionaryValue* user_info); + void SetAccountInfoFromUserInfo(const std::string& account_id, + const base::DictionaryValue* user_info); // Updates the account image. Does nothing if |account_id| does not exist in // |accounts_|. @@ -146,24 +134,18 @@ class AccountTrackerService : public KeyedService { private: friend class AccountFetcherService; friend class FakeAccountFetcherService; - struct AccountState { - AccountInfo info; - gfx::Image image; - }; - void NotifyAccountUpdated(const AccountState& state); - void NotifyAccountImageUpdated(const std::string& account_id, - const gfx::Image& image); + void NotifyAccountUpdated(const AccountInfo& account_info); void NotifyAccountUpdateFailed(const std::string& account_id); - void NotifyAccountRemoved(const AccountState& state); + void NotifyAccountRemoved(const AccountInfo& accoint_info); void StartTrackingAccount(const std::string& account_id); void StopTrackingAccount(const std::string& account_id); // Load the current state of the account info from the preferences file. void LoadFromPrefs(); - void SaveToPrefs(const AccountState& account); - void RemoveFromPrefs(const AccountState& account); + void SaveToPrefs(const AccountInfo& account); + void RemoveFromPrefs(const AccountInfo& account); // Used to load/save account images from/to disc. base::FilePath GetImagePathFor(const std::string& account_id); @@ -194,7 +176,7 @@ class AccountTrackerService : public KeyedService { const PrefService* pref_service); PrefService* pref_service_ = nullptr; // Not owned. - std::map<std::string, AccountState> accounts_; + std::map<std::string, AccountInfo> accounts_; base::ObserverList<Observer>::Unchecked observer_list_; base::FilePath user_data_dir_; @@ -202,6 +184,11 @@ class AccountTrackerService : public KeyedService { // Task runner used for file operations on avatar images. scoped_refptr<base::SequencedTaskRunner> image_storage_task_runner_; +#if defined(OS_ANDROID) + // A reference to the Java counterpart of this object. + base::android::ScopedJavaGlobalRef<jobject> java_ref_; +#endif + SEQUENCE_CHECKER(sequence_checker_); // Used to pass weak pointers of |this| to tasks created by diff --git a/chromium/components/signin/core/browser/account_tracker_service_unittest.cc b/chromium/components/signin/core/browser/account_tracker_service_unittest.cc index e796e3c27d2..2aac21e39b0 100644 --- a/chromium/components/signin/core/browser/account_tracker_service_unittest.cc +++ b/chromium/components/signin/core/browser/account_tracker_service_unittest.cc @@ -21,7 +21,6 @@ #include "components/signin/core/browser/account_info.h" #include "components/signin/core/browser/account_tracker_service.h" #include "components/signin/core/browser/avatar_icon_util.h" -#include "components/signin/core/browser/child_account_info_fetcher.h" #include "components/signin/core/browser/fake_account_fetcher_service.h" #include "components/signin/core/browser/signin_pref_names.h" #include "components/signin/core/browser/test_signin_client.h" @@ -33,6 +32,10 @@ #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" +#if defined(OS_ANDROID) +#include "components/signin/core/browser/child_account_info_fetcher_android.h" +#endif + namespace { // Simple wrapper around a static string; used to avoid implicit conversion // of the account key to an std::string (which is the type used for account @@ -74,7 +77,6 @@ const char kTokenInfoIncompleteResponseFormat[] = enum TrackingEventType { UPDATED, - IMAGE_UPDATED, REMOVED, }; @@ -131,9 +133,6 @@ class TrackingEvent { case UPDATED: typestr = "UPD"; break; - case IMAGE_UPDATED: - typestr = "IMG_UPD"; - break; case REMOVED: typestr = "REM"; break; @@ -181,8 +180,6 @@ class AccountTrackerObserver : public AccountTrackerService::Observer { private: // AccountTrackerService::Observer implementation void OnAccountUpdated(const AccountInfo& ids) override; - void OnAccountImageUpdated(const std::string& account_id, - const gfx::Image& image) override; void OnAccountRemoved(const AccountInfo& ids) override; std::vector<TrackingEvent> events_; @@ -192,12 +189,6 @@ void AccountTrackerObserver::OnAccountUpdated(const AccountInfo& ids) { events_.push_back(TrackingEvent(UPDATED, ids.account_id, ids.gaia)); } -void AccountTrackerObserver::OnAccountImageUpdated( - const std::string& account_id, - const gfx::Image& image) { - events_.push_back(TrackingEvent(IMAGE_UPDATED, account_id, std::string())); -} - void AccountTrackerObserver::OnAccountRemoved(const AccountInfo& ids) { events_.push_back(TrackingEvent(REMOVED, ids.account_id, ids.gaia)); } @@ -232,7 +223,9 @@ testing::AssertionResult AccountTrackerObserver::CheckEvents( class AccountTrackerServiceTest : public testing::Test { public: AccountTrackerServiceTest() : signin_client_(&pref_service_) { - ChildAccountInfoFetcher::InitializeForTests(); +#if defined(OS_ANDROID) + ChildAccountInfoFetcherAndroid::InitializeForTests(); +#endif AccountTrackerService::RegisterPrefs(pref_service_.registry()); AccountFetcherService::RegisterPrefs(pref_service_.registry()); @@ -289,7 +282,7 @@ class AccountTrackerServiceTest : public testing::Test { EXPECT_EQ(AccountKeyToAccountId(account_key), info.account_id); EXPECT_EQ(AccountKeyToGaiaId(account_key), info.gaia); EXPECT_EQ(AccountKeyToEmail(account_key), info.email); - EXPECT_EQ(AccountTrackerService::kNoHostedDomainFound, info.hosted_domain); + EXPECT_EQ(kNoHostedDomainFound, info.hosted_domain); EXPECT_EQ(AccountKeyToFullName(account_key), info.full_name); EXPECT_EQ(AccountKeyToGivenName(account_key), info.given_name); EXPECT_EQ(AccountKeyToLocale(account_key), info.locale); @@ -469,16 +462,16 @@ TEST_F(AccountTrackerServiceTest, TokenAvailable_UserInfo_ImageSuccess) { })); EXPECT_TRUE(account_tracker() - ->GetAccountImage(AccountKeyToAccountId(kAccountKeyAlpha)) - .IsEmpty()); + ->GetAccountInfo(AccountKeyToAccountId(kAccountKeyAlpha)) + .account_image.IsEmpty()); ReturnAccountImageFetchSuccess(kAccountKeyAlpha); EXPECT_TRUE(observer()->CheckEvents({ - TrackingEvent(IMAGE_UPDATED, AccountKeyToAccountId(kAccountKeyAlpha), + TrackingEvent(UPDATED, AccountKeyToAccountId(kAccountKeyAlpha), AccountKeyToGaiaId(kAccountKeyAlpha)), })); EXPECT_FALSE(account_tracker() - ->GetAccountImage(AccountKeyToAccountId(kAccountKeyAlpha)) - .IsEmpty()); + ->GetAccountInfo(AccountKeyToAccountId(kAccountKeyAlpha)) + .account_image.IsEmpty()); } TEST_F(AccountTrackerServiceTest, TokenAvailable_UserInfo_ImageFailure) { @@ -491,12 +484,12 @@ TEST_F(AccountTrackerServiceTest, TokenAvailable_UserInfo_ImageFailure) { })); EXPECT_TRUE(account_tracker() - ->GetAccountImage(AccountKeyToAccountId(kAccountKeyAlpha)) - .IsEmpty()); + ->GetAccountInfo(AccountKeyToAccountId(kAccountKeyAlpha)) + .account_image.IsEmpty()); ReturnAccountImageFetchFailure(kAccountKeyAlpha); EXPECT_TRUE(account_tracker() - ->GetAccountImage(AccountKeyToAccountId(kAccountKeyAlpha)) - .IsEmpty()); + ->GetAccountInfo(AccountKeyToAccountId(kAccountKeyAlpha)) + .account_image.IsEmpty()); } TEST_F(AccountTrackerServiceTest, TokenAvailable_UserInfo_Revoked) { @@ -697,9 +690,9 @@ TEST_F(AccountTrackerServiceTest, Persistence) { // Wait until all account images are loaded. scoped_task_environment_.RunUntilIdle(); EXPECT_TRUE(observer()->CheckEvents({ - TrackingEvent(IMAGE_UPDATED, AccountKeyToAccountId(kAccountKeyAlpha), + TrackingEvent(UPDATED, AccountKeyToAccountId(kAccountKeyAlpha), AccountKeyToGaiaId(kAccountKeyAlpha)), - TrackingEvent(IMAGE_UPDATED, AccountKeyToAccountId(kAccountKeyBeta), + TrackingEvent(UPDATED, AccountKeyToAccountId(kAccountKeyBeta), AccountKeyToGaiaId(kAccountKeyBeta)), })); @@ -777,7 +770,7 @@ TEST_F(AccountTrackerServiceTest, SeedAccountInfoFull) { // Validate that seeding new full informations to an existing account works // and sends a notification. info.given_name = AccountKeyToGivenName(kAccountKeyAlpha); - info.hosted_domain = AccountTrackerService::kNoHostedDomainFound; + info.hosted_domain = kNoHostedDomainFound; info.locale = AccountKeyToLocale(kAccountKeyAlpha); info.picture_url = AccountKeyToPictureURL(kAccountKeyAlpha); account_tracker()->SeedAccountInfo(info); @@ -791,7 +784,7 @@ TEST_F(AccountTrackerServiceTest, SeedAccountInfoFull) { // Validate that seeding invalid information to an existing account doesn't // work and doesn't send a notification. - info.given_name = AccountKeyToGivenName(kAccountKeyBeta); + info.given_name = std::string(); account_tracker()->SeedAccountInfo(info); stored_info = account_tracker()->GetAccountInfo(info.account_id); EXPECT_EQ(info.gaia, stored_info.gaia); @@ -928,7 +921,7 @@ TEST_F(AccountTrackerServiceTest, NoDeprecatedServiceFlags) { const std::string email_alpha = AccountKeyToEmail(kAccountKeyAlpha); const std::string gaia_alpha = AccountKeyToGaiaId(kAccountKeyAlpha); - ListPrefUpdate update(prefs(), AccountTrackerService::kAccountInfoPref); + ListPrefUpdate update(prefs(), prefs::kAccountInfo); std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue()); dict->SetString("account_id", email_alpha); @@ -947,7 +940,7 @@ TEST_F(AccountTrackerServiceTest, MigrateDeprecatedServiceFlags) { const std::string email_alpha = AccountKeyToEmail(kAccountKeyAlpha); const std::string gaia_alpha = AccountKeyToGaiaId(kAccountKeyAlpha); - ListPrefUpdate update(prefs(), AccountTrackerService::kAccountInfoPref); + ListPrefUpdate update(prefs(), prefs::kAccountInfo); std::unique_ptr<base::ListValue> service_flags(new base::ListValue()); service_flags->Append(std::make_unique<base::Value>("uca")); @@ -975,7 +968,7 @@ TEST_F(AccountTrackerServiceTest, MigrateAccountIdToGaiaId) { const std::string email_beta = AccountKeyToEmail(kAccountKeyBeta); const std::string gaia_beta = AccountKeyToGaiaId(kAccountKeyBeta); - ListPrefUpdate update(prefs(), AccountTrackerService::kAccountInfoPref); + ListPrefUpdate update(prefs(), prefs::kAccountInfo); std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue()); dict->SetString("account_id", email_alpha); @@ -1019,7 +1012,7 @@ TEST_F(AccountTrackerServiceTest, CanNotMigrateAccountIdToGaiaId) { const std::string gaia_alpha = AccountKeyToGaiaId(kAccountKeyAlpha); const std::string email_beta = AccountKeyToEmail(kAccountKeyBeta); - ListPrefUpdate update(prefs(), AccountTrackerService::kAccountInfoPref); + ListPrefUpdate update(prefs(), prefs::kAccountInfo); std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue()); dict->SetString("account_id", email_alpha); @@ -1063,7 +1056,7 @@ TEST_F(AccountTrackerServiceTest, GaiaIdMigrationCrashInTheMiddle) { const std::string email_beta = AccountKeyToEmail(kAccountKeyBeta); const std::string gaia_beta = AccountKeyToGaiaId(kAccountKeyBeta); - ListPrefUpdate update(prefs(), AccountTrackerService::kAccountInfoPref); + ListPrefUpdate update(prefs(), prefs::kAccountInfo); std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue()); dict->SetString("account_id", email_alpha); @@ -1340,7 +1333,7 @@ TEST_F(AccountTrackerServiceTest, CountOfLoadedAccounts_TwoAccounts) { const std::string email_beta = AccountKeyToEmail(kAccountKeyBeta); const std::string gaia_beta = AccountKeyToGaiaId(kAccountKeyBeta); - ListPrefUpdate update(prefs(), AccountTrackerService::kAccountInfoPref); + ListPrefUpdate update(prefs(), prefs::kAccountInfo); std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue()); dict->SetString("account_id", email_alpha); @@ -1368,7 +1361,7 @@ TEST_F(AccountTrackerServiceTest, CountOfLoadedAccounts_TwoAccountsOneInvalid) { const std::string email_foobar = AccountKeyToEmail(kAccountKeyFooDotBar); const std::string gaia_foobar = AccountKeyToGaiaId(kAccountKeyFooDotBar); - ListPrefUpdate update(prefs(), AccountTrackerService::kAccountInfoPref); + ListPrefUpdate update(prefs(), prefs::kAccountInfo); std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue()); dict->SetString("account_id", email_alpha); diff --git a/chromium/components/signin/core/browser/android/BUILD.gn b/chromium/components/signin/core/browser/android/BUILD.gn index 61fb92b96be..71bc6deee25 100644 --- a/chromium/components/signin/core/browser/android/BUILD.gn +++ b/chromium/components/signin/core/browser/android/BUILD.gn @@ -6,7 +6,9 @@ import("//build/config/android/rules.gni") generate_jni("jni_headers") { sources = [ + "java/src/org/chromium/components/signin/AccountTrackerService.java", "java/src/org/chromium/components/signin/ChildAccountInfoFetcher.java", + "java/src/org/chromium/components/signin/OAuth2TokenService.java", ] jni_package = "components/signin" } @@ -28,6 +30,7 @@ android_library("java") { "java/src/org/chromium/components/signin/AccountManagerFacade.java", "java/src/org/chromium/components/signin/AccountManagerResult.java", "java/src/org/chromium/components/signin/AccountsChangeObserver.java", + "java/src/org/chromium/components/signin/AccountTrackerService.java", "java/src/org/chromium/components/signin/AuthException.java", "java/src/org/chromium/components/signin/ChildAccountInfoFetcher.java", "java/src/org/chromium/components/signin/ChildAccountStatus.java", @@ -35,6 +38,7 @@ android_library("java") { "java/src/org/chromium/components/signin/GmsAvailabilityException.java", "java/src/org/chromium/components/signin/GmsJustUpdatedException.java", "java/src/org/chromium/components/signin/util/PatternMatcher.java", + "java/src/org/chromium/components/signin/OAuth2TokenService.java", "java/src/org/chromium/components/signin/ProfileDataSource.java", "java/src/org/chromium/components/signin/SystemAccountManagerDelegate.java", ] @@ -87,5 +91,6 @@ android_library("signin_java_test_support") { java_files = [ "javatests/src/org/chromium/components/signin/test/util/AccountHolder.java", "javatests/src/org/chromium/components/signin/test/util/FakeAccountManagerDelegate.java", + "javatests/src/org/chromium/components/signin/test/util/FakeProfileDataSource.java", ] } diff --git a/chromium/components/signin/core/browser/android/java/src/org/chromium/components/signin/AccountManagerFacade.java b/chromium/components/signin/core/browser/android/java/src/org/chromium/components/signin/AccountManagerFacade.java index 5f4cc8dd9bd..3feda0e0544 100644 --- a/chromium/components/signin/core/browser/android/java/src/org/chromium/components/signin/AccountManagerFacade.java +++ b/chromium/components/signin/core/browser/android/java/src/org/chromium/components/signin/AccountManagerFacade.java @@ -20,6 +20,7 @@ import android.os.UserManager; import android.support.annotation.AnyThread; import android.support.annotation.MainThread; import android.support.annotation.Nullable; +import android.support.annotation.WorkerThread; import org.chromium.base.Callback; import org.chromium.base.ContextUtils; @@ -30,7 +31,6 @@ import org.chromium.base.VisibleForTesting; import org.chromium.base.metrics.CachedMetrics; import org.chromium.base.task.AsyncTask; import org.chromium.components.signin.util.PatternMatcher; -import org.chromium.net.NetworkChangeNotifier; import java.util.ArrayList; import java.util.Arrays; @@ -39,8 +39,6 @@ import java.util.List; import java.util.Locale; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.regex.Pattern; @@ -100,26 +98,6 @@ public class AccountManagerFacade { private final ArrayList<Runnable> mCallbacksWaitingForPendingUpdates = new ArrayList<>(); /** - * A simple callback for getAuthToken. - */ - public interface GetAuthTokenCallback { - /** - * Invoked on the UI thread if a token is provided by the AccountManager. - * - * @param token Auth token, guaranteed not to be null. - */ - void tokenAvailable(String token); - - /** - * Invoked on the UI thread if no token is available. - * - * @param isTransientError Indicates if the error is transient (network timeout or - * unavailable, etc) or persistent (bad credentials, permission denied, etc). - */ - void tokenUnavailable(boolean isTransientError); - } - - /** * @param delegate the AccountManagerDelegate to use as a backend */ private AccountManagerFacade(AccountManagerDelegate delegate) { @@ -451,64 +429,30 @@ public class AccountManagerFacade { } /** - * Gets the auth token and returns the response asynchronously. - * This should be called when we have a foreground activity that needs an auth token. - * If encountered an IO error, it will attempt to retry when the network is back. - * - * - Assumes that the account is a valid account. + * Synchronously gets an OAuth2 access token. May return a cached version, use + * {@link #invalidateAccessToken} to invalidate a token in the cache. + * @param account The {@link Account} for which the token is requested. + * @param scope OAuth2 scope for which the requested token should be valid. + * @return The OAuth2 access token as a string. */ - @MainThread - public void getAuthToken(final Account account, final String authTokenType, - final GetAuthTokenCallback callback) { - ConnectionRetry.runAuthTask(new AuthTask<String>() { - @Override - public String run() throws AuthException { - return mDelegate.getAuthToken(account, authTokenType); - } - @Override - public void onSuccess(String token) { - callback.tokenAvailable(token); - } - @Override - public void onFailure(boolean isTransientError) { - callback.tokenUnavailable(isTransientError); - } - }); - } - - /** - * Invalidates the old token (if non-null/non-empty) and asynchronously generates a new one. - * - * - Assumes that the account is a valid account. - */ - @MainThread - public void getNewAuthToken(Account account, String authToken, String authTokenType, - GetAuthTokenCallback callback) { - invalidateAuthToken(authToken); - getAuthToken(account, authTokenType, callback); + @WorkerThread + String getAccessToken(Account account, String scope) throws AuthException { + assert account != null; + assert scope != null; + // TODO(bsazonov): Rename delegate's getAuthToken to getAccessToken. + return mDelegate.getAuthToken(account, scope); } /** - * Clear an auth token from the local cache with respect to the ApplicationContext. + * Synchronously clears an OAuth2 access token from the cache. Use {@link #getAccessToken} + * to issue a new token after invalidating the old one. + * @param accessToken The access token to invalidate. */ - @MainThread - public void invalidateAuthToken(final String authToken) { - if (authToken == null || authToken.isEmpty()) { - return; - } - ConnectionRetry.runAuthTask(new AuthTask<Boolean>() { - @Override - public Boolean run() throws AuthException { - mDelegate.invalidateAuthToken(authToken); - return true; - } - @Override - public void onSuccess(Boolean result) {} - @Override - public void onFailure(boolean isTransientError) { - Log.e(TAG, "Failed to invalidate auth token: " + authToken); - } - }); + @WorkerThread + void invalidateAccessToken(String accessToken) throws AuthException { + assert accessToken != null; + // TODO(bsazonov): Rename delegate's invalidateAuthToken to invalidateAccessToken. + mDelegate.invalidateAuthToken(accessToken); } // Incorrectly infers that this is called on a worker thread because of AsyncTask doInBackground @@ -763,83 +707,4 @@ public class AccountManagerFacade { decrementUpdateCounter(); } } - - private interface AuthTask<T> { - T run() throws AuthException; - void onSuccess(T result); - void onFailure(boolean isTransientError); - } - - /** - * A helper class to encapsulate network connection retry logic for AuthTasks. - * - * The task will be run on the background thread. If it encounters a transient error, it will - * wait for a network change and retry up to MAX_TRIES times. - */ - private static class ConnectionRetry<T> - implements NetworkChangeNotifier.ConnectionTypeObserver { - private static final int MAX_TRIES = 3; - - private final AuthTask<T> mAuthTask; - private final AtomicInteger mNumTries; - private final AtomicBoolean mIsTransientError; - - public static <T> void runAuthTask(AuthTask<T> authTask) { - new ConnectionRetry<>(authTask).attempt(); - } - - private ConnectionRetry(AuthTask<T> authTask) { - mAuthTask = authTask; - mNumTries = new AtomicInteger(0); - mIsTransientError = new AtomicBoolean(false); - } - - /** - * Tries running the {@link AuthTask} in the background. This object is never registered - * as a {@link NetworkChangeNotifier.ConnectionTypeObserver} when this method is called. - */ - private void attempt() { - ThreadUtils.assertOnUiThread(); - // Clear any transient error. - mIsTransientError.set(false); - new AsyncTask<T>() { - @Override - public T doInBackground() { - try { - return mAuthTask.run(); - } catch (AuthException ex) { - Log.w(TAG, "Failed to perform auth task: %s", ex.stringifyCausalChain()); - Log.d(TAG, "Exception details:", ex); - mIsTransientError.set(ex.isTransientError()); - } - return null; - } - @Override - public void onPostExecute(T result) { - if (result != null) { - mAuthTask.onSuccess(result); - } else if (!mIsTransientError.get() || mNumTries.incrementAndGet() >= MAX_TRIES - || !NetworkChangeNotifier.isInitialized()) { - // Permanent error, ran out of tries, or we can't listen for network - // change events; give up. - mAuthTask.onFailure(mIsTransientError.get()); - } else { - // Transient error with tries left; register for another attempt. - NetworkChangeNotifier.addConnectionTypeObserver(ConnectionRetry.this); - } - } - } - .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - - @Override - public void onConnectionTypeChanged(int connectionType) { - assert mNumTries.get() < MAX_TRIES; - if (NetworkChangeNotifier.isOnline()) { - // The network is back; stop listening and try again. - NetworkChangeNotifier.removeConnectionTypeObserver(this); - attempt(); - } - } - } } diff --git a/chromium/components/signin/core/browser/android/java/src/org/chromium/components/signin/AccountTrackerService.java b/chromium/components/signin/core/browser/android/java/src/org/chromium/components/signin/AccountTrackerService.java new file mode 100644 index 00000000000..a431cc2dbde --- /dev/null +++ b/chromium/components/signin/core/browser/android/java/src/org/chromium/components/signin/AccountTrackerService.java @@ -0,0 +1,253 @@ +// Copyright 2015 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. + +package org.chromium.components.signin; + +import android.os.SystemClock; +import android.support.annotation.IntDef; + +import org.chromium.base.Log; +import org.chromium.base.ObserverList; +import org.chromium.base.ThreadUtils; +import org.chromium.base.VisibleForTesting; +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.metrics.RecordHistogram; +import org.chromium.base.task.AsyncTask; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.concurrent.TimeUnit; + +/** + * Android wrapper of AccountTrackerService which provides access from the java layer. + * It offers the capability of fetching and seeding system accounts into AccountTrackerService in + * C++ layer, and notifies observers when it is complete. + */ +public class AccountTrackerService { + private static final String TAG = "AccountService"; + + private final long mNativeAccountTrackerService; + private @SystemAccountsSeedingStatus int mSystemAccountsSeedingStatus; + private boolean mSystemAccountsChanged; + private boolean mSyncForceRefreshedForTest; + private AccountsChangeObserver mAccountsChangeObserver; + + @IntDef({SystemAccountsSeedingStatus.SEEDING_NOT_STARTED, + SystemAccountsSeedingStatus.SEEDING_IN_PROGRESS, + SystemAccountsSeedingStatus.SEEDING_DONE, + SystemAccountsSeedingStatus.SEEDING_VALIDATING}) + @Retention(RetentionPolicy.SOURCE) + public @interface SystemAccountsSeedingStatus { + int SEEDING_NOT_STARTED = 0; + int SEEDING_IN_PROGRESS = 1; + int SEEDING_DONE = 2; + int SEEDING_VALIDATING = 3; + } + + /** + * Classes that want to listen for system accounts fetching and seeding should implement + * this interface and register with {@link #addSystemAccountsSeededListener}. + */ + public interface OnSystemAccountsSeededListener { + // Called at the end of seedSystemAccounts(). + void onSystemAccountsSeedingComplete(); + // Called in invalidateAccountSeedStatus() indicating that accounts have changed. + void onSystemAccountsChanged(); + } + + private final ObserverList<OnSystemAccountsSeededListener> mSystemAccountsSeedingObservers = + new ObserverList<>(); + + private AccountTrackerService(long nativeAccountTrackerService) { + mNativeAccountTrackerService = nativeAccountTrackerService; + mSystemAccountsSeedingStatus = SystemAccountsSeedingStatus.SEEDING_NOT_STARTED; + mSystemAccountsChanged = false; + } + + @CalledByNative + private static AccountTrackerService create(long nativeAccountTrackerService) { + ThreadUtils.assertOnUiThread(); + return new AccountTrackerService(nativeAccountTrackerService); + } + + /** + * Checks whether the account id <-> email mapping has been seeded into C++ layer. + * If not, it automatically starts fetching the mapping and seeds it. + * @return Whether the accounts have been seeded already. + */ + public boolean checkAndSeedSystemAccounts() { + ThreadUtils.assertOnUiThread(); + if (mSystemAccountsSeedingStatus == SystemAccountsSeedingStatus.SEEDING_DONE + && !mSystemAccountsChanged) { + return true; + } + if ((mSystemAccountsSeedingStatus == SystemAccountsSeedingStatus.SEEDING_NOT_STARTED + || mSystemAccountsChanged) + && mSystemAccountsSeedingStatus + != SystemAccountsSeedingStatus.SEEDING_IN_PROGRESS) { + seedSystemAccounts(); + } + return false; + } + + /** + * Register an |observer| to observe system accounts seeding status. + */ + public void addSystemAccountsSeededListener(OnSystemAccountsSeededListener observer) { + ThreadUtils.assertOnUiThread(); + mSystemAccountsSeedingObservers.addObserver(observer); + if (mSystemAccountsSeedingStatus == SystemAccountsSeedingStatus.SEEDING_DONE) { + observer.onSystemAccountsSeedingComplete(); + } + } + + /** + * Remove an |observer| from the list of observers. + */ + public void removeSystemAccountsSeededListener(OnSystemAccountsSeededListener observer) { + ThreadUtils.assertOnUiThread(); + mSystemAccountsSeedingObservers.removeObserver(observer); + } + + private void seedSystemAccounts() { + ThreadUtils.assertOnUiThread(); + mSystemAccountsChanged = false; + mSyncForceRefreshedForTest = false; + + final AccountIdProvider accountIdProvider = AccountIdProvider.getInstance(); + if (accountIdProvider.canBeUsed()) { + mSystemAccountsSeedingStatus = SystemAccountsSeedingStatus.SEEDING_IN_PROGRESS; + } else { + mSystemAccountsSeedingStatus = SystemAccountsSeedingStatus.SEEDING_NOT_STARTED; + return; + } + + if (mAccountsChangeObserver == null) { + mAccountsChangeObserver = + () -> invalidateAccountSeedStatus(false /* don't reseed right now */); + AccountManagerFacade.get().addObserver(mAccountsChangeObserver); + } + + AccountManagerFacade.get().tryGetGoogleAccounts(accounts -> { + new AsyncTask<String[][]>() { + @Override + public String[][] doInBackground() { + Log.d(TAG, "Getting id/email mapping"); + + long seedingStartTime = SystemClock.elapsedRealtime(); + + String[][] accountIdNameMap = new String[2][accounts.size()]; + for (int i = 0; i < accounts.size(); ++i) { + accountIdNameMap[0][i] = + accountIdProvider.getAccountId(accounts.get(i).name); + accountIdNameMap[1][i] = accounts.get(i).name; + } + + RecordHistogram.recordTimesHistogram("Signin.AndroidGetAccountIdsTime", + SystemClock.elapsedRealtime() - seedingStartTime, + TimeUnit.MILLISECONDS); + + return accountIdNameMap; + } + @Override + public void onPostExecute(String[][] accountIdNameMap) { + if (mSyncForceRefreshedForTest) return; + if (mSystemAccountsChanged) { + seedSystemAccounts(); + return; + } + if (areAccountIdsValid(accountIdNameMap[0])) { + nativeSeedAccountsInfo(mNativeAccountTrackerService, accountIdNameMap[0], + accountIdNameMap[1]); + mSystemAccountsSeedingStatus = SystemAccountsSeedingStatus.SEEDING_DONE; + notifyObserversOnSeedingComplete(); + } else { + Log.w(TAG, "Invalid mapping of id/email"); + seedSystemAccounts(); + } + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + }); + } + + private boolean areAccountIdsValid(String[] accountIds) { + for (String accountId : accountIds) { + if (accountId == null) return false; + } + return true; + } + + private void notifyObserversOnSeedingComplete() { + for (OnSystemAccountsSeededListener observer : mSystemAccountsSeedingObservers) { + observer.onSystemAccountsSeedingComplete(); + } + } + + /** + * Seed system accounts into AccountTrackerService synchronously for test purpose. + */ + @VisibleForTesting + public void syncForceRefreshForTest(String[] accountIds, String[] accountNames) { + ThreadUtils.assertOnUiThread(); + mSystemAccountsSeedingStatus = SystemAccountsSeedingStatus.SEEDING_IN_PROGRESS; + mSystemAccountsChanged = false; + mSyncForceRefreshedForTest = true; + nativeSeedAccountsInfo(mNativeAccountTrackerService, accountIds, accountNames); + mSystemAccountsSeedingStatus = SystemAccountsSeedingStatus.SEEDING_DONE; + } + + /** + * Notifies the AccountTrackerService about changed system accounts. without actually triggering + * @param reSeedAccounts Whether to also start seeding the new account information immediately. + */ + public void invalidateAccountSeedStatus(boolean reSeedAccounts) { + ThreadUtils.assertOnUiThread(); + mSystemAccountsChanged = true; + notifyObserversOnAccountsChange(); + if (reSeedAccounts) checkAndSeedSystemAccounts(); + } + + /** + * Verifies whether seeded accounts in AccountTrackerService are up-to-date with the accounts in + * Android. It sets seeding status to SEEDING_VALIDATING temporarily to block services depending + * on it and sets it back to SEEDING_DONE after passing the verification. This function is + * created because accounts changed notification from Android to Chrome has latency. + */ + public void validateSystemAccounts() { + ThreadUtils.assertOnUiThread(); + if (!checkAndSeedSystemAccounts()) { + // Do nothing if seeding is not done. + return; + } + + mSystemAccountsSeedingStatus = SystemAccountsSeedingStatus.SEEDING_VALIDATING; + AccountManagerFacade.get().tryGetGoogleAccounts(accounts -> { + if (mSystemAccountsChanged + || mSystemAccountsSeedingStatus + != SystemAccountsSeedingStatus.SEEDING_VALIDATING) { + return; + } + + String[] accountNames = new String[accounts.size()]; + for (int i = 0; i < accounts.size(); ++i) { + accountNames[i] = accounts.get(i).name; + } + if (nativeAreAccountsSeeded(mNativeAccountTrackerService, accountNames)) { + mSystemAccountsSeedingStatus = SystemAccountsSeedingStatus.SEEDING_DONE; + notifyObserversOnSeedingComplete(); + } + }); + } + + private void notifyObserversOnAccountsChange() { + for (OnSystemAccountsSeededListener observer : mSystemAccountsSeedingObservers) { + observer.onSystemAccountsChanged(); + } + } + + private static native void nativeSeedAccountsInfo( + long accountTrackerServicePtr, String[] gaiaIds, String[] accountNames); + private static native boolean nativeAreAccountsSeeded( + long accountTrackerServicePtr, String[] accountNames); +} diff --git a/chromium/components/signin/core/browser/android/java/src/org/chromium/components/signin/OAuth2TokenService.java b/chromium/components/signin/core/browser/android/java/src/org/chromium/components/signin/OAuth2TokenService.java new file mode 100644 index 00000000000..c1f25744a9f --- /dev/null +++ b/chromium/components/signin/core/browser/android/java/src/org/chromium/components/signin/OAuth2TokenService.java @@ -0,0 +1,500 @@ +// Copyright 2013 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. + +package org.chromium.components.signin; + +import android.accounts.Account; +import android.support.annotation.MainThread; +import android.support.annotation.Nullable; +import android.text.TextUtils; + +import org.chromium.base.ContextUtils; +import org.chromium.base.Log; +import org.chromium.base.ObserverList; +import org.chromium.base.StrictModeContext; +import org.chromium.base.ThreadUtils; +import org.chromium.base.VisibleForTesting; +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.task.AsyncTask; +import org.chromium.net.NetworkChangeNotifier; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +/** + * Java instance for the native OAuth2TokenService. + * <p/> + * This class forwards calls to request or invalidate access tokens made by native code to + * AccountManagerFacade and forwards callbacks to native code. + * <p/> + */ +public final class OAuth2TokenService + implements AccountTrackerService.OnSystemAccountsSeededListener { + private static final String TAG = "OAuth2TokenService"; + + @VisibleForTesting + public static final String STORED_ACCOUNTS_KEY = "google.services.stored_accounts"; + + /** + * A simple callback for getAccessToken. + */ + public interface GetAccessTokenCallback { + /** + * Invoked on the UI thread if a token is provided by the AccountManager. + * + * @param token Access token, guaranteed not to be null. + */ + void onGetTokenSuccess(String token); + + /** + * Invoked on the UI thread if no token is available. + * + * @param isTransientError Indicates if the error is transient (network timeout or + * unavailable, etc) or persistent (bad credentials, permission denied, etc). + */ + void onGetTokenFailure(boolean isTransientError); + } + + /** + * Classes that want to listen for refresh token availability should + * implement this interface and register with {@link #addObserver}. + */ + public interface OAuth2TokenServiceObserver { + void onRefreshTokenAvailable(Account account); + void onRefreshTokenRevoked(Account account); + void onRefreshTokensLoaded(); + } + + private static final String OAUTH2_SCOPE_PREFIX = "oauth2:"; + + private final long mNativeOAuth2TokenServiceDelegate; + private final AccountTrackerService mAccountTrackerService; + private final ObserverList<OAuth2TokenServiceObserver> mObservers = new ObserverList<>(); + + private boolean mPendingValidation; + private boolean mPendingValidationForceNotifications; + + private OAuth2TokenService( + long nativeOAuth2TokenServiceDelegate, AccountTrackerService accountTrackerService) { + mNativeOAuth2TokenServiceDelegate = nativeOAuth2TokenServiceDelegate; + mAccountTrackerService = accountTrackerService; + + mAccountTrackerService.addSystemAccountsSeededListener(this); + } + + @CalledByNative + private static OAuth2TokenService create( + long nativeOAuth2TokenServiceDelegate, AccountTrackerService accountTrackerService) { + ThreadUtils.assertOnUiThread(); + return new OAuth2TokenService(nativeOAuth2TokenServiceDelegate, accountTrackerService); + } + + @VisibleForTesting + public void addObserver(OAuth2TokenServiceObserver observer) { + ThreadUtils.assertOnUiThread(); + mObservers.addObserver(observer); + } + + @VisibleForTesting + public void removeObserver(OAuth2TokenServiceObserver observer) { + ThreadUtils.assertOnUiThread(); + mObservers.removeObserver(observer); + } + + private static Account getAccountOrNullFromUsername(String username) { + if (username == null) { + Log.e(TAG, "Username is null"); + return null; + } + + AccountManagerFacade accountManagerFacade = AccountManagerFacade.get(); + Account account = accountManagerFacade.getAccountFromName(username); + if (account == null) { + Log.e(TAG, "Account not found for provided username."); + return null; + } + return account; + } + + /** + * Called by native to list the active account names in the OS. + */ + @VisibleForTesting + @CalledByNative + public static String[] getSystemAccountNames() { + // TODO(https://crbug.com/768366): Remove this after adding cache to account manager facade. + // This function is called by native code on UI thread. + try (StrictModeContext unused = StrictModeContext.allowDiskReads()) { + List<String> accountNames = AccountManagerFacade.get().tryGetGoogleAccountNames(); + return accountNames.toArray(new String[accountNames.size()]); + } + } + + /** + * Called by native to list the accounts Id with OAuth2 refresh tokens. + * This can differ from getSystemAccountNames as the user add/remove accounts + * from the OS. validateAccounts should be called to keep these two + * in sync. + */ + @CalledByNative + public static String[] getAccounts() { + return getStoredAccounts(); + } + + /** + * Called by native to retrieve OAuth2 tokens. + * @param username The native username (email address). + * @param scope The scope to get an auth token for (without Android-style 'oauth2:' prefix). + * @param nativeCallback The pointer to the native callback that should be run upon completion. + */ + @MainThread + @CalledByNative + private static void getAccessTokenFromNative( + String username, String scope, final long nativeCallback) { + Account account = getAccountOrNullFromUsername(username); + if (account == null) { + ThreadUtils.postOnUiThread(() -> nativeOAuth2TokenFetched(null, false, nativeCallback)); + return; + } + String oauth2Scope = OAUTH2_SCOPE_PREFIX + scope; + getAccessToken(account, oauth2Scope, new GetAccessTokenCallback() { + @Override + public void onGetTokenSuccess(String token) { + nativeOAuth2TokenFetched(token, false, nativeCallback); + } + + @Override + public void onGetTokenFailure(boolean isTransientError) { + nativeOAuth2TokenFetched(null, isTransientError, nativeCallback); + } + }); + } + + /** + * Call this method to retrieve an OAuth2 access token for the given account and scope. Please + * note that this method expects a scope with 'oauth2:' prefix. + * @param account the account to get the access token for. + * @param scope The scope to get an auth token for (with Android-style 'oauth2:' prefix). + * @param callback called on successful and unsuccessful fetching of auth token. + */ + @MainThread + public static void getAccessToken( + Account account, String scope, GetAccessTokenCallback callback) { + ConnectionRetry.runAuthTask(new AuthTask<String>() { + @Override + public String run() throws AuthException { + return AccountManagerFacade.get().getAccessToken(account, scope); + } + @Override + public void onSuccess(String token) { + callback.onGetTokenSuccess(token); + } + @Override + public void onFailure(boolean isTransientError) { + callback.onGetTokenFailure(isTransientError); + } + }); + } + + /** + * Called by native to invalidate an OAuth2 token. Please note that the token is invalidated + * asynchronously. + */ + @MainThread + @CalledByNative + public static void invalidateAccessToken(String accessToken) { + if (TextUtils.isEmpty(accessToken)) { + return; + } + ConnectionRetry.runAuthTask(new AuthTask<Boolean>() { + @Override + public Boolean run() throws AuthException { + AccountManagerFacade.get().invalidateAccessToken(accessToken); + return true; + } + @Override + public void onSuccess(Boolean result) {} + @Override + public void onFailure(boolean isTransientError) { + Log.e(TAG, "Failed to invalidate auth token: " + accessToken); + } + }); + } + + /** + * Invalidates the old token (if non-null/non-empty) and asynchronously generates a new one. + * @param account the account to get the access token for. + * @param oldToken The old token to be invalidated or null. + * @param scope The scope to get an auth token for (with Android-style 'oauth2:' prefix). + * @param callback called on successful and unsuccessful fetching of auth token. + */ + public static void getNewAccessToken(Account account, @Nullable String oldToken, String scope, + GetAccessTokenCallback callback) { + ConnectionRetry.runAuthTask(new AuthTask<String>() { + @Override + public String run() throws AuthException { + if (!TextUtils.isEmpty(oldToken)) { + AccountManagerFacade.get().invalidateAccessToken(oldToken); + } + return AccountManagerFacade.get().getAccessToken(account, scope); + } + @Override + public void onSuccess(String token) { + callback.onGetTokenSuccess(token); + } + @Override + public void onFailure(boolean isTransientError) { + callback.onGetTokenFailure(isTransientError); + } + }); + } + + /** + * Call this method to retrieve an OAuth2 access token for the given account and scope. This + * method times out after the specified timeout, and will return null if that happens. + * + * Given that this is a blocking method call, this should never be called from the UI thread. + * + * @param account the account to get the access token for. + * @param scope The scope to get an auth token for (without Android-style 'oauth2:' prefix). + * @param timeout the timeout. + * @param unit the unit for |timeout|. + */ + @VisibleForTesting + public static String getAccessTokenWithTimeout( + Account account, String scope, long timeout, TimeUnit unit) { + assert !ThreadUtils.runningOnUiThread(); + final AtomicReference<String> result = new AtomicReference<>(); + final Semaphore semaphore = new Semaphore(0); + getAccessToken(account, scope, new GetAccessTokenCallback() { + @Override + public void onGetTokenSuccess(String token) { + result.set(token); + semaphore.release(); + } + + @Override + public void onGetTokenFailure(boolean isTransientError) { + result.set(null); + semaphore.release(); + } + }); + try { + if (semaphore.tryAcquire(timeout, unit)) { + return result.get(); + } else { + Log.d(TAG, "Failed to retrieve auth token within timeout (%s %s)", timeout, unit); + return null; + } + } catch (InterruptedException e) { + Log.w(TAG, "Got interrupted while waiting for auth token"); + return null; + } + } + + /** + * Called by native to check whether the account has an OAuth2 refresh token. + */ + @CalledByNative + public static boolean hasOAuth2RefreshToken(String accountName) { + if (!AccountManagerFacade.get().isCachePopulated()) { + return false; + } + + // Temporarily allowing disk read while fixing. TODO: http://crbug.com/618096. + // This function is called in RefreshTokenIsAvailable of OAuth2TokenService which is + // expected to be called in the UI thread synchronously. + try (StrictModeContext unused = StrictModeContext.allowDiskReads()) { + return AccountManagerFacade.get().hasAccountForName(accountName); + } + } + + /** + * Continue pending accounts validation after system accounts have been seeded into + * AccountTrackerService. + */ + @Override + public void onSystemAccountsSeedingComplete() { + if (mPendingValidation) { + validateAccountsWithSignedInAccountName(mPendingValidationForceNotifications); + mPendingValidation = false; + mPendingValidationForceNotifications = false; + } + } + + /** + * Clear pending accounts validation when system accounts in AccountTrackerService were + * refreshed. + */ + @Override + public void onSystemAccountsChanged() { + mPendingValidationForceNotifications = false; + } + + @CalledByNative + public void validateAccounts(boolean forceNotifications) { + ThreadUtils.assertOnUiThread(); + if (!mAccountTrackerService.checkAndSeedSystemAccounts()) { + mPendingValidation = true; + mPendingValidationForceNotifications = forceNotifications; + return; + } + + validateAccountsWithSignedInAccountName(forceNotifications); + } + + private void validateAccountsWithSignedInAccountName(boolean forceNotifications) { + String currentlySignedInAccount = ChromeSigninController.get().getSignedInAccountName(); + if (currentlySignedInAccount != null + && isSignedInAccountChanged(currentlySignedInAccount)) { + // Set currentlySignedInAccount to null for validation if signed-in account was changed + // (renamed or removed from the device), this will cause all credentials in token + // service be revoked. + // Could only get here during Chrome cold startup. + // After chrome started, SigninHelper and AccountsChangedReceiver will handle account + // change (re-signin or sign out signed-in account). + currentlySignedInAccount = null; + } + nativeValidateAccounts( + mNativeOAuth2TokenServiceDelegate, currentlySignedInAccount, forceNotifications); + } + + private boolean isSignedInAccountChanged(String signedInAccountName) { + String[] accountNames = getSystemAccountNames(); + for (String accountName : accountNames) { + if (accountName.equals(signedInAccountName)) return false; + } + return true; + } + + @CalledByNative + private void notifyRefreshTokenAvailable(String accountName) { + assert accountName != null; + Account account = AccountManagerFacade.createAccountFromName(accountName); + for (OAuth2TokenServiceObserver observer : mObservers) { + observer.onRefreshTokenAvailable(account); + } + } + + @CalledByNative + public void notifyRefreshTokenRevoked(String accountName) { + assert accountName != null; + Account account = AccountManagerFacade.createAccountFromName(accountName); + for (OAuth2TokenServiceObserver observer : mObservers) { + observer.onRefreshTokenRevoked(account); + } + } + + @CalledByNative + public void notifyRefreshTokensLoaded() { + for (OAuth2TokenServiceObserver observer : mObservers) { + observer.onRefreshTokensLoaded(); + } + } + + private static String[] getStoredAccounts() { + Set<String> accounts = + ContextUtils.getAppSharedPreferences().getStringSet(STORED_ACCOUNTS_KEY, null); + return accounts == null ? new String[] {} : accounts.toArray(new String[0]); + } + + @CalledByNative + private static void saveStoredAccounts(String[] accounts) { + Set<String> set = new HashSet<>(Arrays.asList(accounts)); + ContextUtils.getAppSharedPreferences() + .edit() + .putStringSet(STORED_ACCOUNTS_KEY, set) + .apply(); + } + + private interface AuthTask<T> { + T run() throws AuthException; + void onSuccess(T result); + void onFailure(boolean isTransientError); + } + + /** + * A helper class to encapsulate network connection retry logic for AuthTasks. + * + * The task will be run on the background thread. If it encounters a transient error, it will + * wait for a network change and retry up to MAX_TRIES times. + */ + private static class ConnectionRetry<T> + implements NetworkChangeNotifier.ConnectionTypeObserver { + private static final int MAX_TRIES = 3; + + private final AuthTask<T> mAuthTask; + private final AtomicInteger mNumTries; + private final AtomicBoolean mIsTransientError; + + public static <T> void runAuthTask(AuthTask<T> authTask) { + new ConnectionRetry<>(authTask).attempt(); + } + + private ConnectionRetry(AuthTask<T> authTask) { + mAuthTask = authTask; + mNumTries = new AtomicInteger(0); + mIsTransientError = new AtomicBoolean(false); + } + + /** + * Tries running the {@link AuthTask} in the background. This object is never registered + * as a {@link NetworkChangeNotifier.ConnectionTypeObserver} when this method is called. + */ + private void attempt() { + ThreadUtils.assertOnUiThread(); + // Clear any transient error. + mIsTransientError.set(false); + new AsyncTask<T>() { + @Override + public T doInBackground() { + try { + return mAuthTask.run(); + } catch (AuthException ex) { + Log.w(TAG, "Failed to perform auth task: %s", ex.stringifyCausalChain()); + Log.d(TAG, "Exception details:", ex); + mIsTransientError.set(ex.isTransientError()); + } + return null; + } + @Override + public void onPostExecute(T result) { + if (result != null) { + mAuthTask.onSuccess(result); + } else if (!mIsTransientError.get() || mNumTries.incrementAndGet() >= MAX_TRIES + || !NetworkChangeNotifier.isInitialized()) { + // Permanent error, ran out of tries, or we can't listen for network + // change events; give up. + mAuthTask.onFailure(mIsTransientError.get()); + } else { + // Transient error with tries left; register for another attempt. + NetworkChangeNotifier.addConnectionTypeObserver(ConnectionRetry.this); + } + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + @Override + public void onConnectionTypeChanged(int connectionType) { + assert mNumTries.get() < MAX_TRIES; + if (NetworkChangeNotifier.isOnline()) { + // The network is back; stop listening and try again. + NetworkChangeNotifier.removeConnectionTypeObserver(this); + attempt(); + } + } + } + + private static native void nativeOAuth2TokenFetched( + String authToken, boolean isTransientError, long nativeCallback); + private native void nativeValidateAccounts(long nativeOAuth2TokenServiceDelegateAndroid, + String currentlySignedInAccount, boolean forceNotifications); +} diff --git a/chromium/components/signin/core/browser/child_account_info_fetcher.cc b/chromium/components/signin/core/browser/child_account_info_fetcher.cc deleted file mode 100644 index bc8e2670c78..00000000000 --- a/chromium/components/signin/core/browser/child_account_info_fetcher.cc +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2015 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 "components/signin/core/browser/child_account_info_fetcher.h" - -#include "build/build_config.h" - -#include "services/network/public/cpp/shared_url_loader_factory.h" -#if defined(OS_ANDROID) -#include "components/signin/core/browser/child_account_info_fetcher_android.h" -#else -#include "components/signin/core/browser/child_account_info_fetcher_impl.h" -#endif - -// static -std::unique_ptr<ChildAccountInfoFetcher> ChildAccountInfoFetcher::CreateFrom( - const std::string& account_id, - AccountFetcherService* fetcher_service, - OAuth2TokenService* token_service, - scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory, - invalidation::InvalidationService* invalidation_service) { -#if defined(OS_ANDROID) - return ChildAccountInfoFetcherAndroid::Create(fetcher_service, account_id); -#else - return std::make_unique<ChildAccountInfoFetcherImpl>( - account_id, fetcher_service, token_service, url_loader_factory, - invalidation_service); -#endif -} - -ChildAccountInfoFetcher::~ChildAccountInfoFetcher() { -} - -void ChildAccountInfoFetcher::InitializeForTests() { -#if defined(OS_ANDROID) - ChildAccountInfoFetcherAndroid::InitializeForTests(); -#endif -} diff --git a/chromium/components/signin/core/browser/child_account_info_fetcher.h b/chromium/components/signin/core/browser/child_account_info_fetcher.h deleted file mode 100644 index f5891f5c97f..00000000000 --- a/chromium/components/signin/core/browser/child_account_info_fetcher.h +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2015 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. - -#ifndef COMPONENTS_SIGNIN_CORE_BROWSER_CHILD_ACCOUNT_INFO_FETCHER_H_ -#define COMPONENTS_SIGNIN_CORE_BROWSER_CHILD_ACCOUNT_INFO_FETCHER_H_ - -#include <memory> -#include <string> - -#include "base/memory/ref_counted.h" -#include "build/build_config.h" - -#if defined(OS_ANDROID) -#include <jni.h> -#endif - -namespace invalidation { -class InvalidationService; -} -namespace network { -class SharedURLLoaderFactory; -} -class AccountFetcherService; -class OAuth2TokenService; - -class ChildAccountInfoFetcher { - public: - // Caller takes ownership of the fetcher and keeps it alive in order to - // receive updates. - static std::unique_ptr<ChildAccountInfoFetcher> CreateFrom( - const std::string& account_id, - AccountFetcherService* fetcher_service, - OAuth2TokenService* token_service, - scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory, - invalidation::InvalidationService* invalidation_service); - virtual ~ChildAccountInfoFetcher(); - - static void InitializeForTests(); -}; - -#endif // COMPONENTS_SIGNIN_CORE_BROWSER_CHILD_ACCOUNT_INFO_FETCHER_H_ diff --git a/chromium/components/signin/core/browser/child_account_info_fetcher_android.cc b/chromium/components/signin/core/browser/child_account_info_fetcher_android.cc index 529906b09e8..85c6304d81b 100644 --- a/chromium/components/signin/core/browser/child_account_info_fetcher_android.cc +++ b/chromium/components/signin/core/browser/child_account_info_fetcher_android.cc @@ -8,6 +8,7 @@ #include "base/android/jni_android.h" #include "base/android/jni_string.h" +#include "base/memory/ptr_util.h" #include "components/signin/core/browser/account_fetcher_service.h" #include "components/signin/core/browser/account_tracker_service.h" #include "jni/ChildAccountInfoFetcher_jni.h" @@ -15,9 +16,9 @@ using base::android::JavaParamRef; // static -std::unique_ptr<ChildAccountInfoFetcher> ChildAccountInfoFetcherAndroid::Create( - AccountFetcherService* service, - const std::string& account_id) { +std::unique_ptr<ChildAccountInfoFetcherAndroid> +ChildAccountInfoFetcherAndroid::Create(AccountFetcherService* service, + const std::string& account_id) { std::string account_name = service->account_tracker_service()->GetAccountInfo(account_id).email; // The AccountTrackerService may not be populated correctly in tests. @@ -25,9 +26,8 @@ std::unique_ptr<ChildAccountInfoFetcher> ChildAccountInfoFetcherAndroid::Create( return nullptr; // Call the constructor directly instead of using std::make_unique because the - // constructor is private. Also, use the std::unique_ptr<> constructor instead - // of base::WrapUnique because the _destructor_ of the subclass is private. - return std::unique_ptr<ChildAccountInfoFetcher>( + // constructor is private. + return base::WrapUnique( new ChildAccountInfoFetcherAndroid(service, account_id, account_name)); } @@ -54,7 +54,6 @@ ChildAccountInfoFetcherAndroid::~ChildAccountInfoFetcherAndroid() { void JNI_ChildAccountInfoFetcher_SetIsChildAccount( JNIEnv* env, - const JavaParamRef<jclass>& caller, jlong native_service, const JavaParamRef<jstring>& j_account_id, jboolean is_child_account) { diff --git a/chromium/components/signin/core/browser/child_account_info_fetcher_android.h b/chromium/components/signin/core/browser/child_account_info_fetcher_android.h index 0b73cce4592..1007fe59fd3 100644 --- a/chromium/components/signin/core/browser/child_account_info_fetcher_android.h +++ b/chromium/components/signin/core/browser/child_account_info_fetcher_android.h @@ -9,15 +9,15 @@ #include <string> #include "base/android/scoped_java_ref.h" -#include "components/signin/core/browser/child_account_info_fetcher.h" class AccountFetcherService; -class ChildAccountInfoFetcherAndroid : public ChildAccountInfoFetcher { +class ChildAccountInfoFetcherAndroid { public: - static std::unique_ptr<ChildAccountInfoFetcher> Create( + static std::unique_ptr<ChildAccountInfoFetcherAndroid> Create( AccountFetcherService* service, const std::string& account_id); + ~ChildAccountInfoFetcherAndroid(); static void InitializeForTests(); @@ -25,9 +25,7 @@ class ChildAccountInfoFetcherAndroid : public ChildAccountInfoFetcher { ChildAccountInfoFetcherAndroid(AccountFetcherService* service, const std::string& account_id, const std::string& account_name); - ~ChildAccountInfoFetcherAndroid() override; - private: base::android::ScopedJavaGlobalRef<jobject> j_child_account_info_fetcher_; DISALLOW_COPY_AND_ASSIGN(ChildAccountInfoFetcherAndroid); diff --git a/chromium/components/signin/core/browser/child_account_info_fetcher_impl.cc b/chromium/components/signin/core/browser/child_account_info_fetcher_impl.cc deleted file mode 100644 index cc0fbf4e808..00000000000 --- a/chromium/components/signin/core/browser/child_account_info_fetcher_impl.cc +++ /dev/null @@ -1,181 +0,0 @@ -// Copyright 2015 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 "components/signin/core/browser/child_account_info_fetcher_impl.h" - -#include "base/stl_util.h" -#include "base/strings/string_split.h" -#include "base/trace_event/trace_event.h" -#include "base/values.h" -#include "components/invalidation/public/invalidation_service.h" -#include "components/invalidation/public/object_id_invalidation_map.h" -#include "components/signin/core/browser/account_fetcher_service.h" -#include "components/signin/core/browser/account_tracker_service.h" -#include "components/signin/core/browser/signin_client.h" -#include "google/cacheinvalidation/types.pb.h" -#include "google_apis/gaia/gaia_auth_fetcher.h" -#include "google_apis/gaia/gaia_constants.h" -#include "services/network/public/cpp/shared_url_loader_factory.h" - -// TODO(maroun): Remove this file. - -namespace { - -const char kFetcherId[] = "ChildAccountInfoFetcherImpl"; - -// Exponential backoff policy on service flag fetching failure. -const net::BackoffEntry::Policy kBackoffPolicy = { - 0, // Number of initial errors to ignore without backoff. - 2000, // Initial delay for backoff in ms. - 2, // Factor to multiply waiting time by. - 0.2, // Fuzzing percentage. 20% will spread requests randomly between - // 80-100% of the calculated time. - 1000 * 60 * 60* 4, // Maximum time to delay requests by (4 hours). - -1, // Don't discard entry even if unused. - false, // Don't use the initial delay unless the last request was an error. -}; - -// The invalidation object ID used for child account graduation event. -// The syntax is: -// 'U' -> This is a user specific invalidation. -// 'CA' -> Namespace used for all ChildAccount invalidations. -// 'GRAD' -> Indicates the actual event i.e. child account graduation. -const char kChildAccountGraduationId[] = "UCAGRAD"; - -} // namespace - -ChildAccountInfoFetcherImpl::ChildAccountInfoFetcherImpl( - const std::string& account_id, - AccountFetcherService* fetcher_service, - OAuth2TokenService* token_service, - scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory, - invalidation::InvalidationService* invalidation_service) - : OAuth2TokenService::Consumer(kFetcherId), - token_service_(token_service), - url_loader_factory_(url_loader_factory), - fetcher_service_(fetcher_service), - invalidation_service_(invalidation_service), - account_id_(account_id), - backoff_(&kBackoffPolicy), - fetch_in_progress_(false) { - TRACE_EVENT_ASYNC_BEGIN1("AccountFetcherService", kFetcherId, this, - "account_id", account_id); - // Invalidation service may not be available in tests. - if (invalidation_service_) { - invalidation_service_->RegisterInvalidationHandler(this); - syncer::ObjectIdSet ids; - ids.insert(invalidation::ObjectId( - ipc::invalidation::ObjectSource::CHROME_COMPONENTS, - kChildAccountGraduationId)); - bool insert_success = - invalidation_service_->UpdateRegisteredInvalidationIds(this, ids); - DCHECK(insert_success); - } - FetchIfNotInProgress(); -} - -ChildAccountInfoFetcherImpl::~ChildAccountInfoFetcherImpl() { - TRACE_EVENT_ASYNC_END0("AccountFetcherService", kFetcherId, this); - if (invalidation_service_) - UnregisterInvalidationHandler(); -} - -void ChildAccountInfoFetcherImpl::FetchIfNotInProgress() { - DCHECK(thread_checker_.CalledOnValidThread()); - if (fetch_in_progress_) - return; - fetch_in_progress_ = true; - OAuth2TokenService::ScopeSet scopes; - scopes.insert(GaiaConstants::kOAuth1LoginScope); - login_token_request_ = - token_service_->StartRequest(account_id_, scopes, this); -} - -void ChildAccountInfoFetcherImpl::OnGetTokenSuccess( - const OAuth2TokenService::Request* request, - const OAuth2AccessTokenConsumer::TokenResponse& token_response) { - TRACE_EVENT_ASYNC_STEP_PAST0("AccountFetcherService", kFetcherId, this, - "OnGetTokenSuccess"); - DCHECK_EQ(request, login_token_request_.get()); - - gaia_auth_fetcher_ = fetcher_service_->signin_client_->CreateGaiaAuthFetcher( - this, gaia::GaiaSource::kChrome, url_loader_factory_); - gaia_auth_fetcher_->StartOAuthLogin(token_response.access_token, - GaiaConstants::kGaiaService); -} - -void ChildAccountInfoFetcherImpl::OnGetTokenFailure( - const OAuth2TokenService::Request* request, - const GoogleServiceAuthError& error) { - HandleFailure(); -} - -void ChildAccountInfoFetcherImpl::OnClientLoginSuccess( - const ClientLoginResult& result) { - gaia_auth_fetcher_->StartGetUserInfo(result.lsid); -} - -void ChildAccountInfoFetcherImpl::OnClientLoginFailure( - const GoogleServiceAuthError& error) { - HandleFailure(); -} - -void ChildAccountInfoFetcherImpl::OnGetUserInfoSuccess( - const UserInfoMap& data) { - auto services_iter = data.find("allServices"); - if (services_iter != data.end()) { - std::vector<std::string> service_flags = base::SplitString( - services_iter->second, ",", base::TRIM_WHITESPACE, - base::SPLIT_WANT_ALL); - bool is_child_account = base::ContainsValue( - service_flags, AccountTrackerService::kChildAccountServiceFlag); - if (!is_child_account && invalidation_service_) { - // Don't bother listening for invalidations as a non-child account can't - // become a child account. - bool insert_success = - invalidation_service_->UpdateRegisteredInvalidationIds( - this, syncer::ObjectIdSet()); - DCHECK(insert_success); - UnregisterInvalidationHandler(); - } - fetcher_service_->SetIsChildAccount(account_id_, is_child_account); - } else { - DLOG(ERROR) << "ChildAccountInfoFetcherImpl::OnGetUserInfoSuccess: " - << "GetUserInfo response didn't include allServices field."; - } - fetch_in_progress_ = false; -} - -void ChildAccountInfoFetcherImpl::OnGetUserInfoFailure( - const GoogleServiceAuthError& error) { - HandleFailure(); -} - -void ChildAccountInfoFetcherImpl::HandleFailure() { - fetch_in_progress_ = false; - backoff_.InformOfRequest(false); - timer_.Start(FROM_HERE, backoff_.GetTimeUntilRelease(), this, - &ChildAccountInfoFetcherImpl::FetchIfNotInProgress); -} - -void ChildAccountInfoFetcherImpl::UnregisterInvalidationHandler() { - invalidation_service_->UnregisterInvalidationHandler(this); - invalidation_service_ = nullptr; -} - -void ChildAccountInfoFetcherImpl::OnInvalidatorStateChange( - syncer::InvalidatorState state) { - if (state == syncer::INVALIDATOR_SHUTTING_DOWN) - UnregisterInvalidationHandler(); -} - -void ChildAccountInfoFetcherImpl::OnIncomingInvalidation( - const syncer::ObjectIdInvalidationMap& invalidation_map) { - FetchIfNotInProgress(); - invalidation_map.AcknowledgeAll(); -} - -std::string ChildAccountInfoFetcherImpl::GetOwnerName() const { - return std::string(kFetcherId); -} diff --git a/chromium/components/signin/core/browser/child_account_info_fetcher_impl.h b/chromium/components/signin/core/browser/child_account_info_fetcher_impl.h deleted file mode 100644 index e5383f6530d..00000000000 --- a/chromium/components/signin/core/browser/child_account_info_fetcher_impl.h +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright 2015 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. - -#ifndef COMPONENTS_SIGNIN_CORE_BROWSER_CHILD_ACCOUNT_INFO_FETCHER_IMPL_H_ -#define COMPONENTS_SIGNIN_CORE_BROWSER_CHILD_ACCOUNT_INFO_FETCHER_IMPL_H_ - -#include <memory> - -#include "base/macros.h" -#include "base/memory/ref_counted.h" -#include "base/threading/thread_checker.h" -#include "base/timer/timer.h" -#include "components/invalidation/public/invalidation_handler.h" -#include "components/signin/core/browser/child_account_info_fetcher.h" -#include "google_apis/gaia/gaia_auth_consumer.h" -#include "google_apis/gaia/oauth2_token_service.h" -#include "net/base/backoff_entry.h" - -// TODO(maroun): Remove this file. - -namespace network { -class SharedURLLoaderFactory; -} - -class GaiaAuthFetcher; - -class ChildAccountInfoFetcherImpl : public ChildAccountInfoFetcher, - public OAuth2TokenService::Consumer, - public GaiaAuthConsumer, - public syncer::InvalidationHandler { - public: - ChildAccountInfoFetcherImpl( - const std::string& account_id, - AccountFetcherService* fetcher_service, - OAuth2TokenService* token_service, - scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory, - invalidation::InvalidationService* invalidation_service); - ~ChildAccountInfoFetcherImpl() override; - - private: - void FetchIfNotInProgress(); - void HandleFailure(); - void UnregisterInvalidationHandler(); - - // OAuth2TokenService::Consumer: - void OnGetTokenSuccess( - const OAuth2TokenService::Request* request, - const OAuth2AccessTokenConsumer::TokenResponse& token_response) override; - void OnGetTokenFailure(const OAuth2TokenService::Request* request, - const GoogleServiceAuthError& error) override; - - // GaiaAuthConsumer: - void OnClientLoginSuccess(const ClientLoginResult& result) override; - void OnClientLoginFailure(const GoogleServiceAuthError& error) override; - void OnGetUserInfoSuccess(const UserInfoMap& data) override; - void OnGetUserInfoFailure(const GoogleServiceAuthError& error) override; - - // syncer::InvalidationHandler: - void OnInvalidatorStateChange(syncer::InvalidatorState state) override; - void OnIncomingInvalidation( - const syncer::ObjectIdInvalidationMap& invalidation_map) override; - std::string GetOwnerName() const override; - - OAuth2TokenService* token_service_; - scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_; - AccountFetcherService* fetcher_service_; - invalidation::InvalidationService* invalidation_service_; - const std::string account_id_; - - // If fetching fails, retry with exponential backoff. - base::OneShotTimer timer_; - net::BackoffEntry backoff_; - - std::unique_ptr<OAuth2TokenService::Request> login_token_request_; - std::unique_ptr<GaiaAuthFetcher> gaia_auth_fetcher_; - - bool fetch_in_progress_; - base::ThreadChecker thread_checker_; - - DISALLOW_COPY_AND_ASSIGN(ChildAccountInfoFetcherImpl); -}; - -#endif // COMPONENTS_SIGNIN_CORE_BROWSER_CHILD_ACCOUNT_INFO_FETCHER_IMPL_H_ diff --git a/chromium/components/signin/core/browser/dice_account_reconcilor_delegate.cc b/chromium/components/signin/core/browser/dice_account_reconcilor_delegate.cc index 53aacc6b21c..5b053f49f0e 100644 --- a/chromium/components/signin/core/browser/dice_account_reconcilor_delegate.cc +++ b/chromium/components/signin/core/browser/dice_account_reconcilor_delegate.cc @@ -20,11 +20,12 @@ DiceAccountReconcilorDelegate::DiceAccountReconcilorDelegate( AccountConsistencyMethod account_consistency) : signin_client_(signin_client), account_consistency_(account_consistency) { DCHECK(signin_client_); + DCHECK(DiceMethodGreaterOrEqual(account_consistency_, + AccountConsistencyMethod::kDiceMigration)); } bool DiceAccountReconcilorDelegate::IsReconcileEnabled() const { - return DiceMethodGreaterOrEqual(account_consistency_, - AccountConsistencyMethod::kDiceMigration); + return true; } bool DiceAccountReconcilorDelegate::IsAccountConsistencyEnforced() const { @@ -171,12 +172,9 @@ void DiceAccountReconcilorDelegate::OnReconcileFinished( // Migration happens on startup if the last reconcile was a no-op and the // refresh tokens are Dice-compatible. - if (DiceMethodGreaterOrEqual(account_consistency_, - AccountConsistencyMethod::kDiceMigration)) { - signin_client_->SetReadyForDiceMigration( - reconcile_is_noop && signin_client_->GetPrefs()->GetBoolean( - prefs::kTokenServiceDiceCompatible)); - } + signin_client_->SetReadyForDiceMigration( + reconcile_is_noop && signin_client_->GetPrefs()->GetBoolean( + prefs::kTokenServiceDiceCompatible)); } } // namespace signin diff --git a/chromium/components/signin/core/browser/dice_account_reconcilor_delegate_unittest.cc b/chromium/components/signin/core/browser/dice_account_reconcilor_delegate_unittest.cc index f3b36df5807..4c467559d79 100644 --- a/chromium/components/signin/core/browser/dice_account_reconcilor_delegate_unittest.cc +++ b/chromium/components/signin/core/browser/dice_account_reconcilor_delegate_unittest.cc @@ -66,15 +66,6 @@ TEST(DiceAccountReconcilorDelegateTest, OnReconcileFinished) { DiceTestSigninClient client(&pref_service); { - // Dice migration not enabled. - testing::InSequence mock_sequence; - EXPECT_CALL(client, SetReadyForDiceMigration(testing::_)).Times(0); - DiceAccountReconcilorDelegate delegate( - &client, AccountConsistencyMethod::kDiceFixAuthErrors); - delegate.OnReconcileFinished("account", true /* is_reconcile_noop */); - } - - { // Dice migration enabled, but token service is not ready. testing::InSequence mock_sequence; EXPECT_CALL(client, SetReadyForDiceMigration(false)).Times(1); diff --git a/chromium/components/signin/core/browser/dice_header_helper.cc b/chromium/components/signin/core/browser/dice_header_helper.cc index fb51916e604..4a7c91f9f36 100644 --- a/chromium/components/signin/core/browser/dice_header_helper.cc +++ b/chromium/components/signin/core/browser/dice_header_helper.cc @@ -20,7 +20,6 @@ namespace { // Request parameters. const char kRequestSigninAll[] = "all_accounts"; -const char kRequestSigninSyncAccount[] = "sync_account"; const char kRequestSignoutNoConfirmation[] = "no_confirmation"; const char kRequestSignoutShowConfirmation[] = "show_confirmation"; @@ -194,24 +193,12 @@ bool DiceHeaderHelper::IsUrlEligibleForRequestHeader(const GURL& url) { return false; } - // With kDiceFixAuthError, only set the request header if the user is signed - // in and has an authentication error. - if (!signed_in_with_auth_error_ && - (account_consistency_ == AccountConsistencyMethod::kDiceFixAuthErrors)) { - return false; - } - return gaia::IsGaiaSignonRealm(url.GetOrigin()); } std::string DiceHeaderHelper::BuildRequestHeader( const std::string& sync_account_id, const std::string& device_id) { - // When fixing auth errors, only add the header when Sync is actually in error - // state. - DCHECK( - signed_in_with_auth_error_ || - (account_consistency_ != AccountConsistencyMethod::kDiceFixAuthErrors)); DCHECK(!(sync_account_id.empty() && signed_in_with_auth_error_)); std::vector<std::string> parts; @@ -224,10 +211,7 @@ std::string DiceHeaderHelper::BuildRequestHeader( parts.push_back("sync_account_id=" + sync_account_id); // Restrict Signin to Sync account only when fixing auth errors. - std::string signin_mode = - (account_consistency_ == AccountConsistencyMethod::kDiceFixAuthErrors) - ? kRequestSigninSyncAccount - : kRequestSigninAll; + std::string signin_mode = kRequestSigninAll; parts.push_back("signin_mode=" + signin_mode); // Show the signout confirmation only when Dice is fully enabled. diff --git a/chromium/components/signin/core/browser/fake_account_fetcher_service.cc b/chromium/components/signin/core/browser/fake_account_fetcher_service.cc index 684cb9e2e44..c855b354b10 100644 --- a/chromium/components/signin/core/browser/fake_account_fetcher_service.cc +++ b/chromium/components/signin/core/browser/fake_account_fetcher_service.cc @@ -28,14 +28,7 @@ void FakeAccountFetcherService::FakeUserInfoFetchSuccess( user_info.SetString("given_name", given_name); user_info.SetString("locale", locale); user_info.SetString("picture", picture_url); - account_tracker_service()->SetAccountStateFromUserInfo(account_id, - &user_info); -} - -void FakeAccountFetcherService::FakeSetIsChildAccount( - const std::string& account_id, - bool is_child_account) { - SetIsChildAccount(account_id, is_child_account); + account_tracker_service()->SetAccountInfoFromUserInfo(account_id, &user_info); } void FakeAccountFetcherService::StartFetchingUserInfo( @@ -43,11 +36,6 @@ void FakeAccountFetcherService::StartFetchingUserInfo( // In tests, don't do actual network fetch. } -void FakeAccountFetcherService::StartFetchingChildInfo( - const std::string& account_id) { - // In tests, don't do actual network fetch. -} - TestImageDecoder::TestImageDecoder() = default; TestImageDecoder::~TestImageDecoder() = default; diff --git a/chromium/components/signin/core/browser/fake_account_fetcher_service.h b/chromium/components/signin/core/browser/fake_account_fetcher_service.h index 5a3cafa27c2..9e1298f2d43 100644 --- a/chromium/components/signin/core/browser/fake_account_fetcher_service.h +++ b/chromium/components/signin/core/browser/fake_account_fetcher_service.h @@ -33,14 +33,11 @@ class FakeAccountFetcherService : public AccountFetcherService { const std::string& given_name, const std::string& locale, const std::string& picture_url); - void FakeSetIsChildAccount(const std::string& account_id, - bool is_child_account); FakeAccountFetcherService(); private: void StartFetchingUserInfo(const std::string& account_id) override; - void StartFetchingChildInfo(const std::string& account_id) override; DISALLOW_COPY_AND_ASSIGN(FakeAccountFetcherService); }; diff --git a/chromium/components/signin/core/browser/fake_auth_status_provider.cc b/chromium/components/signin/core/browser/fake_auth_status_provider.cc deleted file mode 100644 index 77e10db9d92..00000000000 --- a/chromium/components/signin/core/browser/fake_auth_status_provider.cc +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2014 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 "components/signin/core/browser/fake_auth_status_provider.h" - -FakeAuthStatusProvider::FakeAuthStatusProvider(SigninErrorController* error) - : error_provider_(error), - auth_error_(GoogleServiceAuthError::AuthErrorNone()) { - error_provider_->AddProvider(this); -} - -FakeAuthStatusProvider::~FakeAuthStatusProvider() { - error_provider_->RemoveProvider(this); -} - -std::string FakeAuthStatusProvider::GetAccountId() const { - return account_id_; -} - -GoogleServiceAuthError FakeAuthStatusProvider::GetAuthStatus() const { - return auth_error_; -} - -void FakeAuthStatusProvider::SetAuthError(const std::string& account_id, - const GoogleServiceAuthError& error) { - account_id_ = account_id; - auth_error_ = error; - error_provider_->AuthStatusChanged(); -} diff --git a/chromium/components/signin/core/browser/fake_auth_status_provider.h b/chromium/components/signin/core/browser/fake_auth_status_provider.h deleted file mode 100644 index 48a03787ea1..00000000000 --- a/chromium/components/signin/core/browser/fake_auth_status_provider.h +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2014 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. - -#ifndef COMPONENTS_SIGNIN_CORE_BROWSER_FAKE_AUTH_STATUS_PROVIDER_H_ -#define COMPONENTS_SIGNIN_CORE_BROWSER_FAKE_AUTH_STATUS_PROVIDER_H_ - -#include "components/signin/core/browser/signin_error_controller.h" - -// Helper class that reports auth errors to SigninErrorController. Automatically -// registers and de-registers itself as an AuthStatusProvider in the -// constructor and destructor. -class FakeAuthStatusProvider - : public SigninErrorController::AuthStatusProvider { - public: - explicit FakeAuthStatusProvider(SigninErrorController* error); - ~FakeAuthStatusProvider() override; - - // Sets the auth error that this provider reports to SigninErrorController. - // Also notifies SigninErrorController via AuthStatusChanged(). - void SetAuthError(const std::string& account_id, - const GoogleServiceAuthError& error); - - void set_error_without_status_change(const GoogleServiceAuthError& error) { - auth_error_ = error; - } - - // AuthStatusProvider implementation. - std::string GetAccountId() const override; - GoogleServiceAuthError GetAuthStatus() const override; - - private: - SigninErrorController* error_provider_; - std::string account_id_; - GoogleServiceAuthError auth_error_; -}; - -#endif // COMPONENTS_SIGNIN_CORE_BROWSER_FAKE_AUTH_STATUS_PROVIDER_H_ diff --git a/chromium/components/signin/core/browser/fake_gaia_cookie_manager_service.cc b/chromium/components/signin/core/browser/fake_gaia_cookie_manager_service.cc index 619a489a7f9..e37588f3941 100644 --- a/chromium/components/signin/core/browser/fake_gaia_cookie_manager_service.cc +++ b/chromium/components/signin/core/browser/fake_gaia_cookie_manager_service.cc @@ -3,9 +3,9 @@ // found in the LICENSE file. #include "components/signin/core/browser/fake_gaia_cookie_manager_service.h" - #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" +#include "components/signin/core/browser/list_accounts_test_utils.h" #include "components/signin/core/browser/profile_oauth2_token_service.h" #include "google_apis/gaia/gaia_constants.h" #include "google_apis/gaia/gaia_urls.h" @@ -13,79 +13,80 @@ #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h" #include "services/network/test/test_url_loader_factory.h" +namespace { +// Factory method to return a SharedURLLoaderFactory of our choosing. +scoped_refptr<network::SharedURLLoaderFactory> GetSharedURLLoaderFactory( + scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory) { + return shared_url_loader_factory; +} +} // namespace + +FakeGaiaCookieManagerService::FakeGaiaCookieManagerService( + OAuth2TokenService* token_service, + SigninClient* client) + : GaiaCookieManagerService( + token_service, + client, + base::BindRepeating(&SigninClient::GetURLLoaderFactory, + base::Unretained(client))) {} + FakeGaiaCookieManagerService::FakeGaiaCookieManagerService( OAuth2TokenService* token_service, SigninClient* client, - bool use_fake_url_loader) - : GaiaCookieManagerService(token_service, client) { - if (use_fake_url_loader) { - test_url_loader_factory_ = - std::make_unique<network::TestURLLoaderFactory>(); - shared_loader_factory_ = - base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>( - test_url_loader_factory_.get()); - } -} + network::TestURLLoaderFactory* test_url_loader_factory) + : FakeGaiaCookieManagerService( + token_service, + client, + test_url_loader_factory, + base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>( + test_url_loader_factory)) {} + +FakeGaiaCookieManagerService::FakeGaiaCookieManagerService( + OAuth2TokenService* token_service, + SigninClient* client, + network::TestURLLoaderFactory* test_url_loader_factory, + scoped_refptr<network::WeakWrapperSharedURLLoaderFactory> + shared_url_loader_factory) + : GaiaCookieManagerService(token_service, + client, + base::BindRepeating(&GetSharedURLLoaderFactory, + shared_url_loader_factory)), + test_url_loader_factory_(test_url_loader_factory), + shared_url_loader_factory_(shared_url_loader_factory) {} FakeGaiaCookieManagerService::~FakeGaiaCookieManagerService() { - if (shared_loader_factory_) - shared_loader_factory_->Detach(); + if (shared_url_loader_factory_) + shared_url_loader_factory_->Detach(); } void FakeGaiaCookieManagerService::SetListAccountsResponseHttpNotFound() { - test_url_loader_factory_->AddResponse( - GaiaUrls::GetInstance() - ->ListAccountsURLWithSource(GaiaConstants::kChromeSource) - .spec(), - /*content=*/"", net::HTTP_NOT_FOUND); + signin::SetListAccountsResponseHttpNotFound(test_url_loader_factory_); } void FakeGaiaCookieManagerService::SetListAccountsResponseWebLoginRequired() { - test_url_loader_factory_->AddResponse( - GaiaUrls::GetInstance() - ->ListAccountsURLWithSource(GaiaConstants::kChromeSource) - .spec(), - "Info=WebLoginRequired"); + signin::SetListAccountsResponseWebLoginRequired(test_url_loader_factory_); } void FakeGaiaCookieManagerService::SetListAccountsResponseWithParams( - const std::vector<CookieParams>& params) { - std::vector<std::string> response_body; - for (const auto& param : params) { - std::string response_part = base::StringPrintf( - "[\"b\", 0, \"n\", \"%s\", \"p\", 0, 0, 0, 0, %d, \"%s\"", - param.email.c_str(), param.valid ? 1 : 0, param.gaia_id.c_str()); - if (param.signed_out || !param.verified) { - response_part += - base::StringPrintf(", null, null, null, %d, %d", - param.signed_out ? 1 : 0, param.verified ? 1 : 0); - } - response_part += "]"; - response_body.push_back(response_part); - } - - test_url_loader_factory_->AddResponse( - GaiaUrls::GetInstance() - ->ListAccountsURLWithSource(GaiaConstants::kChromeSource) - .spec(), - std::string("[\"f\", [") + base::JoinString(response_body, ", ") + "]]"); + const std::vector<signin::CookieParams>& params) { + signin::SetListAccountsResponseWithParams(params, test_url_loader_factory_); } void FakeGaiaCookieManagerService::SetListAccountsResponseNoAccounts() { - SetListAccountsResponseWithParams({}); + signin::SetListAccountsResponseNoAccounts(test_url_loader_factory_); } void FakeGaiaCookieManagerService::SetListAccountsResponseOneAccount( const std::string& email, const std::string& gaia_id) { - CookieParams params = {email, gaia_id, true /* valid */, - false /* signed_out */, true /* verified */}; - SetListAccountsResponseWithParams({params}); + signin::SetListAccountsResponseOneAccount(email, gaia_id, + test_url_loader_factory_); } void FakeGaiaCookieManagerService::SetListAccountsResponseOneAccountWithParams( - const CookieParams& params) { - SetListAccountsResponseWithParams({params}); + const signin::CookieParams& params) { + signin::SetListAccountsResponseOneAccountWithParams(params, + test_url_loader_factory_); } void FakeGaiaCookieManagerService::SetListAccountsResponseTwoAccounts( @@ -93,16 +94,6 @@ void FakeGaiaCookieManagerService::SetListAccountsResponseTwoAccounts( const std::string& gaia_id1, const std::string& email2, const std::string& gaia_id2) { - SetListAccountsResponseWithParams( - {{email1, gaia_id1, true /* valid */, false /* signed_out */, - true /* verified */}, - {email2, gaia_id2, true /* valid */, false /* signed_out */, - true /* verified */}}); -} - -scoped_refptr<network::SharedURLLoaderFactory> -FakeGaiaCookieManagerService::GetURLLoaderFactory() { - return shared_loader_factory_ - ? shared_loader_factory_ - : GaiaCookieManagerService::GetURLLoaderFactory(); + signin::SetListAccountsResponseTwoAccounts(email1, gaia_id1, email2, gaia_id2, + test_url_loader_factory_); } diff --git a/chromium/components/signin/core/browser/fake_gaia_cookie_manager_service.h b/chromium/components/signin/core/browser/fake_gaia_cookie_manager_service.h index 66a67cdb0b8..9da87b92636 100644 --- a/chromium/components/signin/core/browser/fake_gaia_cookie_manager_service.h +++ b/chromium/components/signin/core/browser/fake_gaia_cookie_manager_service.h @@ -10,50 +10,61 @@ #include "base/macros.h" #include "base/memory/scoped_refptr.h" #include "components/signin/core/browser/gaia_cookie_manager_service.h" +#include "components/signin/core/browser/list_accounts_test_utils.h" +#include "services/network/test/test_url_loader_factory.h" namespace network { -class TestURLLoaderFactory; class WeakWrapperSharedURLLoaderFactory; } class FakeGaiaCookieManagerService : public GaiaCookieManagerService { public: - // Parameters for the fake ListAccounts response. - struct CookieParams { - std::string email; - std::string gaia_id; - bool valid; - bool signed_out; - bool verified; - }; - + // Convenience constructor overload which uses the SharedURLLoaderFactory from + // SigninClient. FakeGaiaCookieManagerService(OAuth2TokenService* token_service, - SigninClient* client, - bool use_fake_url_fetcher = true); + SigninClient* client); + + // Constructor overload for tests that want to use a TestURLLoaderFactory for + // cookie related requests. + FakeGaiaCookieManagerService( + OAuth2TokenService* token_service, + SigninClient* client, + network::TestURLLoaderFactory* test_url_loader_factory); + ~FakeGaiaCookieManagerService() override; void SetListAccountsResponseHttpNotFound(); void SetListAccountsResponseWebLoginRequired(); void SetListAccountsResponseWithParams( - const std::vector<CookieParams>& params); + const std::vector<signin::CookieParams>& params); // Helper methods, equivalent to calling SetListAccountsResponseWithParams(). void SetListAccountsResponseNoAccounts(); void SetListAccountsResponseOneAccount(const std::string& email, const std::string& gaia_id); - void SetListAccountsResponseOneAccountWithParams(const CookieParams& params); + void SetListAccountsResponseOneAccountWithParams( + const signin::CookieParams& params); void SetListAccountsResponseTwoAccounts(const std::string& email1, const std::string& gaia_id1, const std::string& email2, const std::string& gaia_id2); private: - scoped_refptr<network::SharedURLLoaderFactory> GetURLLoaderFactory() override; + // Internal constructor which does the actual construction. + FakeGaiaCookieManagerService( + OAuth2TokenService* token_service, + SigninClient* client, + network::TestURLLoaderFactory* test_url_loader_factory, + scoped_refptr<network::WeakWrapperSharedURLLoaderFactory> + shared_url_loader_factory); + + // Provides a fake response for calls to /ListAccounts. + // Owned by the client if passed in via the constructor that takes in this + // pointer; null otherwise. + network::TestURLLoaderFactory* test_url_loader_factory_ = nullptr; - // Provide a fake response for calls to /ListAccounts. - std::unique_ptr<network::TestURLLoaderFactory> test_url_loader_factory_; scoped_refptr<network::WeakWrapperSharedURLLoaderFactory> - shared_loader_factory_; + shared_url_loader_factory_; DISALLOW_COPY_AND_ASSIGN(FakeGaiaCookieManagerService); }; diff --git a/chromium/components/signin/core/browser/fake_signin_manager.cc b/chromium/components/signin/core/browser/fake_signin_manager.cc index 8013a5ec1b4..8fe75e3c647 100644 --- a/chromium/components/signin/core/browser/fake_signin_manager.cc +++ b/chromium/components/signin/core/browser/fake_signin_manager.cc @@ -14,11 +14,9 @@ FakeSigninManagerBase::FakeSigninManagerBase( SigninClient* client, - AccountTrackerService* account_tracker_service, - SigninErrorController* signin_error_controller) - : SigninManagerBase(client, - account_tracker_service, - signin_error_controller) {} + ProfileOAuth2TokenService* token_service, + AccountTrackerService* account_tracker_service) + : SigninManagerBase(client, token_service, account_tracker_service) {} FakeSigninManagerBase::~FakeSigninManagerBase() {} @@ -37,20 +35,6 @@ FakeSigninManager::FakeSigninManager( token_service, account_tracker_service, cookie_manager_service, - nullptr, - signin::AccountConsistencyMethod::kDisabled) {} - -FakeSigninManager::FakeSigninManager( - SigninClient* client, - ProfileOAuth2TokenService* token_service, - AccountTrackerService* account_tracker_service, - GaiaCookieManagerService* cookie_manager_service, - SigninErrorController* signin_error_controller) - : FakeSigninManager(client, - token_service, - account_tracker_service, - cookie_manager_service, - signin_error_controller, signin::AccountConsistencyMethod::kDisabled) {} FakeSigninManager::FakeSigninManager( @@ -58,13 +42,11 @@ FakeSigninManager::FakeSigninManager( ProfileOAuth2TokenService* token_service, AccountTrackerService* account_tracker_service, GaiaCookieManagerService* cookie_manager_service, - SigninErrorController* signin_error_controller, signin::AccountConsistencyMethod account_consistency) : SigninManager(client, token_service, account_tracker_service, cookie_manager_service, - signin_error_controller, account_consistency), token_service_(token_service) {} diff --git a/chromium/components/signin/core/browser/fake_signin_manager.h b/chromium/components/signin/core/browser/fake_signin_manager.h index 33a2af3bc5a..5c7de4a6737 100644 --- a/chromium/components/signin/core/browser/fake_signin_manager.h +++ b/chromium/components/signin/core/browser/fake_signin_manager.h @@ -17,10 +17,9 @@ class FakeSigninManagerBase : public SigninManagerBase { public: - FakeSigninManagerBase( - SigninClient* client, - AccountTrackerService* account_tracker_service, - SigninErrorController* signin_error_controller = nullptr); + FakeSigninManagerBase(SigninClient* client, + ProfileOAuth2TokenService* token_service, + AccountTrackerService* account_tracker_service); ~FakeSigninManagerBase() override; void SignIn(const std::string& account_id); @@ -41,13 +40,6 @@ class FakeSigninManager : public SigninManager { ProfileOAuth2TokenService* token_service, AccountTrackerService* account_tracker_service, GaiaCookieManagerService* cookie_manager_service, - SigninErrorController* signin_error_controller); - - FakeSigninManager(SigninClient* client, - ProfileOAuth2TokenService* token_service, - AccountTrackerService* account_tracker_service, - GaiaCookieManagerService* cookie_manager_service, - SigninErrorController* signin_error_controller, signin::AccountConsistencyMethod account_consistency); ~FakeSigninManager() override; diff --git a/chromium/components/signin/core/browser/gaia_cookie_manager_service.cc b/chromium/components/signin/core/browser/gaia_cookie_manager_service.cc index b9020bde77a..b9aa8215508 100644 --- a/chromium/components/signin/core/browser/gaia_cookie_manager_service.cc +++ b/chromium/components/signin/core/browser/gaia_cookie_manager_service.cc @@ -20,6 +20,7 @@ #include "components/data_use_measurement/core/data_use_user_data.h" #include "components/signin/core/browser/account_tracker_service.h" #include "components/signin/core/browser/signin_metrics.h" +#include "components/signin/core/browser/ubertoken_fetcher_impl.h" #include "google_apis/gaia/gaia_constants.h" #include "google_apis/gaia/gaia_urls.h" #include "google_apis/gaia/oauth2_token_service.h" @@ -156,8 +157,7 @@ GaiaCookieManagerService::GaiaCookieRequest::GaiaCookieRequest( account_ids_(other.account_ids()), source_(other.source()) {} -GaiaCookieManagerService::GaiaCookieRequest::~GaiaCookieRequest() { -} +GaiaCookieManagerService::GaiaCookieRequest::~GaiaCookieRequest() {} const std::string GaiaCookieManagerService::GaiaCookieRequest::GetAccountID() { DCHECK_EQ(request_type_, GaiaCookieRequestType::ADD_ACCOUNT); @@ -420,17 +420,28 @@ void GaiaCookieManagerService::ExternalCcResultFetcher:: GaiaCookieManagerService::GaiaCookieManagerService( OAuth2TokenService* token_service, SigninClient* signin_client) + : GaiaCookieManagerService( + token_service, + signin_client, + base::BindRepeating(&SigninClient::GetURLLoaderFactory, + base::Unretained(signin_client))) {} + +GaiaCookieManagerService::GaiaCookieManagerService( + OAuth2TokenService* token_service, + SigninClient* signin_client, + base::RepeatingCallback<scoped_refptr<network::SharedURLLoaderFactory>()> + shared_url_loader_factory_getter) : OAuth2TokenService::Consumer("gaia_cookie_manager"), token_service_(token_service), signin_client_(signin_client), + shared_url_loader_factory_getter_(shared_url_loader_factory_getter), external_cc_result_fetcher_(this), fetcher_backoff_(&kBackoffPolicy), fetcher_retries_(0), cookie_listener_binding_(this), external_cc_result_fetched_(false), list_accounts_stale_(true), - weak_ptr_factory_(this) { -} + weak_ptr_factory_(this) {} GaiaCookieManagerService::~GaiaCookieManagerService() { CancelAll(); @@ -507,8 +518,8 @@ void GaiaCookieManagerService::AddAccountToCookieInternal( gaia::GaiaSource source) { DCHECK(!account_id.empty()); if (!signin_client_->AreSigninCookiesAllowed()) { - SignalComplete(account_id, - GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED)); + SignalComplete(account_id, GoogleServiceAuthError( + GoogleServiceAuthError::REQUEST_CANCELED)); return; } @@ -616,8 +627,8 @@ void GaiaCookieManagerService::LogOutAllAccounts(gaia::GaiaSource source) { // Remove all but the executing request. Re-add all requests being kept. if (requests_.size() > 1) { requests_.erase(requests_.begin() + 1, requests_.end()); - requests_.insert( - requests_.end(), requests_to_keep.begin(), requests_to_keep.end()); + requests_.insert(requests_.end(), requests_to_keep.begin(), + requests_to_keep.end()); } } @@ -649,7 +660,7 @@ void GaiaCookieManagerService::CancelAll() { scoped_refptr<network::SharedURLLoaderFactory> GaiaCookieManagerService::GetURLLoaderFactory() { - return signin_client_->GetURLLoaderFactory(); + return shared_url_loader_factory_getter_.Run(); } void GaiaCookieManagerService::OnCookieChange( @@ -703,10 +714,21 @@ void GaiaCookieManagerService::SignalSetAccountsComplete( observer.OnSetAccountsInCookieCompleted(error); } -void GaiaCookieManagerService::OnUbertokenSuccess( +void GaiaCookieManagerService::OnUbertokenFetchComplete( + GoogleServiceAuthError error, const std::string& uber_token) { + if (error != GoogleServiceAuthError::AuthErrorNone()) { + // Note that the UberToken fetcher already retries transient errors. + const std::string account_id = requests_.front().GetAccountID(); + VLOG(1) << "Failed to retrieve ubertoken" + << " account=" << account_id << " error=" << error.ToString(); + HandleNextRequest(); + SignalComplete(account_id, error); + return; + } + DCHECK(requests_.front().request_type() == - GaiaCookieRequestType::ADD_ACCOUNT); + GaiaCookieRequestType::ADD_ACCOUNT); VLOG(1) << "GaiaCookieManagerService::OnUbertokenSuccess" << " account=" << requests_.front().GetAccountID(); fetcher_retries_ = 0; @@ -723,16 +745,6 @@ void GaiaCookieManagerService::OnUbertokenSuccess( base::Unretained(this))); } -void GaiaCookieManagerService::OnUbertokenFailure( - const GoogleServiceAuthError& error) { - // Note that the UberToken fetcher already retries transient errors. - const std::string account_id = requests_.front().GetAccountID(); - VLOG(1) << "Failed to retrieve ubertoken" - << " account=" << account_id << " error=" << error.ToString(); - HandleNextRequest(); - SignalComplete(account_id, error); -} - void GaiaCookieManagerService::OnTokenFetched(const std::string& account_id, const std::string& token) { access_tokens_.insert(std::make_pair(account_id, token)); @@ -805,8 +817,8 @@ void GaiaCookieManagerService::OnMergeSessionFailure( if (++fetcher_retries_ < signin::kMaxFetcherRetries && error.IsTransientError()) { fetcher_backoff_.InformOfRequest(false); - UMA_HISTOGRAM_ENUMERATION("OAuth2Login.MergeSessionRetry", - error.state(), GoogleServiceAuthError::NUM_STATES); + UMA_HISTOGRAM_ENUMERATION("OAuth2Login.MergeSessionRetry", error.state(), + GoogleServiceAuthError::NUM_STATES); fetcher_timer_.Start( FROM_HERE, fetcher_backoff_.GetTimeUntilRelease(), base::BindOnce( @@ -818,8 +830,8 @@ void GaiaCookieManagerService::OnMergeSessionFailure( uber_token_ = std::string(); - UMA_HISTOGRAM_ENUMERATION("OAuth2Login.MergeSessionFailure", - error.state(), GoogleServiceAuthError::NUM_STATES); + UMA_HISTOGRAM_ENUMERATION("OAuth2Login.MergeSessionFailure", error.state(), + GoogleServiceAuthError::NUM_STATES); HandleNextRequest(); SignalComplete(account_id, error); } @@ -876,8 +888,8 @@ void GaiaCookieManagerService::OnListAccountsSuccess(const std::string& data) { GaiaCookieRequestType::LIST_ACCOUNTS); fetcher_backoff_.InformOfRequest(true); - if (!gaia::ParseListAccountsData( - data, &listed_accounts_, &signed_out_accounts_)) { + if (!gaia::ParseListAccountsData(data, &listed_accounts_, + &signed_out_accounts_)) { listed_accounts_.clear(); signed_out_accounts_.clear(); GoogleServiceAuthError error( @@ -920,8 +932,8 @@ void GaiaCookieManagerService::OnListAccountsFailure( if (++fetcher_retries_ < signin::kMaxFetcherRetries && error.IsTransientError()) { fetcher_backoff_.InformOfRequest(false); - UMA_HISTOGRAM_ENUMERATION("Signin.ListAccountsRetry", - error.state(), GoogleServiceAuthError::NUM_STATES); + UMA_HISTOGRAM_ENUMERATION("Signin.ListAccountsRetry", error.state(), + GoogleServiceAuthError::NUM_STATES); fetcher_timer_.Start( FROM_HERE, fetcher_backoff_.GetTimeUntilRelease(), base::BindOnce( @@ -996,8 +1008,11 @@ void GaiaCookieManagerService::StartFetchingUbertoken() { const std::string account_id = requests_.front().GetAccountID(); VLOG(1) << "GaiaCookieManagerService::StartFetchingUbertoken account_id=" << requests_.front().GetAccountID(); - uber_token_fetcher_ = std::make_unique<UbertokenFetcher>( - token_service_, this, GetURLLoaderFactory(), + uber_token_fetcher_ = std::make_unique<signin::UbertokenFetcherImpl>( + account_id, access_token_, token_service_, + base::BindOnce(&GaiaCookieManagerService::OnUbertokenFetchComplete, + base::Unretained(this)), + GetURLLoaderFactory(), base::BindRepeating( [](SigninClient* client, GaiaAuthConsumer* consumer, scoped_refptr<network::SharedURLLoaderFactory> url_loader) @@ -1006,12 +1021,6 @@ void GaiaCookieManagerService::StartFetchingUbertoken() { consumer, gaia::GaiaSource::kChrome, url_loader); }, base::Unretained(signin_client_))); - if (access_token_.empty()) { - uber_token_fetcher_->StartFetchingToken(account_id); - } else { - uber_token_fetcher_->StartFetchingTokenWithAccessToken(account_id, - access_token_); - } } void GaiaCookieManagerService::StartFetchingMultiLogin( @@ -1027,8 +1036,8 @@ void GaiaCookieManagerService::StartFetchingMergeSession() { gaia_auth_fetcher_ = signin_client_->CreateGaiaAuthFetcher( this, requests_.front().source(), GetURLLoaderFactory()); - gaia_auth_fetcher_->StartMergeSession(uber_token_, - external_cc_result_fetcher_.GetExternalCcResult()); + gaia_auth_fetcher_->StartMergeSession( + uber_token_, external_cc_result_fetcher_.GetExternalCcResult()); } void GaiaCookieManagerService::StartGaiaLogOut() { @@ -1113,7 +1122,7 @@ void GaiaCookieManagerService::HandleNextRequest() { GaiaCookieRequestType::LIST_ACCOUNTS) { // This and any directly subsequent list accounts would return the same. while (!requests_.empty() && requests_.front().request_type() == - GaiaCookieRequestType::LIST_ACCOUNTS) { + GaiaCookieRequestType::LIST_ACCOUNTS) { requests_.pop_front(); } } else { diff --git a/chromium/components/signin/core/browser/gaia_cookie_manager_service.h b/chromium/components/signin/core/browser/gaia_cookie_manager_service.h index c9305f706dc..0e62a0bdbb0 100644 --- a/chromium/components/signin/core/browser/gaia_cookie_manager_service.h +++ b/chromium/components/signin/core/browser/gaia_cookie_manager_service.h @@ -12,6 +12,7 @@ #include <utility> #include <vector> +#include "base/callback.h" #include "base/containers/circular_deque.h" #include "base/macros.h" #include "base/memory/weak_ptr.h" @@ -21,8 +22,8 @@ #include "google_apis/gaia/gaia_auth_consumer.h" #include "google_apis/gaia/gaia_auth_fetcher.h" #include "google_apis/gaia/gaia_auth_util.h" +#include "google_apis/gaia/oauth2_token_service.h" #include "google_apis/gaia/oauth_multilogin_result.h" -#include "google_apis/gaia/ubertoken_fetcher.h" #include "mojo/public/cpp/bindings/binding.h" #include "net/base/backoff_entry.h" #include "services/network/public/mojom/cookie_manager.mojom.h" @@ -30,14 +31,16 @@ class GaiaAuthFetcher; class GaiaCookieRequest; class GoogleServiceAuthError; -class OAuth2TokenService; namespace network { class SharedURLLoaderFactory; class SimpleURLLoader; -} +} // namespace network namespace signin { + +class UbertokenFetcherImpl; + // The maximum number of retries for a fetcher used in this class. constexpr int kMaxFetcherRetries = 8; @@ -68,7 +71,6 @@ struct MultiloginParameters { // lifetime of this object, when the first call is made to AddAccountToCookie. class GaiaCookieManagerService : public KeyedService, public GaiaAuthConsumer, - public UbertokenConsumer, public network::mojom::CookieChangeListener, public OAuth2TokenService::Consumer { public: @@ -214,6 +216,20 @@ class GaiaCookieManagerService : public KeyedService, GaiaCookieManagerService(OAuth2TokenService* token_service, SigninClient* signin_client); + + // Creates a GaiaCookieManagerService that uses the provided + // |shared_url_loader_factory_getter| to determine the SharedUrlLoaderFactory + // used for cookie-related requests. + // Note: SharedUrlLoaderFactory is passed via callback, so that if the + // callback has side-effects (e.g. network initialization), they do not occur + // until the first time GaiaCookieManagerService::GetSharedUrlLoaderFactory is + // called. + GaiaCookieManagerService( + OAuth2TokenService* token_service, + SigninClient* signin_client, + base::RepeatingCallback<scoped_refptr<network::SharedURLLoaderFactory>()> + shared_url_loader_factory_getter); + ~GaiaCookieManagerService() override; void InitCookieListener(); @@ -286,12 +302,13 @@ class GaiaCookieManagerService : public KeyedService, } // Returns a non-NULL pointer to its instance of net::BackoffEntry - const net::BackoffEntry* GetBackoffEntry() { - return &fetcher_backoff_; - } + const net::BackoffEntry* GetBackoffEntry() { return &fetcher_backoff_; } + + scoped_refptr<network::SharedURLLoaderFactory> GetURLLoaderFactory(); - // Can be overridden by tests. - virtual scoped_refptr<network::SharedURLLoaderFactory> GetURLLoaderFactory(); + // Ubertoken fetch completion callback. Called by unittests directly. + void OnUbertokenFetchComplete(GoogleServiceAuthError error, + const std::string& uber_token); private: FRIEND_TEST_ALL_PREFIXES(GaiaCookieManagerServiceTest, @@ -314,10 +331,6 @@ class GaiaCookieManagerService : public KeyedService, network::mojom::CookieChangeCause cause) override; void OnCookieListenerConnectionError(); - // Overridden from UbertokenConsumer. - void OnUbertokenSuccess(const std::string& token) override; - void OnUbertokenFailure(const GoogleServiceAuthError& error) override; - // Overridden from OAuth2TokenService::Consumer. void OnGetTokenSuccess( const OAuth2TokenService::Request* request, @@ -388,8 +401,11 @@ class GaiaCookieManagerService : public KeyedService, OAuth2TokenService* token_service_; SigninClient* signin_client_; + + base::RepeatingCallback<scoped_refptr<network::SharedURLLoaderFactory>()> + shared_url_loader_factory_getter_; std::unique_ptr<GaiaAuthFetcher> gaia_auth_fetcher_; - std::unique_ptr<UbertokenFetcher> uber_token_fetcher_; + std::unique_ptr<signin::UbertokenFetcherImpl> uber_token_fetcher_; ExternalCcResultFetcher external_cc_result_fetcher_; // If the GaiaAuthFetcher or SimpleURLLoader fails, retry with exponential diff --git a/chromium/components/signin/core/browser/gaia_cookie_manager_service_unittest.cc b/chromium/components/signin/core/browser/gaia_cookie_manager_service_unittest.cc index 170eb8d26e4..2cbf0fc474f 100644 --- a/chromium/components/signin/core/browser/gaia_cookie_manager_service_unittest.cc +++ b/chromium/components/signin/core/browser/gaia_cookie_manager_service_unittest.cc @@ -12,11 +12,11 @@ #include "base/location.h" #include "base/macros.h" #include "base/memory/ref_counted.h" -#include "base/message_loop/message_loop.h" #include "base/run_loop.h" #include "base/single_thread_task_runner.h" #include "base/strings/stringprintf.h" #include "base/test/metrics/histogram_tester.h" +#include "base/test/scoped_task_environment.h" #include "base/test/test_mock_time_task_runner.h" #include "base/threading/thread_task_runner_handle.h" #include "components/prefs/pref_registry_simple.h" @@ -47,6 +47,7 @@ class MockObserver : public GaiaCookieManagerService::Observer { void(const std::vector<gaia::ListedAccount>&, const std::vector<gaia::ListedAccount>&, const GoogleServiceAuthError&)); + private: GaiaCookieManagerService* helper_; @@ -90,11 +91,13 @@ MATCHER_P(ListedAccountEquals, expected, "") { class InstrumentedGaiaCookieManagerService : public GaiaCookieManagerService { public: - InstrumentedGaiaCookieManagerService( - OAuth2TokenService* token_service, - SigninClient* signin_client) - : GaiaCookieManagerService(token_service, - signin_client) { + InstrumentedGaiaCookieManagerService(OAuth2TokenService* token_service, + SigninClient* signin_client) + : GaiaCookieManagerService( + token_service, + signin_client, + base::BindRepeating(&SigninClient::GetURLLoaderFactory, + base::Unretained(signin_client))) { total++; } @@ -138,14 +141,15 @@ class GaiaCookieManagerServiceTest : public testing::Test { OAuth2TokenService* token_service() { return &token_service_; } TestSigninClient* signin_client() { return signin_client_.get(); } - void SimulateUbertokenSuccess(UbertokenConsumer* consumer, + void SimulateUbertokenSuccess(GaiaCookieManagerService* gcms, const std::string& uber_token) { - consumer->OnUbertokenSuccess(uber_token); + gcms->OnUbertokenFetchComplete( + GoogleServiceAuthError(GoogleServiceAuthError::NONE), uber_token); } - void SimulateUbertokenFailure(UbertokenConsumer* consumer, + void SimulateUbertokenFailure(GaiaCookieManagerService* gcms, const GoogleServiceAuthError& error) { - consumer->OnUbertokenFailure(error); + gcms->OnUbertokenFetchComplete(error, /*uber_token=*/std::string()); } void SimulateAccessTokenFailure(OAuth2TokenService::Consumer* consumer, @@ -231,7 +235,7 @@ class GaiaCookieManagerServiceTest : public testing::Test { } private: - base::MessageLoop message_loop_; + base::test::ScopedTaskEnvironment task_environment_; FakeOAuth2TokenService token_service_; GoogleServiceAuthError no_error_; GoogleServiceAuthError error_; @@ -249,8 +253,8 @@ TEST_F(GaiaCookieManagerServiceTest, Success) { MockObserver observer(&helper); EXPECT_CALL(helper, StartFetchingUbertoken()); - EXPECT_CALL(observer, OnAddAccountToCookieCompleted("acc1@gmail.com", - no_error())); + EXPECT_CALL(observer, + OnAddAccountToCookieCompleted("acc1@gmail.com", no_error())); helper.AddAccountToCookie("acc1@gmail.com", gaia::GaiaSource::kChrome); SimulateMergeSessionSuccess(&helper, "token"); @@ -262,15 +266,15 @@ TEST_F(GaiaCookieManagerServiceTest, FailedMergeSession) { base::HistogramTester histograms; EXPECT_CALL(helper, StartFetchingUbertoken()); - EXPECT_CALL(observer, OnAddAccountToCookieCompleted("acc1@gmail.com", - error())); + EXPECT_CALL(observer, + OnAddAccountToCookieCompleted("acc1@gmail.com", error())); helper.AddAccountToCookie("acc1@gmail.com", gaia::GaiaSource::kChrome); SimulateMergeSessionFailure(&helper, error()); // Persistent error incurs no further retries. DCHECK(!helper.is_running()); histograms.ExpectUniqueSample("OAuth2Login.MergeSessionFailure", - GoogleServiceAuthError::SERVICE_ERROR, 1); + GoogleServiceAuthError::SERVICE_ERROR, 1); } TEST_F(GaiaCookieManagerServiceTest, AddAccountCookiesDisabled) { @@ -278,8 +282,8 @@ TEST_F(GaiaCookieManagerServiceTest, AddAccountCookiesDisabled) { MockObserver observer(&helper); signin_client()->set_are_signin_cookies_allowed(false); - EXPECT_CALL(observer, OnAddAccountToCookieCompleted("acc1@gmail.com", - canceled())); + EXPECT_CALL(observer, + OnAddAccountToCookieCompleted("acc1@gmail.com", canceled())); helper.AddAccountToCookie("acc1@gmail.com", gaia::GaiaSource::kChrome); } @@ -294,8 +298,8 @@ TEST_F(GaiaCookieManagerServiceTest, MergeSessionRetried) { EXPECT_CALL(helper, StartFetchingUbertoken()); EXPECT_CALL(helper, StartFetchingMergeSession()); - EXPECT_CALL(observer, OnAddAccountToCookieCompleted("acc1@gmail.com", - no_error())); + EXPECT_CALL(observer, + OnAddAccountToCookieCompleted("acc1@gmail.com", no_error())); helper.AddAccountToCookie("acc1@gmail.com", gaia::GaiaSource::kChrome); SimulateMergeSessionFailure(&helper, canceled()); @@ -316,8 +320,8 @@ TEST_F(GaiaCookieManagerServiceTest, MergeSessionRetriedTwice) { EXPECT_CALL(helper, StartFetchingUbertoken()); EXPECT_CALL(helper, StartFetchingMergeSession()).Times(2); - EXPECT_CALL(observer, OnAddAccountToCookieCompleted("acc1@gmail.com", - no_error())); + EXPECT_CALL(observer, + OnAddAccountToCookieCompleted("acc1@gmail.com", no_error())); helper.AddAccountToCookie("acc1@gmail.com", gaia::GaiaSource::kChrome); SimulateMergeSessionFailure(&helper, canceled()); @@ -329,7 +333,7 @@ TEST_F(GaiaCookieManagerServiceTest, MergeSessionRetriedTwice) { SimulateMergeSessionSuccess(&helper, "token"); DCHECK(!helper.is_running()); histograms.ExpectUniqueSample("OAuth2Login.MergeSessionRetry", - GoogleServiceAuthError::REQUEST_CANCELED, 2); + GoogleServiceAuthError::REQUEST_CANCELED, 2); } TEST_F(GaiaCookieManagerServiceTest, FailedUbertoken) { @@ -337,8 +341,8 @@ TEST_F(GaiaCookieManagerServiceTest, FailedUbertoken) { MockObserver observer(&helper); EXPECT_CALL(helper, StartFetchingUbertoken()); - EXPECT_CALL(observer, OnAddAccountToCookieCompleted("acc1@gmail.com", - error())); + EXPECT_CALL(observer, + OnAddAccountToCookieCompleted("acc1@gmail.com", error())); helper.AddAccountToCookie("acc1@gmail.com", gaia::GaiaSource::kChrome); SimulateUbertokenFailure(&helper, error()); @@ -874,10 +878,10 @@ TEST_F(GaiaCookieManagerServiceTest, ContinueAfterSuccess) { MockObserver observer(&helper); EXPECT_CALL(helper, StartFetchingUbertoken()).Times(2); - EXPECT_CALL(observer, OnAddAccountToCookieCompleted("acc1@gmail.com", - no_error())); - EXPECT_CALL(observer, OnAddAccountToCookieCompleted("acc2@gmail.com", - no_error())); + EXPECT_CALL(observer, + OnAddAccountToCookieCompleted("acc1@gmail.com", no_error())); + EXPECT_CALL(observer, + OnAddAccountToCookieCompleted("acc2@gmail.com", no_error())); helper.AddAccountToCookie("acc1@gmail.com", gaia::GaiaSource::kChrome); helper.AddAccountToCookie("acc2@gmail.com", gaia::GaiaSource::kChrome); @@ -890,10 +894,10 @@ TEST_F(GaiaCookieManagerServiceTest, ContinueAfterFailure1) { MockObserver observer(&helper); EXPECT_CALL(helper, StartFetchingUbertoken()).Times(2); - EXPECT_CALL(observer, OnAddAccountToCookieCompleted("acc1@gmail.com", - error())); - EXPECT_CALL(observer, OnAddAccountToCookieCompleted("acc2@gmail.com", - no_error())); + EXPECT_CALL(observer, + OnAddAccountToCookieCompleted("acc1@gmail.com", error())); + EXPECT_CALL(observer, + OnAddAccountToCookieCompleted("acc2@gmail.com", no_error())); helper.AddAccountToCookie("acc1@gmail.com", gaia::GaiaSource::kChrome); helper.AddAccountToCookie("acc2@gmail.com", gaia::GaiaSource::kChrome); @@ -906,10 +910,10 @@ TEST_F(GaiaCookieManagerServiceTest, ContinueAfterFailure2) { MockObserver observer(&helper); EXPECT_CALL(helper, StartFetchingUbertoken()).Times(2); - EXPECT_CALL(observer, OnAddAccountToCookieCompleted("acc1@gmail.com", - error())); - EXPECT_CALL(observer, OnAddAccountToCookieCompleted("acc2@gmail.com", - no_error())); + EXPECT_CALL(observer, + OnAddAccountToCookieCompleted("acc1@gmail.com", error())); + EXPECT_CALL(observer, + OnAddAccountToCookieCompleted("acc2@gmail.com", no_error())); helper.AddAccountToCookie("acc1@gmail.com", gaia::GaiaSource::kChrome); helper.AddAccountToCookie("acc2@gmail.com", gaia::GaiaSource::kChrome); @@ -944,8 +948,8 @@ TEST_F(GaiaCookieManagerServiceTest, LogOutAllAccountsNoQueue) { MockObserver observer(&helper); EXPECT_CALL(helper, StartFetchingUbertoken()); - EXPECT_CALL(observer, OnAddAccountToCookieCompleted("acc2@gmail.com", - no_error())); + EXPECT_CALL(observer, + OnAddAccountToCookieCompleted("acc2@gmail.com", no_error())); EXPECT_CALL(helper, StartFetchingLogOut()); helper.AddAccountToCookie("acc2@gmail.com", gaia::GaiaSource::kChrome); @@ -961,8 +965,8 @@ TEST_F(GaiaCookieManagerServiceTest, LogOutAllAccountsFails) { MockObserver observer(&helper); EXPECT_CALL(helper, StartFetchingUbertoken()); - EXPECT_CALL(observer, OnAddAccountToCookieCompleted("acc2@gmail.com", - no_error())); + EXPECT_CALL(observer, + OnAddAccountToCookieCompleted("acc2@gmail.com", no_error())); EXPECT_CALL(helper, StartFetchingLogOut()); helper.AddAccountToCookie("acc2@gmail.com", gaia::GaiaSource::kChrome); @@ -979,8 +983,8 @@ TEST_F(GaiaCookieManagerServiceTest, LogOutAllAccountsAfterOneAddInQueue) { MockObserver observer(&helper); EXPECT_CALL(helper, StartFetchingUbertoken()); - EXPECT_CALL(observer, OnAddAccountToCookieCompleted("acc2@gmail.com", - no_error())); + EXPECT_CALL(observer, + OnAddAccountToCookieCompleted("acc2@gmail.com", no_error())); EXPECT_CALL(helper, StartFetchingLogOut()); helper.AddAccountToCookie("acc2@gmail.com", gaia::GaiaSource::kChrome); @@ -995,10 +999,10 @@ TEST_F(GaiaCookieManagerServiceTest, LogOutAllAccountsAfterTwoAddsInQueue) { MockObserver observer(&helper); EXPECT_CALL(helper, StartFetchingUbertoken()); - EXPECT_CALL(observer, OnAddAccountToCookieCompleted("acc1@gmail.com", - no_error())); - EXPECT_CALL(observer, OnAddAccountToCookieCompleted("acc2@gmail.com", - canceled())); + EXPECT_CALL(observer, + OnAddAccountToCookieCompleted("acc1@gmail.com", no_error())); + EXPECT_CALL(observer, + OnAddAccountToCookieCompleted("acc2@gmail.com", canceled())); EXPECT_CALL(helper, StartFetchingLogOut()); helper.AddAccountToCookie("acc1@gmail.com", gaia::GaiaSource::kChrome); @@ -1015,8 +1019,8 @@ TEST_F(GaiaCookieManagerServiceTest, LogOutAllAccountsTwice) { MockObserver observer(&helper); EXPECT_CALL(helper, StartFetchingUbertoken()); - EXPECT_CALL(observer, OnAddAccountToCookieCompleted("acc2@gmail.com", - no_error())); + EXPECT_CALL(observer, + OnAddAccountToCookieCompleted("acc2@gmail.com", no_error())); EXPECT_CALL(helper, StartFetchingLogOut()); helper.AddAccountToCookie("acc2@gmail.com", gaia::GaiaSource::kChrome); @@ -1033,11 +1037,11 @@ TEST_F(GaiaCookieManagerServiceTest, LogOutAllAccountsBeforeAdd) { MockObserver observer(&helper); EXPECT_CALL(helper, StartFetchingUbertoken()).Times(2); - EXPECT_CALL(observer, OnAddAccountToCookieCompleted("acc2@gmail.com", - no_error())); + EXPECT_CALL(observer, + OnAddAccountToCookieCompleted("acc2@gmail.com", no_error())); EXPECT_CALL(helper, StartFetchingLogOut()); - EXPECT_CALL(observer, OnAddAccountToCookieCompleted("acc3@gmail.com", - no_error())); + EXPECT_CALL(observer, + OnAddAccountToCookieCompleted("acc3@gmail.com", no_error())); helper.AddAccountToCookie("acc2@gmail.com", gaia::GaiaSource::kChrome); SimulateMergeSessionSuccess(&helper, "token1"); @@ -1053,13 +1057,12 @@ TEST_F(GaiaCookieManagerServiceTest, LogOutAllAccountsBeforeLogoutAndAdd) { InstrumentedGaiaCookieManagerService helper(token_service(), signin_client()); MockObserver observer(&helper); - EXPECT_CALL(helper, StartFetchingUbertoken()).Times(2); - EXPECT_CALL(observer, OnAddAccountToCookieCompleted("acc2@gmail.com", - no_error())); + EXPECT_CALL(observer, + OnAddAccountToCookieCompleted("acc2@gmail.com", no_error())); EXPECT_CALL(helper, StartFetchingLogOut()); - EXPECT_CALL(observer, OnAddAccountToCookieCompleted("acc3@gmail.com", - no_error())); + EXPECT_CALL(observer, + OnAddAccountToCookieCompleted("acc3@gmail.com", no_error())); helper.AddAccountToCookie("acc2@gmail.com", gaia::GaiaSource::kChrome); SimulateMergeSessionSuccess(&helper, "token1"); @@ -1079,13 +1082,13 @@ TEST_F(GaiaCookieManagerServiceTest, PendingSigninThenSignout) { MockObserver observer(&helper); // From the first Signin. - EXPECT_CALL(observer, OnAddAccountToCookieCompleted("acc1@gmail.com", - no_error())); + EXPECT_CALL(observer, + OnAddAccountToCookieCompleted("acc1@gmail.com", no_error())); // From the sign out and then re-sign in. EXPECT_CALL(helper, StartFetchingLogOut()); - EXPECT_CALL(observer, OnAddAccountToCookieCompleted("acc3@gmail.com", - no_error())); + EXPECT_CALL(observer, + OnAddAccountToCookieCompleted("acc3@gmail.com", no_error())); // Total sign in 2 times, not enforcing ordered sequences. EXPECT_CALL(helper, StartFetchingUbertoken()).Times(2); @@ -1105,10 +1108,10 @@ TEST_F(GaiaCookieManagerServiceTest, CancelSignIn) { MockObserver observer(&helper); EXPECT_CALL(helper, StartFetchingUbertoken()); - EXPECT_CALL(observer, OnAddAccountToCookieCompleted("acc2@gmail.com", - canceled())); - EXPECT_CALL(observer, OnAddAccountToCookieCompleted("acc1@gmail.com", - no_error())); + EXPECT_CALL(observer, + OnAddAccountToCookieCompleted("acc2@gmail.com", canceled())); + EXPECT_CALL(observer, + OnAddAccountToCookieCompleted("acc1@gmail.com", no_error())); EXPECT_CALL(helper, StartFetchingLogOut()); helper.AddAccountToCookie("acc1@gmail.com", gaia::GaiaSource::kChrome); @@ -1156,7 +1159,8 @@ TEST_F(GaiaCookieManagerServiceTest, ListAccountsFindsOneAccount) { ASSERT_FALSE(helper.ListAccounts(&list_accounts, &signed_out_accounts)); - SimulateListAccountsSuccess(&helper, + SimulateListAccountsSuccess( + &helper, "[\"f\", [[\"b\", 0, \"n\", \"a@b.com\", \"p\", 0, 0, 0, 0, 1, \"8\"]]]"); } @@ -1189,11 +1193,12 @@ TEST_F(GaiaCookieManagerServiceTest, ListAccountsFindsSignedOutAccounts) { ASSERT_FALSE(helper.ListAccounts(&list_accounts, &signed_out_accounts)); - SimulateListAccountsSuccess(&helper, + SimulateListAccountsSuccess( + &helper, "[\"f\"," "[[\"b\", 0, \"n\", \"a@b.com\", \"p\", 0, 0, 0, 0, 1, \"8\"]," " [\"b\", 0, \"n\", \"c@d.com\", \"p\", 0, 0, 0, 0, 1, \"9\"," - "null,null,null,1]]]"); + "null,null,null,1]]]"); } TEST_F(GaiaCookieManagerServiceTest, ListAccountsAcceptsNull) { @@ -1202,11 +1207,12 @@ TEST_F(GaiaCookieManagerServiceTest, ListAccountsAcceptsNull) { ASSERT_FALSE(helper.ListAccounts(nullptr, nullptr)); - SimulateListAccountsSuccess(&helper, + SimulateListAccountsSuccess( + &helper, "[\"f\"," "[[\"b\", 0, \"n\", \"a@b.com\", \"p\", 0, 0, 0, 0, 1, \"8\"]," " [\"b\", 0, \"n\", \"c@d.com\", \"p\", 0, 0, 0, 0, 1, \"9\"," - "null,null,null,1]]]"); + "null,null,null,1]]]"); std::vector<gaia::ListedAccount> signed_out_accounts; ASSERT_TRUE(helper.ListAccounts(nullptr, &signed_out_accounts)); diff --git a/chromium/components/signin/core/browser/list_accounts_test_utils.cc b/chromium/components/signin/core/browser/list_accounts_test_utils.cc new file mode 100644 index 00000000000..a8a41b2e2e7 --- /dev/null +++ b/chromium/components/signin/core/browser/list_accounts_test_utils.cc @@ -0,0 +1,93 @@ +// Copyright 2019 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 "components/signin/core/browser/list_accounts_test_utils.h" + +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "google_apis/gaia/gaia_constants.h" +#include "google_apis/gaia/gaia_urls.h" +#include "services/network/test/test_url_loader_factory.h" + +namespace signin { + +using network::TestURLLoaderFactory; + +void SetListAccountsResponseHttpNotFound( + TestURLLoaderFactory* test_url_loader_factory) { + test_url_loader_factory->AddResponse( + GaiaUrls::GetInstance() + ->ListAccountsURLWithSource(GaiaConstants::kChromeSource) + .spec(), + /*content=*/"", net::HTTP_NOT_FOUND); +} + +void SetListAccountsResponseWebLoginRequired( + TestURLLoaderFactory* test_url_loader_factory) { + test_url_loader_factory->AddResponse( + GaiaUrls::GetInstance() + ->ListAccountsURLWithSource(GaiaConstants::kChromeSource) + .spec(), + "Info=WebLoginRequired"); +} + +void SetListAccountsResponseWithParams( + const std::vector<CookieParams>& params, + TestURLLoaderFactory* test_url_loader_factory) { + std::vector<std::string> response_body; + for (const auto& param : params) { + std::string response_part = base::StringPrintf( + "[\"b\", 0, \"n\", \"%s\", \"p\", 0, 0, 0, 0, %d, \"%s\"", + param.email.c_str(), param.valid ? 1 : 0, param.gaia_id.c_str()); + if (param.signed_out || !param.verified) { + response_part += + base::StringPrintf(", null, null, null, %d, %d", + param.signed_out ? 1 : 0, param.verified ? 1 : 0); + } + response_part += "]"; + response_body.push_back(response_part); + } + + test_url_loader_factory->AddResponse( + GaiaUrls::GetInstance() + ->ListAccountsURLWithSource(GaiaConstants::kChromeSource) + .spec(), + std::string("[\"f\", [") + base::JoinString(response_body, ", ") + "]]"); +} + +void SetListAccountsResponseNoAccounts( + TestURLLoaderFactory* test_url_loader_factory) { + SetListAccountsResponseWithParams({}, test_url_loader_factory); +} + +void SetListAccountsResponseOneAccount( + const std::string& email, + const std::string& gaia_id, + TestURLLoaderFactory* test_url_loader_factory) { + CookieParams params = {email, gaia_id, /*valid=*/true, + /*signed_out=*/false, /*verified=*/true}; + SetListAccountsResponseWithParams({params}, test_url_loader_factory); +} + +void SetListAccountsResponseOneAccountWithParams( + const CookieParams& params, + TestURLLoaderFactory* test_url_loader_factory) { + SetListAccountsResponseWithParams({params}, test_url_loader_factory); +} + +void SetListAccountsResponseTwoAccounts( + const std::string& email1, + const std::string& gaia_id1, + const std::string& email2, + const std::string& gaia_id2, + TestURLLoaderFactory* test_url_loader_factory) { + SetListAccountsResponseWithParams( + {{email1, gaia_id1, /*valid=*/true, /*signed_out=*/false, + /*verified=*/true}, + {email2, gaia_id2, /*valid=*/true, /*signed_out=*/false, + /*verified=*/true}}, + test_url_loader_factory); +} + +} // namespace signin diff --git a/chromium/components/signin/core/browser/list_accounts_test_utils.h b/chromium/components/signin/core/browser/list_accounts_test_utils.h new file mode 100644 index 00000000000..9a5d897f0df --- /dev/null +++ b/chromium/components/signin/core/browser/list_accounts_test_utils.h @@ -0,0 +1,67 @@ +// Copyright 2019 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. +#ifndef COMPONENTS_SIGNIN_CORE_BROWSER_LIST_ACCOUNTS_TEST_UTILS_H_ +#define COMPONENTS_SIGNIN_CORE_BROWSER_LIST_ACCOUNTS_TEST_UTILS_H_ + +#include <string> +#include <vector> + +namespace network { +class TestURLLoaderFactory; +} // namespace network + +namespace signin { + +// Parameters for the fake ListAccounts response. +struct CookieParams { + std::string email; + std::string gaia_id; + bool valid; + bool signed_out; + bool verified; +}; + +// Make ListAccounts call return NotFound. +void SetListAccountsResponseHttpNotFound( + network::TestURLLoaderFactory* test_url_loader_factory); + +// Make ListAccounts call return Info=WebLoginRequired. +void SetListAccountsResponseWebLoginRequired( + network::TestURLLoaderFactory* test_url_loader_factory); + +// Make ListAccounts return a list of accounts based on the provided |params|. +void SetListAccountsResponseWithParams( + const std::vector<CookieParams>& params, + network::TestURLLoaderFactory* test_url_loader_factory); + +// Helper methods, equivalent to calling +// SetListAccountsResponseWithParams(). + +// Make ListAccounts return no accounts. +void SetListAccountsResponseNoAccounts( + network::TestURLLoaderFactory* test_url_loader_factory); + +// Make ListAccounts return one account with the provided |email| and +// |gaia_id|. +void SetListAccountsResponseOneAccount( + const std::string& email, + const std::string& gaia_id, + network::TestURLLoaderFactory* test_url_loader_factory); + +// Make ListAccounts return one account based on the provided |params|. +void SetListAccountsResponseOneAccountWithParams( + const CookieParams& params, + network::TestURLLoaderFactory* test_url_loader_factory); + +// Make ListAccounts return two accounts with the provided emails and gaia_ids. +void SetListAccountsResponseTwoAccounts( + const std::string& email1, + const std::string& gaia_id1, + const std::string& email2, + const std::string& gaia_id2, + network::TestURLLoaderFactory* test_url_loader_factory); + +} // namespace signin + +#endif // COMPONENTS_SIGNIN_CORE_BROWSER_LIST_ACCOUNTS_TEST_UTILS_H_ diff --git a/chromium/components/signin/core/browser/mirror_account_reconcilor_delegate.h b/chromium/components/signin/core/browser/mirror_account_reconcilor_delegate.h index da8044614ec..05f9f72069a 100644 --- a/chromium/components/signin/core/browser/mirror_account_reconcilor_delegate.h +++ b/chromium/components/signin/core/browser/mirror_account_reconcilor_delegate.h @@ -5,6 +5,9 @@ #ifndef COMPONENTS_SIGNIN_CORE_BROWSER_MIRROR_ACCOUNT_RECONCILOR_DELEGATE_H_ #define COMPONENTS_SIGNIN_CORE_BROWSER_MIRROR_ACCOUNT_RECONCILOR_DELEGATE_H_ +#include <string> +#include <vector> + #include "base/macros.h" #include "components/signin/core/browser/account_reconcilor_delegate.h" #include "services/identity/public/cpp/identity_manager.h" @@ -16,12 +19,18 @@ class MirrorAccountReconcilorDelegate : public AccountReconcilorDelegate, public identity::IdentityManager::Observer { public: - MirrorAccountReconcilorDelegate(identity::IdentityManager* identity_manager); + explicit MirrorAccountReconcilorDelegate( + identity::IdentityManager* identity_manager); ~MirrorAccountReconcilorDelegate() override; - private: + protected: // AccountReconcilorDelegate: + // TODO(sinhak): Make this private after deleting + // |ChromeOSAccountReconcilorDelegate|. bool IsReconcileEnabled() const override; + + private: + // AccountReconcilorDelegate: bool IsAccountConsistencyEnforced() const override; gaia::GaiaSource GetGaiaApiSource() const override; bool ShouldAbortReconcileIfPrimaryHasError() const override; diff --git a/chromium/components/signin/core/browser/mutable_profile_oauth2_token_service_delegate.cc b/chromium/components/signin/core/browser/mutable_profile_oauth2_token_service_delegate.cc new file mode 100644 index 00000000000..b191706de13 --- /dev/null +++ b/chromium/components/signin/core/browser/mutable_profile_oauth2_token_service_delegate.cc @@ -0,0 +1,944 @@ +// Copyright 2015 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 "components/signin/core/browser/mutable_profile_oauth2_token_service_delegate.h" + +#include <stddef.h> + +#include <map> +#include <string> + +#include "base/bind.h" +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "base/metrics/histogram_macros.h" +#include "build/build_config.h" +#include "components/prefs/pref_registry_simple.h" +#include "components/prefs/pref_service.h" +#include "components/signin/core/browser/account_info.h" +#include "components/signin/core/browser/signin_client.h" +#include "components/signin/core/browser/signin_metrics.h" +#include "components/signin/core/browser/signin_pref_names.h" +#include "components/signin/core/browser/webdata/token_web_data.h" +#include "components/webdata/common/web_data_service_base.h" +#include "google_apis/gaia/gaia_auth_fetcher.h" +#include "google_apis/gaia/gaia_auth_util.h" +#include "google_apis/gaia/gaia_constants.h" +#include "google_apis/gaia/oauth2_access_token_fetcher_immediate_error.h" +#include "google_apis/gaia/oauth2_access_token_fetcher_impl.h" +#include "services/network/public/cpp/shared_url_loader_factory.h" + +namespace { + +const char kAccountIdPrefix[] = "AccountId-"; +const size_t kAccountIdPrefixLength = 10; + +// Used to record token state transitions in histograms. +// Do not change existing values, new values can only be added at the end. +enum class TokenStateTransition { + // Update events. + kNoneToInvalid = 0, + kNoneToRegular, + kInvalidToRegular, + kRegularToInvalid, + kRegularToRegular, + + // Revocation events. + kInvalidToNone, + kRegularToNone, + + // Load events. + kLoadRegular, + kLoadInvalid, + kLoadInvalidNoTokenForPrimaryAccount, + + kCount +}; + +// Enum for the Signin.LoadTokenFromDB histogram. +// Do not modify, or add or delete other than directly before +// NUM_LOAD_TOKEN_FROM_DB_STATUS. +enum class LoadTokenFromDBStatus { + // Token was loaded. + TOKEN_LOADED = 0, + // Token was revoked as part of Dice migration. + TOKEN_REVOKED_DICE_MIGRATION = 1, + // Token was revoked because it is a secondary account and account consistency + // is disabled. + TOKEN_REVOKED_SECONDARY_ACCOUNT = 2, + // Token was revoked on load due to cookie settings. + TOKEN_REVOKED_ON_LOAD = 3, + + NUM_LOAD_TOKEN_FROM_DB_STATUS +}; + +// Used to record events related to token revocation requests in histograms. +// Do not change existing values, new values can only be added at the end. +enum class TokenRevocationRequestProgress { + // The request was created. + kRequestCreated = 0, + // The request was sent over the network. + kRequestStarted = 1, + // The network request completed with a failure. + kRequestFailed = 2, + // The network request completed with a success. + kRequestSucceeded = 3, + + kMaxValue = kRequestSucceeded +}; + +// Adds a sample to the TokenStateTransition histogram. Encapsuled in a function +// to reduce executable size, because histogram macros may generate a lot of +// code. +void RecordTokenStateTransition(TokenStateTransition transition) { + UMA_HISTOGRAM_ENUMERATION("Signin.TokenStateTransition", transition, + TokenStateTransition::kCount); +} + +// Adds a sample to the TokenRevocationRequestProgress histogram. Encapsuled in +// a function to reduce executable size, because histogram macros may generate a +// lot of code. +void RecordRefreshTokenRevocationRequestEvent( + TokenRevocationRequestProgress event) { + UMA_HISTOGRAM_ENUMERATION("Signin.RefreshTokenRevocationRequestProgress", + event); +} + +// Record metrics when a token was updated. +void RecordTokenChanged(const std::string& existing_token, + const std::string& new_token) { + DCHECK_NE(existing_token, new_token); + DCHECK(!new_token.empty()); + TokenStateTransition transition = TokenStateTransition::kCount; + if (existing_token.empty()) { + transition = (new_token == OAuth2TokenServiceDelegate::kInvalidRefreshToken) + ? TokenStateTransition::kNoneToInvalid + : TokenStateTransition::kNoneToRegular; + } else if (existing_token == + OAuth2TokenServiceDelegate::kInvalidRefreshToken) { + transition = TokenStateTransition::kInvalidToRegular; + } else { + // Existing token is a regular token. + transition = (new_token == OAuth2TokenServiceDelegate::kInvalidRefreshToken) + ? TokenStateTransition::kRegularToInvalid + : TokenStateTransition::kRegularToRegular; + } + DCHECK_NE(TokenStateTransition::kCount, transition); + RecordTokenStateTransition(transition); +} + +// Record metrics when a token was loaded. +void RecordTokenLoaded(const std::string& token) { + RecordTokenStateTransition( + (token == OAuth2TokenServiceDelegate::kInvalidRefreshToken) + ? TokenStateTransition::kLoadInvalid + : TokenStateTransition::kLoadRegular); +} + +// Record metrics when a token was revoked. +void RecordTokenRevoked(const std::string& token) { + RecordTokenStateTransition( + (token == OAuth2TokenServiceDelegate::kInvalidRefreshToken) + ? TokenStateTransition::kInvalidToNone + : TokenStateTransition::kRegularToNone); +} + +std::string ApplyAccountIdPrefix(const std::string& account_id) { + return kAccountIdPrefix + account_id; +} + +bool IsLegacyRefreshTokenId(const std::string& service_id) { + return service_id == GaiaConstants::kGaiaOAuth2LoginRefreshToken; +} + +bool IsLegacyServiceId(const std::string& account_id) { + return account_id.compare(0u, kAccountIdPrefixLength, kAccountIdPrefix) != 0; +} + +std::string RemoveAccountIdPrefix(const std::string& prefixed_account_id) { + return prefixed_account_id.substr(kAccountIdPrefixLength); +} + +OAuth2TokenServiceDelegate::LoadCredentialsState +LoadCredentialsStateFromTokenResult(TokenServiceTable::Result token_result) { + switch (token_result) { + case TokenServiceTable::TOKEN_DB_RESULT_SQL_INVALID_STATEMENT: + case TokenServiceTable::TOKEN_DB_RESULT_BAD_ENTRY: + return OAuth2TokenServiceDelegate:: + LOAD_CREDENTIALS_FINISHED_WITH_DB_ERRORS; + case TokenServiceTable::TOKEN_DB_RESULT_DECRYPT_ERROR: + return OAuth2TokenServiceDelegate:: + LOAD_CREDENTIALS_FINISHED_WITH_DECRYPT_ERRORS; + case TokenServiceTable::TOKEN_DB_RESULT_SUCCESS: + return OAuth2TokenServiceDelegate::LOAD_CREDENTIALS_FINISHED_WITH_SUCCESS; + } + NOTREACHED(); + return OAuth2TokenServiceDelegate:: + LOAD_CREDENTIALS_FINISHED_WITH_UNKNOWN_ERRORS; +} + +// Returns whether the token service should be migrated to Dice. +// Migration can happen if the following conditions are met: +// - Token service Dice migration is not already done, +// - AccountTrackerService migration is done, +// - All accounts in the AccountTrackerService are valid, +// - Account consistency is DiceMigration or greater. +// TODO(droger): Remove this code once Dice is fully enabled. +bool ShouldMigrateToDice(signin::AccountConsistencyMethod account_consistency, + PrefService* prefs, + AccountTrackerService* account_tracker, + const std::map<std::string, std::string>& db_tokens) { + AccountTrackerService::AccountIdMigrationState migration_state = + account_tracker->GetMigrationState(); + if ((account_consistency == signin::AccountConsistencyMethod::kMirror) || + !signin::DiceMethodGreaterOrEqual( + account_consistency, + signin::AccountConsistencyMethod::kDiceMigration) || + (migration_state != AccountTrackerService::MIGRATION_DONE) || + prefs->GetBoolean(prefs::kTokenServiceDiceCompatible)) { + return false; + } + + // Do not migrate if some accounts are not valid. + for (auto iter = db_tokens.begin(); iter != db_tokens.end(); ++iter) { + const std::string& prefixed_account_id = iter->first; + std::string account_id = RemoveAccountIdPrefix(prefixed_account_id); + AccountInfo account_info = account_tracker->GetAccountInfo(account_id); + if (!account_info.IsValid()) { + return false; + } + } + return true; +} + +} // namespace + +// This class sends a request to GAIA to revoke the given refresh token from +// the server. This is a best effort attempt only. This class deletes itself +// when done successfully or otherwise. +class MutableProfileOAuth2TokenServiceDelegate::RevokeServerRefreshToken + : public GaiaAuthConsumer { + public: + RevokeServerRefreshToken( + MutableProfileOAuth2TokenServiceDelegate* token_service_delegate, + SigninClient* client, + const std::string& refresh_token, + int attempt); + ~RevokeServerRefreshToken() override; + + private: + // Starts the network request. + void Start(); + // Returns true if the request should be retried. + bool ShouldRetry(GaiaAuthConsumer::TokenRevocationStatus status); + // GaiaAuthConsumer overrides: + void OnOAuth2RevokeTokenCompleted( + GaiaAuthConsumer::TokenRevocationStatus status) override; + + MutableProfileOAuth2TokenServiceDelegate* token_service_delegate_; + GaiaAuthFetcher fetcher_; + std::string refresh_token_; + int attempt_; + base::WeakPtrFactory<RevokeServerRefreshToken> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(RevokeServerRefreshToken); +}; + +MutableProfileOAuth2TokenServiceDelegate::RevokeServerRefreshToken:: + RevokeServerRefreshToken( + MutableProfileOAuth2TokenServiceDelegate* token_service_delegate, + SigninClient* client, + const std::string& refresh_token, + int attempt) + : token_service_delegate_(token_service_delegate), + fetcher_(this, + gaia::GaiaSource::kChrome, + token_service_delegate_->GetURLLoaderFactory()), + refresh_token_(refresh_token), + attempt_(attempt), + weak_ptr_factory_(this) { + RecordRefreshTokenRevocationRequestEvent( + TokenRevocationRequestProgress::kRequestCreated); + client->DelayNetworkCall( + base::BindRepeating(&MutableProfileOAuth2TokenServiceDelegate:: + RevokeServerRefreshToken::Start, + weak_ptr_factory_.GetWeakPtr())); +} + +void MutableProfileOAuth2TokenServiceDelegate::RevokeServerRefreshToken:: + Start() { + RecordRefreshTokenRevocationRequestEvent( + TokenRevocationRequestProgress::kRequestStarted); + fetcher_.StartRevokeOAuth2Token(refresh_token_); +} + +MutableProfileOAuth2TokenServiceDelegate::RevokeServerRefreshToken:: + ~RevokeServerRefreshToken() {} + +bool MutableProfileOAuth2TokenServiceDelegate::RevokeServerRefreshToken:: + ShouldRetry(GaiaAuthConsumer::TokenRevocationStatus status) { + // Token revocation can be retried up to 3 times. + if (attempt_ >= 2) + return false; + + switch (status) { + case GaiaAuthConsumer::TokenRevocationStatus::kServerError: + case GaiaAuthConsumer::TokenRevocationStatus::kConnectionFailed: + case GaiaAuthConsumer::TokenRevocationStatus::kConnectionTimeout: + case GaiaAuthConsumer::TokenRevocationStatus::kConnectionCanceled: + return true; + case GaiaAuthConsumer::TokenRevocationStatus::kSuccess: + case GaiaAuthConsumer::TokenRevocationStatus::kInvalidToken: + case GaiaAuthConsumer::TokenRevocationStatus::kInvalidRequest: + case GaiaAuthConsumer::TokenRevocationStatus::kUnknownError: + return false; + } +} + +void MutableProfileOAuth2TokenServiceDelegate::RevokeServerRefreshToken:: + OnOAuth2RevokeTokenCompleted( + GaiaAuthConsumer::TokenRevocationStatus status) { + UMA_HISTOGRAM_ENUMERATION("Signin.RefreshTokenRevocationStatus", status); + if (ShouldRetry(status)) { + token_service_delegate_->server_revokes_.push_back( + std::make_unique<RevokeServerRefreshToken>( + token_service_delegate_, token_service_delegate_->client_, + refresh_token_, attempt_ + 1)); + } else { + RecordRefreshTokenRevocationRequestEvent( + (status == GaiaAuthConsumer::TokenRevocationStatus::kSuccess) + ? TokenRevocationRequestProgress::kRequestSucceeded + : TokenRevocationRequestProgress::kRequestFailed); + UMA_HISTOGRAM_ENUMERATION("Signin.RefreshTokenRevocationCompleted", status); + } + // |this| pointer will be deleted when removed from the vector, so don't + // access any members after call to erase(). + token_service_delegate_->server_revokes_.erase(std::find_if( + token_service_delegate_->server_revokes_.begin(), + token_service_delegate_->server_revokes_.end(), + [this](const std::unique_ptr<MutableProfileOAuth2TokenServiceDelegate:: + RevokeServerRefreshToken>& item) { + return item.get() == this; + })); +} + +MutableProfileOAuth2TokenServiceDelegate:: + MutableProfileOAuth2TokenServiceDelegate( + SigninClient* client, + AccountTrackerService* account_tracker_service, + network::NetworkConnectionTracker* network_connection_tracker, + scoped_refptr<TokenWebData> token_web_data, + signin::AccountConsistencyMethod account_consistency, + bool revoke_all_tokens_on_load, + bool can_revoke_credentials, + FixRequestErrorCallback fix_request_error_callback) + : web_data_service_request_(0), + backoff_entry_(&backoff_policy_), + backoff_error_(GoogleServiceAuthError::NONE), + client_(client), + account_tracker_service_(account_tracker_service), + network_connection_tracker_(network_connection_tracker), + token_web_data_(token_web_data), + account_consistency_(account_consistency), + revoke_all_tokens_on_load_(revoke_all_tokens_on_load), + can_revoke_credentials_(can_revoke_credentials), + fix_request_error_callback_(fix_request_error_callback) { + VLOG(1) << "MutablePO2TS::MutablePO2TS"; + DCHECK(client); + DCHECK(account_tracker_service_); + DCHECK(network_connection_tracker_); + // It's okay to fill the backoff policy after being used in construction. + backoff_policy_.num_errors_to_ignore = 0; + backoff_policy_.initial_delay_ms = 1000; + backoff_policy_.multiply_factor = 2.0; + backoff_policy_.jitter_factor = 0.2; + backoff_policy_.maximum_backoff_ms = 15 * 60 * 1000; + backoff_policy_.entry_lifetime_ms = -1; + backoff_policy_.always_use_initial_delay = false; + network_connection_tracker_->AddNetworkConnectionObserver(this); +} + +MutableProfileOAuth2TokenServiceDelegate:: + ~MutableProfileOAuth2TokenServiceDelegate() { + VLOG(1) << "MutablePO2TS::~MutablePO2TS"; + DCHECK(server_revokes_.empty()); + network_connection_tracker_->RemoveNetworkConnectionObserver(this); +} + +// static +void MutableProfileOAuth2TokenServiceDelegate::RegisterProfilePrefs( + PrefRegistrySimple* registry) { + registry->RegisterBooleanPref(prefs::kTokenServiceDiceCompatible, false); +} + +OAuth2AccessTokenFetcher* +MutableProfileOAuth2TokenServiceDelegate::CreateAccessTokenFetcher( + const std::string& account_id, + scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory, + OAuth2AccessTokenConsumer* consumer) { + ValidateAccountId(account_id); + // check whether the account has persistent error. + if (refresh_tokens_[account_id].last_auth_error.IsPersistentError()) { + VLOG(1) << "Request for token has been rejected due to persistent error #" + << refresh_tokens_[account_id].last_auth_error.state(); + return new OAuth2AccessTokenFetcherImmediateError( + consumer, refresh_tokens_[account_id].last_auth_error); + } + if (backoff_entry_.ShouldRejectRequest()) { + VLOG(1) << "Request for token has been rejected due to backoff rules from" + << " previous error #" << backoff_error_.state(); + return new OAuth2AccessTokenFetcherImmediateError(consumer, backoff_error_); + } + std::string refresh_token = GetRefreshToken(account_id); + DCHECK(!refresh_token.empty()); + return new OAuth2AccessTokenFetcherImpl(consumer, url_loader_factory, + refresh_token); +} + +GoogleServiceAuthError MutableProfileOAuth2TokenServiceDelegate::GetAuthError( + const std::string& account_id) const { + auto it = refresh_tokens_.find(account_id); + return (it == refresh_tokens_.end()) ? GoogleServiceAuthError::AuthErrorNone() + : it->second.last_auth_error; +} + +void MutableProfileOAuth2TokenServiceDelegate::UpdateAuthError( + const std::string& account_id, + const GoogleServiceAuthError& error) { + VLOG(1) << "MutablePO2TS::UpdateAuthError. Error: " << error.state() + << " account_id=" << account_id; + backoff_entry_.InformOfRequest(!error.IsTransientError()); + ValidateAccountId(account_id); + + // Do not report connection errors as these are not actually auth errors. + // We also want to avoid masking a "real" auth error just because we + // subsequently get a transient network error. We do keep it around though + // to report for future requests being denied for "backoff" reasons. + if (error.IsTransientError()) { + backoff_error_ = error; + return; + } + + if (refresh_tokens_.count(account_id) == 0) { + // This could happen if the preferences have been corrupted (see + // http://crbug.com/321370). In a Debug build that would be a bug, but in a + // Release build we want to deal with it gracefully. + NOTREACHED(); + return; + } + + AccountStatus* status = &refresh_tokens_[account_id]; + if (error != status->last_auth_error) { + status->last_auth_error = error; + FireAuthErrorChanged(account_id, error); + } +} + +std::string MutableProfileOAuth2TokenServiceDelegate::GetTokenForMultilogin( + const std::string& account_id) const { + auto iter = refresh_tokens_.find(account_id); + if (iter == refresh_tokens_.end() || + iter->second.last_auth_error != GoogleServiceAuthError::AuthErrorNone()) { + return std::string(); + } + const std::string& refresh_token = iter->second.refresh_token; + DCHECK(!refresh_token.empty()); + return refresh_token; +} + +bool MutableProfileOAuth2TokenServiceDelegate::RefreshTokenIsAvailable( + const std::string& account_id) const { + VLOG(1) << "MutablePO2TS::RefreshTokenIsAvailable"; + return !GetRefreshToken(account_id).empty(); +} + +std::string MutableProfileOAuth2TokenServiceDelegate::GetRefreshToken( + const std::string& account_id) const { + auto iter = refresh_tokens_.find(account_id); + if (iter != refresh_tokens_.end()) { + const std::string refresh_token = iter->second.refresh_token; + DCHECK(!refresh_token.empty()); + return refresh_token; + } + return std::string(); +} + +std::string MutableProfileOAuth2TokenServiceDelegate::GetRefreshTokenForTest( + const std::string& account_id) const { + return GetRefreshToken(account_id); +} + +std::vector<std::string> +MutableProfileOAuth2TokenServiceDelegate::GetAccounts() { + std::vector<std::string> account_ids; + for (auto& token : refresh_tokens_) { + account_ids.push_back(token.first); + } + return account_ids; +} + +scoped_refptr<network::SharedURLLoaderFactory> +MutableProfileOAuth2TokenServiceDelegate::GetURLLoaderFactory() const { + return client_->GetURLLoaderFactory(); +} + +void MutableProfileOAuth2TokenServiceDelegate::InvalidateTokenForMultilogin( + const std::string& failed_account) { + UpdateAuthError( + failed_account, + GoogleServiceAuthError(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS)); +} + +void MutableProfileOAuth2TokenServiceDelegate::LoadCredentials( + const std::string& primary_account_id) { + if (load_credentials_state() == LOAD_CREDENTIALS_IN_PROGRESS) { + VLOG(1) << "Load credentials operation already in progress"; + return; + } + + set_load_credentials_state(LOAD_CREDENTIALS_IN_PROGRESS); + +#if defined(OS_CHROMEOS) + // ChromeOS OOBE loads credentials without a primary account and expects this + // to be a no-op. See htttp://crbug.com/891818 + if (primary_account_id.empty()) { + set_load_credentials_state(LOAD_CREDENTIALS_FINISHED_WITH_SUCCESS); + FinishLoadingCredentials(); + return; + } +#endif + + if (!primary_account_id.empty()) + ValidateAccountId(primary_account_id); + DCHECK(loading_primary_account_id_.empty()); + DCHECK_EQ(0, web_data_service_request_); + + refresh_tokens_.clear(); + + if (!token_web_data_) { + // This case only exists in unit tests that do not care about loading + // credentials. + set_load_credentials_state(LOAD_CREDENTIALS_FINISHED_WITH_UNKNOWN_ERRORS); + FinishLoadingCredentials(); + return; + } + + // If |account_id| is an email address, then canonicalize it. This is needed + // to support legacy account IDs, and will not be needed after switching to + // gaia IDs. + if (primary_account_id.find('@') != std::string::npos) { + loading_primary_account_id_ = gaia::CanonicalizeEmail(primary_account_id); + } else { + loading_primary_account_id_ = primary_account_id; + } + + web_data_service_request_ = token_web_data_->GetAllTokens(this); +} + +void MutableProfileOAuth2TokenServiceDelegate::OnWebDataServiceRequestDone( + WebDataServiceBase::Handle handle, + std::unique_ptr<WDTypedResult> result) { + VLOG(1) << "MutablePO2TS::OnWebDataServiceRequestDone. Result type: " + << (result.get() == nullptr ? -1 + : static_cast<int>(result->GetType())); + + DCHECK_EQ(web_data_service_request_, handle); + web_data_service_request_ = 0; + + if (result) { + DCHECK(result->GetType() == TOKEN_RESULT); + const WDResult<TokenResult>* token_result = + static_cast<const WDResult<TokenResult>*>(result.get()); + LoadAllCredentialsIntoMemory(token_result->GetValue().tokens); + set_load_credentials_state(LoadCredentialsStateFromTokenResult( + token_result->GetValue().db_result)); + } else { + set_load_credentials_state(LOAD_CREDENTIALS_FINISHED_WITH_UNKNOWN_ERRORS); + } + + // Make sure that we have an entry for |loading_primary_account_id_| in the + // map. The entry could be missing if there is a corruption in the token DB + // while this profile is connected to an account. + if (!loading_primary_account_id_.empty() && + refresh_tokens_.count(loading_primary_account_id_) == 0) { + if (load_credentials_state() == LOAD_CREDENTIALS_FINISHED_WITH_SUCCESS) { + set_load_credentials_state( + LOAD_CREDENTIALS_FINISHED_WITH_NO_TOKEN_FOR_PRIMARY_ACCOUNT); + } + AddAccountStatus(loading_primary_account_id_, kInvalidRefreshToken, + GoogleServiceAuthError::FromInvalidGaiaCredentialsReason( + GoogleServiceAuthError::InvalidGaiaCredentialsReason:: + CREDENTIALS_MISSING)); + RecordTokenStateTransition( + TokenStateTransition::kLoadInvalidNoTokenForPrimaryAccount); + FireRefreshTokenAvailable(loading_primary_account_id_); + } + +#ifndef NDEBUG + for (auto& token : refresh_tokens_) { + DCHECK(RefreshTokenIsAvailable(token.first)) + << "Missing token for " << token.first; + } +#endif + + loading_primary_account_id_.clear(); + FinishLoadingCredentials(); +} + +void MutableProfileOAuth2TokenServiceDelegate::LoadAllCredentialsIntoMemory( + const std::map<std::string, std::string>& db_tokens) { + std::string old_login_token; + bool migrate_to_dice = + ShouldMigrateToDice(account_consistency_, client_->GetPrefs(), + account_tracker_service_, db_tokens); + + { + ScopedBatchChange batch(this); + + VLOG(1) << "MutablePO2TS::LoadAllCredentialsIntoMemory; " + << db_tokens.size() << " Credential(s)."; + AccountTrackerService::AccountIdMigrationState migration_state = + account_tracker_service_->GetMigrationState(); + for (auto iter = db_tokens.begin(); iter != db_tokens.end(); ++iter) { + std::string prefixed_account_id = iter->first; + std::string refresh_token = iter->second; + + if (IsLegacyRefreshTokenId(prefixed_account_id) && !refresh_token.empty()) + old_login_token = refresh_token; + + if (IsLegacyServiceId(prefixed_account_id)) { + if (token_web_data_) { + VLOG(1) << "MutablePO2TS remove legacy refresh token for account id " + << prefixed_account_id; + token_web_data_->RemoveTokenForService(prefixed_account_id); + } + } else { + DCHECK(!refresh_token.empty()); + std::string account_id = RemoveAccountIdPrefix(prefixed_account_id); + + switch (migration_state) { + case AccountTrackerService::MIGRATION_IN_PROGRESS: { + // Migrate to gaia-ids. + AccountInfo account_info = + account_tracker_service_->FindAccountInfoByEmail(account_id); + // |account_info.gaia| could be empty if |account_id| is already + // gaia id. This could happen if the chrome was closed in the middle + // of migration. + if (!account_info.gaia.empty()) { + ClearPersistedCredentials(account_id); + PersistCredentials(account_info.gaia, refresh_token); + account_id = account_info.gaia; + } + + // Skip duplicate accounts, this could happen if migration was + // crashed in the middle. + if (refresh_tokens_.count(account_id) != 0) + continue; + break; + } + case AccountTrackerService::MIGRATION_NOT_STARTED: + // If the account_id is an email address, then canonicalize it. This + // is to support legacy account_ids, and will not be needed after + // switching to gaia-ids. + if (account_id.find('@') != std::string::npos) { + // If the canonical account id is not the same as the loaded + // account id, make sure not to overwrite a refresh token from + // a canonical version. If no canonical version was loaded, then + // re-persist this refresh token with the canonical account id. + std::string canon_account_id = + gaia::CanonicalizeEmail(account_id); + if (canon_account_id != account_id) { + ClearPersistedCredentials(account_id); + if (db_tokens.count(ApplyAccountIdPrefix(canon_account_id)) == + 0) + PersistCredentials(canon_account_id, refresh_token); + } + account_id = canon_account_id; + } + break; + case AccountTrackerService::MIGRATION_DONE: + DCHECK_EQ(std::string::npos, account_id.find('@')); + break; + case AccountTrackerService::NUM_MIGRATION_STATES: + NOTREACHED(); + break; + } + + // Only load secondary accounts when account consistency is enabled. + bool load_account = + (account_id == loading_primary_account_id_) || + (account_consistency_ == + signin::AccountConsistencyMethod::kMirror) || + signin::DiceMethodGreaterOrEqual( + account_consistency_, + signin::AccountConsistencyMethod::kDiceMigration); + LoadTokenFromDBStatus load_token_status = + load_account + ? LoadTokenFromDBStatus::TOKEN_LOADED + : LoadTokenFromDBStatus::TOKEN_REVOKED_SECONDARY_ACCOUNT; + + if (migrate_to_dice) { + // Revoke old hosted domain accounts as part of Dice migration. + AccountInfo account_info = + account_tracker_service_->GetAccountInfo(account_id); + DCHECK(account_info.IsValid()); + if (account_info.hosted_domain != kNoHostedDomainFound) { + load_account = false; + load_token_status = + LoadTokenFromDBStatus::TOKEN_REVOKED_DICE_MIGRATION; + } + } + + if (load_account && revoke_all_tokens_on_load_) { + if (account_id == loading_primary_account_id_) { + RevokeCredentialsOnServer(refresh_token); + refresh_token = kInvalidRefreshToken; + PersistCredentials(account_id, refresh_token); + } else { + load_account = false; + } + load_token_status = LoadTokenFromDBStatus::TOKEN_REVOKED_ON_LOAD; + } + + UMA_HISTOGRAM_ENUMERATION( + "Signin.LoadTokenFromDB", load_token_status, + LoadTokenFromDBStatus::NUM_LOAD_TOKEN_FROM_DB_STATUS); + + if (load_account) { + RecordTokenLoaded(refresh_token); + UpdateCredentialsInMemory(account_id, refresh_token); + FireRefreshTokenAvailable(account_id); + } else { + RecordTokenRevoked(refresh_token); + RevokeCredentialsOnServer(refresh_token); + ClearPersistedCredentials(account_id); + FireRefreshTokenRevoked(account_id); + } + } + } + + if (!old_login_token.empty()) { + DCHECK(!loading_primary_account_id_.empty()); + if (refresh_tokens_.count(loading_primary_account_id_) == 0) + UpdateCredentials(loading_primary_account_id_, old_login_token); + } + } + + if (migrate_to_dice) + client_->GetPrefs()->SetBoolean(prefs::kTokenServiceDiceCompatible, true); +} + +void MutableProfileOAuth2TokenServiceDelegate::UpdateCredentials( + const std::string& account_id, + const std::string& refresh_token) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + DCHECK(!account_id.empty()); + DCHECK(!refresh_token.empty()); + + ValidateAccountId(account_id); + signin_metrics::LogSigninAddAccount(); + + const std::string& existing_token = GetRefreshToken(account_id); + if (existing_token != refresh_token) { + ScopedBatchChange batch(this); + RecordTokenChanged(existing_token, refresh_token); + UpdateCredentialsInMemory(account_id, refresh_token); + PersistCredentials(account_id, refresh_token); + FireRefreshTokenAvailable(account_id); + } +} + +void MutableProfileOAuth2TokenServiceDelegate::UpdateCredentialsInMemory( + const std::string& account_id, + const std::string& refresh_token) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + DCHECK(!account_id.empty()); + DCHECK(!refresh_token.empty()); + + bool is_refresh_token_invalidated = refresh_token == kInvalidRefreshToken; + GoogleServiceAuthError error = + is_refresh_token_invalidated + ? GoogleServiceAuthError::FromInvalidGaiaCredentialsReason( + GoogleServiceAuthError::InvalidGaiaCredentialsReason:: + CREDENTIALS_REJECTED_BY_CLIENT) + : GoogleServiceAuthError::AuthErrorNone(); + + bool refresh_token_present = refresh_tokens_.count(account_id) > 0; + // If token present, and different from the new one, cancel its requests, + // and clear the entries in cache related to that account. + if (refresh_token_present) { + DCHECK_NE(refresh_token, refresh_tokens_[account_id].refresh_token); + VLOG(1) << "MutablePO2TS::UpdateCredentials; Refresh Token was present. " + << "account_id=" << account_id; + + // The old refresh token must be revoked on the server only when it is + // invalidated. + // + // The refresh token is updated to a new valid one in case of reauth. + // In the reauth case the old and the new refresh tokens have the same + // device ID. When revoking a refresh token on the server, Gaia revokes + // all the refresh tokens that have the same device ID. + // Therefore, the old refresh token must not be revoked on the server + // when it is updated to a new valid one (otherwise the new refresh token + // would also be invalidated server-side). + // See http://crbug.com/865189 for more information about this regression. + if (is_refresh_token_invalidated) + RevokeCredentialsOnServer(refresh_tokens_[account_id].refresh_token); + + refresh_tokens_[account_id].refresh_token = refresh_token; + UpdateAuthError(account_id, error); + } else { + VLOG(1) << "MutablePO2TS::UpdateCredentials; Refresh Token was absent. " + << "account_id=" << account_id; + AddAccountStatus(account_id, refresh_token, error); + } +} + +void MutableProfileOAuth2TokenServiceDelegate::PersistCredentials( + const std::string& account_id, + const std::string& refresh_token) { + DCHECK(!account_id.empty()); + DCHECK(!refresh_token.empty()); + if (token_web_data_) { + VLOG(1) << "MutablePO2TS::PersistCredentials for account_id=" << account_id; + token_web_data_->SetTokenForService(ApplyAccountIdPrefix(account_id), + refresh_token); + } +} + +void MutableProfileOAuth2TokenServiceDelegate::RevokeAllCredentials() { + if (!can_revoke_credentials_) + return; + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + + VLOG(1) << "MutablePO2TS::RevokeAllCredentials"; + + ScopedBatchChange batch(this); + if (load_credentials_state() == LOAD_CREDENTIALS_IN_PROGRESS) { + VLOG(1) << "MutablePO2TS::RevokeAllCredentials before tokens are loaded."; + // If |RevokeAllCredentials| is called while credentials are being loaded, + // then the load must be cancelled and the load credentials state updated. + DCHECK_NE(0, web_data_service_request_); + CancelWebTokenFetch(); + set_load_credentials_state(LOAD_CREDENTIALS_FINISHED_WITH_SUCCESS); + FinishLoadingCredentials(); + } + + // Make a temporary copy of the account ids. + std::vector<std::string> accounts; + for (const auto& token : refresh_tokens_) + accounts.push_back(token.first); + for (const std::string& account : accounts) + RevokeCredentials(account); + + DCHECK_EQ(0u, refresh_tokens_.size()); + + // Make sure all tokens are removed from storage. + if (token_web_data_) + token_web_data_->RemoveAllTokens(); +} + +void MutableProfileOAuth2TokenServiceDelegate::RevokeCredentials( + const std::string& account_id) { + RevokeCredentialsImpl(account_id, /*revoke_on_server=*/true); +} + +void MutableProfileOAuth2TokenServiceDelegate::ClearPersistedCredentials( + const std::string& account_id) { + DCHECK(!account_id.empty()); + if (token_web_data_) { + VLOG(1) << "MutablePO2TS::ClearPersistedCredentials for account_id=" + << account_id; + token_web_data_->RemoveTokenForService(ApplyAccountIdPrefix(account_id)); + } +} + +void MutableProfileOAuth2TokenServiceDelegate::RevokeCredentialsOnServer( + const std::string& refresh_token) { + DCHECK(!refresh_token.empty()); + + if (refresh_token == kInvalidRefreshToken) + return; + + // Keep track or all server revoke requests. This way they can be deleted + // before the token service is shutdown and won't outlive the profile. + server_revokes_.push_back(std::make_unique<RevokeServerRefreshToken>( + this, client_, refresh_token, 0)); +} + +void MutableProfileOAuth2TokenServiceDelegate::CancelWebTokenFetch() { + if (web_data_service_request_ != 0) { + DCHECK(token_web_data_); + token_web_data_->CancelRequest(web_data_service_request_); + web_data_service_request_ = 0; + } +} + +void MutableProfileOAuth2TokenServiceDelegate::ExtractCredentials( + OAuth2TokenService* to_service, + const std::string& account_id) { + static_cast<ProfileOAuth2TokenService*>(to_service) + ->UpdateCredentials(account_id, GetRefreshToken(account_id), + signin_metrics::SourceForRefreshTokenOperation:: + kTokenService_ExtractCredentials); + RevokeCredentialsImpl(account_id, /*revoke_on_server=*/false); +} + +void MutableProfileOAuth2TokenServiceDelegate::Shutdown() { + VLOG(1) << "MutablePO2TS::Shutdown"; + server_revokes_.clear(); + CancelWebTokenFetch(); + refresh_tokens_.clear(); + OAuth2TokenServiceDelegate::Shutdown(); +} + +void MutableProfileOAuth2TokenServiceDelegate::OnConnectionChanged( + network::mojom::ConnectionType type) { + // If our network has changed, reset the backoff timer so that errors caused + // by a previous lack of network connectivity don't prevent new requests. + backoff_entry_.Reset(); +} + +const net::BackoffEntry* +MutableProfileOAuth2TokenServiceDelegate::BackoffEntry() const { + return &backoff_entry_; +} + +bool MutableProfileOAuth2TokenServiceDelegate::FixRequestErrorIfPossible() { + return !fix_request_error_callback_.is_null() + ? fix_request_error_callback_.Run() + : false; +} + +void MutableProfileOAuth2TokenServiceDelegate::AddAccountStatus( + const std::string& account_id, + const std::string& refresh_token, + const GoogleServiceAuthError& error) { + DCHECK_EQ(0u, refresh_tokens_.count(account_id)); + refresh_tokens_[account_id] = AccountStatus{refresh_token, error}; + FireAuthErrorChanged(account_id, error); +} + +void MutableProfileOAuth2TokenServiceDelegate::FinishLoadingCredentials() { + FireRefreshTokensLoaded(); +} + +void MutableProfileOAuth2TokenServiceDelegate::RevokeCredentialsImpl( + const std::string& account_id, + bool revoke_on_server) { + ValidateAccountId(account_id); + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + + if (refresh_tokens_.count(account_id) > 0) { + VLOG(1) << "MutablePO2TS::RevokeCredentials for account_id=" << account_id; + ScopedBatchChange batch(this); + const std::string& token = refresh_tokens_[account_id].refresh_token; + RecordTokenRevoked(token); + if (revoke_on_server) + RevokeCredentialsOnServer(token); + refresh_tokens_.erase(account_id); + ClearPersistedCredentials(account_id); + FireRefreshTokenRevoked(account_id); + } +} diff --git a/chromium/components/signin/core/browser/mutable_profile_oauth2_token_service_delegate.h b/chromium/components/signin/core/browser/mutable_profile_oauth2_token_service_delegate.h new file mode 100644 index 00000000000..906e25a5c9e --- /dev/null +++ b/chromium/components/signin/core/browser/mutable_profile_oauth2_token_service_delegate.h @@ -0,0 +1,236 @@ +// Copyright 2015 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. + +#ifndef COMPONENTS_SIGNIN_CORE_BROWSER_MUTABLE_PROFILE_OAUTH2_TOKEN_SERVICE_DELEGATE_H_ +#define COMPONENTS_SIGNIN_CORE_BROWSER_MUTABLE_PROFILE_OAUTH2_TOKEN_SERVICE_DELEGATE_H_ + +#include <memory> +#include <vector> + +#include "base/callback.h" +#include "base/gtest_prod_util.h" +#include "base/macros.h" +#include "base/threading/thread_checker.h" +#include "components/signin/core/browser/account_consistency_method.h" +#include "components/signin/core/browser/account_tracker_service.h" +#include "components/signin/core/browser/profile_oauth2_token_service.h" +#include "components/signin/core/browser/webdata/token_web_data.h" +#include "components/webdata/common/web_data_service_base.h" +#include "components/webdata/common/web_data_service_consumer.h" +#include "google_apis/gaia/oauth2_token_service_delegate.h" +#include "net/base/backoff_entry.h" +#include "services/network/public/cpp/network_connection_tracker.h" + +class PrefRegistrySimple; +class SigninClient; + +class MutableProfileOAuth2TokenServiceDelegate + : public OAuth2TokenServiceDelegate, + public WebDataServiceConsumer, + public network::NetworkConnectionTracker::NetworkConnectionObserver { + public: + using FixRequestErrorCallback = base::RepeatingCallback<bool()>; + + MutableProfileOAuth2TokenServiceDelegate( + SigninClient* client, + AccountTrackerService* account_tracker_service, + network::NetworkConnectionTracker* network_connection_tracker, + scoped_refptr<TokenWebData> token_web_data, + signin::AccountConsistencyMethod account_consistency, + bool revoke_all_tokens_on_load, + bool can_revoke_credentials, + FixRequestErrorCallback fix_request_error_callback); + ~MutableProfileOAuth2TokenServiceDelegate() override; + + static void RegisterProfilePrefs(PrefRegistrySimple* registry); + + // Overridden from OAuth2TokenServiceDelegate. + OAuth2AccessTokenFetcher* CreateAccessTokenFetcher( + const std::string& account_id, + scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory, + OAuth2AccessTokenConsumer* consumer) override; + + // Updates the internal cache of the result from the most-recently-completed + // auth request (used for reporting errors to the user). + void UpdateAuthError(const std::string& account_id, + const GoogleServiceAuthError& error) override; + + std::string GetTokenForMultilogin( + const std::string& account_id) const override; + bool RefreshTokenIsAvailable(const std::string& account_id) const override; + GoogleServiceAuthError GetAuthError( + const std::string& account_id) const override; + std::vector<std::string> GetAccounts() override; + scoped_refptr<network::SharedURLLoaderFactory> GetURLLoaderFactory() + const override; + void LoadCredentials(const std::string& primary_account_id) override; + void UpdateCredentials(const std::string& account_id, + const std::string& refresh_token) override; + void RevokeAllCredentials() override; + void RevokeCredentials(const std::string& account_id) override; + void ExtractCredentials(OAuth2TokenService* to_service, + const std::string& account_id) override; + void Shutdown() override; + + // Overridden from NetworkConnectionTracker::NetworkConnectionObserver. + void OnConnectionChanged(network::mojom::ConnectionType type) override; + + // Overridden from OAuth2TokenServiceDelegate. + const net::BackoffEntry* BackoffEntry() const override; + + bool FixRequestErrorIfPossible() override; + + // Returns the account's refresh token used for testing purposes. + std::string GetRefreshTokenForTest(const std::string& account_id) const; + + private: + friend class MutableProfileOAuth2TokenServiceDelegateTest; + + class RevokeServerRefreshToken; + + struct AccountStatus { + std::string refresh_token; + GoogleServiceAuthError last_auth_error; + }; + + FRIEND_TEST_ALL_PREFIXES(MutableProfileOAuth2TokenServiceDelegateTest, + PersistenceDBUpgrade); + FRIEND_TEST_ALL_PREFIXES(MutableProfileOAuth2TokenServiceDelegateTest, + FetchPersistentError); + FRIEND_TEST_ALL_PREFIXES( + MutableProfileOAuth2TokenServiceDelegateTest, + PersistenceLoadCredentialsEmptyPrimaryAccountId_DiceEnabled); + FRIEND_TEST_ALL_PREFIXES( + MutableProfileOAuth2TokenServiceDelegateTest, + LoadCredentialsClearsTokenDBWhenNoPrimaryAccount_DiceDisabled); + FRIEND_TEST_ALL_PREFIXES(MutableProfileOAuth2TokenServiceDelegateTest, + PersistenceLoadCredentials); + FRIEND_TEST_ALL_PREFIXES(MutableProfileOAuth2TokenServiceDelegateTest, + RevokeOnUpdate); + FRIEND_TEST_ALL_PREFIXES(MutableProfileOAuth2TokenServiceDelegateTest, + DelayedRevoke); + FRIEND_TEST_ALL_PREFIXES(MutableProfileOAuth2TokenServiceDelegateTest, + DiceMigrationHostedDomainPrimaryAccount); + FRIEND_TEST_ALL_PREFIXES(MutableProfileOAuth2TokenServiceDelegateTest, + ShutdownDuringRevoke); + FRIEND_TEST_ALL_PREFIXES(MutableProfileOAuth2TokenServiceDelegateTest, + RevokeRetries); + FRIEND_TEST_ALL_PREFIXES(MutableProfileOAuth2TokenServiceDelegateTest, + UpdateInvalidToken); + FRIEND_TEST_ALL_PREFIXES(MutableProfileOAuth2TokenServiceDelegateTest, + LoadInvalidToken); + FRIEND_TEST_ALL_PREFIXES(MutableProfileOAuth2TokenServiceDelegateTest, + GetAccounts); + FRIEND_TEST_ALL_PREFIXES(MutableProfileOAuth2TokenServiceDelegateTest, + RetryBackoff); + FRIEND_TEST_ALL_PREFIXES(MutableProfileOAuth2TokenServiceDelegateTest, + CanonicalizeAccountId); + FRIEND_TEST_ALL_PREFIXES(MutableProfileOAuth2TokenServiceDelegateTest, + CanonAndNonCanonAccountId); + FRIEND_TEST_ALL_PREFIXES(MutableProfileOAuth2TokenServiceDelegateTest, + ShutdownService); + FRIEND_TEST_ALL_PREFIXES(MutableProfileOAuth2TokenServiceDelegateTest, + ClearTokensOnStartup); + FRIEND_TEST_ALL_PREFIXES(MutableProfileOAuth2TokenServiceDelegateTest, + InvalidateTokensForMultilogin); + FRIEND_TEST_ALL_PREFIXES(MutableProfileOAuth2TokenServiceDelegateTest, + ExtractCredentials); + + // WebDataServiceConsumer implementation: + void OnWebDataServiceRequestDone( + WebDataServiceBase::Handle handle, + std::unique_ptr<WDTypedResult> result) override; + + // Loads credentials into in memory stucture. + void LoadAllCredentialsIntoMemory( + const std::map<std::string, std::string>& db_tokens); + + // Updates the in-memory representation of the credentials. + void UpdateCredentialsInMemory(const std::string& account_id, + const std::string& refresh_token); + + // Sets refresh token in error. + void InvalidateTokenForMultilogin(const std::string& failed_account) override; + + // Persists credentials for |account_id|. Enables overriding for + // testing purposes, or other cases, when accessing the DB is not desired. + void PersistCredentials(const std::string& account_id, + const std::string& refresh_token); + + // Clears credentials persisted for |account_id|. Enables overriding for + // testing purposes, or other cases, when accessing the DB is not desired. + void ClearPersistedCredentials(const std::string& account_id); + + // Revokes the refresh token on the server. + void RevokeCredentialsOnServer(const std::string& refresh_token); + + // Cancels any outstanding fetch for tokens from the web database. + void CancelWebTokenFetch(); + + std::string GetRefreshToken(const std::string& account_id) const; + + // Creates a new AccountStatus and adds it to the AccountStatusMap. + // The account must not be already in the map. + void AddAccountStatus(const std::string& account_id, + const std::string& refresh_token, + const GoogleServiceAuthError& error); + + // Called at when tokens are loaded. Performs housekeeping tasks and notifies + // the observers. + void FinishLoadingCredentials(); + + // Deletes the credential locally and notifies observers through + // OnRefreshTokenRevoked(). If |revoke_on_server| is true, the token is also + // revoked on the server. + void RevokeCredentialsImpl(const std::string& account_id, + bool revoke_on_server); + + // Maps the |account_id| of accounts known to ProfileOAuth2TokenService + // to information about the account. + typedef std::map<std::string, AccountStatus> AccountStatusMap; + // In memory refresh token store mapping account_id to refresh_token. + AccountStatusMap refresh_tokens_; + + // Handle to the request reading tokens from database. + WebDataServiceBase::Handle web_data_service_request_; + + // The primary account id of this service's profile during the loading of + // credentials. This member is empty otherwise. + std::string loading_primary_account_id_; + + std::vector<std::unique_ptr<RevokeServerRefreshToken>> server_revokes_; + + // Used to verify that certain methods are called only on the thread on which + // this instance was created. + THREAD_CHECKER(thread_checker_); + + // Used to rate-limit network token requests so as to not overload the server. + net::BackoffEntry::Policy backoff_policy_; + net::BackoffEntry backoff_entry_; + GoogleServiceAuthError backoff_error_; + + SigninClient* client_; + AccountTrackerService* account_tracker_service_; + network::NetworkConnectionTracker* network_connection_tracker_; + scoped_refptr<TokenWebData> token_web_data_; + signin::AccountConsistencyMethod account_consistency_; + + // Revokes all the tokens after loading them. Secondary accounts will be + // completely removed, and the primary account will be kept in authentication + // error state. + const bool revoke_all_tokens_on_load_; + + // Supervised users cannot revoke credentials. + // TODO(droger): remove this when supervised users are no longer supported on + // any platform. + const bool can_revoke_credentials_; + + // Callback function that attempts to correct request errors. Best effort + // only. Returns true if the error was fixed and retry should be reattempted. + FixRequestErrorCallback fix_request_error_callback_; + + DISALLOW_COPY_AND_ASSIGN(MutableProfileOAuth2TokenServiceDelegate); +}; + +#endif // CHROME_BROWSER_SIGNIN_MUTABLE_PROFILE_OAUTH2_TOKEN_SERVICE_DELEGATE_H_ diff --git a/chromium/components/signin/core/browser/mutable_profile_oauth2_token_service_delegate_unittest.cc b/chromium/components/signin/core/browser/mutable_profile_oauth2_token_service_delegate_unittest.cc new file mode 100644 index 00000000000..b9b8e588287 --- /dev/null +++ b/chromium/components/signin/core/browser/mutable_profile_oauth2_token_service_delegate_unittest.cc @@ -0,0 +1,1615 @@ +// Copyright 2014 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 "components/signin/core/browser/mutable_profile_oauth2_token_service_delegate.h" + +#include <map> +#include <string> +#include <utility> +#include <vector> + +#include "base/bind.h" +#include "base/files/scoped_temp_dir.h" +#include "base/macros.h" +#include "base/run_loop.h" +#include "base/test/metrics/histogram_tester.h" +#include "base/test/scoped_task_environment.h" +#include "base/threading/thread_task_runner_handle.h" +#include "base/time/time.h" +#include "build/build_config.h" +#include "build/buildflag.h" +#include "components/os_crypt/os_crypt_mocker.h" +#include "components/prefs/pref_registry_simple.h" +#include "components/prefs/scoped_user_pref_update.h" +#include "components/signin/core/browser/account_consistency_method.h" +#include "components/signin/core/browser/account_info.h" +#include "components/signin/core/browser/device_id_helper.h" +#include "components/signin/core/browser/profile_oauth2_token_service.h" +#include "components/signin/core/browser/signin_buildflags.h" +#include "components/signin/core/browser/signin_manager_base.h" +#include "components/signin/core/browser/signin_pref_names.h" +#include "components/signin/core/browser/test_signin_client.h" +#include "components/signin/core/browser/webdata/token_web_data.h" +#include "components/sync_preferences/testing_pref_service_syncable.h" +#include "components/webdata/common/web_data_service_base.h" +#include "components/webdata/common/web_database_service.h" +#include "google_apis/gaia/fake_oauth2_token_service_delegate.h" +#include "google_apis/gaia/gaia_constants.h" +#include "google_apis/gaia/gaia_urls.h" +#include "google_apis/gaia/google_service_auth_error.h" +#include "google_apis/gaia/oauth2_access_token_consumer.h" +#include "google_apis/gaia/oauth2_token_service_test_util.h" +#include "net/http/http_status_code.h" +#include "services/network/public/cpp/shared_url_loader_factory.h" +#include "services/network/test/test_network_connection_tracker.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +// Defining constant here to handle backward compatiblity tests, but this +// constant is no longer used in current versions of chrome. +static const char kLSOService[] = "lso"; +static const char kEmail[] = "user@gmail.com"; + +namespace { + +#if BUILDFLAG(ENABLE_DICE_SUPPORT) +// Create test account info. +AccountInfo CreateTestAccountInfo(const std::string& name, + bool is_hosted_domain, + bool is_valid) { + AccountInfo account_info; + account_info.account_id = name; + account_info.gaia = name; + account_info.email = name + "@email.com"; + account_info.full_name = "name"; + account_info.given_name = "name"; + if (is_valid) { + account_info.hosted_domain = + is_hosted_domain ? "example.com" : kNoHostedDomainFound; + } + account_info.locale = "en"; + account_info.picture_url = "https://example.com"; + account_info.is_child_account = false; + EXPECT_EQ(is_valid, account_info.IsValid()); + return account_info; +} +#endif + +} // namespace + +class MutableProfileOAuth2TokenServiceDelegateTest + : public testing::Test, + public OAuth2AccessTokenConsumer, + public OAuth2TokenService::Observer, + public OAuth2TokenService::DiagnosticsObserver, + public WebDataServiceConsumer { + public: + MutableProfileOAuth2TokenServiceDelegateTest() + : scoped_task_environment_( + base::test::ScopedTaskEnvironment::MainThreadType::UI, + base::test::ScopedTaskEnvironment::ExecutionMode::ASYNC), + access_token_success_count_(0), + access_token_failure_count_(0), + access_token_failure_(GoogleServiceAuthError::NONE), + token_available_count_(0), + token_revoked_count_(0), + tokens_loaded_count_(0), + start_batch_changes_(0), + end_batch_changes_(0), + auth_error_changed_count_(0), + revoke_all_tokens_on_load_(false) {} + + void SetUp() override { + OSCryptMocker::SetUp(); + + MutableProfileOAuth2TokenServiceDelegate::RegisterProfilePrefs( + pref_service_.registry()); + AccountTrackerService::RegisterPrefs(pref_service_.registry()); + SigninManagerBase::RegisterProfilePrefs(pref_service_.registry()); + client_.reset(new TestSigninClient(&pref_service_)); + client_->test_url_loader_factory()->AddResponse( + GaiaUrls::GetInstance()->oauth2_revoke_url().spec(), ""); + LoadTokenDatabase(); + account_tracker_service_.Initialize(&pref_service_, base::FilePath()); + } + + void TearDown() override { + base::RunLoop().RunUntilIdle(); + if (oauth2_service_delegate_) { + oauth2_service_delegate_->RemoveObserver(this); + oauth2_service_delegate_->Shutdown(); + } + OSCryptMocker::TearDown(); + } + + void LoadTokenDatabase() { + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + base::FilePath path = temp_dir_.GetPath().AppendASCII("TestWebDB"); + scoped_refptr<WebDatabaseService> web_database = + new WebDatabaseService(path, base::ThreadTaskRunnerHandle::Get(), + base::ThreadTaskRunnerHandle::Get()); + web_database->AddTable(std::make_unique<TokenServiceTable>()); + web_database->LoadDatabase(); + token_web_data_ = + new TokenWebData(web_database, base::ThreadTaskRunnerHandle::Get(), + base::ThreadTaskRunnerHandle::Get(), + WebDataServiceBase::ProfileErrorCallback()); + token_web_data_->Init(); + } + + void AddSuccessfulOAuhTokenResponse() { + client_->test_url_loader_factory()->AddResponse( + GaiaUrls::GetInstance()->oauth2_token_url().spec(), + GetValidTokenResponse("token", 3600)); + } + + std::unique_ptr<MutableProfileOAuth2TokenServiceDelegate> + CreateOAuth2ServiceDelegate( + signin::AccountConsistencyMethod account_consistency) { + return std::make_unique<MutableProfileOAuth2TokenServiceDelegate>( + client_.get(), &account_tracker_service_, + network::TestNetworkConnectionTracker::GetInstance(), token_web_data_, + account_consistency, revoke_all_tokens_on_load_, + true /* can_revoke_credantials */, + MutableProfileOAuth2TokenServiceDelegate::FixRequestErrorCallback()); + } + + void InitializeOAuth2ServiceDelegate( + signin::AccountConsistencyMethod account_consistency) { + oauth2_service_delegate_ = CreateOAuth2ServiceDelegate(account_consistency); + oauth2_service_delegate_->AddObserver(this); + } + + void AddAuthTokenManually(const std::string& service, + const std::string& value) { + if (token_web_data_) + token_web_data_->SetTokenForService(service, value); + } + + // WebDataServiceConsumer implementation + void OnWebDataServiceRequestDone( + WebDataServiceBase::Handle h, + std::unique_ptr<WDTypedResult> result) override { + DCHECK(!token_web_data_result_); + DCHECK_EQ(TOKEN_RESULT, result->GetType()); + token_web_data_result_.reset( + static_cast<WDResult<TokenResult>*>(result.release())); + } + + // OAuth2AccessTokenConusmer implementation + void OnGetTokenSuccess( + const OAuth2AccessTokenConsumer::TokenResponse& token_response) override { + ++access_token_success_count_; + } + + void OnGetTokenFailure(const GoogleServiceAuthError& error) override { + ++access_token_failure_count_; + access_token_failure_ = error; + } + + // OAuth2TokenService::Observer implementation. + void OnRefreshTokenAvailable(const std::string& account_id) override { + ++token_available_count_; + } + void OnRefreshTokenRevoked(const std::string& account_id) override { + ++token_revoked_count_; + } + void OnRefreshTokensLoaded() override { ++tokens_loaded_count_; } + + void OnStartBatchChanges() override { ++start_batch_changes_; } + + void OnEndBatchChanges() override { ++end_batch_changes_; } + + void OnAuthErrorChanged(const std::string& account_id, + const GoogleServiceAuthError& auth_error) override { + ++auth_error_changed_count_; + } + + // OAuth2TokenService::DiagnosticsObserver implementation + void OnRefreshTokenAvailableFromSource(const std::string& account_id, + bool is_refresh_token_valid, + const std::string& source) override { + source_for_refresh_token_available_ = source; + } + void OnRefreshTokenRevokedFromSource(const std::string& account_id, + const std::string& source) override { + source_for_refresh_token_revoked_ = source; + } + + void ResetObserverCounts() { + token_available_count_ = 0; + token_revoked_count_ = 0; + tokens_loaded_count_ = 0; + start_batch_changes_ = 0; + end_batch_changes_ = 0; + auth_error_changed_count_ = 0; + } + + void ExpectNoNotifications() { + EXPECT_EQ(0, token_available_count_); + EXPECT_EQ(0, token_revoked_count_); + EXPECT_EQ(0, tokens_loaded_count_); + ResetObserverCounts(); + } + + void ExpectOneTokenAvailableNotification() { + EXPECT_EQ(1, token_available_count_); + EXPECT_EQ(0, token_revoked_count_); + EXPECT_EQ(0, tokens_loaded_count_); + ResetObserverCounts(); + } + + void ExpectOneTokenRevokedNotification() { + EXPECT_EQ(0, token_available_count_); + EXPECT_EQ(1, token_revoked_count_); + EXPECT_EQ(0, tokens_loaded_count_); + ResetObserverCounts(); + } + + void ExpectOneTokensLoadedNotification() { + EXPECT_EQ(0, token_available_count_); + EXPECT_EQ(0, token_revoked_count_); + EXPECT_EQ(1, tokens_loaded_count_); + ResetObserverCounts(); + } + + protected: + base::test::ScopedTaskEnvironment scoped_task_environment_; + base::ScopedTempDir temp_dir_; + std::unique_ptr<TestSigninClient> client_; + std::unique_ptr<MutableProfileOAuth2TokenServiceDelegate> + oauth2_service_delegate_; + TestingOAuth2TokenServiceConsumer consumer_; + sync_preferences::TestingPrefServiceSyncable pref_service_; + AccountTrackerService account_tracker_service_; + scoped_refptr<TokenWebData> token_web_data_; + std::unique_ptr<WDResult<TokenResult>> token_web_data_result_; + int access_token_success_count_; + int access_token_failure_count_; + GoogleServiceAuthError access_token_failure_; + int token_available_count_; + int token_revoked_count_; + int tokens_loaded_count_; + int start_batch_changes_; + int end_batch_changes_; + int auth_error_changed_count_; + bool revoke_all_tokens_on_load_; + std::string source_for_refresh_token_available_; + std::string source_for_refresh_token_revoked_; +}; + +TEST_F(MutableProfileOAuth2TokenServiceDelegateTest, PersistenceDBUpgrade) { + InitializeOAuth2ServiceDelegate(signin::AccountConsistencyMethod::kMirror); + std::string main_account_id("account_id"); + std::string main_refresh_token("old_refresh_token"); + + // Populate DB with legacy tokens. + AddAuthTokenManually(GaiaConstants::kSyncService, "syncServiceToken"); + AddAuthTokenManually(kLSOService, "lsoToken"); + AddAuthTokenManually(GaiaConstants::kGaiaOAuth2LoginRefreshToken, + main_refresh_token); + + // Force LoadCredentials. + oauth2_service_delegate_->LoadCredentials(main_account_id); + base::RunLoop().RunUntilIdle(); + + // Legacy tokens get discarded, but the old refresh token is kept. + EXPECT_EQ(1, tokens_loaded_count_); + EXPECT_EQ(1, token_available_count_); + EXPECT_EQ(1, start_batch_changes_); + EXPECT_EQ(1, end_batch_changes_); + EXPECT_TRUE( + oauth2_service_delegate_->RefreshTokenIsAvailable(main_account_id)); + EXPECT_EQ(1U, oauth2_service_delegate_->refresh_tokens_.size()); + EXPECT_EQ( + main_refresh_token, + oauth2_service_delegate_->refresh_tokens_[main_account_id].refresh_token); + + // Add an old legacy token to the DB, to ensure it will not overwrite existing + // credentials for main account. + AddAuthTokenManually(GaiaConstants::kGaiaOAuth2LoginRefreshToken, + "secondOldRefreshToken"); + // Add some other legacy token. (Expected to get discarded). + AddAuthTokenManually(kLSOService, "lsoToken"); + // Also add a token using PO2TS.UpdateCredentials and make sure upgrade does + // not wipe it. + std::string other_account_id("other_account_id"); + std::string other_refresh_token("other_refresh_token"); + oauth2_service_delegate_->UpdateCredentials(other_account_id, + other_refresh_token); + ResetObserverCounts(); + + // Force LoadCredentials. + oauth2_service_delegate_->LoadCredentials(main_account_id); + base::RunLoop().RunUntilIdle(); + + // Again legacy tokens get discarded, but since the main porfile account + // token is present it is not overwritten. + EXPECT_EQ(2, token_available_count_); + EXPECT_EQ(1, tokens_loaded_count_); + EXPECT_EQ(1, start_batch_changes_); + EXPECT_EQ(1, end_batch_changes_); + EXPECT_EQ(main_refresh_token, + oauth2_service_delegate_->GetRefreshToken(main_account_id)); + EXPECT_TRUE( + oauth2_service_delegate_->RefreshTokenIsAvailable(main_account_id)); + // TODO(fgorski): cover both using RefreshTokenIsAvailable() and then get the + // tokens using GetRefreshToken() + EXPECT_EQ(2U, oauth2_service_delegate_->refresh_tokens_.size()); + EXPECT_EQ( + main_refresh_token, + oauth2_service_delegate_->refresh_tokens_[main_account_id].refresh_token); + EXPECT_EQ(other_refresh_token, + oauth2_service_delegate_->refresh_tokens_[other_account_id] + .refresh_token); + + oauth2_service_delegate_->RevokeAllCredentials(); + EXPECT_EQ(2, start_batch_changes_); + EXPECT_EQ(2, end_batch_changes_); +} + +TEST_F(MutableProfileOAuth2TokenServiceDelegateTest, + PersistenceRevokeCredentials) { + InitializeOAuth2ServiceDelegate(signin::AccountConsistencyMethod::kDisabled); + std::string account_id_1 = "account_id_1"; + std::string refresh_token_1 = "refresh_token_1"; + std::string account_id_2 = "account_id_2"; + std::string refresh_token_2 = "refresh_token_2"; + + EXPECT_FALSE(oauth2_service_delegate_->RefreshTokenIsAvailable(account_id_1)); + EXPECT_FALSE(oauth2_service_delegate_->RefreshTokenIsAvailable(account_id_2)); + oauth2_service_delegate_->UpdateCredentials(account_id_1, refresh_token_1); + oauth2_service_delegate_->UpdateCredentials(account_id_2, refresh_token_2); + EXPECT_EQ(2, start_batch_changes_); + EXPECT_EQ(2, end_batch_changes_); + + EXPECT_TRUE(oauth2_service_delegate_->RefreshTokenIsAvailable(account_id_1)); + EXPECT_TRUE(oauth2_service_delegate_->RefreshTokenIsAvailable(account_id_2)); + + ResetObserverCounts(); + oauth2_service_delegate_->RevokeCredentials(account_id_1); + EXPECT_EQ(1, start_batch_changes_); + EXPECT_EQ(1, end_batch_changes_); + ExpectOneTokenRevokedNotification(); + + EXPECT_FALSE(oauth2_service_delegate_->RefreshTokenIsAvailable(account_id_1)); + EXPECT_TRUE(oauth2_service_delegate_->RefreshTokenIsAvailable(account_id_2)); + + oauth2_service_delegate_->RevokeAllCredentials(); + EXPECT_EQ(0, token_available_count_); + EXPECT_EQ(1, token_revoked_count_); + EXPECT_EQ(0, tokens_loaded_count_); + EXPECT_EQ(1, start_batch_changes_); + EXPECT_EQ(1, end_batch_changes_); + ResetObserverCounts(); +} + +TEST_F(MutableProfileOAuth2TokenServiceDelegateTest, + LoadCredentialsStateEmptyPrimaryAccountId) { + InitializeOAuth2ServiceDelegate(signin::AccountConsistencyMethod::kDisabled); + // Ensure DB is clean. + oauth2_service_delegate_->RevokeAllCredentials(); + + EXPECT_EQ(OAuth2TokenServiceDelegate::LOAD_CREDENTIALS_NOT_STARTED, + oauth2_service_delegate_->load_credentials_state()); + oauth2_service_delegate_->LoadCredentials(""); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(OAuth2TokenServiceDelegate::LOAD_CREDENTIALS_FINISHED_WITH_SUCCESS, + oauth2_service_delegate_->load_credentials_state()); +} + +TEST_F(MutableProfileOAuth2TokenServiceDelegateTest, + PersistenceLoadCredentials) { + InitializeOAuth2ServiceDelegate(signin::AccountConsistencyMethod::kMirror); + + // Ensure DB is clean. + oauth2_service_delegate_->RevokeAllCredentials(); + ResetObserverCounts(); + + // Perform a load from an empty DB. + EXPECT_EQ(OAuth2TokenServiceDelegate::LOAD_CREDENTIALS_NOT_STARTED, + oauth2_service_delegate_->load_credentials_state()); + oauth2_service_delegate_->LoadCredentials("account_id"); + EXPECT_EQ(OAuth2TokenServiceDelegate::LOAD_CREDENTIALS_IN_PROGRESS, + oauth2_service_delegate_->load_credentials_state()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(OAuth2TokenServiceDelegate:: + LOAD_CREDENTIALS_FINISHED_WITH_NO_TOKEN_FOR_PRIMARY_ACCOUNT, + oauth2_service_delegate_->load_credentials_state()); + EXPECT_EQ(GoogleServiceAuthError::FromInvalidGaiaCredentialsReason( + GoogleServiceAuthError::InvalidGaiaCredentialsReason:: + CREDENTIALS_MISSING), + oauth2_service_delegate_->GetAuthError("account_id")); + EXPECT_EQ(1, start_batch_changes_); + EXPECT_EQ(1, end_batch_changes_); + EXPECT_EQ(1, auth_error_changed_count_); + + // A"tokens loaded" notification should have been fired. + EXPECT_EQ(1, tokens_loaded_count_); + + // As the delegate puts the primary account into the token map with an invalid + // token in the case of loading from an empty TB, a "token available" + // notification should have been fired as well. + EXPECT_EQ(1, token_available_count_); + + ResetObserverCounts(); + + // LoadCredentials() guarantees that the account given to it as argument + // is in the refresh_token map. + EXPECT_EQ(1U, oauth2_service_delegate_->refresh_tokens_.size()); + EXPECT_EQ( + MutableProfileOAuth2TokenServiceDelegate::kInvalidRefreshToken, + oauth2_service_delegate_->refresh_tokens_["account_id"].refresh_token); + // Setup a DB with tokens that don't require upgrade and clear memory. + oauth2_service_delegate_->UpdateCredentials("account_id", "refresh_token"); + oauth2_service_delegate_->UpdateCredentials("account_id2", "refresh_token2"); + oauth2_service_delegate_->refresh_tokens_.clear(); + EXPECT_EQ(2, start_batch_changes_); + EXPECT_EQ(2, end_batch_changes_); + EXPECT_EQ(2, auth_error_changed_count_); + ResetObserverCounts(); + + oauth2_service_delegate_->LoadCredentials("account_id"); + EXPECT_EQ(OAuth2TokenServiceDelegate::LOAD_CREDENTIALS_IN_PROGRESS, + oauth2_service_delegate_->load_credentials_state()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(OAuth2TokenServiceDelegate::LOAD_CREDENTIALS_FINISHED_WITH_SUCCESS, + oauth2_service_delegate_->load_credentials_state()); + EXPECT_EQ(GoogleServiceAuthError::AuthErrorNone(), + oauth2_service_delegate_->GetAuthError("account_id")); + EXPECT_EQ(2, token_available_count_); + EXPECT_EQ(0, token_revoked_count_); + EXPECT_EQ(1, tokens_loaded_count_); + EXPECT_EQ(1, start_batch_changes_); + EXPECT_EQ(1, end_batch_changes_); + EXPECT_EQ(2, auth_error_changed_count_); + ResetObserverCounts(); + + EXPECT_TRUE(oauth2_service_delegate_->RefreshTokenIsAvailable("account_id")); + EXPECT_TRUE(oauth2_service_delegate_->RefreshTokenIsAvailable("account_id2")); + + oauth2_service_delegate_->RevokeAllCredentials(); + EXPECT_EQ(0, token_available_count_); + EXPECT_EQ(2, token_revoked_count_); + EXPECT_EQ(0, tokens_loaded_count_); + EXPECT_EQ(1, start_batch_changes_); + EXPECT_EQ(1, end_batch_changes_); + EXPECT_EQ(0, auth_error_changed_count_); + ResetObserverCounts(); +} + +#if BUILDFLAG(ENABLE_DICE_SUPPORT) + +TEST_F(MutableProfileOAuth2TokenServiceDelegateTest, + PersistenceLoadCredentialsEmptyPrimaryAccountId_DiceEnabled) { + InitializeOAuth2ServiceDelegate(signin::AccountConsistencyMethod::kDice); + + // Ensure DB is clean. + oauth2_service_delegate_->RevokeAllCredentials(); + ResetObserverCounts(); + // Perform a load from an empty DB. + EXPECT_EQ(OAuth2TokenServiceDelegate::LOAD_CREDENTIALS_NOT_STARTED, + oauth2_service_delegate_->load_credentials_state()); + oauth2_service_delegate_->LoadCredentials(""); + EXPECT_EQ(OAuth2TokenServiceDelegate::LOAD_CREDENTIALS_IN_PROGRESS, + oauth2_service_delegate_->load_credentials_state()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(OAuth2TokenServiceDelegate::LOAD_CREDENTIALS_FINISHED_WITH_SUCCESS, + oauth2_service_delegate_->load_credentials_state()); + EXPECT_EQ(1, start_batch_changes_); + EXPECT_EQ(1, end_batch_changes_); + EXPECT_EQ(0, auth_error_changed_count_); + ExpectOneTokensLoadedNotification(); + + // No account should be present in the refresh token as no primary account + // was passed to the token service. + EXPECT_TRUE(oauth2_service_delegate_->refresh_tokens_.empty()); + + // Setup a DB with tokens that don't require upgrade and clear memory. + oauth2_service_delegate_->UpdateCredentials("account_id", "refresh_token"); + oauth2_service_delegate_->UpdateCredentials("account_id2", "refresh_token2"); + oauth2_service_delegate_->refresh_tokens_.clear(); + EXPECT_EQ(2, start_batch_changes_); + EXPECT_EQ(2, end_batch_changes_); + EXPECT_EQ(2, auth_error_changed_count_); + ResetObserverCounts(); + + oauth2_service_delegate_->LoadCredentials(""); + EXPECT_EQ(OAuth2TokenServiceDelegate::LOAD_CREDENTIALS_IN_PROGRESS, + oauth2_service_delegate_->load_credentials_state()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(OAuth2TokenServiceDelegate::LOAD_CREDENTIALS_FINISHED_WITH_SUCCESS, + oauth2_service_delegate_->load_credentials_state()); + EXPECT_EQ(2, token_available_count_); + EXPECT_EQ(0, token_revoked_count_); + EXPECT_EQ(1, tokens_loaded_count_); + EXPECT_EQ(1, start_batch_changes_); + EXPECT_EQ(1, end_batch_changes_); + EXPECT_EQ(2, auth_error_changed_count_); + ResetObserverCounts(); + + EXPECT_TRUE(oauth2_service_delegate_->RefreshTokenIsAvailable("account_id")); + EXPECT_TRUE(oauth2_service_delegate_->RefreshTokenIsAvailable("account_id2")); + + oauth2_service_delegate_->RevokeAllCredentials(); + EXPECT_EQ(0, token_available_count_); + EXPECT_EQ(2, token_revoked_count_); + EXPECT_EQ(0, tokens_loaded_count_); + EXPECT_EQ(1, start_batch_changes_); + EXPECT_EQ(1, end_batch_changes_); + EXPECT_EQ(0, auth_error_changed_count_); + ResetObserverCounts(); +} + +// Tests that Dice migration does not happen if an account is invalid. In +// particular, no hosted domain tokens are revoked. +TEST_F(MutableProfileOAuth2TokenServiceDelegateTest, + DiceNoMigrationOnInvalidAccount) { + ASSERT_FALSE(pref_service_.GetBoolean(prefs::kTokenServiceDiceCompatible)); + InitializeOAuth2ServiceDelegate( + signin::AccountConsistencyMethod::kDiceMigration); + oauth2_service_delegate_->RevokeAllCredentials(); + + // Add account info to the account tracker. + AccountInfo primary_account = CreateTestAccountInfo( + "primary_account", true /* is_hosted_domain*/, true /* is_valid*/); + AccountInfo secondary_account = CreateTestAccountInfo( + "secondary_account", false /* is_hosted_domain*/, false /* is_valid*/); + account_tracker_service_.SeedAccountInfo(primary_account); + account_tracker_service_.SeedAccountInfo(secondary_account); + + ResetObserverCounts(); + AddAuthTokenManually("AccountId-" + primary_account.account_id, + "refresh_token"); + AddAuthTokenManually("AccountId-" + secondary_account.account_id, + "refresh_token"); + oauth2_service_delegate_->LoadCredentials(primary_account.account_id); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(1, tokens_loaded_count_); + EXPECT_EQ(2, token_available_count_); + EXPECT_EQ(0, token_revoked_count_); + EXPECT_EQ(1, start_batch_changes_); + EXPECT_EQ(1, end_batch_changes_); + EXPECT_EQ(2, auth_error_changed_count_); + EXPECT_TRUE(oauth2_service_delegate_->RefreshTokenIsAvailable( + primary_account.account_id)); + EXPECT_TRUE(oauth2_service_delegate_->RefreshTokenIsAvailable( + secondary_account.account_id)); + EXPECT_EQ(OAuth2TokenServiceDelegate::LOAD_CREDENTIALS_FINISHED_WITH_SUCCESS, + oauth2_service_delegate_->load_credentials_state()); + + EXPECT_FALSE(pref_service_.GetBoolean(prefs::kTokenServiceDiceCompatible)); +} + +// Tests that the migration happened after loading consummer accounts. +TEST_F(MutableProfileOAuth2TokenServiceDelegateTest, + DiceMigrationConsummerAccounts) { + ASSERT_EQ(AccountTrackerService::MIGRATION_DONE, + account_tracker_service_.GetMigrationState()); + ASSERT_FALSE(pref_service_.GetBoolean(prefs::kTokenServiceDiceCompatible)); + InitializeOAuth2ServiceDelegate( + signin::AccountConsistencyMethod::kDiceMigration); + oauth2_service_delegate_->RevokeAllCredentials(); + + // Add account info to the account tracker. + AccountInfo primary_account = CreateTestAccountInfo( + "primary_account", false /* is_hosted_domain*/, true /* is_valid*/); + AccountInfo secondary_account = CreateTestAccountInfo( + "secondary_account", false /* is_hosted_domain*/, true /* is_valid*/); + account_tracker_service_.SeedAccountInfo(primary_account); + account_tracker_service_.SeedAccountInfo(secondary_account); + + ResetObserverCounts(); + AddAuthTokenManually("AccountId-" + primary_account.account_id, + "refresh_token"); + AddAuthTokenManually("AccountId-" + secondary_account.account_id, + "refresh_token"); + oauth2_service_delegate_->LoadCredentials(primary_account.account_id); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(1, tokens_loaded_count_); + EXPECT_EQ(2, token_available_count_); + EXPECT_EQ(0, token_revoked_count_); + EXPECT_EQ(1, start_batch_changes_); + EXPECT_EQ(1, end_batch_changes_); + EXPECT_EQ(2, auth_error_changed_count_); + EXPECT_TRUE(oauth2_service_delegate_->RefreshTokenIsAvailable( + primary_account.account_id)); + EXPECT_TRUE(oauth2_service_delegate_->RefreshTokenIsAvailable( + secondary_account.account_id)); + EXPECT_EQ(OAuth2TokenServiceDelegate::LOAD_CREDENTIALS_FINISHED_WITH_SUCCESS, + oauth2_service_delegate_->load_credentials_state()); + + EXPECT_TRUE(pref_service_.GetBoolean(prefs::kTokenServiceDiceCompatible)); +} + +// Tests that the migration revokes the hosted domain tokens. +TEST_F(MutableProfileOAuth2TokenServiceDelegateTest, + DiceMigrationHostedDomainAccounts) { + ASSERT_EQ(AccountTrackerService::MIGRATION_DONE, + account_tracker_service_.GetMigrationState()); + ASSERT_FALSE(pref_service_.GetBoolean(prefs::kTokenServiceDiceCompatible)); + InitializeOAuth2ServiceDelegate( + signin::AccountConsistencyMethod::kDiceMigration); + oauth2_service_delegate_->RevokeAllCredentials(); + + // Add account info to the account tracker. + AccountInfo primary_account = CreateTestAccountInfo( + "primary_account", false /* is_hosted_domain*/, true /* is_valid*/); + AccountInfo secondary_account = CreateTestAccountInfo( + "secondary_account", true /* is_hosted_domain*/, true /* is_valid*/); + account_tracker_service_.SeedAccountInfo(primary_account); + account_tracker_service_.SeedAccountInfo(secondary_account); + + ResetObserverCounts(); + AddAuthTokenManually("AccountId-" + primary_account.account_id, + "refresh_token"); + AddAuthTokenManually("AccountId-" + secondary_account.account_id, + "refresh_token"); + oauth2_service_delegate_->LoadCredentials(primary_account.account_id); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(1, tokens_loaded_count_); + EXPECT_EQ(1, token_available_count_); + EXPECT_EQ(1, token_revoked_count_); + EXPECT_EQ(1, start_batch_changes_); + EXPECT_EQ(1, end_batch_changes_); + EXPECT_EQ(1, auth_error_changed_count_); + EXPECT_TRUE(oauth2_service_delegate_->RefreshTokenIsAvailable( + primary_account.account_id)); + EXPECT_EQ(OAuth2TokenServiceDelegate::LOAD_CREDENTIALS_FINISHED_WITH_SUCCESS, + oauth2_service_delegate_->load_credentials_state()); + + EXPECT_TRUE(pref_service_.GetBoolean(prefs::kTokenServiceDiceCompatible)); +} + +// Tests that the migration can revoke the primary token too. +TEST_F(MutableProfileOAuth2TokenServiceDelegateTest, + DiceMigrationHostedDomainPrimaryAccount) { + ASSERT_EQ(AccountTrackerService::MIGRATION_DONE, + account_tracker_service_.GetMigrationState()); + ASSERT_FALSE(pref_service_.GetBoolean(prefs::kTokenServiceDiceCompatible)); + InitializeOAuth2ServiceDelegate( + signin::AccountConsistencyMethod::kDiceMigration); + oauth2_service_delegate_->RevokeAllCredentials(); + + // Add account info to the account tracker. + AccountInfo primary_account = CreateTestAccountInfo( + "primary_account", true /* is_hosted_domain*/, true /* is_valid*/); + account_tracker_service_.SeedAccountInfo(primary_account); + + ResetObserverCounts(); + AddAuthTokenManually("AccountId-" + primary_account.account_id, + "refresh_token"); + oauth2_service_delegate_->LoadCredentials(primary_account.account_id); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(1, tokens_loaded_count_); + EXPECT_EQ(1, token_revoked_count_); + EXPECT_EQ(1, start_batch_changes_); + EXPECT_EQ(1, end_batch_changes_); + EXPECT_EQ(1, auth_error_changed_count_); + + // After having revoked the primary account's token during loading, the + // delegate should have noticed that it had no token for the primary account + // when the load was complete and inserted an invalid token for that account. + EXPECT_EQ(1, token_available_count_); + EXPECT_TRUE(oauth2_service_delegate_->RefreshTokenIsAvailable( + primary_account.account_id)); + EXPECT_EQ( + MutableProfileOAuth2TokenServiceDelegate::kInvalidRefreshToken, + oauth2_service_delegate_->refresh_tokens_[primary_account.account_id] + .refresh_token); + EXPECT_EQ( + GoogleServiceAuthError::InvalidGaiaCredentialsReason::CREDENTIALS_MISSING, + oauth2_service_delegate_->GetAuthError(primary_account.account_id) + .GetInvalidGaiaCredentialsReason()); + EXPECT_EQ(OAuth2TokenServiceDelegate:: + LOAD_CREDENTIALS_FINISHED_WITH_NO_TOKEN_FOR_PRIMARY_ACCOUNT, + oauth2_service_delegate_->load_credentials_state()); + + EXPECT_TRUE(pref_service_.GetBoolean(prefs::kTokenServiceDiceCompatible)); +} + +#endif // BUILDFLAG(ENABLE_DICE_SUPPORT) + +#if !defined(OS_CHROMEOS) +TEST_F(MutableProfileOAuth2TokenServiceDelegateTest, + LoadCredentialsClearsTokenDBWhenNoPrimaryAccount_DiceDisabled) { + // Populate DB with 2 valid tokens. + AddAuthTokenManually("AccountId-12345", "refresh_token"); + AddAuthTokenManually("AccountId-67890", "refresh_token"); + + InitializeOAuth2ServiceDelegate(signin::AccountConsistencyMethod::kDisabled); + oauth2_service_delegate_->LoadCredentials(/*primary_account_id=*/""); + base::RunLoop().RunUntilIdle(); + + // No tokens were loaded. + EXPECT_EQ(1, tokens_loaded_count_); + EXPECT_EQ(1, start_batch_changes_); + EXPECT_EQ(0, token_available_count_); + EXPECT_EQ(2, token_revoked_count_); + EXPECT_EQ(1, end_batch_changes_); + EXPECT_EQ(0U, oauth2_service_delegate_->refresh_tokens_.size()); + + // Handle to the request reading tokens from database. + token_web_data_->GetAllTokens(this); + base::RunLoop().RunUntilIdle(); + ASSERT_TRUE(token_web_data_result_.get()); + ASSERT_EQ(0u, token_web_data_result_->GetValue().tokens.size()); +} +#endif // !defined(OS_CHROMEOS) + +// Tests that calling UpdateCredentials revokes the old token, without sending +// the notification. +TEST_F(MutableProfileOAuth2TokenServiceDelegateTest, RevokeOnUpdate) { + // Add a token. + InitializeOAuth2ServiceDelegate(signin::AccountConsistencyMethod::kDisabled); + ASSERT_TRUE(oauth2_service_delegate_->server_revokes_.empty()); + oauth2_service_delegate_->UpdateCredentials("account_id", "refresh_token"); + EXPECT_TRUE(oauth2_service_delegate_->server_revokes_.empty()); + ExpectOneTokenAvailableNotification(); + + // Updating the token does not revoke the old one. + // Regression test for http://crbug.com/865189 + oauth2_service_delegate_->UpdateCredentials("account_id", "refresh_token2"); + EXPECT_TRUE(oauth2_service_delegate_->server_revokes_.empty()); + ExpectOneTokenAvailableNotification(); + + // Flush the server revokes. + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(oauth2_service_delegate_->server_revokes_.empty()); + + // Set the same token again. + oauth2_service_delegate_->UpdateCredentials("account_id", "refresh_token2"); + EXPECT_TRUE(oauth2_service_delegate_->server_revokes_.empty()); + ExpectNoNotifications(); + + // Clear the token. + oauth2_service_delegate_->RevokeAllCredentials(); + EXPECT_EQ(1u, oauth2_service_delegate_->server_revokes_.size()); + ExpectOneTokenRevokedNotification(); + + // Flush the server revokes. + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(oauth2_service_delegate_->server_revokes_.empty()); +} + +TEST_F(MutableProfileOAuth2TokenServiceDelegateTest, DelayedRevoke) { + client_->SetNetworkCallsDelayed(true); + InitializeOAuth2ServiceDelegate(signin::AccountConsistencyMethod::kDisabled); + oauth2_service_delegate_->UpdateCredentials("account_id", "refresh_token"); + EXPECT_TRUE(oauth2_service_delegate_->server_revokes_.empty()); + oauth2_service_delegate_->RevokeCredentials("account_id"); + + // The revoke does not start until network calls are unblocked. + EXPECT_EQ(1u, oauth2_service_delegate_->server_revokes_.size()); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(1u, oauth2_service_delegate_->server_revokes_.size()); + + // Unblock network calls, and check that the revocation goes through. + client_->SetNetworkCallsDelayed(false); + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(oauth2_service_delegate_->server_revokes_.empty()); +} + +TEST_F(MutableProfileOAuth2TokenServiceDelegateTest, ShutdownDuringRevoke) { + // Shutdown cancels the revocation. + client_->SetNetworkCallsDelayed(true); + InitializeOAuth2ServiceDelegate(signin::AccountConsistencyMethod::kDisabled); + oauth2_service_delegate_->UpdateCredentials("account_id", "refresh_token"); + oauth2_service_delegate_->RevokeCredentials("account_id"); + EXPECT_EQ(1u, oauth2_service_delegate_->server_revokes_.size()); + + // Shutdown. + oauth2_service_delegate_->Shutdown(); + EXPECT_TRUE(oauth2_service_delegate_->server_revokes_.empty()); + + // Unblocking network calls after shutdown does not crash. + client_->SetNetworkCallsDelayed(false); + base::RunLoop().RunUntilIdle(); +} + +TEST_F(MutableProfileOAuth2TokenServiceDelegateTest, RevokeRetries) { + InitializeOAuth2ServiceDelegate(signin::AccountConsistencyMethod::kDisabled); + const std::string url = GaiaUrls::GetInstance()->oauth2_revoke_url().spec(); + // Revokes will remain in "pending" state. + client_->test_url_loader_factory()->ClearResponses(); + + oauth2_service_delegate_->UpdateCredentials("account_id", "refresh_token"); + EXPECT_TRUE(oauth2_service_delegate_->server_revokes_.empty()); + EXPECT_FALSE(client_->test_url_loader_factory()->IsPending(url)); + + oauth2_service_delegate_->RevokeCredentials("account_id"); + EXPECT_EQ(1u, oauth2_service_delegate_->server_revokes_.size()); + EXPECT_TRUE(client_->test_url_loader_factory()->IsPending(url)); + // Fail and retry. + client_->test_url_loader_factory()->SimulateResponseForPendingRequest( + url, std::string(), net::HTTP_INTERNAL_SERVER_ERROR); + EXPECT_TRUE(client_->test_url_loader_factory()->IsPending(url)); + EXPECT_EQ(1u, oauth2_service_delegate_->server_revokes_.size()); + // Fail and retry. + client_->test_url_loader_factory()->SimulateResponseForPendingRequest( + url, std::string(), net::HTTP_INTERNAL_SERVER_ERROR); + EXPECT_TRUE(client_->test_url_loader_factory()->IsPending(url)); + EXPECT_EQ(1u, oauth2_service_delegate_->server_revokes_.size()); + // Do not retry after third attempt. + client_->test_url_loader_factory()->SimulateResponseForPendingRequest( + url, std::string(), net::HTTP_INTERNAL_SERVER_ERROR); + EXPECT_FALSE(client_->test_url_loader_factory()->IsPending(url)); + EXPECT_TRUE(oauth2_service_delegate_->server_revokes_.empty()); + + // No retry after success. + oauth2_service_delegate_->UpdateCredentials("account_id", "refresh_token"); + oauth2_service_delegate_->RevokeCredentials("account_id"); + EXPECT_EQ(1u, oauth2_service_delegate_->server_revokes_.size()); + EXPECT_TRUE(client_->test_url_loader_factory()->IsPending(url)); + client_->test_url_loader_factory()->SimulateResponseForPendingRequest( + url, std::string(), net::HTTP_OK); + EXPECT_FALSE(client_->test_url_loader_factory()->IsPending(url)); + EXPECT_TRUE(oauth2_service_delegate_->server_revokes_.empty()); +} + +TEST_F(MutableProfileOAuth2TokenServiceDelegateTest, UpdateInvalidToken) { + // Add the invalid token. + InitializeOAuth2ServiceDelegate(signin::AccountConsistencyMethod::kDisabled); + ASSERT_TRUE(oauth2_service_delegate_->server_revokes_.empty()); + oauth2_service_delegate_->UpdateCredentials( + "account_id", + MutableProfileOAuth2TokenServiceDelegate::kInvalidRefreshToken); + EXPECT_TRUE(oauth2_service_delegate_->server_revokes_.empty()); + EXPECT_EQ(1, auth_error_changed_count_); + ExpectOneTokenAvailableNotification(); + + // The account is in authentication error. + EXPECT_EQ(GoogleServiceAuthError( + GoogleServiceAuthError::FromInvalidGaiaCredentialsReason( + GoogleServiceAuthError::InvalidGaiaCredentialsReason:: + CREDENTIALS_REJECTED_BY_CLIENT)), + oauth2_service_delegate_->GetAuthError("account_id")); + + // Update the token: authentication error is fixed, no actual server + // revocation. + oauth2_service_delegate_->UpdateCredentials("account_id", "refresh_token"); + EXPECT_TRUE(oauth2_service_delegate_->server_revokes_.empty()); + EXPECT_EQ(1, auth_error_changed_count_); + ExpectOneTokenAvailableNotification(); + EXPECT_EQ(GoogleServiceAuthError::AuthErrorNone(), + oauth2_service_delegate_->GetAuthError("account_id")); +} + +TEST_F(MutableProfileOAuth2TokenServiceDelegateTest, + InvalidateTokensForMultilogin) { + class TokenServiceErrorObserver : public OAuth2TokenService::Observer { + public: + MOCK_METHOD2(OnAuthErrorChanged, + void(const std::string&, const GoogleServiceAuthError&)); + }; + + InitializeOAuth2ServiceDelegate(signin::AccountConsistencyMethod::kDice); + TokenServiceErrorObserver observer; + oauth2_service_delegate_->AddObserver(&observer); + + const std::string account_id1 = "account_id1"; + const std::string account_id2 = "account_id2"; + + // This will be fired from UpdateCredentials. + EXPECT_CALL( + observer, + OnAuthErrorChanged(::testing::_, GoogleServiceAuthError::AuthErrorNone())) + .Times(2); + oauth2_service_delegate_->UpdateCredentials(account_id1, "refresh_token1"); + oauth2_service_delegate_->UpdateCredentials(account_id2, "refresh_token2"); + + testing::Mock::VerifyAndClearExpectations(&observer); + + // This should be fired after error is set. + EXPECT_CALL( + observer, + OnAuthErrorChanged(account_id1, + GoogleServiceAuthError( + GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS))) + .Times(1); + + oauth2_service_delegate_->InvalidateTokenForMultilogin(account_id1); + EXPECT_EQ(oauth2_service_delegate_->GetAuthError(account_id1).state(), + GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS); + EXPECT_EQ(oauth2_service_delegate_->GetAuthError(account_id2).state(), + GoogleServiceAuthError::NONE); + + oauth2_service_delegate_->RemoveObserver(&observer); +} + +TEST_F(MutableProfileOAuth2TokenServiceDelegateTest, LoadInvalidToken) { + InitializeOAuth2ServiceDelegate(signin::AccountConsistencyMethod::kDice); + std::map<std::string, std::string> tokens; + tokens["AccountId-account_id"] = + MutableProfileOAuth2TokenServiceDelegate::kInvalidRefreshToken; + + oauth2_service_delegate_->LoadAllCredentialsIntoMemory(tokens); + + EXPECT_EQ(1u, oauth2_service_delegate_->GetAccounts().size()); + EXPECT_TRUE(oauth2_service_delegate_->RefreshTokenIsAvailable("account_id")); + EXPECT_STREQ(MutableProfileOAuth2TokenServiceDelegate::kInvalidRefreshToken, + oauth2_service_delegate_->GetRefreshToken("account_id").c_str()); + + // The account is in authentication error. + EXPECT_EQ(GoogleServiceAuthError( + GoogleServiceAuthError::FromInvalidGaiaCredentialsReason( + GoogleServiceAuthError::InvalidGaiaCredentialsReason:: + CREDENTIALS_REJECTED_BY_CLIENT)), + oauth2_service_delegate_->GetAuthError("account_id")); +} + +TEST_F(MutableProfileOAuth2TokenServiceDelegateTest, GetTokenForMultilogin) { + InitializeOAuth2ServiceDelegate(signin::AccountConsistencyMethod::kDice); + const std::string account_id1 = "account_id1"; + const std::string account_id2 = "account_id2"; + + oauth2_service_delegate_->UpdateCredentials(account_id1, "refresh_token1"); + oauth2_service_delegate_->UpdateCredentials(account_id2, "refresh_token2"); + oauth2_service_delegate_->UpdateAuthError( + account_id2, + GoogleServiceAuthError(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS)); + + EXPECT_EQ(oauth2_service_delegate_->GetTokenForMultilogin(account_id1), + "refresh_token1"); + EXPECT_EQ(oauth2_service_delegate_->GetTokenForMultilogin(account_id2), + std::string()); + EXPECT_EQ(oauth2_service_delegate_->GetTokenForMultilogin("unknown account"), + std::string()); +} + +TEST_F(MutableProfileOAuth2TokenServiceDelegateTest, PersistenceNotifications) { + InitializeOAuth2ServiceDelegate(signin::AccountConsistencyMethod::kDisabled); + oauth2_service_delegate_->UpdateCredentials("account_id", "refresh_token"); + ExpectOneTokenAvailableNotification(); + + oauth2_service_delegate_->UpdateCredentials("account_id", "refresh_token"); + ExpectNoNotifications(); + + oauth2_service_delegate_->UpdateCredentials("account_id", "refresh_token2"); + ExpectOneTokenAvailableNotification(); + + oauth2_service_delegate_->RevokeCredentials("account_id"); + ExpectOneTokenRevokedNotification(); + + oauth2_service_delegate_->UpdateCredentials("account_id", "refresh_token2"); + ExpectOneTokenAvailableNotification(); + + oauth2_service_delegate_->RevokeAllCredentials(); + ResetObserverCounts(); +} + +TEST_F(MutableProfileOAuth2TokenServiceDelegateTest, GetAccounts) { + InitializeOAuth2ServiceDelegate(signin::AccountConsistencyMethod::kDisabled); + EXPECT_TRUE(oauth2_service_delegate_->GetAccounts().empty()); + oauth2_service_delegate_->UpdateCredentials("account_id1", "refresh_token1"); + oauth2_service_delegate_->UpdateCredentials("account_id2", "refresh_token2"); + std::vector<std::string> accounts = oauth2_service_delegate_->GetAccounts(); + EXPECT_EQ(2u, accounts.size()); + EXPECT_EQ(1, count(accounts.begin(), accounts.end(), "account_id1")); + EXPECT_EQ(1, count(accounts.begin(), accounts.end(), "account_id2")); + oauth2_service_delegate_->RevokeCredentials("account_id2"); + accounts = oauth2_service_delegate_->GetAccounts(); + EXPECT_EQ(1u, oauth2_service_delegate_->GetAccounts().size()); + EXPECT_EQ(1, count(accounts.begin(), accounts.end(), "account_id1")); +} + +TEST_F(MutableProfileOAuth2TokenServiceDelegateTest, FetchPersistentError) { + InitializeOAuth2ServiceDelegate(signin::AccountConsistencyMethod::kDisabled); + oauth2_service_delegate_->UpdateCredentials(kEmail, "refreshToken"); + EXPECT_EQ(GoogleServiceAuthError::AuthErrorNone(), + oauth2_service_delegate_->GetAuthError(kEmail)); + + GoogleServiceAuthError authfail(GoogleServiceAuthError::ACCOUNT_DELETED); + oauth2_service_delegate_->UpdateAuthError(kEmail, authfail); + EXPECT_NE(GoogleServiceAuthError::AuthErrorNone(), + oauth2_service_delegate_->GetAuthError(kEmail)); + + // Create a "success" fetch we don't expect to get called. + AddSuccessfulOAuhTokenResponse(); + + EXPECT_EQ(0, access_token_success_count_); + EXPECT_EQ(0, access_token_failure_count_); + std::vector<std::string> scope_list; + scope_list.push_back("scope"); + std::unique_ptr<OAuth2AccessTokenFetcher> fetcher( + oauth2_service_delegate_->CreateAccessTokenFetcher( + kEmail, oauth2_service_delegate_->GetURLLoaderFactory(), this)); + fetcher->Start("foo", "bar", scope_list); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(0, access_token_success_count_); + EXPECT_EQ(1, access_token_failure_count_); +} + +TEST_F(MutableProfileOAuth2TokenServiceDelegateTest, RetryBackoff) { + InitializeOAuth2ServiceDelegate(signin::AccountConsistencyMethod::kDisabled); + oauth2_service_delegate_->UpdateCredentials(kEmail, "refreshToken"); + EXPECT_EQ(GoogleServiceAuthError::AuthErrorNone(), + oauth2_service_delegate_->GetAuthError(kEmail)); + + GoogleServiceAuthError authfail(GoogleServiceAuthError::SERVICE_UNAVAILABLE); + oauth2_service_delegate_->UpdateAuthError(kEmail, authfail); + EXPECT_EQ(GoogleServiceAuthError::AuthErrorNone(), + oauth2_service_delegate_->GetAuthError(kEmail)); + + // Create a "success" fetch we don't expect to get called just yet. + AddSuccessfulOAuhTokenResponse(); + + // Transient error will repeat until backoff period expires. + EXPECT_EQ(0, access_token_success_count_); + EXPECT_EQ(0, access_token_failure_count_); + std::vector<std::string> scope_list; + scope_list.push_back("scope"); + std::unique_ptr<OAuth2AccessTokenFetcher> fetcher1( + oauth2_service_delegate_->CreateAccessTokenFetcher( + kEmail, oauth2_service_delegate_->GetURLLoaderFactory(), this)); + fetcher1->Start("foo", "bar", scope_list); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(0, access_token_success_count_); + EXPECT_EQ(1, access_token_failure_count_); + // Expect a positive backoff time. + EXPECT_GT(oauth2_service_delegate_->backoff_entry_.GetTimeUntilRelease(), + base::TimeDelta()); + + // Pretend that backoff has expired and try again. + oauth2_service_delegate_->backoff_entry_.SetCustomReleaseTime( + base::TimeTicks()); + std::unique_ptr<OAuth2AccessTokenFetcher> fetcher2( + oauth2_service_delegate_->CreateAccessTokenFetcher( + kEmail, oauth2_service_delegate_->GetURLLoaderFactory(), this)); + fetcher2->Start("foo", "bar", scope_list); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(1, access_token_success_count_); + EXPECT_EQ(1, access_token_failure_count_); +} + +TEST_F(MutableProfileOAuth2TokenServiceDelegateTest, ResetBackoff) { + InitializeOAuth2ServiceDelegate(signin::AccountConsistencyMethod::kDisabled); + oauth2_service_delegate_->UpdateCredentials(kEmail, "refreshToken"); + EXPECT_EQ(GoogleServiceAuthError::AuthErrorNone(), + oauth2_service_delegate_->GetAuthError(kEmail)); + + GoogleServiceAuthError authfail(GoogleServiceAuthError::SERVICE_UNAVAILABLE); + oauth2_service_delegate_->UpdateAuthError(kEmail, authfail); + EXPECT_EQ(GoogleServiceAuthError::AuthErrorNone(), + oauth2_service_delegate_->GetAuthError(kEmail)); + + // Create a "success" fetch we don't expect to get called just yet. + AddSuccessfulOAuhTokenResponse(); + + // Transient error will repeat until backoff period expires. + EXPECT_EQ(0, access_token_success_count_); + EXPECT_EQ(0, access_token_failure_count_); + std::vector<std::string> scope_list; + scope_list.push_back("scope"); + std::unique_ptr<OAuth2AccessTokenFetcher> fetcher1( + oauth2_service_delegate_->CreateAccessTokenFetcher( + kEmail, oauth2_service_delegate_->GetURLLoaderFactory(), this)); + fetcher1->Start("foo", "bar", scope_list); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(0, access_token_success_count_); + EXPECT_EQ(1, access_token_failure_count_); + + // Notify of network change and ensure that request now runs. + oauth2_service_delegate_->OnConnectionChanged( + network::mojom::ConnectionType::CONNECTION_WIFI); + std::unique_ptr<OAuth2AccessTokenFetcher> fetcher2( + oauth2_service_delegate_->CreateAccessTokenFetcher( + kEmail, oauth2_service_delegate_->GetURLLoaderFactory(), this)); + fetcher2->Start("foo", "bar", scope_list); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(1, access_token_success_count_); + EXPECT_EQ(1, access_token_failure_count_); +} + +TEST_F(MutableProfileOAuth2TokenServiceDelegateTest, CanonicalizeAccountId) { + pref_service_.SetInteger(prefs::kAccountIdMigrationState, + AccountTrackerService::MIGRATION_NOT_STARTED); + InitializeOAuth2ServiceDelegate(signin::AccountConsistencyMethod::kMirror); + std::map<std::string, std::string> tokens; + tokens["AccountId-user@gmail.com"] = "refresh_token"; + tokens["AccountId-Foo.Bar@gmail.com"] = "refresh_token"; + tokens["AccountId-12345"] = "refresh_token"; + + oauth2_service_delegate_->LoadAllCredentialsIntoMemory(tokens); + + EXPECT_TRUE( + oauth2_service_delegate_->RefreshTokenIsAvailable("user@gmail.com")); + EXPECT_TRUE( + oauth2_service_delegate_->RefreshTokenIsAvailable("foobar@gmail.com")); + EXPECT_TRUE(oauth2_service_delegate_->RefreshTokenIsAvailable("12345")); +} + +TEST_F(MutableProfileOAuth2TokenServiceDelegateTest, + CanonAndNonCanonAccountId) { + pref_service_.SetInteger(prefs::kAccountIdMigrationState, + AccountTrackerService::MIGRATION_NOT_STARTED); + InitializeOAuth2ServiceDelegate(signin::AccountConsistencyMethod::kMirror); + std::map<std::string, std::string> tokens; + tokens["AccountId-Foo.Bar@gmail.com"] = "bad_token"; + tokens["AccountId-foobar@gmail.com"] = "good_token"; + + oauth2_service_delegate_->LoadAllCredentialsIntoMemory(tokens); + + EXPECT_EQ(1u, oauth2_service_delegate_->GetAccounts().size()); + EXPECT_TRUE( + oauth2_service_delegate_->RefreshTokenIsAvailable("foobar@gmail.com")); + EXPECT_STREQ( + "good_token", + oauth2_service_delegate_->GetRefreshToken("foobar@gmail.com").c_str()); +} + +TEST_F(MutableProfileOAuth2TokenServiceDelegateTest, ShutdownService) { + InitializeOAuth2ServiceDelegate(signin::AccountConsistencyMethod::kMirror); + EXPECT_TRUE(oauth2_service_delegate_->GetAccounts().empty()); + oauth2_service_delegate_->UpdateCredentials("account_id1", "refresh_token1"); + oauth2_service_delegate_->UpdateCredentials("account_id2", "refresh_token2"); + std::vector<std::string> accounts = oauth2_service_delegate_->GetAccounts(); + EXPECT_EQ(2u, accounts.size()); + EXPECT_EQ(1, count(accounts.begin(), accounts.end(), "account_id1")); + EXPECT_EQ(1, count(accounts.begin(), accounts.end(), "account_id2")); + oauth2_service_delegate_->LoadCredentials("account_id1"); + oauth2_service_delegate_->UpdateCredentials("account_id1", "refresh_token3"); + oauth2_service_delegate_->Shutdown(); + EXPECT_TRUE(oauth2_service_delegate_->server_revokes_.empty()); + EXPECT_TRUE(oauth2_service_delegate_->refresh_tokens_.empty()); + EXPECT_EQ(0, oauth2_service_delegate_->web_data_service_request_); +} + +TEST_F(MutableProfileOAuth2TokenServiceDelegateTest, GaiaIdMigration) { + InitializeOAuth2ServiceDelegate(signin::AccountConsistencyMethod::kMirror); + if (account_tracker_service_.GetMigrationState() != + AccountTrackerService::MIGRATION_NOT_STARTED) { + std::string email = "foo@gmail.com"; + std::string gaia_id = "foo's gaia id"; + + pref_service_.SetInteger(prefs::kAccountIdMigrationState, + AccountTrackerService::MIGRATION_NOT_STARTED); + + ListPrefUpdate update(&pref_service_, prefs::kAccountInfo); + update->Clear(); + auto dict = std::make_unique<base::DictionaryValue>(); + dict->SetString("account_id", email); + dict->SetString("email", email); + dict->SetString("gaia", gaia_id); + update->Append(std::move(dict)); + account_tracker_service_.Shutdown(); + account_tracker_service_.Initialize(&pref_service_, base::FilePath()); + + AddAuthTokenManually("AccountId-" + email, "refresh_token"); + oauth2_service_delegate_->LoadCredentials(gaia_id); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(1, tokens_loaded_count_); + EXPECT_EQ(1, token_available_count_); + EXPECT_EQ(1, start_batch_changes_); + EXPECT_EQ(1, end_batch_changes_); + + std::vector<std::string> accounts = oauth2_service_delegate_->GetAccounts(); + EXPECT_EQ(1u, accounts.size()); + + EXPECT_FALSE(oauth2_service_delegate_->RefreshTokenIsAvailable(email)); + EXPECT_TRUE(oauth2_service_delegate_->RefreshTokenIsAvailable(gaia_id)); + + account_tracker_service_.SetMigrationDone(); + oauth2_service_delegate_->Shutdown(); + ResetObserverCounts(); + + oauth2_service_delegate_->LoadCredentials(gaia_id); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(1, tokens_loaded_count_); + EXPECT_EQ(1, token_available_count_); + EXPECT_EQ(1, start_batch_changes_); + EXPECT_EQ(1, end_batch_changes_); + + EXPECT_FALSE(oauth2_service_delegate_->RefreshTokenIsAvailable(email)); + EXPECT_TRUE(oauth2_service_delegate_->RefreshTokenIsAvailable(gaia_id)); + accounts = oauth2_service_delegate_->GetAccounts(); + EXPECT_EQ(1u, accounts.size()); + } +} + +TEST_F(MutableProfileOAuth2TokenServiceDelegateTest, + GaiaIdMigrationCrashInTheMiddle) { + InitializeOAuth2ServiceDelegate(signin::AccountConsistencyMethod::kMirror); + if (account_tracker_service_.GetMigrationState() != + AccountTrackerService::MIGRATION_NOT_STARTED) { + std::string email1 = "foo@gmail.com"; + std::string gaia_id1 = "foo's gaia id"; + std::string email2 = "bar@gmail.com"; + std::string gaia_id2 = "bar's gaia id"; + + pref_service_.SetInteger(prefs::kAccountIdMigrationState, + AccountTrackerService::MIGRATION_NOT_STARTED); + + ListPrefUpdate update(&pref_service_, prefs::kAccountInfo); + update->Clear(); + auto dict = std::make_unique<base::DictionaryValue>(); + dict->SetString("account_id", email1); + dict->SetString("email", email1); + dict->SetString("gaia", gaia_id1); + update->Append(std::move(dict)); + dict = std::make_unique<base::DictionaryValue>(); + dict->SetString("account_id", email2); + dict->SetString("email", email2); + dict->SetString("gaia", gaia_id2); + update->Append(std::move(dict)); + account_tracker_service_.Shutdown(); + account_tracker_service_.Initialize(&pref_service_, base::FilePath()); + + AddAuthTokenManually("AccountId-" + email1, "refresh_token"); + AddAuthTokenManually("AccountId-" + email2, "refresh_token"); + AddAuthTokenManually("AccountId-" + gaia_id1, "refresh_token"); + oauth2_service_delegate_->LoadCredentials(gaia_id1); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(1, tokens_loaded_count_); + EXPECT_EQ(2, token_available_count_); + EXPECT_EQ(1, start_batch_changes_); + EXPECT_EQ(1, end_batch_changes_); + + std::vector<std::string> accounts = oauth2_service_delegate_->GetAccounts(); + EXPECT_EQ(2u, accounts.size()); + + EXPECT_FALSE(oauth2_service_delegate_->RefreshTokenIsAvailable(email1)); + EXPECT_TRUE(oauth2_service_delegate_->RefreshTokenIsAvailable(gaia_id1)); + EXPECT_FALSE(oauth2_service_delegate_->RefreshTokenIsAvailable(email2)); + EXPECT_TRUE(oauth2_service_delegate_->RefreshTokenIsAvailable(gaia_id2)); + + account_tracker_service_.SetMigrationDone(); + oauth2_service_delegate_->Shutdown(); + ResetObserverCounts(); + + oauth2_service_delegate_->LoadCredentials(gaia_id1); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(1, tokens_loaded_count_); + EXPECT_EQ(2, token_available_count_); + EXPECT_EQ(1, start_batch_changes_); + EXPECT_EQ(1, end_batch_changes_); + + EXPECT_FALSE(oauth2_service_delegate_->RefreshTokenIsAvailable(email1)); + EXPECT_TRUE(oauth2_service_delegate_->RefreshTokenIsAvailable(gaia_id1)); + EXPECT_FALSE(oauth2_service_delegate_->RefreshTokenIsAvailable(email2)); + EXPECT_TRUE(oauth2_service_delegate_->RefreshTokenIsAvailable(gaia_id2)); + accounts = oauth2_service_delegate_->GetAccounts(); + EXPECT_EQ(2u, accounts.size()); + } +} + +TEST_F(MutableProfileOAuth2TokenServiceDelegateTest, + LoadPrimaryAccountOnlyWhenAccountConsistencyDisabled) { + InitializeOAuth2ServiceDelegate(signin::AccountConsistencyMethod::kDisabled); + std::string primary_account = "primaryaccount"; + std::string secondary_account = "secondaryaccount"; + + oauth2_service_delegate_->RevokeAllCredentials(); + ResetObserverCounts(); + AddAuthTokenManually("AccountId-" + primary_account, "refresh_token"); + AddAuthTokenManually("AccountId-" + secondary_account, "refresh_token"); + oauth2_service_delegate_->LoadCredentials(primary_account); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(1, tokens_loaded_count_); + EXPECT_EQ(1, token_available_count_); + EXPECT_EQ(1, token_revoked_count_); + EXPECT_EQ(1, start_batch_changes_); + EXPECT_EQ(1, end_batch_changes_); + EXPECT_TRUE( + oauth2_service_delegate_->RefreshTokenIsAvailable(primary_account)); + EXPECT_FALSE( + oauth2_service_delegate_->RefreshTokenIsAvailable(secondary_account)); +} + +TEST_F(MutableProfileOAuth2TokenServiceDelegateTest, + LoadSecondaryAccountsWhenMirrorEnabled) { + InitializeOAuth2ServiceDelegate(signin::AccountConsistencyMethod::kMirror); + std::string primary_account = "primaryaccount"; + std::string secondary_account = "secondaryaccount"; + + oauth2_service_delegate_->RevokeAllCredentials(); + ResetObserverCounts(); + AddAuthTokenManually("AccountId-" + primary_account, "refresh_token"); + AddAuthTokenManually("AccountId-" + secondary_account, "refresh_token"); + oauth2_service_delegate_->LoadCredentials(primary_account); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(1, tokens_loaded_count_); + EXPECT_EQ(2, token_available_count_); + EXPECT_EQ(0, token_revoked_count_); + EXPECT_EQ(1, start_batch_changes_); + EXPECT_EQ(1, end_batch_changes_); + EXPECT_TRUE( + oauth2_service_delegate_->RefreshTokenIsAvailable(primary_account)); + EXPECT_TRUE( + oauth2_service_delegate_->RefreshTokenIsAvailable(secondary_account)); +} + +// Regression test for https://crbug.com/823707 +// Checks that OnAuthErrorChanged() is called during UpdateCredentials(), and +// that RefreshTokenIsAvailable() can be used at this time. +TEST_F(MutableProfileOAuth2TokenServiceDelegateTest, OnAuthErrorChanged) { + class TokenServiceErrorObserver : public OAuth2TokenService::Observer { + public: + explicit TokenServiceErrorObserver( + MutableProfileOAuth2TokenServiceDelegate* delegate) + : delegate_(delegate) {} + + void OnAuthErrorChanged(const std::string& account_id, + const GoogleServiceAuthError& auth_error) override { + error_changed_ = true; + EXPECT_EQ("account_id", account_id); + EXPECT_EQ(GoogleServiceAuthError::AuthErrorNone(), auth_error); + EXPECT_TRUE(delegate_->RefreshTokenIsAvailable("account_id")); + EXPECT_EQ(GoogleServiceAuthError::AuthErrorNone(), + delegate_->GetAuthError("account_id")); + } + + MutableProfileOAuth2TokenServiceDelegate* delegate_; + bool error_changed_ = false; + + DISALLOW_COPY_AND_ASSIGN(TokenServiceErrorObserver); + }; + + InitializeOAuth2ServiceDelegate(signin::AccountConsistencyMethod::kDisabled); + + // Start with the SigninErrorController in error state, so that it calls + // OnErrorChanged() from AddProvider(). + oauth2_service_delegate_->UpdateCredentials( + "error_account_id", + MutableProfileOAuth2TokenServiceDelegate::kInvalidRefreshToken); + + TokenServiceErrorObserver token_service_observer( + oauth2_service_delegate_.get()); + oauth2_service_delegate_->AddObserver(&token_service_observer); + + ASSERT_FALSE(token_service_observer.error_changed_); + oauth2_service_delegate_->UpdateCredentials("account_id", "token"); + EXPECT_TRUE(token_service_observer.error_changed_); + + oauth2_service_delegate_->RemoveObserver(&token_service_observer); +} + +TEST_F(MutableProfileOAuth2TokenServiceDelegateTest, GetAuthError) { + InitializeOAuth2ServiceDelegate(signin::AccountConsistencyMethod::kDisabled); + // Accounts have no error by default. + oauth2_service_delegate_->UpdateCredentials("account_id", "refresh_token"); + EXPECT_EQ(GoogleServiceAuthError::AuthErrorNone(), + oauth2_service_delegate_->GetAuthError("account_id")); + // Update the error. + GoogleServiceAuthError error = + GoogleServiceAuthError::FromInvalidGaiaCredentialsReason( + GoogleServiceAuthError::InvalidGaiaCredentialsReason:: + CREDENTIALS_REJECTED_BY_SERVER); + oauth2_service_delegate_->UpdateAuthError("account_id", error); + EXPECT_EQ(error, oauth2_service_delegate_->GetAuthError("account_id")); + // Unknown account has no error. + EXPECT_EQ(GoogleServiceAuthError::AuthErrorNone(), + oauth2_service_delegate_->GetAuthError("foo")); + // Add account with invalid token. + oauth2_service_delegate_->UpdateCredentials( + "account_id_2", + MutableProfileOAuth2TokenServiceDelegate::kInvalidRefreshToken); + EXPECT_EQ(GoogleServiceAuthError::FromInvalidGaiaCredentialsReason( + GoogleServiceAuthError::InvalidGaiaCredentialsReason:: + CREDENTIALS_REJECTED_BY_CLIENT), + oauth2_service_delegate_->GetAuthError("account_id_2")); +} + +// Checks that OnAuthErrorChanged() is called before OnRefreshTokenAvailable, +// and that the error state is correctly available from within both calls. +// Regression test for https://crbug.com/824791. +TEST_F(MutableProfileOAuth2TokenServiceDelegateTest, + InvalidTokenObserverCallsOrdering) { + class TokenServiceErrorObserver : public OAuth2TokenService::Observer { + public: + explicit TokenServiceErrorObserver( + MutableProfileOAuth2TokenServiceDelegate* delegate) + : delegate_(delegate) {} + + void OnAuthErrorChanged(const std::string& account_id, + const GoogleServiceAuthError& auth_error) override { + error_changed_ = true; + EXPECT_FALSE(token_available_) + << "OnAuthErrorChanged() should be called first"; + EXPECT_EQ(auth_error, delegate_->GetAuthError(account_id)); + CheckTokenState(account_id); + } + + void OnRefreshTokenAvailable(const std::string& account_id) override { + token_available_ = true; + EXPECT_TRUE(error_changed_) + << "OnAuthErrorChanged() should be called first"; + CheckTokenState(account_id); + } + + void CheckTokenState(const std::string& account_id) { + EXPECT_EQ("account_id", account_id); + EXPECT_TRUE(delegate_->RefreshTokenIsAvailable("account_id")); + EXPECT_EQ(GoogleServiceAuthError::FromInvalidGaiaCredentialsReason( + GoogleServiceAuthError::InvalidGaiaCredentialsReason:: + CREDENTIALS_REJECTED_BY_CLIENT), + delegate_->GetAuthError("account_id")); + } + + MutableProfileOAuth2TokenServiceDelegate* delegate_; + bool error_changed_ = false; + bool token_available_ = false; + + DISALLOW_COPY_AND_ASSIGN(TokenServiceErrorObserver); + }; + + InitializeOAuth2ServiceDelegate(signin::AccountConsistencyMethod::kDisabled); + TokenServiceErrorObserver token_service_observer( + oauth2_service_delegate_.get()); + oauth2_service_delegate_->AddObserver(&token_service_observer); + oauth2_service_delegate_->UpdateCredentials( + "account_id", + MutableProfileOAuth2TokenServiceDelegate::kInvalidRefreshToken); + EXPECT_TRUE(token_service_observer.token_available_); + EXPECT_TRUE(token_service_observer.error_changed_); + oauth2_service_delegate_->RemoveObserver(&token_service_observer); +} + +// Checks that set_revoke_all_tokens_on_first_load() revokes the tokens, +// updates the database, and is applied only once. +TEST_F(MutableProfileOAuth2TokenServiceDelegateTest, ClearTokensOnStartup) { + client_->SetNetworkCallsDelayed(true); + revoke_all_tokens_on_load_ = true; + InitializeOAuth2ServiceDelegate(signin::AccountConsistencyMethod::kDisabled); + std::string primary_account = "primaryaccount"; + std::string secondary_account = "secondaryaccount"; + + oauth2_service_delegate_->RevokeAllCredentials(); + ResetObserverCounts(); + AddAuthTokenManually("AccountId-" + primary_account, "refresh_token"); + AddAuthTokenManually("AccountId-" + secondary_account, "refresh_token"); + oauth2_service_delegate_->LoadCredentials(primary_account); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(1, tokens_loaded_count_); + EXPECT_EQ(1, token_available_count_); + EXPECT_EQ(1, token_revoked_count_); + EXPECT_EQ(1, start_batch_changes_); + EXPECT_EQ(1, end_batch_changes_); + EXPECT_TRUE( + oauth2_service_delegate_->RefreshTokenIsAvailable(primary_account)); + EXPECT_FALSE( + oauth2_service_delegate_->RefreshTokenIsAvailable(secondary_account)); + EXPECT_STREQ( + MutableProfileOAuth2TokenServiceDelegate::kInvalidRefreshToken, + oauth2_service_delegate_->GetRefreshToken(primary_account).c_str()); + EXPECT_EQ(GoogleServiceAuthError::FromInvalidGaiaCredentialsReason( + GoogleServiceAuthError::InvalidGaiaCredentialsReason:: + CREDENTIALS_REJECTED_BY_CLIENT), + oauth2_service_delegate_->GetAuthError(primary_account)); + + // Tokens are revoked on the server. + EXPECT_EQ(2u, oauth2_service_delegate_->server_revokes_.size()); + client_->SetNetworkCallsDelayed(false); + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(oauth2_service_delegate_->server_revokes_.empty()); + + // Check that the changes have been persisted in the database: tokens are not + // revoked again on the server. + client_->SetNetworkCallsDelayed(true); + oauth2_service_delegate_->LoadCredentials(primary_account); + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE( + oauth2_service_delegate_->RefreshTokenIsAvailable(primary_account)); + EXPECT_FALSE( + oauth2_service_delegate_->RefreshTokenIsAvailable(secondary_account)); + EXPECT_STREQ( + MutableProfileOAuth2TokenServiceDelegate::kInvalidRefreshToken, + oauth2_service_delegate_->GetRefreshToken(primary_account).c_str()); + EXPECT_TRUE(oauth2_service_delegate_->server_revokes_.empty()); +} + +// Tests that ProfileOAuthTokenService refresh token operations correctly pass +// the source when used with a |MutableProfileOAuth2TokenServiceDelegate| +// delegate. +TEST_F(MutableProfileOAuth2TokenServiceDelegateTest, + SourceForRefreshTokenOperations) { + using Source = signin_metrics::SourceForRefreshTokenOperation; + + ProfileOAuth2TokenService::RegisterProfilePrefs(pref_service_.registry()); + ProfileOAuth2TokenService token_service( + &pref_service_, + CreateOAuth2ServiceDelegate(signin::AccountConsistencyMethod::kDisabled)); + token_service.AddDiagnosticsObserver(this); + + { + base::HistogramTester h_tester; + AddAuthTokenManually("account_id", "refresh_token"); + token_service.LoadCredentials("account_id"); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ("TokenService::LoadCredentials", + source_for_refresh_token_available_); + h_tester.ExpectUniqueSample( + "Signin.RefreshTokenUpdated.ToValidToken.Source", + Source::kTokenService_LoadCredentials, 1); + } + + { + base::HistogramTester h_tester; + token_service.UpdateCredentials("account_id", "refresh_token", + Source::kSupervisedUser_InitSync); + EXPECT_EQ("SupervisedUser::InitSync", source_for_refresh_token_available_); + h_tester.ExpectUniqueSample( + "Signin.RefreshTokenUpdated.ToValidToken.Source", + Source::kSupervisedUser_InitSync, 1); + + token_service.RevokeCredentials( + "account_id", Source::kAccountReconcilor_GaiaCookiesUpdated); + EXPECT_EQ("AccountReconcilor::GaiaCookiesUpdated", + source_for_refresh_token_revoked_); + h_tester.ExpectUniqueSample("Signin.RefreshTokenRevoked.Source", + Source::kAccountReconcilor_GaiaCookiesUpdated, + 1); + base::RunLoop().RunUntilIdle(); + } + + { + base::HistogramTester h_tester; + token_service.UpdateCredentials("account_id_1", "refresh_token", + Source::kDiceResponseHandler_Signin); + EXPECT_EQ("DiceResponseHandler::Signin", + source_for_refresh_token_available_); + h_tester.ExpectUniqueSample( + "Signin.RefreshTokenUpdated.ToValidToken.Source", + Source::kDiceResponseHandler_Signin, 1); + + token_service.UpdateCredentials( + "account_id_2", OAuth2TokenServiceDelegate::kInvalidRefreshToken, + Source::kDiceResponseHandler_Signin); + EXPECT_EQ("DiceResponseHandler::Signin", + source_for_refresh_token_available_); + h_tester.ExpectUniqueSample( + "Signin.RefreshTokenUpdated.ToInvalidToken.Source", + Source::kDiceResponseHandler_Signin, 1); + + token_service.RevokeAllCredentials(Source::kDiceResponseHandler_Signout); + EXPECT_EQ("DiceResponseHandler::Signout", + source_for_refresh_token_revoked_); + h_tester.ExpectUniqueSample("Signin.RefreshTokenRevoked.Source", + Source::kDiceResponseHandler_Signout, 2); + base::RunLoop().RunUntilIdle(); + } + + token_service.RemoveDiagnosticsObserver(this); + token_service.Shutdown(); +} + +TEST_F(MutableProfileOAuth2TokenServiceDelegateTest, ExtractCredentials) { + InitializeOAuth2ServiceDelegate(signin::AccountConsistencyMethod::kDice); + oauth2_service_delegate_->LoadCredentials(std::string()); + + // Create another token service + sync_preferences::TestingPrefServiceSyncable prefs; + ProfileOAuth2TokenService::RegisterProfilePrefs(prefs.registry()); + std::unique_ptr<FakeOAuth2TokenServiceDelegate> delegate = + std::make_unique<FakeOAuth2TokenServiceDelegate>(); + FakeOAuth2TokenServiceDelegate* other_delegate = delegate.get(); + ProfileOAuth2TokenService other_token_service(&prefs, std::move(delegate)); + other_token_service.LoadCredentials(std::string()); + + // Add credentials to the first token service delegate. + oauth2_service_delegate_->UpdateCredentials("account_id", "token"); + + // Extract the credentials. + ResetObserverCounts(); + oauth2_service_delegate_->ExtractCredentials(&other_token_service, + "account_id"); + + EXPECT_EQ(1, token_revoked_count_); + EXPECT_TRUE(oauth2_service_delegate_->server_revokes_.empty()); + EXPECT_FALSE(oauth2_service_delegate_->RefreshTokenIsAvailable("account_id")); + EXPECT_TRUE(other_delegate->RefreshTokenIsAvailable("account_id")); + EXPECT_EQ("token", other_delegate->GetRefreshToken("account_id")); +} diff --git a/chromium/components/signin/core/browser/oauth2_token_service_delegate_android.cc b/chromium/components/signin/core/browser/oauth2_token_service_delegate_android.cc new file mode 100644 index 00000000000..0d872ed9d62 --- /dev/null +++ b/chromium/components/signin/core/browser/oauth2_token_service_delegate_android.cc @@ -0,0 +1,529 @@ +// Copyright 2013 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 "components/signin/core/browser/oauth2_token_service_delegate_android.h" + +#include "base/android/jni_android.h" +#include "base/android/jni_array.h" +#include "base/android/jni_string.h" +#include "base/bind.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/metrics/histogram_macros.h" +#include "base/stl_util.h" +#include "components/signin/core/browser/account_info.h" +#include "google_apis/gaia/gaia_auth_util.h" +#include "google_apis/gaia/oauth2_access_token_fetcher.h" +#include "jni/OAuth2TokenService_jni.h" + +using base::android::AttachCurrentThread; +using base::android::ConvertJavaStringToUTF8; +using base::android::ConvertUTF8ToJavaString; +using base::android::JavaParamRef; +using base::android::JavaRef; +using base::android::ScopedJavaLocalRef; + +namespace { + +// Callback from FetchOAuth2TokenWithUsername(). +// Arguments: +// - the error, or NONE if the token fetch was successful. +// - the OAuth2 access token. +// - the expiry time of the token (may be null, indicating that the expiry +// time is unknown. +typedef base::Callback< + void(const GoogleServiceAuthError&, const std::string&, const base::Time&)> + FetchOAuth2TokenCallback; + +class AndroidAccessTokenFetcher : public OAuth2AccessTokenFetcher { + public: + AndroidAccessTokenFetcher(OAuth2AccessTokenConsumer* consumer, + const std::string& account_id); + ~AndroidAccessTokenFetcher() override; + + // Overrides from OAuth2AccessTokenFetcher: + void Start(const std::string& client_id, + const std::string& client_secret, + const std::vector<std::string>& scopes) override; + void CancelRequest() override; + + // Handles an access token response. + void OnAccessTokenResponse(const GoogleServiceAuthError& error, + const std::string& access_token, + const base::Time& expiration_time); + + private: + std::string CombineScopes(const std::vector<std::string>& scopes); + + std::string account_id_; + bool request_was_cancelled_; + base::WeakPtrFactory<AndroidAccessTokenFetcher> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(AndroidAccessTokenFetcher); +}; + +AndroidAccessTokenFetcher::AndroidAccessTokenFetcher( + OAuth2AccessTokenConsumer* consumer, + const std::string& account_id) + : OAuth2AccessTokenFetcher(consumer), + account_id_(account_id), + request_was_cancelled_(false), + weak_factory_(this) {} + +AndroidAccessTokenFetcher::~AndroidAccessTokenFetcher() {} + +void AndroidAccessTokenFetcher::Start(const std::string& client_id, + const std::string& client_secret, + const std::vector<std::string>& scopes) { + JNIEnv* env = AttachCurrentThread(); + std::string scope = CombineScopes(scopes); + ScopedJavaLocalRef<jstring> j_username = + ConvertUTF8ToJavaString(env, account_id_); + ScopedJavaLocalRef<jstring> j_scope = ConvertUTF8ToJavaString(env, scope); + std::unique_ptr<FetchOAuth2TokenCallback> heap_callback( + new FetchOAuth2TokenCallback( + base::Bind(&AndroidAccessTokenFetcher::OnAccessTokenResponse, + weak_factory_.GetWeakPtr()))); + + // Call into Java to get a new token. + Java_OAuth2TokenService_getAccessTokenFromNative( + env, j_username, j_scope, + reinterpret_cast<intptr_t>(heap_callback.release())); +} + +void AndroidAccessTokenFetcher::CancelRequest() { + request_was_cancelled_ = true; +} + +void AndroidAccessTokenFetcher::OnAccessTokenResponse( + const GoogleServiceAuthError& error, + const std::string& access_token, + const base::Time& expiration_time) { + if (request_was_cancelled_) { + // Ignore the callback if the request was cancelled. + return; + } + if (error.state() == GoogleServiceAuthError::NONE) { + FireOnGetTokenSuccess(OAuth2AccessTokenConsumer::TokenResponse( + access_token, expiration_time, std::string())); + } else { + FireOnGetTokenFailure(error); + } +} + +// static +std::string AndroidAccessTokenFetcher::CombineScopes( + const std::vector<std::string>& scopes) { + // The Android AccountManager supports multiple scopes separated by a space: + // https://code.google.com/p/google-api-java-client/wiki/OAuth2#Android + std::string scope; + for (std::vector<std::string>::const_iterator it = scopes.begin(); + it != scopes.end(); ++it) { + if (!scope.empty()) + scope += " "; + scope += *it; + } + return scope; +} + +} // namespace + +bool OAuth2TokenServiceDelegateAndroid::is_testing_profile_ = false; + +OAuth2TokenServiceDelegateAndroid::OAuth2TokenServiceDelegateAndroid( + AccountTrackerService* account_tracker_service) + : account_tracker_service_(account_tracker_service), + fire_refresh_token_loaded_(RT_LOAD_NOT_START) { + DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::ctor"; + DCHECK(account_tracker_service_); + JNIEnv* env = AttachCurrentThread(); + base::android::ScopedJavaLocalRef<jobject> local_java_ref = + Java_OAuth2TokenService_create(env, reinterpret_cast<intptr_t>(this), + account_tracker_service_->GetJavaObject()); + java_ref_.Reset(env, local_java_ref.obj()); + + if (account_tracker_service_->GetMigrationState() == + AccountTrackerService::MIGRATION_IN_PROGRESS) { + std::vector<std::string> accounts = GetAccounts(); + std::vector<std::string> accounts_id; + for (auto account_name : accounts) { + AccountInfo account_info = + account_tracker_service_->FindAccountInfoByEmail(account_name); + DCHECK(!account_info.gaia.empty()); + accounts_id.push_back(account_info.gaia); + } + ScopedJavaLocalRef<jobjectArray> java_accounts( + base::android::ToJavaArrayOfStrings(env, accounts_id)); + Java_OAuth2TokenService_saveStoredAccounts(env, java_accounts); + } + + if (!is_testing_profile_) { + Java_OAuth2TokenService_validateAccounts(AttachCurrentThread(), java_ref_, + JNI_TRUE); + } +} + +OAuth2TokenServiceDelegateAndroid::~OAuth2TokenServiceDelegateAndroid() {} + +ScopedJavaLocalRef<jobject> OAuth2TokenServiceDelegateAndroid::GetJavaObject() { + return ScopedJavaLocalRef<jobject>(java_ref_); +} + +bool OAuth2TokenServiceDelegateAndroid::RefreshTokenIsAvailable( + const std::string& account_id) const { + DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::RefreshTokenIsAvailable" + << " account= " << account_id; + std::string account_name = MapAccountIdToAccountName(account_id); + if (account_name.empty()) { + // This corresponds to the case when the account with id |account_id| is not + // present on the device and thus was not seeded. + DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::RefreshTokenIsAvailable" + << " cannot find account name for account id " << account_id; + return false; + } + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jstring> j_account_id = + ConvertUTF8ToJavaString(env, account_name); + jboolean refresh_token_is_available = + Java_OAuth2TokenService_hasOAuth2RefreshToken(env, j_account_id); + return refresh_token_is_available == JNI_TRUE; +} + +GoogleServiceAuthError OAuth2TokenServiceDelegateAndroid::GetAuthError( + const std::string& account_id) const { + auto it = errors_.find(account_id); + return (it == errors_.end()) ? GoogleServiceAuthError::AuthErrorNone() + : it->second; +} + +void OAuth2TokenServiceDelegateAndroid::UpdateAuthError( + const std::string& account_id, + const GoogleServiceAuthError& error) { + DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::UpdateAuthError" + << " account=" << account_id << " error=" << error.ToString(); + + if (error.IsTransientError()) + return; + + auto it = errors_.find(account_id); + if (error.state() == GoogleServiceAuthError::NONE) { + if (it == errors_.end()) + return; + errors_.erase(it); + } else { + if (it != errors_.end() && it->second == error) + return; + errors_[account_id] = error; + } + FireAuthErrorChanged(account_id, error); +} + +std::vector<std::string> OAuth2TokenServiceDelegateAndroid::GetAccounts() { + std::vector<std::string> accounts; + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jobjectArray> j_accounts = + Java_OAuth2TokenService_getAccounts(env); + // TODO(fgorski): We may decide to filter out some of the accounts. + base::android::AppendJavaStringArrayToStringVector(env, j_accounts, + &accounts); + return accounts; +} + +std::vector<std::string> +OAuth2TokenServiceDelegateAndroid::GetSystemAccountNames() { + std::vector<std::string> account_names; + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jobjectArray> j_accounts = + Java_OAuth2TokenService_getSystemAccountNames(env); + base::android::AppendJavaStringArrayToStringVector(env, j_accounts, + &account_names); + return account_names; +} + +OAuth2AccessTokenFetcher* +OAuth2TokenServiceDelegateAndroid::CreateAccessTokenFetcher( + const std::string& account_id, + scoped_refptr<network::SharedURLLoaderFactory> url_factory, + OAuth2AccessTokenConsumer* consumer) { + DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::CreateAccessTokenFetcher" + << " account= " << account_id; + ValidateAccountId(account_id); + std::string account_name = MapAccountIdToAccountName(account_id); + DCHECK(!account_name.empty()) + << "Cannot find account name for account id " << account_id; + return new AndroidAccessTokenFetcher(consumer, account_name); +} + +void OAuth2TokenServiceDelegateAndroid::InvalidateAccessToken( + const std::string& account_id, + const std::string& client_id, + const OAuth2TokenService::ScopeSet& scopes, + const std::string& access_token) { + ValidateAccountId(account_id); + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jstring> j_access_token = + ConvertUTF8ToJavaString(env, access_token); + Java_OAuth2TokenService_invalidateAccessToken(env, j_access_token); +} + +void OAuth2TokenServiceDelegateAndroid::ValidateAccounts( + JNIEnv* env, + const JavaParamRef<jobject>& obj, + const JavaParamRef<jstring>& j_current_acc, + jboolean j_force_notifications) { + std::string signed_in_account_name; + DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::ValidateAccounts from java"; + if (j_current_acc) + signed_in_account_name = ConvertJavaStringToUTF8(env, j_current_acc); + if (!signed_in_account_name.empty()) + signed_in_account_name = gaia::CanonicalizeEmail(signed_in_account_name); + + // Clear any auth errors so that client can retry to get access tokens. + errors_.clear(); + + ValidateAccounts(MapAccountNameToAccountId(signed_in_account_name), + j_force_notifications != JNI_FALSE); +} + +void OAuth2TokenServiceDelegateAndroid::ValidateAccounts( + const std::string& signed_in_account_id, + bool force_notifications) { + std::vector<std::string> curr_ids; + for (const std::string& curr_name : GetSystemAccountNames()) { + std::string curr_id(MapAccountNameToAccountId(curr_name)); + if (!curr_id.empty()) + curr_ids.push_back(curr_id); + } + + std::vector<std::string> prev_ids; + for (const std::string& prev_id : GetAccounts()) { + if (ValidateAccountId(prev_id)) + prev_ids.push_back(prev_id); + } + + DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::ValidateAccounts:" + << " sigined_in_account_id=" << signed_in_account_id + << " prev_ids=" << prev_ids.size() << " curr_ids=" << curr_ids.size() + << " force=" << (force_notifications ? "true" : "false"); + + std::vector<std::string> refreshed_ids; + std::vector<std::string> revoked_ids; + bool currently_signed_in = + ValidateAccounts(signed_in_account_id, prev_ids, curr_ids, &refreshed_ids, + &revoked_ids, force_notifications); + + ScopedBatchChange batch(this); + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jobjectArray> java_accounts; + if (currently_signed_in) { + java_accounts = base::android::ToJavaArrayOfStrings(env, curr_ids); + } else { + java_accounts = + base::android::ToJavaArrayOfStrings(env, std::vector<std::string>()); + } + + // Save the current accounts in the token service before calling + // FireRefreshToken* methods. + Java_OAuth2TokenService_saveStoredAccounts(env, java_accounts); + + for (const std::string& refreshed_id : refreshed_ids) + FireRefreshTokenAvailable(refreshed_id); + for (const std::string& revoked_id : revoked_ids) + FireRefreshTokenRevoked(revoked_id); + if (fire_refresh_token_loaded_ == RT_WAIT_FOR_VALIDATION) { + fire_refresh_token_loaded_ = RT_LOADED; + FireRefreshTokensLoaded(); + } else if (fire_refresh_token_loaded_ == RT_LOAD_NOT_START) { + fire_refresh_token_loaded_ = RT_HAS_BEEN_VALIDATED; + } + + // Clear accounts no longer exist on device from AccountTrackerService. + std::vector<AccountInfo> accounts_info = + account_tracker_service_->GetAccounts(); + for (const AccountInfo& info : accounts_info) { + if (!base::ContainsValue(curr_ids, info.account_id)) + account_tracker_service_->RemoveAccount(info.account_id); + } + + // No need to wait for SigninManager to finish migration if not signed in. + if (account_tracker_service_->GetMigrationState() == + AccountTrackerService::MIGRATION_IN_PROGRESS && + signed_in_account_id.empty()) { + account_tracker_service_->SetMigrationDone(); + } +} + +bool OAuth2TokenServiceDelegateAndroid::ValidateAccounts( + const std::string& signed_in_id, + const std::vector<std::string>& prev_ids, + const std::vector<std::string>& curr_ids, + std::vector<std::string>* refreshed_ids, + std::vector<std::string>* revoked_ids, + bool force_notifications) { + bool currently_signed_in = base::ContainsValue(curr_ids, signed_in_id); + if (currently_signed_in) { + // Revoke token for ids that have been removed from the device. + for (const std::string& prev_id : prev_ids) { + if (prev_id == signed_in_id) + continue; + if (!base::ContainsValue(curr_ids, prev_id)) { + DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::ValidateAccounts:" + << "revoked=" << prev_id; + revoked_ids->push_back(prev_id); + } + } + + // Refresh token for new ids or all ids if |force_notifications|. + if (force_notifications || !base::ContainsValue(prev_ids, signed_in_id)) { + // Always fire the primary signed in account first. + DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::ValidateAccounts:" + << "refreshed=" << signed_in_id; + refreshed_ids->push_back(signed_in_id); + } + for (const std::string& curr_id : curr_ids) { + if (curr_id == signed_in_id) + continue; + if (force_notifications || !base::ContainsValue(prev_ids, curr_id)) { + DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::ValidateAccounts:" + << "refreshed=" << curr_id; + refreshed_ids->push_back(curr_id); + } + } + } else { + if (base::ContainsValue(prev_ids, signed_in_id)) { + DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::ValidateAccounts:" + << "revoked=" << signed_in_id; + revoked_ids->push_back(signed_in_id); + } + for (const std::string& prev_id : prev_ids) { + if (prev_id == signed_in_id) + continue; + DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::ValidateAccounts:" + << "revoked=" << prev_id; + revoked_ids->push_back(prev_id); + } + } + return currently_signed_in; +} + +void OAuth2TokenServiceDelegateAndroid::FireRefreshTokenAvailable( + const std::string& account_id) { + DCHECK(!account_id.empty()); + DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::FireRefreshTokenAvailable id=" + << account_id; + std::string account_name = MapAccountIdToAccountName(account_id); + DCHECK(!account_name.empty()) + << "Cannot find account name for account id " << account_id; + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jstring> j_account_name = + ConvertUTF8ToJavaString(env, account_name); + Java_OAuth2TokenService_notifyRefreshTokenAvailable(env, java_ref_, + j_account_name); + OAuth2TokenServiceDelegate::FireRefreshTokenAvailable(account_id); +} + +void OAuth2TokenServiceDelegateAndroid::FireRefreshTokenRevoked( + const std::string& account_id) { + DCHECK(!account_id.empty()); + DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::FireRefreshTokenRevoked id=" + << account_id; + std::string account_name = MapAccountIdToAccountName(account_id); + if (!account_name.empty()) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jstring> j_account_name = + ConvertUTF8ToJavaString(env, account_name); + Java_OAuth2TokenService_notifyRefreshTokenRevoked(env, java_ref_, + j_account_name); + } else { + // Current prognosis is that we have an unmigrated account which is due for + // deletion. Record a histogram to debug this. + UMA_HISTOGRAM_ENUMERATION("OAuth2Login.AccountRevoked.MigrationState", + account_tracker_service_->GetMigrationState(), + AccountTrackerService::NUM_MIGRATION_STATES); + bool is_email_id = account_id.find('@') != std::string::npos; + UMA_HISTOGRAM_BOOLEAN("OAuth2Login.AccountRevoked.IsEmailId", is_email_id); + } + OAuth2TokenServiceDelegate::FireRefreshTokenRevoked(account_id); +} + +void OAuth2TokenServiceDelegateAndroid::FireRefreshTokensLoaded() { + DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::FireRefreshTokensLoaded"; + + DCHECK_EQ(LOAD_CREDENTIALS_IN_PROGRESS, load_credentials_state()); + set_load_credentials_state(LOAD_CREDENTIALS_FINISHED_WITH_SUCCESS); + + JNIEnv* env = AttachCurrentThread(); + Java_OAuth2TokenService_notifyRefreshTokensLoaded(env, java_ref_); + OAuth2TokenServiceDelegate::FireRefreshTokensLoaded(); +} + +void OAuth2TokenServiceDelegateAndroid::RevokeAllCredentials() { + DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::RevokeAllCredentials"; + ScopedBatchChange batch(this); + std::vector<std::string> accounts_to_revoke = GetAccounts(); + + // Clear accounts in the token service before calling + // |FireRefreshTokenRevoked|. + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jobjectArray> java_accounts( + base::android::ToJavaArrayOfStrings(env, std::vector<std::string>())); + Java_OAuth2TokenService_saveStoredAccounts(env, java_accounts); + + for (const std::string& account : accounts_to_revoke) + FireRefreshTokenRevoked(account); +} + +void OAuth2TokenServiceDelegateAndroid::LoadCredentials( + const std::string& primary_account_id) { + DCHECK_EQ(LOAD_CREDENTIALS_NOT_STARTED, load_credentials_state()); + set_load_credentials_state(LOAD_CREDENTIALS_IN_PROGRESS); + if (primary_account_id.empty()) { + FireRefreshTokensLoaded(); + return; + } + if (fire_refresh_token_loaded_ == RT_HAS_BEEN_VALIDATED) { + fire_refresh_token_loaded_ = RT_LOADED; + FireRefreshTokensLoaded(); + } else if (fire_refresh_token_loaded_ == RT_LOAD_NOT_START) { + fire_refresh_token_loaded_ = RT_WAIT_FOR_VALIDATION; + } +} + +std::string OAuth2TokenServiceDelegateAndroid::MapAccountIdToAccountName( + const std::string& account_id) const { + return account_tracker_service_->GetAccountInfo(account_id).email; +} + +std::string OAuth2TokenServiceDelegateAndroid::MapAccountNameToAccountId( + const std::string& account_name) const { + std::string account_id = + account_tracker_service_->FindAccountInfoByEmail(account_name).account_id; + DCHECK(!account_id.empty() || account_name.empty()) + << "Can't find account id, account_name=" << account_name; + return account_id; +} + +// Called from Java when fetching of an OAuth2 token is finished. The +// |authToken| param is only valid when |result| is true. +void JNI_OAuth2TokenService_OAuth2TokenFetched( + JNIEnv* env, + const JavaParamRef<jstring>& authToken, + jboolean isTransientError, + jlong nativeCallback) { + std::string token; + if (authToken) + token = ConvertJavaStringToUTF8(env, authToken); + std::unique_ptr<FetchOAuth2TokenCallback> heap_callback( + reinterpret_cast<FetchOAuth2TokenCallback*>(nativeCallback)); + GoogleServiceAuthError err = GoogleServiceAuthError::AuthErrorNone(); + if (!authToken) { + err = + isTransientError + ? GoogleServiceAuthError(GoogleServiceAuthError::CONNECTION_FAILED) + : GoogleServiceAuthError::FromInvalidGaiaCredentialsReason( + GoogleServiceAuthError::InvalidGaiaCredentialsReason:: + CREDENTIALS_REJECTED_BY_SERVER); + } + heap_callback->Run(err, token, base::Time()); +} diff --git a/chromium/components/signin/core/browser/oauth2_token_service_delegate_android.h b/chromium/components/signin/core/browser/oauth2_token_service_delegate_android.h new file mode 100644 index 00000000000..9ecb1dbbdd3 --- /dev/null +++ b/chromium/components/signin/core/browser/oauth2_token_service_delegate_android.h @@ -0,0 +1,132 @@ +// Copyright 2013 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. + +#ifndef COMPONENTS_SIGNIN_CORE_BROWSER_OAUTH2_TOKEN_SERVICE_DELEGATE_ANDROID_H_ +#define COMPONENTS_SIGNIN_CORE_BROWSER_OAUTH2_TOKEN_SERVICE_DELEGATE_ANDROID_H_ + +#include <map> +#include <memory> +#include <string> + +#include "base/android/jni_weak_ref.h" +#include "base/android/scoped_java_ref.h" +#include "base/callback.h" +#include "base/macros.h" +#include "base/time/time.h" +#include "components/signin/core/browser/account_tracker_service.h" +#include "components/signin/core/browser/profile_oauth2_token_service.h" +#include "google_apis/gaia/google_service_auth_error.h" +#include "google_apis/gaia/oauth2_token_service_delegate.h" + +// A specialization of OAuth2TokenServiceDelegate that will be returned by +// OAuth2TokenServiceDelegateFactory for OS_ANDROID. This instance uses +// native Android features to lookup OAuth2 tokens. +// +// See |OAuth2TokenServiceDelegate| for usage details. +// +// Note: requests should be started from the UI thread. To start a +// request from other thread, please use OAuth2TokenServiceRequest. +class OAuth2TokenServiceDelegateAndroid : public OAuth2TokenServiceDelegate { + public: + OAuth2TokenServiceDelegateAndroid( + AccountTrackerService* account_tracker_service); + ~OAuth2TokenServiceDelegateAndroid() override; + + // Creates a new instance of the OAuth2TokenServiceDelegateAndroid. + static OAuth2TokenServiceDelegateAndroid* Create(); + + // Returns a reference to the corresponding Java OAuth2TokenService object. + base::android::ScopedJavaLocalRef<jobject> GetJavaObject(); + + // Called by the TestingProfile class to disable account validation in + // tests. This prevents the token service from trying to look up system + // accounts which requires special permission. + static void set_is_testing_profile() { is_testing_profile_ = true; } + + // OAuth2TokenServiceDelegate overrides: + bool RefreshTokenIsAvailable(const std::string& account_id) const override; + GoogleServiceAuthError GetAuthError( + const std::string& account_id) const override; + void UpdateAuthError(const std::string& account_id, + const GoogleServiceAuthError& error) override; + std::vector<std::string> GetAccounts() override; + + // Lists account names at the OS level. + std::vector<std::string> GetSystemAccountNames(); + + void ValidateAccounts( + JNIEnv* env, + const base::android::JavaParamRef<jobject>& obj, + const base::android::JavaParamRef<jstring>& current_account, + jboolean force_notifications); + + // Takes a the signed in sync account as well as all the other + // android account ids and check the token status of each. If + // |force_notifications| is true, TokenAvailable notifications will + // be sent anyway, even if the account was already known. + void ValidateAccounts(const std::string& signed_in_account_id, + bool force_notifications); + + // Overridden from OAuth2TokenService to complete signout of all + // OA2TService aware accounts. + void RevokeAllCredentials() override; + + void LoadCredentials(const std::string& primary_account_id) override; + + protected: + OAuth2AccessTokenFetcher* CreateAccessTokenFetcher( + const std::string& account_id, + scoped_refptr<network::SharedURLLoaderFactory> url_factory, + OAuth2AccessTokenConsumer* consumer) override; + + // Overridden from OAuth2TokenService to intercept token fetch requests and + // redirect them to the Account Manager. + void InvalidateAccessToken(const std::string& account_id, + const std::string& client_id, + const OAuth2TokenService::ScopeSet& scopes, + const std::string& access_token) override; + + // Called to notify observers when a refresh token is available. + void FireRefreshTokenAvailable(const std::string& account_id) override; + // Called to notify observers when a refresh token has been revoked. + void FireRefreshTokenRevoked(const std::string& account_id) override; + // Called to notify observers when refresh tokans have been loaded. + void FireRefreshTokensLoaded() override; + + private: + std::string MapAccountIdToAccountName(const std::string& account_id) const; + std::string MapAccountNameToAccountId(const std::string& account_name) const; + + enum RefreshTokenLoadStatus { + RT_LOAD_NOT_START, + RT_WAIT_FOR_VALIDATION, + RT_HAS_BEEN_VALIDATED, + RT_LOADED + }; + + // Return whether |signed_in_id| is valid and we have access + // to all the tokens in |curr_ids|. If |force_notifications| is true, + // TokenAvailable notifications will be sent anyway, even if the account was + // already known. + bool ValidateAccounts(const std::string& signed_in_id, + const std::vector<std::string>& prev_ids, + const std::vector<std::string>& curr_ids, + std::vector<std::string>* refreshed_ids, + std::vector<std::string>* revoked_ids, + bool force_notifications); + + base::android::ScopedJavaGlobalRef<jobject> java_ref_; + + // Maps account_id to the last error for that account. + std::map<std::string, GoogleServiceAuthError> errors_; + + AccountTrackerService* account_tracker_service_; + RefreshTokenLoadStatus fire_refresh_token_loaded_; + + static bool is_testing_profile_; + + DISALLOW_COPY_AND_ASSIGN(OAuth2TokenServiceDelegateAndroid); +}; + +#endif // CHROME_BROWSER_SIGNIN_OAUTH2_TOKEN_SERVICE_DELEGATE_ANDROID_H_ diff --git a/chromium/components/signin/core/browser/profile_oauth2_token_service.cc b/chromium/components/signin/core/browser/profile_oauth2_token_service.cc index 2384e3367ce..50fb83dd5ce 100644 --- a/chromium/components/signin/core/browser/profile_oauth2_token_service.cc +++ b/chromium/components/signin/core/browser/profile_oauth2_token_service.cc @@ -49,6 +49,10 @@ std::string SourceToString(SourceForRefreshTokenOperation source) { return "DiceResponseHandler::Signout"; case SourceForRefreshTokenOperation::kDiceTurnOnSyncHelper_Abort: return "DiceTurnOnSyncHelper::Abort"; + case SourceForRefreshTokenOperation::kMachineLogon_CredentialProvider: + return "MachineLogon::CredentialProvider"; + case SourceForRefreshTokenOperation::kTokenService_ExtractCredentials: + return "TokenService::ExtractCredentials"; } } } // namespace @@ -127,6 +131,17 @@ const net::BackoffEntry* ProfileOAuth2TokenService::GetDelegateBackoffEntry() { return GetDelegate()->BackoffEntry(); } +#if BUILDFLAG(ENABLE_DICE_SUPPORT) +void ProfileOAuth2TokenService::ExtractCredentials( + ProfileOAuth2TokenService* to_service, + const std::string& account_id) { + base::AutoReset<SourceForRefreshTokenOperation> auto_reset( + &update_refresh_token_source_, + SourceForRefreshTokenOperation::kTokenService_ExtractCredentials); + GetDelegate()->ExtractCredentials(to_service, account_id); +} +#endif + void ProfileOAuth2TokenService::OnRefreshTokenAvailable( const std::string& account_id) { // Check if the newly-updated token is valid (invalid tokens are inserted when diff --git a/chromium/components/signin/core/browser/profile_oauth2_token_service.h b/chromium/components/signin/core/browser/profile_oauth2_token_service.h index 01866935923..c7c4ae65f87 100644 --- a/chromium/components/signin/core/browser/profile_oauth2_token_service.h +++ b/chromium/components/signin/core/browser/profile_oauth2_token_service.h @@ -8,8 +8,9 @@ #include <string> #include "base/macros.h" -#include "base/memory/linked_ptr.h" +#include "build/buildflag.h" #include "components/keyed_service/core/keyed_service.h" +#include "components/signin/core/browser/signin_buildflags.h" #include "components/signin/core/browser/signin_metrics.h" #include "google_apis/gaia/oauth2_token_service.h" #include "google_apis/gaia/oauth2_token_service_delegate.h" @@ -89,6 +90,15 @@ class ProfileOAuth2TokenService : public OAuth2TokenService, // is no such instance. const net::BackoffEntry* GetDelegateBackoffEntry(); +#if BUILDFLAG(ENABLE_DICE_SUPPORT) + // Removes the credentials associated to account_id from the internal storage, + // and moves them to |to_service|. The credentials are not revoked on the + // server, but the OnRefreshTokenRevoked() notification is sent to the + // observers. + void ExtractCredentials(ProfileOAuth2TokenService* to_service, + const std::string& account_id); +#endif + void set_all_credentials_loaded_for_testing(bool loaded) { all_credentials_loaded_ = loaded; } @@ -96,6 +106,7 @@ class ProfileOAuth2TokenService : public OAuth2TokenService, private: friend class identity::IdentityManager; + // OAuth2TokenService::Observer implementation. void OnRefreshTokenAvailable(const std::string& account_id) override; void OnRefreshTokenRevoked(const std::string& account_id) override; void OnRefreshTokensLoaded() override; diff --git a/chromium/components/signin/core/browser/signin_error_controller.cc b/chromium/components/signin/core/browser/signin_error_controller.cc index 305228d7b25..6f1859ea21e 100644 --- a/chromium/components/signin/core/browser/signin_error_controller.cc +++ b/chromium/components/signin/core/browser/signin_error_controller.cc @@ -6,45 +6,26 @@ #include "components/signin/core/browser/signin_metrics.h" -namespace { - -typedef std::set<const SigninErrorController::AuthStatusProvider*> - AuthStatusProviderSet; - -} // namespace - -SigninErrorController::AuthStatusProvider::AuthStatusProvider() { -} - -SigninErrorController::AuthStatusProvider::~AuthStatusProvider() { -} - -SigninErrorController::SigninErrorController(AccountMode mode) +SigninErrorController::SigninErrorController( + AccountMode mode, + identity::IdentityManager* identity_manager) : account_mode_(mode), - auth_error_(GoogleServiceAuthError::AuthErrorNone()) {} + identity_manager_(identity_manager), + scoped_identity_manager_observer_(this), + auth_error_(GoogleServiceAuthError::AuthErrorNone()) { + DCHECK(identity_manager_); + scoped_identity_manager_observer_.Add(identity_manager_); -SigninErrorController::~SigninErrorController() { - DCHECK(provider_set_.empty()) - << "All AuthStatusProviders should be unregistered before " - << "SigninErrorController is destroyed"; + Update(); } -void SigninErrorController::AddProvider(const AuthStatusProvider* provider) { - DCHECK(provider_set_.find(provider) == provider_set_.end()) - << "Adding same AuthStatusProvider multiple times"; - provider_set_.insert(provider); - AuthStatusChanged(); -} +SigninErrorController::~SigninErrorController() = default; -void SigninErrorController::RemoveProvider(const AuthStatusProvider* provider) { - auto iter = provider_set_.find(provider); - DCHECK(iter != provider_set_.end()) - << "Removing provider that was never added"; - provider_set_.erase(iter); - AuthStatusChanged(); +void SigninErrorController::Shutdown() { + scoped_identity_manager_observer_.RemoveAll(); } -void SigninErrorController::AuthStatusChanged() { +void SigninErrorController::Update() { GoogleServiceAuthError::State prev_state = auth_error_.state(); std::string prev_account_id = error_account_id_; bool error_changed = false; @@ -53,25 +34,33 @@ void SigninErrorController::AuthStatusChanged() { // actionable error state and some provider exposes a similar error and // account id, use that error. Otherwise, just take the first actionable // error we find. - for (auto it = provider_set_.begin(); it != provider_set_.end(); ++it) { - std::string account_id = (*it)->GetAccountId(); + for (const AccountInfo& account_info : + identity_manager_->GetAccountsWithRefreshTokens()) { + std::string account_id = account_info.account_id; // In PRIMARY_ACCOUNT mode, ignore all secondary accounts. if (account_mode_ == AccountMode::PRIMARY_ACCOUNT && - (account_id != primary_account_id_)) { + (account_id != identity_manager_->GetPrimaryAccountId())) { continue; } - GoogleServiceAuthError error = (*it)->GetAuthStatus(); - - // Ignore the states we don't want to elevate to the user. - if (error.state() == GoogleServiceAuthError::NONE || - error.IsTransientError()) { + if (!identity_manager_->HasAccountWithRefreshTokenInPersistentErrorState( + account_id)) { continue; } + GoogleServiceAuthError error = + identity_manager_->GetErrorStateOfRefreshTokenForAccount(account_id); + // IdentityManager only reports persistent errors. + DCHECK(error.IsPersistentError()); + // Prioritize this error if it matches the previous |auth_error_|. if (error.state() == prev_state && account_id == prev_account_id) { + // The previous error for the previous account still exists. This error is + // preferred to avoid UI churn, so |auth_error_| and |error_account_id_| + // must be updated to match the previous state. This is needed in case + // |auth_error_| and |error_account_id_| were updated to other values in + // a previous iteration via the if statement below. auth_error_ = error; error_account_id_ = account_id; error_changed = true; @@ -94,22 +83,23 @@ void SigninErrorController::AuthStatusChanged() { error_changed = true; } - if (error_changed) { - signin_metrics::LogAuthError(auth_error_); - for (auto& observer : observer_list_) - observer.OnErrorChanged(); + if (!error_changed) + return; + + if (auth_error_.state() == prev_state && + error_account_id_ == prev_account_id) { + // Only fire notification if the auth error state or account were updated. + return; } -} -bool SigninErrorController::HasError() const { - return auth_error_.state() != GoogleServiceAuthError::NONE && - auth_error_.state() != GoogleServiceAuthError::CONNECTION_FAILED; + signin_metrics::LogAuthError(auth_error_); + for (auto& observer : observer_list_) + observer.OnErrorChanged(); } -void SigninErrorController::SetPrimaryAccountID(const std::string& account_id) { - primary_account_id_ = account_id; - if (account_mode_ == AccountMode::PRIMARY_ACCOUNT) - AuthStatusChanged(); // Recompute the error state. +bool SigninErrorController::HasError() const { + DCHECK(!auth_error_.IsTransientError()); + return auth_error_.state() != GoogleServiceAuthError::NONE; } void SigninErrorController::AddObserver(Observer* observer) { @@ -119,3 +109,31 @@ void SigninErrorController::AddObserver(Observer* observer) { void SigninErrorController::RemoveObserver(Observer* observer) { observer_list_.RemoveObserver(observer); } + +void SigninErrorController::OnEndBatchOfRefreshTokenStateChanges() { + Update(); +} + +void SigninErrorController::OnErrorStateOfRefreshTokenUpdatedForAccount( + const AccountInfo& account_info, + const GoogleServiceAuthError& error) { + Update(); +} + +void SigninErrorController::OnPrimaryAccountSet( + const AccountInfo& primary_account_info) { + // Ignore updates to the primary account if not in PRIMARY_ACCOUNT mode. + if (account_mode_ != AccountMode::PRIMARY_ACCOUNT) + return; + + Update(); +} + +void SigninErrorController::OnPrimaryAccountCleared( + const AccountInfo& previous_primary_account_info) { + // Ignore updates to the primary account if not in PRIMARY_ACCOUNT mode. + if (account_mode_ != AccountMode::PRIMARY_ACCOUNT) + return; + + Update(); +} diff --git a/chromium/components/signin/core/browser/signin_error_controller.h b/chromium/components/signin/core/browser/signin_error_controller.h index 1a2c08a8212..334e426db23 100644 --- a/chromium/components/signin/core/browser/signin_error_controller.h +++ b/chromium/components/signin/core/browser/signin_error_controller.h @@ -6,17 +6,22 @@ #define COMPONENTS_SIGNIN_CORE_BROWSER_SIGNIN_ERROR_CONTROLLER_H_ #include <set> +#include <string> + #include "base/compiler_specific.h" #include "base/macros.h" #include "base/observer_list.h" +#include "base/scoped_observer.h" #include "components/keyed_service/core/keyed_service.h" #include "google_apis/gaia/google_service_auth_error.h" +#include "services/identity/public/cpp/identity_manager.h" // Keep track of auth errors and expose them to observers in the UI. Services // that wish to expose auth errors to the user should register an // AuthStatusProvider to report their current authentication state, and should // invoke AuthStatusChanged() when their authentication state may have changed. -class SigninErrorController : public KeyedService { +class SigninErrorController : public KeyedService, + public identity::IdentityManager::Observer { public: enum class AccountMode { // Signin error controller monitors all the accounts. When multiple accounts @@ -28,19 +33,6 @@ class SigninErrorController : public KeyedService { PRIMARY_ACCOUNT }; - class AuthStatusProvider { - public: - AuthStatusProvider(); - virtual ~AuthStatusProvider(); - - // Returns the account id with the status specified by GetAuthStatus(). - virtual std::string GetAccountId() const = 0; - - // API invoked by SigninErrorController to get the current auth status of - // the various signed in services. - virtual GoogleServiceAuthError GetAuthStatus() const = 0; - }; - // The observer class for SigninErrorController lets the controller notify // observers when an error arises or changes. class Observer { @@ -49,26 +41,19 @@ class SigninErrorController : public KeyedService { virtual void OnErrorChanged() = 0; }; - explicit SigninErrorController(AccountMode mode); + SigninErrorController(AccountMode mode, + identity::IdentityManager* identity_manager); ~SigninErrorController() override; - // Adds a provider which the SigninErrorController object will start querying - // for auth status. - void AddProvider(const AuthStatusProvider* provider); - - // Removes a provider previously added by SigninErrorController (generally - // only called in preparation for shutdown). - void RemoveProvider(const AuthStatusProvider* provider); - - // Invoked when the auth status of an AuthStatusProvider has changed. - void AuthStatusChanged(); + // KeyedService implementation: + void Shutdown() override; - // True if there exists an error worth elevating to the user. + // True if there exists an error worth elevating to the user. Note that + // |SigninErrorController| can be running in |AccountMode::ANY_ACCOUNT| mode, + // in which case |HasError| can return an error for any account, not just the + // Primary Account. See |error_account_id()|. bool HasError() const; - // Sets the primary account id. Only used in the PRIMARY_ACCOUNT account mode. - void SetPrimaryAccountID(const std::string& account_id); - void AddObserver(Observer* observer); void RemoveObserver(Observer* observer); @@ -76,11 +61,23 @@ class SigninErrorController : public KeyedService { const GoogleServiceAuthError& auth_error() const { return auth_error_; } private: + // Invoked when the auth status has changed. + void Update(); + + // identity::IdentityManager::Observer: + void OnEndBatchOfRefreshTokenStateChanges() override; + void OnErrorStateOfRefreshTokenUpdatedForAccount( + const AccountInfo& account_info, + const GoogleServiceAuthError& error) override; + void OnPrimaryAccountSet(const AccountInfo& primary_account_info) override; + void OnPrimaryAccountCleared( + const AccountInfo& previous_primary_account_info) override; + const AccountMode account_mode_; - std::set<const AuthStatusProvider*> provider_set_; + identity::IdentityManager* identity_manager_; - // The primary account ID. Only used in the PRIMARY_ACCOUNT account mode. - std::string primary_account_id_; + ScopedObserver<identity::IdentityManager, SigninErrorController> + scoped_identity_manager_observer_; // The account that generated the last auth error. std::string error_account_id_; diff --git a/chromium/components/signin/core/browser/signin_error_controller_unittest.cc b/chromium/components/signin/core/browser/signin_error_controller_unittest.cc index 62bec6a8536..2b88b04cf39 100644 --- a/chromium/components/signin/core/browser/signin_error_controller_unittest.cc +++ b/chromium/components/signin/core/browser/signin_error_controller_unittest.cc @@ -9,293 +9,279 @@ #include <functional> #include <memory> -#include "base/macros.h" -#include "components/signin/core/browser/fake_auth_status_provider.h" +#include "base/scoped_observer.h" +#include "base/stl_util.h" +#include "base/test/scoped_task_environment.h" +#include "services/identity/public/cpp/identity_test_environment.h" +#include "services/identity/public/cpp/primary_account_mutator.h" +#include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" -static const char kTestAccountId[] = "testuser@test.com"; -static const char kOtherTestAccountId[] = "otheruser@test.com"; +namespace { -TEST(SigninErrorControllerTest, NoErrorAuthStatusProviders) { - SigninErrorController error_controller( - SigninErrorController::AccountMode::ANY_ACCOUNT); - std::unique_ptr<FakeAuthStatusProvider> provider; +static const char kTestEmail[] = "me@test.com"; +static const char kOtherTestEmail[] = "you@test.com"; - // No providers. - ASSERT_FALSE(error_controller.HasError()); +class MockSigninErrorControllerObserver + : public SigninErrorController::Observer { + public: + MOCK_METHOD0(OnErrorChanged, void()); +}; - // Add a provider. - provider.reset(new FakeAuthStatusProvider(&error_controller)); - ASSERT_FALSE(error_controller.HasError()); +} // namespace - // Remove the provider. - provider.reset(); - ASSERT_FALSE(error_controller.HasError()); -} +TEST(SigninErrorControllerTest, SingleAccount) { + MockSigninErrorControllerObserver observer; + EXPECT_CALL(observer, OnErrorChanged()).Times(0); -TEST(SigninErrorControllerTest, ErrorAuthStatusProvider) { + base::test::ScopedTaskEnvironment task_environment; + identity::IdentityTestEnvironment identity_test_env; SigninErrorController error_controller( - SigninErrorController::AccountMode::ANY_ACCOUNT); - std::unique_ptr<FakeAuthStatusProvider> provider; - std::unique_ptr<FakeAuthStatusProvider> error_provider; - - provider.reset(new FakeAuthStatusProvider(&error_controller)); - ASSERT_FALSE(error_controller.HasError()); - - error_provider.reset(new FakeAuthStatusProvider(&error_controller)); - error_provider->SetAuthError( - kTestAccountId, - GoogleServiceAuthError( - GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS)); - ASSERT_TRUE(error_controller.HasError()); - - error_provider.reset(); - ASSERT_FALSE(error_controller.HasError()); - - provider.reset(); - // All providers should be removed now. + SigninErrorController::AccountMode::ANY_ACCOUNT, + identity_test_env.identity_manager()); + ScopedObserver<SigninErrorController, SigninErrorController::Observer> + scoped_observer(&observer); + scoped_observer.Add(&error_controller); ASSERT_FALSE(error_controller.HasError()); + ::testing::Mock::VerifyAndClearExpectations(&observer); + + // IdentityTestEnvironment does not call OnEndBatchChanges() as part of + // MakeAccountAvailable(), and thus the signin error controller is not + // updated. + EXPECT_CALL(observer, OnErrorChanged()).Times(0); + + std::string test_account_id = + identity_test_env.MakeAccountAvailable(kTestEmail).account_id; + ::testing::Mock::VerifyAndClearExpectations(&observer); + + GoogleServiceAuthError error1 = + GoogleServiceAuthError(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS); + EXPECT_CALL(observer, OnErrorChanged()).Times(1); + identity_test_env.UpdatePersistentErrorOfRefreshTokenForAccount( + test_account_id, error1); + EXPECT_TRUE(error_controller.HasError()); + EXPECT_EQ(error1, error_controller.auth_error()); + ::testing::Mock::VerifyAndClearExpectations(&observer); + + GoogleServiceAuthError error2 = + GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DISABLED); + EXPECT_CALL(observer, OnErrorChanged()).Times(1); + identity_test_env.UpdatePersistentErrorOfRefreshTokenForAccount( + test_account_id, error2); + EXPECT_TRUE(error_controller.HasError()); + EXPECT_EQ(error2, error_controller.auth_error()); + ::testing::Mock::VerifyAndClearExpectations(&observer); + + EXPECT_CALL(observer, OnErrorChanged()).Times(1); + identity_test_env.UpdatePersistentErrorOfRefreshTokenForAccount( + test_account_id, GoogleServiceAuthError::AuthErrorNone()); + EXPECT_FALSE(error_controller.HasError()); + EXPECT_EQ(GoogleServiceAuthError::AuthErrorNone(), + error_controller.auth_error()); + ::testing::Mock::VerifyAndClearExpectations(&observer); } -TEST(SigninErrorControllerTest, AuthStatusProviderErrorTransition) { - SigninErrorController error_controller( - SigninErrorController::AccountMode::ANY_ACCOUNT); - std::unique_ptr<FakeAuthStatusProvider> provider0( - new FakeAuthStatusProvider(&error_controller)); - std::unique_ptr<FakeAuthStatusProvider> provider1( - new FakeAuthStatusProvider(&error_controller)); +TEST(SigninErrorControllerTest, AccountTransitionAnyAccount) { + base::test::ScopedTaskEnvironment task_environment; + identity::IdentityTestEnvironment identity_test_env; - ASSERT_FALSE(error_controller.HasError()); - provider0->SetAuthError( - kTestAccountId, - GoogleServiceAuthError( - GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS)); - ASSERT_TRUE(error_controller.HasError()); - provider1->SetAuthError( - kTestAccountId, - GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DISABLED)); - ASSERT_TRUE(error_controller.HasError()); - - // Now resolve the auth errors - the menu item should go away. - provider0->SetAuthError(kTestAccountId, - GoogleServiceAuthError::AuthErrorNone()); - ASSERT_TRUE(error_controller.HasError()); - provider1->SetAuthError(kTestAccountId, - GoogleServiceAuthError::AuthErrorNone()); - ASSERT_FALSE(error_controller.HasError()); - - provider0.reset(); - provider1.reset(); - ASSERT_FALSE(error_controller.HasError()); -} - -TEST(SigninErrorControllerTest, AuthStatusProviderAccountTransitionAnyAccount) { + std::string test_account_id = + identity_test_env.MakeAccountAvailable(kTestEmail).account_id; + std::string other_test_account_id = + identity_test_env.MakeAccountAvailable(kOtherTestEmail).account_id; SigninErrorController error_controller( - SigninErrorController::AccountMode::ANY_ACCOUNT); - std::unique_ptr<FakeAuthStatusProvider> provider0( - new FakeAuthStatusProvider(&error_controller)); - std::unique_ptr<FakeAuthStatusProvider> provider1( - new FakeAuthStatusProvider(&error_controller)); - + SigninErrorController::AccountMode::ANY_ACCOUNT, + identity_test_env.identity_manager()); ASSERT_FALSE(error_controller.HasError()); - provider0->SetAuthError( - kTestAccountId, - GoogleServiceAuthError( - GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS)); - provider1->SetAuthError( - kOtherTestAccountId, - GoogleServiceAuthError(GoogleServiceAuthError::NONE)); - ASSERT_TRUE(error_controller.HasError()); - ASSERT_STREQ(kTestAccountId, error_controller.error_account_id().c_str()); - - // Swap providers reporting errors. - provider1->set_error_without_status_change( - GoogleServiceAuthError( - GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS)); - provider0->set_error_without_status_change( + identity_test_env.UpdatePersistentErrorOfRefreshTokenForAccount( + test_account_id, + GoogleServiceAuthError(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS)); + identity_test_env.UpdatePersistentErrorOfRefreshTokenForAccount( + other_test_account_id, GoogleServiceAuthError(GoogleServiceAuthError::NONE)); - error_controller.AuthStatusChanged(); ASSERT_TRUE(error_controller.HasError()); - ASSERT_STREQ(kOtherTestAccountId, - error_controller.error_account_id().c_str()); + ASSERT_EQ(test_account_id, error_controller.error_account_id()); // Now resolve the auth errors - the menu item should go away. - provider0->set_error_without_status_change( - GoogleServiceAuthError::AuthErrorNone()); - provider1->set_error_without_status_change( - GoogleServiceAuthError::AuthErrorNone()); - error_controller.AuthStatusChanged(); - ASSERT_FALSE(error_controller.HasError()); - - provider0.reset(); - provider1.reset(); + identity_test_env.UpdatePersistentErrorOfRefreshTokenForAccount( + test_account_id, GoogleServiceAuthError::AuthErrorNone()); ASSERT_FALSE(error_controller.HasError()); } -TEST(SigninErrorControllerTest, - AuthStatusProviderAccountTransitionPrimaryAccount) { +// This test exercises behavior on signin/signout, which is not relevant on +// ChromeOS. +#if !defined(OS_CHROMEOS) +TEST(SigninErrorControllerTest, AccountTransitionPrimaryAccount) { + base::test::ScopedTaskEnvironment task_environment; + identity::IdentityTestEnvironment identity_test_env; + identity::PrimaryAccountMutator* primary_account_mutator = + identity_test_env.identity_manager()->GetPrimaryAccountMutator(); + + std::string test_account_id = + identity_test_env.MakeAccountAvailable(kTestEmail).account_id; + std::string other_test_account_id = + identity_test_env.MakeAccountAvailable(kOtherTestEmail).account_id; SigninErrorController error_controller( - SigninErrorController::AccountMode::PRIMARY_ACCOUNT); - std::unique_ptr<FakeAuthStatusProvider> provider0( - new FakeAuthStatusProvider(&error_controller)); - std::unique_ptr<FakeAuthStatusProvider> provider1( - new FakeAuthStatusProvider(&error_controller)); - + SigninErrorController::AccountMode::PRIMARY_ACCOUNT, + identity_test_env.identity_manager()); ASSERT_FALSE(error_controller.HasError()); - provider0->SetAuthError( - kTestAccountId, + identity_test_env.UpdatePersistentErrorOfRefreshTokenForAccount( + test_account_id, GoogleServiceAuthError(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS)); - provider1->SetAuthError(kOtherTestAccountId, - GoogleServiceAuthError(GoogleServiceAuthError::NONE)); + identity_test_env.UpdatePersistentErrorOfRefreshTokenForAccount( + other_test_account_id, + GoogleServiceAuthError(GoogleServiceAuthError::NONE)); ASSERT_FALSE(error_controller.HasError()); // No primary account. - error_controller.SetPrimaryAccountID(kOtherTestAccountId); - ASSERT_FALSE(error_controller.HasError()); // Error on secondary. - error_controller.SetPrimaryAccountID(kTestAccountId); + + // Set the primary account. + identity_test_env.SetPrimaryAccount(kOtherTestEmail); + + ASSERT_FALSE(error_controller.HasError()); // Error is on secondary. + + // Change the primary account to the account with an error and check that the + // error controller updates its error status accordingly. + primary_account_mutator->ClearPrimaryAccount( + identity::PrimaryAccountMutator::ClearAccountsAction::kKeepAll, + signin_metrics::FORCE_SIGNOUT_ALWAYS_ALLOWED_FOR_TEST, + signin_metrics::SignoutDelete::IGNORE_METRIC); + identity_test_env.SetPrimaryAccount(kTestEmail); ASSERT_TRUE(error_controller.HasError()); - ASSERT_STREQ(kTestAccountId, error_controller.error_account_id().c_str()); + ASSERT_EQ(test_account_id, error_controller.error_account_id()); - // Change the primary account. - provider1->SetAuthError( - kOtherTestAccountId, + identity_test_env.UpdatePersistentErrorOfRefreshTokenForAccount( + other_test_account_id, GoogleServiceAuthError(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS)); ASSERT_TRUE(error_controller.HasError()); - ASSERT_STREQ(kTestAccountId, error_controller.error_account_id().c_str()); - error_controller.SetPrimaryAccountID(kOtherTestAccountId); + ASSERT_EQ(test_account_id, error_controller.error_account_id()); + + // Change the primary account again and check that the error controller + // updates its error status accordingly. + primary_account_mutator->ClearPrimaryAccount( + identity::PrimaryAccountMutator::ClearAccountsAction::kKeepAll, + signin_metrics::FORCE_SIGNOUT_ALWAYS_ALLOWED_FOR_TEST, + signin_metrics::SignoutDelete::IGNORE_METRIC); + identity_test_env.SetPrimaryAccount(kOtherTestEmail); ASSERT_TRUE(error_controller.HasError()); - ASSERT_STREQ(kOtherTestAccountId, - error_controller.error_account_id().c_str()); - - // Signout. - error_controller.SetPrimaryAccountID(""); + ASSERT_EQ(other_test_account_id, error_controller.error_account_id()); + + // Sign out and check that that the error controller updates its error status + // accordingly. + primary_account_mutator->ClearPrimaryAccount( + identity::PrimaryAccountMutator::ClearAccountsAction::kKeepAll, + signin_metrics::FORCE_SIGNOUT_ALWAYS_ALLOWED_FOR_TEST, + signin_metrics::SignoutDelete::IGNORE_METRIC); ASSERT_FALSE(error_controller.HasError()); - - provider0.reset(); - provider1.reset(); } +#endif // Verify that SigninErrorController handles errors properly. TEST(SigninErrorControllerTest, AuthStatusEnumerateAllErrors) { + base::test::ScopedTaskEnvironment task_environment; + identity::IdentityTestEnvironment identity_test_env; + + std::string test_account_id = + identity_test_env.MakeAccountAvailable(kTestEmail).account_id; SigninErrorController error_controller( - SigninErrorController::AccountMode::ANY_ACCOUNT); - typedef struct { - GoogleServiceAuthError::State error_state; - bool is_error; - } ErrorTableEntry; - - ErrorTableEntry table[] = { - { GoogleServiceAuthError::NONE, false }, - { GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS, true }, - { GoogleServiceAuthError::USER_NOT_SIGNED_UP, true }, - { GoogleServiceAuthError::CONNECTION_FAILED, false }, - { GoogleServiceAuthError::CAPTCHA_REQUIRED, true }, - { GoogleServiceAuthError::ACCOUNT_DELETED, true }, - { GoogleServiceAuthError::ACCOUNT_DISABLED, true }, - { GoogleServiceAuthError::SERVICE_UNAVAILABLE, false }, - { GoogleServiceAuthError::TWO_FACTOR, true }, - { GoogleServiceAuthError::REQUEST_CANCELED, false }, - { GoogleServiceAuthError::HOSTED_NOT_ALLOWED_DEPRECATED, false }, - { GoogleServiceAuthError::UNEXPECTED_SERVICE_RESPONSE, true }, - { GoogleServiceAuthError::SERVICE_ERROR, true }, - { GoogleServiceAuthError::WEB_LOGIN_REQUIRED, true }, - }; - static_assert(arraysize(table) == GoogleServiceAuthError::NUM_STATES, - "table array does not match the number of auth error types"); - - for (size_t i = 0; i < arraysize(table); ++i) { - if (GoogleServiceAuthError::IsDeprecated(table[i].error_state)) + SigninErrorController::AccountMode::ANY_ACCOUNT, + identity_test_env.identity_manager()); + + GoogleServiceAuthError::State table[] = { + GoogleServiceAuthError::NONE, + GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS, + GoogleServiceAuthError::USER_NOT_SIGNED_UP, + GoogleServiceAuthError::CONNECTION_FAILED, + GoogleServiceAuthError::CAPTCHA_REQUIRED, + GoogleServiceAuthError::ACCOUNT_DELETED, + GoogleServiceAuthError::ACCOUNT_DISABLED, + GoogleServiceAuthError::SERVICE_UNAVAILABLE, + GoogleServiceAuthError::TWO_FACTOR, + GoogleServiceAuthError::REQUEST_CANCELED, + GoogleServiceAuthError::HOSTED_NOT_ALLOWED_DEPRECATED, + GoogleServiceAuthError::UNEXPECTED_SERVICE_RESPONSE, + GoogleServiceAuthError::SERVICE_ERROR, + GoogleServiceAuthError::WEB_LOGIN_REQUIRED}; + static_assert(base::size(table) == GoogleServiceAuthError::NUM_STATES, + "table array does not match the number of auth error types"); + + for (GoogleServiceAuthError::State state : table) { + if (GoogleServiceAuthError::IsDeprecated(state)) continue; - FakeAuthStatusProvider provider(&error_controller); - provider.SetAuthError(kTestAccountId, - GoogleServiceAuthError(table[i].error_state)); - EXPECT_EQ(error_controller.HasError(), table[i].is_error); + GoogleServiceAuthError error(state); + + if (error.IsTransientError()) + continue; // Only persistent errors or non-errors are reported. + + identity_test_env.UpdatePersistentErrorOfRefreshTokenForAccount( + test_account_id, error); - if (table[i].is_error) { - EXPECT_EQ(table[i].error_state, error_controller.auth_error().state()); - EXPECT_STREQ(kTestAccountId, error_controller.error_account_id().c_str()); + EXPECT_EQ(error_controller.HasError(), error.IsPersistentError()); + + if (error.IsPersistentError()) { + EXPECT_EQ(state, error_controller.auth_error().state()); + EXPECT_EQ(test_account_id, error_controller.error_account_id()); } else { EXPECT_EQ(GoogleServiceAuthError::NONE, error_controller.auth_error().state()); - EXPECT_STREQ("", error_controller.error_account_id().c_str()); + EXPECT_EQ("", error_controller.error_account_id()); } } } // Verify that existing error is not replaced by new error. TEST(SigninErrorControllerTest, AuthStatusChange) { + base::test::ScopedTaskEnvironment task_environment; + identity::IdentityTestEnvironment identity_test_env; + + std::string test_account_id = + identity_test_env.MakeAccountAvailable(kTestEmail).account_id; + std::string other_test_account_id = + identity_test_env.MakeAccountAvailable(kOtherTestEmail).account_id; SigninErrorController error_controller( - SigninErrorController::AccountMode::ANY_ACCOUNT); - std::unique_ptr<FakeAuthStatusProvider> fake_provider0( - new FakeAuthStatusProvider(&error_controller)); - std::unique_ptr<FakeAuthStatusProvider> fake_provider1( - new FakeAuthStatusProvider(&error_controller)); - - // If there are multiple providers in the provider set... - // - // | provider0 | provider1 | ... - // | NONE | INVALID_GAIA_CREDENTIALS | ... - // - // SigninErrorController picks the first error found when iterating through - // the set. But if another error crops up... - // - // | provider0 | provider1 | ... - // | SERVICE_ERROR | INVALID_GAIA_CREDENTIALS | ... - // - // we want the controller to still use the original error. - - // The provider pointers are stored in a set, which is sorted by std::less. - std::less<SigninErrorController::AuthStatusProvider*> compare; - FakeAuthStatusProvider* provider0 = - compare(fake_provider0.get(), fake_provider1.get()) ? - fake_provider0.get() : fake_provider1.get(); - FakeAuthStatusProvider* provider1 = - provider0 == fake_provider0.get() ? - fake_provider1.get() : fake_provider0.get(); - - provider0->SetAuthError( - kTestAccountId, - GoogleServiceAuthError( - GoogleServiceAuthError::NONE)); - provider1->SetAuthError( - kOtherTestAccountId, - GoogleServiceAuthError( - GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS)); + SigninErrorController::AccountMode::ANY_ACCOUNT, + identity_test_env.identity_manager()); + ASSERT_FALSE(error_controller.HasError()); + + // Set an error for other_test_account_id. + identity_test_env.UpdatePersistentErrorOfRefreshTokenForAccount( + test_account_id, GoogleServiceAuthError(GoogleServiceAuthError::NONE)); + identity_test_env.UpdatePersistentErrorOfRefreshTokenForAccount( + other_test_account_id, + GoogleServiceAuthError(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS)); ASSERT_EQ(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS, error_controller.auth_error().state()); - ASSERT_STREQ(kOtherTestAccountId, - error_controller.error_account_id().c_str()); + ASSERT_EQ(other_test_account_id, error_controller.error_account_id()); - // Change the 1st provider's error. - provider1->SetAuthError( - kOtherTestAccountId, + // Change the error for other_test_account_id. + identity_test_env.UpdatePersistentErrorOfRefreshTokenForAccount( + other_test_account_id, GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_ERROR)); ASSERT_EQ(GoogleServiceAuthError::SERVICE_ERROR, error_controller.auth_error().state()); - ASSERT_STREQ(kOtherTestAccountId, - error_controller.error_account_id().c_str()); + ASSERT_EQ(other_test_account_id, error_controller.error_account_id()); - // Set the 0th provider's error -- nothing should change. - provider0->SetAuthError( - kTestAccountId, + // Set the error for test_account_id -- nothing should change. + identity_test_env.UpdatePersistentErrorOfRefreshTokenForAccount( + test_account_id, GoogleServiceAuthError( GoogleServiceAuthError::UNEXPECTED_SERVICE_RESPONSE)); ASSERT_EQ(GoogleServiceAuthError::SERVICE_ERROR, error_controller.auth_error().state()); - ASSERT_STREQ(kOtherTestAccountId, - error_controller.error_account_id().c_str()); + ASSERT_EQ(other_test_account_id, error_controller.error_account_id()); - // Clear the 1st provider's error, so the 0th provider's error is used. - provider1->SetAuthError( - kOtherTestAccountId, - GoogleServiceAuthError( - GoogleServiceAuthError::NONE)); + // Clear the error for other_test_account_id, so the test_account_id's error + // is used. + identity_test_env.UpdatePersistentErrorOfRefreshTokenForAccount( + other_test_account_id, + GoogleServiceAuthError(GoogleServiceAuthError::NONE)); ASSERT_EQ(GoogleServiceAuthError::UNEXPECTED_SERVICE_RESPONSE, error_controller.auth_error().state()); - ASSERT_STREQ(kTestAccountId, error_controller.error_account_id().c_str()); + ASSERT_EQ(test_account_id, error_controller.error_account_id()); - fake_provider0.reset(); - fake_provider1.reset(); + // Clear the remaining error. + identity_test_env.UpdatePersistentErrorOfRefreshTokenForAccount( + test_account_id, GoogleServiceAuthError(GoogleServiceAuthError::NONE)); ASSERT_FALSE(error_controller.HasError()); } diff --git a/chromium/components/signin/core/browser/signin_header_helper_unittest.cc b/chromium/components/signin/core/browser/signin_header_helper_unittest.cc index 5414b2f896f..1697fd9bcd2 100644 --- a/chromium/components/signin/core/browser/signin_header_helper_unittest.cc +++ b/chromium/components/signin/core/browser/signin_header_helper_unittest.cc @@ -8,8 +8,8 @@ #include <string> #include "base/command_line.h" -#include "base/message_loop/message_loop.h" #include "base/strings/stringprintf.h" +#include "base/test/scoped_task_environment.h" #include "components/content_settings/core/browser/cookie_settings.h" #include "components/prefs/pref_member.h" #include "components/signin/core/browser/account_consistency_method.h" @@ -112,7 +112,7 @@ class SigninHeaderHelperTest : public testing::Test { } #endif - base::MessageLoop loop_; + base::test::ScopedTaskEnvironment task_environment_; bool sync_enabled_ = false; bool sync_has_auth_error_ = false; @@ -327,37 +327,6 @@ TEST_F(SigninHeaderHelperTest, TestDiceMigration) { kDiceProtocolVersion, client_id.c_str())); } -// Tests that a Dice request is returned only when there is an authentication -// error if the method is kDiceFixAuthErrors. -TEST_F(SigninHeaderHelperTest, TestDiceFixAuthError) { - account_consistency_ = AccountConsistencyMethod::kDiceFixAuthErrors; - // No Dice request unless all conditions are met. - CheckDiceHeaderRequest(GURL("https://accounts.google.com"), "0123456789", - "mode=0,enable_account_consistency=false", ""); - sync_has_auth_error_ = false; - sync_enabled_ = true; - CheckDiceHeaderRequest(GURL("https://accounts.google.com"), "0123456789", - "mode=0,enable_account_consistency=false", ""); - sync_has_auth_error_ = true; - sync_enabled_ = false; - CheckDiceHeaderRequest(GURL("https://accounts.google.com"), "0123456789", - "mode=0,enable_account_consistency=false", ""); - sync_has_auth_error_ = true; - sync_enabled_ = true; - CheckDiceHeaderRequest(GURL("https://accounts.google.com"), "", "", ""); - - // Dice request when there is an account id, Sync is enabled and in error - // state. - std::string client_id = GaiaUrls::GetInstance()->oauth2_chrome_client_id(); - CheckDiceHeaderRequest( - GURL("https://accounts.google.com"), "0123456789", - "mode=0,enable_account_consistency=false", - base::StringPrintf("version=%s,client_id=%s,device_id=DeviceID," - "sync_account_id=0123456789,signin_mode=sync_account," - "signout_mode=no_confirmation", - kDiceProtocolVersion, client_id.c_str())); -} - // Tests that the Mirror request is returned with the GAIA Id on Drive origin, // even if account consistency is disabled. TEST_F(SigninHeaderHelperTest, TestMirrorRequestDrive) { diff --git a/chromium/components/signin/core/browser/signin_internals_util.cc b/chromium/components/signin/core/browser/signin_internals_util.cc index fa9d492224e..68884202990 100644 --- a/chromium/components/signin/core/browser/signin_internals_util.cc +++ b/chromium/components/signin/core/browser/signin_internals_util.cc @@ -37,8 +37,6 @@ std::string SigninStatusFieldToString(TimedSigninStatusField field) { switch (field) { ENUM_CASE(AUTHENTICATION_RESULT_RECEIVED); ENUM_CASE(REFRESH_TOKEN_RECEIVED); - ENUM_CASE(SIGNIN_STARTED); - ENUM_CASE(SIGNIN_COMPLETED); case TIMED_FIELDS_END: NOTREACHED(); return std::string(); diff --git a/chromium/components/signin/core/browser/signin_internals_util.h b/chromium/components/signin/core/browser/signin_internals_util.h index d397d04a712..576710b70c2 100644 --- a/chromium/components/signin/core/browser/signin_internals_util.h +++ b/chromium/components/signin/core/browser/signin_internals_util.h @@ -45,8 +45,6 @@ enum TimedSigninStatusField { TIMED_FIELDS_BEGIN = TIMED_FIELDS_BEGIN_UNTYPED, AUTHENTICATION_RESULT_RECEIVED = TIMED_FIELDS_BEGIN, REFRESH_TOKEN_RECEIVED, - SIGNIN_STARTED, - SIGNIN_COMPLETED, TIMED_FIELDS_END }; @@ -65,14 +63,6 @@ std::string TokenPrefPath(const std::string& service_name); std::string SigninStatusFieldToString(UntimedSigninStatusField field); std::string SigninStatusFieldToString(TimedSigninStatusField field); -// An Observer class for authentication and token diagnostic information. -class SigninDiagnosticsObserver { - public: - // Credentials and signin related changes. - virtual void NotifySigninValueChanged(const TimedSigninStatusField& field, - const std::string& value) {} -}; - // Gets the first 6 hex characters of the SHA256 hash of the passed in string. // These are enough to perform equality checks across a single users tokens, // while preventing outsiders from reverse-engineering the actual token from diff --git a/chromium/components/signin/core/browser/signin_investigator_unittest.cc b/chromium/components/signin/core/browser/signin_investigator_unittest.cc index cba6e2028c9..cb861a03128 100644 --- a/chromium/components/signin/core/browser/signin_investigator_unittest.cc +++ b/chromium/components/signin/core/browser/signin_investigator_unittest.cc @@ -5,12 +5,13 @@ #include <string> #include "base/test/metrics/histogram_tester.h" +#include "base/test/scoped_task_environment.h" #include "components/prefs/pref_registry_simple.h" -#include "components/prefs/testing_pref_service.h" #include "components/signin/core/browser/signin_investigator.h" -#include "components/signin/core/browser/signin_manager_base.h" #include "components/signin/core/browser/signin_metrics.h" #include "components/signin/core/browser/signin_pref_names.h" +#include "components/sync_preferences/testing_pref_service_syncable.h" +#include "services/identity/public/cpp/identity_test_environment.h" #include "testing/gtest/include/gtest/gtest.h" using signin_metrics::AccountEquality; @@ -24,13 +25,17 @@ const char kEmptyId[] = ""; class FakeProvider : public SigninInvestigator::DependencyProvider { public: - FakeProvider(const std::string& last_email, const std::string& last_id) { - SigninManagerBase::RegisterProfilePrefs(prefs_.registry()); + FakeProvider(const std::string& last_email, const std::string& last_id) + : identity_test_env_(/*test_url_loader_factory=*/nullptr, &prefs_) { prefs_.SetString(prefs::kGoogleServicesLastUsername, last_email); prefs_.SetString(prefs::kGoogleServicesLastAccountId, last_id); } PrefService* GetPrefs() override { return &prefs_; } - TestingPrefServiceSimple prefs_; + + private: + base::test::ScopedTaskEnvironment task_environment_; + sync_preferences::TestingPrefServiceSyncable prefs_; + identity::IdentityTestEnvironment identity_test_env_; }; } // namespace diff --git a/chromium/components/signin/core/browser/signin_manager.cc b/chromium/components/signin/core/browser/signin_manager.cc index f3c819f1a3f..5ac21878b9b 100644 --- a/chromium/components/signin/core/browser/signin_manager.cc +++ b/chromium/components/signin/core/browser/signin_manager.cc @@ -30,14 +30,9 @@ SigninManager::SigninManager( ProfileOAuth2TokenService* token_service, AccountTrackerService* account_tracker_service, GaiaCookieManagerService* cookie_manager_service, - SigninErrorController* signin_error_controller, signin::AccountConsistencyMethod account_consistency) - : SigninManagerBase(client, - account_tracker_service, - signin_error_controller), + : SigninManagerBase(client, token_service, account_tracker_service), type_(SIGNIN_TYPE_NONE), - client_(client), - token_service_(token_service), cookie_manager_service_(cookie_manager_service), account_consistency_(account_consistency), signin_manager_signed_in_(false), @@ -90,8 +85,6 @@ bool SigninManager::PrepareForSignin(SigninType type, password_.assign(password); signin_manager_signed_in_ = false; user_info_fetched_by_account_tracker_ = false; - NotifyDiagnosticsObservers(signin_internals_util::SIGNIN_STARTED, - SigninTypeToString(type)); return true; } @@ -128,7 +121,7 @@ void SigninManager::CopyCredentialsFrom(const SigninManager& source) { possibly_invalid_email_ = source.possibly_invalid_email_; temp_refresh_token_ = source.temp_refresh_token_; password_ = source.password_; - source.client_->AfterCredentialsCopied(); + source.signin_client()->AfterCredentialsCopied(); } void SigninManager::ClearTransientSigninData() { @@ -177,7 +170,7 @@ void SigninManager::StartSignOut( signin_metrics::ProfileSignout signout_source_metric, signin_metrics::SignoutDelete signout_delete_metric, RemoveAccountsOption remove_option) { - client_->PreSignOut( + signin_client()->PreSignOut( base::BindOnce(&SigninManager::OnSignoutDecisionReached, base::Unretained(this), signout_source_metric, signout_delete_metric, remove_option), @@ -221,13 +214,13 @@ void SigninManager::OnSignoutDecisionReached( const std::string account_id = GetAuthenticatedAccountId(); const std::string username = account_info.email; const base::Time signin_time = - base::Time::FromInternalValue( - client_->GetPrefs()->GetInt64(prefs::kSignedInTime)); + base::Time::FromDeltaSinceWindowsEpoch(base::TimeDelta::FromMicroseconds( + signin_client()->GetPrefs()->GetInt64(prefs::kSignedInTime))); ClearAuthenticatedAccountId(); - client_->GetPrefs()->ClearPref(prefs::kGoogleServicesHostedDomain); - client_->GetPrefs()->ClearPref(prefs::kGoogleServicesAccountId); - client_->GetPrefs()->ClearPref(prefs::kGoogleServicesUserAccountId); - client_->GetPrefs()->ClearPref(prefs::kSignedInTime); + signin_client()->GetPrefs()->ClearPref(prefs::kGoogleServicesHostedDomain); + signin_client()->GetPrefs()->ClearPref(prefs::kGoogleServicesAccountId); + signin_client()->GetPrefs()->ClearPref(prefs::kGoogleServicesUserAccountId); + signin_client()->GetPrefs()->ClearPref(prefs::kSignedInTime); // Determine the duration the user was logged in and log that to UMA. if (!signin_time.is_null()) { @@ -243,13 +236,13 @@ void SigninManager::OnSignoutDecisionReached( case RemoveAccountsOption::kRemoveAllAccounts: VLOG(0) << "Revoking all refresh tokens on server. Reason: sign out, " << "IsSigninAllowed: " << IsSigninAllowed(); - token_service_->RevokeAllCredentials( + token_service()->RevokeAllCredentials( signin_metrics::SourceForRefreshTokenOperation:: kSigninManager_ClearPrimaryAccount); break; case RemoveAccountsOption::kRemoveAuthenticatedAccountIfInError: - if (token_service_->RefreshTokenHasError(account_id)) - token_service_->RevokeCredentials( + if (token_service()->RefreshTokenHasError(account_id)) + token_service()->RevokeCredentials( account_id, signin_metrics::SourceForRefreshTokenOperation:: kSigninManager_ClearPrimaryAccount); break; @@ -261,9 +254,8 @@ void SigninManager::OnSignoutDecisionReached( FireGoogleSignedOut(account_info); } -void SigninManager::Initialize(PrefService* local_state) { - SigninManagerBase::Initialize(local_state); - +void SigninManager::FinalizeInitBeforeLoadingRefreshTokens( + PrefService* local_state) { // local_state can be null during unit tests. if (local_state) { local_state_pref_registrar_.Init(local_state); @@ -272,13 +264,12 @@ void SigninManager::Initialize(PrefService* local_state) { base::Bind(&SigninManager::OnGoogleServicesUsernamePatternChanged, weak_pointer_factory_.GetWeakPtr())); } - signin_allowed_.Init(prefs::kSigninAllowed, - client_->GetPrefs(), + signin_allowed_.Init(prefs::kSigninAllowed, signin_client()->GetPrefs(), base::Bind(&SigninManager::OnSigninAllowedPrefChanged, base::Unretained(this))); std::string account_id = - client_->GetPrefs()->GetString(prefs::kGoogleServicesAccountId); + signin_client()->GetPrefs()->GetString(prefs::kGoogleServicesAccountId); std::string user = account_id.empty() ? std::string() : account_tracker_service()->GetAccountInfo(account_id).email; if (!account_id.empty() && (!IsAllowedUsername(user) || !IsSigninAllowed())) { @@ -307,12 +298,11 @@ void SigninManager::Initialize(PrefService* local_state) { // It is important to only load credentials after starting to observe the // token service. - token_service_->AddObserver(this); - token_service_->LoadCredentials(GetAuthenticatedAccountId()); + token_service()->AddObserver(this); } void SigninManager::Shutdown() { - token_service_->RemoveObserver(this); + token_service()->RemoveObserver(this); account_tracker_service()->RemoveObserver(this); local_state_pref_registrar_.RemoveAll(); SigninManagerBase::Shutdown(); @@ -386,8 +376,6 @@ void SigninManager::MergeSigninCredentialIntoCookieJar() { } void SigninManager::CompletePendingSignin() { - NotifyDiagnosticsObservers(signin_internals_util::SIGNIN_COMPLETED, - "Successful"); DCHECK(!possibly_invalid_account_id_.empty()); OnSignedIn(); @@ -395,7 +383,7 @@ void SigninManager::CompletePendingSignin() { if (!temp_refresh_token_.empty()) { std::string account_id = GetAuthenticatedAccountId(); - token_service_->UpdateCredentials( + token_service()->UpdateCredentials( account_id, temp_refresh_token_, signin_metrics::SourceForRefreshTokenOperation:: kSigninManager_LegacyPreDiceSigninFlow); @@ -418,8 +406,9 @@ void SigninManager::OnExternalSigninCompleted(const std::string& username) { void SigninManager::OnSignedIn() { bool reauth_in_progress = IsAuthenticated(); - client_->GetPrefs()->SetInt64(prefs::kSignedInTime, - base::Time::Now().ToInternalValue()); + signin_client()->GetPrefs()->SetInt64( + prefs::kSignedInTime, + base::Time::Now().ToDeltaSinceWindowsEpoch().InMicroseconds()); SetAuthenticatedAccountInfo(possibly_invalid_gaia_id_, possibly_invalid_email_); @@ -433,8 +422,8 @@ void SigninManager::OnSignedIn() { if (!reauth_in_progress) FireGoogleSigninSucceeded(); - signin_metrics::LogSigninProfile(client_->IsFirstRun(), - client_->GetInstallDate()); + signin_metrics::LogSigninProfile(signin_client()->IsFirstRun(), + signin_client()->GetInstallDate()); PostSignedIn(); } @@ -457,8 +446,8 @@ void SigninManager::PostSignedIn() { if (!signin_manager_signed_in_ || !user_info_fetched_by_account_tracker_) return; - client_->PostSignedIn(GetAuthenticatedAccountId(), - GetAuthenticatedAccountInfo().email, password_); + signin_client()->PostSignedIn(GetAuthenticatedAccountId(), + GetAuthenticatedAccountInfo().email, password_); password_.clear(); } @@ -476,7 +465,7 @@ void SigninManager::OnAccountUpdateFailed(const std::string& account_id) { } void SigninManager::OnRefreshTokensLoaded() { - token_service_->RemoveObserver(this); + token_service()->RemoveObserver(this); if (account_tracker_service()->GetMigrationState() == AccountTrackerService::MIGRATION_IN_PROGRESS) { @@ -484,12 +473,12 @@ void SigninManager::OnRefreshTokensLoaded() { } // Remove account information from the account tracker service if needed. - if (token_service_->HasLoadCredentialsFinishedWithNoErrors()) { + if (token_service()->HasLoadCredentialsFinishedWithNoErrors()) { std::vector<AccountInfo> accounts_in_tracker_service = account_tracker_service()->GetAccounts(); for (const auto& account : accounts_in_tracker_service) { if (GetAuthenticatedAccountId() != account.account_id && - !token_service_->RefreshTokenIsAvailable(account.account_id)) { + !token_service()->RefreshTokenIsAvailable(account.account_id)) { DVLOG(0) << "Removed account from account tracker service: " << account.account_id; account_tracker_service()->RemoveAccount(account.account_id); diff --git a/chromium/components/signin/core/browser/signin_manager.h b/chromium/components/signin/core/browser/signin_manager.h index 83682f78a60..cf3bbc158b3 100644 --- a/chromium/components/signin/core/browser/signin_manager.h +++ b/chromium/components/signin/core/browser/signin_manager.h @@ -48,9 +48,6 @@ class GaiaCookieManagerService; class GoogleServiceAuthError; class PrefService; -class ProfileOAuth2TokenService; -class SigninClient; -class SigninErrorController; namespace identity { class IdentityManager; @@ -88,7 +85,6 @@ class SigninManager : public SigninManagerBase, ProfileOAuth2TokenService* token_service, AccountTrackerService* account_tracker_service, GaiaCookieManagerService* cookie_manager_service, - SigninErrorController* signin_error_controller, signin::AccountConsistencyMethod account_consistency); ~SigninManager() override; @@ -143,7 +139,9 @@ class SigninManager : public SigninManagerBase, // On platforms where SigninManager is responsible for dealing with // invalid username policy updates, we need to check this during // initialization and sign the user out. - void Initialize(PrefService* local_state) override; + void FinalizeInitBeforeLoadingRefreshTokens( + PrefService* local_state) override; + void Shutdown() override; // If applicable, merge the signed in account into the cookie jar. @@ -268,14 +266,6 @@ class SigninManager : public SigninManagerBase, // token service so that it does not need to mint new ones. std::string temp_refresh_token_; - // The SigninClient object associated with this object. Must outlive this - // object. - SigninClient* client_; - - // The ProfileOAuth2TokenService instance associated with this object. Must - // outlive this object. - ProfileOAuth2TokenService* token_service_; - // Object used to use the token to push a GAIA cookie into the cookie jar. GaiaCookieManagerService* cookie_manager_service_; diff --git a/chromium/components/signin/core/browser/signin_manager_base.cc b/chromium/components/signin/core/browser/signin_manager_base.cc index a75a5a1d872..6012474fe02 100644 --- a/chromium/components/signin/core/browser/signin_manager_base.cc +++ b/chromium/components/signin/core/browser/signin_manager_base.cc @@ -16,8 +16,8 @@ #include "components/prefs/pref_service.h" #include "components/signin/core/browser/account_info.h" #include "components/signin/core/browser/account_tracker_service.h" +#include "components/signin/core/browser/profile_oauth2_token_service.h" #include "components/signin/core/browser/signin_client.h" -#include "components/signin/core/browser/signin_error_controller.h" #include "components/signin/core/browser/signin_pref_names.h" #include "components/signin/core/browser/signin_switches.h" #include "google_apis/gaia/gaia_auth_util.h" @@ -26,11 +26,11 @@ SigninManagerBase::SigninManagerBase( SigninClient* client, - AccountTrackerService* account_tracker_service, - SigninErrorController* signin_error_controller) + ProfileOAuth2TokenService* token_service, + AccountTrackerService* account_tracker_service) : client_(client), + token_service_(token_service), account_tracker_service_(account_tracker_service), - signin_error_controller_(signin_error_controller), initialized_(false), weak_pointer_factory_(this) { DCHECK(client_); @@ -56,6 +56,7 @@ void SigninManagerBase::RegisterProfilePrefs(PrefRegistrySimple* registry) { registry->RegisterBooleanPref(prefs::kSigninAllowed, true); registry->RegisterInt64Pref(prefs::kSignedInTime, base::Time().ToInternalValue()); + registry->RegisterBooleanPref(prefs::kSignedInWithCredentialProvider, false); // Deprecated prefs: will be removed in a future release. registry->RegisterStringPref(prefs::kGoogleServicesUsername, std::string()); @@ -147,8 +148,13 @@ void SigninManagerBase::Initialize(PrefService* local_state) { } SetAuthenticatedAccountId(account_id); } + FinalizeInitBeforeLoadingRefreshTokens(local_state); + token_service()->LoadCredentials(GetAuthenticatedAccountId()); } +void SigninManagerBase::FinalizeInitBeforeLoadingRefreshTokens( + PrefService* local_state) {} + bool SigninManagerBase::IsInitialized() const { return initialized_; } bool SigninManagerBase::IsSigninAllowed() const { @@ -214,15 +220,10 @@ void SigninManagerBase::SetAuthenticatedAccountId( // Commit authenticated account info immediately so that it does not get lost // if Chrome crashes before the next commit interval. client_->GetPrefs()->CommitPendingWrite(); - - if (signin_error_controller_) - signin_error_controller_->SetPrimaryAccountID(authenticated_account_id_); } void SigninManagerBase::ClearAuthenticatedAccountId() { authenticated_account_id_.clear(); - if (signin_error_controller_) - signin_error_controller_->SetPrimaryAccountID(std::string()); } bool SigninManagerBase::IsAuthenticated() const { @@ -245,20 +246,3 @@ void SigninManagerBase::AddObserver(Observer* observer) { void SigninManagerBase::RemoveObserver(Observer* observer) { observer_list_.RemoveObserver(observer); } - -void SigninManagerBase::AddSigninDiagnosticsObserver( - signin_internals_util::SigninDiagnosticsObserver* observer) { - signin_diagnostics_observers_.AddObserver(observer); -} - -void SigninManagerBase::RemoveSigninDiagnosticsObserver( - signin_internals_util::SigninDiagnosticsObserver* observer) { - signin_diagnostics_observers_.RemoveObserver(observer); -} - -void SigninManagerBase::NotifyDiagnosticsObservers( - const signin_internals_util::TimedSigninStatusField& field, - const std::string& value) { - for (auto& observer : signin_diagnostics_observers_) - observer.NotifySigninValueChanged(field, value); -} diff --git a/chromium/components/signin/core/browser/signin_manager_base.h b/chromium/components/signin/core/browser/signin_manager_base.h index e30a2ea1451..f75a647f315 100644 --- a/chromium/components/signin/core/browser/signin_manager_base.h +++ b/chromium/components/signin/core/browser/signin_manager_base.h @@ -41,8 +41,8 @@ class AccountTrackerService; class PrefRegistrySimple; class PrefService; +class ProfileOAuth2TokenService; class SigninClient; -class SigninErrorController; class SigninManagerBase : public KeyedService { public: @@ -95,8 +95,8 @@ class SigninManagerBase : public KeyedService { private: #endif SigninManagerBase(SigninClient* client, - AccountTrackerService* account_tracker_service, - SigninErrorController* signin_error_controller); + ProfileOAuth2TokenService* token_service, + AccountTrackerService* account_tracker_service); #if !defined(OS_CHROMEOS) public: #endif @@ -110,7 +110,7 @@ class SigninManagerBase : public KeyedService { static void RegisterPrefs(PrefRegistrySimple* registry); // If user was signed in, load tokens from DB if available. - virtual void Initialize(PrefService* local_state); + void Initialize(PrefService* local_state); bool IsInitialized() const; // Returns true if a signin to Chrome is allowed (by policy or pref). @@ -154,15 +154,11 @@ class SigninManagerBase : public KeyedService { void AddObserver(Observer* observer); void RemoveObserver(Observer* observer); - // Methods to register or remove SigninDiagnosticObservers. - void AddSigninDiagnosticsObserver( - signin_internals_util::SigninDiagnosticsObserver* observer); - void RemoveSigninDiagnosticsObserver( - signin_internals_util::SigninDiagnosticsObserver* observer); - // Gives access to the SigninClient instance associated with this instance. SigninClient* signin_client() const { return client_; } + ProfileOAuth2TokenService* token_service() const { return token_service_; } + // Adds a callback that will be called when this instance is shut down.Not // intended for general usage, but rather for usage only by the Identity // Service implementation during the time period of conversion of Chrome to @@ -177,6 +173,10 @@ class SigninManagerBase : public KeyedService { return account_tracker_service_; } + // Invoked at the end of |Initialize| before the refresh token for the primary + // account is loaded. + virtual void FinalizeInitBeforeLoadingRefreshTokens(PrefService* local_state); + // Sets the authenticated user's account id. // If the user is already authenticated with the same account id, then this // method is a no-op. @@ -196,11 +196,6 @@ class SigninManagerBase : public KeyedService { // Makes sure list is empty on destruction. base::ObserverList<Observer, true>::Unchecked observer_list_; - // Helper method to notify all registered diagnostics observers with. - void NotifyDiagnosticsObservers( - const signin_internals_util::TimedSigninStatusField& field, - const std::string& value); - private: friend class FakeSigninManagerBase; friend class FakeSigninManager; @@ -211,17 +206,17 @@ class SigninManagerBase : public KeyedService { friend class SigninManager; SigninClient* client_; + + // The ProfileOAuth2TokenService instance associated with this object. Must + // outlive this object. + ProfileOAuth2TokenService* token_service_; + AccountTrackerService* account_tracker_service_; - SigninErrorController* signin_error_controller_; bool initialized_; // Account id after successful authentication. std::string authenticated_account_id_; - // The list of SigninDiagnosticObservers. - base::ObserverList<signin_internals_util::SigninDiagnosticsObserver, - true>::Unchecked signin_diagnostics_observers_; - // The list of callbacks notified on shutdown. base::CallbackList<void()> on_shutdown_callback_list_; diff --git a/chromium/components/signin/core/browser/signin_manager_unittest.cc b/chromium/components/signin/core/browser/signin_manager_unittest.cc index 75bd3d8c506..5cb99d8efd2 100644 --- a/chromium/components/signin/core/browser/signin_manager_unittest.cc +++ b/chromium/components/signin/core/browser/signin_manager_unittest.cc @@ -11,8 +11,8 @@ #include "base/bind.h" #include "base/bind_helpers.h" #include "base/compiler_specific.h" -#include "base/message_loop/message_loop.h" #include "base/run_loop.h" +#include "base/test/scoped_task_environment.h" #include "build/build_config.h" #include "components/prefs/pref_registry_simple.h" #include "components/prefs/pref_service.h" @@ -77,8 +77,7 @@ class SigninManagerTest : public testing::Test { SigninManagerTest() : test_signin_client_(&user_prefs_), token_service_(&user_prefs_), - cookie_manager_service_(&token_service_, - &test_signin_client_), + cookie_manager_service_(&token_service_, &test_signin_client_), account_consistency_(signin::AccountConsistencyMethod::kDisabled) { AccountFetcherService::RegisterPrefs(user_prefs_.registry()); AccountTrackerService::RegisterPrefs(user_prefs_.registry()); @@ -123,8 +122,7 @@ class SigninManagerTest : public testing::Test { DCHECK(!manager_); manager_ = std::make_unique<SigninManager>( &test_signin_client_, &token_service_, &account_tracker_, - &cookie_manager_service_, nullptr /* signin_error_controller */, - account_consistency_); + &cookie_manager_service_, account_consistency_); manager_->Initialize(&local_state_); manager_->AddObserver(&test_observer_); } @@ -156,13 +154,13 @@ class SigninManagerTest : public testing::Test { manager_->CompletePendingSignin(); } - base::MessageLoop loop_; + base::test::ScopedTaskEnvironment task_environment_; sync_preferences::TestingPrefServiceSyncable user_prefs_; TestingPrefServiceSimple local_state_; TestSigninClient test_signin_client_; FakeProfileOAuth2TokenService token_service_; AccountTrackerService account_tracker_; - FakeGaiaCookieManagerService cookie_manager_service_; + GaiaCookieManagerService cookie_manager_service_; FakeAccountFetcherService account_fetcher_; std::unique_ptr<SigninManager> manager_; TestSigninManagerObserver test_observer_; @@ -499,8 +497,7 @@ TEST_F(SigninManagerTest, GaiaIdMigration) { PrefService* client_prefs = signin_client()->GetPrefs(); client_prefs->SetInteger(prefs::kAccountIdMigrationState, AccountTrackerService::MIGRATION_NOT_STARTED); - ListPrefUpdate update(client_prefs, - AccountTrackerService::kAccountInfoPref); + ListPrefUpdate update(client_prefs, prefs::kAccountInfo); update->Clear(); auto dict = std::make_unique<base::DictionaryValue>(); dict->SetString("account_id", email); @@ -529,8 +526,7 @@ TEST_F(SigninManagerTest, VeryOldProfileGaiaIdMigration) { PrefService* client_prefs = signin_client()->GetPrefs(); client_prefs->SetInteger(prefs::kAccountIdMigrationState, AccountTrackerService::MIGRATION_NOT_STARTED); - ListPrefUpdate update(client_prefs, - AccountTrackerService::kAccountInfoPref); + ListPrefUpdate update(client_prefs, prefs::kAccountInfo); update->Clear(); auto dict = std::make_unique<base::DictionaryValue>(); dict->SetString("account_id", email); @@ -559,8 +555,7 @@ TEST_F(SigninManagerTest, GaiaIdMigrationCrashInTheMiddle) { PrefService* client_prefs = signin_client()->GetPrefs(); client_prefs->SetInteger(prefs::kAccountIdMigrationState, AccountTrackerService::MIGRATION_NOT_STARTED); - ListPrefUpdate update(client_prefs, - AccountTrackerService::kAccountInfoPref); + ListPrefUpdate update(client_prefs, prefs::kAccountInfo); update->Clear(); auto dict = std::make_unique<base::DictionaryValue>(); dict->SetString("account_id", email); diff --git a/chromium/components/signin/core/browser/signin_metrics.cc b/chromium/components/signin/core/browser/signin_metrics.cc index 224ae0411b6..7c0bf8613fa 100644 --- a/chromium/components/signin/core/browser/signin_metrics.cc +++ b/chromium/components/signin/core/browser/signin_metrics.cc @@ -118,6 +118,10 @@ void RecordSigninUserActionForAccessPoint(AccessPoint access_point) { base::RecordAction( base::UserMetricsAction("Signin_Signin_FromMachineLogon")); break; + case AccessPoint::ACCESS_POINT_GOOGLE_SERVICES_SETTINGS: + base::RecordAction( + base::UserMetricsAction("Signin_Signin_FromGoogleServicesSettings")); + break; case AccessPoint::ACCESS_POINT_MAX: NOTREACHED(); break; @@ -186,6 +190,7 @@ void RecordSigninWithDefaultUserActionForAccessPoint( case AccessPoint::ACCESS_POINT_RESIGNIN_INFOBAR: case AccessPoint::ACCESS_POINT_UNKNOWN: case AccessPoint::ACCESS_POINT_MACHINE_LOGON: + case AccessPoint::ACCESS_POINT_GOOGLE_SERVICES_SETTINGS: NOTREACHED() << "Signin_SigninWithDefault_From* user actions" << " are not recorded for access_point " << static_cast<int>(access_point) @@ -259,6 +264,7 @@ void RecordSigninNotDefaultUserActionForAccessPoint( case AccessPoint::ACCESS_POINT_RESIGNIN_INFOBAR: case AccessPoint::ACCESS_POINT_UNKNOWN: case AccessPoint::ACCESS_POINT_MACHINE_LOGON: + case AccessPoint::ACCESS_POINT_GOOGLE_SERVICES_SETTINGS: NOTREACHED() << "Signin_SigninNotDefault_From* user actions" << " are not recorded for access point " << static_cast<int>(access_point) @@ -332,6 +338,7 @@ void RecordSigninNewAccountPreDiceUserActionForAccessPoint( case AccessPoint::ACCESS_POINT_RESIGNIN_INFOBAR: case AccessPoint::ACCESS_POINT_UNKNOWN: case AccessPoint::ACCESS_POINT_MACHINE_LOGON: + case AccessPoint::ACCESS_POINT_GOOGLE_SERVICES_SETTINGS: // These access points do not support personalized sign-in promos, so // |Signin_SigninNewAccountPreDice_From*| user actions should not // be recorded for them. Note: To avoid bloating the sign-in APIs, the @@ -416,6 +423,7 @@ void RecordSigninNewAccountNoExistingAccountUserActionForAccessPoint( case AccessPoint::ACCESS_POINT_RESIGNIN_INFOBAR: case AccessPoint::ACCESS_POINT_UNKNOWN: case AccessPoint::ACCESS_POINT_MACHINE_LOGON: + case AccessPoint::ACCESS_POINT_GOOGLE_SERVICES_SETTINGS: // These access points do not support personalized sign-in promos, so // |Signin_SigninNewAccountNoExistingAccount_From*| user actions should // not be recorded for them. Note: To avoid bloating the sign-in APIs, the @@ -496,6 +504,7 @@ void RecordSigninNewAccountExistingAccountUserActionForAccessPoint( case AccessPoint::ACCESS_POINT_RESIGNIN_INFOBAR: case AccessPoint::ACCESS_POINT_UNKNOWN: case AccessPoint::ACCESS_POINT_MACHINE_LOGON: + case AccessPoint::ACCESS_POINT_GOOGLE_SERVICES_SETTINGS: // These access points do not support personalized sign-in promos, so // |Signin_SigninNewAccountExistingAccount_From*| user actions should not // be recorded for them. Note: To avoid bloating the sign-in APIs, the @@ -945,6 +954,10 @@ void RecordSigninImpressionUserActionForAccessPoint(AccessPoint access_point) { base::RecordAction( base::UserMetricsAction("Signin_Impression_FromManageCardsBubble")); break; + case AccessPoint::ACCESS_POINT_GOOGLE_SERVICES_SETTINGS: + base::RecordAction(base::UserMetricsAction( + "Signin_Impression_FromGoogleServicesSettings")); + break; case AccessPoint::ACCESS_POINT_CONTENT_AREA: case AccessPoint::ACCESS_POINT_EXTENSIONS: case AccessPoint::ACCESS_POINT_SUPERVISED_USER: @@ -1079,6 +1092,7 @@ void RecordSigninImpressionWithAccountUserActionForAccessPoint( case AccessPoint::ACCESS_POINT_RESIGNIN_INFOBAR: case AccessPoint::ACCESS_POINT_UNKNOWN: case AccessPoint::ACCESS_POINT_MACHINE_LOGON: + case AccessPoint::ACCESS_POINT_GOOGLE_SERVICES_SETTINGS: NOTREACHED() << "Signin_Impression{With|WithNo}Account_From* user actions" << " are not recorded for access point " << static_cast<int>(access_point) diff --git a/chromium/components/signin/core/browser/signin_metrics.h b/chromium/components/signin/core/browser/signin_metrics.h index 3b52acd161d..dcd0aebc082 100644 --- a/chromium/components/signin/core/browser/signin_metrics.h +++ b/chromium/components/signin/core/browser/signin_metrics.h @@ -159,6 +159,7 @@ enum class AccessPoint : int { ACCESS_POINT_SAVE_CARD_BUBBLE = 24, ACCESS_POINT_MANAGE_CARDS_BUBBLE = 25, ACCESS_POINT_MACHINE_LOGON = 26, + ACCESS_POINT_GOOGLE_SERVICES_SETTINGS = 27, ACCESS_POINT_MAX, // This must be last. }; @@ -342,7 +343,10 @@ enum class SourceForRefreshTokenOperation { kDiceResponseHandler_Signin, kDiceResponseHandler_Signout, kDiceTurnOnSyncHelper_Abort, - kMaxValue = kDiceTurnOnSyncHelper_Abort + kMachineLogon_CredentialProvider, + kTokenService_ExtractCredentials, + + kMaxValue = kTokenService_ExtractCredentials }; // Different types of reporting. This is used as a histogram suffix. diff --git a/chromium/components/signin/core/browser/signin_metrics_unittest.cc b/chromium/components/signin/core/browser/signin_metrics_unittest.cc index 72ed284a50e..ec4cb749f40 100644 --- a/chromium/components/signin/core/browser/signin_metrics_unittest.cc +++ b/chromium/components/signin/core/browser/signin_metrics_unittest.cc @@ -106,6 +106,8 @@ class SigninMetricsTest : public ::testing::Test { return "ManageCardsBubble"; case AccessPoint::ACCESS_POINT_MACHINE_LOGON: return "MachineLogon"; + case AccessPoint::ACCESS_POINT_GOOGLE_SERVICES_SETTINGS: + return "GoogleServicesSettings"; case AccessPoint::ACCESS_POINT_MAX: NOTREACHED(); return ""; diff --git a/chromium/components/signin/core/browser/signin_pref_names.cc b/chromium/components/signin/core/browser/signin_pref_names.cc index 8de4eccd27b..c5923a59b12 100644 --- a/chromium/components/signin/core/browser/signin_pref_names.cc +++ b/chromium/components/signin/core/browser/signin_pref_names.cc @@ -17,6 +17,10 @@ const char kAccountConsistencyMirrorRequired[] = // for possible values. const char kAccountIdMigrationState[] = "account_id_migration_state"; +// Name of the preference property that persists the account information +// tracked by this signin. +const char kAccountInfo[] = "account_info"; + // Boolean identifying whether reverse auto-login is enabled. const char kAutologinEnabled[] = "autologin.enabled"; @@ -39,8 +43,8 @@ const char kGaiaCookiePeriodicReportTime[] = "gaia_cookie.periodic_report_time"; // eventually be fixed, allowing the removal of kGoogleServicesUserAccountId. const char kGoogleServicesAccountId[] = "google.services.account_id"; -// The profile's hosted domain; empty if unset; -// AccountTrackerService::kNoHostedDomainFound if there is none. +// The profile's hosted domain; empty if unset; kNoHostedDomainFound if there +// is none. const char kGoogleServicesHostedDomain[] = "google.services.hosted_domain"; // Similar to kGoogleServicesLastUsername, this is the corresponding version of @@ -86,6 +90,11 @@ const char kReverseAutologinRejectedEmailList[] = // to the browser. const char kSignedInTime[] = "signin.signedin_time"; +// Boolean indicating if this profile was signed in with information from a +// credential provider. +const char kSignedInWithCredentialProvider[] = + "signin.with_credential_provider"; + // Boolean which stores if the user is allowed to signin to chrome. const char kSigninAllowed[] = "signin.allowed"; diff --git a/chromium/components/signin/core/browser/signin_pref_names.h b/chromium/components/signin/core/browser/signin_pref_names.h index 2ebafcbc7a9..61391ea9f6b 100644 --- a/chromium/components/signin/core/browser/signin_pref_names.h +++ b/chromium/components/signin/core/browser/signin_pref_names.h @@ -11,6 +11,7 @@ namespace prefs { extern const char kAccountConsistencyMirrorRequired[]; #endif extern const char kAccountIdMigrationState[]; +extern const char kAccountInfo[]; extern const char kAutologinEnabled[]; extern const char kGaiaCookieHash[]; extern const char kGaiaCookieChangedTime[]; @@ -25,6 +26,7 @@ extern const char kGoogleServicesUsername[]; extern const char kGoogleServicesUsernamePattern[]; extern const char kReverseAutologinRejectedEmailList[]; extern const char kSignedInTime[]; +extern const char kSignedInWithCredentialProvider[]; extern const char kSigninAllowed[]; extern const char kTokenServiceDiceCompatible[]; extern const char kTokenServiceExcludeAllSecondaryAccounts[]; diff --git a/chromium/components/signin/core/browser/signin_tracker.cc b/chromium/components/signin/core/browser/signin_tracker.cc index 0a82a897a96..230b49eb9db 100644 --- a/chromium/components/signin/core/browser/signin_tracker.cc +++ b/chromium/components/signin/core/browser/signin_tracker.cc @@ -4,45 +4,36 @@ #include "components/signin/core/browser/signin_tracker.h" -#include "components/signin/core/browser/gaia_cookie_manager_service.h" -#include "components/signin/core/browser/profile_oauth2_token_service.h" #include "google_apis/gaia/gaia_constants.h" -SigninTracker::SigninTracker(ProfileOAuth2TokenService* token_service, - SigninManagerBase* signin_manager, - GaiaCookieManagerService* cookie_manager_service, +SigninTracker::SigninTracker(identity::IdentityManager* identity_manager, Observer* observer) - : token_service_(token_service), - signin_manager_(signin_manager), - cookie_manager_service_(cookie_manager_service), - observer_(observer) { + : identity_manager_(identity_manager), observer_(observer) { Initialize(); } SigninTracker::~SigninTracker() { - signin_manager_->RemoveObserver(this); - token_service_->RemoveObserver(this); - cookie_manager_service_->RemoveObserver(this); + identity_manager_->RemoveObserver(this); } void SigninTracker::Initialize() { DCHECK(observer_); - signin_manager_->AddObserver(this); - token_service_->AddObserver(this); - cookie_manager_service_->AddObserver(this); + identity_manager_->AddObserver(this); } -void SigninTracker::GoogleSigninSucceeded(const AccountInfo& account_info) { - if (token_service_->RefreshTokenIsAvailable(account_info.account_id)) +void SigninTracker::OnPrimaryAccountSet(const AccountInfo& account_info) { + if (identity_manager_->HasAccountWithRefreshToken(account_info.account_id)) observer_->SigninSuccess(); } -void SigninTracker::GoogleSigninFailed(const GoogleServiceAuthError& error) { +void SigninTracker::OnPrimaryAccountSigninFailed( + const GoogleServiceAuthError& error) { observer_->SigninFailed(error); } -void SigninTracker::OnRefreshTokenAvailable(const std::string& account_id) { - if (account_id != signin_manager_->GetAuthenticatedAccountId()) +void SigninTracker::OnRefreshTokenUpdatedForAccount( + const AccountInfo& account_info) { + if (account_info.account_id != identity_manager_->GetPrimaryAccountId()) return; observer_->SigninSuccess(); diff --git a/chromium/components/signin/core/browser/signin_tracker.h b/chromium/components/signin/core/browser/signin_tracker.h index cb857e3afd7..0e6e8c5bb35 100644 --- a/chromium/components/signin/core/browser/signin_tracker.h +++ b/chromium/components/signin/core/browser/signin_tracker.h @@ -8,17 +8,14 @@ #include <memory> #include "base/macros.h" -#include "components/signin/core/browser/gaia_cookie_manager_service.h" -#include "components/signin/core/browser/signin_manager.h" #include "google_apis/gaia/google_service_auth_error.h" - -class ProfileOAuth2TokenService; +#include "services/identity/public/cpp/identity_manager.h" // The signin flow logic is spread across several classes with varying // responsibilities: // -// SigninTracker (this class) - This class listens to notifications from various -// services (SigninManager, OAuth2TokenService) and coalesces them into +// SigninTracker (this class) - This class listens to notifications from the +// IdentityManager services and coalesces them into // notifications for the UI layer. This is the class that encapsulates the logic // that determines whether a user is fully logged in or not, and exposes // callbacks so various pieces of the UI (OneClickSyncStarter) can track the @@ -35,25 +32,21 @@ class ProfileOAuth2TokenService; // (a KeyedService that keeps track of the currently visible // login UI). // -// SigninManager - Records the currently-logged-in user and handles all +// IdentityManager - Records the currently-logged-in user and handles all // interaction with the GAIA backend during the signin process. Unlike -// SigninTracker, SigninManager only knows about the GAIA login state and is +// SigninTracker, IdentityManager only knows about the GAIA login state and is // not aware of the state of any signed in services. -// -// OAuth2TokenService - Maintains and manages OAuth2 tokens for the accounts -// connected to this profile. -// -// GaiaCookieManagerService - Responsible for adding or removing cookies from -// the cookie jar from the browser process. A single source of information about -// GAIA cookies in the cookie jar that are fetchable via /ListAccounts. +// What is more, IdentityManager also maintains and manages OAuth2 tokens for +// the accounts connected to this profile. +// Last, the IdentityManager is also responsible for adding or removing cookies +// from the cookie jar from the browser process. A single source of information +// about GAIA cookies in the cookie jar that are fetchable via /ListAccounts. // // ProfileSyncService - Provides the external API for interacting with the // sync framework. Listens for notifications for tokens to know when to startup // sync, and provides an Observer interface to notify the UI layer of changes // in sync state so they can be reflected in the UI. -class SigninTracker : public SigninManagerBase::Observer, - public OAuth2TokenService::Observer, - public GaiaCookieManagerService::Observer { +class SigninTracker : public identity::IdentityManager::Observer { public: class Observer { public: @@ -73,32 +66,26 @@ class SigninTracker : public SigninManagerBase::Observer, // instances with the exception of |account_reconcilor| must be non-null and // must outlive the SigninTracker. |account_reconcilor| will be used if it is // non-null. - SigninTracker(ProfileOAuth2TokenService* token_service, - SigninManagerBase* signin_manager, - GaiaCookieManagerService* cookie_manager_service, + SigninTracker(identity::IdentityManager* identity_manager, Observer* observer); ~SigninTracker() override; - // SigninManagerBase::Observer implementation. - void GoogleSigninSucceeded(const AccountInfo& account_info) override; - void GoogleSigninFailed(const GoogleServiceAuthError& error) override; - - // OAuth2TokenService::Observer implementation. - void OnRefreshTokenAvailable(const std::string& account_id) override; + // identity::IdentityManager::Observer implementation. + void OnPrimaryAccountSet(const AccountInfo& account_info) override; + void OnPrimaryAccountSigninFailed( + const GoogleServiceAuthError& error) override; + void OnRefreshTokenUpdatedForAccount( + const AccountInfo& account_info) override; + void OnAddAccountToCookieCompleted( + const std::string& account_id, + const GoogleServiceAuthError& error) override; private: // Initializes this by adding notifications and observers. void Initialize(); - // GaiaCookieManagerService::Observer implementation. - void OnAddAccountToCookieCompleted( - const std::string& account_id, - const GoogleServiceAuthError& error) override; - // The classes whose collective signin status we are tracking. - ProfileOAuth2TokenService* token_service_; - SigninManagerBase* signin_manager_; - GaiaCookieManagerService* cookie_manager_service_; + identity::IdentityManager* identity_manager_; // Weak pointer to the observer we call when the signin state changes. Observer* observer_; diff --git a/chromium/components/signin/core/browser/signin_tracker_unittest.cc b/chromium/components/signin/core/browser/signin_tracker_unittest.cc index 78cc5424b6f..e6114a3729c 100644 --- a/chromium/components/signin/core/browser/signin_tracker_unittest.cc +++ b/chromium/components/signin/core/browser/signin_tracker_unittest.cc @@ -5,18 +5,11 @@ #include "components/signin/core/browser/signin_tracker.h" #include "base/compiler_specific.h" -#include "base/message_loop/message_loop.h" +#include "base/test/scoped_task_environment.h" #include "build/build_config.h" -#include "components/signin/core/browser/account_consistency_method.h" -#include "components/signin/core/browser/account_tracker_service.h" -#include "components/signin/core/browser/fake_gaia_cookie_manager_service.h" -#include "components/signin/core/browser/fake_profile_oauth2_token_service.h" -#include "components/signin/core/browser/fake_signin_manager.h" -#include "components/signin/core/browser/signin_switches.h" -#include "components/signin/core/browser/test_signin_client.h" -#include "components/sync_preferences/testing_pref_service_syncable.h" #include "google_apis/gaia/gaia_constants.h" #include "google_apis/gaia/google_service_auth_error.h" +#include "services/identity/public/cpp/identity_test_environment.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -49,41 +42,16 @@ class MockObserver : public SigninTracker::Observer { class SigninTrackerTest : public testing::Test { public: - SigninTrackerTest() - : signin_client_(&pref_service_), - fake_oauth2_token_service_(&pref_service_), - fake_gaia_cookie_manager_service_(&fake_oauth2_token_service_, - &signin_client_), -#if defined(OS_CHROMEOS) - fake_signin_manager_(&signin_client_, &account_tracker_) { -#else - fake_signin_manager_(&signin_client_, - &fake_oauth2_token_service_, - &account_tracker_, - &fake_gaia_cookie_manager_service_) { -#endif - - AccountTrackerService::RegisterPrefs(pref_service_.registry()); - SigninManagerBase::RegisterProfilePrefs(pref_service_.registry()); - SigninManagerBase::RegisterPrefs(pref_service_.registry()); - - account_tracker_.Initialize(&pref_service_, base::FilePath()); - + SigninTrackerTest() { tracker_ = std::make_unique<SigninTracker>( - &fake_oauth2_token_service_, &fake_signin_manager_, - &fake_gaia_cookie_manager_service_, &observer_); + identity_test_env_.identity_manager(), &observer_); } ~SigninTrackerTest() override { tracker_.reset(); } - base::MessageLoop message_loop_; + base::test::ScopedTaskEnvironment task_environment_; std::unique_ptr<SigninTracker> tracker_; - sync_preferences::TestingPrefServiceSyncable pref_service_; - AccountTrackerService account_tracker_; - TestSigninClient signin_client_; - FakeProfileOAuth2TokenService fake_oauth2_token_service_; - FakeGaiaCookieManagerService fake_gaia_cookie_manager_service_; - FakeSigninManagerForTesting fake_signin_manager_; + identity::IdentityTestEnvironment identity_test_env_; MockObserver observer_; }; @@ -96,7 +64,8 @@ TEST_F(SigninTrackerTest, SignInFails) { EXPECT_CALL(observer_, SigninSuccess()).Times(0); EXPECT_CALL(observer_, SigninFailed(error)); - fake_signin_manager_.FailSignin(error); + // Mimic calling IdentityManager::GoogleSigninFailed(). + tracker_->OnPrimaryAccountSigninFailed(error); } #endif // !defined(OS_CHROMEOS) @@ -104,11 +73,8 @@ TEST_F(SigninTrackerTest, SignInSucceeds) { EXPECT_CALL(observer_, SigninSuccess()); EXPECT_CALL(observer_, SigninFailed(_)).Times(0); - std::string gaia_id = "gaia_id"; std::string email = "user@gmail.com"; - std::string account_id = account_tracker_.SeedAccountInfo(gaia_id, email); - fake_signin_manager_.SetAuthenticatedAccountInfo(gaia_id, email); - fake_oauth2_token_service_.UpdateCredentials(account_id, "refresh_token"); + identity_test_env_.MakePrimaryAccountAvailable(email); } #if !defined(OS_CHROMEOS) @@ -116,10 +82,8 @@ TEST_F(SigninTrackerTest, SignInSucceedsWithExistingAccount) { EXPECT_CALL(observer_, SigninSuccess()); EXPECT_CALL(observer_, SigninFailed(_)).Times(0); - std::string gaia_id = "gaia_id"; std::string email = "user@gmail.com"; - std::string account_id = account_tracker_.SeedAccountInfo(gaia_id, email); - fake_oauth2_token_service_.UpdateCredentials(account_id, "refresh_token"); - fake_signin_manager_.SignIn(gaia_id, email, std::string()); + AccountInfo account_info = identity_test_env_.MakeAccountAvailable(email); + identity_test_env_.SetPrimaryAccount(account_info.email); } #endif diff --git a/chromium/components/signin/core/browser/ubertoken_fetcher.cc b/chromium/components/signin/core/browser/ubertoken_fetcher.cc new file mode 100644 index 00000000000..6eea8ba9c4d --- /dev/null +++ b/chromium/components/signin/core/browser/ubertoken_fetcher.cc @@ -0,0 +1,15 @@ +// Copyright 2019 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 "components/signin/core/browser/ubertoken_fetcher.h" + +namespace signin { + +// While defining providing an implementation to pure virtual methods is rarely +// useful, one must define a pure virtual destructor. This is because the +// destructor of a base class is always called when a derived object is +// destroyed. Failing to define it will cause a link error. +UbertokenFetcher::~UbertokenFetcher() {} + +} // namespace signin diff --git a/chromium/components/signin/core/browser/ubertoken_fetcher.h b/chromium/components/signin/core/browser/ubertoken_fetcher.h new file mode 100644 index 00000000000..cc4f1691e5c --- /dev/null +++ b/chromium/components/signin/core/browser/ubertoken_fetcher.h @@ -0,0 +1,35 @@ +// Copyright 2019 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. + +#ifndef COMPONENTS_SIGNIN_CORE_BROWSER_UBERTOKEN_FETCHER_H_ +#define COMPONENTS_SIGNIN_CORE_BROWSER_UBERTOKEN_FETCHER_H_ + +#include <memory> + +#include "base/bind.h" +#include "base/macros.h" + +class GoogleServiceAuthError; + +namespace signin { + +// Opaque interface that fetches ubertokens for a given account. Clients must +// go through IdentityManager to create a functioning instance. +class UbertokenFetcher { + public: + using CompletionCallback = + base::OnceCallback<void(GoogleServiceAuthError error, + const std::string& token)>; + + // Constructs an instance and start fetching the token for |account_id|. + UbertokenFetcher() = default; + virtual ~UbertokenFetcher() = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(UbertokenFetcher); +}; + +} // namespace signin + +#endif // COMPONENTS_SIGNIN_CORE_BROWSER_UBERTOKEN_FETCHER_H_ diff --git a/chromium/components/signin/core/browser/ubertoken_fetcher_impl.cc b/chromium/components/signin/core/browser/ubertoken_fetcher_impl.cc new file mode 100644 index 00000000000..53c3f7dd5ec --- /dev/null +++ b/chromium/components/signin/core/browser/ubertoken_fetcher_impl.cc @@ -0,0 +1,156 @@ +// Copyright 2014 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 "components/signin/core/browser/ubertoken_fetcher_impl.h" + +#include <vector> + +#include "base/logging.h" +#include "base/metrics/histogram_macros.h" +#include "base/rand_util.h" +#include "base/time/time.h" +#include "google_apis/gaia/gaia_constants.h" +#include "google_apis/gaia/google_service_auth_error.h" +#include "google_apis/gaia/oauth2_token_service.h" +#include "services/network/public/cpp/wrapper_shared_url_loader_factory.h" + +namespace { +std::unique_ptr<GaiaAuthFetcher> CreateGaiaAuthFetcher( + gaia::GaiaSource source, + GaiaAuthConsumer* consumer, + scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory) { + return std::make_unique<GaiaAuthFetcher>(consumer, source, + url_loader_factory); +} +} // namespace + +namespace signin { + +const int UbertokenFetcherImpl::kMaxRetries = 3; + +UbertokenFetcherImpl::UbertokenFetcherImpl( + const std::string& account_id, + OAuth2TokenService* token_service, + CompletionCallback ubertoken_callback, + gaia::GaiaSource source, + scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory, + bool is_bound_to_channel_id) + : UbertokenFetcherImpl(account_id, + /*access_token=*/"", + token_service, + std::move(ubertoken_callback), + url_loader_factory, + base::BindRepeating(CreateGaiaAuthFetcher, source), + is_bound_to_channel_id) {} + +UbertokenFetcherImpl::UbertokenFetcherImpl( + const std::string& account_id, + const std::string& access_token, + OAuth2TokenService* token_service, + CompletionCallback ubertoken_callback, + scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory, + GaiaAuthFetcherFactory factory, + bool is_bound_to_channel_id) + : OAuth2TokenService::Consumer("uber_token_fetcher"), + token_service_(token_service), + ubertoken_callback_(std::move(ubertoken_callback)), + url_loader_factory_(url_loader_factory), + is_bound_to_channel_id_(is_bound_to_channel_id), + gaia_auth_fetcher_factory_(factory), + account_id_(account_id), + access_token_(access_token), + retry_number_(0), + second_access_token_request_(false) { + DCHECK(!account_id.empty()); + DCHECK(token_service); + DCHECK(!ubertoken_callback_.is_null()); + DCHECK(url_loader_factory); + + if (access_token_.empty()) { + RequestAccessToken(); + return; + } + + ExchangeTokens(); +} + +UbertokenFetcherImpl::~UbertokenFetcherImpl() {} + +void UbertokenFetcherImpl::OnUberAuthTokenSuccess(const std::string& token) { + std::move(ubertoken_callback_) + .Run(GoogleServiceAuthError::AuthErrorNone(), token); +} + +void UbertokenFetcherImpl::OnUberAuthTokenFailure( + const GoogleServiceAuthError& error) { + // Retry only transient errors. + bool should_retry = + error.state() == GoogleServiceAuthError::CONNECTION_FAILED || + error.state() == GoogleServiceAuthError::SERVICE_UNAVAILABLE; + if (should_retry) { + if (retry_number_ < kMaxRetries) { + // Calculate an exponential backoff with randomness of less than 1 sec. + double backoff = base::RandDouble() + (1 << retry_number_); + ++retry_number_; + UMA_HISTOGRAM_ENUMERATION("Signin.UberTokenRetry", error.state(), + GoogleServiceAuthError::NUM_STATES); + retry_timer_.Stop(); + retry_timer_.Start(FROM_HERE, base::TimeDelta::FromSecondsD(backoff), + this, &UbertokenFetcherImpl::ExchangeTokens); + return; + } + } else { + // The access token is invalid. Tell the token service. + OAuth2TokenService::ScopeSet scopes; + scopes.insert(GaiaConstants::kOAuth1LoginScope); + token_service_->InvalidateAccessToken(account_id_, scopes, access_token_); + + // In case the access was just stale, try one more time. + if (!second_access_token_request_) { + second_access_token_request_ = true; + RequestAccessToken(); + return; + } + } + + UMA_HISTOGRAM_ENUMERATION("Signin.UberTokenFailure", error.state(), + GoogleServiceAuthError::NUM_STATES); + std::move(ubertoken_callback_).Run(error, /*access_token=*/std::string()); +} + +void UbertokenFetcherImpl::OnGetTokenSuccess( + const OAuth2TokenService::Request* request, + const OAuth2AccessTokenConsumer::TokenResponse& token_response) { + DCHECK(!token_response.access_token.empty()); + access_token_ = token_response.access_token; + access_token_request_.reset(); + ExchangeTokens(); +} + +void UbertokenFetcherImpl::OnGetTokenFailure( + const OAuth2TokenService::Request* request, + const GoogleServiceAuthError& error) { + access_token_request_.reset(); + std::move(ubertoken_callback_).Run(error, /*access_token=*/std::string()); +} + +void UbertokenFetcherImpl::RequestAccessToken() { + retry_number_ = 0; + gaia_auth_fetcher_.reset(); + retry_timer_.Stop(); + + OAuth2TokenService::ScopeSet scopes; + scopes.insert(GaiaConstants::kOAuth1LoginScope); + access_token_request_ = + token_service_->StartRequest(account_id_, scopes, this); +} + +void UbertokenFetcherImpl::ExchangeTokens() { + gaia_auth_fetcher_ = + gaia_auth_fetcher_factory_.Run(this, url_loader_factory_); + gaia_auth_fetcher_->StartTokenFetchForUberAuthExchange( + access_token_, is_bound_to_channel_id_); +} + +} // namespace signin diff --git a/chromium/components/signin/core/browser/ubertoken_fetcher_impl.h b/chromium/components/signin/core/browser/ubertoken_fetcher_impl.h new file mode 100644 index 00000000000..9a07e0f1d1c --- /dev/null +++ b/chromium/components/signin/core/browser/ubertoken_fetcher_impl.h @@ -0,0 +1,108 @@ +// Copyright 2014 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. + +#ifndef COMPONENTS_SIGNIN_CORE_BROWSER_UBERTOKEN_FETCHER_IMPL_H_ +#define COMPONENTS_SIGNIN_CORE_BROWSER_UBERTOKEN_FETCHER_IMPL_H_ + +#include <memory> + +#include "base/macros.h" +#include "base/timer/timer.h" +#include "components/signin/core/browser/ubertoken_fetcher.h" +#include "google_apis/gaia/gaia_auth_consumer.h" +#include "google_apis/gaia/gaia_auth_fetcher.h" +#include "google_apis/gaia/oauth2_token_service.h" + +// Allow to retrieves an uber-auth token for the user. This class uses the +// |OAuth2TokenService| and considers that the user is already logged in. It +// will use the OAuth2 access token to generate the uber-auth token. +// +// This class should be used on a single thread, but it can be whichever thread +// that you like. +// +// This class can handle one request at a time. + +class GoogleServiceAuthError; + +namespace network { +class SharedURLLoaderFactory; +} + +namespace signin { + +using GaiaAuthFetcherFactory = + base::RepeatingCallback<std::unique_ptr<GaiaAuthFetcher>( + GaiaAuthConsumer*, + scoped_refptr<network::SharedURLLoaderFactory>)>; + +// Allows to retrieve an uber-auth token. +class UbertokenFetcherImpl : public UbertokenFetcher, + public GaiaAuthConsumer, + public OAuth2TokenService::Consumer { + public: + // Maximum number of retries to get the uber-auth token before giving up. + static const int kMaxRetries; + + using CompletionCallback = + base::OnceCallback<void(GoogleServiceAuthError error, + const std::string& token)>; + + // Constructs an instance and starts fetching the access token and ubertoken + // sequencially for |account_id|. + UbertokenFetcherImpl( + const std::string& account_id, + OAuth2TokenService* token_service, + CompletionCallback ubertoken_callback, + gaia::GaiaSource source, + scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory, + bool is_bound_to_channel_id = true); + + // Constructs an instance and starts fetching the ubertoken for |account_id|. + UbertokenFetcherImpl( + const std::string& account_id, + const std::string& access_token, + OAuth2TokenService* token_service, + CompletionCallback ubertoken_callback, + scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory, + GaiaAuthFetcherFactory factory, + bool is_bound_to_channel_id = true); + ~UbertokenFetcherImpl() override; + + // Overriden from GaiaAuthConsumer + void OnUberAuthTokenSuccess(const std::string& token) override; + void OnUberAuthTokenFailure(const GoogleServiceAuthError& error) override; + + // Overriden from OAuth2TokenService::Consumer: + void OnGetTokenSuccess( + const OAuth2TokenService::Request* request, + const OAuth2AccessTokenConsumer::TokenResponse& token_response) override; + void OnGetTokenFailure(const OAuth2TokenService::Request* request, + const GoogleServiceAuthError& error) override; + + private: + // Request a login-scoped access token from the token service. + void RequestAccessToken(); + + // Exchanges an oauth2 access token for an uber-auth token. + void ExchangeTokens(); + + OAuth2TokenService* token_service_; + CompletionCallback ubertoken_callback_; + scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_; + bool is_bound_to_channel_id_; // defaults to true + GaiaAuthFetcherFactory gaia_auth_fetcher_factory_; + std::unique_ptr<GaiaAuthFetcher> gaia_auth_fetcher_; + std::unique_ptr<OAuth2TokenService::Request> access_token_request_; + std::string account_id_; + std::string access_token_; + int retry_number_; + base::OneShotTimer retry_timer_; + bool second_access_token_request_; + + DISALLOW_COPY_AND_ASSIGN(UbertokenFetcherImpl); +}; + +} // namespace signin + +#endif // COMPONENTS_SIGNIN_CORE_BROWSER_UBERTOKEN_FETCHER_IMPL_H_ diff --git a/chromium/components/signin/core/browser/ubertoken_fetcher_impl_unittest.cc b/chromium/components/signin/core/browser/ubertoken_fetcher_impl_unittest.cc new file mode 100644 index 00000000000..b9ce26d86f9 --- /dev/null +++ b/chromium/components/signin/core/browser/ubertoken_fetcher_impl_unittest.cc @@ -0,0 +1,179 @@ +// Copyright 2014 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 "components/signin/core/browser/ubertoken_fetcher_impl.h" + +#include <memory> + +#include "base/memory/ref_counted.h" +#include "base/test/scoped_task_environment.h" +#include "base/threading/thread_task_runner_handle.h" +#include "google_apis/gaia/fake_oauth2_token_service.h" +#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h" +#include "services/network/test/test_url_loader_factory.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +const char kTestAccountId[] = "test@gmail.com"; + +class MockUbertokenConsumer { + public: + MockUbertokenConsumer() + : nb_correct_token_(0), + last_error_(GoogleServiceAuthError::AuthErrorNone()), + nb_error_(0) {} + virtual ~MockUbertokenConsumer() = default; + + void OnUbertokenFetchComplete(GoogleServiceAuthError error, + const std::string& token) { + if (error != GoogleServiceAuthError::AuthErrorNone()) { + last_error_ = error; + ++nb_error_; + return; + } + + last_token_ = token; + ++nb_correct_token_; + } + + std::string last_token_; + int nb_correct_token_; + GoogleServiceAuthError last_error_; + int nb_error_; +}; + +} // namespace + +class UbertokenFetcherImplTest : public testing::Test { + public: + UbertokenFetcherImplTest() + : scoped_task_environment_( + base::test::ScopedTaskEnvironment::MainThreadType::UI), + test_shared_loader_factory_( + base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>( + &url_loader_factory_)) { + fetcher_ = std::make_unique<signin::UbertokenFetcherImpl>( + kTestAccountId, &token_service_, + base::BindOnce(&MockUbertokenConsumer::OnUbertokenFetchComplete, + base::Unretained(&consumer_)), + gaia::GaiaSource::kChrome, test_shared_loader_factory_); + } + + protected: + base::test::ScopedTaskEnvironment scoped_task_environment_; + FakeOAuth2TokenService token_service_; + network::TestURLLoaderFactory url_loader_factory_; + scoped_refptr<network::SharedURLLoaderFactory> test_shared_loader_factory_; + MockUbertokenConsumer consumer_; + std::unique_ptr<signin::UbertokenFetcherImpl> fetcher_; +}; + +TEST_F(UbertokenFetcherImplTest, Basic) {} + +TEST_F(UbertokenFetcherImplTest, Success) { + fetcher_->OnGetTokenSuccess(NULL, + OAuth2AccessTokenConsumer::TokenResponse( + "accessToken", base::Time(), std::string())); + fetcher_->OnUberAuthTokenSuccess("uberToken"); + + EXPECT_EQ(0, consumer_.nb_error_); + EXPECT_EQ(1, consumer_.nb_correct_token_); + EXPECT_EQ("uberToken", consumer_.last_token_); +} + +TEST_F(UbertokenFetcherImplTest, NoRefreshToken) { + GoogleServiceAuthError error(GoogleServiceAuthError::USER_NOT_SIGNED_UP); + fetcher_->OnGetTokenFailure(NULL, error); + + EXPECT_EQ(1, consumer_.nb_error_); + EXPECT_EQ(0, consumer_.nb_correct_token_); +} + +TEST_F(UbertokenFetcherImplTest, FailureToGetAccessToken) { + GoogleServiceAuthError error(GoogleServiceAuthError::USER_NOT_SIGNED_UP); + fetcher_->OnGetTokenFailure(NULL, error); + + EXPECT_EQ(1, consumer_.nb_error_); + EXPECT_EQ(0, consumer_.nb_correct_token_); + EXPECT_EQ("", consumer_.last_token_); +} + +TEST_F(UbertokenFetcherImplTest, TransientFailureEventualFailure) { + GoogleServiceAuthError error(GoogleServiceAuthError::CONNECTION_FAILED); + fetcher_->OnGetTokenSuccess(NULL, + OAuth2AccessTokenConsumer::TokenResponse( + "accessToken", base::Time(), std::string())); + + for (int i = 0; i < signin::UbertokenFetcherImpl::kMaxRetries; ++i) { + fetcher_->OnUberAuthTokenFailure(error); + EXPECT_EQ(0, consumer_.nb_error_); + EXPECT_EQ(0, consumer_.nb_correct_token_); + EXPECT_EQ("", consumer_.last_token_); + } + + fetcher_->OnUberAuthTokenFailure(error); + EXPECT_EQ(1, consumer_.nb_error_); + EXPECT_EQ(0, consumer_.nb_correct_token_); + EXPECT_EQ("", consumer_.last_token_); +} + +TEST_F(UbertokenFetcherImplTest, TransientFailureEventualSuccess) { + GoogleServiceAuthError error(GoogleServiceAuthError::CONNECTION_FAILED); + fetcher_->OnGetTokenSuccess(NULL, + OAuth2AccessTokenConsumer::TokenResponse( + "accessToken", base::Time(), std::string())); + + for (int i = 0; i < signin::UbertokenFetcherImpl::kMaxRetries; ++i) { + fetcher_->OnUberAuthTokenFailure(error); + EXPECT_EQ(0, consumer_.nb_error_); + EXPECT_EQ(0, consumer_.nb_correct_token_); + EXPECT_EQ("", consumer_.last_token_); + } + + fetcher_->OnUberAuthTokenSuccess("uberToken"); + EXPECT_EQ(0, consumer_.nb_error_); + EXPECT_EQ(1, consumer_.nb_correct_token_); + EXPECT_EQ("uberToken", consumer_.last_token_); +} + +TEST_F(UbertokenFetcherImplTest, PermanentFailureEventualFailure) { + fetcher_->OnGetTokenSuccess(NULL, + OAuth2AccessTokenConsumer::TokenResponse( + "accessToken", base::Time(), std::string())); + + GoogleServiceAuthError error(GoogleServiceAuthError::USER_NOT_SIGNED_UP); + fetcher_->OnUberAuthTokenFailure(error); + EXPECT_EQ(0, consumer_.nb_error_); + EXPECT_EQ(0, consumer_.nb_correct_token_); + EXPECT_EQ("", consumer_.last_token_); + + fetcher_->OnGetTokenSuccess(NULL, + OAuth2AccessTokenConsumer::TokenResponse( + "accessToken", base::Time(), std::string())); + fetcher_->OnUberAuthTokenFailure(error); + EXPECT_EQ(1, consumer_.nb_error_); + EXPECT_EQ(0, consumer_.nb_correct_token_); + EXPECT_EQ("", consumer_.last_token_); +} + +TEST_F(UbertokenFetcherImplTest, PermanentFailureEventualSuccess) { + GoogleServiceAuthError error(GoogleServiceAuthError::USER_NOT_SIGNED_UP); + fetcher_->OnGetTokenSuccess(NULL, + OAuth2AccessTokenConsumer::TokenResponse( + "accessToken", base::Time(), std::string())); + + fetcher_->OnUberAuthTokenFailure(error); + EXPECT_EQ(0, consumer_.nb_error_); + EXPECT_EQ(0, consumer_.nb_correct_token_); + EXPECT_EQ("", consumer_.last_token_); + + fetcher_->OnGetTokenSuccess(NULL, + OAuth2AccessTokenConsumer::TokenResponse( + "accessToken", base::Time(), std::string())); + fetcher_->OnUberAuthTokenSuccess("uberToken"); + EXPECT_EQ(0, consumer_.nb_error_); + EXPECT_EQ(1, consumer_.nb_correct_token_); + EXPECT_EQ("uberToken", consumer_.last_token_); +} diff --git a/chromium/components/signin/ios/DEPS b/chromium/components/signin/ios/DEPS index 4dd63070bb3..bb46705add5 100644 --- a/chromium/components/signin/ios/DEPS +++ b/chromium/components/signin/ios/DEPS @@ -1,4 +1,5 @@ include_rules = [ "+ios/web/public", + "+services/identity/public/cpp", "+third_party/ocmock", ] diff --git a/chromium/components/signin/ios/browser/BUILD.gn b/chromium/components/signin/ios/browser/BUILD.gn index 9a5285b7585..088e53e3f0b 100644 --- a/chromium/components/signin/ios/browser/BUILD.gn +++ b/chromium/components/signin/ios/browser/BUILD.gn @@ -10,8 +10,6 @@ source_set("browser") { "manage_accounts_delegate.h", "merge_session_observer_bridge.h", "merge_session_observer_bridge.mm", - "oauth2_token_service_observer_bridge.h", - "oauth2_token_service_observer_bridge.mm", "profile_oauth2_token_service_ios_delegate.h", "profile_oauth2_token_service_ios_delegate.mm", "profile_oauth2_token_service_ios_provider.h", @@ -31,6 +29,7 @@ source_set("browser") { "//google_apis", "//ios/web", "//net", + "//services/identity/public/cpp:cpp", ] } @@ -78,11 +77,12 @@ source_set("unit_tests") { ":test_support", "//components/prefs:test_support", "//components/signin/core/browser", - "//components/signin/core/browser:test_support", + "//components/signin/core/browser:internals_test_support", "//components/sync_preferences:test_support", "//ios/web", "//ios/web/public/test", "//ios/web/public/test/fakes", + "//services/identity/public/cpp:test_support", "//third_party/ocmock", ] } diff --git a/chromium/components/signin/ios/browser/account_consistency_service.h b/chromium/components/signin/ios/browser/account_consistency_service.h index 4488de9d378..e7ebe2345e7 100644 --- a/chromium/components/signin/ios/browser/account_consistency_service.h +++ b/chromium/components/signin/ios/browser/account_consistency_service.h @@ -18,9 +18,9 @@ #include "components/content_settings/core/browser/cookie_settings.h" #include "components/keyed_service/core/keyed_service.h" #include "components/signin/core/browser/gaia_cookie_manager_service.h" -#include "components/signin/core/browser/signin_manager.h" #include "components/signin/ios/browser/active_state_manager.h" #import "components/signin/ios/browser/manage_accounts_delegate.h" +#import "services/identity/public/cpp/identity_manager.h" namespace web { class BrowserState; @@ -41,7 +41,7 @@ class SigninClient; // This is currently only used when WKWebView is enabled. class AccountConsistencyService : public KeyedService, public GaiaCookieManagerService::Observer, - public SigninManagerBase::Observer, + public identity::IdentityManager::Observer, public ActiveStateManager::Observer { public: // Name of the preference property that persists the domains that have a @@ -54,7 +54,7 @@ class AccountConsistencyService : public KeyedService, scoped_refptr<content_settings::CookieSettings> cookie_settings, GaiaCookieManagerService* gaia_cookie_manager_service, SigninClient* signin_client, - SigninManager* signin_manager); + identity::IdentityManager* identity_manager); ~AccountConsistencyService() override; // Registers the preferences used by AccountConsistencyService. @@ -155,9 +155,10 @@ class AccountConsistencyService : public KeyedService, const std::vector<gaia::ListedAccount>& signed_out_accounts, const GoogleServiceAuthError& error) override; - // SigninManagerBase::Observer implementation. - void GoogleSigninSucceeded(const AccountInfo& account_info) override; - void GoogleSignedOut(const AccountInfo& account_info) override; + // IdentityManager::Observer implementation. + void OnPrimaryAccountSet(const AccountInfo& account_info) override; + void OnPrimaryAccountCleared( + const AccountInfo& previous_account_info) override; // ActiveStateManager::Observer implementation. void OnActive() override; @@ -176,8 +177,9 @@ class AccountConsistencyService : public KeyedService, GaiaCookieManagerService* gaia_cookie_manager_service_; // Signin client, used to access prefs. SigninClient* signin_client_; - // Signin manager, observed to be notified of signin and signout events. - SigninManager* signin_manager_; + // Identity manager, observed to be notified of primary account signin and + // signout events. + identity::IdentityManager* identity_manager_; // Whether a CHROME_CONNECTED cookie request is currently being applied. bool applying_cookie_requests_; diff --git a/chromium/components/signin/ios/browser/account_consistency_service.mm b/chromium/components/signin/ios/browser/account_consistency_service.mm index 39095fa67ef..f4de0d48b49 100644 --- a/chromium/components/signin/ios/browser/account_consistency_service.mm +++ b/chromium/components/signin/ios/browser/account_consistency_service.mm @@ -221,19 +221,19 @@ AccountConsistencyService::AccountConsistencyService( scoped_refptr<content_settings::CookieSettings> cookie_settings, GaiaCookieManagerService* gaia_cookie_manager_service, SigninClient* signin_client, - SigninManager* signin_manager) + identity::IdentityManager* identity_manager) : browser_state_(browser_state), account_reconcilor_(account_reconcilor), cookie_settings_(cookie_settings), gaia_cookie_manager_service_(gaia_cookie_manager_service), signin_client_(signin_client), - signin_manager_(signin_manager), + identity_manager_(identity_manager), applying_cookie_requests_(false) { gaia_cookie_manager_service_->AddObserver(this); - signin_manager_->AddObserver(this); + identity_manager_->AddObserver(this); ActiveStateManager::FromBrowserState(browser_state_)->AddObserver(this); LoadFromPrefs(); - if (signin_manager_->IsAuthenticated()) { + if (identity_manager_->HasPrimaryAccount()) { AddChromeConnectedCookies(); } else { RemoveChromeConnectedCookies(base::OnceClosure()); @@ -333,7 +333,7 @@ void AccountConsistencyService::LoadFromPrefs() { void AccountConsistencyService::Shutdown() { gaia_cookie_manager_service_->RemoveObserver(this); - signin_manager_->RemoveObserver(this); + identity_manager_->RemoveObserver(this); ActiveStateManager::FromBrowserState(browser_state_)->RemoveObserver(this); ResetWKWebView(); web_state_handlers_.clear(); @@ -363,7 +363,7 @@ void AccountConsistencyService::ApplyCookieRequests() { switch (cookie_requests_.front().request_type) { case ADD_CHROME_CONNECTED_COOKIE: cookie_value = signin::BuildMirrorRequestCookieIfPossible( - url, signin_manager_->GetAuthenticatedAccountInfo().gaia, + url, identity_manager_->GetPrimaryAccountInfo().gaia, signin::AccountConsistencyMethod::kMirror, cookie_settings_.get(), signin::PROFILE_MODE_DEFAULT); if (cookie_value.empty()) { @@ -490,13 +490,13 @@ void AccountConsistencyService::OnGaiaAccountsInCookieUpdated( AddChromeConnectedCookies(); } -void AccountConsistencyService::GoogleSigninSucceeded( +void AccountConsistencyService::OnPrimaryAccountSet( const AccountInfo& account_info) { AddChromeConnectedCookies(); } -void AccountConsistencyService::GoogleSignedOut( - const AccountInfo& account_info) { +void AccountConsistencyService::OnPrimaryAccountCleared( + const AccountInfo& previous_account_info) { // There is not need to remove CHROME_CONNECTED cookies on |GoogleSignedOut| // events as these cookies will be removed by the GaiaCookieManagerServer // right before fetching the Gaia logout request. diff --git a/chromium/components/signin/ios/browser/account_consistency_service_unittest.mm b/chromium/components/signin/ios/browser/account_consistency_service_unittest.mm index 4193365a296..1a7a24a2303 100644 --- a/chromium/components/signin/ios/browser/account_consistency_service_unittest.mm +++ b/chromium/components/signin/ios/browser/account_consistency_service_unittest.mm @@ -14,6 +14,7 @@ #include "components/signin/core/browser/account_reconcilor.h" #include "components/signin/core/browser/account_reconcilor_delegate.h" #include "components/signin/core/browser/account_tracker_service.h" +#include "components/signin/core/browser/fake_gaia_cookie_manager_service.h" #include "components/signin/core/browser/fake_profile_oauth2_token_service.h" #include "components/signin/core/browser/fake_signin_manager.h" #include "components/signin/core/browser/gaia_cookie_manager_service.h" @@ -24,6 +25,8 @@ #import "ios/web/public/test/fakes/test_web_state.h" #include "ios/web/public/test/test_web_thread_bundle.h" #include "ios/web/public/web_state/web_state_policy_decider.h" +#import "services/identity/public/cpp/identity_manager.h" +#import "services/identity/public/cpp/identity_test_environment.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" #include "testing/platform_test.h" @@ -59,13 +62,13 @@ class FakeAccountConsistencyService : public AccountConsistencyService { scoped_refptr<content_settings::CookieSettings> cookie_settings, GaiaCookieManagerService* gaia_cookie_manager_service, SigninClient* signin_client, - SigninManager* signin_manager) + identity::IdentityManager* identity_manager) : AccountConsistencyService(browser_state, account_reconcilor, cookie_settings, gaia_cookie_manager_service, signin_client, - signin_manager) {} + identity_manager) {} private: WKWebView* BuildWKWebView() override { @@ -83,20 +86,29 @@ class MockAccountReconcilor : public AccountReconcilor { MockAccountReconcilor(SigninClient* client) : AccountReconcilor( nullptr, - nullptr, client, nullptr, std::make_unique<signin::AccountReconcilorDelegate>()) {} MOCK_METHOD1(OnReceivedManageAccountsResponse, void(signin::GAIAServiceType)); }; -// Mock GaiaCookieManagerService to catch call to ForceOnCookieChangeProcessing -class MockGaiaCookieManagerService : public GaiaCookieManagerService { +// Mock GaiaCookieManagerService to catch call to ForceOnCookieChangeProcessing. +// It isn't an actual mock as it is not desirable to extend a Mock from a Fake. +class CustomGaiaCookieManagerService : public FakeGaiaCookieManagerService { public: - MockGaiaCookieManagerService() - : GaiaCookieManagerService(nullptr, - nullptr) {} - MOCK_METHOD0(ForceOnCookieChangeProcessing, void()); + CustomGaiaCookieManagerService() + : FakeGaiaCookieManagerService(nullptr, nullptr), + calls_to_force_on_cookie_change_processing_(0) {} + + uint8_t CallsToForceOnCookieChangeProcessing() { + return calls_to_force_on_cookie_change_processing_; + } + + private: + void ForceOnCookieChangeProcessing() override { + calls_to_force_on_cookie_change_processing_++; + }; + uint8_t calls_to_force_on_cookie_change_processing_; }; // TestWebState that allows control over its policy decider. @@ -147,7 +159,7 @@ class AccountConsistencyServiceTest : public PlatformTest { SigninManagerBase::RegisterProfilePrefs(prefs_.registry()); web_view_load_expection_count_ = 0; - gaia_cookie_manager_service_.reset(new MockGaiaCookieManagerService()); + gaia_cookie_manager_service_.reset(new CustomGaiaCookieManagerService()); signin_client_.reset(new TestSigninClient(&prefs_)); account_tracker_service_.Initialize(&prefs_, base::FilePath()); token_service_.reset(new FakeProfileOAuth2TokenService(&prefs_)); @@ -155,6 +167,9 @@ class AccountConsistencyServiceTest : public PlatformTest { new FakeSigninManager(signin_client_.get(), token_service_.get(), &account_tracker_service_, nullptr)); signin_manager_->Initialize(nullptr); + identity_test_env_.reset(new identity::IdentityTestEnvironment( + &account_tracker_service_, token_service_.get(), signin_manager_.get(), + gaia_cookie_manager_service_.get())); settings_map_ = new HostContentSettingsMap( &prefs_, false /* incognito_profile */, false /* guest_profile */, false /* store_last_modified */, @@ -172,6 +187,7 @@ class AccountConsistencyServiceTest : public PlatformTest { account_consistency_service_->Shutdown(); settings_map_->ShutdownOnUIThread(); ActiveStateManager::FromBrowserState(&browser_state_)->SetActive(false); + identity_test_env_.reset(); PlatformTest::TearDown(); } @@ -201,7 +217,7 @@ class AccountConsistencyServiceTest : public PlatformTest { account_consistency_service_.reset(new FakeAccountConsistencyService( &browser_state_, account_reconcilor_.get(), cookie_settings_, gaia_cookie_manager_service_.get(), signin_client_.get(), - signin_manager_.get())); + identity_test_env_->identity_manager())); } void SignIn() { @@ -256,13 +272,14 @@ class AccountConsistencyServiceTest : public PlatformTest { web::TestBrowserState browser_state_; sync_preferences::TestingPrefServiceSyncable prefs_; TestWebState web_state_; + std::unique_ptr<identity::IdentityTestEnvironment> identity_test_env_; // AccountConsistencyService being tested. Actually a // FakeAccountConsistencyService to be able to use a mock web view. std::unique_ptr<AccountConsistencyService> account_consistency_service_; std::unique_ptr<TestSigninClient> signin_client_; std::unique_ptr<FakeProfileOAuth2TokenService> token_service_; std::unique_ptr<FakeSigninManager> signin_manager_; - std::unique_ptr<MockGaiaCookieManagerService> gaia_cookie_manager_service_; + std::unique_ptr<CustomGaiaCookieManagerService> gaia_cookie_manager_service_; std::unique_ptr<MockAccountReconcilor> account_reconcilor_; scoped_refptr<HostContentSettingsMap> settings_map_; scoped_refptr<content_settings::CookieSettings> cookie_settings_; @@ -451,11 +468,11 @@ TEST_F(AccountConsistencyServiceTest, DomainsClearedOnBrowsingDataRemoved) { prefs_.GetDictionary(AccountConsistencyService::kDomainsWithCookiePref); EXPECT_EQ(2u, dict->size()); - EXPECT_CALL(*gaia_cookie_manager_service_, ForceOnCookieChangeProcessing()) - .Times(1); account_consistency_service_->OnBrowsingDataRemoved(); dict = prefs_.GetDictionary(AccountConsistencyService::kDomainsWithCookiePref); + EXPECT_EQ( + 1u, gaia_cookie_manager_service_->CallsToForceOnCookieChangeProcessing()); EXPECT_EQ(0u, dict->size()); } @@ -468,9 +485,9 @@ TEST_F(AccountConsistencyServiceTest, DomainsClearedOnBrowsingDataRemoved2) { AddPageLoadedExpectation(kGoogleUrl, false /* continue_navigation */); SimulateGaiaCookieManagerServiceLogout(false); - EXPECT_CALL(*gaia_cookie_manager_service_, ForceOnCookieChangeProcessing()) - .Times(1); account_consistency_service_->OnBrowsingDataRemoved(); + EXPECT_EQ( + 1u, gaia_cookie_manager_service_->CallsToForceOnCookieChangeProcessing()); EXPECT_TRUE(remove_cookie_callback_called_); } diff --git a/chromium/components/signin/ios/browser/oauth2_token_service_observer_bridge.h b/chromium/components/signin/ios/browser/oauth2_token_service_observer_bridge.h deleted file mode 100644 index a168fac439a..00000000000 --- a/chromium/components/signin/ios/browser/oauth2_token_service_observer_bridge.h +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2014 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. - -#ifndef COMPONENTS_SIGNIN_IOS_BROWSER_OAUTH2_TOKEN_SERVICE_OBSERVER_BRIDGE_H_ -#define COMPONENTS_SIGNIN_IOS_BROWSER_OAUTH2_TOKEN_SERVICE_OBSERVER_BRIDGE_H_ - -#import <Foundation/Foundation.h> - -#include "base/macros.h" -#include "google_apis/gaia/oauth2_token_service.h" - -@protocol OAuth2TokenServiceObserverBridgeDelegate <NSObject> - -@optional - -// Informs the delegate that a refresh token is avaible for |account_id|. -- (void)onRefreshTokenAvailable:(const std::string&)account_id; - -// Informs the delegate that the refresh token was revoked for |account_id|. -- (void)onRefreshTokenRevoked:(const std::string&)account_id; - -// Informs the delegate that the token service has finished loading the tokens. -- (void)onRefreshTokensLoaded; - -// Informs the delegate that a batch of refresh token changes will start. -- (void)onStartBatchChanges; - -// Informs the delegate that a batch of refresh token changes has ended. -- (void)onEndBatchChanges; - -@end - -// Bridge class that listens for |OAuth2TokenService| notifications and passes -// them to its Objective-C delegate. -class OAuth2TokenServiceObserverBridge : public OAuth2TokenService::Observer { - public: - OAuth2TokenServiceObserverBridge( - OAuth2TokenService* token_service, - id<OAuth2TokenServiceObserverBridgeDelegate> delegate); - ~OAuth2TokenServiceObserverBridge() override; - - // OAuth2TokenService::Observer - void OnRefreshTokenAvailable(const std::string& account_id) override; - void OnRefreshTokenRevoked(const std::string& account_id) override; - void OnRefreshTokensLoaded() override; - void OnStartBatchChanges() override; - void OnEndBatchChanges() override; - - private: - OAuth2TokenService* token_service_; // weak - __weak id<OAuth2TokenServiceObserverBridgeDelegate> delegate_; - - DISALLOW_COPY_AND_ASSIGN(OAuth2TokenServiceObserverBridge); -}; - -#endif // COMPONENTS_SIGNIN_IOS_BROWSER_OAUTH2_TOKEN_SERVICE_OBSERVER_BRIDGE_H_ diff --git a/chromium/components/signin/ios/browser/oauth2_token_service_observer_bridge.mm b/chromium/components/signin/ios/browser/oauth2_token_service_observer_bridge.mm deleted file mode 100644 index ddaddb068d3..00000000000 --- a/chromium/components/signin/ios/browser/oauth2_token_service_observer_bridge.mm +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2014 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 "components/signin/ios/browser/oauth2_token_service_observer_bridge.h" - -#if !defined(__has_feature) || !__has_feature(objc_arc) -#error "This file requires ARC support." -#endif - -OAuth2TokenServiceObserverBridge::OAuth2TokenServiceObserverBridge( - OAuth2TokenService* token_service, - id<OAuth2TokenServiceObserverBridgeDelegate> delegate) - : token_service_(token_service), - delegate_(delegate) { - DCHECK(token_service_); - token_service_->AddObserver(this); -} -OAuth2TokenServiceObserverBridge::~OAuth2TokenServiceObserverBridge() { - token_service_->RemoveObserver(this); -} - -void OAuth2TokenServiceObserverBridge::OnRefreshTokenAvailable( - const std::string& account_id) { - if ([delegate_ respondsToSelector:@selector(onRefreshTokenAvailable:)]) { - [delegate_ onRefreshTokenAvailable:account_id]; - } -} - -void OAuth2TokenServiceObserverBridge::OnRefreshTokenRevoked( - const std::string& account_id) { - if ([delegate_ respondsToSelector:@selector(onRefreshTokenRevoked:)]) { - [delegate_ onRefreshTokenRevoked:account_id]; - } -} -void OAuth2TokenServiceObserverBridge::OnRefreshTokensLoaded() { - if ([delegate_ respondsToSelector:@selector(onRefreshTokensLoaded)]) { - [delegate_ onRefreshTokensLoaded]; - } -} -void OAuth2TokenServiceObserverBridge::OnStartBatchChanges() { - if ([delegate_ respondsToSelector:@selector(onStartBatchChanges)]) { - [delegate_ onStartBatchChanges]; - } - -} - -void OAuth2TokenServiceObserverBridge::OnEndBatchChanges() { - if ([delegate_ respondsToSelector:@selector(onEndBatchChanges)]) { - [delegate_ onEndBatchChanges]; - } -} diff --git a/chromium/components/signin/ios/browser/profile_oauth2_token_service_ios_delegate.h b/chromium/components/signin/ios/browser/profile_oauth2_token_service_ios_delegate.h index 1f13b5dcaab..69168d83eee 100644 --- a/chromium/components/signin/ios/browser/profile_oauth2_token_service_ios_delegate.h +++ b/chromium/components/signin/ios/browser/profile_oauth2_token_service_ios_delegate.h @@ -8,9 +8,7 @@ #include "base/gtest_prod_util.h" #include "base/macros.h" -#include "base/memory/linked_ptr.h" #include "base/threading/thread_checker.h" -#include "components/signin/core/browser/signin_error_controller.h" #include "google_apis/gaia/oauth2_token_service_delegate.h" class AccountTrackerService; @@ -22,8 +20,7 @@ class ProfileOAuth2TokenServiceIOSDelegate : public OAuth2TokenServiceDelegate { ProfileOAuth2TokenServiceIOSDelegate( SigninClient* client, std::unique_ptr<ProfileOAuth2TokenServiceIOSProvider> provider, - AccountTrackerService* account_tracker_service, - SigninErrorController* signin_error_controller); + AccountTrackerService* account_tracker_service); ~ProfileOAuth2TokenServiceIOSDelegate() override; OAuth2AccessTokenFetcher* CreateAccessTokenFetcher( @@ -78,29 +75,13 @@ class ProfileOAuth2TokenServiceIOSDelegate : public OAuth2TokenServiceDelegate { FRIEND_TEST_ALL_PREFIXES(ProfileOAuth2TokenServiceIOSDelegateTest, LoadRevokeCredentialsClearsExcludedAccounts); - class AccountStatus : public SigninErrorController::AuthStatusProvider { - public: - AccountStatus(SigninErrorController* signin_error_controller, - const std::string& account_id); - ~AccountStatus() override; - - void SetLastAuthError(const GoogleServiceAuthError& error); - - // SigninErrorController::AuthStatusProvider implementation. - std::string GetAccountId() const override; - GoogleServiceAuthError GetAuthStatus() const override; - - private: - SigninErrorController* signin_error_controller_; - std::string account_id_; - GoogleServiceAuthError last_auth_error_; - - DISALLOW_COPY_AND_ASSIGN(AccountStatus); + struct AccountStatus { + GoogleServiceAuthError last_auth_error; }; // Maps the |account_id| of accounts known to ProfileOAuth2TokenService // to information about the account. - typedef std::map<std::string, linked_ptr<AccountStatus>> AccountStatusMap; + typedef std::map<std::string, AccountStatus> AccountStatusMap; // Clears exclude secondary accounts preferences. void ClearExcludedSecondaryAccounts(); @@ -121,9 +102,6 @@ class ProfileOAuth2TokenServiceIOSDelegate : public OAuth2TokenServiceDelegate { std::unique_ptr<ProfileOAuth2TokenServiceIOSProvider> provider_; AccountTrackerService* account_tracker_service_; - // The error controller with which this instance was initialized, or NULL. - SigninErrorController* signin_error_controller_; - DISALLOW_COPY_AND_ASSIGN(ProfileOAuth2TokenServiceIOSDelegate); }; #endif // COMPONENTS_SIGNIN_IOS_BROWSER_PROFILE_OAUTH2_TOKEN_SERVICE_IOS_DELEGATE_H_ diff --git a/chromium/components/signin/ios/browser/profile_oauth2_token_service_ios_delegate.mm b/chromium/components/signin/ios/browser/profile_oauth2_token_service_ios_delegate.mm index c27f49c70fa..ad6e03de3a7 100644 --- a/chromium/components/signin/ios/browser/profile_oauth2_token_service_ios_delegate.mm +++ b/chromium/components/signin/ios/browser/profile_oauth2_token_service_ios_delegate.mm @@ -147,50 +147,16 @@ void SSOAccessTokenFetcher::OnAccessTokenResponse(NSString* token, } // namespace -ProfileOAuth2TokenServiceIOSDelegate::AccountStatus::AccountStatus( - SigninErrorController* signin_error_controller, - const std::string& account_id) - : signin_error_controller_(signin_error_controller), - account_id_(account_id), - last_auth_error_(GoogleServiceAuthError::NONE) { - DCHECK(signin_error_controller_); - DCHECK(!account_id_.empty()); - signin_error_controller_->AddProvider(this); -} - -ProfileOAuth2TokenServiceIOSDelegate::AccountStatus::~AccountStatus() { - signin_error_controller_->RemoveProvider(this); -} - -void ProfileOAuth2TokenServiceIOSDelegate::AccountStatus::SetLastAuthError( - const GoogleServiceAuthError& error) { - last_auth_error_ = error; - signin_error_controller_->AuthStatusChanged(); -} - -std::string ProfileOAuth2TokenServiceIOSDelegate::AccountStatus::GetAccountId() - const { - return account_id_; -} - -GoogleServiceAuthError -ProfileOAuth2TokenServiceIOSDelegate::AccountStatus::GetAuthStatus() const { - return last_auth_error_; -} - ProfileOAuth2TokenServiceIOSDelegate::ProfileOAuth2TokenServiceIOSDelegate( SigninClient* client, std::unique_ptr<ProfileOAuth2TokenServiceIOSProvider> provider, - AccountTrackerService* account_tracker_service, - SigninErrorController* signin_error_controller) + AccountTrackerService* account_tracker_service) : client_(client), provider_(std::move(provider)), - account_tracker_service_(account_tracker_service), - signin_error_controller_(signin_error_controller) { + account_tracker_service_(account_tracker_service) { DCHECK(client_); DCHECK(provider_); DCHECK(account_tracker_service_); - DCHECK(signin_error_controller_); } ProfileOAuth2TokenServiceIOSDelegate::~ProfileOAuth2TokenServiceIOSDelegate() { @@ -333,7 +299,7 @@ GoogleServiceAuthError ProfileOAuth2TokenServiceIOSDelegate::GetAuthError( const std::string& account_id) const { auto it = accounts_.find(account_id); return (it == accounts_.end()) ? GoogleServiceAuthError::AuthErrorNone() - : it->second->GetAuthStatus(); + : it->second.last_auth_error; } void ProfileOAuth2TokenServiceIOSDelegate::UpdateAuthError( @@ -352,9 +318,9 @@ void ProfileOAuth2TokenServiceIOSDelegate::UpdateAuthError( return; } - AccountStatus* status = accounts_[account_id].get(); - if (error.state() != status->GetAuthStatus().state()) { - status->SetLastAuthError(error); + AccountStatus* status = &accounts_[account_id]; + if (error.state() != status->last_auth_error.state()) { + status->last_auth_error = error; FireAuthErrorChanged(account_id, error); } } @@ -370,21 +336,16 @@ void ProfileOAuth2TokenServiceIOSDelegate::AddOrUpdateAccount( DCHECK(!account_tracker_service_->GetAccountInfo(account_id).email.empty()); bool account_present = accounts_.count(account_id) > 0; - if (account_present && - accounts_[account_id]->GetAuthStatus().state() == - GoogleServiceAuthError::NONE) { + if (account_present && accounts_[account_id].last_auth_error.state() == + GoogleServiceAuthError::NONE) { // No need to update the account if it is already a known account and if // there is no auth error. return; } - if (!account_present) { - accounts_[account_id].reset( - new AccountStatus(signin_error_controller_, account_id)); - FireAuthErrorChanged(account_id, accounts_[account_id]->GetAuthStatus()); - } - - UpdateAuthError(account_id, GoogleServiceAuthError::AuthErrorNone()); + accounts_[account_id].last_auth_error = + GoogleServiceAuthError::AuthErrorNone(); + FireAuthErrorChanged(account_id, GoogleServiceAuthError::AuthErrorNone()); FireRefreshTokenAvailable(account_id); } diff --git a/chromium/components/signin/ios/browser/profile_oauth2_token_service_ios_delegate_unittest.mm b/chromium/components/signin/ios/browser/profile_oauth2_token_service_ios_delegate_unittest.mm index 6af23b48139..aec0b1cc31e 100644 --- a/chromium/components/signin/ios/browser/profile_oauth2_token_service_ios_delegate_unittest.mm +++ b/chromium/components/signin/ios/browser/profile_oauth2_token_service_ios_delegate_unittest.mm @@ -11,7 +11,6 @@ #include "components/prefs/testing_pref_service.h" #include "components/signin/core/browser/account_tracker_service.h" #include "components/signin/core/browser/profile_oauth2_token_service.h" -#include "components/signin/core/browser/signin_error_controller.h" #include "components/signin/core/browser/signin_pref_names.h" #include "components/signin/core/browser/test_signin_client.h" #include "components/signin/ios/browser/fake_profile_oauth2_token_service_ios_provider.h" @@ -30,29 +29,21 @@ typedef ProfileOAuth2TokenServiceIOSProvider::AccountInfo ProviderAccount; class ProfileOAuth2TokenServiceIOSDelegateTest : public testing::Test, public OAuth2AccessTokenConsumer, - public OAuth2TokenService::Observer, - public SigninErrorController::Observer { + public OAuth2TokenService::Observer { public: ProfileOAuth2TokenServiceIOSDelegateTest() : factory_(NULL), client_(&prefs_), - signin_error_controller_( - SigninErrorController::AccountMode::ANY_ACCOUNT), token_available_count_(0), token_revoked_count_(0), tokens_loaded_count_(0), access_token_success_(0), access_token_failure_(0), - error_changed_count_(0), auth_error_changed_count_(0), last_access_token_error_(GoogleServiceAuthError::NONE) {} void SetUp() override { - prefs_.registry()->RegisterListPref( - AccountTrackerService::kAccountInfoPref); - prefs_.registry()->RegisterIntegerPref( - prefs::kAccountIdMigrationState, - AccountTrackerService::MIGRATION_NOT_STARTED); + AccountTrackerService::RegisterPrefs(prefs_.registry()); account_tracker_.Initialize(&prefs_, base::FilePath()); prefs_.registry()->RegisterBooleanPref( @@ -64,14 +55,11 @@ class ProfileOAuth2TokenServiceIOSDelegateTest factory_.SetFakeResponse(GaiaUrls::GetInstance()->oauth2_revoke_url(), "", net::HTTP_OK, net::URLRequestStatus::SUCCESS); oauth2_delegate_.reset(new ProfileOAuth2TokenServiceIOSDelegate( - &client_, base::WrapUnique(fake_provider_), &account_tracker_, - &signin_error_controller_)); + &client_, base::WrapUnique(fake_provider_), &account_tracker_)); oauth2_delegate_->AddObserver(this); - signin_error_controller_.AddObserver(this); } void TearDown() override { - signin_error_controller_.RemoveObserver(this); oauth2_delegate_->RemoveObserver(this); oauth2_delegate_->Shutdown(); } @@ -100,16 +88,12 @@ class ProfileOAuth2TokenServiceIOSDelegateTest ++auth_error_changed_count_; } - // SigninErrorController::Observer implementation. - void OnErrorChanged() override { ++error_changed_count_; } - void ResetObserverCounts() { token_available_count_ = 0; token_revoked_count_ = 0; tokens_loaded_count_ = 0; token_available_count_ = 0; access_token_failure_ = 0; - error_changed_count_ = 0; auth_error_changed_count_ = 0; } @@ -124,7 +108,6 @@ class ProfileOAuth2TokenServiceIOSDelegateTest TestingPrefServiceSimple prefs_; TestSigninClient client_; AccountTrackerService account_tracker_; - SigninErrorController signin_error_controller_; FakeProfileOAuth2TokenServiceIOSProvider* fake_provider_; std::unique_ptr<ProfileOAuth2TokenServiceIOSDelegate> oauth2_delegate_; TestingOAuth2TokenServiceConsumer consumer_; @@ -133,7 +116,6 @@ class ProfileOAuth2TokenServiceIOSDelegateTest int tokens_loaded_count_; int access_token_success_; int access_token_failure_; - int error_changed_count_; int auth_error_changed_count_; GoogleServiceAuthError last_access_token_error_; }; @@ -311,13 +293,11 @@ TEST_F(ProfileOAuth2TokenServiceIOSDelegateTest, oauth2_delegate_->UpdateAuthError(GetAccountId(account1), error); EXPECT_EQ(error, oauth2_delegate_->GetAuthError("gaia_1")); EXPECT_EQ(1, auth_error_changed_count_); - EXPECT_EQ(1, error_changed_count_); oauth2_delegate_->RevokeAllCredentials(); ResetObserverCounts(); oauth2_delegate_->UpdateAuthError(GetAccountId(account1), error); EXPECT_EQ(0, auth_error_changed_count_); - EXPECT_EQ(0, error_changed_count_); } TEST_F(ProfileOAuth2TokenServiceIOSDelegateTest, GetAuthError) { |