gaia_auth_fetcher.cc revision 46d4c2bc3267f3f028f39e7e311b0f89aba2e4fd
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";
117// static
118const char GaiaAuthFetcher::kAccountDisabledError[] = "AccountDisabled";
119// static
120const char GaiaAuthFetcher::kBadAuthenticationError[] = "BadAuthentication";
121// static
122const char GaiaAuthFetcher::kCaptchaError[] = "CaptchaRequired";
123// static
124const char GaiaAuthFetcher::kServiceUnavailableError[] =
125    "ServiceUnavailable";
126// static
127const char GaiaAuthFetcher::kErrorParam[] = "Error";
128// static
129const char GaiaAuthFetcher::kErrorUrlParam[] = "Url";
130// static
131const char GaiaAuthFetcher::kCaptchaUrlParam[] = "CaptchaUrl";
132// static
133const char GaiaAuthFetcher::kCaptchaTokenParam[] = "CaptchaToken";
134
135// static
136const char GaiaAuthFetcher::kCookiePersistence[] = "true";
137// static
138// TODO(johnnyg): When hosted accounts are supported by sync,
139// we can always use "HOSTED_OR_GOOGLE"
140const char GaiaAuthFetcher::kAccountTypeHostedOrGoogle[] =
141    "HOSTED_OR_GOOGLE";
142const char GaiaAuthFetcher::kAccountTypeGoogle[] =
143    "GOOGLE";
144
145// static
146const char GaiaAuthFetcher::kSecondFactor[] = "Info=InvalidSecondFactor";
147
148// static
149const char GaiaAuthFetcher::kAuthHeaderFormat[] =
150    "Authorization: GoogleLogin auth=%s";
151// static
152const char GaiaAuthFetcher::kOAuthHeaderFormat[] = "Authorization: OAuth %s";
153// static
154const char GaiaAuthFetcher::kOAuth2BearerHeaderFormat[] =
155    "Authorization: Bearer %s";
156// static
157const char GaiaAuthFetcher::kClientLoginToOAuth2CookiePartSecure[] = "secure";
158// static
159const char GaiaAuthFetcher::kClientLoginToOAuth2CookiePartHttpOnly[] =
160    "httponly";
161// static
162const char GaiaAuthFetcher::kClientLoginToOAuth2CookiePartCodePrefix[] =
163    "oauth_code=";
164// static
165const int GaiaAuthFetcher::kClientLoginToOAuth2CookiePartCodePrefixLength =
166    arraysize(GaiaAuthFetcher::kClientLoginToOAuth2CookiePartCodePrefix) - 1;
167
168GaiaAuthFetcher::GaiaAuthFetcher(GaiaAuthConsumer* consumer,
169                                 const std::string& source,
170                                 net::URLRequestContextGetter* getter)
171    : consumer_(consumer),
172      getter_(getter),
173      source_(source),
174      client_login_gurl_(GaiaUrls::GetInstance()->client_login_url()),
175      issue_auth_token_gurl_(GaiaUrls::GetInstance()->issue_auth_token_url()),
176      oauth2_token_gurl_(GaiaUrls::GetInstance()->oauth2_token_url()),
177      oauth2_revoke_gurl_(GaiaUrls::GetInstance()->oauth2_revoke_url()),
178      get_user_info_gurl_(GaiaUrls::GetInstance()->get_user_info_url()),
179      merge_session_gurl_(GaiaUrls::GetInstance()->merge_session_url()),
180      uberauth_token_gurl_(GaiaUrls::GetInstance()->oauth1_login_url().Resolve(
181          base::StringPrintf(kUberAuthTokenURLFormat, source.c_str()))),
182      oauth_login_gurl_(GaiaUrls::GetInstance()->oauth1_login_url()),
183      list_accounts_gurl_(GaiaUrls::GetInstance()->list_accounts_url()),
184      client_login_to_oauth2_gurl_(
185          GaiaUrls::GetInstance()->client_login_to_oauth2_url()),
186      fetch_pending_(false) {}
187
188GaiaAuthFetcher::~GaiaAuthFetcher() {}
189
190bool GaiaAuthFetcher::HasPendingFetch() {
191  return fetch_pending_;
192}
193
194void GaiaAuthFetcher::CancelRequest() {
195  fetcher_.reset();
196  fetch_pending_ = false;
197}
198
199// static
200net::URLFetcher* GaiaAuthFetcher::CreateGaiaFetcher(
201    net::URLRequestContextGetter* getter,
202    const std::string& body,
203    const std::string& headers,
204    const GURL& gaia_gurl,
205    int load_flags,
206    net::URLFetcherDelegate* delegate) {
207  net::URLFetcher* to_return = net::URLFetcher::Create(
208      0, gaia_gurl,
209      body == "" ? net::URLFetcher::GET : net::URLFetcher::POST,
210      delegate);
211  to_return->SetRequestContext(getter);
212  to_return->SetUploadData("application/x-www-form-urlencoded", body);
213
214  DVLOG(2) << "Gaia fetcher URL: " << gaia_gurl.spec();
215  DVLOG(2) << "Gaia fetcher headers: " << headers;
216  DVLOG(2) << "Gaia fetcher body: " << body;
217
218  // The Gaia token exchange requests do not require any cookie-based
219  // identification as part of requests.  We suppress sending any cookies to
220  // maintain a separation between the user's browsing and Chrome's internal
221  // services.  Where such mixing is desired (MergeSession or OAuthLogin), it
222  // will be done explicitly.
223  to_return->SetLoadFlags(load_flags);
224
225  // Fetchers are sometimes cancelled because a network change was detected,
226  // especially at startup and after sign-in on ChromeOS. Retrying once should
227  // be enough in those cases; let the fetcher retry up to 3 times just in case.
228  // http://crbug.com/163710
229  to_return->SetAutomaticallyRetryOnNetworkChanges(3);
230
231  if (!headers.empty())
232    to_return->SetExtraRequestHeaders(headers);
233
234  return to_return;
235}
236
237// static
238std::string GaiaAuthFetcher::MakeClientLoginBody(
239    const std::string& username,
240    const std::string& password,
241    const std::string& source,
242    const char* service,
243    const std::string& login_token,
244    const std::string& login_captcha,
245    HostedAccountsSetting allow_hosted_accounts) {
246  std::string encoded_username = net::EscapeUrlEncodedData(username, true);
247  std::string encoded_password = net::EscapeUrlEncodedData(password, true);
248  std::string encoded_login_token = net::EscapeUrlEncodedData(login_token,
249                                                              true);
250  std::string encoded_login_captcha = net::EscapeUrlEncodedData(login_captcha,
251                                                                true);
252
253  const char* account_type = allow_hosted_accounts == HostedAccountsAllowed ?
254      kAccountTypeHostedOrGoogle :
255      kAccountTypeGoogle;
256
257  if (login_token.empty() || login_captcha.empty()) {
258    return base::StringPrintf(kClientLoginFormat,
259                              encoded_username.c_str(),
260                              encoded_password.c_str(),
261                              kCookiePersistence,
262                              account_type,
263                              source.c_str(),
264                              service);
265  }
266
267  return base::StringPrintf(kClientLoginCaptchaFormat,
268                            encoded_username.c_str(),
269                            encoded_password.c_str(),
270                            kCookiePersistence,
271                            account_type,
272                            source.c_str(),
273                            service,
274                            encoded_login_token.c_str(),
275                            encoded_login_captcha.c_str());
276}
277
278// static
279std::string GaiaAuthFetcher::MakeIssueAuthTokenBody(
280    const std::string& sid,
281    const std::string& lsid,
282    const char* const service) {
283  std::string encoded_sid = net::EscapeUrlEncodedData(sid, true);
284  std::string encoded_lsid = net::EscapeUrlEncodedData(lsid, true);
285
286  // All tokens should be session tokens except the gaia auth token.
287  bool session = true;
288  if (!strcmp(service, GaiaConstants::kGaiaService))
289    session = false;
290
291  return base::StringPrintf(kIssueAuthTokenFormat,
292                            encoded_sid.c_str(),
293                            encoded_lsid.c_str(),
294                            service,
295                            session ? "true" : "false");
296}
297
298// static
299std::string GaiaAuthFetcher::MakeGetAuthCodeBody() {
300  std::string encoded_scope = net::EscapeUrlEncodedData(
301      GaiaConstants::kOAuth1LoginScope, true);
302  std::string encoded_client_id = net::EscapeUrlEncodedData(
303      GaiaUrls::GetInstance()->oauth2_chrome_client_id(), true);
304  return base::StringPrintf(kClientLoginToOAuth2BodyFormat,
305                            encoded_scope.c_str(),
306                            encoded_client_id.c_str());
307}
308
309// static
310std::string GaiaAuthFetcher::MakeGetTokenPairBody(
311    const std::string& auth_code) {
312  std::string encoded_scope = net::EscapeUrlEncodedData(
313      GaiaConstants::kOAuth1LoginScope, true);
314  std::string encoded_client_id = net::EscapeUrlEncodedData(
315      GaiaUrls::GetInstance()->oauth2_chrome_client_id(), true);
316  std::string encoded_client_secret = net::EscapeUrlEncodedData(
317      GaiaUrls::GetInstance()->oauth2_chrome_client_secret(), true);
318  std::string encoded_auth_code = net::EscapeUrlEncodedData(auth_code, true);
319  return base::StringPrintf(kOAuth2CodeToTokenPairBodyFormat,
320                            encoded_scope.c_str(),
321                            encoded_client_id.c_str(),
322                            encoded_client_secret.c_str(),
323                            encoded_auth_code.c_str());
324}
325
326// static
327std::string GaiaAuthFetcher::MakeRevokeTokenBody(
328    const std::string& auth_token) {
329  return base::StringPrintf(kOAuth2RevokeTokenBodyFormat, auth_token.c_str());
330}
331
332// static
333std::string GaiaAuthFetcher::MakeGetUserInfoBody(const std::string& lsid) {
334  std::string encoded_lsid = net::EscapeUrlEncodedData(lsid, true);
335  return base::StringPrintf(kGetUserInfoFormat, encoded_lsid.c_str());
336}
337
338// static
339std::string GaiaAuthFetcher::MakeMergeSessionBody(
340    const std::string& auth_token,
341    const std::string& continue_url,
342    const std::string& source) {
343  std::string encoded_auth_token = net::EscapeUrlEncodedData(auth_token, true);
344  std::string encoded_continue_url = net::EscapeUrlEncodedData(continue_url,
345                                                               true);
346  std::string encoded_source = net::EscapeUrlEncodedData(source, true);
347  return base::StringPrintf(kMergeSessionFormat,
348                            encoded_auth_token.c_str(),
349                            encoded_continue_url.c_str(),
350                            encoded_source.c_str());
351}
352
353// static
354std::string GaiaAuthFetcher::MakeGetAuthCodeHeader(
355    const std::string& auth_token) {
356  return base::StringPrintf(kAuthHeaderFormat, auth_token.c_str());
357}
358
359// Helper method that extracts tokens from a successful reply.
360// static
361void GaiaAuthFetcher::ParseClientLoginResponse(const std::string& data,
362                                               std::string* sid,
363                                               std::string* lsid,
364                                               std::string* token) {
365  using std::vector;
366  using std::pair;
367  using std::string;
368  sid->clear();
369  lsid->clear();
370  token->clear();
371  vector<pair<string, string> > tokens;
372  base::SplitStringIntoKeyValuePairs(data, '=', '\n', &tokens);
373  for (vector<pair<string, string> >::iterator i = tokens.begin();
374      i != tokens.end(); ++i) {
375    if (i->first == "SID") {
376      sid->assign(i->second);
377    } else if (i->first == "LSID") {
378      lsid->assign(i->second);
379    } else if (i->first == "Auth") {
380      token->assign(i->second);
381    }
382  }
383  // If this was a request for uberauth token, then that's all we've got in
384  // data.
385  if (sid->empty() && lsid->empty() && token->empty())
386    token->assign(data);
387}
388
389// static
390std::string GaiaAuthFetcher::MakeOAuthLoginBody(const std::string& service,
391                                                const std::string& source) {
392  std::string encoded_service = net::EscapeUrlEncodedData(service, true);
393  std::string encoded_source = net::EscapeUrlEncodedData(source, true);
394  return base::StringPrintf(kOAuthLoginFormat,
395                            encoded_service.c_str(),
396                            encoded_source.c_str());
397}
398
399// static
400void GaiaAuthFetcher::ParseClientLoginFailure(const std::string& data,
401                                              std::string* error,
402                                              std::string* error_url,
403                                              std::string* captcha_url,
404                                              std::string* captcha_token) {
405  using std::vector;
406  using std::pair;
407  using std::string;
408
409  vector<pair<string, string> > tokens;
410  base::SplitStringIntoKeyValuePairs(data, '=', '\n', &tokens);
411  for (vector<pair<string, string> >::iterator i = tokens.begin();
412       i != tokens.end(); ++i) {
413    if (i->first == kErrorParam) {
414      error->assign(i->second);
415    } else if (i->first == kErrorUrlParam) {
416      error_url->assign(i->second);
417    } else if (i->first == kCaptchaUrlParam) {
418      captcha_url->assign(i->second);
419    } else if (i->first == kCaptchaTokenParam) {
420      captcha_token->assign(i->second);
421    }
422  }
423}
424
425// static
426bool GaiaAuthFetcher::ParseClientLoginToOAuth2Response(
427    const net::ResponseCookies& cookies,
428    std::string* auth_code) {
429  DCHECK(auth_code);
430  net::ResponseCookies::const_iterator iter;
431  for (iter = cookies.begin(); iter != cookies.end(); ++iter) {
432    if (ParseClientLoginToOAuth2Cookie(*iter, auth_code))
433      return true;
434  }
435  return false;
436}
437
438// static
439bool GaiaAuthFetcher::ParseClientLoginToOAuth2Cookie(const std::string& cookie,
440                                                     std::string* auth_code) {
441  std::vector<std::string> parts;
442  base::SplitString(cookie, ';', &parts);
443  // Per documentation, the cookie should have Secure and HttpOnly.
444  if (!CookiePartsContains(parts, kClientLoginToOAuth2CookiePartSecure) ||
445      !CookiePartsContains(parts, kClientLoginToOAuth2CookiePartHttpOnly)) {
446    return false;
447  }
448
449  std::vector<std::string>::const_iterator iter;
450  for (iter = parts.begin(); iter != parts.end(); ++iter) {
451    const std::string& part = *iter;
452    if (StartsWithASCII(
453        part, kClientLoginToOAuth2CookiePartCodePrefix, false)) {
454      auth_code->assign(part.substr(
455          kClientLoginToOAuth2CookiePartCodePrefixLength));
456      return true;
457    }
458  }
459  return false;
460}
461
462void GaiaAuthFetcher::StartClientLogin(
463    const std::string& username,
464    const std::string& password,
465    const char* const service,
466    const std::string& login_token,
467    const std::string& login_captcha,
468    HostedAccountsSetting allow_hosted_accounts) {
469
470  DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
471
472  // This class is thread agnostic, so be sure to call this only on the
473  // same thread each time.
474  DVLOG(1) << "Starting new ClientLogin fetch for:" << username;
475
476  // Must outlive fetcher_.
477  request_body_ = MakeClientLoginBody(username,
478                                      password,
479                                      source_,
480                                      service,
481                                      login_token,
482                                      login_captcha,
483                                      allow_hosted_accounts);
484  fetcher_.reset(CreateGaiaFetcher(getter_,
485                                   request_body_,
486                                   std::string(),
487                                   client_login_gurl_,
488                                   kLoadFlagsIgnoreCookies,
489                                   this));
490  fetch_pending_ = true;
491  fetcher_->Start();
492}
493
494void GaiaAuthFetcher::StartIssueAuthToken(const std::string& sid,
495                                          const std::string& lsid,
496                                          const char* const service) {
497  DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
498
499  DVLOG(1) << "Starting IssueAuthToken for: " << service;
500  requested_service_ = service;
501  request_body_ = MakeIssueAuthTokenBody(sid, lsid, service);
502  fetcher_.reset(CreateGaiaFetcher(getter_,
503                                   request_body_,
504                                   std::string(),
505                                   issue_auth_token_gurl_,
506                                   kLoadFlagsIgnoreCookies,
507                                   this));
508  fetch_pending_ = true;
509  fetcher_->Start();
510}
511
512void GaiaAuthFetcher::StartLsoForOAuthLoginTokenExchange(
513    const std::string& auth_token) {
514  DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
515
516  DVLOG(1) << "Starting OAuth login token exchange with auth_token";
517  request_body_ = MakeGetAuthCodeBody();
518  client_login_to_oauth2_gurl_ =
519      GaiaUrls::GetInstance()->client_login_to_oauth2_url();
520
521  fetcher_.reset(CreateGaiaFetcher(getter_,
522                                   request_body_,
523                                   MakeGetAuthCodeHeader(auth_token),
524                                   client_login_to_oauth2_gurl_,
525                                   kLoadFlagsIgnoreCookies,
526                                   this));
527  fetch_pending_ = true;
528  fetcher_->Start();
529}
530
531void GaiaAuthFetcher::StartRevokeOAuth2Token(const std::string& auth_token) {
532  DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
533
534  DVLOG(1) << "Starting OAuth2 token revocation";
535  request_body_ = MakeRevokeTokenBody(auth_token);
536  fetcher_.reset(CreateGaiaFetcher(getter_,
537                                   request_body_,
538                                   std::string(),
539                                   oauth2_revoke_gurl_,
540                                   kLoadFlagsIgnoreCookies,
541                                   this));
542  fetch_pending_ = true;
543  fetcher_->Start();
544}
545
546void GaiaAuthFetcher::StartCookieForOAuthLoginTokenExchange(
547    const std::string& session_index) {
548  DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
549
550  DVLOG(1) << "Starting OAuth login token fetch with cookie jar";
551  request_body_ = MakeGetAuthCodeBody();
552
553  client_login_to_oauth2_gurl_ =
554      GaiaUrls::GetInstance()->client_login_to_oauth2_url();
555  if (!session_index.empty()) {
556    client_login_to_oauth2_gurl_ =
557        client_login_to_oauth2_gurl_.Resolve("?authuser=" + session_index);
558  }
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                                   net::LOAD_NORMAL,
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                                   net::LOAD_NORMAL,
656                                   this));
657  fetch_pending_ = true;
658  fetcher_->Start();
659}
660
661void GaiaAuthFetcher::StartListAccounts() {
662  DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
663
664  fetcher_.reset(CreateGaiaFetcher(getter_,
665                                   " ",  // To force an HTTP POST.
666                                   "Origin: https://www.google.com",
667                                   list_accounts_gurl_,
668                                   net::LOAD_NORMAL,
669                                   this));
670  fetch_pending_ = true;
671  fetcher_->Start();
672}
673
674// static
675GoogleServiceAuthError GaiaAuthFetcher::GenerateAuthError(
676    const std::string& data,
677    const net::URLRequestStatus& status) {
678  if (!status.is_success()) {
679    if (status.status() == net::URLRequestStatus::CANCELED) {
680      return GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED);
681    }
682    DLOG(WARNING) << "Could not reach Google Accounts servers: errno "
683                  << status.error();
684    return GoogleServiceAuthError::FromConnectionError(status.error());
685  }
686
687  if (IsSecondFactorSuccess(data))
688    return GoogleServiceAuthError(GoogleServiceAuthError::TWO_FACTOR);
689
690  std::string error;
691  std::string url;
692  std::string captcha_url;
693  std::string captcha_token;
694  ParseClientLoginFailure(data, &error, &url, &captcha_url, &captcha_token);
695  DLOG(WARNING) << "ClientLogin failed with " << error;
696
697  if (error == kCaptchaError) {
698    return GoogleServiceAuthError::FromClientLoginCaptchaChallenge(
699        captcha_token,
700        GURL(GaiaUrls::GetInstance()->captcha_base_url().Resolve(captcha_url)),
701        GURL(url));
702  }
703  if (error == kAccountDeletedError)
704    return GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DELETED);
705  if (error == kAccountDisabledError)
706    return GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DISABLED);
707  if (error == kBadAuthenticationError) {
708    return GoogleServiceAuthError(
709        GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);
710  }
711  if (error == kServiceUnavailableError) {
712    return GoogleServiceAuthError(
713        GoogleServiceAuthError::SERVICE_UNAVAILABLE);
714  }
715
716  DLOG(WARNING) << "Incomprehensible response from Google Accounts servers.";
717  return GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE);
718}
719
720void GaiaAuthFetcher::OnClientLoginFetched(const std::string& data,
721                                           const net::URLRequestStatus& status,
722                                           int response_code) {
723  if (status.is_success() && response_code == net::HTTP_OK) {
724    DVLOG(1) << "ClientLogin successful!";
725    std::string sid;
726    std::string lsid;
727    std::string token;
728    ParseClientLoginResponse(data, &sid, &lsid, &token);
729    consumer_->OnClientLoginSuccess(
730        GaiaAuthConsumer::ClientLoginResult(sid, lsid, token, data));
731  } else {
732    consumer_->OnClientLoginFailure(GenerateAuthError(data, status));
733  }
734}
735
736void GaiaAuthFetcher::OnIssueAuthTokenFetched(
737    const std::string& data,
738    const net::URLRequestStatus& status,
739    int response_code) {
740  if (status.is_success() && response_code == net::HTTP_OK) {
741    // Only the bare token is returned in the body of this Gaia call
742    // without any padding.
743    consumer_->OnIssueAuthTokenSuccess(requested_service_, data);
744  } else {
745    consumer_->OnIssueAuthTokenFailure(requested_service_,
746        GenerateAuthError(data, status));
747  }
748}
749
750void GaiaAuthFetcher::OnClientLoginToOAuth2Fetched(
751    const std::string& data,
752    const net::ResponseCookies& cookies,
753    const net::URLRequestStatus& status,
754    int response_code) {
755  if (status.is_success() && response_code == net::HTTP_OK) {
756    std::string auth_code;
757    ParseClientLoginToOAuth2Response(cookies, &auth_code);
758    StartAuthCodeForOAuth2TokenExchange(auth_code);
759  } else {
760    GoogleServiceAuthError auth_error(GenerateAuthError(data, status));
761    consumer_->OnClientOAuthFailure(auth_error);
762  }
763}
764
765void GaiaAuthFetcher::OnOAuth2TokenPairFetched(
766    const std::string& data,
767    const net::URLRequestStatus& status,
768    int response_code) {
769  std::string refresh_token;
770  std::string access_token;
771  int expires_in_secs = 0;
772
773  bool success = false;
774  if (status.is_success() && response_code == net::HTTP_OK) {
775    scoped_ptr<base::Value> value(base::JSONReader::Read(data));
776    if (value.get() && value->GetType() == base::Value::TYPE_DICTIONARY) {
777      base::DictionaryValue* dict =
778          static_cast<base::DictionaryValue*>(value.get());
779      success = ExtractOAuth2TokenPairResponse(dict, &refresh_token,
780                                               &access_token, &expires_in_secs);
781    }
782  }
783
784  if (success) {
785    consumer_->OnClientOAuthSuccess(
786        GaiaAuthConsumer::ClientOAuthResult(refresh_token, access_token,
787                                            expires_in_secs));
788  } else {
789    consumer_->OnClientOAuthFailure(GenerateAuthError(data, status));
790  }
791}
792
793void GaiaAuthFetcher::OnOAuth2RevokeTokenFetched(
794    const std::string& data,
795    const net::URLRequestStatus& status,
796    int response_code) {
797  consumer_->OnOAuth2RevokeTokenCompleted();
798}
799
800void GaiaAuthFetcher::OnListAccountsFetched(const std::string& data,
801                                            const net::URLRequestStatus& status,
802                                            int response_code) {
803  if (status.is_success() && response_code == net::HTTP_OK) {
804    consumer_->OnListAccountsSuccess(data);
805  } else {
806    consumer_->OnListAccountsFailure(GenerateAuthError(data, status));
807  }
808}
809
810void GaiaAuthFetcher::OnGetUserInfoFetched(
811    const std::string& data,
812    const net::URLRequestStatus& status,
813    int response_code) {
814  if (status.is_success() && response_code == net::HTTP_OK) {
815    std::vector<std::pair<std::string, std::string> > tokens;
816    UserInfoMap matches;
817    base::SplitStringIntoKeyValuePairs(data, '=', '\n', &tokens);
818    std::vector<std::pair<std::string, std::string> >::iterator i;
819    for (i = tokens.begin(); i != tokens.end(); ++i) {
820      matches[i->first] = i->second;
821    }
822    consumer_->OnGetUserInfoSuccess(matches);
823  } else {
824    consumer_->OnGetUserInfoFailure(GenerateAuthError(data, status));
825  }
826}
827
828void GaiaAuthFetcher::OnMergeSessionFetched(const std::string& data,
829                                            const net::URLRequestStatus& status,
830                                            int response_code) {
831  if (status.is_success() && response_code == net::HTTP_OK) {
832    consumer_->OnMergeSessionSuccess(data);
833  } else {
834    consumer_->OnMergeSessionFailure(GenerateAuthError(data, status));
835  }
836}
837
838void GaiaAuthFetcher::OnUberAuthTokenFetch(const std::string& data,
839                                           const net::URLRequestStatus& status,
840                                           int response_code) {
841  if (status.is_success() && response_code == net::HTTP_OK) {
842    consumer_->OnUberAuthTokenSuccess(data);
843  } else {
844    consumer_->OnUberAuthTokenFailure(GenerateAuthError(data, status));
845  }
846}
847
848void GaiaAuthFetcher::OnOAuthLoginFetched(const std::string& data,
849                                          const net::URLRequestStatus& status,
850                                          int response_code) {
851  if (status.is_success() && response_code == net::HTTP_OK) {
852    DVLOG(1) << "ClientLogin successful!";
853    std::string sid;
854    std::string lsid;
855    std::string token;
856    ParseClientLoginResponse(data, &sid, &lsid, &token);
857    consumer_->OnClientLoginSuccess(
858        GaiaAuthConsumer::ClientLoginResult(sid, lsid, token, data));
859  } else {
860    consumer_->OnClientLoginFailure(GenerateAuthError(data, status));
861  }
862}
863
864void GaiaAuthFetcher::OnURLFetchComplete(const net::URLFetcher* source) {
865  fetch_pending_ = false;
866  // Some of the GAIA requests perform redirects, which results in the final
867  // URL of the fetcher not being the original URL requested.  Therefore use
868  // the original URL when determining which OnXXX function to call.
869  const GURL& url = source->GetOriginalURL();
870  const net::URLRequestStatus& status = source->GetStatus();
871  int response_code = source->GetResponseCode();
872  std::string data;
873  source->GetResponseAsString(&data);
874#ifndef NDEBUG
875  std::string headers;
876  if (source->GetResponseHeaders())
877    source->GetResponseHeaders()->GetNormalizedHeaders(&headers);
878  DVLOG(2) << "Response " << url.spec() << ", code = " << response_code << "\n"
879           << headers << "\n";
880  DVLOG(2) << "data: " << data << "\n";
881#endif
882  // Retrieve the response headers from the request.  Must only be called after
883  // the OnURLFetchComplete callback has run.
884  if (url == client_login_gurl_) {
885    OnClientLoginFetched(data, status, response_code);
886  } else if (url == issue_auth_token_gurl_) {
887    OnIssueAuthTokenFetched(data, status, response_code);
888  } else if (url == client_login_to_oauth2_gurl_) {
889    OnClientLoginToOAuth2Fetched(
890        data, source->GetCookies(), status, response_code);
891  } else if (url == oauth2_token_gurl_) {
892    OnOAuth2TokenPairFetched(data, status, response_code);
893  } else if (url == get_user_info_gurl_) {
894    OnGetUserInfoFetched(data, status, response_code);
895  } else if (url == merge_session_gurl_) {
896    OnMergeSessionFetched(data, status, response_code);
897  } else if (url == uberauth_token_gurl_) {
898    OnUberAuthTokenFetch(data, status, response_code);
899  } else if (url == oauth_login_gurl_) {
900    OnOAuthLoginFetched(data, status, response_code);
901  } else if (url == oauth2_revoke_gurl_) {
902    OnOAuth2RevokeTokenFetched(data, status, response_code);
903  } else if (url == list_accounts_gurl_) {
904    OnListAccountsFetched(data, status, response_code);
905  } else {
906    NOTREACHED();
907  }
908}
909
910// static
911bool GaiaAuthFetcher::IsSecondFactorSuccess(
912    const std::string& alleged_error) {
913  return alleged_error.find(kSecondFactor) !=
914      std::string::npos;
915}
916