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