blob: 609a3ce23472037d2f59d531d33cfb8cce794027 [file] [log] [blame]
// 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/signin_manager.h"
#include <string>
#include <vector>
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "components/prefs/pref_service.h"
#include "components/signin/core/browser/account_tracker_service.h"
#include "components/signin/core/browser/gaia_cookie_manager_service.h"
#include "components/signin/core/browser/signin_internals_util.h"
#include "components/signin/core/browser/signin_metrics.h"
#include "components/signin/core/browser/signin_pref_names.h"
#include "google_apis/gaia/gaia_auth_util.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 "third_party/icu/source/i18n/unicode/regex.h"
SigninManager::SigninManager(
SigninClient* client,
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),
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),
user_info_fetched_by_account_tracker_(false),
weak_pointer_factory_(this) {}
SigninManager::~SigninManager() {}
std::string SigninManager::SigninTypeToString(SigninManager::SigninType type) {
switch (type) {
case SIGNIN_TYPE_NONE:
return "No Signin";
case SIGNIN_TYPE_WITH_REFRESH_TOKEN:
return "With refresh token";
case SIGNIN_TYPE_WITHOUT_REFRESH_TOKEN:
return "Without refresh token";
}
NOTREACHED();
return std::string();
}
bool SigninManager::PrepareForSignin(SigninType type,
const std::string& gaia_id,
const std::string& username,
const std::string& password) {
std::string account_id =
account_tracker_service()->PickAccountIdForAccount(gaia_id, username);
DCHECK(!account_id.empty());
DCHECK(possibly_invalid_account_id_.empty() ||
possibly_invalid_account_id_ == account_id);
if (!IsAllowedUsername(username)) {
// Account is not allowed by admin policy.
HandleAuthError(
GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DISABLED));
return false;
}
// This attempt is either 1) the user trying to establish initial sync, or
// 2) trying to refresh credentials for an existing username. If it is 2, we
// need to try again, but take care to leave state around tracking that the
// user has successfully signed in once before with this username, so that on
// restart we don't think sync setup has never completed.
ClearTransientSigninData();
type_ = type;
possibly_invalid_account_id_.assign(account_id);
possibly_invalid_gaia_id_.assign(gaia_id);
possibly_invalid_email_.assign(username);
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;
}
void SigninManager::StartSignInWithRefreshToken(
const std::string& refresh_token,
const std::string& gaia_id,
const std::string& username,
const std::string& password,
const OAuthTokenFetchedCallback& callback) {
DCHECK(!IsAuthenticated());
SigninType signin_type = refresh_token.empty()
? SIGNIN_TYPE_WITHOUT_REFRESH_TOKEN
: SIGNIN_TYPE_WITH_REFRESH_TOKEN;
if (!PrepareForSignin(signin_type, gaia_id, username, password)) {
return;
}
// Store the refresh token.
temp_refresh_token_ = refresh_token;
if (!callback.is_null()) {
// Callback present, let the caller complete the pending sign-in.
callback.Run(temp_refresh_token_);
} else {
// No callback, so just complete the pending signin.
CompletePendingSignin();
}
}
void SigninManager::CopyCredentialsFrom(const SigninManager& source) {
DCHECK_NE(this, &source);
possibly_invalid_account_id_ = source.possibly_invalid_account_id_;
possibly_invalid_gaia_id_ = source.possibly_invalid_gaia_id_;
possibly_invalid_email_ = source.possibly_invalid_email_;
temp_refresh_token_ = source.temp_refresh_token_;
password_ = source.password_;
source.client_->AfterCredentialsCopied();
}
void SigninManager::ClearTransientSigninData() {
DCHECK(IsInitialized());
possibly_invalid_account_id_.clear();
possibly_invalid_gaia_id_.clear();
possibly_invalid_email_.clear();
password_.clear();
type_ = SIGNIN_TYPE_NONE;
temp_refresh_token_.clear();
}
void SigninManager::HandleAuthError(const GoogleServiceAuthError& error) {
ClearTransientSigninData();
for (auto& observer : observer_list_)
observer.GoogleSigninFailed(error);
}
void SigninManager::SignOut(
signin_metrics::ProfileSignout signout_source_metric,
signin_metrics::SignoutDelete signout_delete_metric) {
RemoveAccountsOption remove_option =
(account_consistency_ == signin::AccountConsistencyMethod::kDice)
? RemoveAccountsOption::kRemoveAuthenticatedAccountIfInError
: RemoveAccountsOption::kRemoveAllAccounts;
StartSignOut(signout_source_metric, signout_delete_metric, remove_option);
}
void SigninManager::SignOutAndRemoveAllAccounts(
signin_metrics::ProfileSignout signout_source_metric,
signin_metrics::SignoutDelete signout_delete_metric) {
StartSignOut(signout_source_metric, signout_delete_metric,
RemoveAccountsOption::kRemoveAllAccounts);
}
void SigninManager::SignOutAndKeepAllAccounts(
signin_metrics::ProfileSignout signout_source_metric,
signin_metrics::SignoutDelete signout_delete_metric) {
StartSignOut(signout_source_metric, signout_delete_metric,
RemoveAccountsOption::kKeepAllAccounts);
}
void SigninManager::StartSignOut(
signin_metrics::ProfileSignout signout_source_metric,
signin_metrics::SignoutDelete signout_delete_metric,
RemoveAccountsOption remove_option) {
client_->PreSignOut(
base::BindOnce(&SigninManager::OnSignoutDecisionReached,
base::Unretained(this), signout_source_metric,
signout_delete_metric, remove_option),
signout_source_metric);
}
void SigninManager::OnSignoutDecisionReached(
signin_metrics::ProfileSignout signout_source_metric,
signin_metrics::SignoutDelete signout_delete_metric,
RemoveAccountsOption remove_option,
SigninClient::SignoutDecision signout_decision) {
DCHECK(IsInitialized());
signin_metrics::LogSignout(signout_source_metric, signout_delete_metric);
if (!IsAuthenticated()) {
if (AuthInProgress()) {
// If the user is in the process of signing in, then treat a call to
// SignOut as a cancellation request.
GoogleServiceAuthError error(GoogleServiceAuthError::REQUEST_CANCELED);
HandleAuthError(error);
} else {
// Clean up our transient data and exit if we aren't signed in.
// This avoids a perf regression from clearing out the TokenDB if
// SignOut() is invoked on startup to clean up any incomplete previous
// signin attempts.
ClearTransientSigninData();
}
return;
}
// TODO(crbug.com/887756): Consider moving this higher up, or document why
// the above blocks are exempt from the |signout_decision| early return.
if (signout_decision == SigninClient::SignoutDecision::DISALLOW_SIGNOUT) {
DVLOG(1) << "Ignoring attempt to sign out while signout disallowed";
return;
}
ClearTransientSigninData();
AccountInfo account_info = GetAuthenticatedAccountInfo();
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));
ClearAuthenticatedAccountId();
client_->GetPrefs()->ClearPref(prefs::kGoogleServicesHostedDomain);
client_->GetPrefs()->ClearPref(prefs::kGoogleServicesAccountId);
client_->GetPrefs()->ClearPref(prefs::kGoogleServicesUserAccountId);
client_->GetPrefs()->ClearPref(prefs::kSignedInTime);
client_->OnSignedOut();
// Determine the duration the user was logged in and log that to UMA.
if (!signin_time.is_null()) {
base::TimeDelta signed_in_duration = base::Time::Now() - signin_time;
UMA_HISTOGRAM_COUNTS_1M("Signin.SignedInDurationBeforeSignout",
signed_in_duration.InMinutes());
}
// Revoke all tokens before sending signed_out notification, because there
// may be components that don't listen for token service events when the
// profile is not connected to an account.
switch (remove_option) {
case RemoveAccountsOption::kRemoveAllAccounts:
VLOG(0) << "Revoking all refresh tokens on server. Reason: sign out, "
<< "IsSigninAllowed: " << IsSigninAllowed();
token_service_->RevokeAllCredentials();
break;
case RemoveAccountsOption::kRemoveAuthenticatedAccountIfInError:
if (token_service_->RefreshTokenHasError(account_id))
token_service_->RevokeCredentials(account_id);
break;
case RemoveAccountsOption::kKeepAllAccounts:
// Do nothing.
break;
}
FireGoogleSignedOut(account_id, account_info);
}
void SigninManager::Initialize(PrefService* local_state) {
SigninManagerBase::Initialize(local_state);
// local_state can be null during unit tests.
if (local_state) {
local_state_pref_registrar_.Init(local_state);
local_state_pref_registrar_.Add(
prefs::kGoogleServicesUsernamePattern,
base::Bind(&SigninManager::OnGoogleServicesUsernamePatternChanged,
weak_pointer_factory_.GetWeakPtr()));
}
signin_allowed_.Init(prefs::kSigninAllowed,
client_->GetPrefs(),
base::Bind(&SigninManager::OnSigninAllowedPrefChanged,
base::Unretained(this)));
std::string account_id =
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()) {
// User is signed in, but the username is invalid or signin is no longer
// allowed, so the user must be sign out.
//
// This may happen in the following cases:
// a. The user has toggled off signin allowed in settings.
// b. The administrator changed the policy since the last signin.
//
// Note: The token service has not yet loaded its credentials, so accounts
// cannot be revoked here.
SignOutAndKeepAllAccounts(signin_metrics::SIGNIN_PREF_CHANGED_DURING_SIGNIN,
signin_metrics::SignoutDelete::IGNORE_METRIC);
}
account_tracker_service()->AddObserver(this);
// It is important to only load credentials after starting to observe the
// token service.
token_service_->AddObserver(this);
token_service_->LoadCredentials(GetAuthenticatedAccountId());
}
void SigninManager::Shutdown() {
token_service_->RemoveObserver(this);
account_tracker_service()->RemoveObserver(this);
local_state_pref_registrar_.RemoveAll();
SigninManagerBase::Shutdown();
}
void SigninManager::OnGoogleServicesUsernamePatternChanged() {
if (IsAuthenticated() &&
!IsAllowedUsername(GetAuthenticatedAccountInfo().email)) {
// Signed in user is invalid according to the current policy so sign
// the user out.
SignOut(signin_metrics::GOOGLE_SERVICE_NAME_PATTERN_CHANGED,
signin_metrics::SignoutDelete::IGNORE_METRIC);
}
}
bool SigninManager::IsSigninAllowed() const {
return signin_allowed_.GetValue();
}
void SigninManager::OnSigninAllowedPrefChanged() {
if (!IsSigninAllowed() && (IsAuthenticated() || AuthInProgress()))
SignOut(signin_metrics::SIGNOUT_PREF_CHANGED,
signin_metrics::SignoutDelete::IGNORE_METRIC);
}
// static
bool SigninManager::IsUsernameAllowedByPolicy(const std::string& username,
const std::string& policy) {
if (policy.empty())
return true;
// Patterns like "*@foo.com" are not accepted by our regex engine (since they
// are not valid regular expressions - they should instead be ".*@foo.com").
// For convenience, detect these patterns and insert a "." character at the
// front.
base::string16 pattern = base::UTF8ToUTF16(policy);
if (pattern[0] == L'*')
pattern.insert(pattern.begin(), L'.');
// See if the username matches the policy-provided pattern.
UErrorCode status = U_ZERO_ERROR;
const icu::UnicodeString icu_pattern(FALSE, pattern.data(), pattern.length());
icu::RegexMatcher matcher(icu_pattern, UREGEX_CASE_INSENSITIVE, status);
if (!U_SUCCESS(status)) {
LOG(ERROR) << "Invalid login regex: " << pattern << ", status: " << status;
// If an invalid pattern is provided, then prohibit *all* logins (better to
// break signin than to quietly allow users to sign in).
return false;
}
// The default encoding is UTF-8 in Chromium's ICU.
icu::UnicodeString icu_input(username.data());
matcher.reset(icu_input);
status = U_ZERO_ERROR;
UBool match = matcher.matches(status);
DCHECK(U_SUCCESS(status));
return !!match; // !! == convert from UBool to bool.
}
// static
SigninManager* SigninManager::FromSigninManagerBase(
SigninManagerBase* manager) {
return static_cast<SigninManager*>(manager);
}
bool SigninManager::IsAllowedUsername(const std::string& username) const {
const PrefService* local_state = local_state_pref_registrar_.prefs();
if (!local_state)
return true; // In a unit test with no local state - all names are allowed.
std::string pattern =
local_state->GetString(prefs::kGoogleServicesUsernamePattern);
return IsUsernameAllowedByPolicy(username, pattern);
}
bool SigninManager::AuthInProgress() const {
return !possibly_invalid_account_id_.empty();
}
const std::string& SigninManager::GetAccountIdForAuthInProgress() const {
return possibly_invalid_account_id_;
}
const std::string& SigninManager::GetGaiaIdForAuthInProgress() const {
return possibly_invalid_gaia_id_;
}
const std::string& SigninManager::GetUsernameForAuthInProgress() const {
return possibly_invalid_email_;
}
void SigninManager::MergeSigninCredentialIntoCookieJar() {
if (account_consistency_ == signin::AccountConsistencyMethod::kMirror)
return;
if (!IsAuthenticated())
return;
cookie_manager_service_->AddAccountToCookie(GetAuthenticatedAccountId(),
"ChromiumSigninManager");
}
void SigninManager::CompletePendingSignin() {
NotifyDiagnosticsObservers(signin_internals_util::SIGNIN_COMPLETED,
"Successful");
DCHECK(!possibly_invalid_account_id_.empty());
OnSignedIn();
DCHECK(IsAuthenticated());
if (!temp_refresh_token_.empty()) {
std::string account_id = GetAuthenticatedAccountId();
token_service_->UpdateCredentials(account_id, temp_refresh_token_);
temp_refresh_token_.clear();
}
MergeSigninCredentialIntoCookieJar();
}
void SigninManager::OnExternalSigninCompleted(const std::string& username) {
AccountInfo info =
account_tracker_service()->FindAccountInfoByEmail(username);
DCHECK(!info.gaia.empty());
DCHECK(!info.email.empty());
possibly_invalid_account_id_ = info.account_id;
possibly_invalid_gaia_id_ = info.gaia;
possibly_invalid_email_ = info.email;
OnSignedIn();
}
void SigninManager::OnSignedIn() {
bool reauth_in_progress = IsAuthenticated();
client_->GetPrefs()->SetInt64(prefs::kSignedInTime,
base::Time::Now().ToInternalValue());
SetAuthenticatedAccountInfo(possibly_invalid_gaia_id_,
possibly_invalid_email_);
const std::string gaia_id = possibly_invalid_gaia_id_;
possibly_invalid_account_id_.clear();
possibly_invalid_gaia_id_.clear();
possibly_invalid_email_.clear();
signin_manager_signed_in_ = true;
if (!reauth_in_progress)
FireGoogleSigninSucceeded();
client_->OnSignedIn(GetAuthenticatedAccountId(), gaia_id,
GetAuthenticatedAccountInfo().email, password_);
signin_metrics::LogSigninProfile(client_->IsFirstRun(),
client_->GetInstallDate());
PostSignedIn();
}
void SigninManager::FireGoogleSigninSucceeded() {
std::string account_id = GetAuthenticatedAccountId();
std::string email = GetAuthenticatedAccountInfo().email;
for (auto& observer : observer_list_) {
observer.GoogleSigninSucceeded(account_id, email);
observer.GoogleSigninSucceeded(GetAuthenticatedAccountInfo());
observer.GoogleSigninSucceededWithPassword(account_id, email, password_);
}
}
void SigninManager::FireGoogleSignedOut(const std::string& account_id,
const AccountInfo& account_info) {
for (auto& observer : observer_list_) {
observer.GoogleSignedOut(account_id, account_info.email);
observer.GoogleSignedOut(account_info);
}
}
void SigninManager::PostSignedIn() {
if (!signin_manager_signed_in_ || !user_info_fetched_by_account_tracker_)
return;
client_->PostSignedIn(GetAuthenticatedAccountId(),
GetAuthenticatedAccountInfo().email, password_);
password_.clear();
}
void SigninManager::OnAccountUpdated(const AccountInfo& info) {
if (!info.IsValid())
return;
user_info_fetched_by_account_tracker_ = true;
PostSignedIn();
}
void SigninManager::OnAccountUpdateFailed(const std::string& account_id) {
user_info_fetched_by_account_tracker_ = true;
PostSignedIn();
}
void SigninManager::OnRefreshTokensLoaded() {
token_service_->RemoveObserver(this);
if (account_tracker_service()->GetMigrationState() ==
AccountTrackerService::MIGRATION_IN_PROGRESS) {
account_tracker_service()->SetMigrationDone();
}
// Remove account information from the account tracker service if needed.
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)) {
DVLOG(0) << "Removed account from account tracker service: "
<< account.account_id;
account_tracker_service()->RemoveAccount(account.account_id);
}
}
}
}