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
28using namespace signin_internals_util;
29
30namespace {
31
32const 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.
38const char SigninManager::kChromeSigninEffectiveSite[] =
39    "chrome-extension://acfccoigjajmmgbhpfbjnpckhjjegnih";
40
41// static
42bool 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
61SigninManager::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
70void SigninManager::AddMergeSessionObserver(
71    MergeSessionHelper::Observer* observer) {
72  if (merge_session_helper_)
73    merge_session_helper_->AddObserver(observer);
74}
75
76void SigninManager::RemoveMergeSessionObserver(
77    MergeSessionHelper::Observer* observer) {
78  if (merge_session_helper_)
79    merge_session_helper_->RemoveObserver(observer);
80}
81
82SigninManager::~SigninManager() {}
83
84void SigninManager::InitTokenService() {
85  const std::string& account_id = GetAuthenticatedUsername();
86  if (token_service_ && !account_id.empty())
87    token_service_->LoadCredentials(account_id);
88}
89
90std::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
102bool 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
129void 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
154void 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
161void 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
170void SigninManager::HandleAuthError(const GoogleServiceAuthError& error) {
171  ClearTransientSigninData();
172
173  FOR_EACH_OBSERVER(Observer, observer_list_, GoogleSigninFailed(error));
174}
175
176void 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
236void 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
265void 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
274void 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
283bool SigninManager::IsSigninAllowed() const {
284  return signin_allowed_.GetValue();
285}
286
287void SigninManager::OnSigninAllowedPrefChanged() {
288  if (!IsSigninAllowed())
289    SignOut(signin_metrics::SIGNOUT_PREF_CHANGED);
290}
291
292// static
293bool 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
325bool 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
335bool SigninManager::AuthInProgress() const {
336  return !possibly_invalid_username_.empty();
337}
338
339const std::string& SigninManager::GetUsernameForAuthInProgress() const {
340  return possibly_invalid_username_;
341}
342
343void SigninManager::DisableOneClickSignIn(PrefService* prefs) {
344  prefs->SetBoolean(prefs::kReverseAutologinEnabled, false);
345}
346
347void 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
366void SigninManager::OnExternalSigninCompleted(const std::string& username) {
367  OnSignedIn(username);
368}
369
370void 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
394void SigninManager::ProhibitSignout(bool prohibit_signout) {
395  prohibit_signout_ = prohibit_signout;
396}
397
398bool SigninManager::IsSignoutProhibited() const { return prohibit_signout_; }
399