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 URLFetcher
* CreateFetcher(URLRequestContextGetter
* getter
,
95 const std::string
& body
,
96 URLFetcherDelegate
* delegate
) {
97 bool empty_body
= body
.empty();
98 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
;
159 CreateFetcher(getter_
,
160 MakeGetAccessTokenUrl(),
161 MakeGetAccessTokenBody(
162 client_id_
, client_secret_
, refresh_token_
, scopes_
),
164 fetcher_
->Start(); // OnURLFetchComplete will be called.
167 void OAuth2AccessTokenFetcherImpl::EndGetAccessToken(
168 const net::URLFetcher
* source
) {
169 CHECK_EQ(GET_ACCESS_TOKEN_STARTED
, state_
);
170 state_
= GET_ACCESS_TOKEN_DONE
;
172 URLRequestStatus status
= source
->GetStatus();
173 int histogram_value
=
174 status
.is_success() ? source
->GetResponseCode() : status
.error();
175 UMA_HISTOGRAM_SPARSE_SLOWLY("Gaia.ResponseCodesForOAuth2AccessToken",
177 if (!status
.is_success()) {
178 OnGetTokenFailure(CreateAuthError(status
));
182 switch (source
->GetResponseCode()) {
185 case net::HTTP_FORBIDDEN
:
186 // HTTP_FORBIDDEN (403) is treated as temporary error, because it may be
187 // '403 Rate Limit Exeeded.'
189 GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE
));
191 case net::HTTP_BAD_REQUEST
: {
192 // HTTP_BAD_REQUEST (400) usually contains error as per
193 // http://tools.ietf.org/html/rfc6749#section-5.2.
194 std::string gaia_error
;
195 if (!ParseGetAccessTokenFailureResponse(source
, &gaia_error
)) {
197 GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_ERROR
));
201 OAuth2ErrorCodesForHistogram
access_error(
202 OAuth2ErrorToHistogramValue(gaia_error
));
203 UMA_HISTOGRAM_ENUMERATION("Gaia.BadRequestTypeForOAuth2AccessToken",
205 OAUTH2_ACCESS_ERROR_COUNT
);
208 access_error
== OAUTH2_ACCESS_ERROR_INVALID_GRANT
209 ? GoogleServiceAuthError(
210 GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS
)
211 : GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_ERROR
));
215 if (source
->GetResponseCode() >= net::HTTP_INTERNAL_SERVER_ERROR
) {
216 // 5xx is always treated as transient.
217 OnGetTokenFailure(GoogleServiceAuthError(
218 GoogleServiceAuthError::SERVICE_UNAVAILABLE
));
220 // The other errors are treated as permanent error.
221 DLOG(ERROR
) << "Unexpected persistent error: http_status="
222 << source
->GetResponseCode();
223 OnGetTokenFailure(GoogleServiceAuthError(
224 GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS
));
230 // The request was successfully fetched and it returned OK.
231 // Parse out the access token and the expiration time.
232 std::string access_token
;
234 if (!ParseGetAccessTokenSuccessResponse(source
, &access_token
, &expires_in
)) {
235 DLOG(WARNING
) << "Response doesn't match expected format";
237 GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE
));
240 // The token will expire in |expires_in| seconds. Take a 10% error margin to
241 // prevent reusing a token too close to its expiration date.
244 base::Time::Now() + base::TimeDelta::FromSeconds(9 * expires_in
/ 10));
247 void OAuth2AccessTokenFetcherImpl::OnGetTokenSuccess(
248 const std::string
& access_token
,
249 const base::Time
& expiration_time
) {
250 FireOnGetTokenSuccess(access_token
, expiration_time
);
253 void OAuth2AccessTokenFetcherImpl::OnGetTokenFailure(
254 const GoogleServiceAuthError
& error
) {
255 state_
= ERROR_STATE
;
256 FireOnGetTokenFailure(error
);
259 void OAuth2AccessTokenFetcherImpl::OnURLFetchComplete(
260 const net::URLFetcher
* source
) {
262 CHECK(state_
== GET_ACCESS_TOKEN_STARTED
);
263 EndGetAccessToken(source
);
267 GURL
OAuth2AccessTokenFetcherImpl::MakeGetAccessTokenUrl() {
268 return GaiaUrls::GetInstance()->oauth2_token_url();
272 std::string
OAuth2AccessTokenFetcherImpl::MakeGetAccessTokenBody(
273 const std::string
& client_id
,
274 const std::string
& client_secret
,
275 const std::string
& refresh_token
,
276 const std::vector
<std::string
>& scopes
) {
277 std::string enc_client_id
= net::EscapeUrlEncodedData(client_id
, true);
278 std::string enc_client_secret
=
279 net::EscapeUrlEncodedData(client_secret
, true);
280 std::string enc_refresh_token
=
281 net::EscapeUrlEncodedData(refresh_token
, true);
282 if (scopes
.empty()) {
283 return base::StringPrintf(kGetAccessTokenBodyFormat
,
284 enc_client_id
.c_str(),
285 enc_client_secret
.c_str(),
286 enc_refresh_token
.c_str());
288 std::string scopes_string
= JoinString(scopes
, ' ');
289 return base::StringPrintf(
290 kGetAccessTokenBodyWithScopeFormat
,
291 enc_client_id
.c_str(),
292 enc_client_secret
.c_str(),
293 enc_refresh_token
.c_str(),
294 net::EscapeUrlEncodedData(scopes_string
, true).c_str());
299 bool OAuth2AccessTokenFetcherImpl::ParseGetAccessTokenSuccessResponse(
300 const net::URLFetcher
* source
,
301 std::string
* access_token
,
304 scoped_ptr
<base::DictionaryValue
> value
= ParseGetAccessTokenResponse(source
);
305 if (value
.get() == NULL
)
308 return value
->GetString(kAccessTokenKey
, access_token
) &&
309 value
->GetInteger(kExpiresInKey
, expires_in
);
313 bool OAuth2AccessTokenFetcherImpl::ParseGetAccessTokenFailureResponse(
314 const net::URLFetcher
* source
,
315 std::string
* error
) {
317 scoped_ptr
<base::DictionaryValue
> value
= ParseGetAccessTokenResponse(source
);
318 if (value
.get() == NULL
)
320 return value
->GetString(kErrorKey
, error
);