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.
5 #include "google_apis/gaia/oauth2_access_token_fetcher.h"
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"
25 using net::ResponseCookies;
26 using net::URLFetcher;
27 using net::URLFetcherDelegate;
28 using net::URLRequestContextGetter;
29 using net::URLRequestStatus;
32 static const char kGetAccessTokenBodyFormat[] =
35 "grant_type=refresh_token&"
38 static const char kGetAccessTokenBodyWithScopeFormat[] =
41 "grant_type=refresh_token&"
45 static const char kAccessTokenKey[] = "access_token";
46 static const char kExpiresInKey[] = "expires_in";
48 static GoogleServiceAuthError CreateAuthError(URLRequestStatus status) {
49 CHECK(!status.is_success());
50 if (status.status() == URLRequestStatus::CANCELED) {
51 return GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED);
53 DLOG(WARNING) << "Could not reach Google Accounts servers: errno "
55 return GoogleServiceAuthError::FromConnectionError(status.error());
59 static URLFetcher* CreateFetcher(URLRequestContextGetter* getter,
61 const std::string& body,
62 URLFetcherDelegate* delegate) {
63 bool empty_body = body.empty();
64 URLFetcher* result = net::URLFetcher::Create(
66 empty_body ? URLFetcher::GET : URLFetcher::POST,
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);
79 result->SetUploadData("application/x-www-form-urlencoded", body);
85 OAuth2AccessTokenFetcher::OAuth2AccessTokenFetcher(
86 OAuth2AccessTokenConsumer* consumer,
87 URLRequestContextGetter* getter)
88 : consumer_(consumer),
92 OAuth2AccessTokenFetcher::~OAuth2AccessTokenFetcher() { }
94 void OAuth2AccessTokenFetcher::CancelRequest() {
98 void 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;
106 StartGetAccessToken();
109 void OAuth2AccessTokenFetcher::StartGetAccessToken() {
110 CHECK_EQ(INITIAL, state_);
111 state_ = GET_ACCESS_TOKEN_STARTED;
112 fetcher_.reset(CreateFetcher(
114 MakeGetAccessTokenUrl(),
115 MakeGetAccessTokenBody(
116 client_id_, client_secret_, refresh_token_, scopes_),
118 fetcher_->Start(); // OnURLFetchComplete will be called.
121 void OAuth2AccessTokenFetcher::EndGetAccessToken(
122 const net::URLFetcher* source) {
123 CHECK_EQ(GET_ACCESS_TOKEN_STARTED, state_);
124 state_ = GET_ACCESS_TOKEN_DONE;
126 URLRequestStatus status = source->GetStatus();
127 if (!status.is_success()) {
128 OnGetTokenFailure(CreateAuthError(status));
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));
140 // The other errors are treated as permanent error.
141 if (source->GetResponseCode() != net::HTTP_OK) {
142 OnGetTokenFailure(GoogleServiceAuthError(
143 GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS));
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;
151 if (!ParseGetAccessTokenResponse(source, &access_token, &expires_in)) {
152 DLOG(WARNING) << "Response doesn't match expected format";
154 GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE));
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.
161 base::Time::Now() + base::TimeDelta::FromSeconds(9 * expires_in / 10));
164 void OAuth2AccessTokenFetcher::OnGetTokenSuccess(
165 const std::string& access_token,
166 const base::Time& expiration_time) {
167 consumer_->OnGetTokenSuccess(access_token, expiration_time);
170 void OAuth2AccessTokenFetcher::OnGetTokenFailure(
171 const GoogleServiceAuthError& error) {
172 state_ = ERROR_STATE;
173 consumer_->OnGetTokenFailure(error);
176 void OAuth2AccessTokenFetcher::OnURLFetchComplete(
177 const net::URLFetcher* source) {
179 CHECK(state_ == GET_ACCESS_TOKEN_STARTED);
180 EndGetAccessToken(source);
184 GURL OAuth2AccessTokenFetcher::MakeGetAccessTokenUrl() {
185 return GaiaUrls::GetInstance()->oauth2_token_url();
189 std::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());
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());
217 bool OAuth2AccessTokenFetcher::ParseGetAccessTokenResponse(
218 const net::URLFetcher* source,
219 std::string* access_token,
224 source->GetResponseAsString(&data);
225 scoped_ptr<base::Value> value(base::JSONReader::Read(data));
226 if (!value.get() || value->GetType() != base::Value::TYPE_DICTIONARY)
229 base::DictionaryValue* dict =
230 static_cast<base::DictionaryValue*>(value.get());
231 return dict->GetString(kAccessTokenKey, access_token) &&
232 dict->GetInteger(kExpiresInKey, expires_in);