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/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"
26
27 using namespace signin_internals_util;
28
29 namespace {
30
31 const char kChromiumSyncService[] = "service=chromiumsync";
32
33 } // namespace
34
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";
39
40 // static
IsWebBasedSigninFlowURL(const GURL & url)41 bool SigninManager::IsWebBasedSigninFlowURL(const GURL& url) {
42 GURL effective(kChromeSigninEffectiveSite);
43 if (url.SchemeIs(effective.scheme().c_str()) &&
44 url.host() == effective.host()) {
45 return true;
46 }
47
48 GURL service_login(GaiaUrls::GetInstance()->service_login_url());
49 if (url.GetOrigin() != service_login.GetOrigin())
50 return false;
51
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;
58 }
59
SigninManager(SigninClient * client,ProfileOAuth2TokenService * token_service)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),
66 client_(client),
67 token_service_(token_service) {}
68
AddMergeSessionObserver(MergeSessionHelper::Observer * observer)69 void SigninManager::AddMergeSessionObserver(
70 MergeSessionHelper::Observer* observer) {
71 if (merge_session_helper_)
72 merge_session_helper_->AddObserver(observer);
73 }
74
RemoveMergeSessionObserver(MergeSessionHelper::Observer * observer)75 void SigninManager::RemoveMergeSessionObserver(
76 MergeSessionHelper::Observer* observer) {
77 if (merge_session_helper_)
78 merge_session_helper_->RemoveObserver(observer);
79 }
80
~SigninManager()81 SigninManager::~SigninManager() {}
82
InitTokenService()83 void SigninManager::InitTokenService() {
84 const std::string& account_id = GetAuthenticatedUsername();
85 if (token_service_ && !account_id.empty())
86 token_service_->LoadCredentials(account_id);
87 }
88
SigninTypeToString(SigninManager::SigninType type)89 std::string SigninManager::SigninTypeToString(SigninManager::SigninType type) {
90 switch (type) {
91 case SIGNIN_TYPE_NONE:
92 return "No Signin";
93 case SIGNIN_TYPE_WITH_REFRESH_TOKEN:
94 return "Signin with refresh token";
95 }
96
97 NOTREACHED();
98 return std::string();
99 }
100
PrepareForSignin(SigninType type,const std::string & username,const std::string & password)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());
107
108 if (!IsAllowedUsername(username)) {
109 // Account is not allowed by admin policy.
110 HandleAuthError(
111 GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DISABLED));
112 return false;
113 }
114
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();
121 type_ = type;
122 possibly_invalid_username_.assign(username);
123 password_.assign(password);
124 NotifyDiagnosticsObservers(SIGNIN_TYPE, SigninTypeToString(type));
125 return true;
126 }
127
StartSignInWithRefreshToken(const std::string & refresh_token,const std::string & username,const std::string & password,const OAuthTokenFetchedCallback & callback)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()));
135
136 if (!PrepareForSignin(SIGNIN_TYPE_WITH_REFRESH_TOKEN, username, password))
137 return;
138
139 // Store our callback and token.
140 temp_refresh_token_ = refresh_token;
141 possibly_invalid_username_ = username;
142
143 NotifyDiagnosticsObservers(GET_USER_INFO_STATUS, "Successful");
144
145 if (!callback.is_null() && !temp_refresh_token_.empty()) {
146 callback.Run(temp_refresh_token_);
147 } else {
148 // No oauth token or callback, so just complete our pending signin.
149 CompletePendingSignin();
150 }
151 }
152
CopyCredentialsFrom(const SigninManager & source)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_;
158 }
159
ClearTransientSigninData()160 void SigninManager::ClearTransientSigninData() {
161 DCHECK(IsInitialized());
162
163 possibly_invalid_username_.clear();
164 password_.clear();
165 type_ = SIGNIN_TYPE_NONE;
166 temp_refresh_token_.clear();
167 }
168
HandleAuthError(const GoogleServiceAuthError & error)169 void SigninManager::HandleAuthError(const GoogleServiceAuthError& error) {
170 ClearTransientSigninData();
171
172 FOR_EACH_OBSERVER(Observer, observer_list_, GoogleSigninFailed(error));
173 }
174
SignOut(signin_metrics::ProfileSignout signout_source_metric)175 void SigninManager::SignOut(
176 signin_metrics::ProfileSignout signout_source_metric) {
177 DCHECK(IsInitialized());
178
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);
186 } else {
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
190 // signin attempts.
191 ClearTransientSigninData();
192 }
193 return;
194 }
195
196 if (prohibit_signout_) {
197 DVLOG(1) << "Ignoring attempt to sign out while signout is prohibited";
198 return;
199 }
200
201 ClearTransientSigninData();
202
203 const std::string username = GetAuthenticatedUsername();
204 clear_authenticated_username();
205 client_->GetPrefs()->ClearPref(prefs::kGoogleServicesUsername);
206
207 // Erase (now) stale information from AboutSigninInternals.
208 NotifyDiagnosticsObservers(USERNAME, "");
209
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();
216
217 FOR_EACH_OBSERVER(Observer, observer_list_, GoogleSignedOut(username));
218 }
219
Initialize(PrefService * local_state)220 void SigninManager::Initialize(PrefService* local_state) {
221 SigninManagerBase::Initialize(local_state);
222
223 // local_state can be null during unit tests.
224 if (local_state) {
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()));
230 }
231 signin_allowed_.Init(prefs::kSigninAllowed,
232 client_->GetPrefs(),
233 base::Bind(&SigninManager::OnSigninAllowedPrefChanged,
234 base::Unretained(this)));
235
236 std::string user =
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);
242 }
243
244 InitTokenService();
245 account_id_helper_.reset(
246 new SigninAccountIdHelper(client_, token_service_, this));
247 }
248
Shutdown()249 void SigninManager::Shutdown() {
250 if (merge_session_helper_)
251 merge_session_helper_->CancelAll();
252
253 local_state_pref_registrar_.RemoveAll();
254 account_id_helper_.reset();
255 SigninManagerBase::Shutdown();
256 }
257
OnGoogleServicesUsernamePatternChanged()258 void SigninManager::OnGoogleServicesUsernamePatternChanged() {
259 if (!GetAuthenticatedUsername().empty() &&
260 !IsAllowedUsername(GetAuthenticatedUsername())) {
261 // Signed in user is invalid according to the current policy so sign
262 // the user out.
263 SignOut(signin_metrics::GOOGLE_SERVICE_NAME_PATTERN_CHANGED);
264 }
265 }
266
IsSigninAllowed() const267 bool SigninManager::IsSigninAllowed() const {
268 return signin_allowed_.GetValue();
269 }
270
OnSigninAllowedPrefChanged()271 void SigninManager::OnSigninAllowedPrefChanged() {
272 if (!IsSigninAllowed())
273 SignOut(signin_metrics::SIGNOUT_PREF_CHANGED);
274 }
275
276 // static
IsUsernameAllowedByPolicy(const std::string & username,const std::string & policy)277 bool SigninManager::IsUsernameAllowedByPolicy(const std::string& username,
278 const std::string& policy) {
279 if (policy.empty())
280 return true;
281
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
285 // front.
286 base::string16 pattern = base::UTF8ToUTF16(policy);
287 if (pattern[0] == L'*')
288 pattern.insert(pattern.begin(), L'.');
289
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).
298 return false;
299 }
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.
307 }
308
IsAllowedUsername(const std::string & username) const309 bool SigninManager::IsAllowedUsername(const std::string& username) const {
310 const PrefService* local_state = local_state_pref_registrar_.prefs();
311 if (!local_state)
312 return true; // In a unit test with no local state - all names are allowed.
313
314 std::string pattern =
315 local_state->GetString(prefs::kGoogleServicesUsernamePattern);
316 return IsUsernameAllowedByPolicy(username, pattern);
317 }
318
AuthInProgress() const319 bool SigninManager::AuthInProgress() const {
320 return !possibly_invalid_username_.empty();
321 }
322
GetUsernameForAuthInProgress() const323 const std::string& SigninManager::GetUsernameForAuthInProgress() const {
324 return possibly_invalid_username_;
325 }
326
DisableOneClickSignIn(PrefService * prefs)327 void SigninManager::DisableOneClickSignIn(PrefService* prefs) {
328 prefs->SetBoolean(prefs::kReverseAutologinEnabled, false);
329 }
330
CompletePendingSignin()331 void SigninManager::CompletePendingSignin() {
332 DCHECK(!possibly_invalid_username_.empty());
333 OnSignedIn(possibly_invalid_username_);
334
335 if (client_->ShouldMergeSigninCredentialsIntoCookieJar()) {
336 merge_session_helper_.reset(new MergeSessionHelper(
337 token_service_, client_->GetURLRequestContext(), NULL));
338 }
339
340 DCHECK(!temp_refresh_token_.empty());
341 DCHECK(!GetAuthenticatedUsername().empty());
342 token_service_->UpdateCredentials(GetAuthenticatedUsername(),
343 temp_refresh_token_);
344 temp_refresh_token_.clear();
345
346 if (client_->ShouldMergeSigninCredentialsIntoCookieJar())
347 merge_session_helper_->LogIn(GetAuthenticatedUsername());
348 }
349
OnExternalSigninCompleted(const std::string & username)350 void SigninManager::OnExternalSigninCompleted(const std::string& username) {
351 OnSignedIn(username);
352 }
353
OnSignedIn(const std::string & username)354 void SigninManager::OnSignedIn(const std::string& username) {
355 SetAuthenticatedUsername(username);
356 possibly_invalid_username_.clear();
357
358 FOR_EACH_OBSERVER(
359 Observer,
360 observer_list_,
361 GoogleSigninSucceeded(GetAuthenticatedUsername(), password_));
362
363 client_->GoogleSigninSucceeded(GetAuthenticatedUsername(), password_);
364
365 password_.clear(); // Don't need it anymore.
366 DisableOneClickSignIn(client_->GetPrefs()); // Don't ever offer again.
367 }
368
ProhibitSignout(bool prohibit_signout)369 void SigninManager::ProhibitSignout(bool prohibit_signout) {
370 prohibit_signout_ = prohibit_signout;
371 }
372
IsSignoutProhibited() const373 bool SigninManager::IsSignoutProhibited() const { return prohibit_signout_; }
374