1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "components/signin/core/browser/signin_manager.h"
10 #include "base/prefs/pref_service.h"
11 #include "base/strings/string_split.h"
12 #include "base/strings/string_util.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "base/time/time.h"
15 #include "components/signin/core/browser/profile_oauth2_token_service.h"
16 #include "components/signin/core/browser/signin_account_id_helper.h"
17 #include "components/signin/core/browser/signin_client.h"
18 #include "components/signin/core/browser/signin_internals_util.h"
19 #include "components/signin/core/browser/signin_manager_cookie_helper.h"
20 #include "components/signin/core/browser/signin_metrics.h"
21 #include "components/signin/core/common/signin_pref_names.h"
22 #include "google_apis/gaia/gaia_auth_util.h"
23 #include "google_apis/gaia/gaia_urls.h"
24 #include "net/base/escape.h"
25 #include "third_party/icu/source/i18n/unicode/regex.h"
27 using namespace signin_internals_util;
31 const char kChromiumSyncService[] = "service=chromiumsync";
35 // Under the covers, we use a dummy chrome-extension ID to serve the purposes
36 // outlined in the .h file comment for this string.
37 const char SigninManager::kChromeSigninEffectiveSite[] =
38 "chrome-extension://acfccoigjajmmgbhpfbjnpckhjjegnih";
41 bool SigninManager::IsWebBasedSigninFlowURL(const GURL& url) {
42 GURL effective(kChromeSigninEffectiveSite);
43 if (url.SchemeIs(effective.scheme().c_str()) &&
44 url.host() == effective.host()) {
48 GURL service_login(GaiaUrls::GetInstance()->service_login_url());
49 if (url.GetOrigin() != service_login.GetOrigin())
52 // Any login UI URLs with signin=chromiumsync should be considered a web
53 // URL (relies on GAIA keeping the "service=chromiumsync" query string
54 // fragment present even when embedding inside a "continue" parameter).
55 return net::UnescapeURLComponent(url.query(),
56 net::UnescapeRule::URL_SPECIAL_CHARS)
57 .find(kChromiumSyncService) != std::string::npos;
60 SigninManager::SigninManager(SigninClient* client,
61 ProfileOAuth2TokenService* token_service)
62 : SigninManagerBase(client),
63 prohibit_signout_(false),
64 type_(SIGNIN_TYPE_NONE),
65 weak_pointer_factory_(this),
67 token_service_(token_service) {}
69 void SigninManager::AddMergeSessionObserver(
70 MergeSessionHelper::Observer* observer) {
71 if (merge_session_helper_)
72 merge_session_helper_->AddObserver(observer);
75 void SigninManager::RemoveMergeSessionObserver(
76 MergeSessionHelper::Observer* observer) {
77 if (merge_session_helper_)
78 merge_session_helper_->RemoveObserver(observer);
81 SigninManager::~SigninManager() {}
83 void SigninManager::InitTokenService() {
84 const std::string& account_id = GetAuthenticatedUsername();
85 if (token_service_ && !account_id.empty())
86 token_service_->LoadCredentials(account_id);
89 std::string SigninManager::SigninTypeToString(SigninManager::SigninType type) {
91 case SIGNIN_TYPE_NONE:
93 case SIGNIN_TYPE_WITH_REFRESH_TOKEN:
94 return "Signin with refresh token";
101 bool SigninManager::PrepareForSignin(SigninType type,
102 const std::string& username,
103 const std::string& password) {
104 DCHECK(possibly_invalid_username_.empty() ||
105 possibly_invalid_username_ == username);
106 DCHECK(!username.empty());
108 if (!IsAllowedUsername(username)) {
109 // Account is not allowed by admin policy.
111 GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DISABLED));
115 // This attempt is either 1) the user trying to establish initial sync, or
116 // 2) trying to refresh credentials for an existing username. If it is 2, we
117 // need to try again, but take care to leave state around tracking that the
118 // user has successfully signed in once before with this username, so that on
119 // restart we don't think sync setup has never completed.
120 ClearTransientSigninData();
122 possibly_invalid_username_.assign(username);
123 password_.assign(password);
124 NotifyDiagnosticsObservers(SIGNIN_TYPE, SigninTypeToString(type));
128 void SigninManager::StartSignInWithRefreshToken(
129 const std::string& refresh_token,
130 const std::string& username,
131 const std::string& password,
132 const OAuthTokenFetchedCallback& callback) {
133 DCHECK(GetAuthenticatedUsername().empty() ||
134 gaia::AreEmailsSame(username, GetAuthenticatedUsername()));
136 if (!PrepareForSignin(SIGNIN_TYPE_WITH_REFRESH_TOKEN, username, password))
139 // Store our callback and token.
140 temp_refresh_token_ = refresh_token;
141 possibly_invalid_username_ = username;
143 NotifyDiagnosticsObservers(GET_USER_INFO_STATUS, "Successful");
145 if (!callback.is_null() && !temp_refresh_token_.empty()) {
146 callback.Run(temp_refresh_token_);
148 // No oauth token or callback, so just complete our pending signin.
149 CompletePendingSignin();
153 void SigninManager::CopyCredentialsFrom(const SigninManager& source) {
154 DCHECK_NE(this, &source);
155 possibly_invalid_username_ = source.possibly_invalid_username_;
156 temp_refresh_token_ = source.temp_refresh_token_;
157 password_ = source.password_;
160 void SigninManager::ClearTransientSigninData() {
161 DCHECK(IsInitialized());
163 possibly_invalid_username_.clear();
165 type_ = SIGNIN_TYPE_NONE;
166 temp_refresh_token_.clear();
169 void SigninManager::HandleAuthError(const GoogleServiceAuthError& error) {
170 ClearTransientSigninData();
172 FOR_EACH_OBSERVER(Observer, observer_list_, GoogleSigninFailed(error));
175 void SigninManager::SignOut(
176 signin_metrics::ProfileSignout signout_source_metric) {
177 DCHECK(IsInitialized());
179 signin_metrics::LogSignout(signout_source_metric);
180 if (GetAuthenticatedUsername().empty()) {
181 if (AuthInProgress()) {
182 // If the user is in the process of signing in, then treat a call to
183 // SignOut as a cancellation request.
184 GoogleServiceAuthError error(GoogleServiceAuthError::REQUEST_CANCELED);
185 HandleAuthError(error);
187 // Clean up our transient data and exit if we aren't signed in.
188 // This avoids a perf regression from clearing out the TokenDB if
189 // SignOut() is invoked on startup to clean up any incomplete previous
191 ClearTransientSigninData();
196 if (prohibit_signout_) {
197 DVLOG(1) << "Ignoring attempt to sign out while signout is prohibited";
201 ClearTransientSigninData();
203 const std::string username = GetAuthenticatedUsername();
204 clear_authenticated_username();
205 client_->GetPrefs()->ClearPref(prefs::kGoogleServicesUsername);
207 // Erase (now) stale information from AboutSigninInternals.
208 NotifyDiagnosticsObservers(USERNAME, "");
210 // Revoke all tokens before sending signed_out notification, because there
211 // may be components that don't listen for token service events when the
212 // profile is not connected to an account.
213 LOG(WARNING) << "Revoking refresh token on server. Reason: sign out, "
214 << "IsSigninAllowed: " << IsSigninAllowed();
215 token_service_->RevokeAllCredentials();
217 FOR_EACH_OBSERVER(Observer, observer_list_, GoogleSignedOut(username));
220 void SigninManager::Initialize(PrefService* local_state) {
221 SigninManagerBase::Initialize(local_state);
223 // local_state can be null during unit tests.
225 local_state_pref_registrar_.Init(local_state);
226 local_state_pref_registrar_.Add(
227 prefs::kGoogleServicesUsernamePattern,
228 base::Bind(&SigninManager::OnGoogleServicesUsernamePatternChanged,
229 weak_pointer_factory_.GetWeakPtr()));
231 signin_allowed_.Init(prefs::kSigninAllowed,
233 base::Bind(&SigninManager::OnSigninAllowedPrefChanged,
234 base::Unretained(this)));
237 client_->GetPrefs()->GetString(prefs::kGoogleServicesUsername);
238 if ((!user.empty() && !IsAllowedUsername(user)) || !IsSigninAllowed()) {
239 // User is signed in, but the username is invalid - the administrator must
240 // have changed the policy since the last signin, so sign out the user.
241 SignOut(signin_metrics::SIGNIN_PREF_CHANGED_DURING_SIGNIN);
245 account_id_helper_.reset(
246 new SigninAccountIdHelper(client_, token_service_, this));
249 void SigninManager::Shutdown() {
250 if (merge_session_helper_)
251 merge_session_helper_->CancelAll();
253 local_state_pref_registrar_.RemoveAll();
254 account_id_helper_.reset();
255 SigninManagerBase::Shutdown();
258 void SigninManager::OnGoogleServicesUsernamePatternChanged() {
259 if (!GetAuthenticatedUsername().empty() &&
260 !IsAllowedUsername(GetAuthenticatedUsername())) {
261 // Signed in user is invalid according to the current policy so sign
263 SignOut(signin_metrics::GOOGLE_SERVICE_NAME_PATTERN_CHANGED);
267 bool SigninManager::IsSigninAllowed() const {
268 return signin_allowed_.GetValue();
271 void SigninManager::OnSigninAllowedPrefChanged() {
272 if (!IsSigninAllowed())
273 SignOut(signin_metrics::SIGNOUT_PREF_CHANGED);
277 bool SigninManager::IsUsernameAllowedByPolicy(const std::string& username,
278 const std::string& policy) {
282 // Patterns like "*@foo.com" are not accepted by our regex engine (since they
283 // are not valid regular expressions - they should instead be ".*@foo.com").
284 // For convenience, detect these patterns and insert a "." character at the
286 base::string16 pattern = base::UTF8ToUTF16(policy);
287 if (pattern[0] == L'*')
288 pattern.insert(pattern.begin(), L'.');
290 // See if the username matches the policy-provided pattern.
291 UErrorCode status = U_ZERO_ERROR;
292 const icu::UnicodeString icu_pattern(pattern.data(), pattern.length());
293 icu::RegexMatcher matcher(icu_pattern, UREGEX_CASE_INSENSITIVE, status);
294 if (!U_SUCCESS(status)) {
295 LOG(ERROR) << "Invalid login regex: " << pattern << ", status: " << status;
296 // If an invalid pattern is provided, then prohibit *all* logins (better to
297 // break signin than to quietly allow users to sign in).
300 base::string16 username16 = base::UTF8ToUTF16(username);
301 icu::UnicodeString icu_input(username16.data(), username16.length());
302 matcher.reset(icu_input);
303 status = U_ZERO_ERROR;
304 UBool match = matcher.matches(status);
305 DCHECK(U_SUCCESS(status));
306 return !!match; // !! == convert from UBool to bool.
309 bool SigninManager::IsAllowedUsername(const std::string& username) const {
310 const PrefService* local_state = local_state_pref_registrar_.prefs();
312 return true; // In a unit test with no local state - all names are allowed.
314 std::string pattern =
315 local_state->GetString(prefs::kGoogleServicesUsernamePattern);
316 return IsUsernameAllowedByPolicy(username, pattern);
319 bool SigninManager::AuthInProgress() const {
320 return !possibly_invalid_username_.empty();
323 const std::string& SigninManager::GetUsernameForAuthInProgress() const {
324 return possibly_invalid_username_;
327 void SigninManager::DisableOneClickSignIn(PrefService* prefs) {
328 prefs->SetBoolean(prefs::kReverseAutologinEnabled, false);
331 void SigninManager::CompletePendingSignin() {
332 DCHECK(!possibly_invalid_username_.empty());
333 OnSignedIn(possibly_invalid_username_);
335 if (client_->ShouldMergeSigninCredentialsIntoCookieJar()) {
336 merge_session_helper_.reset(new MergeSessionHelper(
337 token_service_, client_->GetURLRequestContext(), NULL));
340 DCHECK(!temp_refresh_token_.empty());
341 DCHECK(!GetAuthenticatedUsername().empty());
342 token_service_->UpdateCredentials(GetAuthenticatedUsername(),
343 temp_refresh_token_);
344 temp_refresh_token_.clear();
346 if (client_->ShouldMergeSigninCredentialsIntoCookieJar())
347 merge_session_helper_->LogIn(GetAuthenticatedUsername());
350 void SigninManager::OnExternalSigninCompleted(const std::string& username) {
351 OnSignedIn(username);
354 void SigninManager::OnSignedIn(const std::string& username) {
355 SetAuthenticatedUsername(username);
356 possibly_invalid_username_.clear();
361 GoogleSigninSucceeded(GetAuthenticatedUsername(), password_));
363 client_->GoogleSigninSucceeded(GetAuthenticatedUsername(), password_);
365 password_.clear(); // Don't need it anymore.
366 DisableOneClickSignIn(client_->GetPrefs()); // Don't ever offer again.
369 void SigninManager::ProhibitSignout(bool prohibit_signout) {
370 prohibit_signout_ = prohibit_signout;
373 bool SigninManager::IsSignoutProhibited() const { return prohibit_signout_; }