gaia_auth_fetcher.cc revision a1401311d1ab56c4ed0a474bd38c108f75cb0cd9
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.
4
5#include "google_apis/gaia/gaia_auth_fetcher.h"
6
7#include <string>
8#include <utility>
9#include <vector>
10
11#include "base/json/json_reader.h"
12#include "base/json/json_writer.h"
13#include "base/strings/string_split.h"
14#include "base/strings/string_util.h"
15#include "base/strings/stringprintf.h"
16#include "base/values.h"
17#include "google_apis/gaia/gaia_auth_consumer.h"
18#include "google_apis/gaia/gaia_constants.h"
19#include "google_apis/gaia/gaia_urls.h"
20#include "google_apis/gaia/google_service_auth_error.h"
21#include "net/base/escape.h"
22#include "net/base/load_flags.h"
23#include "net/http/http_response_headers.h"
24#include "net/http/http_status_code.h"
25#include "net/url_request/url_fetcher.h"
26#include "net/url_request/url_request_context_getter.h"
27#include "net/url_request/url_request_status.h"
28
29namespace {
30const int kLoadFlagsIgnoreCookies = net::LOAD_DO_NOT_SEND_COOKIES |
31                                    net::LOAD_DO_NOT_SAVE_COOKIES;
32
33static bool CookiePartsContains(const std::vector<std::string>& parts,
34                                const char* part) {
35  for (std::vector<std::string>::const_iterator it = parts.begin();
36       it != parts.end(); ++it) {
37    if (LowerCaseEqualsASCII(*it, part))
38      return true;
39  }
40  return false;
41}
42
43bool ExtractOAuth2TokenPairResponse(base::DictionaryValue* dict,
44                                    std::string* refresh_token,
45                                    std::string* access_token,
46                                    int* expires_in_secs) {
47  DCHECK(refresh_token);
48  DCHECK(access_token);
49  DCHECK(expires_in_secs);
50
51  if (!dict->GetStringWithoutPathExpansion("refresh_token", refresh_token) ||
52      !dict->GetStringWithoutPathExpansion("access_token", access_token) ||
53      !dict->GetIntegerWithoutPathExpansion("expires_in", expires_in_secs)) {
54    return false;
55  }
56
57  return true;
58}
59
60}  // namespace
61
62// TODO(chron): Add sourceless version of this formatter.
63// static
64const char GaiaAuthFetcher::kClientLoginFormat[] =
65    "Email=%s&"
66    "Passwd=%s&"
67    "PersistentCookie=%s&"
68    "accountType=%s&"
69    "source=%s&"
70    "service=%s";
71// static
72const char GaiaAuthFetcher::kClientLoginCaptchaFormat[] =
73    "Email=%s&"
74    "Passwd=%s&"
75    "PersistentCookie=%s&"
76    "accountType=%s&"
77    "source=%s&"
78    "service=%s&"
79    "logintoken=%s&"
80    "logincaptcha=%s";
81// static
82const char GaiaAuthFetcher::kIssueAuthTokenFormat[] =
83    "SID=%s&"
84    "LSID=%s&"
85    "service=%s&"
86    "Session=%s";
87// static
88const char GaiaAuthFetcher::kClientLoginToOAuth2BodyFormat[] =
89    "scope=%s&client_id=%s";
90// static
91const char GaiaAuthFetcher::kOAuth2CodeToTokenPairBodyFormat[] =
92    "scope=%s&"
93    "grant_type=authorization_code&"
94    "client_id=%s&"
95    "client_secret=%s&"
96    "code=%s";
97// static
98const char GaiaAuthFetcher::kOAuth2RevokeTokenBodyFormat[] =
99    "token=%s";
100// static
101const char GaiaAuthFetcher::kGetUserInfoFormat[] =
102    "LSID=%s";
103// static
104const char GaiaAuthFetcher::kMergeSessionFormat[] =
105    "uberauth=%s&"
106    "continue=%s&"
107    "source=%s";
108// static
109const char GaiaAuthFetcher::kUberAuthTokenURLFormat[] =
110    "?source=%s&"
111    "issueuberauth=1";
112
113const char GaiaAuthFetcher::kOAuthLoginFormat[] = "service=%s&source=%s";
114
115// static
116const char GaiaAuthFetcher::kAccountDeletedError[] = "AccountDeleted";
117const char GaiaAuthFetcher::kAccountDeletedErrorCode[] = "adel";
118// static
119const char GaiaAuthFetcher::kAccountDisabledError[] = "AccountDisabled";
120const char GaiaAuthFetcher::kAccountDisabledErrorCode[] = "adis";
121// static
122const char GaiaAuthFetcher::kBadAuthenticationError[] = "BadAuthentication";
123const char GaiaAuthFetcher::kBadAuthenticationErrorCode[] = "badauth";
124// static
125const char GaiaAuthFetcher::kCaptchaError[] = "CaptchaRequired";
126const char GaiaAuthFetcher::kCaptchaErrorCode[] = "cr";
127// static
128const char GaiaAuthFetcher::kServiceUnavailableError[] =
129    "ServiceUnavailable";
130const char GaiaAuthFetcher::kServiceUnavailableErrorCode[] =
131    "ire";
132// static
133const char GaiaAuthFetcher::kErrorParam[] = "Error";
134// static
135const char GaiaAuthFetcher::kErrorUrlParam[] = "Url";
136// static
137const char GaiaAuthFetcher::kCaptchaUrlParam[] = "CaptchaUrl";
138// static
139const char GaiaAuthFetcher::kCaptchaTokenParam[] = "CaptchaToken";
140
141// static
142const char GaiaAuthFetcher::kCookiePersistence[] = "true";
143// static
144// TODO(johnnyg): When hosted accounts are supported by sync,
145// we can always use "HOSTED_OR_GOOGLE"
146const char GaiaAuthFetcher::kAccountTypeHostedOrGoogle[] =
147    "HOSTED_OR_GOOGLE";
148const char GaiaAuthFetcher::kAccountTypeGoogle[] =
149    "GOOGLE";
150
151// static
152const char GaiaAuthFetcher::kSecondFactor[] = "Info=InvalidSecondFactor";
153
154// static
155const char GaiaAuthFetcher::kAuthHeaderFormat[] =
156    "Authorization: GoogleLogin auth=%s";
157// static
158const char GaiaAuthFetcher::kOAuthHeaderFormat[] = "Authorization: OAuth %s";
159// static
160const char GaiaAuthFetcher::kOAuth2BearerHeaderFormat[] =
161    "Authorization: Bearer %s";
162// static
163const char GaiaAuthFetcher::kClientLoginToOAuth2CookiePartSecure[] = "secure";
164// static
165const char GaiaAuthFetcher::kClientLoginToOAuth2CookiePartHttpOnly[] =
166    "httponly";
167// static
168const char GaiaAuthFetcher::kClientLoginToOAuth2CookiePartCodePrefix[] =
169    "oauth_code=";
170// static
171const int GaiaAuthFetcher::kClientLoginToOAuth2CookiePartCodePrefixLength =
172    arraysize(GaiaAuthFetcher::kClientLoginToOAuth2CookiePartCodePrefix) - 1;
173
174GaiaAuthFetcher::GaiaAuthFetcher(GaiaAuthConsumer* consumer,
175                                 const std::string& source,
176                                 net::URLRequestContextGetter* getter)
177    : consumer_(consumer),
178      getter_(getter),
179      source_(source),
180      client_login_gurl_(GaiaUrls::GetInstance()->client_login_url()),
181      issue_auth_token_gurl_(GaiaUrls::GetInstance()->issue_auth_token_url()),
182      oauth2_token_gurl_(GaiaUrls::GetInstance()->oauth2_token_url()),
183      oauth2_revoke_gurl_(GaiaUrls::GetInstance()->oauth2_revoke_url()),
184      get_user_info_gurl_(GaiaUrls::GetInstance()->get_user_info_url()),
185      merge_session_gurl_(GaiaUrls::GetInstance()->merge_session_url()),
186      uberauth_token_gurl_(GaiaUrls::GetInstance()->oauth1_login_url().Resolve(
187          base::StringPrintf(kUberAuthTokenURLFormat, source.c_str()))),
188      oauth_login_gurl_(GaiaUrls::GetInstance()->oauth1_login_url()),
189      list_accounts_gurl_(GaiaUrls::GetInstance()->list_accounts_url()),
190      client_login_to_oauth2_gurl_(
191          GaiaUrls::GetInstance()->client_login_to_oauth2_url()),
192      fetch_pending_(false),
193      fetch_code_only_(false) {}
194
195GaiaAuthFetcher::~GaiaAuthFetcher() {}
196
197bool GaiaAuthFetcher::HasPendingFetch() {
198  return fetch_pending_;
199}
200
201void GaiaAuthFetcher::CancelRequest() {
202  fetcher_.reset();
203  fetch_pending_ = false;
204}
205
206// static
207net::URLFetcher* GaiaAuthFetcher::CreateGaiaFetcher(
208    net::URLRequestContextGetter* getter,
209    const std::string& body,
210    const std::string& headers,
211    const GURL& gaia_gurl,
212    int load_flags,
213    net::URLFetcherDelegate* delegate) {
214  net::URLFetcher* to_return = net::URLFetcher::Create(
215      0, gaia_gurl,
216      body == "" ? net::URLFetcher::GET : net::URLFetcher::POST,
217      delegate);
218  to_return->SetRequestContext(getter);
219  to_return->SetUploadData("application/x-www-form-urlencoded", body);
220
221  DVLOG(2) << "Gaia fetcher URL: " << gaia_gurl.spec();
222  DVLOG(2) << "Gaia fetcher headers: " << headers;
223  DVLOG(2) << "Gaia fetcher body: " << body;
224
225  // The Gaia token exchange requests do not require any cookie-based
226  // identification as part of requests.  We suppress sending any cookies to
227  // maintain a separation between the user's browsing and Chrome's internal
228  // services.  Where such mixing is desired (MergeSession or OAuthLogin), it
229  // will be done explicitly.
230  to_return->SetLoadFlags(load_flags);
231
232  // Fetchers are sometimes cancelled because a network change was detected,
233  // especially at startup and after sign-in on ChromeOS. Retrying once should
234  // be enough in those cases; let the fetcher retry up to 3 times just in case.
235  // http://crbug.com/163710
236  to_return->SetAutomaticallyRetryOnNetworkChanges(3);
237
238  if (!headers.empty())
239    to_return->SetExtraRequestHeaders(headers);
240
241  return to_return;
242}
243
244// static
245std::string GaiaAuthFetcher::MakeClientLoginBody(
246    const std::string& username,
247    const std::string& password,
248    const std::string& source,
249    const char* service,
250    const std::string& login_token,
251    const std::string& login_captcha,
252    HostedAccountsSetting allow_hosted_accounts) {
253  std::string encoded_username = net::EscapeUrlEncodedData(username, true);
254  std::string encoded_password = net::EscapeUrlEncodedData(password, true);
255  std::string encoded_login_token = net::EscapeUrlEncodedData(login_token,
256                                                              true);
257  std::string encoded_login_captcha = net::EscapeUrlEncodedData(login_captcha,
258                                                                true);
259
260  const char* account_type = allow_hosted_accounts == HostedAccountsAllowed ?
261      kAccountTypeHostedOrGoogle :
262      kAccountTypeGoogle;
263
264  if (login_token.empty() || login_captcha.empty()) {
265    return base::StringPrintf(kClientLoginFormat,
266                              encoded_username.c_str(),
267                              encoded_password.c_str(),
268                              kCookiePersistence,
269                              account_type,
270                              source.c_str(),
271                              service);
272  }
273
274  return base::StringPrintf(kClientLoginCaptchaFormat,
275                            encoded_username.c_str(),
276                            encoded_password.c_str(),
277                            kCookiePersistence,
278                            account_type,
279                            source.c_str(),
280                            service,
281                            encoded_login_token.c_str(),
282                            encoded_login_captcha.c_str());
283}
284
285// static
286std::string GaiaAuthFetcher::MakeIssueAuthTokenBody(
287    const std::string& sid,
288    const std::string& lsid,
289    const char* const service) {
290  std::string encoded_sid = net::EscapeUrlEncodedData(sid, true);
291  std::string encoded_lsid = net::EscapeUrlEncodedData(lsid, true);
292
293  // All tokens should be session tokens except the gaia auth token.
294  bool session = true;
295  if (!strcmp(service, GaiaConstants::kGaiaService))
296    session = false;
297
298  return base::StringPrintf(kIssueAuthTokenFormat,
299                            encoded_sid.c_str(),
300                            encoded_lsid.c_str(),
301                            service,
302                            session ? "true" : "false");
303}
304
305// static
306std::string GaiaAuthFetcher::MakeGetAuthCodeBody() {
307  std::string encoded_scope = net::EscapeUrlEncodedData(
308      GaiaConstants::kOAuth1LoginScope, true);
309  std::string encoded_client_id = net::EscapeUrlEncodedData(
310      GaiaUrls::GetInstance()->oauth2_chrome_client_id(), true);
311  return base::StringPrintf(kClientLoginToOAuth2BodyFormat,
312                            encoded_scope.c_str(),
313                            encoded_client_id.c_str());
314}
315
316// static
317std::string GaiaAuthFetcher::MakeGetTokenPairBody(
318    const std::string& auth_code) {
319  std::string encoded_scope = net::EscapeUrlEncodedData(
320      GaiaConstants::kOAuth1LoginScope, true);
321  std::string encoded_client_id = net::EscapeUrlEncodedData(
322      GaiaUrls::GetInstance()->oauth2_chrome_client_id(), true);
323  std::string encoded_client_secret = net::EscapeUrlEncodedData(
324      GaiaUrls::GetInstance()->oauth2_chrome_client_secret(), true);
325  std::string encoded_auth_code = net::EscapeUrlEncodedData(auth_code, true);
326  return base::StringPrintf(kOAuth2CodeToTokenPairBodyFormat,
327                            encoded_scope.c_str(),
328                            encoded_client_id.c_str(),
329                            encoded_client_secret.c_str(),
330                            encoded_auth_code.c_str());
331}
332
333// static
334std::string GaiaAuthFetcher::MakeRevokeTokenBody(
335    const std::string& auth_token) {
336  return base::StringPrintf(kOAuth2RevokeTokenBodyFormat, auth_token.c_str());
337}
338
339// static
340std::string GaiaAuthFetcher::MakeGetUserInfoBody(const std::string& lsid) {
341  std::string encoded_lsid = net::EscapeUrlEncodedData(lsid, true);
342  return base::StringPrintf(kGetUserInfoFormat, encoded_lsid.c_str());
343}
344
345// static
346std::string GaiaAuthFetcher::MakeMergeSessionBody(
347    const std::string& auth_token,
348    const std::string& continue_url,
349    const std::string& source) {
350  std::string encoded_auth_token = net::EscapeUrlEncodedData(auth_token, true);
351  std::string encoded_continue_url = net::EscapeUrlEncodedData(continue_url,
352                                                               true);
353  std::string encoded_source = net::EscapeUrlEncodedData(source, true);
354  return base::StringPrintf(kMergeSessionFormat,
355                            encoded_auth_token.c_str(),
356                            encoded_continue_url.c_str(),
357                            encoded_source.c_str());
358}
359
360// static
361std::string GaiaAuthFetcher::MakeGetAuthCodeHeader(
362    const std::string& auth_token) {
363  return base::StringPrintf(kAuthHeaderFormat, auth_token.c_str());
364}
365
366// Helper method that extracts tokens from a successful reply.
367// static
368void GaiaAuthFetcher::ParseClientLoginResponse(const std::string& data,
369                                               std::string* sid,
370                                               std::string* lsid,
371                                               std::string* token) {
372  using std::vector;
373  using std::pair;
374  using std::string;
375  sid->clear();
376  lsid->clear();
377  token->clear();
378  vector<pair<string, string> > tokens;
379  base::SplitStringIntoKeyValuePairs(data, '=', '\n', &tokens);
380  for (vector<pair<string, string> >::iterator i = tokens.begin();
381      i != tokens.end(); ++i) {
382    if (i->first == "SID") {
383      sid->assign(i->second);
384    } else if (i->first == "LSID") {
385      lsid->assign(i->second);
386    } else if (i->first == "Auth") {
387      token->assign(i->second);
388    }
389  }
390  // If this was a request for uberauth token, then that's all we've got in
391  // data.
392  if (sid->empty() && lsid->empty() && token->empty())
393    token->assign(data);
394}
395
396// static
397std::string GaiaAuthFetcher::MakeOAuthLoginBody(const std::string& service,
398                                                const std::string& source) {
399  std::string encoded_service = net::EscapeUrlEncodedData(service, true);
400  std::string encoded_source = net::EscapeUrlEncodedData(source, true);
401  return base::StringPrintf(kOAuthLoginFormat,
402                            encoded_service.c_str(),
403                            encoded_source.c_str());
404}
405
406// static
407void GaiaAuthFetcher::ParseClientLoginFailure(const std::string& data,
408                                              std::string* error,
409                                              std::string* error_url,
410                                              std::string* captcha_url,
411                                              std::string* captcha_token) {
412  using std::vector;
413  using std::pair;
414  using std::string;
415
416  vector<pair<string, string> > tokens;
417  base::SplitStringIntoKeyValuePairs(data, '=', '\n', &tokens);
418  for (vector<pair<string, string> >::iterator i = tokens.begin();
419       i != tokens.end(); ++i) {
420    if (i->first == kErrorParam) {
421      error->assign(i->second);
422    } else if (i->first == kErrorUrlParam) {
423      error_url->assign(i->second);
424    } else if (i->first == kCaptchaUrlParam) {
425      captcha_url->assign(i->second);
426    } else if (i->first == kCaptchaTokenParam) {
427      captcha_token->assign(i->second);
428    }
429  }
430}
431
432// static
433bool GaiaAuthFetcher::ParseClientLoginToOAuth2Response(
434    const net::ResponseCookies& cookies,
435    std::string* auth_code) {
436  DCHECK(auth_code);
437  net::ResponseCookies::const_iterator iter;
438  for (iter = cookies.begin(); iter != cookies.end(); ++iter) {
439    if (ParseClientLoginToOAuth2Cookie(*iter, auth_code))
440      return true;
441  }
442  return false;
443}
444
445// static
446bool GaiaAuthFetcher::ParseClientLoginToOAuth2Cookie(const std::string& cookie,
447                                                     std::string* auth_code) {
448  std::vector<std::string> parts;
449  base::SplitString(cookie, ';', &parts);
450  // Per documentation, the cookie should have Secure and HttpOnly.
451  if (!CookiePartsContains(parts, kClientLoginToOAuth2CookiePartSecure) ||
452      !CookiePartsContains(parts, kClientLoginToOAuth2CookiePartHttpOnly)) {
453    return false;
454  }
455
456  std::vector<std::string>::const_iterator iter;
457  for (iter = parts.begin(); iter != parts.end(); ++iter) {
458    const std::string& part = *iter;
459    if (StartsWithASCII(
460        part, kClientLoginToOAuth2CookiePartCodePrefix, false)) {
461      auth_code->assign(part.substr(
462          kClientLoginToOAuth2CookiePartCodePrefixLength));
463      return true;
464    }
465  }
466  return false;
467}
468
469void GaiaAuthFetcher::StartClientLogin(
470    const std::string& username,
471    const std::string& password,
472    const char* const service,
473    const std::string& login_token,
474    const std::string& login_captcha,
475    HostedAccountsSetting allow_hosted_accounts) {
476
477  DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
478
479  // This class is thread agnostic, so be sure to call this only on the
480  // same thread each time.
481  DVLOG(1) << "Starting new ClientLogin fetch for:" << username;
482
483  // Must outlive fetcher_.
484  request_body_ = MakeClientLoginBody(username,
485                                      password,
486                                      source_,
487                                      service,
488                                      login_token,
489                                      login_captcha,
490                                      allow_hosted_accounts);
491  fetcher_.reset(CreateGaiaFetcher(getter_,
492                                   request_body_,
493                                   std::string(),
494                                   client_login_gurl_,
495                                   kLoadFlagsIgnoreCookies,
496                                   this));
497  fetch_pending_ = true;
498  fetcher_->Start();
499}
500
501void GaiaAuthFetcher::StartIssueAuthToken(const std::string& sid,
502                                          const std::string& lsid,
503                                          const char* const service) {
504  DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
505
506  DVLOG(1) << "Starting IssueAuthToken for: " << service;
507  requested_service_ = service;
508  request_body_ = MakeIssueAuthTokenBody(sid, lsid, service);
509  fetcher_.reset(CreateGaiaFetcher(getter_,
510                                   request_body_,
511                                   std::string(),
512                                   issue_auth_token_gurl_,
513                                   kLoadFlagsIgnoreCookies,
514                                   this));
515  fetch_pending_ = true;
516  fetcher_->Start();
517}
518
519void GaiaAuthFetcher::StartLsoForOAuthLoginTokenExchange(
520    const std::string& auth_token) {
521  DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
522
523  DVLOG(1) << "Starting OAuth login token exchange with auth_token";
524  request_body_ = MakeGetAuthCodeBody();
525  client_login_to_oauth2_gurl_ =
526      GaiaUrls::GetInstance()->client_login_to_oauth2_url();
527
528  fetcher_.reset(CreateGaiaFetcher(getter_,
529                                   request_body_,
530                                   MakeGetAuthCodeHeader(auth_token),
531                                   client_login_to_oauth2_gurl_,
532                                   kLoadFlagsIgnoreCookies,
533                                   this));
534  fetch_pending_ = true;
535  fetcher_->Start();
536}
537
538void GaiaAuthFetcher::StartRevokeOAuth2Token(const std::string& auth_token) {
539  DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
540
541  DVLOG(1) << "Starting OAuth2 token revocation";
542  request_body_ = MakeRevokeTokenBody(auth_token);
543  fetcher_.reset(CreateGaiaFetcher(getter_,
544                                   request_body_,
545                                   std::string(),
546                                   oauth2_revoke_gurl_,
547                                   kLoadFlagsIgnoreCookies,
548                                   this));
549  fetch_pending_ = true;
550  fetcher_->Start();
551}
552
553void GaiaAuthFetcher::StartCookieForOAuthCodeExchange(
554    const std::string& session_index) {
555  // Same as the first step of StartCookieForOAuthLoginTokenExchange;
556  StartCookieForOAuthLoginTokenExchange(session_index);
557  fetch_code_only_ = true;
558}
559
560void GaiaAuthFetcher::StartCookieForOAuthLoginTokenExchange(
561    const std::string& session_index) {
562  DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
563
564  DVLOG(1) << "Starting OAuth login token fetch with cookie jar";
565  request_body_ = MakeGetAuthCodeBody();
566
567  client_login_to_oauth2_gurl_ =
568      GaiaUrls::GetInstance()->client_login_to_oauth2_url();
569  if (!session_index.empty()) {
570    client_login_to_oauth2_gurl_ =
571        client_login_to_oauth2_gurl_.Resolve("?authuser=" + session_index);
572  }
573
574  fetcher_.reset(CreateGaiaFetcher(getter_,
575                                   request_body_,
576                                   std::string(),
577                                   client_login_to_oauth2_gurl_,
578                                   net::LOAD_NORMAL,
579                                   this));
580  fetch_pending_ = true;
581  fetch_code_only_ = false;
582  fetcher_->Start();
583}
584
585void GaiaAuthFetcher::StartAuthCodeForOAuth2TokenExchange(
586    const std::string& auth_code) {
587  DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
588
589  DVLOG(1) << "Starting OAuth token pair fetch";
590  request_body_ = MakeGetTokenPairBody(auth_code);
591  fetcher_.reset(CreateGaiaFetcher(getter_,
592                                   request_body_,
593                                   std::string(),
594                                   oauth2_token_gurl_,
595                                   kLoadFlagsIgnoreCookies,
596                                   this));
597  fetch_pending_ = true;
598  fetcher_->Start();
599}
600
601void GaiaAuthFetcher::StartGetUserInfo(const std::string& lsid) {
602  DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
603
604  DVLOG(1) << "Starting GetUserInfo for lsid=" << lsid;
605  request_body_ = MakeGetUserInfoBody(lsid);
606  fetcher_.reset(CreateGaiaFetcher(getter_,
607                                   request_body_,
608                                   std::string(),
609                                   get_user_info_gurl_,
610                                   kLoadFlagsIgnoreCookies,
611                                   this));
612  fetch_pending_ = true;
613  fetcher_->Start();
614}
615
616void GaiaAuthFetcher::StartMergeSession(const std::string& uber_token) {
617  DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
618
619  DVLOG(1) << "Starting MergeSession with uber_token=" << uber_token;
620
621  // The continue URL is a required parameter of the MergeSession API, but in
622  // this case we don't actually need or want to navigate to it.  Setting it to
623  // an arbitrary Google URL.
624  //
625  // In order for the new session to be merged correctly, the server needs to
626  // know what sessions already exist in the browser.  The fetcher needs to be
627  // created such that it sends the cookies with the request, which is
628  // different from all other requests the fetcher can make.
629  std::string continue_url("http://www.google.com");
630  request_body_ = MakeMergeSessionBody(uber_token, continue_url, source_);
631  fetcher_.reset(CreateGaiaFetcher(getter_,
632                                   request_body_,
633                                   std::string(),
634                                   merge_session_gurl_,
635                                   net::LOAD_NORMAL,
636                                   this));
637  fetch_pending_ = true;
638  fetcher_->Start();
639}
640
641void GaiaAuthFetcher::StartTokenFetchForUberAuthExchange(
642    const std::string& access_token) {
643  DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
644
645  DVLOG(1) << "Starting StartTokenFetchForUberAuthExchange with access_token="
646           << access_token;
647  std::string authentication_header =
648      base::StringPrintf(kOAuthHeaderFormat, access_token.c_str());
649  fetcher_.reset(CreateGaiaFetcher(getter_,
650                                   std::string(),
651                                   authentication_header,
652                                   uberauth_token_gurl_,
653                                   net::LOAD_NORMAL,
654                                   this));
655  fetch_pending_ = true;
656  fetcher_->Start();
657}
658
659void GaiaAuthFetcher::StartOAuthLogin(const std::string& access_token,
660                                      const std::string& service) {
661  DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
662
663  request_body_ = MakeOAuthLoginBody(service, source_);
664  std::string authentication_header =
665      base::StringPrintf(kOAuth2BearerHeaderFormat, access_token.c_str());
666  fetcher_.reset(CreateGaiaFetcher(getter_,
667                                   request_body_,
668                                   authentication_header,
669                                   oauth_login_gurl_,
670                                   net::LOAD_NORMAL,
671                                   this));
672  fetch_pending_ = true;
673  fetcher_->Start();
674}
675
676void GaiaAuthFetcher::StartListAccounts() {
677  DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
678
679  fetcher_.reset(CreateGaiaFetcher(getter_,
680                                   " ",  // To force an HTTP POST.
681                                   "Origin: https://www.google.com",
682                                   list_accounts_gurl_,
683                                   net::LOAD_NORMAL,
684                                   this));
685  fetch_pending_ = true;
686  fetcher_->Start();
687}
688
689// static
690GoogleServiceAuthError GaiaAuthFetcher::GenerateAuthError(
691    const std::string& data,
692    const net::URLRequestStatus& status) {
693  if (!status.is_success()) {
694    if (status.status() == net::URLRequestStatus::CANCELED) {
695      return GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED);
696    } else {
697      DLOG(WARNING) << "Could not reach Google Accounts servers: errno "
698          << status.error();
699      return GoogleServiceAuthError::FromConnectionError(status.error());
700    }
701  } else {
702    if (IsSecondFactorSuccess(data)) {
703      return GoogleServiceAuthError(GoogleServiceAuthError::TWO_FACTOR);
704    }
705
706    std::string error;
707    std::string url;
708    std::string captcha_url;
709    std::string captcha_token;
710    ParseClientLoginFailure(data, &error, &url, &captcha_url, &captcha_token);
711    DLOG(WARNING) << "ClientLogin failed with " << error;
712
713    if (error == kCaptchaError) {
714      GURL image_url(
715          GaiaUrls::GetInstance()->captcha_base_url().Resolve(captcha_url));
716      GURL unlock_url(url);
717      return GoogleServiceAuthError::FromClientLoginCaptchaChallenge(
718          captcha_token, image_url, unlock_url);
719    }
720    if (error == kAccountDeletedError)
721      return GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DELETED);
722    if (error == kAccountDisabledError)
723      return GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DISABLED);
724    if (error == kBadAuthenticationError) {
725      return GoogleServiceAuthError(
726          GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);
727    }
728    if (error == kServiceUnavailableError) {
729      return GoogleServiceAuthError(
730          GoogleServiceAuthError::SERVICE_UNAVAILABLE);
731    }
732
733    DLOG(WARNING) << "Incomprehensible response from Google Accounts servers.";
734    return GoogleServiceAuthError(
735        GoogleServiceAuthError::SERVICE_UNAVAILABLE);
736  }
737
738  NOTREACHED();
739  return GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE);
740}
741
742// static
743GoogleServiceAuthError GaiaAuthFetcher::GenerateOAuthLoginError(
744    const std::string& data,
745    const net::URLRequestStatus& status) {
746  if (!status.is_success()) {
747    if (status.status() == net::URLRequestStatus::CANCELED) {
748      return GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED);
749    } else {
750      DLOG(WARNING) << "Could not reach Google Accounts servers: errno "
751          << status.error();
752      return GoogleServiceAuthError::FromConnectionError(status.error());
753    }
754  } else {
755    if (IsSecondFactorSuccess(data)) {
756      return GoogleServiceAuthError(GoogleServiceAuthError::TWO_FACTOR);
757    }
758
759    std::string error;
760    std::string url;
761    std::string captcha_url;
762    std::string captcha_token;
763    ParseClientLoginFailure(data, &error, &url, &captcha_url, &captcha_token);
764    LOG(WARNING) << "OAuthLogin failed with " << error;
765
766    if (error == kCaptchaErrorCode) {
767      GURL image_url(
768          GaiaUrls::GetInstance()->captcha_base_url().Resolve(captcha_url));
769      GURL unlock_url(url);
770      return GoogleServiceAuthError::FromClientLoginCaptchaChallenge(
771          captcha_token, image_url, unlock_url);
772    }
773    if (error == kAccountDeletedErrorCode)
774      return GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DELETED);
775    if (error == kAccountDisabledErrorCode)
776      return GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DISABLED);
777    if (error == kBadAuthenticationErrorCode) {
778      return GoogleServiceAuthError(
779          GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);
780    }
781    if (error == kServiceUnavailableErrorCode) {
782      return GoogleServiceAuthError(
783          GoogleServiceAuthError::SERVICE_UNAVAILABLE);
784    }
785
786    DLOG(WARNING) << "Incomprehensible response from Google Accounts servers.";
787    return GoogleServiceAuthError(
788        GoogleServiceAuthError::SERVICE_UNAVAILABLE);
789  }
790
791  NOTREACHED();
792  return GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE);
793}
794
795void GaiaAuthFetcher::OnClientLoginFetched(const std::string& data,
796                                           const net::URLRequestStatus& status,
797                                           int response_code) {
798  if (status.is_success() && response_code == net::HTTP_OK) {
799    DVLOG(1) << "ClientLogin successful!";
800    std::string sid;
801    std::string lsid;
802    std::string token;
803    ParseClientLoginResponse(data, &sid, &lsid, &token);
804    consumer_->OnClientLoginSuccess(
805        GaiaAuthConsumer::ClientLoginResult(sid, lsid, token, data));
806  } else {
807    consumer_->OnClientLoginFailure(GenerateAuthError(data, status));
808  }
809}
810
811void GaiaAuthFetcher::OnIssueAuthTokenFetched(
812    const std::string& data,
813    const net::URLRequestStatus& status,
814    int response_code) {
815  if (status.is_success() && response_code == net::HTTP_OK) {
816    // Only the bare token is returned in the body of this Gaia call
817    // without any padding.
818    consumer_->OnIssueAuthTokenSuccess(requested_service_, data);
819  } else {
820    consumer_->OnIssueAuthTokenFailure(requested_service_,
821        GenerateAuthError(data, status));
822  }
823}
824
825void GaiaAuthFetcher::OnClientLoginToOAuth2Fetched(
826    const std::string& data,
827    const net::ResponseCookies& cookies,
828    const net::URLRequestStatus& status,
829    int response_code) {
830  if (status.is_success() && response_code == net::HTTP_OK) {
831    std::string auth_code;
832    ParseClientLoginToOAuth2Response(cookies, &auth_code);
833    if (fetch_code_only_)
834      consumer_->OnClientOAuthCodeSuccess(auth_code);
835    else
836      StartAuthCodeForOAuth2TokenExchange(auth_code);
837  } else {
838    GoogleServiceAuthError auth_error(GenerateAuthError(data, status));
839    if (fetch_code_only_)
840      consumer_->OnClientOAuthCodeFailure(auth_error);
841    else
842      consumer_->OnClientOAuthFailure(auth_error);
843  }
844}
845
846void GaiaAuthFetcher::OnOAuth2TokenPairFetched(
847    const std::string& data,
848    const net::URLRequestStatus& status,
849    int response_code) {
850  std::string refresh_token;
851  std::string access_token;
852  int expires_in_secs = 0;
853
854  bool success = false;
855  if (status.is_success() && response_code == net::HTTP_OK) {
856    scoped_ptr<base::Value> value(base::JSONReader::Read(data));
857    if (value.get() && value->GetType() == base::Value::TYPE_DICTIONARY) {
858      base::DictionaryValue* dict =
859          static_cast<base::DictionaryValue*>(value.get());
860      success = ExtractOAuth2TokenPairResponse(dict, &refresh_token,
861                                               &access_token, &expires_in_secs);
862    }
863  }
864
865  if (success) {
866    consumer_->OnClientOAuthSuccess(
867        GaiaAuthConsumer::ClientOAuthResult(refresh_token, access_token,
868                                            expires_in_secs));
869  } else {
870    consumer_->OnClientOAuthFailure(GenerateAuthError(data, status));
871  }
872}
873
874void GaiaAuthFetcher::OnOAuth2RevokeTokenFetched(
875    const std::string& data,
876    const net::URLRequestStatus& status,
877    int response_code) {
878  consumer_->OnOAuth2RevokeTokenCompleted();
879}
880
881void GaiaAuthFetcher::OnListAccountsFetched(const std::string& data,
882                                            const net::URLRequestStatus& status,
883                                            int response_code) {
884  if (status.is_success() && response_code == net::HTTP_OK) {
885    consumer_->OnListAccountsSuccess(data);
886  } else {
887    consumer_->OnListAccountsFailure(GenerateAuthError(data, status));
888  }
889}
890
891void GaiaAuthFetcher::OnGetUserInfoFetched(
892    const std::string& data,
893    const net::URLRequestStatus& status,
894    int response_code) {
895  if (status.is_success() && response_code == net::HTTP_OK) {
896    std::vector<std::pair<std::string, std::string> > tokens;
897    UserInfoMap matches;
898    base::SplitStringIntoKeyValuePairs(data, '=', '\n', &tokens);
899    std::vector<std::pair<std::string, std::string> >::iterator i;
900    for (i = tokens.begin(); i != tokens.end(); ++i) {
901      matches[i->first] = i->second;
902    }
903    consumer_->OnGetUserInfoSuccess(matches);
904  } else {
905    consumer_->OnGetUserInfoFailure(GenerateAuthError(data, status));
906  }
907}
908
909void GaiaAuthFetcher::OnMergeSessionFetched(const std::string& data,
910                                            const net::URLRequestStatus& status,
911                                            int response_code) {
912  if (status.is_success() && response_code == net::HTTP_OK) {
913    consumer_->OnMergeSessionSuccess(data);
914  } else {
915    consumer_->OnMergeSessionFailure(GenerateAuthError(data, status));
916  }
917}
918
919void GaiaAuthFetcher::OnUberAuthTokenFetch(const std::string& data,
920                                           const net::URLRequestStatus& status,
921                                           int response_code) {
922  if (status.is_success() && response_code == net::HTTP_OK) {
923    consumer_->OnUberAuthTokenSuccess(data);
924  } else {
925    consumer_->OnUberAuthTokenFailure(GenerateAuthError(data, status));
926  }
927}
928
929void GaiaAuthFetcher::OnOAuthLoginFetched(const std::string& data,
930                                          const net::URLRequestStatus& status,
931                                          int response_code) {
932  if (status.is_success() && response_code == net::HTTP_OK) {
933    DVLOG(1) << "ClientLogin successful!";
934    std::string sid;
935    std::string lsid;
936    std::string token;
937    ParseClientLoginResponse(data, &sid, &lsid, &token);
938    consumer_->OnClientLoginSuccess(
939        GaiaAuthConsumer::ClientLoginResult(sid, lsid, token, data));
940  } else {
941    consumer_->OnClientLoginFailure(GenerateAuthError(data, status));
942  }
943}
944
945void GaiaAuthFetcher::OnURLFetchComplete(const net::URLFetcher* source) {
946  fetch_pending_ = false;
947  // Some of the GAIA requests perform redirects, which results in the final
948  // URL of the fetcher not being the original URL requested.  Therefore use
949  // the original URL when determining which OnXXX function to call.
950  const GURL& url = source->GetOriginalURL();
951  const net::URLRequestStatus& status = source->GetStatus();
952  int response_code = source->GetResponseCode();
953  std::string data;
954  source->GetResponseAsString(&data);
955#ifndef NDEBUG
956  std::string headers;
957  if (source->GetResponseHeaders())
958    source->GetResponseHeaders()->GetNormalizedHeaders(&headers);
959  DVLOG(2) << "Response " << url.spec() << ", code = " << response_code << "\n"
960           << headers << "\n";
961  DVLOG(2) << "data: " << data << "\n";
962#endif
963  // Retrieve the response headers from the request.  Must only be called after
964  // the OnURLFetchComplete callback has run.
965  if (url == client_login_gurl_) {
966    OnClientLoginFetched(data, status, response_code);
967  } else if (url == issue_auth_token_gurl_) {
968    OnIssueAuthTokenFetched(data, status, response_code);
969  } else if (url == client_login_to_oauth2_gurl_) {
970    OnClientLoginToOAuth2Fetched(
971        data, source->GetCookies(), status, response_code);
972  } else if (url == oauth2_token_gurl_) {
973    OnOAuth2TokenPairFetched(data, status, response_code);
974  } else if (url == get_user_info_gurl_) {
975    OnGetUserInfoFetched(data, status, response_code);
976  } else if (url == merge_session_gurl_) {
977    OnMergeSessionFetched(data, status, response_code);
978  } else if (url == uberauth_token_gurl_) {
979    OnUberAuthTokenFetch(data, status, response_code);
980  } else if (url == oauth_login_gurl_) {
981    OnOAuthLoginFetched(data, status, response_code);
982  } else if (url == oauth2_revoke_gurl_) {
983    OnOAuth2RevokeTokenFetched(data, status, response_code);
984  } else if (url == list_accounts_gurl_) {
985    OnListAccountsFetched(data, status, response_code);
986  } else {
987    NOTREACHED();
988  }
989}
990
991// static
992bool GaiaAuthFetcher::IsSecondFactorSuccess(
993    const std::string& alleged_error) {
994  return alleged_error.find(kSecondFactor) !=
995      std::string::npos;
996}
997