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/gaia_oauth_client.h"
7 #include "base/json/json_reader.h"
8 #include "base/logging.h"
9 #include "base/memory/scoped_ptr.h"
10 #include "base/strings/string_util.h"
11 #include "base/values.h"
12 #include "google_apis/gaia/gaia_urls.h"
13 #include "net/base/escape.h"
14 #include "net/base/load_flags.h"
15 #include "net/http/http_status_code.h"
16 #include "net/url_request/url_fetcher.h"
17 #include "net/url_request/url_fetcher_delegate.h"
18 #include "net/url_request/url_request_context_getter.h"
22 const char kAccessTokenValue
[] = "access_token";
23 const char kRefreshTokenValue
[] = "refresh_token";
24 const char kExpiresInValue
[] = "expires_in";
29 // Use a non-zero number, so unit tests can differentiate the URLFetcher used by
30 // this class from other fetchers (most other code just hardcodes the ID to 0).
31 const int GaiaOAuthClient::kUrlFetcherId
= 17109006;
33 class GaiaOAuthClient::Core
34 : public base::RefCountedThreadSafe
<GaiaOAuthClient::Core
>,
35 public net::URLFetcherDelegate
{
37 Core(net::URLRequestContextGetter
* request_context_getter
)
39 request_context_getter_(request_context_getter
),
41 request_type_(NO_PENDING_REQUEST
) {
44 void GetTokensFromAuthCode(const OAuthClientInfo
& oauth_client_info
,
45 const std::string
& auth_code
,
47 GaiaOAuthClient::Delegate
* delegate
);
48 void RefreshToken(const OAuthClientInfo
& oauth_client_info
,
49 const std::string
& refresh_token
,
50 const std::vector
<std::string
>& scopes
,
52 GaiaOAuthClient::Delegate
* delegate
);
53 void GetUserEmail(const std::string
& oauth_access_token
,
56 void GetUserId(const std::string
& oauth_access_token
,
59 void GetUserInfo(const std::string
& oauth_access_token
,
62 void GetTokenInfo(const std::string
& oauth_access_token
,
66 // net::URLFetcherDelegate implementation.
67 void OnURLFetchComplete(const net::URLFetcher
* source
) override
;
70 friend class base::RefCountedThreadSafe
<Core
>;
74 TOKENS_FROM_AUTH_CODE
,
84 void GetUserInfoImpl(RequestType type
,
85 const std::string
& oauth_access_token
,
88 void MakeGaiaRequest(const GURL
& url
,
89 const std::string
& post_body
,
91 GaiaOAuthClient::Delegate
* delegate
);
92 void HandleResponse(const net::URLFetcher
* source
,
93 bool* should_retry_request
);
96 scoped_refptr
<net::URLRequestContextGetter
> request_context_getter_
;
97 GaiaOAuthClient::Delegate
* delegate_
;
98 scoped_ptr
<net::URLFetcher
> request_
;
99 RequestType request_type_
;
102 void GaiaOAuthClient::Core::GetTokensFromAuthCode(
103 const OAuthClientInfo
& oauth_client_info
,
104 const std::string
& auth_code
,
106 GaiaOAuthClient::Delegate
* delegate
) {
107 DCHECK_EQ(request_type_
, NO_PENDING_REQUEST
);
108 request_type_
= TOKENS_FROM_AUTH_CODE
;
109 std::string post_body
=
110 "code=" + net::EscapeUrlEncodedData(auth_code
, true) +
111 "&client_id=" + net::EscapeUrlEncodedData(oauth_client_info
.client_id
,
114 net::EscapeUrlEncodedData(oauth_client_info
.client_secret
, true) +
116 net::EscapeUrlEncodedData(oauth_client_info
.redirect_uri
, true) +
117 "&grant_type=authorization_code";
118 MakeGaiaRequest(GURL(GaiaUrls::GetInstance()->oauth2_token_url()),
119 post_body
, max_retries
, delegate
);
122 void GaiaOAuthClient::Core::RefreshToken(
123 const OAuthClientInfo
& oauth_client_info
,
124 const std::string
& refresh_token
,
125 const std::vector
<std::string
>& scopes
,
127 GaiaOAuthClient::Delegate
* delegate
) {
128 DCHECK_EQ(request_type_
, NO_PENDING_REQUEST
);
129 request_type_
= REFRESH_TOKEN
;
130 std::string post_body
=
131 "refresh_token=" + net::EscapeUrlEncodedData(refresh_token
, true) +
132 "&client_id=" + net::EscapeUrlEncodedData(oauth_client_info
.client_id
,
135 net::EscapeUrlEncodedData(oauth_client_info
.client_secret
, true) +
136 "&grant_type=refresh_token";
138 if (!scopes
.empty()) {
139 std::string scopes_string
= JoinString(scopes
, ' ');
140 post_body
+= "&scope=" + net::EscapeUrlEncodedData(scopes_string
, true);
143 MakeGaiaRequest(GURL(GaiaUrls::GetInstance()->oauth2_token_url()),
144 post_body
, max_retries
, delegate
);
147 void GaiaOAuthClient::Core::GetUserEmail(const std::string
& oauth_access_token
,
149 Delegate
* delegate
) {
150 GetUserInfoImpl(USER_EMAIL
, oauth_access_token
, max_retries
, delegate
);
153 void GaiaOAuthClient::Core::GetUserId(const std::string
& oauth_access_token
,
155 Delegate
* delegate
) {
156 GetUserInfoImpl(USER_ID
, oauth_access_token
, max_retries
, delegate
);
159 void GaiaOAuthClient::Core::GetUserInfo(const std::string
& oauth_access_token
,
161 Delegate
* delegate
) {
162 GetUserInfoImpl(USER_INFO
, oauth_access_token
, max_retries
, delegate
);
165 void GaiaOAuthClient::Core::GetUserInfoImpl(
167 const std::string
& oauth_access_token
,
169 Delegate
* delegate
) {
170 DCHECK_EQ(request_type_
, NO_PENDING_REQUEST
);
171 DCHECK(!request_
.get());
172 request_type_
= type
;
173 delegate_
= delegate
;
175 request_
.reset(net::URLFetcher::Create(
176 kUrlFetcherId
, GURL(GaiaUrls::GetInstance()->oauth_user_info_url()),
177 net::URLFetcher::GET
, this));
178 request_
->SetRequestContext(request_context_getter_
.get());
179 request_
->AddExtraRequestHeader("Authorization: OAuth " + oauth_access_token
);
180 request_
->SetMaxRetriesOn5xx(max_retries
);
181 request_
->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES
|
182 net::LOAD_DO_NOT_SAVE_COOKIES
);
184 // Fetchers are sometimes cancelled because a network change was detected,
185 // especially at startup and after sign-in on ChromeOS. Retrying once should
186 // be enough in those cases; let the fetcher retry up to 3 times just in case.
187 // http://crbug.com/163710
188 request_
->SetAutomaticallyRetryOnNetworkChanges(3);
192 void GaiaOAuthClient::Core::GetTokenInfo(const std::string
& oauth_access_token
,
194 Delegate
* delegate
) {
195 DCHECK_EQ(request_type_
, NO_PENDING_REQUEST
);
196 DCHECK(!request_
.get());
197 request_type_
= TOKEN_INFO
;
198 std::string post_body
=
199 "access_token=" + net::EscapeUrlEncodedData(oauth_access_token
, true);
200 MakeGaiaRequest(GURL(GaiaUrls::GetInstance()->oauth2_token_info_url()),
206 void GaiaOAuthClient::Core::MakeGaiaRequest(
208 const std::string
& post_body
,
210 GaiaOAuthClient::Delegate
* delegate
) {
211 DCHECK(!request_
.get()) << "Tried to fetch two things at once!";
212 delegate_
= delegate
;
214 request_
.reset(net::URLFetcher::Create(
215 kUrlFetcherId
, url
, net::URLFetcher::POST
, this));
216 request_
->SetRequestContext(request_context_getter_
.get());
217 request_
->SetUploadData("application/x-www-form-urlencoded", post_body
);
218 request_
->SetMaxRetriesOn5xx(max_retries
);
219 request_
->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES
|
220 net::LOAD_DO_NOT_SAVE_COOKIES
);
221 // See comment on SetAutomaticallyRetryOnNetworkChanges() above.
222 request_
->SetAutomaticallyRetryOnNetworkChanges(3);
226 // URLFetcher::Delegate implementation.
227 void GaiaOAuthClient::Core::OnURLFetchComplete(
228 const net::URLFetcher
* source
) {
229 bool should_retry
= false;
230 HandleResponse(source
, &should_retry
);
232 // Explicitly call ReceivedContentWasMalformed() to ensure the current
233 // request gets counted as a failure for calculation of the back-off
234 // period. If it was already a failure by status code, this call will
236 request_
->ReceivedContentWasMalformed();
238 // We must set our request_context_getter_ again because
239 // URLFetcher::Core::RetryOrCompleteUrlFetch resets it to NULL...
240 request_
->SetRequestContext(request_context_getter_
.get());
245 void GaiaOAuthClient::Core::HandleResponse(
246 const net::URLFetcher
* source
,
247 bool* should_retry_request
) {
248 // Move ownership of the request fetcher into a local scoped_ptr which
249 // will be nuked when we're done handling the request, unless we need
250 // to retry, in which case ownership will be returned to request_.
251 scoped_ptr
<net::URLFetcher
> old_request
= request_
.Pass();
252 DCHECK_EQ(source
, old_request
.get());
254 // HTTP_BAD_REQUEST means the arguments are invalid. HTTP_UNAUTHORIZED means
255 // the access or refresh token is invalid. No point retrying. We are
257 int response_code
= source
->GetResponseCode();
258 if (response_code
== net::HTTP_BAD_REQUEST
||
259 response_code
== net::HTTP_UNAUTHORIZED
) {
260 delegate_
->OnOAuthError();
264 scoped_ptr
<base::DictionaryValue
> response_dict
;
265 if (source
->GetResponseCode() == net::HTTP_OK
) {
267 source
->GetResponseAsString(&data
);
268 scoped_ptr
<base::Value
> message_value(base::JSONReader::Read(data
));
269 if (message_value
.get() &&
270 message_value
->IsType(base::Value::TYPE_DICTIONARY
)) {
272 static_cast<base::DictionaryValue
*>(message_value
.release()));
276 if (!response_dict
.get()) {
277 // If we don't have an access token yet and the the error was not
278 // RC_BAD_REQUEST, we may need to retry.
279 if ((source
->GetMaxRetriesOn5xx() != -1) &&
280 (num_retries_
>= source
->GetMaxRetriesOn5xx())) {
281 // Retry limit reached. Give up.
282 delegate_
->OnNetworkError(source
->GetResponseCode());
284 request_
= old_request
.Pass();
285 *should_retry_request
= true;
290 RequestType type
= request_type_
;
291 request_type_
= NO_PENDING_REQUEST
;
296 response_dict
->GetString("email", &email
);
297 delegate_
->OnGetUserEmailResponse(email
);
303 response_dict
->GetString("id", &id
);
304 delegate_
->OnGetUserIdResponse(id
);
309 delegate_
->OnGetUserInfoResponse(response_dict
.Pass());
314 delegate_
->OnGetTokenInfoResponse(response_dict
.Pass());
318 case TOKENS_FROM_AUTH_CODE
:
319 case REFRESH_TOKEN
: {
320 std::string access_token
;
321 std::string refresh_token
;
322 int expires_in_seconds
= 0;
323 response_dict
->GetString(kAccessTokenValue
, &access_token
);
324 response_dict
->GetString(kRefreshTokenValue
, &refresh_token
);
325 response_dict
->GetInteger(kExpiresInValue
, &expires_in_seconds
);
327 if (access_token
.empty()) {
328 delegate_
->OnOAuthError();
332 if (type
== REFRESH_TOKEN
) {
333 delegate_
->OnRefreshTokenResponse(access_token
, expires_in_seconds
);
335 delegate_
->OnGetTokensResponse(refresh_token
,
347 GaiaOAuthClient::GaiaOAuthClient(net::URLRequestContextGetter
* context_getter
) {
348 core_
= new Core(context_getter
);
351 GaiaOAuthClient::~GaiaOAuthClient() {
354 void GaiaOAuthClient::GetTokensFromAuthCode(
355 const OAuthClientInfo
& oauth_client_info
,
356 const std::string
& auth_code
,
358 Delegate
* delegate
) {
359 return core_
->GetTokensFromAuthCode(oauth_client_info
,
365 void GaiaOAuthClient::RefreshToken(
366 const OAuthClientInfo
& oauth_client_info
,
367 const std::string
& refresh_token
,
368 const std::vector
<std::string
>& scopes
,
370 Delegate
* delegate
) {
371 return core_
->RefreshToken(oauth_client_info
,
378 void GaiaOAuthClient::GetUserEmail(const std::string
& access_token
,
380 Delegate
* delegate
) {
381 return core_
->GetUserEmail(access_token
, max_retries
, delegate
);
384 void GaiaOAuthClient::GetUserId(const std::string
& access_token
,
386 Delegate
* delegate
) {
387 return core_
->GetUserId(access_token
, max_retries
, delegate
);
390 void GaiaOAuthClient::GetUserInfo(const std::string
& access_token
,
392 Delegate
* delegate
) {
393 return core_
->GetUserInfo(access_token
, max_retries
, delegate
);
396 void GaiaOAuthClient::GetTokenInfo(const std::string
& access_token
,
398 Delegate
* delegate
) {
399 return core_
->GetTokenInfo(access_token
, max_retries
, delegate
);