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/metrics/histogram.h"
11 #include "base/prefs/pref_service.h"
12 #include "base/strings/string_split.h"
13 #include "base/strings/string_util.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "base/time/time.h"
16 #include "components/signin/core/browser/profile_oauth2_token_service.h"
17 #include "components/signin/core/browser/signin_account_id_helper.h"
18 #include "components/signin/core/browser/signin_client.h"
19 #include "components/signin/core/browser/signin_internals_util.h"
20 #include "components/signin/core/browser/signin_manager_cookie_helper.h"
21 #include "components/signin/core/browser/signin_metrics.h"
22 #include "components/signin/core/common/signin_pref_names.h"
23 #include "google_apis/gaia/gaia_auth_util.h"
24 #include "google_apis/gaia/gaia_constants.h"
25 #include "google_apis/gaia/gaia_urls.h"
26 #include "net/base/escape.h"
27 #include "third_party/icu/source/i18n/unicode/regex.h"
29 using namespace signin_internals_util
;
33 const char kChromiumSyncService
[] = "service=chromiumsync";
37 // Under the covers, we use a dummy chrome-extension ID to serve the purposes
38 // outlined in the .h file comment for this string.
39 const char SigninManager::kChromeSigninEffectiveSite
[] =
40 "chrome-extension://acfccoigjajmmgbhpfbjnpckhjjegnih";
43 bool SigninManager::IsWebBasedSigninFlowURL(const GURL
& url
) {
44 GURL
effective(kChromeSigninEffectiveSite
);
45 if (url
.SchemeIs(effective
.scheme().c_str()) &&
46 url
.host() == effective
.host()) {
50 GURL
service_login(GaiaUrls::GetInstance()->service_login_url());
51 if (url
.GetOrigin() != service_login
.GetOrigin())
54 // Any login UI URLs with signin=chromiumsync should be considered a web
55 // URL (relies on GAIA keeping the "service=chromiumsync" query string
56 // fragment present even when embedding inside a "continue" parameter).
57 return net::UnescapeURLComponent(url
.query(),
58 net::UnescapeRule::URL_SPECIAL_CHARS
)
59 .find(kChromiumSyncService
) != std::string::npos
;
62 SigninManager::SigninManager(SigninClient
* client
,
63 ProfileOAuth2TokenService
* token_service
,
64 AccountTrackerService
* account_tracker_service
)
65 : SigninManagerBase(client
),
66 prohibit_signout_(false),
67 type_(SIGNIN_TYPE_NONE
),
69 token_service_(token_service
),
70 account_tracker_service_(account_tracker_service
),
71 weak_pointer_factory_(this),
72 signin_manager_signed_in_(false),
73 user_info_fetched_by_account_tracker_(false) {}
75 void SigninManager::AddMergeSessionObserver(
76 MergeSessionHelper::Observer
* observer
) {
77 if (merge_session_helper_
)
78 merge_session_helper_
->AddObserver(observer
);
81 void SigninManager::RemoveMergeSessionObserver(
82 MergeSessionHelper::Observer
* observer
) {
83 if (merge_session_helper_
)
84 merge_session_helper_
->RemoveObserver(observer
);
87 SigninManager::~SigninManager() {}
89 void SigninManager::InitTokenService() {
90 if (token_service_
&& IsAuthenticated())
91 token_service_
->LoadCredentials(GetAuthenticatedAccountId());
94 std::string
SigninManager::SigninTypeToString(SigninManager::SigninType type
) {
96 case SIGNIN_TYPE_NONE
:
98 case SIGNIN_TYPE_WITH_REFRESH_TOKEN
:
99 return "Signin with refresh token";
103 return std::string();
106 bool SigninManager::PrepareForSignin(SigninType type
,
107 const std::string
& username
,
108 const std::string
& password
) {
109 DCHECK(possibly_invalid_username_
.empty() ||
110 possibly_invalid_username_
== username
);
111 DCHECK(!username
.empty());
113 if (!IsAllowedUsername(username
)) {
114 // Account is not allowed by admin policy.
116 GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DISABLED
));
120 // This attempt is either 1) the user trying to establish initial sync, or
121 // 2) trying to refresh credentials for an existing username. If it is 2, we
122 // need to try again, but take care to leave state around tracking that the
123 // user has successfully signed in once before with this username, so that on
124 // restart we don't think sync setup has never completed.
125 ClearTransientSigninData();
127 possibly_invalid_username_
.assign(username
);
128 password_
.assign(password
);
129 signin_manager_signed_in_
= false;
130 user_info_fetched_by_account_tracker_
= false;
131 NotifyDiagnosticsObservers(SIGNIN_TYPE
, SigninTypeToString(type
));
135 void SigninManager::StartSignInWithRefreshToken(
136 const std::string
& refresh_token
,
137 const std::string
& username
,
138 const std::string
& password
,
139 const OAuthTokenFetchedCallback
& callback
) {
140 DCHECK(!IsAuthenticated() ||
141 gaia::AreEmailsSame(username
, GetAuthenticatedUsername()));
143 if (!PrepareForSignin(SIGNIN_TYPE_WITH_REFRESH_TOKEN
, username
, password
))
146 // Store our callback and token.
147 temp_refresh_token_
= refresh_token
;
148 possibly_invalid_username_
= username
;
150 NotifyDiagnosticsObservers(GET_USER_INFO_STATUS
, "Successful");
152 if (!callback
.is_null() && !temp_refresh_token_
.empty()) {
153 callback
.Run(temp_refresh_token_
);
155 // No oauth token or callback, so just complete our pending signin.
156 CompletePendingSignin();
160 void SigninManager::CopyCredentialsFrom(const SigninManager
& source
) {
161 DCHECK_NE(this, &source
);
162 possibly_invalid_username_
= source
.possibly_invalid_username_
;
163 temp_refresh_token_
= source
.temp_refresh_token_
;
164 password_
= source
.password_
;
167 void SigninManager::ClearTransientSigninData() {
168 DCHECK(IsInitialized());
170 possibly_invalid_username_
.clear();
172 type_
= SIGNIN_TYPE_NONE
;
173 temp_refresh_token_
.clear();
176 void SigninManager::HandleAuthError(const GoogleServiceAuthError
& error
) {
177 ClearTransientSigninData();
179 FOR_EACH_OBSERVER(SigninManagerBase::Observer
,
181 GoogleSigninFailed(error
));
184 void SigninManager::SignOut(
185 signin_metrics::ProfileSignout signout_source_metric
) {
186 DCHECK(IsInitialized());
188 signin_metrics::LogSignout(signout_source_metric
);
189 if (!IsAuthenticated()) {
190 if (AuthInProgress()) {
191 // If the user is in the process of signing in, then treat a call to
192 // SignOut as a cancellation request.
193 GoogleServiceAuthError
error(GoogleServiceAuthError::REQUEST_CANCELED
);
194 HandleAuthError(error
);
196 // Clean up our transient data and exit if we aren't signed in.
197 // This avoids a perf regression from clearing out the TokenDB if
198 // SignOut() is invoked on startup to clean up any incomplete previous
200 ClearTransientSigninData();
205 if (prohibit_signout_
) {
206 DVLOG(1) << "Ignoring attempt to sign out while signout is prohibited";
210 ClearTransientSigninData();
212 const std::string account_id
= GetAuthenticatedAccountId();
213 const std::string username
= GetAuthenticatedUsername();
214 const base::Time signin_time
=
215 base::Time::FromInternalValue(
216 client_
->GetPrefs()->GetInt64(prefs::kSignedInTime
));
217 ClearAuthenticatedUsername();
218 client_
->GetPrefs()->ClearPref(prefs::kGoogleServicesHostedDomain
);
219 client_
->GetPrefs()->ClearPref(prefs::kGoogleServicesUsername
);
220 client_
->GetPrefs()->ClearPref(prefs::kSignedInTime
);
221 client_
->OnSignedOut();
223 // Erase (now) stale information from AboutSigninInternals.
224 NotifyDiagnosticsObservers(USERNAME
, "");
226 // Determine the duration the user was logged in and log that to UMA.
227 if (!signin_time
.is_null()) {
228 base::TimeDelta signed_in_duration
= base::Time::Now() - signin_time
;
229 UMA_HISTOGRAM_COUNTS("Signin.SignedInDurationBeforeSignout",
230 signed_in_duration
.InMinutes());
233 // Revoke all tokens before sending signed_out notification, because there
234 // may be components that don't listen for token service events when the
235 // profile is not connected to an account.
236 LOG(WARNING
) << "Revoking refresh token on server. Reason: sign out, "
237 << "IsSigninAllowed: " << IsSigninAllowed();
238 token_service_
->RevokeAllCredentials();
240 FOR_EACH_OBSERVER(SigninManagerBase::Observer
,
242 GoogleSignedOut(account_id
, username
));
245 void SigninManager::Initialize(PrefService
* local_state
) {
246 SigninManagerBase::Initialize(local_state
);
248 // local_state can be null during unit tests.
250 local_state_pref_registrar_
.Init(local_state
);
251 local_state_pref_registrar_
.Add(
252 prefs::kGoogleServicesUsernamePattern
,
253 base::Bind(&SigninManager::OnGoogleServicesUsernamePatternChanged
,
254 weak_pointer_factory_
.GetWeakPtr()));
256 signin_allowed_
.Init(prefs::kSigninAllowed
,
258 base::Bind(&SigninManager::OnSigninAllowedPrefChanged
,
259 base::Unretained(this)));
262 client_
->GetPrefs()->GetString(prefs::kGoogleServicesUsername
);
263 if ((!user
.empty() && !IsAllowedUsername(user
)) || !IsSigninAllowed()) {
264 // User is signed in, but the username is invalid - the administrator must
265 // have changed the policy since the last signin, so sign out the user.
266 SignOut(signin_metrics::SIGNIN_PREF_CHANGED_DURING_SIGNIN
);
270 account_id_helper_
.reset(
271 new SigninAccountIdHelper(client_
, token_service_
, this));
273 account_tracker_service_
->AddObserver(this);
276 void SigninManager::Shutdown() {
277 account_tracker_service_
->RemoveObserver(this);
278 if (merge_session_helper_
)
279 merge_session_helper_
->CancelAll();
281 local_state_pref_registrar_
.RemoveAll();
282 account_id_helper_
.reset();
283 SigninManagerBase::Shutdown();
286 void SigninManager::OnGoogleServicesUsernamePatternChanged() {
287 if (IsAuthenticated() &&
288 !IsAllowedUsername(GetAuthenticatedUsername())) {
289 // Signed in user is invalid according to the current policy so sign
291 SignOut(signin_metrics::GOOGLE_SERVICE_NAME_PATTERN_CHANGED
);
295 bool SigninManager::IsSigninAllowed() const {
296 return signin_allowed_
.GetValue();
299 void SigninManager::OnSigninAllowedPrefChanged() {
300 if (!IsSigninAllowed())
301 SignOut(signin_metrics::SIGNOUT_PREF_CHANGED
);
305 bool SigninManager::IsUsernameAllowedByPolicy(const std::string
& username
,
306 const std::string
& policy
) {
310 // Patterns like "*@foo.com" are not accepted by our regex engine (since they
311 // are not valid regular expressions - they should instead be ".*@foo.com").
312 // For convenience, detect these patterns and insert a "." character at the
314 base::string16 pattern
= base::UTF8ToUTF16(policy
);
315 if (pattern
[0] == L
'*')
316 pattern
.insert(pattern
.begin(), L
'.');
318 // See if the username matches the policy-provided pattern.
319 UErrorCode status
= U_ZERO_ERROR
;
320 const icu::UnicodeString
icu_pattern(pattern
.data(), pattern
.length());
321 icu::RegexMatcher
matcher(icu_pattern
, UREGEX_CASE_INSENSITIVE
, status
);
322 if (!U_SUCCESS(status
)) {
323 LOG(ERROR
) << "Invalid login regex: " << pattern
<< ", status: " << status
;
324 // If an invalid pattern is provided, then prohibit *all* logins (better to
325 // break signin than to quietly allow users to sign in).
328 base::string16 username16
= base::UTF8ToUTF16(username
);
329 icu::UnicodeString
icu_input(username16
.data(), username16
.length());
330 matcher
.reset(icu_input
);
331 status
= U_ZERO_ERROR
;
332 UBool match
= matcher
.matches(status
);
333 DCHECK(U_SUCCESS(status
));
334 return !!match
; // !! == convert from UBool to bool.
337 bool SigninManager::IsAllowedUsername(const std::string
& username
) const {
338 const PrefService
* local_state
= local_state_pref_registrar_
.prefs();
340 return true; // In a unit test with no local state - all names are allowed.
342 std::string pattern
=
343 local_state
->GetString(prefs::kGoogleServicesUsernamePattern
);
344 return IsUsernameAllowedByPolicy(username
, pattern
);
347 bool SigninManager::AuthInProgress() const {
348 return !possibly_invalid_username_
.empty();
351 const std::string
& SigninManager::GetUsernameForAuthInProgress() const {
352 return possibly_invalid_username_
;
355 void SigninManager::DisableOneClickSignIn(PrefService
* prefs
) {
356 prefs
->SetBoolean(prefs::kReverseAutologinEnabled
, false);
359 void SigninManager::CompletePendingSignin() {
360 DCHECK(!possibly_invalid_username_
.empty());
361 OnSignedIn(possibly_invalid_username_
);
363 if (client_
->ShouldMergeSigninCredentialsIntoCookieJar()) {
364 merge_session_helper_
.reset(new MergeSessionHelper(
365 token_service_
, GaiaConstants::kChromeSource
,
366 client_
->GetURLRequestContext(), NULL
));
369 DCHECK(!temp_refresh_token_
.empty());
370 DCHECK(IsAuthenticated());
371 std::string account_id
= GetAuthenticatedAccountId();
372 token_service_
->UpdateCredentials(account_id
, temp_refresh_token_
);
373 temp_refresh_token_
.clear();
375 if (client_
->ShouldMergeSigninCredentialsIntoCookieJar())
376 merge_session_helper_
->LogIn(account_id
);
379 void SigninManager::OnExternalSigninCompleted(const std::string
& username
) {
380 OnSignedIn(username
);
383 void SigninManager::OnSignedIn(const std::string
& username
) {
384 client_
->GetPrefs()->SetInt64(prefs::kSignedInTime
,
385 base::Time::Now().ToInternalValue());
386 SetAuthenticatedUsername(username
);
387 possibly_invalid_username_
.clear();
388 signin_manager_signed_in_
= true;
391 SigninManagerBase::Observer
,
393 GoogleSigninSucceeded(GetAuthenticatedAccountId(),
394 GetAuthenticatedUsername(),
397 client_
->OnSignedIn(GetAuthenticatedAccountId(),
398 GetAuthenticatedUsername(),
401 signin_metrics::LogSigninProfile(client_
->IsFirstRun(),
402 client_
->GetInstallDate());
404 DisableOneClickSignIn(client_
->GetPrefs()); // Don't ever offer again.
409 void SigninManager::PostSignedIn() {
410 if (!signin_manager_signed_in_
|| !user_info_fetched_by_account_tracker_
)
413 client_
->PostSignedIn(GetAuthenticatedAccountId(),
414 GetAuthenticatedUsername(),
419 void SigninManager::OnAccountUpdated(
420 const AccountTrackerService::AccountInfo
& info
) {
421 user_info_fetched_by_account_tracker_
= true;
425 void SigninManager::OnAccountUpdateFailed(const std::string
& account_id
) {
426 user_info_fetched_by_account_tracker_
= true;
430 void SigninManager::ProhibitSignout(bool prohibit_signout
) {
431 prohibit_signout_
= prohibit_signout
;
434 bool SigninManager::IsSignoutProhibited() const { return prohibit_signout_
; }