signin_manager.cc revision 6e8cce623b6e4fe0c9e4af605d675dd9d0338c38
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 weak_pointer_factory_(this), 67 client_(client), 68 token_service_(token_service) {} 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(GetAuthenticatedUsername().empty() || 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 (GetAuthenticatedUsername().empty()) { 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 username = GetAuthenticatedUsername(); 205 const base::Time signin_time = 206 base::Time::FromInternalValue( 207 client_->GetPrefs()->GetInt64(prefs::kSignedInTime)); 208 clear_authenticated_username(); 209 client_->GetPrefs()->ClearPref(prefs::kGoogleServicesUsername); 210 client_->GetPrefs()->ClearPref(prefs::kSignedInTime); 211 client_->ClearSigninScopedDeviceId(); 212 213 // Erase (now) stale information from AboutSigninInternals. 214 NotifyDiagnosticsObservers(USERNAME, ""); 215 216 // Determine the duration the user was logged in and log that to UMA. 217 if (!signin_time.is_null()) { 218 base::TimeDelta signed_in_duration = base::Time::Now() - signin_time; 219 UMA_HISTOGRAM_COUNTS("Signin.SignedInDurationBeforeSignout", 220 signed_in_duration.InMinutes()); 221 } 222 223 // Revoke all tokens before sending signed_out notification, because there 224 // may be components that don't listen for token service events when the 225 // profile is not connected to an account. 226 LOG(WARNING) << "Revoking refresh token on server. Reason: sign out, " 227 << "IsSigninAllowed: " << IsSigninAllowed(); 228 token_service_->RevokeAllCredentials(); 229 230 FOR_EACH_OBSERVER(Observer, observer_list_, GoogleSignedOut(username)); 231} 232 233void SigninManager::Initialize(PrefService* local_state) { 234 SigninManagerBase::Initialize(local_state); 235 236 // local_state can be null during unit tests. 237 if (local_state) { 238 local_state_pref_registrar_.Init(local_state); 239 local_state_pref_registrar_.Add( 240 prefs::kGoogleServicesUsernamePattern, 241 base::Bind(&SigninManager::OnGoogleServicesUsernamePatternChanged, 242 weak_pointer_factory_.GetWeakPtr())); 243 } 244 signin_allowed_.Init(prefs::kSigninAllowed, 245 client_->GetPrefs(), 246 base::Bind(&SigninManager::OnSigninAllowedPrefChanged, 247 base::Unretained(this))); 248 249 std::string user = 250 client_->GetPrefs()->GetString(prefs::kGoogleServicesUsername); 251 if ((!user.empty() && !IsAllowedUsername(user)) || !IsSigninAllowed()) { 252 // User is signed in, but the username is invalid - the administrator must 253 // have changed the policy since the last signin, so sign out the user. 254 SignOut(signin_metrics::SIGNIN_PREF_CHANGED_DURING_SIGNIN); 255 } 256 257 InitTokenService(); 258 account_id_helper_.reset( 259 new SigninAccountIdHelper(client_, token_service_, this)); 260} 261 262void SigninManager::Shutdown() { 263 if (merge_session_helper_) 264 merge_session_helper_->CancelAll(); 265 266 local_state_pref_registrar_.RemoveAll(); 267 account_id_helper_.reset(); 268 SigninManagerBase::Shutdown(); 269} 270 271void SigninManager::OnGoogleServicesUsernamePatternChanged() { 272 if (!GetAuthenticatedUsername().empty() && 273 !IsAllowedUsername(GetAuthenticatedUsername())) { 274 // Signed in user is invalid according to the current policy so sign 275 // the user out. 276 SignOut(signin_metrics::GOOGLE_SERVICE_NAME_PATTERN_CHANGED); 277 } 278} 279 280bool SigninManager::IsSigninAllowed() const { 281 return signin_allowed_.GetValue(); 282} 283 284void SigninManager::OnSigninAllowedPrefChanged() { 285 if (!IsSigninAllowed()) 286 SignOut(signin_metrics::SIGNOUT_PREF_CHANGED); 287} 288 289// static 290bool SigninManager::IsUsernameAllowedByPolicy(const std::string& username, 291 const std::string& policy) { 292 if (policy.empty()) 293 return true; 294 295 // Patterns like "*@foo.com" are not accepted by our regex engine (since they 296 // are not valid regular expressions - they should instead be ".*@foo.com"). 297 // For convenience, detect these patterns and insert a "." character at the 298 // front. 299 base::string16 pattern = base::UTF8ToUTF16(policy); 300 if (pattern[0] == L'*') 301 pattern.insert(pattern.begin(), L'.'); 302 303 // See if the username matches the policy-provided pattern. 304 UErrorCode status = U_ZERO_ERROR; 305 const icu::UnicodeString icu_pattern(pattern.data(), pattern.length()); 306 icu::RegexMatcher matcher(icu_pattern, UREGEX_CASE_INSENSITIVE, status); 307 if (!U_SUCCESS(status)) { 308 LOG(ERROR) << "Invalid login regex: " << pattern << ", status: " << status; 309 // If an invalid pattern is provided, then prohibit *all* logins (better to 310 // break signin than to quietly allow users to sign in). 311 return false; 312 } 313 base::string16 username16 = base::UTF8ToUTF16(username); 314 icu::UnicodeString icu_input(username16.data(), username16.length()); 315 matcher.reset(icu_input); 316 status = U_ZERO_ERROR; 317 UBool match = matcher.matches(status); 318 DCHECK(U_SUCCESS(status)); 319 return !!match; // !! == convert from UBool to bool. 320} 321 322bool SigninManager::IsAllowedUsername(const std::string& username) const { 323 const PrefService* local_state = local_state_pref_registrar_.prefs(); 324 if (!local_state) 325 return true; // In a unit test with no local state - all names are allowed. 326 327 std::string pattern = 328 local_state->GetString(prefs::kGoogleServicesUsernamePattern); 329 return IsUsernameAllowedByPolicy(username, pattern); 330} 331 332bool SigninManager::AuthInProgress() const { 333 return !possibly_invalid_username_.empty(); 334} 335 336const std::string& SigninManager::GetUsernameForAuthInProgress() const { 337 return possibly_invalid_username_; 338} 339 340void SigninManager::DisableOneClickSignIn(PrefService* prefs) { 341 prefs->SetBoolean(prefs::kReverseAutologinEnabled, false); 342} 343 344void SigninManager::CompletePendingSignin() { 345 DCHECK(!possibly_invalid_username_.empty()); 346 OnSignedIn(possibly_invalid_username_); 347 348 if (client_->ShouldMergeSigninCredentialsIntoCookieJar()) { 349 merge_session_helper_.reset(new MergeSessionHelper( 350 token_service_, client_->GetURLRequestContext(), NULL)); 351 } 352 353 DCHECK(!temp_refresh_token_.empty()); 354 DCHECK(!GetAuthenticatedUsername().empty()); 355 token_service_->UpdateCredentials(GetAuthenticatedUsername(), 356 temp_refresh_token_); 357 temp_refresh_token_.clear(); 358 359 if (client_->ShouldMergeSigninCredentialsIntoCookieJar()) 360 merge_session_helper_->LogIn(GetAuthenticatedUsername()); 361} 362 363void SigninManager::OnExternalSigninCompleted(const std::string& username) { 364 OnSignedIn(username); 365} 366 367void SigninManager::OnSignedIn(const std::string& username) { 368 client_->GetPrefs()->SetInt64(prefs::kSignedInTime, 369 base::Time::Now().ToInternalValue()); 370 SetAuthenticatedUsername(username); 371 possibly_invalid_username_.clear(); 372 373 FOR_EACH_OBSERVER( 374 Observer, 375 observer_list_, 376 GoogleSigninSucceeded(GetAuthenticatedUsername(), password_)); 377 378 client_->GoogleSigninSucceeded(GetAuthenticatedUsername(), password_); 379 380 password_.clear(); // Don't need it anymore. 381 DisableOneClickSignIn(client_->GetPrefs()); // Don't ever offer again. 382} 383 384void SigninManager::ProhibitSignout(bool prohibit_signout) { 385 prohibit_signout_ = prohibit_signout; 386} 387 388bool SigninManager::IsSignoutProhibited() const { return prohibit_signout_; } 389