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/string_util.h"
13 #include "base/stringprintf.h"
14 #include "base/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 GURL(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 DictionaryValue
* dict
= static_cast<DictionaryValue
*>(value
.get());
230 return dict
->GetString(kAccessTokenKey
, access_token
) &&
231 dict
->GetInteger(kExpiresInKey
, expires_in
);