summaryrefslogtreecommitdiff
path: root/chromium/chrome/browser/signin/dice_response_handler.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/chrome/browser/signin/dice_response_handler.cc')
-rw-r--r--chromium/chrome/browser/signin/dice_response_handler.cc426
1 files changed, 426 insertions, 0 deletions
diff --git a/chromium/chrome/browser/signin/dice_response_handler.cc b/chromium/chrome/browser/signin/dice_response_handler.cc
new file mode 100644
index 00000000000..88d7563846f
--- /dev/null
+++ b/chromium/chrome/browser/signin/dice_response_handler.cc
@@ -0,0 +1,426 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/signin/dice_response_handler.h"
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/memory/singleton.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/strings/stringprintf.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/time/time.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profiles_state.h"
+#include "chrome/browser/signin/about_signin_internals_factory.h"
+#include "chrome/browser/signin/account_reconcilor_factory.h"
+#include "chrome/browser/signin/chrome_signin_client_factory.h"
+#include "chrome/browser/signin/identity_manager_factory.h"
+#include "chrome/browser/ui/webui/profile_helper.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+#include "components/signin/core/browser/about_signin_internals.h"
+#include "components/signin/core/browser/signin_header_helper.h"
+#include "components/signin/public/base/signin_client.h"
+#include "components/signin/public/base/signin_metrics.h"
+#include "components/signin/public/identity_manager/accounts_mutator.h"
+#include "components/signin/public/identity_manager/identity_manager.h"
+#include "google_apis/gaia/gaia_auth_fetcher.h"
+#include "google_apis/gaia/gaia_auth_util.h"
+#include "google_apis/gaia/google_service_auth_error.h"
+
+const int kDiceTokenFetchTimeoutSeconds = 10;
+// Timeout for locking the account reconcilor when
+// there was OAuth outage in Dice.
+const int kLockAccountReconcilorTimeoutHours = 12;
+
+const base::Feature kSupportOAuthOutageInDice{"SupportOAuthOutageInDice",
+ base::FEATURE_ENABLED_BY_DEFAULT};
+
+namespace {
+
+// The UMA histograms that logs events related to Dice responses.
+const char kDiceResponseHeaderHistogram[] = "Signin.DiceResponseHeader";
+const char kDiceTokenFetchResultHistogram[] = "Signin.DiceTokenFetchResult";
+
+// Used for UMA. Do not reorder, append new values at the end.
+enum DiceResponseHeader {
+ // Received a signin header.
+ kSignin = 0,
+ // Received a signout header including the Chrome primary account.
+ kSignoutPrimary = 1,
+ // Received a signout header for other account(s).
+ kSignoutSecondary = 2,
+ // Received a "EnableSync" header.
+ kEnableSync = 3,
+
+ kDiceResponseHeaderCount
+};
+
+// Used for UMA. Do not reorder, append new values at the end.
+enum DiceTokenFetchResult {
+ // The token fetch succeeded.
+ kFetchSuccess = 0,
+ // The token fetch was aborted. For example, if another request for the same
+ // account is already in flight.
+ kFetchAbort = 1,
+ // The token fetch failed because Gaia responsed with an error.
+ kFetchFailure = 2,
+ // The token fetch failed because no response was received from Gaia.
+ kFetchTimeout = 3,
+
+ kDiceTokenFetchResultCount
+};
+
+class DiceResponseHandlerFactory : public BrowserContextKeyedServiceFactory {
+ public:
+ // Returns an instance of the factory singleton.
+ static DiceResponseHandlerFactory* GetInstance() {
+ return base::Singleton<DiceResponseHandlerFactory>::get();
+ }
+
+ static DiceResponseHandler* GetForProfile(Profile* profile) {
+ return static_cast<DiceResponseHandler*>(
+ GetInstance()->GetServiceForBrowserContext(profile, true));
+ }
+
+ private:
+ friend struct base::DefaultSingletonTraits<DiceResponseHandlerFactory>;
+
+ DiceResponseHandlerFactory()
+ : BrowserContextKeyedServiceFactory(
+ "DiceResponseHandler",
+ BrowserContextDependencyManager::GetInstance()) {
+ DependsOn(AboutSigninInternalsFactory::GetInstance());
+ DependsOn(AccountReconcilorFactory::GetInstance());
+ DependsOn(ChromeSigninClientFactory::GetInstance());
+ DependsOn(IdentityManagerFactory::GetInstance());
+ }
+
+ ~DiceResponseHandlerFactory() override {}
+
+ // BrowserContextKeyedServiceFactory:
+ KeyedService* BuildServiceInstanceFor(
+ content::BrowserContext* context) const override {
+ if (context->IsOffTheRecord())
+ return nullptr;
+
+ Profile* profile = static_cast<Profile*>(context);
+ return new DiceResponseHandler(
+ ChromeSigninClientFactory::GetForProfile(profile),
+ IdentityManagerFactory::GetForProfile(profile),
+ AccountReconcilorFactory::GetForProfile(profile),
+ AboutSigninInternalsFactory::GetForProfile(profile),
+ profile->GetPath());
+ }
+};
+
+// Histogram macros expand to a lot of code, so it is better to wrap them in
+// functions.
+
+void RecordDiceResponseHeader(DiceResponseHeader header) {
+ UMA_HISTOGRAM_ENUMERATION(kDiceResponseHeaderHistogram, header,
+ kDiceResponseHeaderCount);
+}
+
+void RecordDiceFetchTokenResult(DiceTokenFetchResult result) {
+ UMA_HISTOGRAM_ENUMERATION(kDiceTokenFetchResultHistogram, result,
+ kDiceTokenFetchResultCount);
+}
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+// DiceTokenFetcher
+////////////////////////////////////////////////////////////////////////////////
+
+DiceResponseHandler::DiceTokenFetcher::DiceTokenFetcher(
+ const std::string& gaia_id,
+ const std::string& email,
+ const std::string& authorization_code,
+ SigninClient* signin_client,
+ AccountReconcilor* account_reconcilor,
+ std::unique_ptr<ProcessDiceHeaderDelegate> delegate,
+ DiceResponseHandler* dice_response_handler)
+ : gaia_id_(gaia_id),
+ email_(email),
+ authorization_code_(authorization_code),
+ delegate_(std::move(delegate)),
+ dice_response_handler_(dice_response_handler),
+ timeout_closure_(
+ base::BindOnce(&DiceResponseHandler::DiceTokenFetcher::OnTimeout,
+ base::Unretained(this))),
+ should_enable_sync_(false) {
+ DCHECK(dice_response_handler_);
+ account_reconcilor_lock_ =
+ std::make_unique<AccountReconcilor::Lock>(account_reconcilor);
+ gaia_auth_fetcher_ =
+ signin_client->CreateGaiaAuthFetcher(this, gaia::GaiaSource::kChrome);
+ VLOG(1) << "Start fetching token for account: " << email;
+ gaia_auth_fetcher_->StartAuthCodeForOAuth2TokenExchange(authorization_code_);
+ base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE, timeout_closure_.callback(),
+ base::Seconds(kDiceTokenFetchTimeoutSeconds));
+}
+
+DiceResponseHandler::DiceTokenFetcher::~DiceTokenFetcher() {}
+
+void DiceResponseHandler::DiceTokenFetcher::OnTimeout() {
+ RecordDiceFetchTokenResult(kFetchTimeout);
+ gaia_auth_fetcher_.reset();
+ timeout_closure_.Cancel();
+ dice_response_handler_->OnTokenExchangeFailure(
+ this, GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED));
+ // |this| may be deleted at this point.
+}
+
+void DiceResponseHandler::DiceTokenFetcher::OnClientOAuthSuccess(
+ const GaiaAuthConsumer::ClientOAuthResult& result) {
+ RecordDiceFetchTokenResult(kFetchSuccess);
+ gaia_auth_fetcher_.reset();
+ timeout_closure_.Cancel();
+ dice_response_handler_->OnTokenExchangeSuccess(
+ this, result.refresh_token, result.is_under_advanced_protection);
+ // |this| may be deleted at this point.
+}
+
+void DiceResponseHandler::DiceTokenFetcher::OnClientOAuthFailure(
+ const GoogleServiceAuthError& error) {
+ RecordDiceFetchTokenResult(kFetchFailure);
+ gaia_auth_fetcher_.reset();
+ timeout_closure_.Cancel();
+ dice_response_handler_->OnTokenExchangeFailure(this, error);
+ // |this| may be deleted at this point.
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// DiceResponseHandler
+////////////////////////////////////////////////////////////////////////////////
+
+// static
+DiceResponseHandler* DiceResponseHandler::GetForProfile(Profile* profile) {
+ return DiceResponseHandlerFactory::GetForProfile(profile);
+}
+
+DiceResponseHandler::DiceResponseHandler(
+ SigninClient* signin_client,
+ signin::IdentityManager* identity_manager,
+ AccountReconcilor* account_reconcilor,
+ AboutSigninInternals* about_signin_internals,
+ const base::FilePath& profile_path)
+ : signin_client_(signin_client),
+ identity_manager_(identity_manager),
+ account_reconcilor_(account_reconcilor),
+ about_signin_internals_(about_signin_internals),
+ profile_path_(profile_path) {
+ DCHECK(signin_client_);
+ DCHECK(identity_manager_);
+ DCHECK(account_reconcilor_);
+ DCHECK(about_signin_internals_);
+}
+
+DiceResponseHandler::~DiceResponseHandler() {}
+
+void DiceResponseHandler::ProcessDiceHeader(
+ const signin::DiceResponseParams& dice_params,
+ std::unique_ptr<ProcessDiceHeaderDelegate> delegate) {
+ DCHECK(delegate);
+ switch (dice_params.user_intention) {
+ case signin::DiceAction::SIGNIN: {
+ const signin::DiceResponseParams::AccountInfo& info =
+ dice_params.signin_info->account_info;
+ ProcessDiceSigninHeader(
+ info.gaia_id, info.email, dice_params.signin_info->authorization_code,
+ dice_params.signin_info->no_authorization_code, std::move(delegate));
+ return;
+ }
+ case signin::DiceAction::ENABLE_SYNC: {
+ const signin::DiceResponseParams::AccountInfo& info =
+ dice_params.enable_sync_info->account_info;
+ ProcessEnableSyncHeader(info.gaia_id, info.email, std::move(delegate));
+ return;
+ }
+ case signin::DiceAction::SIGNOUT:
+ DCHECK_GT(dice_params.signout_info->account_infos.size(), 0u);
+ ProcessDiceSignoutHeader(dice_params.signout_info->account_infos);
+ return;
+ case signin::DiceAction::NONE:
+ NOTREACHED() << "Invalid Dice response parameters.";
+ return;
+ }
+ NOTREACHED();
+}
+
+size_t DiceResponseHandler::GetPendingDiceTokenFetchersCountForTesting() const {
+ return token_fetchers_.size();
+}
+
+void DiceResponseHandler::OnTimeoutUnlockReconcilor() {
+ lock_.reset();
+}
+
+void DiceResponseHandler::SetTaskRunner(
+ scoped_refptr<base::SequencedTaskRunner> task_runner) {
+ task_runner_ = std::move(task_runner);
+}
+
+void DiceResponseHandler::ProcessDiceSigninHeader(
+ const std::string& gaia_id,
+ const std::string& email,
+ const std::string& authorization_code,
+ bool no_authorization_code,
+ std::unique_ptr<ProcessDiceHeaderDelegate> delegate) {
+ if (no_authorization_code) {
+ if (base::FeatureList::IsEnabled(kSupportOAuthOutageInDice)) {
+ lock_ = std::make_unique<AccountReconcilor::Lock>(account_reconcilor_);
+ about_signin_internals_->OnRefreshTokenReceived(
+ "Missing authorization code due to OAuth outage in Dice.");
+ if (!timer_) {
+ timer_ = std::make_unique<base::OneShotTimer>();
+ if (task_runner_)
+ timer_->SetTaskRunner(task_runner_);
+ }
+ // If there is already another lock, the timer will be reset and
+ // we'll wait another full timeout.
+ timer_->Start(
+ FROM_HERE, base::Hours(kLockAccountReconcilorTimeoutHours),
+ base::BindOnce(&DiceResponseHandler::OnTimeoutUnlockReconcilor,
+ base::Unretained(this)));
+ }
+ return;
+ }
+
+ DCHECK(!gaia_id.empty());
+ DCHECK(!email.empty());
+ DCHECK(!authorization_code.empty());
+ VLOG(1) << "Start processing Dice signin response";
+ RecordDiceResponseHeader(kSignin);
+
+ for (auto it = token_fetchers_.begin(); it != token_fetchers_.end(); ++it) {
+ if ((it->get()->gaia_id() == gaia_id) && (it->get()->email() == email) &&
+ (it->get()->authorization_code() == authorization_code)) {
+ RecordDiceFetchTokenResult(kFetchAbort);
+ return; // There is already a request in flight with the same parameters.
+ }
+ }
+ token_fetchers_.push_back(std::make_unique<DiceTokenFetcher>(
+ gaia_id, email, authorization_code, signin_client_, account_reconcilor_,
+ std::move(delegate), this));
+}
+
+void DiceResponseHandler::ProcessEnableSyncHeader(
+ const std::string& gaia_id,
+ const std::string& email,
+ std::unique_ptr<ProcessDiceHeaderDelegate> delegate) {
+ VLOG(1) << "Start processing Dice enable sync response";
+ RecordDiceResponseHeader(kEnableSync);
+ for (auto it = token_fetchers_.begin(); it != token_fetchers_.end(); ++it) {
+ DiceTokenFetcher* fetcher = it->get();
+ if (fetcher->gaia_id() == gaia_id) {
+ DCHECK(gaia::AreEmailsSame(fetcher->email(), email));
+ // If there is a fetch in progress for a resfresh token for the given
+ // account, then simply mark it to enable sync after the refresh token is
+ // available.
+ fetcher->set_should_enable_sync(true);
+ return; // There is already a request in flight with the same parameters.
+ }
+ }
+ CoreAccountId account_id =
+ identity_manager_->PickAccountIdForAccount(gaia_id, email);
+ delegate->EnableSync(account_id);
+}
+
+void DiceResponseHandler::ProcessDiceSignoutHeader(
+ const std::vector<signin::DiceResponseParams::AccountInfo>& account_infos) {
+ VLOG(1) << "Start processing Dice signout response";
+
+ CoreAccountId primary_account =
+ identity_manager_->GetPrimaryAccountId(signin::ConsentLevel::kSync);
+ bool primary_account_signed_out = false;
+ auto* accounts_mutator = identity_manager_->GetAccountsMutator();
+ for (const auto& account_info : account_infos) {
+ CoreAccountId signed_out_account =
+ identity_manager_->PickAccountIdForAccount(account_info.gaia_id,
+ account_info.email);
+ if (signed_out_account == primary_account) {
+ primary_account_signed_out = true;
+ RecordDiceResponseHeader(kSignoutPrimary);
+
+ // Put the account in error state.
+ accounts_mutator->InvalidateRefreshTokenForPrimaryAccount(
+ signin_metrics::SourceForRefreshTokenOperation::
+ kDiceResponseHandler_Signout);
+ } else {
+ accounts_mutator->RemoveAccount(
+ signed_out_account, signin_metrics::SourceForRefreshTokenOperation::
+ kDiceResponseHandler_Signout);
+ }
+
+ // If a token fetch is in flight for the same account, cancel it.
+ for (auto it = token_fetchers_.begin(); it != token_fetchers_.end(); ++it) {
+ CoreAccountId token_fetcher_account_id =
+ identity_manager_->PickAccountIdForAccount(it->get()->gaia_id(),
+ it->get()->email());
+ if (token_fetcher_account_id == signed_out_account) {
+ token_fetchers_.erase(it);
+ break;
+ }
+ }
+ }
+
+ if (!primary_account_signed_out)
+ RecordDiceResponseHeader(kSignoutSecondary);
+}
+
+void DiceResponseHandler::DeleteTokenFetcher(DiceTokenFetcher* token_fetcher) {
+ for (auto it = token_fetchers_.begin(); it != token_fetchers_.end(); ++it) {
+ if (it->get() == token_fetcher) {
+ token_fetchers_.erase(it);
+ return;
+ }
+ }
+ NOTREACHED();
+}
+
+void DiceResponseHandler::OnTokenExchangeSuccess(
+ DiceTokenFetcher* token_fetcher,
+ const std::string& refresh_token,
+ bool is_under_advanced_protection) {
+ const std::string& email = token_fetcher->email();
+ const std::string& gaia_id = token_fetcher->gaia_id();
+ VLOG(1) << "[Dice] OAuth success for email " << email;
+ bool should_enable_sync = token_fetcher->should_enable_sync();
+ CoreAccountId account_id =
+ identity_manager_->PickAccountIdForAccount(gaia_id, email);
+ bool is_new_account =
+ !identity_manager_->HasAccountWithRefreshToken(account_id);
+ identity_manager_->GetAccountsMutator()->AddOrUpdateAccount(
+ gaia_id, email, refresh_token, is_under_advanced_protection,
+ signin_metrics::SourceForRefreshTokenOperation::
+ kDiceResponseHandler_Signin);
+ about_signin_internals_->OnRefreshTokenReceived(
+ base::StringPrintf("Successful (%s)", account_id.ToString().c_str()));
+ token_fetcher->delegate()->HandleTokenExchangeSuccess(account_id,
+ is_new_account);
+ if (should_enable_sync)
+ token_fetcher->delegate()->EnableSync(account_id);
+
+ DeleteTokenFetcher(token_fetcher);
+}
+
+void DiceResponseHandler::OnTokenExchangeFailure(
+ DiceTokenFetcher* token_fetcher,
+ const GoogleServiceAuthError& error) {
+ const std::string& email = token_fetcher->email();
+ const std::string& gaia_id = token_fetcher->gaia_id();
+ CoreAccountId account_id =
+ identity_manager_->PickAccountIdForAccount(gaia_id, email);
+ about_signin_internals_->OnRefreshTokenReceived(
+ base::StringPrintf("Failure (%s)", account_id.ToString().c_str()));
+ token_fetcher->delegate()->HandleTokenExchangeFailure(email, error);
+
+ DeleteTokenFetcher(token_fetcher);
+}