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