blob: 8e7d098fa3ca1cc04d442a895d0dbc5e2e215b00 [file] [log] [blame]
// 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/bind_helpers.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/memory/singleton.h"
#include "base/metrics/histogram_macros.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/signin/account_reconcilor_factory.h"
#include "chrome/browser/signin/account_tracker_service_factory.h"
#include "chrome/browser/signin/chrome_signin_client_factory.h"
#include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
#include "chrome/browser/signin/signin_manager_factory.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/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_header_helper.h"
#include "components/signin/core/browser/signin_manager.h"
#include "components/signin/core/browser/signin_metrics.h"
#include "components/signin/core/common/profile_management_switches.h"
#include "google_apis/gaia/gaia_auth_fetcher.h"
#include "google_apis/gaia/gaia_constants.h"
#include "google_apis/gaia/google_service_auth_error.h"
const int kDiceTokenFetchTimeoutSeconds = 10;
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 primary account.
kSignoutPrimary = 1,
// Received a signout header for other account(s).
kSignoutSecondary = 2,
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(AccountReconcilorFactory::GetInstance());
DependsOn(AccountTrackerServiceFactory::GetInstance());
DependsOn(ChromeSigninClientFactory::GetInstance());
DependsOn(ProfileOAuth2TokenServiceFactory::GetInstance());
DependsOn(SigninManagerFactory::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),
SigninManagerFactory::GetForProfile(profile),
ProfileOAuth2TokenServiceFactory::GetForProfile(profile),
AccountTrackerServiceFactory::GetForProfile(profile),
AccountReconcilorFactory::GetForProfile(profile));
}
};
// 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,
DiceResponseHandler* dice_response_handler)
: gaia_id_(gaia_id),
email_(email),
authorization_code_(authorization_code),
dice_response_handler_(dice_response_handler),
timeout_closure_(
base::Bind(&DiceResponseHandler::DiceTokenFetcher::OnTimeout,
base::Unretained(this))) {
DCHECK(dice_response_handler_);
if (signin::IsAccountConsistencyDiceEnabled()) {
account_reconcilor_lock_ =
base::MakeUnique<AccountReconcilor::Lock>(account_reconcilor);
}
gaia_auth_fetcher_ = signin_client->CreateGaiaAuthFetcher(
this, GaiaConstants::kChromeSource,
signin_client->GetURLRequestContext());
gaia_auth_fetcher_->StartAuthCodeForOAuth2TokenExchange(authorization_code_);
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, timeout_closure_.callback(),
base::TimeDelta::FromSeconds(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, gaia_id_, email_,
result);
// |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,
SigninManager* signin_manager,
ProfileOAuth2TokenService* profile_oauth2_token_service,
AccountTrackerService* account_tracker_service,
AccountReconcilor* account_reconcilor)
: signin_manager_(signin_manager),
signin_client_(signin_client),
token_service_(profile_oauth2_token_service),
account_tracker_service_(account_tracker_service),
account_reconcilor_(account_reconcilor) {
DCHECK(signin_client_);
DCHECK(signin_manager_);
DCHECK(token_service_);
DCHECK(account_tracker_service_);
DCHECK(account_reconcilor_);
}
DiceResponseHandler::~DiceResponseHandler() {}
void DiceResponseHandler::ProcessDiceHeader(
const signin::DiceResponseParams& dice_params) {
DCHECK(signin::IsDiceFixAuthErrorsEnabled());
switch (dice_params.user_intention) {
case signin::DiceAction::SIGNIN:
ProcessDiceSigninHeader(dice_params.signin_info.gaia_id,
dice_params.signin_info.email,
dice_params.signin_info.authorization_code);
return;
case signin::DiceAction::SIGNOUT: {
const signin::DiceResponseParams::SignoutInfo& signout_info =
dice_params.signout_info;
DCHECK_GT(signout_info.gaia_id.size(), 0u);
DCHECK_EQ(signout_info.gaia_id.size(), signout_info.email.size());
DCHECK_EQ(signout_info.gaia_id.size(), signout_info.session_index.size());
ProcessDiceSignoutHeader(signout_info.gaia_id, signout_info.email);
return;
}
case signin::DiceAction::NONE:
NOTREACHED() << "Invalid Dice response parameters.";
return;
}
NOTREACHED();
return;
}
size_t DiceResponseHandler::GetPendingDiceTokenFetchersCountForTesting() const {
return token_fetchers_.size();
}
bool DiceResponseHandler::CanGetTokenForAccount(const std::string& gaia_id,
const std::string& email) {
if (signin::IsAccountConsistencyDiceEnabled())
return true;
// When using kDiceFixAuthErrors, only get a token if the account matches
// the current Chrome account.
DCHECK_EQ(signin::AccountConsistencyMethod::kDiceFixAuthErrors,
signin::GetAccountConsistencyMethod());
std::string account =
account_tracker_service_->PickAccountIdForAccount(gaia_id, email);
std::string chrome_account = signin_manager_->GetAuthenticatedAccountId();
bool can_get_token = (chrome_account == account);
VLOG_IF(1, !can_get_token)
<< "[Dice] Dropping Dice signin response for " << account;
return can_get_token;
}
void DiceResponseHandler::ProcessDiceSigninHeader(
const std::string& gaia_id,
const std::string& email,
const std::string& authorization_code) {
DCHECK(!gaia_id.empty());
DCHECK(!email.empty());
DCHECK(!authorization_code.empty());
RecordDiceResponseHeader(kSignin);
if (!CanGetTokenForAccount(gaia_id, email)) {
RecordDiceFetchTokenResult(kFetchAbort);
return;
}
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(base::MakeUnique<DiceTokenFetcher>(
gaia_id, email, authorization_code, signin_client_, account_reconcilor_,
this));
}
void DiceResponseHandler::ProcessDiceSignoutHeader(
const std::vector<std::string>& gaia_ids,
const std::vector<std::string>& emails) {
DCHECK_EQ(gaia_ids.size(), emails.size());
if (!signin::IsAccountConsistencyDiceEnabled()) {
// Ignore signout responses when using kDiceFixAuthErrors.
DCHECK_EQ(signin::AccountConsistencyMethod::kDiceFixAuthErrors,
signin::GetAccountConsistencyMethod());
return;
}
// If one of the signed out accounts is the main Chrome account, then force a
// complete signout. Otherwise simply revoke the corresponding tokens.
std::string current_account = signin_manager_->GetAuthenticatedAccountId();
std::vector<std::string> signed_out_accounts;
for (unsigned int i = 0; i < gaia_ids.size(); ++i) {
std::string signed_out_account =
account_tracker_service_->PickAccountIdForAccount(gaia_ids[i],
emails[i]);
if (signed_out_account == current_account) {
VLOG(1) << "[Dice] Signing out all accounts.";
RecordDiceResponseHeader(kSignoutPrimary);
signin_manager_->SignOutAndRemoveAllAccounts(
signin_metrics::SERVER_FORCED_DISABLE,
signin_metrics::SignoutDelete::IGNORE_METRIC);
// Cancel all Dice token fetches currently in flight.
token_fetchers_.clear();
return;
} else {
signed_out_accounts.push_back(signed_out_account);
}
}
RecordDiceResponseHeader(kSignoutSecondary);
for (const auto& account : signed_out_accounts) {
VLOG(1) << "[Dice]: Revoking token for account: " << account;
token_service_->RevokeCredentials(account);
// If a token fetch is in flight for the same account, cancel it.
for (auto it = token_fetchers_.begin(); it != token_fetchers_.end(); ++it) {
std::string token_fetcher_account_id =
account_tracker_service_->PickAccountIdForAccount(
it->get()->gaia_id(), it->get()->email());
if (token_fetcher_account_id == account) {
token_fetchers_.erase(it);
break;
}
}
}
}
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& gaia_id,
const std::string& email,
const GaiaAuthConsumer::ClientOAuthResult& result) {
if (!CanGetTokenForAccount(gaia_id, email))
return;
std::string account_id =
account_tracker_service_->SeedAccountInfo(gaia_id, email);
VLOG(1) << "[Dice] OAuth success for account: " << account_id;
token_service_->UpdateCredentials(account_id, result.refresh_token);
DeleteTokenFetcher(token_fetcher);
}
void DiceResponseHandler::OnTokenExchangeFailure(
DiceTokenFetcher* token_fetcher,
const GoogleServiceAuthError& error) {
// TODO(droger): Handle authentication errors.
VLOG(1) << "[Dice] OAuth failed with error: " << error.ToString();
DeleteTokenFetcher(token_fetcher);
}