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.
4
5 #include "components/signin/core/browser/signin_manager.h"
6
7 #include <string>
8 #include <vector>
9
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_urls.h"
25 #include "net/base/escape.h"
26 #include "third_party/icu/source/i18n/unicode/regex.h"
27
28 using namespace signin_internals_util;
29
30 namespace {
31
32 const char kChromiumSyncService[] = "service=chromiumsync";
33
34 } // namespace
35
36 // Under the covers, we use a dummy chrome-extension ID to serve the purposes
37 // outlined in the .h file comment for this string.
38 const char SigninManager::kChromeSigninEffectiveSite[] =
39 "chrome-extension://acfccoigjajmmgbhpfbjnpckhjjegnih";
40
41 // static
IsWebBasedSigninFlowURL(const GURL & url)42 bool SigninManager::IsWebBasedSigninFlowURL(const GURL& url) {
43 GURL effective(kChromeSigninEffectiveSite);
44 if (url.SchemeIs(effective.scheme().c_str()) &&
45 url.host() == effective.host()) {
46 return true;
47 }
48
49 GURL service_login(GaiaUrls::GetInstance()->service_login_url());
50 if (url.GetOrigin() != service_login.GetOrigin())
51 return false;
52
53 // Any login UI URLs with signin=chromiumsync should be considered a web
54 // URL (relies on GAIA keeping the "service=chromiumsync" query string
55 // fragment present even when embedding inside a "continue" parameter).
56 return net::UnescapeURLComponent(url.query(),
57 net::UnescapeRule::URL_SPECIAL_CHARS)
58 .find(kChromiumSyncService) != std::string::npos;
59 }
60
SigninManager(SigninClient * client,ProfileOAuth2TokenService * token_service)61 SigninManager::SigninManager(SigninClient* client,
62 ProfileOAuth2TokenService* token_service)
63 : SigninManagerBase(client),
64 prohibit_signout_(false),
65 type_(SIGNIN_TYPE_NONE),
66 client_(client),
67 token_service_(token_service),
68 weak_pointer_factory_(this) {}
69
AddMergeSessionObserver(MergeSessionHelper::Observer * observer)70 void SigninManager::AddMergeSessionObserver(
71 MergeSessionHelper::Observer* observer) {
72 if (merge_session_helper_)
73 merge_session_helper_->AddObserver(observer);
74 }
75
RemoveMergeSessionObserver(MergeSessionHelper::Observer * observer)76 void SigninManager::RemoveMergeSessionObserver(
77 MergeSessionHelper::Observer* observer) {
78 if (merge_session_helper_)
79 merge_session_helper_->RemoveObserver(observer);
80 }
81
~SigninManager()82 SigninManager::~SigninManager() {}
83
InitTokenService()84 void SigninManager::InitTokenService() {
85 const std::string& account_id = GetAuthenticatedUsername();
86 if (token_service_ && !account_id.empty())
87 token_service_->LoadCredentials(account_id);
88 }
89
SigninTypeToString(SigninManager::SigninType type)90 std::string SigninManager::SigninTypeToString(SigninManager::SigninType type) {
91 switch (type) {
92 case SIGNIN_TYPE_NONE:
93 return "No Signin";
94 case SIGNIN_TYPE_WITH_REFRESH_TOKEN:
95 return "Signin with refresh token";
96 }
97
98 NOTREACHED();
99 return std::string();
100 }
101
PrepareForSignin(SigninType type,const std::string & username,const std::string & password)102 bool SigninManager::PrepareForSignin(SigninType type,
103 const std::string& username,
104 const std::string& password) {
105 DCHECK(possibly_invalid_username_.empty() ||
106 possibly_invalid_username_ == username);
107 DCHECK(!username.empty());
108
109 if (!IsAllowedUsername(username)) {
110 // Account is not allowed by admin policy.
111 HandleAuthError(
112 GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DISABLED));
113 return false;
114 }
115
116 // This attempt is either 1) the user trying to establish initial sync, or
117 // 2) trying to refresh credentials for an existing username. If it is 2, we
118 // need to try again, but take care to leave state around tracking that the
119 // user has successfully signed in once before with this username, so that on
120 // restart we don't think sync setup has never completed.
121 ClearTransientSigninData();
122 type_ = type;
123 possibly_invalid_username_.assign(username);
124 password_.assign(password);
125 NotifyDiagnosticsObservers(SIGNIN_TYPE, SigninTypeToString(type));
126 return true;
127 }
128
StartSignInWithRefreshToken(const std::string & refresh_token,const std::string & username,const std::string & password,const OAuthTokenFetchedCallback & callback)129 void SigninManager::StartSignInWithRefreshToken(
130 const std::string& refresh_token,
131 const std::string& username,
132 const std::string& password,
133 const OAuthTokenFetchedCallback& callback) {
134 DCHECK(!IsAuthenticated() ||
135 gaia::AreEmailsSame(username, GetAuthenticatedUsername()));
136
137 if (!PrepareForSignin(SIGNIN_TYPE_WITH_REFRESH_TOKEN, username, password))
138 return;
139
140 // Store our callback and token.
141 temp_refresh_token_ = refresh_token;
142 possibly_invalid_username_ = username;
143
144 NotifyDiagnosticsObservers(GET_USER_INFO_STATUS, "Successful");
145
146 if (!callback.is_null() && !temp_refresh_token_.empty()) {
147 callback.Run(temp_refresh_token_);
148 } else {
149 // No oauth token or callback, so just complete our pending signin.
150 CompletePendingSignin();
151 }
152 }
153
CopyCredentialsFrom(const SigninManager & source)154 void SigninManager::CopyCredentialsFrom(const SigninManager& source) {
155 DCHECK_NE(this, &source);
156 possibly_invalid_username_ = source.possibly_invalid_username_;
157 temp_refresh_token_ = source.temp_refresh_token_;
158 password_ = source.password_;
159 }
160
ClearTransientSigninData()161 void SigninManager::ClearTransientSigninData() {
162 DCHECK(IsInitialized());
163
164 possibly_invalid_username_.clear();
165 password_.clear();
166 type_ = SIGNIN_TYPE_NONE;
167 temp_refresh_token_.clear();
168 }
169
HandleAuthError(const GoogleServiceAuthError & error)170 void SigninManager::HandleAuthError(const GoogleServiceAuthError& error) {
171 ClearTransientSigninData();
172
173 FOR_EACH_OBSERVER(Observer, observer_list_, GoogleSigninFailed(error));
174 }
175
SignOut(signin_metrics::ProfileSignout signout_source_metric)176 void SigninManager::SignOut(
177 signin_metrics::ProfileSignout signout_source_metric) {
178 DCHECK(IsInitialized());
179
180 signin_metrics::LogSignout(signout_source_metric);
181 if (!IsAuthenticated()) {
182 if (AuthInProgress()) {
183 // If the user is in the process of signing in, then treat a call to
184 // SignOut as a cancellation request.
185 GoogleServiceAuthError error(GoogleServiceAuthError::REQUEST_CANCELED);
186 HandleAuthError(error);
187 } else {
188 // Clean up our transient data and exit if we aren't signed in.
189 // This avoids a perf regression from clearing out the TokenDB if
190 // SignOut() is invoked on startup to clean up any incomplete previous
191 // signin attempts.
192 ClearTransientSigninData();
193 }
194 return;
195 }
196
197 if (prohibit_signout_) {
198 DVLOG(1) << "Ignoring attempt to sign out while signout is prohibited";
199 return;
200 }
201
202 ClearTransientSigninData();
203
204 const std::string account_id = GetAuthenticatedAccountId();
205 const std::string username = GetAuthenticatedUsername();
206 const base::Time signin_time =
207 base::Time::FromInternalValue(
208 client_->GetPrefs()->GetInt64(prefs::kSignedInTime));
209 clear_authenticated_username();
210 client_->GetPrefs()->ClearPref(prefs::kGoogleServicesUsername);
211 client_->GetPrefs()->ClearPref(prefs::kSignedInTime);
212 client_->ClearSigninScopedDeviceId();
213
214 // Erase (now) stale information from AboutSigninInternals.
215 NotifyDiagnosticsObservers(USERNAME, "");
216
217 // Determine the duration the user was logged in and log that to UMA.
218 if (!signin_time.is_null()) {
219 base::TimeDelta signed_in_duration = base::Time::Now() - signin_time;
220 UMA_HISTOGRAM_COUNTS("Signin.SignedInDurationBeforeSignout",
221 signed_in_duration.InMinutes());
222 }
223
224 // Revoke all tokens before sending signed_out notification, because there
225 // may be components that don't listen for token service events when the
226 // profile is not connected to an account.
227 LOG(WARNING) << "Revoking refresh token on server. Reason: sign out, "
228 << "IsSigninAllowed: " << IsSigninAllowed();
229 token_service_->RevokeAllCredentials();
230
231 FOR_EACH_OBSERVER(Observer,
232 observer_list_,
233 GoogleSignedOut(account_id, username));
234 }
235
Initialize(PrefService * local_state)236 void SigninManager::Initialize(PrefService* local_state) {
237 SigninManagerBase::Initialize(local_state);
238
239 // local_state can be null during unit tests.
240 if (local_state) {
241 local_state_pref_registrar_.Init(local_state);
242 local_state_pref_registrar_.Add(
243 prefs::kGoogleServicesUsernamePattern,
244 base::Bind(&SigninManager::OnGoogleServicesUsernamePatternChanged,
245 weak_pointer_factory_.GetWeakPtr()));
246 }
247 signin_allowed_.Init(prefs::kSigninAllowed,
248 client_->GetPrefs(),
249 base::Bind(&SigninManager::OnSigninAllowedPrefChanged,
250 base::Unretained(this)));
251
252 std::string user =
253 client_->GetPrefs()->GetString(prefs::kGoogleServicesUsername);
254 if ((!user.empty() && !IsAllowedUsername(user)) || !IsSigninAllowed()) {
255 // User is signed in, but the username is invalid - the administrator must
256 // have changed the policy since the last signin, so sign out the user.
257 SignOut(signin_metrics::SIGNIN_PREF_CHANGED_DURING_SIGNIN);
258 }
259
260 InitTokenService();
261 account_id_helper_.reset(
262 new SigninAccountIdHelper(client_, token_service_, this));
263 }
264
Shutdown()265 void SigninManager::Shutdown() {
266 if (merge_session_helper_)
267 merge_session_helper_->CancelAll();
268
269 local_state_pref_registrar_.RemoveAll();
270 account_id_helper_.reset();
271 SigninManagerBase::Shutdown();
272 }
273
OnGoogleServicesUsernamePatternChanged()274 void SigninManager::OnGoogleServicesUsernamePatternChanged() {
275 if (IsAuthenticated() &&
276 !IsAllowedUsername(GetAuthenticatedUsername())) {
277 // Signed in user is invalid according to the current policy so sign
278 // the user out.
279 SignOut(signin_metrics::GOOGLE_SERVICE_NAME_PATTERN_CHANGED);
280 }
281 }
282
IsSigninAllowed() const283 bool SigninManager::IsSigninAllowed() const {
284 return signin_allowed_.GetValue();
285 }
286
OnSigninAllowedPrefChanged()287 void SigninManager::OnSigninAllowedPrefChanged() {
288 if (!IsSigninAllowed())
289 SignOut(signin_metrics::SIGNOUT_PREF_CHANGED);
290 }
291
292 // static
IsUsernameAllowedByPolicy(const std::string & username,const std::string & policy)293 bool SigninManager::IsUsernameAllowedByPolicy(const std::string& username,
294 const std::string& policy) {
295 if (policy.empty())
296 return true;
297
298 // Patterns like "*@foo.com" are not accepted by our regex engine (since they
299 // are not valid regular expressions - they should instead be ".*@foo.com").
300 // For convenience, detect these patterns and insert a "." character at the
301 // front.
302 base::string16 pattern = base::UTF8ToUTF16(policy);
303 if (pattern[0] == L'*')
304 pattern.insert(pattern.begin(), L'.');
305
306 // See if the username matches the policy-provided pattern.
307 UErrorCode status = U_ZERO_ERROR;
308 const icu::UnicodeString icu_pattern(pattern.data(), pattern.length());
309 icu::RegexMatcher matcher(icu_pattern, UREGEX_CASE_INSENSITIVE, status);
310 if (!U_SUCCESS(status)) {
311 LOG(ERROR) << "Invalid login regex: " << pattern << ", status: " << status;
312 // If an invalid pattern is provided, then prohibit *all* logins (better to
313 // break signin than to quietly allow users to sign in).
314 return false;
315 }
316 base::string16 username16 = base::UTF8ToUTF16(username);
317 icu::UnicodeString icu_input(username16.data(), username16.length());
318 matcher.reset(icu_input);
319 status = U_ZERO_ERROR;
320 UBool match = matcher.matches(status);
321 DCHECK(U_SUCCESS(status));
322 return !!match; // !! == convert from UBool to bool.
323 }
324
IsAllowedUsername(const std::string & username) const325 bool SigninManager::IsAllowedUsername(const std::string& username) const {
326 const PrefService* local_state = local_state_pref_registrar_.prefs();
327 if (!local_state)
328 return true; // In a unit test with no local state - all names are allowed.
329
330 std::string pattern =
331 local_state->GetString(prefs::kGoogleServicesUsernamePattern);
332 return IsUsernameAllowedByPolicy(username, pattern);
333 }
334
AuthInProgress() const335 bool SigninManager::AuthInProgress() const {
336 return !possibly_invalid_username_.empty();
337 }
338
GetUsernameForAuthInProgress() const339 const std::string& SigninManager::GetUsernameForAuthInProgress() const {
340 return possibly_invalid_username_;
341 }
342
DisableOneClickSignIn(PrefService * prefs)343 void SigninManager::DisableOneClickSignIn(PrefService* prefs) {
344 prefs->SetBoolean(prefs::kReverseAutologinEnabled, false);
345 }
346
CompletePendingSignin()347 void SigninManager::CompletePendingSignin() {
348 DCHECK(!possibly_invalid_username_.empty());
349 OnSignedIn(possibly_invalid_username_);
350
351 if (client_->ShouldMergeSigninCredentialsIntoCookieJar()) {
352 merge_session_helper_.reset(new MergeSessionHelper(
353 token_service_, client_->GetURLRequestContext(), NULL));
354 }
355
356 DCHECK(!temp_refresh_token_.empty());
357 DCHECK(IsAuthenticated());
358 token_service_->UpdateCredentials(GetAuthenticatedUsername(),
359 temp_refresh_token_);
360 temp_refresh_token_.clear();
361
362 if (client_->ShouldMergeSigninCredentialsIntoCookieJar())
363 merge_session_helper_->LogIn(GetAuthenticatedUsername());
364 }
365
OnExternalSigninCompleted(const std::string & username)366 void SigninManager::OnExternalSigninCompleted(const std::string& username) {
367 OnSignedIn(username);
368 }
369
OnSignedIn(const std::string & username)370 void SigninManager::OnSignedIn(const std::string& username) {
371 client_->GetPrefs()->SetInt64(prefs::kSignedInTime,
372 base::Time::Now().ToInternalValue());
373 SetAuthenticatedUsername(username);
374 possibly_invalid_username_.clear();
375
376 FOR_EACH_OBSERVER(
377 Observer,
378 observer_list_,
379 GoogleSigninSucceeded(GetAuthenticatedAccountId(),
380 GetAuthenticatedUsername(),
381 password_));
382
383 client_->GoogleSigninSucceeded(GetAuthenticatedAccountId(),
384 GetAuthenticatedUsername(),
385 password_);
386
387 signin_metrics::LogSigninProfile(client_->IsFirstRun(),
388 client_->GetInstallDate());
389
390 password_.clear(); // Don't need it anymore.
391 DisableOneClickSignIn(client_->GetPrefs()); // Don't ever offer again.
392 }
393
ProhibitSignout(bool prohibit_signout)394 void SigninManager::ProhibitSignout(bool prohibit_signout) {
395 prohibit_signout_ = prohibit_signout;
396 }
397
IsSignoutProhibited() const398 bool SigninManager::IsSignoutProhibited() const { return prohibit_signout_; }
399