gaia_auth_fetcher.cc revision d0247b1b59f9c528cb6df88b4f2b9afaf80d181e
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    "?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_(GaiaUrls::GetInstance()->oauth1_login_url().Resolve(
183          base::StringPrintf(kUberAuthTokenURLFormat, 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      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  client_login_to_oauth2_gurl_ =
555      GaiaUrls::GetInstance()->client_login_to_oauth2_url();
556  if (!session_index.empty()) {
557    client_login_to_oauth2_gurl_ =
558        client_login_to_oauth2_gurl_.Resolve("?authuser=" + session_index);
559  }
560
561  fetcher_.reset(CreateGaiaFetcher(getter_,
562                                   request_body_,
563                                   std::string(),
564                                   client_login_to_oauth2_gurl_,
565                                   net::LOAD_NORMAL,
566                                   this));
567  fetch_pending_ = true;
568  fetcher_->Start();
569}
570
571void GaiaAuthFetcher::StartAuthCodeForOAuth2TokenExchange(
572    const std::string& auth_code) {
573  DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
574
575  DVLOG(1) << "Starting OAuth token pair fetch";
576  request_body_ = MakeGetTokenPairBody(auth_code);
577  fetcher_.reset(CreateGaiaFetcher(getter_,
578                                   request_body_,
579                                   std::string(),
580                                   oauth2_token_gurl_,
581                                   kLoadFlagsIgnoreCookies,
582                                   this));
583  fetch_pending_ = true;
584  fetcher_->Start();
585}
586
587void GaiaAuthFetcher::StartGetUserInfo(const std::string& lsid) {
588  DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
589
590  DVLOG(1) << "Starting GetUserInfo for lsid=" << lsid;
591  request_body_ = MakeGetUserInfoBody(lsid);
592  fetcher_.reset(CreateGaiaFetcher(getter_,
593                                   request_body_,
594                                   std::string(),
595                                   get_user_info_gurl_,
596                                   kLoadFlagsIgnoreCookies,
597                                   this));
598  fetch_pending_ = true;
599  fetcher_->Start();
600}
601
602void GaiaAuthFetcher::StartMergeSession(const std::string& uber_token) {
603  DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
604
605  DVLOG(1) << "Starting MergeSession with uber_token=" << uber_token;
606
607  // The continue URL is a required parameter of the MergeSession API, but in
608  // this case we don't actually need or want to navigate to it.  Setting it to
609  // an arbitrary Google URL.
610  //
611  // In order for the new session to be merged correctly, the server needs to
612  // know what sessions already exist in the browser.  The fetcher needs to be
613  // created such that it sends the cookies with the request, which is
614  // different from all other requests the fetcher can make.
615  std::string continue_url("http://www.google.com");
616  request_body_ = MakeMergeSessionBody(uber_token, continue_url, source_);
617  fetcher_.reset(CreateGaiaFetcher(getter_,
618                                   request_body_,
619                                   std::string(),
620                                   merge_session_gurl_,
621                                   net::LOAD_NORMAL,
622                                   this));
623  fetch_pending_ = true;
624  fetcher_->Start();
625}
626
627void GaiaAuthFetcher::StartTokenFetchForUberAuthExchange(
628    const std::string& access_token) {
629  DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
630
631  DVLOG(1) << "Starting StartTokenFetchForUberAuthExchange with access_token="
632           << access_token;
633  std::string authentication_header =
634      base::StringPrintf(kOAuthHeaderFormat, access_token.c_str());
635  fetcher_.reset(CreateGaiaFetcher(getter_,
636                                   std::string(),
637                                   authentication_header,
638                                   uberauth_token_gurl_,
639                                   kLoadFlagsIgnoreCookies,
640                                   this));
641  fetch_pending_ = true;
642  fetcher_->Start();
643}
644
645void GaiaAuthFetcher::StartOAuthLogin(const std::string& access_token,
646                                      const std::string& service) {
647  DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
648
649  request_body_ = MakeOAuthLoginBody(service, source_);
650  std::string authentication_header =
651      base::StringPrintf(kOAuth2BearerHeaderFormat, access_token.c_str());
652  fetcher_.reset(CreateGaiaFetcher(getter_,
653                                   request_body_,
654                                   authentication_header,
655                                   oauth_login_gurl_,
656                                   kLoadFlagsIgnoreCookies,
657                                   this));
658  fetch_pending_ = true;
659  fetcher_->Start();
660}
661
662// static
663GoogleServiceAuthError GaiaAuthFetcher::GenerateAuthError(
664    const std::string& data,
665    const net::URLRequestStatus& status) {
666  if (!status.is_success()) {
667    if (status.status() == net::URLRequestStatus::CANCELED) {
668      return GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED);
669    } else {
670      DLOG(WARNING) << "Could not reach Google Accounts servers: errno "
671          << status.error();
672      return GoogleServiceAuthError::FromConnectionError(status.error());
673    }
674  } else {
675    if (IsSecondFactorSuccess(data)) {
676      return GoogleServiceAuthError(GoogleServiceAuthError::TWO_FACTOR);
677    }
678
679    std::string error;
680    std::string url;
681    std::string captcha_url;
682    std::string captcha_token;
683    ParseClientLoginFailure(data, &error, &url, &captcha_url, &captcha_token);
684    DLOG(WARNING) << "ClientLogin failed with " << error;
685
686    if (error == kCaptchaError) {
687      GURL image_url(
688          GaiaUrls::GetInstance()->captcha_base_url().Resolve(captcha_url));
689      GURL unlock_url(url);
690      return GoogleServiceAuthError::FromClientLoginCaptchaChallenge(
691          captcha_token, image_url, unlock_url);
692    }
693    if (error == kAccountDeletedError)
694      return GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DELETED);
695    if (error == kAccountDisabledError)
696      return GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DISABLED);
697    if (error == kBadAuthenticationError) {
698      return GoogleServiceAuthError(
699          GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);
700    }
701    if (error == kServiceUnavailableError) {
702      return GoogleServiceAuthError(
703          GoogleServiceAuthError::SERVICE_UNAVAILABLE);
704    }
705
706    DLOG(WARNING) << "Incomprehensible response from Google Accounts servers.";
707    return GoogleServiceAuthError(
708        GoogleServiceAuthError::SERVICE_UNAVAILABLE);
709  }
710
711  NOTREACHED();
712  return GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE);
713}
714
715// static
716GoogleServiceAuthError GaiaAuthFetcher::GenerateOAuthLoginError(
717    const std::string& data,
718    const net::URLRequestStatus& status) {
719  if (!status.is_success()) {
720    if (status.status() == net::URLRequestStatus::CANCELED) {
721      return GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED);
722    } else {
723      DLOG(WARNING) << "Could not reach Google Accounts servers: errno "
724          << status.error();
725      return GoogleServiceAuthError::FromConnectionError(status.error());
726    }
727  } else {
728    if (IsSecondFactorSuccess(data)) {
729      return GoogleServiceAuthError(GoogleServiceAuthError::TWO_FACTOR);
730    }
731
732    std::string error;
733    std::string url;
734    std::string captcha_url;
735    std::string captcha_token;
736    ParseClientLoginFailure(data, &error, &url, &captcha_url, &captcha_token);
737    LOG(WARNING) << "OAuthLogin failed with " << error;
738
739    if (error == kCaptchaErrorCode) {
740      GURL image_url(
741          GaiaUrls::GetInstance()->captcha_base_url().Resolve(captcha_url));
742      GURL unlock_url(url);
743      return GoogleServiceAuthError::FromClientLoginCaptchaChallenge(
744          captcha_token, image_url, unlock_url);
745    }
746    if (error == kAccountDeletedErrorCode)
747      return GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DELETED);
748    if (error == kAccountDisabledErrorCode)
749      return GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DISABLED);
750    if (error == kBadAuthenticationErrorCode) {
751      return GoogleServiceAuthError(
752          GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);
753    }
754    if (error == kServiceUnavailableErrorCode) {
755      return GoogleServiceAuthError(
756          GoogleServiceAuthError::SERVICE_UNAVAILABLE);
757    }
758
759    DLOG(WARNING) << "Incomprehensible response from Google Accounts servers.";
760    return GoogleServiceAuthError(
761        GoogleServiceAuthError::SERVICE_UNAVAILABLE);
762  }
763
764  NOTREACHED();
765  return GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE);
766}
767
768void GaiaAuthFetcher::OnClientLoginFetched(const std::string& data,
769                                           const net::URLRequestStatus& status,
770                                           int response_code) {
771  if (status.is_success() && response_code == net::HTTP_OK) {
772    DVLOG(1) << "ClientLogin successful!";
773    std::string sid;
774    std::string lsid;
775    std::string token;
776    ParseClientLoginResponse(data, &sid, &lsid, &token);
777    consumer_->OnClientLoginSuccess(
778        GaiaAuthConsumer::ClientLoginResult(sid, lsid, token, data));
779  } else {
780    consumer_->OnClientLoginFailure(GenerateAuthError(data, status));
781  }
782}
783
784void GaiaAuthFetcher::OnIssueAuthTokenFetched(
785    const std::string& data,
786    const net::URLRequestStatus& status,
787    int response_code) {
788  if (status.is_success() && response_code == net::HTTP_OK) {
789    // Only the bare token is returned in the body of this Gaia call
790    // without any padding.
791    consumer_->OnIssueAuthTokenSuccess(requested_service_, data);
792  } else {
793    consumer_->OnIssueAuthTokenFailure(requested_service_,
794        GenerateAuthError(data, status));
795  }
796}
797
798void GaiaAuthFetcher::OnClientLoginToOAuth2Fetched(
799    const std::string& data,
800    const net::ResponseCookies& cookies,
801    const net::URLRequestStatus& status,
802    int response_code) {
803  if (status.is_success() && response_code == net::HTTP_OK) {
804    std::string auth_code;
805    ParseClientLoginToOAuth2Response(cookies, &auth_code);
806    StartAuthCodeForOAuth2TokenExchange(auth_code);
807  } else {
808    consumer_->OnClientOAuthFailure(GenerateAuthError(data, status));
809  }
810}
811
812void GaiaAuthFetcher::OnOAuth2TokenPairFetched(
813    const std::string& data,
814    const net::URLRequestStatus& status,
815    int response_code) {
816  std::string refresh_token;
817  std::string access_token;
818  int expires_in_secs = 0;
819
820  bool success = false;
821  if (status.is_success() && response_code == net::HTTP_OK) {
822    scoped_ptr<base::Value> value(base::JSONReader::Read(data));
823    if (value.get() && value->GetType() == base::Value::TYPE_DICTIONARY) {
824      base::DictionaryValue* dict =
825          static_cast<base::DictionaryValue*>(value.get());
826      success = ExtractOAuth2TokenPairResponse(dict, &refresh_token,
827                                               &access_token, &expires_in_secs);
828    }
829  }
830
831  if (success) {
832    consumer_->OnClientOAuthSuccess(
833        GaiaAuthConsumer::ClientOAuthResult(refresh_token, access_token,
834                                            expires_in_secs));
835  } else {
836    consumer_->OnClientOAuthFailure(GenerateAuthError(data, status));
837  }
838}
839
840void GaiaAuthFetcher::OnOAuth2RevokeTokenFetched(
841    const std::string& data,
842    const net::URLRequestStatus& status,
843    int response_code) {
844  consumer_->OnOAuth2RevokeTokenCompleted();
845}
846
847void GaiaAuthFetcher::OnGetUserInfoFetched(
848    const std::string& data,
849    const net::URLRequestStatus& status,
850    int response_code) {
851  if (status.is_success() && response_code == net::HTTP_OK) {
852    std::vector<std::pair<std::string, std::string> > tokens;
853    UserInfoMap matches;
854    base::SplitStringIntoKeyValuePairs(data, '=', '\n', &tokens);
855    std::vector<std::pair<std::string, std::string> >::iterator i;
856    for (i = tokens.begin(); i != tokens.end(); ++i) {
857      matches[i->first] = i->second;
858    }
859    consumer_->OnGetUserInfoSuccess(matches);
860  } else {
861    consumer_->OnGetUserInfoFailure(GenerateAuthError(data, status));
862  }
863}
864
865void GaiaAuthFetcher::OnMergeSessionFetched(const std::string& data,
866                                            const net::URLRequestStatus& status,
867                                            int response_code) {
868  if (status.is_success() && response_code == net::HTTP_OK) {
869    consumer_->OnMergeSessionSuccess(data);
870  } else {
871    consumer_->OnMergeSessionFailure(GenerateAuthError(data, status));
872  }
873}
874
875void GaiaAuthFetcher::OnUberAuthTokenFetch(const std::string& data,
876                                           const net::URLRequestStatus& status,
877                                           int response_code) {
878  if (status.is_success() && response_code == net::HTTP_OK) {
879    consumer_->OnUberAuthTokenSuccess(data);
880  } else {
881    consumer_->OnUberAuthTokenFailure(GenerateAuthError(data, status));
882  }
883}
884
885void GaiaAuthFetcher::OnOAuthLoginFetched(const std::string& data,
886                                          const net::URLRequestStatus& status,
887                                          int response_code) {
888  if (status.is_success() && response_code == net::HTTP_OK) {
889    DVLOG(1) << "ClientLogin successful!";
890    std::string sid;
891    std::string lsid;
892    std::string token;
893    ParseClientLoginResponse(data, &sid, &lsid, &token);
894    consumer_->OnClientLoginSuccess(
895        GaiaAuthConsumer::ClientLoginResult(sid, lsid, token, data));
896  } else {
897    consumer_->OnClientLoginFailure(GenerateAuthError(data, status));
898  }
899}
900
901void GaiaAuthFetcher::OnURLFetchComplete(const net::URLFetcher* source) {
902  fetch_pending_ = false;
903  // Some of the GAIA requests perform redirects, which results in the final
904  // URL of the fetcher not being the original URL requested.  Therefore use
905  // the original URL when determining which OnXXX function to call.
906  const GURL& url = source->GetOriginalURL();
907  const net::URLRequestStatus& status = source->GetStatus();
908  int response_code = source->GetResponseCode();
909  std::string data;
910  source->GetResponseAsString(&data);
911#ifndef NDEBUG
912  std::string headers;
913  if (source->GetResponseHeaders())
914    source->GetResponseHeaders()->GetNormalizedHeaders(&headers);
915  DVLOG(2) << "Response " << url.spec() << ", code = " << response_code << "\n"
916           << headers << "\n";
917  DVLOG(2) << "data: " << data << "\n";
918#endif
919  // Retrieve the response headers from the request.  Must only be called after
920  // the OnURLFetchComplete callback has run.
921  if (url == client_login_gurl_) {
922    OnClientLoginFetched(data, status, response_code);
923  } else if (url == issue_auth_token_gurl_) {
924    OnIssueAuthTokenFetched(data, status, response_code);
925  } else if (url == client_login_to_oauth2_gurl_) {
926    OnClientLoginToOAuth2Fetched(
927        data, source->GetCookies(), status, response_code);
928  } else if (url == oauth2_token_gurl_) {
929    OnOAuth2TokenPairFetched(data, status, response_code);
930  } else if (url == get_user_info_gurl_) {
931    OnGetUserInfoFetched(data, status, response_code);
932  } else if (url == merge_session_gurl_) {
933    OnMergeSessionFetched(data, status, response_code);
934  } else if (url == uberauth_token_gurl_) {
935    OnUberAuthTokenFetch(data, status, response_code);
936  } else if (url == oauth_login_gurl_) {
937    OnOAuthLoginFetched(data, status, response_code);
938  } else if (url == oauth2_revoke_gurl_) {
939    OnOAuth2RevokeTokenFetched(data, status, response_code);
940  } else {
941    NOTREACHED();
942  }
943}
944
945// static
946bool GaiaAuthFetcher::IsSecondFactorSuccess(
947    const std::string& alleged_error) {
948  return alleged_error.find(kSecondFactor) !=
949      std::string::npos;
950}
951