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