oauth2_access_token_fetcher.cc revision eb525c5499e34cc9c4b825d6d9e75bb07cc06ace
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_access_token_fetcher.h"
6
7#include <algorithm>
8#include <string>
9#include <vector>
10
11#include "base/json/json_reader.h"
12#include "base/strings/string_util.h"
13#include "base/strings/stringprintf.h"
14#include "base/time/time.h"
15#include "base/values.h"
16#include "google_apis/gaia/gaia_urls.h"
17#include "google_apis/gaia/google_service_auth_error.h"
18#include "net/base/escape.h"
19#include "net/base/load_flags.h"
20#include "net/http/http_status_code.h"
21#include "net/url_request/url_fetcher.h"
22#include "net/url_request/url_request_context_getter.h"
23#include "net/url_request/url_request_status.h"
24
25using net::ResponseCookies;
26using net::URLFetcher;
27using net::URLFetcherDelegate;
28using net::URLRequestContextGetter;
29using net::URLRequestStatus;
30
31namespace {
32static const char kGetAccessTokenBodyFormat[] =
33    "client_id=%s&"
34    "client_secret=%s&"
35    "grant_type=refresh_token&"
36    "refresh_token=%s";
37
38static const char kGetAccessTokenBodyWithScopeFormat[] =
39    "client_id=%s&"
40    "client_secret=%s&"
41    "grant_type=refresh_token&"
42    "refresh_token=%s&"
43    "scope=%s";
44
45static const char kAccessTokenKey[] = "access_token";
46static const char kExpiresInKey[] = "expires_in";
47
48static GoogleServiceAuthError CreateAuthError(URLRequestStatus status) {
49  CHECK(!status.is_success());
50  if (status.status() == URLRequestStatus::CANCELED) {
51    return GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED);
52  } else {
53    DLOG(WARNING) << "Could not reach Google Accounts servers: errno "
54                  << status.error();
55    return GoogleServiceAuthError::FromConnectionError(status.error());
56  }
57}
58
59static URLFetcher* CreateFetcher(URLRequestContextGetter* getter,
60                                 const GURL& url,
61                                 const std::string& body,
62                                 URLFetcherDelegate* delegate) {
63  bool empty_body = body.empty();
64  URLFetcher* result = net::URLFetcher::Create(
65      0, url,
66      empty_body ? URLFetcher::GET : URLFetcher::POST,
67      delegate);
68
69  result->SetRequestContext(getter);
70  result->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES |
71                       net::LOAD_DO_NOT_SAVE_COOKIES);
72  // Fetchers are sometimes cancelled because a network change was detected,
73  // especially at startup and after sign-in on ChromeOS. Retrying once should
74  // be enough in those cases; let the fetcher retry up to 3 times just in case.
75  // http://crbug.com/163710
76  result->SetAutomaticallyRetryOnNetworkChanges(3);
77
78  if (!empty_body)
79    result->SetUploadData("application/x-www-form-urlencoded", body);
80
81  return result;
82}
83}  // namespace
84
85OAuth2AccessTokenFetcher::OAuth2AccessTokenFetcher(
86    OAuth2AccessTokenConsumer* consumer,
87    URLRequestContextGetter* getter)
88    : consumer_(consumer),
89      getter_(getter),
90      state_(INITIAL) { }
91
92OAuth2AccessTokenFetcher::~OAuth2AccessTokenFetcher() { }
93
94void OAuth2AccessTokenFetcher::CancelRequest() {
95  fetcher_.reset();
96}
97
98void OAuth2AccessTokenFetcher::Start(const std::string& client_id,
99                                     const std::string& client_secret,
100                                     const std::string& refresh_token,
101                                     const std::vector<std::string>& scopes) {
102  client_id_ = client_id;
103  client_secret_ = client_secret;
104  refresh_token_ = refresh_token;
105  scopes_ = scopes;
106  StartGetAccessToken();
107}
108
109void OAuth2AccessTokenFetcher::StartGetAccessToken() {
110  CHECK_EQ(INITIAL, state_);
111  state_ = GET_ACCESS_TOKEN_STARTED;
112  fetcher_.reset(CreateFetcher(
113      getter_,
114      MakeGetAccessTokenUrl(),
115      MakeGetAccessTokenBody(
116          client_id_, client_secret_, refresh_token_, scopes_),
117      this));
118  fetcher_->Start();  // OnURLFetchComplete will be called.
119}
120
121void OAuth2AccessTokenFetcher::EndGetAccessToken(
122    const net::URLFetcher* source) {
123  CHECK_EQ(GET_ACCESS_TOKEN_STARTED, state_);
124  state_ = GET_ACCESS_TOKEN_DONE;
125
126  URLRequestStatus status = source->GetStatus();
127  if (!status.is_success()) {
128    OnGetTokenFailure(CreateAuthError(status));
129    return;
130  }
131
132  // HTTP_FORBIDDEN (403) is treated as temporary error, because it may be
133  // '403 Rate Limit Exeeded.'
134  if (source->GetResponseCode() == net::HTTP_FORBIDDEN) {
135    OnGetTokenFailure(GoogleServiceAuthError(
136        GoogleServiceAuthError::SERVICE_UNAVAILABLE));
137    return;
138  }
139
140  // The other errors are treated as permanent error.
141  if (source->GetResponseCode() != net::HTTP_OK) {
142    OnGetTokenFailure(GoogleServiceAuthError(
143        GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS));
144    return;
145  }
146
147  // The request was successfully fetched and it returned OK.
148  // Parse out the access token and the expiration time.
149  std::string access_token;
150  int expires_in;
151  if (!ParseGetAccessTokenResponse(source, &access_token, &expires_in)) {
152    DLOG(WARNING) << "Response doesn't match expected format";
153    OnGetTokenFailure(
154        GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE));
155    return;
156  }
157  // The token will expire in |expires_in| seconds. Take a 10% error margin to
158  // prevent reusing a token too close to its expiration date.
159  OnGetTokenSuccess(
160      access_token,
161      base::Time::Now() + base::TimeDelta::FromSeconds(9 * expires_in / 10));
162}
163
164void OAuth2AccessTokenFetcher::OnGetTokenSuccess(
165    const std::string& access_token,
166    const base::Time& expiration_time) {
167  consumer_->OnGetTokenSuccess(access_token, expiration_time);
168}
169
170void OAuth2AccessTokenFetcher::OnGetTokenFailure(
171    const GoogleServiceAuthError& error) {
172  state_ = ERROR_STATE;
173  consumer_->OnGetTokenFailure(error);
174}
175
176void OAuth2AccessTokenFetcher::OnURLFetchComplete(
177    const net::URLFetcher* source) {
178  CHECK(source);
179  CHECK(state_ == GET_ACCESS_TOKEN_STARTED);
180  EndGetAccessToken(source);
181}
182
183// static
184GURL OAuth2AccessTokenFetcher::MakeGetAccessTokenUrl() {
185  return GURL(GaiaUrls::GetInstance()->oauth2_token_url());
186}
187
188// static
189std::string OAuth2AccessTokenFetcher::MakeGetAccessTokenBody(
190    const std::string& client_id,
191    const std::string& client_secret,
192    const std::string& refresh_token,
193    const std::vector<std::string>& scopes) {
194  std::string enc_client_id = net::EscapeUrlEncodedData(client_id, true);
195  std::string enc_client_secret =
196      net::EscapeUrlEncodedData(client_secret, true);
197  std::string enc_refresh_token =
198      net::EscapeUrlEncodedData(refresh_token, true);
199  if (scopes.empty()) {
200    return base::StringPrintf(
201        kGetAccessTokenBodyFormat,
202        enc_client_id.c_str(),
203        enc_client_secret.c_str(),
204        enc_refresh_token.c_str());
205  } else {
206    std::string scopes_string = JoinString(scopes, ' ');
207    return base::StringPrintf(
208        kGetAccessTokenBodyWithScopeFormat,
209        enc_client_id.c_str(),
210        enc_client_secret.c_str(),
211        enc_refresh_token.c_str(),
212        net::EscapeUrlEncodedData(scopes_string, true).c_str());
213  }
214}
215
216// static
217bool OAuth2AccessTokenFetcher::ParseGetAccessTokenResponse(
218    const net::URLFetcher* source,
219    std::string* access_token,
220    int* expires_in) {
221  CHECK(source);
222  CHECK(access_token);
223  std::string data;
224  source->GetResponseAsString(&data);
225  scoped_ptr<base::Value> value(base::JSONReader::Read(data));
226  if (!value.get() || value->GetType() != base::Value::TYPE_DICTIONARY)
227    return false;
228
229  base::DictionaryValue* dict =
230      static_cast<base::DictionaryValue*>(value.get());
231  return dict->GetString(kAccessTokenKey, access_token) &&
232      dict->GetInteger(kExpiresInKey, expires_in);
233}
234