oauth2_api_call_flow.cc revision c2e0dbddbe15c98d52c4786dac06cb8952a8ae6d
1// Copyright (c) 2012 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/oauth2_api_call_flow.h"
6
7#include <string>
8#include <vector>
9
10#include "base/basictypes.h"
11#include "base/stringprintf.h"
12#include "google_apis/gaia/gaia_urls.h"
13#include "net/base/escape.h"
14#include "net/base/load_flags.h"
15#include "net/http/http_status_code.h"
16#include "net/url_request/url_fetcher.h"
17#include "net/url_request/url_request_context_getter.h"
18#include "net/url_request/url_request_status.h"
19
20using net::ResponseCookies;
21using net::URLFetcher;
22using net::URLFetcherDelegate;
23using net::URLRequestContextGetter;
24using net::URLRequestStatus;
25
26namespace {
27static const char kAuthorizationHeaderFormat[] =
28    "Authorization: Bearer %s";
29
30static std::string MakeAuthorizationHeader(const std::string& auth_token) {
31  return base::StringPrintf(kAuthorizationHeaderFormat, auth_token.c_str());
32}
33}  // namespace
34
35OAuth2ApiCallFlow::OAuth2ApiCallFlow(
36    net::URLRequestContextGetter* context,
37    const std::string& refresh_token,
38    const std::string& access_token,
39    const std::vector<std::string>& scopes)
40    : context_(context),
41      refresh_token_(refresh_token),
42      access_token_(access_token),
43      scopes_(scopes),
44      chrome_client_id_(GaiaUrls::GetInstance()->oauth2_chrome_client_id()),
45      chrome_client_secret_(
46          GaiaUrls::GetInstance()->oauth2_chrome_client_secret()),
47      state_(INITIAL),
48      tried_mint_access_token_(false) {
49}
50
51OAuth2ApiCallFlow::~OAuth2ApiCallFlow() {}
52
53void OAuth2ApiCallFlow::Start() {
54  BeginApiCall();
55}
56
57#if defined(OS_CHROMEOS)
58void OAuth2ApiCallFlow::SetChromeOAuthClientInfo(
59    const std::string& chrome_client_id,
60    const std::string& chrome_client_secret) {
61  chrome_client_id_ = chrome_client_id;
62  chrome_client_secret_ = chrome_client_secret;
63}
64#endif
65
66void OAuth2ApiCallFlow::BeginApiCall() {
67  CHECK(state_ == INITIAL || state_ == MINT_ACCESS_TOKEN_DONE);
68
69  // If the access token is empty then directly try to mint one.
70  if (access_token_.empty()) {
71    BeginMintAccessToken();
72  } else {
73    state_ = API_CALL_STARTED;
74    url_fetcher_.reset(CreateURLFetcher());
75    url_fetcher_->Start();  // OnURLFetchComplete will be called.
76  }
77}
78
79void OAuth2ApiCallFlow::EndApiCall(const net::URLFetcher* source) {
80  CHECK_EQ(API_CALL_STARTED, state_);
81  state_ = API_CALL_DONE;
82
83  URLRequestStatus status = source->GetStatus();
84  if (!status.is_success()) {
85    state_ = ERROR_STATE;
86    ProcessApiCallFailure(source);
87    return;
88  }
89
90  // If the response code is 401 Unauthorized then access token may have
91  // expired. So try generating a new access token.
92  if (source->GetResponseCode() == net::HTTP_UNAUTHORIZED) {
93    // If we already tried minting a new access token, don't do it again.
94    if (tried_mint_access_token_) {
95      state_ = ERROR_STATE;
96      ProcessApiCallFailure(source);
97    } else {
98      BeginMintAccessToken();
99    }
100
101    return;
102  }
103
104  if (source->GetResponseCode() != net::HTTP_OK) {
105    state_ = ERROR_STATE;
106    ProcessApiCallFailure(source);
107    return;
108  }
109
110  ProcessApiCallSuccess(source);
111}
112
113void OAuth2ApiCallFlow::BeginMintAccessToken() {
114  CHECK(state_ == INITIAL || state_ == API_CALL_DONE);
115  CHECK(!tried_mint_access_token_);
116  state_ = MINT_ACCESS_TOKEN_STARTED;
117  tried_mint_access_token_ = true;
118
119  oauth2_access_token_fetcher_.reset(CreateAccessTokenFetcher());
120  oauth2_access_token_fetcher_->Start(
121      chrome_client_id_,
122      chrome_client_secret_,
123      refresh_token_,
124      scopes_);
125}
126
127void OAuth2ApiCallFlow::EndMintAccessToken(
128    const GoogleServiceAuthError* error) {
129  CHECK_EQ(MINT_ACCESS_TOKEN_STARTED, state_);
130
131  if (!error) {
132    state_ = MINT_ACCESS_TOKEN_DONE;
133    BeginApiCall();
134  } else {
135    state_ = ERROR_STATE;
136    ProcessMintAccessTokenFailure(*error);
137  }
138}
139
140OAuth2AccessTokenFetcher* OAuth2ApiCallFlow::CreateAccessTokenFetcher() {
141  return new OAuth2AccessTokenFetcher(this, context_);
142}
143
144void OAuth2ApiCallFlow::OnURLFetchComplete(const net::URLFetcher* source) {
145  CHECK(source);
146  CHECK_EQ(API_CALL_STARTED, state_);
147  EndApiCall(source);
148}
149
150void OAuth2ApiCallFlow::OnGetTokenSuccess(const std::string& access_token,
151                                          const base::Time& expiration_time) {
152  access_token_ = access_token;
153  EndMintAccessToken(NULL);
154}
155
156void OAuth2ApiCallFlow::OnGetTokenFailure(
157    const GoogleServiceAuthError& error) {
158  EndMintAccessToken(&error);
159}
160
161URLFetcher* OAuth2ApiCallFlow::CreateURLFetcher() {
162  std::string body = CreateApiCallBody();
163  bool empty_body = body.empty();
164  URLFetcher* result = net::URLFetcher::Create(
165      0,
166      CreateApiCallUrl(),
167      empty_body ? URLFetcher::GET : URLFetcher::POST,
168      this);
169
170  result->SetRequestContext(context_);
171  result->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES |
172                       net::LOAD_DO_NOT_SAVE_COOKIES);
173  result->AddExtraRequestHeader(MakeAuthorizationHeader(access_token_));
174  // Fetchers are sometimes cancelled because a network change was detected,
175  // especially at startup and after sign-in on ChromeOS. Retrying once should
176  // be enough in those cases; let the fetcher retry up to 3 times just in case.
177  // http://crbug.com/163710
178  result->SetAutomaticallyRetryOnNetworkChanges(3);
179
180  if (!empty_body)
181    result->SetUploadData("application/x-www-form-urlencoded", body);
182
183  return result;
184}
185