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/values.h"
11 #include "google_apis/gaia/gaia_urls.h"
12 #include "googleurl/src/gurl.h"
13 #include "net/base/escape.h"
14 #include "net/http/http_status_code.h"
15 #include "net/url_request/url_fetcher.h"
16 #include "net/url_request/url_fetcher_delegate.h"
17 #include "net/url_request/url_request_context_getter.h"
20 const char kAccessTokenValue
[] = "access_token";
21 const char kRefreshTokenValue
[] = "refresh_token";
22 const char kExpiresInValue
[] = "expires_in";
27 class GaiaOAuthClient::Core
28 : public base::RefCountedThreadSafe
<GaiaOAuthClient::Core
>,
29 public net::URLFetcherDelegate
{
31 Core(const std::string
& gaia_url
,
32 net::URLRequestContextGetter
* request_context_getter
)
33 : gaia_url_(gaia_url
),
35 request_context_getter_(request_context_getter
),
37 request_type_(NO_PENDING_REQUEST
) {
40 void GetTokensFromAuthCode(const OAuthClientInfo
& oauth_client_info
,
41 const std::string
& auth_code
,
43 GaiaOAuthClient::Delegate
* delegate
);
44 void RefreshToken(const OAuthClientInfo
& oauth_client_info
,
45 const std::string
& refresh_token
,
47 GaiaOAuthClient::Delegate
* delegate
);
48 void GetUserInfo(const std::string
& oauth_access_token
,
52 // net::URLFetcherDelegate implementation.
53 virtual void OnURLFetchComplete(const net::URLFetcher
* source
) OVERRIDE
;
56 friend class base::RefCountedThreadSafe
<Core
>;
60 TOKENS_FROM_AUTH_CODE
,
67 void MakeGaiaRequest(const std::string
& post_body
,
69 GaiaOAuthClient::Delegate
* delegate
);
70 void HandleResponse(const net::URLFetcher
* source
,
71 bool* should_retry_request
);
75 scoped_refptr
<net::URLRequestContextGetter
> request_context_getter_
;
76 GaiaOAuthClient::Delegate
* delegate_
;
77 scoped_ptr
<net::URLFetcher
> request_
;
78 RequestType request_type_
;
81 void GaiaOAuthClient::Core::GetTokensFromAuthCode(
82 const OAuthClientInfo
& oauth_client_info
,
83 const std::string
& auth_code
,
85 GaiaOAuthClient::Delegate
* delegate
) {
86 DCHECK_EQ(request_type_
, NO_PENDING_REQUEST
);
87 request_type_
= TOKENS_FROM_AUTH_CODE
;
88 std::string post_body
=
89 "code=" + net::EscapeUrlEncodedData(auth_code
, true) +
90 "&client_id=" + net::EscapeUrlEncodedData(oauth_client_info
.client_id
,
93 net::EscapeUrlEncodedData(oauth_client_info
.client_secret
, true) +
95 net::EscapeUrlEncodedData(oauth_client_info
.redirect_uri
, true) +
96 "&grant_type=authorization_code";
97 MakeGaiaRequest(post_body
, max_retries
, delegate
);
100 void GaiaOAuthClient::Core::RefreshToken(
101 const OAuthClientInfo
& oauth_client_info
,
102 const std::string
& refresh_token
,
104 GaiaOAuthClient::Delegate
* delegate
) {
105 DCHECK_EQ(request_type_
, NO_PENDING_REQUEST
);
106 request_type_
= REFRESH_TOKEN
;
107 std::string post_body
=
108 "refresh_token=" + net::EscapeUrlEncodedData(refresh_token
, true) +
109 "&client_id=" + net::EscapeUrlEncodedData(oauth_client_info
.client_id
,
112 net::EscapeUrlEncodedData(oauth_client_info
.client_secret
, true) +
113 "&grant_type=refresh_token";
114 MakeGaiaRequest(post_body
, max_retries
, delegate
);
117 void GaiaOAuthClient::Core::GetUserInfo(const std::string
& oauth_access_token
,
119 Delegate
* delegate
) {
120 DCHECK_EQ(request_type_
, NO_PENDING_REQUEST
);
121 DCHECK(!request_
.get());
122 request_type_
= USER_INFO
;
123 delegate_
= delegate
;
125 request_
.reset(net::URLFetcher::Create(
126 0, GURL(GaiaUrls::GetInstance()->oauth_user_info_url()),
127 net::URLFetcher::GET
, this));
128 request_
->SetRequestContext(request_context_getter_
);
129 request_
->AddExtraRequestHeader(
130 "Authorization: OAuth " + oauth_access_token
);
131 request_
->SetMaxRetriesOn5xx(max_retries
);
132 // Fetchers are sometimes cancelled because a network change was detected,
133 // especially at startup and after sign-in on ChromeOS. Retrying once should
134 // be enough in those cases; let the fetcher retry up to 3 times just in case.
135 // http://crbug.com/163710
136 request_
->SetAutomaticallyRetryOnNetworkChanges(3);
140 void GaiaOAuthClient::Core::MakeGaiaRequest(
141 const std::string
& post_body
,
143 GaiaOAuthClient::Delegate
* delegate
) {
144 DCHECK(!request_
.get()) << "Tried to fetch two things at once!";
145 delegate_
= delegate
;
147 request_
.reset(net::URLFetcher::Create(
148 0, gaia_url_
, net::URLFetcher::POST
, this));
149 request_
->SetRequestContext(request_context_getter_
);
150 request_
->SetUploadData("application/x-www-form-urlencoded", post_body
);
151 request_
->SetMaxRetriesOn5xx(max_retries
);
152 // See comment on SetAutomaticallyRetryOnNetworkChanges() above.
153 request_
->SetAutomaticallyRetryOnNetworkChanges(3);
157 // URLFetcher::Delegate implementation.
158 void GaiaOAuthClient::Core::OnURLFetchComplete(
159 const net::URLFetcher
* source
) {
160 bool should_retry
= false;
161 HandleResponse(source
, &should_retry
);
163 // Explicitly call ReceivedContentWasMalformed() to ensure the current
164 // request gets counted as a failure for calculation of the back-off
165 // period. If it was already a failure by status code, this call will
167 request_
->ReceivedContentWasMalformed();
169 // We must set our request_context_getter_ again because
170 // URLFetcher::Core::RetryOrCompleteUrlFetch resets it to NULL...
171 request_
->SetRequestContext(request_context_getter_
);
176 void GaiaOAuthClient::Core::HandleResponse(
177 const net::URLFetcher
* source
,
178 bool* should_retry_request
) {
179 // Keep the URLFetcher object in case we need to reuse it.
180 scoped_ptr
<net::URLFetcher
> old_request
= request_
.Pass();
181 DCHECK_EQ(source
, old_request
.get());
183 // RC_BAD_REQUEST means the arguments are invalid. No point retrying. We are
185 if (source
->GetResponseCode() == net::HTTP_BAD_REQUEST
) {
186 delegate_
->OnOAuthError();
190 scoped_ptr
<DictionaryValue
> response_dict
;
191 if (source
->GetResponseCode() == net::HTTP_OK
) {
193 source
->GetResponseAsString(&data
);
194 scoped_ptr
<Value
> message_value(base::JSONReader::Read(data
));
195 if (message_value
.get() &&
196 message_value
->IsType(Value::TYPE_DICTIONARY
)) {
198 static_cast<DictionaryValue
*>(message_value
.release()));
202 if (!response_dict
.get()) {
203 // If we don't have an access token yet and the the error was not
204 // RC_BAD_REQUEST, we may need to retry.
205 if ((source
->GetMaxRetriesOn5xx() != -1) &&
206 (num_retries_
> source
->GetMaxRetriesOn5xx())) {
207 // Retry limit reached. Give up.
208 delegate_
->OnNetworkError(source
->GetResponseCode());
210 request_
= old_request
.Pass();
211 *should_retry_request
= true;
216 RequestType type
= request_type_
;
217 request_type_
= NO_PENDING_REQUEST
;
222 response_dict
->GetString("email", &email
);
223 delegate_
->OnGetUserInfoResponse(email
);
227 case TOKENS_FROM_AUTH_CODE
:
228 case REFRESH_TOKEN
: {
229 std::string access_token
;
230 std::string refresh_token
;
231 int expires_in_seconds
= 0;
232 response_dict
->GetString(kAccessTokenValue
, &access_token
);
233 response_dict
->GetString(kRefreshTokenValue
, &refresh_token
);
234 response_dict
->GetInteger(kExpiresInValue
, &expires_in_seconds
);
236 if (access_token
.empty()) {
237 delegate_
->OnOAuthError();
241 if (type
== REFRESH_TOKEN
) {
242 delegate_
->OnRefreshTokenResponse(access_token
, expires_in_seconds
);
244 delegate_
->OnGetTokensResponse(refresh_token
,
256 GaiaOAuthClient::GaiaOAuthClient(const std::string
& gaia_url
,
257 net::URLRequestContextGetter
* context_getter
) {
258 core_
= new Core(gaia_url
, context_getter
);
261 GaiaOAuthClient::~GaiaOAuthClient() {
264 void GaiaOAuthClient::GetTokensFromAuthCode(
265 const OAuthClientInfo
& oauth_client_info
,
266 const std::string
& auth_code
,
268 Delegate
* delegate
) {
269 return core_
->GetTokensFromAuthCode(oauth_client_info
,
275 void GaiaOAuthClient::RefreshToken(const OAuthClientInfo
& oauth_client_info
,
276 const std::string
& refresh_token
,
278 Delegate
* delegate
) {
279 return core_
->RefreshToken(oauth_client_info
,
285 void GaiaOAuthClient::GetUserInfo(const std::string
& access_token
,
287 Delegate
* delegate
) {
288 return core_
->GetUserInfo(access_token
, max_retries
, delegate
);