Fix experimental app list search box disappearing on profile switch.
[chromium-blink-merge.git] / google_apis / gaia / gaia_oauth_client.cc
blob56647f465dbe5b7457230c53d98e63f8285447e9
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"
19 #include "url/gurl.h"
21 namespace {
22 const char kAccessTokenValue[] = "access_token";
23 const char kRefreshTokenValue[] = "refresh_token";
24 const char kExpiresInValue[] = "expires_in";
27 namespace gaia {
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 {
36 public:
37 Core(net::URLRequestContextGetter* request_context_getter)
38 : num_retries_(0),
39 request_context_getter_(request_context_getter),
40 delegate_(NULL),
41 request_type_(NO_PENDING_REQUEST) {
44 void GetTokensFromAuthCode(const OAuthClientInfo& oauth_client_info,
45 const std::string& auth_code,
46 int max_retries,
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,
51 int max_retries,
52 GaiaOAuthClient::Delegate* delegate);
53 void GetUserEmail(const std::string& oauth_access_token,
54 int max_retries,
55 Delegate* delegate);
56 void GetUserId(const std::string& oauth_access_token,
57 int max_retries,
58 Delegate* delegate);
59 void GetUserInfo(const std::string& oauth_access_token,
60 int max_retries,
61 Delegate* delegate);
62 void GetTokenInfo(const std::string& oauth_access_token,
63 int max_retries,
64 Delegate* delegate);
66 // net::URLFetcherDelegate implementation.
67 void OnURLFetchComplete(const net::URLFetcher* source) override;
69 private:
70 friend class base::RefCountedThreadSafe<Core>;
72 enum RequestType {
73 NO_PENDING_REQUEST,
74 TOKENS_FROM_AUTH_CODE,
75 REFRESH_TOKEN,
76 TOKEN_INFO,
77 USER_EMAIL,
78 USER_ID,
79 USER_INFO,
82 ~Core() override {}
84 void GetUserInfoImpl(RequestType type,
85 const std::string& oauth_access_token,
86 int max_retries,
87 Delegate* delegate);
88 void MakeGaiaRequest(const GURL& url,
89 const std::string& post_body,
90 int max_retries,
91 GaiaOAuthClient::Delegate* delegate);
92 void HandleResponse(const net::URLFetcher* source,
93 bool* should_retry_request);
95 int num_retries_;
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,
105 int max_retries,
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,
112 true) +
113 "&client_secret=" +
114 net::EscapeUrlEncodedData(oauth_client_info.client_secret, true) +
115 "&redirect_uri=" +
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,
126 int max_retries,
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,
133 true) +
134 "&client_secret=" +
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,
148 int max_retries,
149 Delegate* delegate) {
150 GetUserInfoImpl(USER_EMAIL, oauth_access_token, max_retries, delegate);
153 void GaiaOAuthClient::Core::GetUserId(const std::string& oauth_access_token,
154 int max_retries,
155 Delegate* delegate) {
156 GetUserInfoImpl(USER_ID, oauth_access_token, max_retries, delegate);
159 void GaiaOAuthClient::Core::GetUserInfo(const std::string& oauth_access_token,
160 int max_retries,
161 Delegate* delegate) {
162 GetUserInfoImpl(USER_INFO, oauth_access_token, max_retries, delegate);
165 void GaiaOAuthClient::Core::GetUserInfoImpl(
166 RequestType type,
167 const std::string& oauth_access_token,
168 int max_retries,
169 Delegate* delegate) {
170 DCHECK_EQ(request_type_, NO_PENDING_REQUEST);
171 DCHECK(!request_.get());
172 request_type_ = type;
173 delegate_ = delegate;
174 num_retries_ = 0;
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);
189 request_->Start();
192 void GaiaOAuthClient::Core::GetTokenInfo(const std::string& oauth_access_token,
193 int max_retries,
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()),
201 post_body,
202 max_retries,
203 delegate);
206 void GaiaOAuthClient::Core::MakeGaiaRequest(
207 const GURL& url,
208 const std::string& post_body,
209 int max_retries,
210 GaiaOAuthClient::Delegate* delegate) {
211 DCHECK(!request_.get()) << "Tried to fetch two things at once!";
212 delegate_ = delegate;
213 num_retries_ = 0;
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);
223 request_->Start();
226 // URLFetcher::Delegate implementation.
227 void GaiaOAuthClient::Core::OnURLFetchComplete(
228 const net::URLFetcher* source) {
229 bool should_retry = false;
230 HandleResponse(source, &should_retry);
231 if (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
235 // be ignored.
236 request_->ReceivedContentWasMalformed();
237 num_retries_++;
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());
241 request_->Start();
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
256 // done here.
257 int response_code = source->GetResponseCode();
258 if (response_code == net::HTTP_BAD_REQUEST ||
259 response_code == net::HTTP_UNAUTHORIZED) {
260 delegate_->OnOAuthError();
261 return;
264 scoped_ptr<base::DictionaryValue> response_dict;
265 if (source->GetResponseCode() == net::HTTP_OK) {
266 std::string data;
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)) {
271 response_dict.reset(
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());
283 } else {
284 request_ = old_request.Pass();
285 *should_retry_request = true;
287 return;
290 RequestType type = request_type_;
291 request_type_ = NO_PENDING_REQUEST;
293 switch (type) {
294 case USER_EMAIL: {
295 std::string email;
296 response_dict->GetString("email", &email);
297 delegate_->OnGetUserEmailResponse(email);
298 break;
301 case USER_ID: {
302 std::string id;
303 response_dict->GetString("id", &id);
304 delegate_->OnGetUserIdResponse(id);
305 break;
308 case USER_INFO: {
309 delegate_->OnGetUserInfoResponse(response_dict.Pass());
310 break;
313 case TOKEN_INFO: {
314 delegate_->OnGetTokenInfoResponse(response_dict.Pass());
315 break;
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();
329 return;
332 if (type == REFRESH_TOKEN) {
333 delegate_->OnRefreshTokenResponse(access_token, expires_in_seconds);
334 } else {
335 delegate_->OnGetTokensResponse(refresh_token,
336 access_token,
337 expires_in_seconds);
339 break;
342 default:
343 NOTREACHED();
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,
357 int max_retries,
358 Delegate* delegate) {
359 return core_->GetTokensFromAuthCode(oauth_client_info,
360 auth_code,
361 max_retries,
362 delegate);
365 void GaiaOAuthClient::RefreshToken(
366 const OAuthClientInfo& oauth_client_info,
367 const std::string& refresh_token,
368 const std::vector<std::string>& scopes,
369 int max_retries,
370 Delegate* delegate) {
371 return core_->RefreshToken(oauth_client_info,
372 refresh_token,
373 scopes,
374 max_retries,
375 delegate);
378 void GaiaOAuthClient::GetUserEmail(const std::string& access_token,
379 int max_retries,
380 Delegate* delegate) {
381 return core_->GetUserEmail(access_token, max_retries, delegate);
384 void GaiaOAuthClient::GetUserId(const std::string& access_token,
385 int max_retries,
386 Delegate* delegate) {
387 return core_->GetUserId(access_token, max_retries, delegate);
390 void GaiaOAuthClient::GetUserInfo(const std::string& access_token,
391 int max_retries,
392 Delegate* delegate) {
393 return core_->GetUserInfo(access_token, max_retries, delegate);
396 void GaiaOAuthClient::GetTokenInfo(const std::string& access_token,
397 int max_retries,
398 Delegate* delegate) {
399 return core_->GetTokenInfo(access_token, max_retries, delegate);
402 } // namespace gaia