blob: a779cf4ec05f799f3aa48ff0ffbabbf2f9fb3dd3 [file] [log] [blame]
// 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/oauth_multilogin_helper.h"
#include <algorithm>
#include <utility>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback.h"
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "components/signin/core/browser/oauth_multilogin_token_fetcher.h"
#include "components/signin/core/browser/set_accounts_in_cookie_result.h"
#include "components/signin/core/browser/signin_client.h"
#include "google_apis/gaia/google_service_auth_error.h"
#include "google_apis/gaia/oauth_multilogin_result.h"
#include "mojo/public/cpp/bindings/callback_helpers.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
namespace {
constexpr int kMaxFetcherRetries = 3;
std::string FindTokenForAccount(
const std::vector<GaiaAuthFetcher::MultiloginTokenIDPair>&
gaia_id_token_pairs,
const std::string& gaia_id) {
for (const auto& gaia_id_token : gaia_id_token_pairs) {
if (gaia_id == gaia_id_token.gaia_id_)
return gaia_id_token.token_;
}
return std::string();
}
CoreAccountId FindAccountIdForGaiaId(
const std::vector<GaiaCookieManagerService::AccountIdGaiaIdPair>& accounts,
const std::string& gaia_id) {
for (const auto& account : accounts) {
if (gaia_id == account.second)
return account.first;
}
return CoreAccountId();
}
} // namespace
namespace signin {
OAuthMultiloginHelper::OAuthMultiloginHelper(
SigninClient* signin_client,
OAuth2TokenService* token_service,
const std::vector<GaiaCookieManagerService::AccountIdGaiaIdPair>& accounts,
const std::string& external_cc_result,
base::OnceCallback<void(signin::SetAccountsInCookieResult)> callback)
: signin_client_(signin_client),
token_service_(token_service),
accounts_(accounts),
external_cc_result_(external_cc_result),
callback_(std::move(callback)),
weak_ptr_factory_(this) {
DCHECK(signin_client_);
DCHECK(token_service_);
DCHECK(!accounts_.empty());
DCHECK(callback_);
#ifndef NDEBUG
// Check that there is no duplicate accounts.
std::set<GaiaCookieManagerService::AccountIdGaiaIdPair>
accounts_no_duplicates(accounts_.begin(), accounts_.end());
DCHECK_EQ(accounts_.size(), accounts_no_duplicates.size());
#endif
StartFetchingTokens();
}
OAuthMultiloginHelper::~OAuthMultiloginHelper() = default;
void OAuthMultiloginHelper::StartFetchingTokens() {
DCHECK(!token_fetcher_);
DCHECK(gaia_id_token_pairs_.empty());
std::vector<CoreAccountId> account_ids;
for (const auto& account : accounts_)
account_ids.push_back(account.first);
token_fetcher_ = std::make_unique<signin::OAuthMultiloginTokenFetcher>(
signin_client_, token_service_, account_ids,
base::BindOnce(&OAuthMultiloginHelper::OnAccessTokensSuccess,
base::Unretained(this)),
base::BindOnce(&OAuthMultiloginHelper::OnAccessTokensFailure,
base::Unretained(this)));
}
void OAuthMultiloginHelper::OnAccessTokensSuccess(
const std::vector<OAuthMultiloginTokenFetcher::AccountIdTokenPair>&
account_token_pairs) {
DCHECK(gaia_id_token_pairs_.empty());
for (size_t index = 0; index < accounts_.size(); index++) {
// OAuthMultiloginTokenFetcher should return the tokens in the same order
// as the account_ids that was passed to it.
DCHECK_EQ(accounts_[index].first, account_token_pairs[index].account_id);
gaia_id_token_pairs_.emplace_back(accounts_[index].second,
account_token_pairs[index].token);
}
DCHECK_EQ(gaia_id_token_pairs_.size(), accounts_.size());
token_fetcher_.reset();
signin_client_->DelayNetworkCall(
base::BindOnce(&OAuthMultiloginHelper::StartFetchingMultiLogin,
weak_ptr_factory_.GetWeakPtr()));
}
void OAuthMultiloginHelper::OnAccessTokensFailure(
const GoogleServiceAuthError& error) {
token_fetcher_.reset();
std::move(callback_).Run(
error.IsTransientError()
? signin::SetAccountsInCookieResult::kTransientError
: signin::SetAccountsInCookieResult::kPersistentError);
// Do not add anything below this line, because this may be deleted.
}
void OAuthMultiloginHelper::StartFetchingMultiLogin() {
DCHECK_EQ(gaia_id_token_pairs_.size(), accounts_.size());
gaia_auth_fetcher_ =
signin_client_->CreateGaiaAuthFetcher(this, gaia::GaiaSource::kChrome);
gaia_auth_fetcher_->StartOAuthMultilogin(gaia_id_token_pairs_,
external_cc_result_);
}
void OAuthMultiloginHelper::OnOAuthMultiloginFinished(
const OAuthMultiloginResult& result) {
if (result.status() == OAuthMultiloginResponseStatus::kOk) {
std::vector<std::string> account_ids;
for (const auto& account : accounts_)
account_ids.push_back(account.first.id);
VLOG(1) << "Multilogin successful accounts="
<< base::JoinString(account_ids, " ");
StartSettingCookies(result);
return;
}
// If Gaia responded with kInvalidTokens, we have to mark tokens as invalid.
if (result.status() == OAuthMultiloginResponseStatus::kInvalidTokens) {
for (const std::string& failed_gaia_id : result.failed_gaia_ids()) {
std::string failed_token =
FindTokenForAccount(gaia_id_token_pairs_, failed_gaia_id);
if (failed_token.empty()) {
LOG(ERROR)
<< "Unexpected failed token for account not present in request: "
<< failed_gaia_id;
continue;
}
token_service_->InvalidateTokenForMultilogin(
FindAccountIdForGaiaId(accounts_, failed_gaia_id), failed_token);
}
}
bool is_transient_error =
result.status() == OAuthMultiloginResponseStatus::kInvalidTokens ||
result.status() == OAuthMultiloginResponseStatus::kRetry;
if (is_transient_error && ++fetcher_retries_ < kMaxFetcherRetries) {
gaia_id_token_pairs_.clear();
StartFetchingTokens();
return;
}
std::move(callback_).Run(
is_transient_error ? signin::SetAccountsInCookieResult::kTransientError
: signin::SetAccountsInCookieResult::kPersistentError);
// Do not add anything below this line, because this may be deleted.
}
void OAuthMultiloginHelper::StartSettingCookies(
const OAuthMultiloginResult& result) {
DCHECK(cookies_to_set_.empty());
network::mojom::CookieManager* cookie_manager =
signin_client_->GetCookieManager();
const std::vector<net::CanonicalCookie>& cookies = result.cookies();
for (const net::CanonicalCookie& cookie : cookies) {
cookies_to_set_.insert(std::make_pair(cookie.Name(), cookie.Domain()));
}
for (const net::CanonicalCookie& cookie : cookies) {
if (cookies_to_set_.find(std::make_pair(cookie.Name(), cookie.Domain())) !=
cookies_to_set_.end()) {
base::OnceCallback<void(net::CanonicalCookie::CookieInclusionStatus)>
callback = base::BindOnce(&OAuthMultiloginHelper::OnCookieSet,
weak_ptr_factory_.GetWeakPtr(),
cookie.Name(), cookie.Domain());
net::CookieOptions options;
options.set_include_httponly();
// Permit it to set a SameSite cookie if it wants to.
options.set_same_site_cookie_context(
net::CookieOptions::SameSiteCookieContext::SAME_SITE_STRICT);
cookie_manager->SetCanonicalCookie(
cookie, "https", options,
mojo::WrapCallbackWithDefaultInvokeIfNotRun(
std::move(callback), net::CanonicalCookie::CookieInclusionStatus::
EXCLUDE_UNKNOWN_ERROR));
} else {
LOG(ERROR) << "Duplicate cookie found: " << cookie.Name() << " "
<< cookie.Domain();
}
}
}
void OAuthMultiloginHelper::OnCookieSet(
const std::string& cookie_name,
const std::string& cookie_domain,
net::CanonicalCookie::CookieInclusionStatus status) {
cookies_to_set_.erase(std::make_pair(cookie_name, cookie_domain));
bool success =
(status == net::CanonicalCookie::CookieInclusionStatus::INCLUDE);
if (!success) {
LOG(ERROR) << "Failed to set cookie " << cookie_name
<< " for domain=" << cookie_domain << ".";
}
UMA_HISTOGRAM_BOOLEAN("Signin.SetCookieSuccess", success);
if (cookies_to_set_.empty())
std::move(callback_).Run(signin::SetAccountsInCookieResult::kSuccess);
// Do not add anything below this line, because this may be deleted.
}
} // namespace signin