blob: 144242615a9b0fc851069d0c4d50a095e4733b06 [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/prefs/pref_service.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/signin/core/browser/profile_oauth2_token_service.h"
#include "components/signin/core/browser/signin_account_id_helper.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_cookie_helper.h"
#include "components/signin/core/browser/signin_metrics.h"
#include "components/signin/core/common/signin_pref_names.h"
#include "google_apis/gaia/gaia_auth_util.h"
#include "google_apis/gaia/gaia_urls.h"
#include "net/base/escape.h"
#include "third_party/icu/source/i18n/unicode/regex.h"
using namespace signin_internals_util;
namespace {
const char kChromiumSyncService[] = "service=chromiumsync";
} // namespace
// Under the covers, we use a dummy chrome-extension ID to serve the purposes
// outlined in the .h file comment for this string.
const char SigninManager::kChromeSigninEffectiveSite[] =
"chrome-extension://acfccoigjajmmgbhpfbjnpckhjjegnih";
// static
bool SigninManager::IsWebBasedSigninFlowURL(const GURL& url) {
GURL effective(kChromeSigninEffectiveSite);
if (url.SchemeIs(effective.scheme().c_str()) &&
url.host() == effective.host()) {
return true;
}
GURL service_login(GaiaUrls::GetInstance()->service_login_url());
if (url.GetOrigin() != service_login.GetOrigin())
return false;
// Any login UI URLs with signin=chromiumsync should be considered a web
// URL (relies on GAIA keeping the "service=chromiumsync" query string
// fragment present even when embedding inside a "continue" parameter).
return net::UnescapeURLComponent(url.query(),
net::UnescapeRule::URL_SPECIAL_CHARS)
.find(kChromiumSyncService) != std::string::npos;
}
SigninManager::SigninManager(SigninClient* client,
ProfileOAuth2TokenService* token_service)
: SigninManagerBase(client),
prohibit_signout_(false),
type_(SIGNIN_TYPE_NONE),
weak_pointer_factory_(this),
client_(client),
token_service_(token_service) {}
void SigninManager::AddMergeSessionObserver(
MergeSessionHelper::Observer* observer) {
if (merge_session_helper_)
merge_session_helper_->AddObserver(observer);
}
void SigninManager::RemoveMergeSessionObserver(
MergeSessionHelper::Observer* observer) {
if (merge_session_helper_)
merge_session_helper_->RemoveObserver(observer);
}
SigninManager::~SigninManager() {}
void SigninManager::InitTokenService() {
const std::string& account_id = GetAuthenticatedUsername();
if (token_service_ && !account_id.empty())
token_service_->LoadCredentials(account_id);
}
std::string SigninManager::SigninTypeToString(SigninManager::SigninType type) {
switch (type) {
case SIGNIN_TYPE_NONE:
return "No Signin";
case SIGNIN_TYPE_WITH_REFRESH_TOKEN:
return "Signin with refresh token";
}
NOTREACHED();
return std::string();
}
bool SigninManager::PrepareForSignin(SigninType type,
const std::string& username,
const std::string& password) {
DCHECK(possibly_invalid_username_.empty() ||
possibly_invalid_username_ == username);
DCHECK(!username.empty());
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_username_.assign(username);
password_.assign(password);
NotifyDiagnosticsObservers(SIGNIN_TYPE, SigninTypeToString(type));
return true;
}
void SigninManager::StartSignInWithRefreshToken(
const std::string& refresh_token,
const std::string& username,
const std::string& password,
const OAuthTokenFetchedCallback& callback) {
DCHECK(GetAuthenticatedUsername().empty() ||
gaia::AreEmailsSame(username, GetAuthenticatedUsername()));
if (!PrepareForSignin(SIGNIN_TYPE_WITH_REFRESH_TOKEN, username, password))
return;
// Store our callback and token.
temp_refresh_token_ = refresh_token;
possibly_invalid_username_ = username;
NotifyDiagnosticsObservers(GET_USER_INFO_STATUS, "Successful");
if (!callback.is_null() && !temp_refresh_token_.empty()) {
callback.Run(temp_refresh_token_);
} else {
// No oauth token or callback, so just complete our pending signin.
CompletePendingSignin();
}
}
void SigninManager::CopyCredentialsFrom(const SigninManager& source) {
DCHECK_NE(this, &source);
possibly_invalid_username_ = source.possibly_invalid_username_;
temp_refresh_token_ = source.temp_refresh_token_;
password_ = source.password_;
}
void SigninManager::ClearTransientSigninData() {
DCHECK(IsInitialized());
possibly_invalid_username_.clear();
password_.clear();
type_ = SIGNIN_TYPE_NONE;
temp_refresh_token_.clear();
}
void SigninManager::HandleAuthError(const GoogleServiceAuthError& error) {
ClearTransientSigninData();
FOR_EACH_OBSERVER(Observer, observer_list_, GoogleSigninFailed(error));
}
void SigninManager::SignOut(
signin_metrics::ProfileSignout signout_source_metric) {
DCHECK(IsInitialized());
signin_metrics::LogSignout(signout_source_metric);
if (GetAuthenticatedUsername().empty()) {
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;
}
if (prohibit_signout_) {
DVLOG(1) << "Ignoring attempt to sign out while signout is prohibited";
return;
}
ClearTransientSigninData();
const std::string username = GetAuthenticatedUsername();
clear_authenticated_username();
client_->GetPrefs()->ClearPref(prefs::kGoogleServicesUsername);
// Erase (now) stale information from AboutSigninInternals.
NotifyDiagnosticsObservers(USERNAME, "");
// 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.
LOG(WARNING) << "Revoking refresh token on server. Reason: sign out, "
<< "IsSigninAllowed: " << IsSigninAllowed();
token_service_->RevokeAllCredentials();
FOR_EACH_OBSERVER(Observer, observer_list_, GoogleSignedOut(username));
}
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 user =
client_->GetPrefs()->GetString(prefs::kGoogleServicesUsername);
if ((!user.empty() && !IsAllowedUsername(user)) || !IsSigninAllowed()) {
// User is signed in, but the username is invalid - the administrator must
// have changed the policy since the last signin, so sign out the user.
SignOut(signin_metrics::SIGNIN_PREF_CHANGED_DURING_SIGNIN);
}
InitTokenService();
account_id_helper_.reset(
new SigninAccountIdHelper(client_, token_service_, this));
}
void SigninManager::Shutdown() {
if (merge_session_helper_)
merge_session_helper_->CancelAll();
local_state_pref_registrar_.RemoveAll();
account_id_helper_.reset();
SigninManagerBase::Shutdown();
}
void SigninManager::OnGoogleServicesUsernamePatternChanged() {
if (!GetAuthenticatedUsername().empty() &&
!IsAllowedUsername(GetAuthenticatedUsername())) {
// Signed in user is invalid according to the current policy so sign
// the user out.
SignOut(signin_metrics::GOOGLE_SERVICE_NAME_PATTERN_CHANGED);
}
}
bool SigninManager::IsSigninAllowed() const {
return signin_allowed_.GetValue();
}
void SigninManager::OnSigninAllowedPrefChanged() {
if (!IsSigninAllowed())
SignOut(signin_metrics::SIGNOUT_PREF_CHANGED);
}
// 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(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;
}
base::string16 username16 = base::UTF8ToUTF16(username);
icu::UnicodeString icu_input(username16.data(), username16.length());
matcher.reset(icu_input);
status = U_ZERO_ERROR;
UBool match = matcher.matches(status);
DCHECK(U_SUCCESS(status));
return !!match; // !! == convert from UBool to bool.
}
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_username_.empty();
}
const std::string& SigninManager::GetUsernameForAuthInProgress() const {
return possibly_invalid_username_;
}
void SigninManager::DisableOneClickSignIn(PrefService* prefs) {
prefs->SetBoolean(prefs::kReverseAutologinEnabled, false);
}
void SigninManager::CompletePendingSignin() {
DCHECK(!possibly_invalid_username_.empty());
OnSignedIn(possibly_invalid_username_);
if (client_->ShouldMergeSigninCredentialsIntoCookieJar()) {
merge_session_helper_.reset(new MergeSessionHelper(
token_service_, client_->GetURLRequestContext(), NULL));
}
DCHECK(!temp_refresh_token_.empty());
DCHECK(!GetAuthenticatedUsername().empty());
token_service_->UpdateCredentials(GetAuthenticatedUsername(),
temp_refresh_token_);
temp_refresh_token_.clear();
if (client_->ShouldMergeSigninCredentialsIntoCookieJar())
merge_session_helper_->LogIn(GetAuthenticatedUsername());
}
void SigninManager::OnExternalSigninCompleted(const std::string& username) {
OnSignedIn(username);
}
void SigninManager::OnSignedIn(const std::string& username) {
SetAuthenticatedUsername(username);
possibly_invalid_username_.clear();
FOR_EACH_OBSERVER(
Observer,
observer_list_,
GoogleSigninSucceeded(GetAuthenticatedUsername(), password_));
client_->GoogleSigninSucceeded(GetAuthenticatedUsername(), password_);
password_.clear(); // Don't need it anymore.
DisableOneClickSignIn(client_->GetPrefs()); // Don't ever offer again.
}
void SigninManager::ProhibitSignout(bool prohibit_signout) {
prohibit_signout_ = prohibit_signout;
}
bool SigninManager::IsSignoutProhibited() const { return prohibit_signout_; }