oauth2_login_verifier.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 "chrome/browser/chromeos/login/signin/oauth2_login_verifier.h"
6
7#include <vector>
8
9#include "base/logging.h"
10#include "base/metrics/histogram.h"
11#include "base/strings/string_util.h"
12#include "base/strings/stringprintf.h"
13#include "base/time/time.h"
14#include "chrome/browser/chromeos/net/delay_network_call.h"
15#include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
16#include "chrome/browser/signin/signin_manager_factory.h"
17#include "components/signin/core/browser/profile_oauth2_token_service.h"
18#include "components/signin/core/browser/signin_manager.h"
19#include "content/public/browser/browser_thread.h"
20#include "google_apis/gaia/gaia_constants.h"
21
22using content::BrowserThread;
23
24namespace {
25
26// OAuth token request max retry count.
27const int kMaxRequestAttemptCount = 5;
28
29// OAuth token request retry delay in milliseconds.
30const int kRequestRestartDelay = 3000;
31
32// Post merge session verification delay.
33#ifndef NDEBUG
34const int kPostResoreVerificationDelay = 1000;
35#else
36const int kPostResoreVerificationDelay = 1000*60*3;
37#endif
38
39bool IsConnectionOrServiceError(const GoogleServiceAuthError& error) {
40  return error.state() == GoogleServiceAuthError::CONNECTION_FAILED ||
41         error.state() == GoogleServiceAuthError::SERVICE_UNAVAILABLE ||
42         error.state() == GoogleServiceAuthError::REQUEST_CANCELED;
43}
44
45}  // namespace
46
47namespace chromeos {
48
49OAuth2LoginVerifier::OAuth2LoginVerifier(
50    OAuth2LoginVerifier::Delegate* delegate,
51    net::URLRequestContextGetter* system_request_context,
52    net::URLRequestContextGetter* user_request_context,
53    const std::string& oauthlogin_access_token)
54    : OAuth2TokenService::Consumer("cros_login_verifier"),
55      delegate_(delegate),
56      system_request_context_(system_request_context),
57      user_request_context_(user_request_context),
58      access_token_(oauthlogin_access_token),
59      retry_count_(0) {
60  DCHECK(delegate);
61}
62
63OAuth2LoginVerifier::~OAuth2LoginVerifier() {
64}
65
66void OAuth2LoginVerifier::VerifyUserCookies(Profile* profile) {
67  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
68
69  // Delay the verification if the network is not connected or on a captive
70  // portal.
71  DelayNetworkCall(
72      base::Bind(&OAuth2LoginVerifier::StartAuthCookiesVerification,
73                 AsWeakPtr()),
74      base::TimeDelta::FromMilliseconds(kRequestRestartDelay));
75}
76
77void OAuth2LoginVerifier::VerifyProfileTokens(Profile* profile) {
78  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
79
80  // Delay the verification if the network is not connected or on a captive
81  // portal.
82  DelayNetworkCall(
83      base::Bind(
84          &OAuth2LoginVerifier::VerifyProfileTokensImpl, AsWeakPtr(), profile),
85      base::TimeDelta::FromMilliseconds(kRequestRestartDelay));
86}
87
88void OAuth2LoginVerifier::VerifyProfileTokensImpl(Profile* profile) {
89  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
90
91  gaia_token_.clear();
92  if (access_token_.empty()) {
93    // Fetch /OAuthLogin scoped access token.
94    StartFetchingOAuthLoginAccessToken(profile);
95  } else {
96    // If OAuthLogin-scoped access token already exists (if it's generated
97    // together with freshly minted refresh token), then fetch GAIA uber token.
98    StartOAuthLoginForUberToken();
99  }
100}
101
102void OAuth2LoginVerifier::StartFetchingOAuthLoginAccessToken(Profile* profile) {
103  OAuth2TokenService::ScopeSet scopes;
104  scopes.insert(GaiaConstants::kOAuth1LoginScope);
105  ProfileOAuth2TokenService* token_service =
106      ProfileOAuth2TokenServiceFactory::GetForProfile(profile);
107  SigninManagerBase* signin_manager =
108      SigninManagerFactory::GetForProfile(profile);
109  login_token_request_ = token_service->StartRequestWithContext(
110      signin_manager->GetAuthenticatedAccountId(),
111      system_request_context_.get(),
112      scopes,
113      this);
114}
115
116void OAuth2LoginVerifier::StartOAuthLoginForUberToken() {
117  // No service will fetch us uber auth token.
118  gaia_fetcher_.reset(
119      new GaiaAuthFetcher(this,
120                          std::string(GaiaConstants::kChromeOSSource),
121                          user_request_context_.get()));
122  gaia_fetcher_->StartTokenFetchForUberAuthExchange(access_token_);
123}
124
125
126void OAuth2LoginVerifier::OnUberAuthTokenSuccess(
127    const std::string& uber_token) {
128  VLOG(1) << "OAuthLogin(uber_token) successful!";
129  retry_count_ = 0;
130  gaia_token_ = uber_token;
131  StartMergeSession();
132}
133
134void OAuth2LoginVerifier::OnUberAuthTokenFailure(
135    const GoogleServiceAuthError& error) {
136  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
137  LOG(WARNING) << "OAuthLogin(uber_token) failed,"
138               << " error: " << error.state();
139  RetryOnError("OAuthLoginUberToken", error,
140               base::Bind(&OAuth2LoginVerifier::StartOAuthLoginForUberToken,
141                          AsWeakPtr()),
142               base::Bind(&Delegate::OnSessionMergeFailure,
143                          base::Unretained(delegate_)));
144}
145
146void OAuth2LoginVerifier::StartMergeSession() {
147  DCHECK(!gaia_token_.empty());
148  gaia_fetcher_.reset(
149      new GaiaAuthFetcher(this,
150                          std::string(GaiaConstants::kChromeOSSource),
151                          user_request_context_.get()));
152  gaia_fetcher_->StartMergeSession(gaia_token_, std::string());
153}
154
155void OAuth2LoginVerifier::OnMergeSessionSuccess(const std::string& data) {
156  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
157  VLOG(1) << "MergeSession successful.";
158  delegate_->OnSessionMergeSuccess();
159  // Schedule post-merge verification to analyze how many LSID/SID overruns
160  // were created by the session restore.
161  SchedulePostMergeVerification();
162}
163
164void OAuth2LoginVerifier::SchedulePostMergeVerification() {
165  BrowserThread::PostDelayedTask(
166      BrowserThread::UI,
167      FROM_HERE,
168      base::Bind(
169          &OAuth2LoginVerifier::StartAuthCookiesVerification, AsWeakPtr()),
170      base::TimeDelta::FromMilliseconds(kPostResoreVerificationDelay));
171}
172
173void OAuth2LoginVerifier::StartAuthCookiesVerification() {
174  gaia_fetcher_.reset(
175      new GaiaAuthFetcher(this,
176                          std::string(GaiaConstants::kChromeOSSource),
177                          user_request_context_.get()));
178  gaia_fetcher_->StartListAccounts();
179}
180
181void OAuth2LoginVerifier::OnMergeSessionFailure(
182    const GoogleServiceAuthError& error) {
183  LOG(WARNING) << "Failed MergeSession request," << " error: " << error.state();
184  // If MergeSession from GAIA service token fails, retry the session restore
185  // from OAuth2 refresh token. If that failed too, signal the delegate.
186  RetryOnError(
187      "MergeSession",
188      error,
189      base::Bind(&OAuth2LoginVerifier::StartMergeSession,
190                 AsWeakPtr()),
191      base::Bind(&Delegate::OnSessionMergeFailure,
192                 base::Unretained(delegate_)));
193}
194
195void OAuth2LoginVerifier::OnGetTokenSuccess(
196    const OAuth2TokenService::Request* request,
197    const std::string& access_token,
198    const base::Time& expiration_time) {
199  DCHECK_EQ(login_token_request_.get(), request);
200  login_token_request_.reset();
201
202  VLOG(1) << "Got OAuth2 access token!";
203  retry_count_ = 0;
204  access_token_ = access_token;
205  StartOAuthLoginForUberToken();
206}
207
208void OAuth2LoginVerifier::OnGetTokenFailure(
209    const OAuth2TokenService::Request* request,
210    const GoogleServiceAuthError& error) {
211  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
212  DCHECK_EQ(login_token_request_.get(), request);
213  login_token_request_.reset();
214
215  LOG(WARNING) << "Failed to get OAuth2 access token, "
216               << " error: " << error.state();
217  UMA_HISTOGRAM_ENUMERATION(
218      base::StringPrintf("OAuth2Login.%sFailure", "GetOAuth2AccessToken"),
219      error.state(),
220      GoogleServiceAuthError::NUM_STATES);
221  delegate_->OnSessionMergeFailure(IsConnectionOrServiceError(error));
222}
223
224void OAuth2LoginVerifier::OnListAccountsSuccess(
225    const std::string& data) {
226  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
227  VLOG(1) << "ListAccounts successful.";
228  delegate_->OnListAccountsSuccess(data);
229}
230
231void OAuth2LoginVerifier::OnListAccountsFailure(
232    const GoogleServiceAuthError& error) {
233  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
234  LOG(WARNING) << "Failed to get list of session accounts, "
235               << " error: " << error.state();
236  RetryOnError(
237      "ListAccounts",
238      error,
239      base::Bind(&OAuth2LoginVerifier::StartAuthCookiesVerification,
240                 AsWeakPtr()),
241      base::Bind(&Delegate::OnListAccountsFailure,
242                 base::Unretained(delegate_)));
243}
244
245void OAuth2LoginVerifier::RetryOnError(const char* operation_id,
246                                       const GoogleServiceAuthError& error,
247                                       const base::Closure& task_to_retry,
248                                       const ErrorHandler& error_handler) {
249  if (IsConnectionOrServiceError(error) &&
250      retry_count_ < kMaxRequestAttemptCount) {
251    retry_count_++;
252    UMA_HISTOGRAM_ENUMERATION(
253        base::StringPrintf("OAuth2Login.%sRetry", operation_id),
254        error.state(),
255        GoogleServiceAuthError::NUM_STATES);
256    BrowserThread::PostDelayedTask(
257        BrowserThread::UI, FROM_HERE, task_to_retry,
258        base::TimeDelta::FromMilliseconds(kRequestRestartDelay));
259    return;
260  }
261
262  LOG(WARNING) << "Unrecoverable error or retry count max reached for "
263               << operation_id;
264  UMA_HISTOGRAM_ENUMERATION(
265      base::StringPrintf("OAuth2Login.%sFailure", operation_id),
266      error.state(),
267      GoogleServiceAuthError::NUM_STATES);
268
269  error_handler.Run(IsConnectionOrServiceError(error));
270}
271
272}  // namespace chromeos
273