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 "google_apis/gaia/ubertoken_fetcher.h"
6
7#include <vector>
8
9#include "base/logging.h"
10#include "base/rand_util.h"
11#include "base/time/time.h"
12#include "google_apis/gaia/gaia_auth_fetcher.h"
13#include "google_apis/gaia/gaia_constants.h"
14#include "google_apis/gaia/google_service_auth_error.h"
15#include "google_apis/gaia/oauth2_token_service.h"
16
17const int UbertokenFetcher::kMaxRetries = 3;
18
19UbertokenFetcher::UbertokenFetcher(
20    OAuth2TokenService* token_service,
21    UbertokenConsumer* consumer,
22    net::URLRequestContextGetter* request_context)
23    : OAuth2TokenService::Consumer("uber_token_fetcher"),
24      token_service_(token_service),
25      consumer_(consumer),
26      request_context_(request_context),
27      retry_number_(0),
28      second_access_token_request_(false) {
29  DCHECK(token_service);
30  DCHECK(consumer);
31  DCHECK(request_context);
32}
33
34UbertokenFetcher::~UbertokenFetcher() {
35}
36
37void UbertokenFetcher::StartFetchingToken(const std::string& account_id) {
38  DCHECK(!account_id.empty());
39  account_id_ = account_id;
40  second_access_token_request_ = false;
41  RequestAccessToken();
42}
43
44void UbertokenFetcher::OnUberAuthTokenSuccess(const std::string& token) {
45  consumer_->OnUbertokenSuccess(token);
46}
47
48void UbertokenFetcher::OnUberAuthTokenFailure(
49    const GoogleServiceAuthError& error) {
50  // Retry only transient errors.
51  bool should_retry =
52      error.state() == GoogleServiceAuthError::CONNECTION_FAILED ||
53      error.state() == GoogleServiceAuthError::SERVICE_UNAVAILABLE;
54  if (should_retry) {
55    if (retry_number_ < kMaxRetries) {
56      // Calculate an exponential backoff with randomness of less than 1 sec.
57      double backoff = base::RandDouble() + (1 << retry_number_);
58      ++retry_number_;
59      retry_timer_.Stop();
60      retry_timer_.Start(FROM_HERE,
61                         base::TimeDelta::FromSecondsD(backoff),
62                         this,
63                         &UbertokenFetcher::ExchangeTokens);
64      return;
65    }
66  } else {
67    // The access token is invalid.  Tell the token service.
68    OAuth2TokenService::ScopeSet scopes;
69    scopes.insert(GaiaConstants::kOAuth1LoginScope);
70    token_service_->InvalidateToken(account_id_, scopes, access_token_);
71
72    // In case the access was just stale, try one more time.
73    if (!second_access_token_request_) {
74      second_access_token_request_ = true;
75      RequestAccessToken();
76      return;
77    }
78  }
79
80  consumer_->OnUbertokenFailure(error);
81}
82
83void UbertokenFetcher::OnGetTokenSuccess(
84    const OAuth2TokenService::Request* request,
85    const std::string& access_token,
86    const base::Time& expiration_time) {
87  DCHECK(!access_token.empty());
88  access_token_ = access_token;
89  access_token_request_.reset();
90  ExchangeTokens();
91}
92
93void UbertokenFetcher::OnGetTokenFailure(
94    const OAuth2TokenService::Request* request,
95    const GoogleServiceAuthError& error) {
96  access_token_request_.reset();
97  consumer_->OnUbertokenFailure(error);
98}
99
100void UbertokenFetcher::RequestAccessToken() {
101  retry_number_ = 0;
102  gaia_auth_fetcher_.reset();
103  retry_timer_.Stop();
104
105  OAuth2TokenService::ScopeSet scopes;
106  scopes.insert(GaiaConstants::kOAuth1LoginScope);
107  access_token_request_ =
108      token_service_->StartRequest(account_id_, scopes, this);
109}
110
111void UbertokenFetcher::ExchangeTokens() {
112  gaia_auth_fetcher_.reset(new GaiaAuthFetcher(this,
113                                               GaiaConstants::kChromeSource,
114                                               request_context_));
115  gaia_auth_fetcher_->StartTokenFetchForUberAuthExchange(access_token_);
116}
117