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.
5 #include "google_apis/gaia/oauth2_access_token_fetcher_impl.h"
11 #include "base/json/json_reader.h"
12 #include "base/metrics/histogram.h"
13 #include "base/metrics/sparse_histogram.h"
14 #include "base/strings/string_util.h"
15 #include "base/strings/stringprintf.h"
16 #include "base/time/time.h"
17 #include "base/values.h"
18 #include "google_apis/gaia/gaia_urls.h"
19 #include "google_apis/gaia/google_service_auth_error.h"
20 #include "net/base/escape.h"
21 #include "net/base/load_flags.h"
22 #include "net/http/http_status_code.h"
23 #include "net/url_request/url_fetcher.h"
24 #include "net/url_request/url_request_context_getter.h"
25 #include "net/url_request/url_request_status.h"
27 using net::ResponseCookies
;
28 using net::URLFetcher
;
29 using net::URLFetcherDelegate
;
30 using net::URLRequestContextGetter
;
31 using net::URLRequestStatus
;
34 static const char kGetAccessTokenBodyFormat
[] =
37 "grant_type=refresh_token&"
40 static const char kGetAccessTokenBodyWithScopeFormat
[] =
43 "grant_type=refresh_token&"
47 static const char kAccessTokenKey
[] = "access_token";
48 static const char kExpiresInKey
[] = "expires_in";
49 static const char kErrorKey
[] = "error";
51 // Enumerated constants for logging server responses on 400 errors, matching
53 enum OAuth2ErrorCodesForHistogram
{
54 OAUTH2_ACCESS_ERROR_INVALID_REQUEST
= 0,
55 OAUTH2_ACCESS_ERROR_INVALID_CLIENT
,
56 OAUTH2_ACCESS_ERROR_INVALID_GRANT
,
57 OAUTH2_ACCESS_ERROR_UNAUTHORIZED_CLIENT
,
58 OAUTH2_ACCESS_ERROR_UNSUPPORTED_GRANT_TYPE
,
59 OAUTH2_ACCESS_ERROR_INVALID_SCOPE
,
60 OAUTH2_ACCESS_ERROR_UNKNOWN
,
61 OAUTH2_ACCESS_ERROR_COUNT
64 OAuth2ErrorCodesForHistogram
OAuth2ErrorToHistogramValue(
65 const std::string
& error
) {
66 if (error
== "invalid_request")
67 return OAUTH2_ACCESS_ERROR_INVALID_REQUEST
;
68 else if (error
== "invalid_client")
69 return OAUTH2_ACCESS_ERROR_INVALID_CLIENT
;
70 else if (error
== "invalid_grant")
71 return OAUTH2_ACCESS_ERROR_INVALID_GRANT
;
72 else if (error
== "unauthorized_client")
73 return OAUTH2_ACCESS_ERROR_UNAUTHORIZED_CLIENT
;
74 else if (error
== "unsupported_grant_type")
75 return OAUTH2_ACCESS_ERROR_UNSUPPORTED_GRANT_TYPE
;
76 else if (error
== "invalid_scope")
77 return OAUTH2_ACCESS_ERROR_INVALID_SCOPE
;
79 return OAUTH2_ACCESS_ERROR_UNKNOWN
;
82 static GoogleServiceAuthError
CreateAuthError(URLRequestStatus status
) {
83 CHECK(!status
.is_success());
84 if (status
.status() == URLRequestStatus::CANCELED
) {
85 return GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED
);
87 DLOG(WARNING
) << "Could not reach Google Accounts servers: errno "
89 return GoogleServiceAuthError::FromConnectionError(status
.error());
93 static scoped_ptr
<URLFetcher
> CreateFetcher(URLRequestContextGetter
* getter
,
95 const std::string
& body
,
96 URLFetcherDelegate
* delegate
) {
97 bool empty_body
= body
.empty();
98 scoped_ptr
<URLFetcher
> result
= net::URLFetcher::Create(
99 0, url
, empty_body
? URLFetcher::GET
: URLFetcher::POST
, delegate
);
101 result
->SetRequestContext(getter
);
102 result
->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES
|
103 net::LOAD_DO_NOT_SAVE_COOKIES
);
104 // Fetchers are sometimes cancelled because a network change was detected,
105 // especially at startup and after sign-in on ChromeOS. Retrying once should
106 // be enough in those cases; let the fetcher retry up to 3 times just in case.
107 // http://crbug.com/163710
108 result
->SetAutomaticallyRetryOnNetworkChanges(3);
111 result
->SetUploadData("application/x-www-form-urlencoded", body
);
116 scoped_ptr
<base::DictionaryValue
> ParseGetAccessTokenResponse(
117 const net::URLFetcher
* source
) {
121 source
->GetResponseAsString(&data
);
122 scoped_ptr
<base::Value
> value
= base::JSONReader::Read(data
);
123 if (!value
.get() || value
->GetType() != base::Value::TYPE_DICTIONARY
)
126 return scoped_ptr
<base::DictionaryValue
>(
127 static_cast<base::DictionaryValue
*>(value
.release()));
132 OAuth2AccessTokenFetcherImpl::OAuth2AccessTokenFetcherImpl(
133 OAuth2AccessTokenConsumer
* consumer
,
134 net::URLRequestContextGetter
* getter
,
135 const std::string
& refresh_token
)
136 : OAuth2AccessTokenFetcher(consumer
),
138 refresh_token_(refresh_token
),
141 OAuth2AccessTokenFetcherImpl::~OAuth2AccessTokenFetcherImpl() {}
143 void OAuth2AccessTokenFetcherImpl::CancelRequest() { fetcher_
.reset(); }
145 void OAuth2AccessTokenFetcherImpl::Start(
146 const std::string
& client_id
,
147 const std::string
& client_secret
,
148 const std::vector
<std::string
>& scopes
) {
149 client_id_
= client_id
;
150 client_secret_
= client_secret
;
152 StartGetAccessToken();
155 void OAuth2AccessTokenFetcherImpl::StartGetAccessToken() {
156 CHECK_EQ(INITIAL
, state_
);
157 state_
= GET_ACCESS_TOKEN_STARTED
;
158 fetcher_
= CreateFetcher(getter_
, MakeGetAccessTokenUrl(),
159 MakeGetAccessTokenBody(client_id_
, client_secret_
,
160 refresh_token_
, scopes_
),
162 fetcher_
->Start(); // OnURLFetchComplete will be called.
165 void OAuth2AccessTokenFetcherImpl::EndGetAccessToken(
166 const net::URLFetcher
* source
) {
167 CHECK_EQ(GET_ACCESS_TOKEN_STARTED
, state_
);
168 state_
= GET_ACCESS_TOKEN_DONE
;
170 URLRequestStatus status
= source
->GetStatus();
171 int histogram_value
=
172 status
.is_success() ? source
->GetResponseCode() : status
.error();
173 UMA_HISTOGRAM_SPARSE_SLOWLY("Gaia.ResponseCodesForOAuth2AccessToken",
175 if (!status
.is_success()) {
176 OnGetTokenFailure(CreateAuthError(status
));
180 switch (source
->GetResponseCode()) {
183 case net::HTTP_FORBIDDEN
:
184 // HTTP_FORBIDDEN (403) is treated as temporary error, because it may be
185 // '403 Rate Limit Exeeded.'
187 GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE
));
189 case net::HTTP_BAD_REQUEST
: {
190 // HTTP_BAD_REQUEST (400) usually contains error as per
191 // http://tools.ietf.org/html/rfc6749#section-5.2.
192 std::string gaia_error
;
193 if (!ParseGetAccessTokenFailureResponse(source
, &gaia_error
)) {
195 GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_ERROR
));
199 OAuth2ErrorCodesForHistogram
access_error(
200 OAuth2ErrorToHistogramValue(gaia_error
));
201 UMA_HISTOGRAM_ENUMERATION("Gaia.BadRequestTypeForOAuth2AccessToken",
203 OAUTH2_ACCESS_ERROR_COUNT
);
206 access_error
== OAUTH2_ACCESS_ERROR_INVALID_GRANT
207 ? GoogleServiceAuthError(
208 GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS
)
209 : GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_ERROR
));
213 if (source
->GetResponseCode() >= net::HTTP_INTERNAL_SERVER_ERROR
) {
214 // 5xx is always treated as transient.
215 OnGetTokenFailure(GoogleServiceAuthError(
216 GoogleServiceAuthError::SERVICE_UNAVAILABLE
));
218 // The other errors are treated as permanent error.
219 DLOG(ERROR
) << "Unexpected persistent error: http_status="
220 << source
->GetResponseCode();
221 OnGetTokenFailure(GoogleServiceAuthError(
222 GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS
));
228 // The request was successfully fetched and it returned OK.
229 // Parse out the access token and the expiration time.
230 std::string access_token
;
232 if (!ParseGetAccessTokenSuccessResponse(source
, &access_token
, &expires_in
)) {
233 DLOG(WARNING
) << "Response doesn't match expected format";
235 GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE
));
238 // The token will expire in |expires_in| seconds. Take a 10% error margin to
239 // prevent reusing a token too close to its expiration date.
242 base::Time::Now() + base::TimeDelta::FromSeconds(9 * expires_in
/ 10));
245 void OAuth2AccessTokenFetcherImpl::OnGetTokenSuccess(
246 const std::string
& access_token
,
247 const base::Time
& expiration_time
) {
248 FireOnGetTokenSuccess(access_token
, expiration_time
);
251 void OAuth2AccessTokenFetcherImpl::OnGetTokenFailure(
252 const GoogleServiceAuthError
& error
) {
253 state_
= ERROR_STATE
;
254 FireOnGetTokenFailure(error
);
257 void OAuth2AccessTokenFetcherImpl::OnURLFetchComplete(
258 const net::URLFetcher
* source
) {
260 CHECK(state_
== GET_ACCESS_TOKEN_STARTED
);
261 EndGetAccessToken(source
);
265 GURL
OAuth2AccessTokenFetcherImpl::MakeGetAccessTokenUrl() {
266 return GaiaUrls::GetInstance()->oauth2_token_url();
270 std::string
OAuth2AccessTokenFetcherImpl::MakeGetAccessTokenBody(
271 const std::string
& client_id
,
272 const std::string
& client_secret
,
273 const std::string
& refresh_token
,
274 const std::vector
<std::string
>& scopes
) {
275 std::string enc_client_id
= net::EscapeUrlEncodedData(client_id
, true);
276 std::string enc_client_secret
=
277 net::EscapeUrlEncodedData(client_secret
, true);
278 std::string enc_refresh_token
=
279 net::EscapeUrlEncodedData(refresh_token
, true);
280 if (scopes
.empty()) {
281 return base::StringPrintf(kGetAccessTokenBodyFormat
,
282 enc_client_id
.c_str(),
283 enc_client_secret
.c_str(),
284 enc_refresh_token
.c_str());
286 std::string scopes_string
= base::JoinString(scopes
, " ");
287 return base::StringPrintf(
288 kGetAccessTokenBodyWithScopeFormat
,
289 enc_client_id
.c_str(),
290 enc_client_secret
.c_str(),
291 enc_refresh_token
.c_str(),
292 net::EscapeUrlEncodedData(scopes_string
, true).c_str());
297 bool OAuth2AccessTokenFetcherImpl::ParseGetAccessTokenSuccessResponse(
298 const net::URLFetcher
* source
,
299 std::string
* access_token
,
302 scoped_ptr
<base::DictionaryValue
> value
= ParseGetAccessTokenResponse(source
);
303 if (value
.get() == NULL
)
306 return value
->GetString(kAccessTokenKey
, access_token
) &&
307 value
->GetInteger(kExpiresInKey
, expires_in
);
311 bool OAuth2AccessTokenFetcherImpl::ParseGetAccessTokenFailureResponse(
312 const net::URLFetcher
* source
,
313 std::string
* error
) {
315 scoped_ptr
<base::DictionaryValue
> value
= ParseGetAccessTokenResponse(source
);
316 if (value
.get() == NULL
)
318 return value
->GetString(kErrorKey
, error
);