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 GetTokenInfo(const std::string
& oauth_access_token
,
63 // net::URLFetcherDelegate implementation.
64 virtual void OnURLFetchComplete(const net::URLFetcher
* source
) OVERRIDE
;
67 friend class base::RefCountedThreadSafe
<Core
>;
71 TOKENS_FROM_AUTH_CODE
,
80 void GetUserInfo(const std::string
& oauth_access_token
,
83 void MakeGaiaRequest(const GURL
& url
,
84 const std::string
& post_body
,
86 GaiaOAuthClient::Delegate
* delegate
);
87 void HandleResponse(const net::URLFetcher
* source
,
88 bool* should_retry_request
);
91 scoped_refptr
<net::URLRequestContextGetter
> request_context_getter_
;
92 GaiaOAuthClient::Delegate
* delegate_
;
93 scoped_ptr
<net::URLFetcher
> request_
;
94 RequestType request_type_
;
97 void GaiaOAuthClient::Core::GetTokensFromAuthCode(
98 const OAuthClientInfo
& oauth_client_info
,
99 const std::string
& auth_code
,
101 GaiaOAuthClient::Delegate
* delegate
) {
102 DCHECK_EQ(request_type_
, NO_PENDING_REQUEST
);
103 request_type_
= TOKENS_FROM_AUTH_CODE
;
104 std::string post_body
=
105 "code=" + net::EscapeUrlEncodedData(auth_code
, true) +
106 "&client_id=" + net::EscapeUrlEncodedData(oauth_client_info
.client_id
,
109 net::EscapeUrlEncodedData(oauth_client_info
.client_secret
, true) +
111 net::EscapeUrlEncodedData(oauth_client_info
.redirect_uri
, true) +
112 "&grant_type=authorization_code";
113 MakeGaiaRequest(GURL(GaiaUrls::GetInstance()->oauth2_token_url()),
114 post_body
, max_retries
, delegate
);
117 void GaiaOAuthClient::Core::RefreshToken(
118 const OAuthClientInfo
& oauth_client_info
,
119 const std::string
& refresh_token
,
120 const std::vector
<std::string
>& scopes
,
122 GaiaOAuthClient::Delegate
* delegate
) {
123 DCHECK_EQ(request_type_
, NO_PENDING_REQUEST
);
124 request_type_
= REFRESH_TOKEN
;
125 std::string post_body
=
126 "refresh_token=" + net::EscapeUrlEncodedData(refresh_token
, true) +
127 "&client_id=" + net::EscapeUrlEncodedData(oauth_client_info
.client_id
,
130 net::EscapeUrlEncodedData(oauth_client_info
.client_secret
, true) +
131 "&grant_type=refresh_token";
133 if (!scopes
.empty()) {
134 std::string scopes_string
= JoinString(scopes
, ' ');
135 post_body
+= "&scope=" + net::EscapeUrlEncodedData(scopes_string
, true);
138 MakeGaiaRequest(GURL(GaiaUrls::GetInstance()->oauth2_token_url()),
139 post_body
, max_retries
, delegate
);
142 void GaiaOAuthClient::Core::GetUserEmail(const std::string
& oauth_access_token
,
144 Delegate
* delegate
) {
145 DCHECK_EQ(request_type_
, NO_PENDING_REQUEST
);
146 DCHECK(!request_
.get());
147 request_type_
= USER_EMAIL
;
148 GetUserInfo(oauth_access_token
, max_retries
, delegate
);
151 void GaiaOAuthClient::Core::GetUserId(const std::string
& oauth_access_token
,
153 Delegate
* delegate
) {
154 DCHECK_EQ(request_type_
, NO_PENDING_REQUEST
);
155 DCHECK(!request_
.get());
156 request_type_
= USER_ID
;
157 GetUserInfo(oauth_access_token
, max_retries
, delegate
);
160 void GaiaOAuthClient::Core::GetUserInfo(const std::string
& oauth_access_token
,
162 Delegate
* delegate
) {
163 delegate_
= delegate
;
165 request_
.reset(net::URLFetcher::Create(
166 kUrlFetcherId
, GURL(GaiaUrls::GetInstance()->oauth_user_info_url()),
167 net::URLFetcher::GET
, this));
168 request_
->SetRequestContext(request_context_getter_
.get());
169 request_
->AddExtraRequestHeader("Authorization: OAuth " + oauth_access_token
);
170 request_
->SetMaxRetriesOn5xx(max_retries
);
171 request_
->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES
|
172 net::LOAD_DO_NOT_SAVE_COOKIES
);
174 // Fetchers are sometimes cancelled because a network change was detected,
175 // especially at startup and after sign-in on ChromeOS. Retrying once should
176 // be enough in those cases; let the fetcher retry up to 3 times just in case.
177 // http://crbug.com/163710
178 request_
->SetAutomaticallyRetryOnNetworkChanges(3);
182 void GaiaOAuthClient::Core::GetTokenInfo(const std::string
& oauth_access_token
,
184 Delegate
* delegate
) {
185 DCHECK_EQ(request_type_
, NO_PENDING_REQUEST
);
186 DCHECK(!request_
.get());
187 request_type_
= TOKEN_INFO
;
188 std::string post_body
=
189 "access_token=" + net::EscapeUrlEncodedData(oauth_access_token
, true);
190 MakeGaiaRequest(GURL(GaiaUrls::GetInstance()->oauth2_token_info_url()),
196 void GaiaOAuthClient::Core::MakeGaiaRequest(
198 const std::string
& post_body
,
200 GaiaOAuthClient::Delegate
* delegate
) {
201 DCHECK(!request_
.get()) << "Tried to fetch two things at once!";
202 delegate_
= delegate
;
204 request_
.reset(net::URLFetcher::Create(
205 kUrlFetcherId
, url
, net::URLFetcher::POST
, this));
206 request_
->SetRequestContext(request_context_getter_
.get());
207 request_
->SetUploadData("application/x-www-form-urlencoded", post_body
);
208 request_
->SetMaxRetriesOn5xx(max_retries
);
209 request_
->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES
|
210 net::LOAD_DO_NOT_SAVE_COOKIES
);
211 // See comment on SetAutomaticallyRetryOnNetworkChanges() above.
212 request_
->SetAutomaticallyRetryOnNetworkChanges(3);
216 // URLFetcher::Delegate implementation.
217 void GaiaOAuthClient::Core::OnURLFetchComplete(
218 const net::URLFetcher
* source
) {
219 bool should_retry
= false;
220 HandleResponse(source
, &should_retry
);
222 // Explicitly call ReceivedContentWasMalformed() to ensure the current
223 // request gets counted as a failure for calculation of the back-off
224 // period. If it was already a failure by status code, this call will
226 request_
->ReceivedContentWasMalformed();
228 // We must set our request_context_getter_ again because
229 // URLFetcher::Core::RetryOrCompleteUrlFetch resets it to NULL...
230 request_
->SetRequestContext(request_context_getter_
.get());
235 void GaiaOAuthClient::Core::HandleResponse(
236 const net::URLFetcher
* source
,
237 bool* should_retry_request
) {
238 // Move ownership of the request fetcher into a local scoped_ptr which
239 // will be nuked when we're done handling the request, unless we need
240 // to retry, in which case ownership will be returned to request_.
241 scoped_ptr
<net::URLFetcher
> old_request
= request_
.Pass();
242 DCHECK_EQ(source
, old_request
.get());
244 // HTTP_BAD_REQUEST means the arguments are invalid. HTTP_UNAUTHORIZED means
245 // the access or refresh token is invalid. No point retrying. We are
247 int response_code
= source
->GetResponseCode();
248 if (response_code
== net::HTTP_BAD_REQUEST
||
249 response_code
== net::HTTP_UNAUTHORIZED
) {
250 delegate_
->OnOAuthError();
254 scoped_ptr
<base::DictionaryValue
> response_dict
;
255 if (source
->GetResponseCode() == net::HTTP_OK
) {
257 source
->GetResponseAsString(&data
);
258 scoped_ptr
<base::Value
> message_value(base::JSONReader::Read(data
));
259 if (message_value
.get() &&
260 message_value
->IsType(base::Value::TYPE_DICTIONARY
)) {
262 static_cast<base::DictionaryValue
*>(message_value
.release()));
266 if (!response_dict
.get()) {
267 // If we don't have an access token yet and the the error was not
268 // RC_BAD_REQUEST, we may need to retry.
269 if ((source
->GetMaxRetriesOn5xx() != -1) &&
270 (num_retries_
>= source
->GetMaxRetriesOn5xx())) {
271 // Retry limit reached. Give up.
272 delegate_
->OnNetworkError(source
->GetResponseCode());
274 request_
= old_request
.Pass();
275 *should_retry_request
= true;
280 RequestType type
= request_type_
;
281 request_type_
= NO_PENDING_REQUEST
;
286 response_dict
->GetString("email", &email
);
287 delegate_
->OnGetUserEmailResponse(email
);
293 response_dict
->GetString("id", &id
);
294 delegate_
->OnGetUserIdResponse(id
);
299 delegate_
->OnGetTokenInfoResponse(response_dict
.Pass());
303 case TOKENS_FROM_AUTH_CODE
:
304 case REFRESH_TOKEN
: {
305 std::string access_token
;
306 std::string refresh_token
;
307 int expires_in_seconds
= 0;
308 response_dict
->GetString(kAccessTokenValue
, &access_token
);
309 response_dict
->GetString(kRefreshTokenValue
, &refresh_token
);
310 response_dict
->GetInteger(kExpiresInValue
, &expires_in_seconds
);
312 if (access_token
.empty()) {
313 delegate_
->OnOAuthError();
317 if (type
== REFRESH_TOKEN
) {
318 delegate_
->OnRefreshTokenResponse(access_token
, expires_in_seconds
);
320 delegate_
->OnGetTokensResponse(refresh_token
,
332 GaiaOAuthClient::GaiaOAuthClient(net::URLRequestContextGetter
* context_getter
) {
333 core_
= new Core(context_getter
);
336 GaiaOAuthClient::~GaiaOAuthClient() {
339 void GaiaOAuthClient::GetTokensFromAuthCode(
340 const OAuthClientInfo
& oauth_client_info
,
341 const std::string
& auth_code
,
343 Delegate
* delegate
) {
344 return core_
->GetTokensFromAuthCode(oauth_client_info
,
350 void GaiaOAuthClient::RefreshToken(
351 const OAuthClientInfo
& oauth_client_info
,
352 const std::string
& refresh_token
,
353 const std::vector
<std::string
>& scopes
,
355 Delegate
* delegate
) {
356 return core_
->RefreshToken(oauth_client_info
,
363 void GaiaOAuthClient::GetUserEmail(const std::string
& access_token
,
365 Delegate
* delegate
) {
366 return core_
->GetUserEmail(access_token
, max_retries
, delegate
);
369 void GaiaOAuthClient::GetUserId(const std::string
& access_token
,
371 Delegate
* delegate
) {
372 return core_
->GetUserId(access_token
, max_retries
, delegate
);
375 void GaiaOAuthClient::GetTokenInfo(const std::string
& access_token
,
377 Delegate
* delegate
) {
378 return core_
->GetTokenInfo(access_token
, max_retries
, delegate
);