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