| // Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/chromeos/login/oauth2_login_verifier.h" |
| |
| #include <vector> |
| |
| #include "base/logging.h" |
| #include "base/metrics/histogram.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/time/time.h" |
| #include "chrome/browser/chromeos/net/network_portal_detector.h" |
| #include "chrome/browser/signin/profile_oauth2_token_service.h" |
| #include "chrome/browser/signin/profile_oauth2_token_service_factory.h" |
| #include "chromeos/network/network_handler.h" |
| #include "chromeos/network/network_state.h" |
| #include "chromeos/network/network_state_handler.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "google_apis/gaia/gaia_constants.h" |
| #include "google_apis/gaia/gaia_urls.h" |
| #include "third_party/cros_system_api/dbus/service_constants.h" |
| |
| using content::BrowserThread; |
| |
| namespace { |
| |
| // OAuth token request max retry count. |
| const int kMaxRequestAttemptCount = 5; |
| |
| // OAuth token request retry delay in milliseconds. |
| const int kRequestRestartDelay = 3000; |
| |
| // Post merge session verification delay. |
| #ifndef NDEBUG |
| const int kPostResoreVerificationDelay = 1000; |
| #else |
| const int kPostResoreVerificationDelay = 1000*60*3; |
| #endif |
| |
| bool IsConnectionOrServiceError(const GoogleServiceAuthError& error) { |
| return error.state() == GoogleServiceAuthError::CONNECTION_FAILED || |
| error.state() == GoogleServiceAuthError::SERVICE_UNAVAILABLE || |
| error.state() == GoogleServiceAuthError::REQUEST_CANCELED; |
| } |
| |
| } // namespace |
| |
| namespace chromeos { |
| |
| OAuth2LoginVerifier::OAuth2LoginVerifier( |
| OAuth2LoginVerifier::Delegate* delegate, |
| net::URLRequestContextGetter* system_request_context, |
| net::URLRequestContextGetter* user_request_context) |
| : delegate_(delegate), |
| system_request_context_(system_request_context), |
| user_request_context_(user_request_context), |
| retry_count_(0) { |
| DCHECK(delegate); |
| } |
| |
| OAuth2LoginVerifier::~OAuth2LoginVerifier() { |
| } |
| |
| void OAuth2LoginVerifier::VerifyProfileTokens(Profile* profile) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| // Delay the verification if the network is not connected or on a captive |
| // portal. |
| const NetworkState* default_network = |
| NetworkHandler::Get()->network_state_handler()->DefaultNetwork(); |
| NetworkPortalDetector* detector = NetworkPortalDetector::Get(); |
| if (!default_network || |
| default_network->connection_state() == shill::kStatePortal || |
| (detector && detector->GetCaptivePortalState(default_network).status != |
| NetworkPortalDetector::CAPTIVE_PORTAL_STATUS_ONLINE)) { |
| // If network is offline, defer the token fetching until online. |
| VLOG(1) << "Network is offline. Deferring OAuth2 access token fetch."; |
| BrowserThread::PostDelayedTask( |
| BrowserThread::UI, |
| FROM_HERE, |
| base::Bind( |
| &OAuth2LoginVerifier::VerifyProfileTokens, AsWeakPtr(), profile), |
| base::TimeDelta::FromMilliseconds(kRequestRestartDelay)); |
| return; |
| } |
| |
| access_token_.clear(); |
| gaia_token_.clear(); |
| StartFetchingOAuthLoginAccessToken(profile); |
| } |
| |
| void OAuth2LoginVerifier::StartFetchingOAuthLoginAccessToken(Profile* profile) { |
| OAuth2TokenService::ScopeSet scopes; |
| scopes.insert(GaiaUrls::GetInstance()->oauth1_login_scope()); |
| ProfileOAuth2TokenService* token_service = |
| ProfileOAuth2TokenServiceFactory::GetForProfile(profile); |
| login_token_request_ = token_service->StartRequestWithContext( |
| token_service->GetPrimaryAccountId(), |
| system_request_context_.get(), |
| scopes, |
| this); |
| } |
| |
| void OAuth2LoginVerifier::StartOAuthLoginForUberToken() { |
| // No service will fetch us uber auth token. |
| gaia_fetcher_.reset( |
| new GaiaAuthFetcher(this, |
| std::string(GaiaConstants::kChromeOSSource), |
| user_request_context_.get())); |
| gaia_fetcher_->StartTokenFetchForUberAuthExchange(access_token_); |
| } |
| |
| |
| void OAuth2LoginVerifier::OnUberAuthTokenSuccess( |
| const std::string& uber_token) { |
| VLOG(1) << "OAuthLogin(uber_token) successful!"; |
| retry_count_ = 0; |
| gaia_token_ = uber_token; |
| StartMergeSession(); |
| } |
| |
| void OAuth2LoginVerifier::OnUberAuthTokenFailure( |
| const GoogleServiceAuthError& error) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| LOG(WARNING) << "OAuthLogin(uber_token) failed," |
| << " error: " << error.state(); |
| RetryOnError("OAuthLoginUberToken", error, |
| base::Bind(&OAuth2LoginVerifier::StartOAuthLoginForUberToken, |
| AsWeakPtr()), |
| base::Bind(&Delegate::OnSessionMergeFailure, |
| base::Unretained(delegate_))); |
| } |
| |
| void OAuth2LoginVerifier::StartMergeSession() { |
| DCHECK(!gaia_token_.empty()); |
| gaia_fetcher_.reset( |
| new GaiaAuthFetcher(this, |
| std::string(GaiaConstants::kChromeOSSource), |
| user_request_context_.get())); |
| gaia_fetcher_->StartMergeSession(gaia_token_); |
| } |
| |
| void OAuth2LoginVerifier::OnMergeSessionSuccess(const std::string& data) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| VLOG(1) << "MergeSession successful."; |
| delegate_->OnSessionMergeSuccess(); |
| // Schedule post-merge verification to analyze how many LSID/SID overruns |
| // were created by the session restore. |
| SchedulePostMergeVerification(); |
| } |
| |
| void OAuth2LoginVerifier::SchedulePostMergeVerification() { |
| BrowserThread::PostDelayedTask( |
| BrowserThread::UI, |
| FROM_HERE, |
| base::Bind( |
| &OAuth2LoginVerifier::StartPostRestoreVerification, AsWeakPtr()), |
| base::TimeDelta::FromMilliseconds(kPostResoreVerificationDelay)); |
| } |
| |
| void OAuth2LoginVerifier::StartPostRestoreVerification() { |
| gaia_fetcher_.reset( |
| new GaiaAuthFetcher(this, |
| std::string(GaiaConstants::kChromeOSSource), |
| user_request_context_.get())); |
| gaia_fetcher_->StartListAccounts(); |
| } |
| |
| void OAuth2LoginVerifier::OnMergeSessionFailure( |
| const GoogleServiceAuthError& error) { |
| LOG(WARNING) << "Failed MergeSession request," << " error: " << error.state(); |
| // If MergeSession from GAIA service token fails, retry the session restore |
| // from OAuth2 refresh token. If that failed too, signal the delegate. |
| RetryOnError( |
| "MergeSession", |
| error, |
| base::Bind(&OAuth2LoginVerifier::StartMergeSession, |
| AsWeakPtr()), |
| base::Bind(&Delegate::OnSessionMergeFailure, |
| base::Unretained(delegate_))); |
| } |
| |
| void OAuth2LoginVerifier::OnGetTokenSuccess( |
| const OAuth2TokenService::Request* request, |
| const std::string& access_token, |
| const base::Time& expiration_time) { |
| DCHECK_EQ(login_token_request_.get(), request); |
| login_token_request_.reset(); |
| |
| VLOG(1) << "Got OAuth2 access token!"; |
| retry_count_ = 0; |
| access_token_ = access_token; |
| StartOAuthLoginForUberToken(); |
| } |
| |
| void OAuth2LoginVerifier::OnGetTokenFailure( |
| const OAuth2TokenService::Request* request, |
| const GoogleServiceAuthError& error) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK_EQ(login_token_request_.get(), request); |
| login_token_request_.reset(); |
| |
| LOG(WARNING) << "Failed to get OAuth2 access token, " |
| << " error: " << error.state(); |
| UMA_HISTOGRAM_ENUMERATION( |
| base::StringPrintf("OAuth2Login.%sFailure", "GetOAuth2AccessToken"), |
| error.state(), |
| GoogleServiceAuthError::NUM_STATES); |
| delegate_->OnSessionMergeFailure(IsConnectionOrServiceError(error)); |
| } |
| |
| void OAuth2LoginVerifier::OnListAccountsSuccess( |
| const std::string& data) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| VLOG(1) << "ListAccounts successful."; |
| delegate_->OnListAccountsSuccess(data); |
| } |
| |
| void OAuth2LoginVerifier::OnListAccountsFailure( |
| const GoogleServiceAuthError& error) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| LOG(WARNING) << "Failed to get list of session accounts, " |
| << " error: " << error.state(); |
| RetryOnError( |
| "ListAccounts", |
| error, |
| base::Bind(&OAuth2LoginVerifier::StartPostRestoreVerification, |
| AsWeakPtr()), |
| base::Bind(&Delegate::OnListAccountsFailure, |
| base::Unretained(delegate_))); |
| } |
| |
| void OAuth2LoginVerifier::RetryOnError(const char* operation_id, |
| const GoogleServiceAuthError& error, |
| const base::Closure& task_to_retry, |
| const ErrorHandler& error_handler) { |
| if (IsConnectionOrServiceError(error) && |
| retry_count_ < kMaxRequestAttemptCount) { |
| retry_count_++; |
| UMA_HISTOGRAM_ENUMERATION( |
| base::StringPrintf("OAuth2Login.%sRetry", operation_id), |
| error.state(), |
| GoogleServiceAuthError::NUM_STATES); |
| BrowserThread::PostDelayedTask( |
| BrowserThread::UI, FROM_HERE, task_to_retry, |
| base::TimeDelta::FromMilliseconds(kRequestRestartDelay)); |
| return; |
| } |
| |
| LOG(WARNING) << "Unrecoverable error or retry count max reached for " |
| << operation_id; |
| UMA_HISTOGRAM_ENUMERATION( |
| base::StringPrintf("OAuth2Login.%sFailure", operation_id), |
| error.state(), |
| GoogleServiceAuthError::NUM_STATES); |
| |
| error_handler.Run(IsConnectionOrServiceError(error)); |
| } |
| |
| } // namespace chromeos |