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 case net::HTTP_INTERNAL_SERVER_ERROR
:
187 // HTTP_FORBIDDEN (403) is treated as temporary error, because it may be
188 // '403 Rate Limit Exeeded.' 500 is always treated as transient.
190 GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE
));
192 case net::HTTP_BAD_REQUEST
: {
193 // HTTP_BAD_REQUEST (400) usually contains error as per
194 // http://tools.ietf.org/html/rfc6749#section-5.2.
195 std::string gaia_error
;
196 if (!ParseGetAccessTokenFailureResponse(source
, &gaia_error
)) {
198 GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_ERROR
));
202 OAuth2ErrorCodesForHistogram
access_error(
203 OAuth2ErrorToHistogramValue(gaia_error
));
204 UMA_HISTOGRAM_ENUMERATION("Gaia.BadRequestTypeForOAuth2AccessToken",
206 OAUTH2_ACCESS_ERROR_COUNT
);
209 access_error
== OAUTH2_ACCESS_ERROR_INVALID_GRANT
210 ? GoogleServiceAuthError(
211 GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS
)
212 : GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_ERROR
));
216 // The other errors are treated as permanent error.
217 OnGetTokenFailure(GoogleServiceAuthError(
218 GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS
));
222 // The request was successfully fetched and it returned OK.
223 // Parse out the access token and the expiration time.
224 std::string access_token
;
226 if (!ParseGetAccessTokenSuccessResponse(source
, &access_token
, &expires_in
)) {
227 DLOG(WARNING
) << "Response doesn't match expected format";
229 GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE
));
232 // The token will expire in |expires_in| seconds. Take a 10% error margin to
233 // prevent reusing a token too close to its expiration date.
236 base::Time::Now() + base::TimeDelta::FromSeconds(9 * expires_in
/ 10));
239 void OAuth2AccessTokenFetcherImpl::OnGetTokenSuccess(
240 const std::string
& access_token
,
241 const base::Time
& expiration_time
) {
242 FireOnGetTokenSuccess(access_token
, expiration_time
);
245 void OAuth2AccessTokenFetcherImpl::OnGetTokenFailure(
246 const GoogleServiceAuthError
& error
) {
247 state_
= ERROR_STATE
;
248 FireOnGetTokenFailure(error
);
251 void OAuth2AccessTokenFetcherImpl::OnURLFetchComplete(
252 const net::URLFetcher
* source
) {
254 CHECK(state_
== GET_ACCESS_TOKEN_STARTED
);
255 EndGetAccessToken(source
);
259 GURL
OAuth2AccessTokenFetcherImpl::MakeGetAccessTokenUrl() {
260 return GaiaUrls::GetInstance()->oauth2_token_url();
264 std::string
OAuth2AccessTokenFetcherImpl::MakeGetAccessTokenBody(
265 const std::string
& client_id
,
266 const std::string
& client_secret
,
267 const std::string
& refresh_token
,
268 const std::vector
<std::string
>& scopes
) {
269 std::string enc_client_id
= net::EscapeUrlEncodedData(client_id
, true);
270 std::string enc_client_secret
=
271 net::EscapeUrlEncodedData(client_secret
, true);
272 std::string enc_refresh_token
=
273 net::EscapeUrlEncodedData(refresh_token
, true);
274 if (scopes
.empty()) {
275 return base::StringPrintf(kGetAccessTokenBodyFormat
,
276 enc_client_id
.c_str(),
277 enc_client_secret
.c_str(),
278 enc_refresh_token
.c_str());
280 std::string scopes_string
= JoinString(scopes
, ' ');
281 return base::StringPrintf(
282 kGetAccessTokenBodyWithScopeFormat
,
283 enc_client_id
.c_str(),
284 enc_client_secret
.c_str(),
285 enc_refresh_token
.c_str(),
286 net::EscapeUrlEncodedData(scopes_string
, true).c_str());
291 bool OAuth2AccessTokenFetcherImpl::ParseGetAccessTokenSuccessResponse(
292 const net::URLFetcher
* source
,
293 std::string
* access_token
,
296 scoped_ptr
<base::DictionaryValue
> value
= ParseGetAccessTokenResponse(source
);
297 if (value
.get() == NULL
)
300 return value
->GetString(kAccessTokenKey
, access_token
) &&
301 value
->GetInteger(kExpiresInKey
, expires_in
);
305 bool OAuth2AccessTokenFetcherImpl::ParseGetAccessTokenFailureResponse(
306 const net::URLFetcher
* source
,
307 std::string
* error
) {
309 scoped_ptr
<base::DictionaryValue
> value
= ParseGetAccessTokenResponse(source
);
310 if (value
.get() == NULL
)
312 return value
->GetString(kErrorKey
, error
);