gaia_auth_fetcher.cc revision 5821806d5e7f356e8fa4b058a389a808ea183019
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/string_split.h"
15#include "base/string_util.h"
16#include "base/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_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  return std::find(parts.begin(), parts.end(), part) != parts.end();
36}
37
38bool ExtractOAuth2TokenPairResponse(DictionaryValue* dict,
39                                    std::string* refresh_token,
40                                    std::string* access_token,
41                                    int* expires_in_secs) {
42  DCHECK(refresh_token);
43  DCHECK(access_token);
44  DCHECK(expires_in_secs);
45
46  if (!dict->GetStringWithoutPathExpansion("refresh_token", refresh_token) ||
47      !dict->GetStringWithoutPathExpansion("access_token", access_token) ||
48      !dict->GetIntegerWithoutPathExpansion("expires_in", expires_in_secs)) {
49    return false;
50  }
51
52  return true;
53}
54
55}  // namespace
56
57// TODO(chron): Add sourceless version of this formatter.
58// static
59const char GaiaAuthFetcher::kClientLoginFormat[] =
60    "Email=%s&"
61    "Passwd=%s&"
62    "PersistentCookie=%s&"
63    "accountType=%s&"
64    "source=%s&"
65    "service=%s";
66// static
67const char GaiaAuthFetcher::kClientLoginCaptchaFormat[] =
68    "Email=%s&"
69    "Passwd=%s&"
70    "PersistentCookie=%s&"
71    "accountType=%s&"
72    "source=%s&"
73    "service=%s&"
74    "logintoken=%s&"
75    "logincaptcha=%s";
76// static
77const char GaiaAuthFetcher::kIssueAuthTokenFormat[] =
78    "SID=%s&"
79    "LSID=%s&"
80    "service=%s&"
81    "Session=%s";
82// static
83const char GaiaAuthFetcher::kClientLoginToOAuth2BodyFormat[] =
84    "scope=%s&client_id=%s";
85// static
86const char GaiaAuthFetcher::kOAuth2CodeToTokenPairBodyFormat[] =
87    "scope=%s&"
88    "grant_type=authorization_code&"
89    "client_id=%s&"
90    "client_secret=%s&"
91    "code=%s";
92// static
93const char GaiaAuthFetcher::kGetUserInfoFormat[] =
94    "LSID=%s";
95// static
96const char GaiaAuthFetcher::kMergeSessionFormat[] =
97    "uberauth=%s&"
98    "continue=%s&"
99    "source=%s";
100// static
101const char GaiaAuthFetcher::kUberAuthTokenURLFormat[] =
102    "%s?source=%s&"
103    "issueuberauth=1";
104
105const char GaiaAuthFetcher::kOAuthLoginFormat[] = "service=%s&source=%s";
106
107// static
108const char GaiaAuthFetcher::kAccountDeletedError[] = "AccountDeleted";
109const char GaiaAuthFetcher::kAccountDeletedErrorCode[] = "adel";
110// static
111const char GaiaAuthFetcher::kAccountDisabledError[] = "AccountDisabled";
112const char GaiaAuthFetcher::kAccountDisabledErrorCode[] = "adis";
113// static
114const char GaiaAuthFetcher::kBadAuthenticationError[] = "BadAuthentication";
115const char GaiaAuthFetcher::kBadAuthenticationErrorCode[] = "badauth";
116// static
117const char GaiaAuthFetcher::kCaptchaError[] = "CaptchaRequired";
118const char GaiaAuthFetcher::kCaptchaErrorCode[] = "cr";
119// static
120const char GaiaAuthFetcher::kServiceUnavailableError[] =
121    "ServiceUnavailable";
122const char GaiaAuthFetcher::kServiceUnavailableErrorCode[] =
123    "ire";
124// static
125const char GaiaAuthFetcher::kErrorParam[] = "Error";
126// static
127const char GaiaAuthFetcher::kErrorUrlParam[] = "Url";
128// static
129const char GaiaAuthFetcher::kCaptchaUrlParam[] = "CaptchaUrl";
130// static
131const char GaiaAuthFetcher::kCaptchaTokenParam[] = "CaptchaToken";
132
133// static
134const char GaiaAuthFetcher::kNeedsAdditional[] = "NeedsAdditional";
135// static
136const char GaiaAuthFetcher::kCaptcha[] = "Captcha";
137// static
138const char GaiaAuthFetcher::kTwoFactor[] = "TwoStep";
139
140// static
141const char GaiaAuthFetcher::kCookiePersistence[] = "true";
142// static
143// TODO(johnnyg): When hosted accounts are supported by sync,
144// we can always use "HOSTED_OR_GOOGLE"
145const char GaiaAuthFetcher::kAccountTypeHostedOrGoogle[] =
146    "HOSTED_OR_GOOGLE";
147const char GaiaAuthFetcher::kAccountTypeGoogle[] =
148    "GOOGLE";
149
150// static
151const char GaiaAuthFetcher::kSecondFactor[] = "Info=InvalidSecondFactor";
152
153// static
154const char GaiaAuthFetcher::kAuthHeaderFormat[] =
155    "Authorization: GoogleLogin auth=%s";
156// static
157const char GaiaAuthFetcher::kOAuthHeaderFormat[] = "Authorization: OAuth %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      get_user_info_gurl_(GaiaUrls::GetInstance()->get_user_info_url()),
180      merge_session_gurl_(GaiaUrls::GetInstance()->merge_session_url()),
181      uberauth_token_gurl_(base::StringPrintf(kUberAuthTokenURLFormat,
182          GaiaUrls::GetInstance()->oauth1_login_url().c_str(), source.c_str())),
183      client_oauth_gurl_(GaiaUrls::GetInstance()->client_oauth_url()),
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  if (!headers.empty())
227    to_return->SetExtraRequestHeaders(headers);
228
229  return to_return;
230}
231
232// static
233std::string GaiaAuthFetcher::MakeClientLoginBody(
234    const std::string& username,
235    const std::string& password,
236    const std::string& source,
237    const char* service,
238    const std::string& login_token,
239    const std::string& login_captcha,
240    HostedAccountsSetting allow_hosted_accounts) {
241  std::string encoded_username = net::EscapeUrlEncodedData(username, true);
242  std::string encoded_password = net::EscapeUrlEncodedData(password, true);
243  std::string encoded_login_token = net::EscapeUrlEncodedData(login_token,
244                                                              true);
245  std::string encoded_login_captcha = net::EscapeUrlEncodedData(login_captcha,
246                                                                true);
247
248  const char* account_type = allow_hosted_accounts == HostedAccountsAllowed ?
249      kAccountTypeHostedOrGoogle :
250      kAccountTypeGoogle;
251
252  if (login_token.empty() || login_captcha.empty()) {
253    return base::StringPrintf(kClientLoginFormat,
254                              encoded_username.c_str(),
255                              encoded_password.c_str(),
256                              kCookiePersistence,
257                              account_type,
258                              source.c_str(),
259                              service);
260  }
261
262  return base::StringPrintf(kClientLoginCaptchaFormat,
263                            encoded_username.c_str(),
264                            encoded_password.c_str(),
265                            kCookiePersistence,
266                            account_type,
267                            source.c_str(),
268                            service,
269                            encoded_login_token.c_str(),
270                            encoded_login_captcha.c_str());
271}
272
273// static
274std::string GaiaAuthFetcher::MakeIssueAuthTokenBody(
275    const std::string& sid,
276    const std::string& lsid,
277    const char* const service) {
278  std::string encoded_sid = net::EscapeUrlEncodedData(sid, true);
279  std::string encoded_lsid = net::EscapeUrlEncodedData(lsid, true);
280
281  // All tokens should be session tokens except the gaia auth token.
282  bool session = true;
283  if (!strcmp(service, GaiaConstants::kGaiaService))
284    session = false;
285
286  return base::StringPrintf(kIssueAuthTokenFormat,
287                            encoded_sid.c_str(),
288                            encoded_lsid.c_str(),
289                            service,
290                            session ? "true" : "false");
291}
292
293// static
294std::string GaiaAuthFetcher::MakeGetAuthCodeBody() {
295  std::string encoded_scope = net::EscapeUrlEncodedData(
296      GaiaUrls::GetInstance()->oauth1_login_scope(), true);
297  std::string encoded_client_id = net::EscapeUrlEncodedData(
298      GaiaUrls::GetInstance()->oauth2_chrome_client_id(), true);
299  return StringPrintf(kClientLoginToOAuth2BodyFormat,
300                      encoded_scope.c_str(),
301                      encoded_client_id.c_str());
302}
303
304// static
305std::string GaiaAuthFetcher::MakeGetTokenPairBody(
306    const std::string& auth_code) {
307  std::string encoded_scope = net::EscapeUrlEncodedData(
308      GaiaUrls::GetInstance()->oauth1_login_scope(), true);
309  std::string encoded_client_id = net::EscapeUrlEncodedData(
310      GaiaUrls::GetInstance()->oauth2_chrome_client_id(), true);
311  std::string encoded_client_secret = net::EscapeUrlEncodedData(
312      GaiaUrls::GetInstance()->oauth2_chrome_client_secret(), true);
313  std::string encoded_auth_code = net::EscapeUrlEncodedData(auth_code, true);
314  return StringPrintf(kOAuth2CodeToTokenPairBodyFormat,
315                      encoded_scope.c_str(),
316                      encoded_client_id.c_str(),
317                      encoded_client_secret.c_str(),
318                      encoded_auth_code.c_str());
319}
320
321// static
322std::string GaiaAuthFetcher::MakeGetUserInfoBody(const std::string& lsid) {
323  std::string encoded_lsid = net::EscapeUrlEncodedData(lsid, true);
324  return base::StringPrintf(kGetUserInfoFormat, encoded_lsid.c_str());
325}
326
327// static
328std::string GaiaAuthFetcher::MakeMergeSessionBody(
329    const std::string& auth_token,
330    const std::string& continue_url,
331    const std::string& source) {
332  std::string encoded_auth_token = net::EscapeUrlEncodedData(auth_token, true);
333  std::string encoded_continue_url = net::EscapeUrlEncodedData(continue_url,
334                                                               true);
335  std::string encoded_source = net::EscapeUrlEncodedData(source, true);
336  return base::StringPrintf(kMergeSessionFormat,
337                            encoded_auth_token.c_str(),
338                            encoded_continue_url.c_str(),
339                            encoded_source.c_str());
340}
341
342// static
343std::string GaiaAuthFetcher::MakeGetAuthCodeHeader(
344    const std::string& auth_token) {
345  return StringPrintf(kAuthHeaderFormat, auth_token.c_str());
346}
347
348// Helper method that extracts tokens from a successful reply.
349// static
350void GaiaAuthFetcher::ParseClientLoginResponse(const std::string& data,
351                                               std::string* sid,
352                                               std::string* lsid,
353                                               std::string* token) {
354  using std::vector;
355  using std::pair;
356  using std::string;
357
358  vector<pair<string, string> > tokens;
359  base::SplitStringIntoKeyValuePairs(data, '=', '\n', &tokens);
360  for (vector<pair<string, string> >::iterator i = tokens.begin();
361      i != tokens.end(); ++i) {
362    if (i->first == "SID") {
363      sid->assign(i->second);
364    } else if (i->first == "LSID") {
365      lsid->assign(i->second);
366    } else if (i->first == "Auth") {
367      token->assign(i->second);
368    }
369  }
370}
371
372// static
373std::string GaiaAuthFetcher::MakeClientOAuthBody(
374    const std::string& username,
375    const std::string& password,
376    const std::vector<std::string>& scopes,
377    const std::string& persistent_id,
378    const std::string& friendly_name,
379    const std::string& locale) {
380  scoped_ptr<base::DictionaryValue> dict(new base::DictionaryValue);
381  dict->SetString(GaiaConstants::kClientOAuthEmailKey, username);
382  dict->SetString(GaiaConstants::kClientOAuthPasswordKey, password);
383
384  scoped_ptr<base::ListValue> scope_list(new base::ListValue);
385  for (size_t i = 0; i < scopes.size(); ++i)
386    scope_list->Append(base::Value::CreateStringValue(scopes[i]));
387  dict->Set(GaiaConstants::kClientOAuthScopesKey, scope_list.release());
388
389  dict->SetString(GaiaConstants::kClientOAuthOAuth2ClientIdKey,
390                  GaiaUrls::GetInstance()->oauth2_chrome_client_id());
391  // crbug.com/129600: use a less generic friendly name.
392  dict->SetString(GaiaConstants::kClientOAuthFriendlyDeviceNameKey,
393                  friendly_name);
394
395  scoped_ptr<base::ListValue> accepts_challenge_list(new base::ListValue);
396  accepts_challenge_list->Append(base::Value::CreateStringValue(kCaptcha));
397  accepts_challenge_list->Append(base::Value::CreateStringValue(kTwoFactor));
398  dict->Set(GaiaConstants::kClientOAuthAcceptsChallengesKey,
399            accepts_challenge_list.release());
400
401  dict->SetString(GaiaConstants::kClientOAuthLocaleKey, locale);
402  // Chrome presently does not not support a web-fallback for ClientOAuth,
403  // but need to hardcode an arbitrary one here since the endpoint expects it.
404  dict->SetString(GaiaConstants::kClientOAuthFallbackNameKey, "GetOAuth2Token");
405
406  std::string json_string;
407  base::JSONWriter::Write(dict.get(), &json_string);
408  return json_string;
409}
410
411// static
412std::string GaiaAuthFetcher::MakeClientOAuthChallengeResponseBody(
413    const std::string& name,
414    const std::string& token,
415    const std::string& solution) {
416  scoped_ptr<base::DictionaryValue> dict(new base::DictionaryValue);
417  std::string field_name = name == kTwoFactor ? "otp" : "solution";
418
419  scoped_ptr<base::DictionaryValue> challenge_reply(new base::DictionaryValue);
420  challenge_reply->SetString(GaiaConstants::kClientOAuthNameKey, name);
421  challenge_reply->SetString(GaiaConstants::kClientOAuthChallengeTokenKey,
422                             token);
423  challenge_reply->SetString(field_name, solution);
424  dict->Set(GaiaConstants::kClientOAuthchallengeReplyKey,
425            challenge_reply.release());
426
427  std::string json_string;
428  base::JSONWriter::Write(dict.get(), &json_string);
429  return json_string;
430}
431
432// static
433std::string GaiaAuthFetcher::MakeOAuthLoginBody(const std::string& service,
434                                                const std::string& source) {
435  std::string encoded_service = net::EscapeUrlEncodedData(service, true);
436  std::string encoded_source = net::EscapeUrlEncodedData(source, true);
437  return StringPrintf(kOAuthLoginFormat, encoded_service.c_str(),
438                      encoded_source.c_str());
439}
440
441// static
442void GaiaAuthFetcher::ParseClientLoginFailure(const std::string& data,
443                                              std::string* error,
444                                              std::string* error_url,
445                                              std::string* captcha_url,
446                                              std::string* captcha_token) {
447  using std::vector;
448  using std::pair;
449  using std::string;
450
451  vector<pair<string, string> > tokens;
452  base::SplitStringIntoKeyValuePairs(data, '=', '\n', &tokens);
453  for (vector<pair<string, string> >::iterator i = tokens.begin();
454       i != tokens.end(); ++i) {
455    if (i->first == kErrorParam) {
456      error->assign(i->second);
457    } else if (i->first == kErrorUrlParam) {
458      error_url->assign(i->second);
459    } else if (i->first == kCaptchaUrlParam) {
460      captcha_url->assign(i->second);
461    } else if (i->first == kCaptchaTokenParam) {
462      captcha_token->assign(i->second);
463    }
464  }
465}
466
467// static
468bool GaiaAuthFetcher::ParseClientLoginToOAuth2Response(
469    const net::ResponseCookies& cookies,
470    std::string* auth_code) {
471  DCHECK(auth_code);
472  net::ResponseCookies::const_iterator iter;
473  for (iter = cookies.begin(); iter != cookies.end(); ++iter) {
474    if (ParseClientLoginToOAuth2Cookie(*iter, auth_code))
475      return true;
476  }
477  return false;
478}
479
480// static
481bool GaiaAuthFetcher::ParseClientLoginToOAuth2Cookie(const std::string& cookie,
482                                                     std::string* auth_code) {
483  std::vector<std::string> parts;
484  base::SplitString(cookie, ';', &parts);
485  // Per documentation, the cookie should have Secure and HttpOnly.
486  if (!CookiePartsContains(parts, kClientLoginToOAuth2CookiePartSecure) ||
487      !CookiePartsContains(parts, kClientLoginToOAuth2CookiePartHttpOnly)) {
488    return false;
489  }
490
491  std::vector<std::string>::const_iterator iter;
492  for (iter = parts.begin(); iter != parts.end(); ++iter) {
493    const std::string& part = *iter;
494    if (StartsWithASCII(
495        part, kClientLoginToOAuth2CookiePartCodePrefix, false)) {
496      auth_code->assign(part.substr(
497          kClientLoginToOAuth2CookiePartCodePrefixLength));
498      return true;
499    }
500  }
501  return false;
502}
503
504// static
505GoogleServiceAuthError
506GaiaAuthFetcher::GenerateClientOAuthError(const std::string& data,
507                                          const net::URLRequestStatus& status) {
508  scoped_ptr<base::Value> value(base::JSONReader::Read(data));
509  if (!value.get() || value->GetType() != base::Value::TYPE_DICTIONARY)
510    return GenerateAuthError(data, status);
511  DictionaryValue* dict = static_cast<DictionaryValue*>(value.get());
512
513  std::string cause;
514  if (!dict->GetStringWithoutPathExpansion("cause", &cause))
515    return GoogleServiceAuthError::FromClientOAuthError(data);
516
517  if (cause != kNeedsAdditional)
518    return GoogleServiceAuthError::FromClientOAuthError(data);
519
520  DictionaryValue* challenge;
521  if (!dict->GetDictionaryWithoutPathExpansion("challenge", &challenge))
522    return GoogleServiceAuthError::FromClientOAuthError(data);
523
524  std::string name;
525  if (!challenge->GetStringWithoutPathExpansion("name", &name))
526    return GoogleServiceAuthError::FromClientOAuthError(data);
527
528  if (name == kCaptcha) {
529    std::string token;
530    std::string audio_url;
531    std::string image_url;
532    int image_width;
533    int image_height;
534    if (!challenge->GetStringWithoutPathExpansion("challenge_token", &token) ||
535        !challenge->GetStringWithoutPathExpansion("audio_url", &audio_url) ||
536        !challenge->GetStringWithoutPathExpansion("image_url", &image_url) ||
537        !challenge->GetIntegerWithoutPathExpansion("image_width",
538                                                   &image_width) ||
539        !challenge->GetIntegerWithoutPathExpansion("image_height",
540                                                   &image_height)) {
541      return GoogleServiceAuthError::FromClientOAuthError(data);
542    }
543    return GoogleServiceAuthError::FromCaptchaChallenge(token, GURL(audio_url),
544                                                        GURL(image_url),
545                                                        image_width,
546                                                        image_height);
547  } else if (name == kTwoFactor) {
548    std::string token;
549    std::string prompt_text;
550    std::string alternate_text;
551    int field_length;
552
553    // The protocol doc says these are required, but in practice they are not
554    // returned.  So only a missing challenge token will cause an error here.
555    challenge->GetStringWithoutPathExpansion("prompt_text", &prompt_text);
556    challenge->GetStringWithoutPathExpansion("alternate_text", &alternate_text);
557    challenge->GetIntegerWithoutPathExpansion("field_length", &field_length);
558    if (!challenge->GetStringWithoutPathExpansion("challenge_token", &token))
559      return GoogleServiceAuthError::FromClientOAuthError(data);
560
561    return GoogleServiceAuthError::FromSecondFactorChallenge(token, prompt_text,
562                                                             alternate_text,
563                                                             field_length);
564  }
565
566  return GoogleServiceAuthError::FromClientOAuthError(data);
567}
568
569void GaiaAuthFetcher::StartClientLogin(
570    const std::string& username,
571    const std::string& password,
572    const char* const service,
573    const std::string& login_token,
574    const std::string& login_captcha,
575    HostedAccountsSetting allow_hosted_accounts) {
576
577  DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
578
579  // This class is thread agnostic, so be sure to call this only on the
580  // same thread each time.
581  DVLOG(1) << "Starting new ClientLogin fetch for:" << username;
582
583  // Must outlive fetcher_.
584  request_body_ = MakeClientLoginBody(username,
585                                      password,
586                                      source_,
587                                      service,
588                                      login_token,
589                                      login_captcha,
590                                      allow_hosted_accounts);
591  fetcher_.reset(CreateGaiaFetcher(getter_,
592                                   request_body_,
593                                   "",
594                                   client_login_gurl_,
595                                   kLoadFlagsIgnoreCookies,
596                                   this));
597  fetch_pending_ = true;
598  fetcher_->Start();
599}
600
601void GaiaAuthFetcher::StartIssueAuthToken(const std::string& sid,
602                                          const std::string& lsid,
603                                          const char* const service) {
604  DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
605
606  DVLOG(1) << "Starting IssueAuthToken for: " << service;
607  requested_service_ = service;
608  request_body_ = MakeIssueAuthTokenBody(sid, lsid, service);
609  fetcher_.reset(CreateGaiaFetcher(getter_,
610                                   request_body_,
611                                   "",
612                                   issue_auth_token_gurl_,
613                                   kLoadFlagsIgnoreCookies,
614                                   this));
615  fetch_pending_ = true;
616  fetcher_->Start();
617}
618
619void GaiaAuthFetcher::StartLsoForOAuthLoginTokenExchange(
620    const std::string& auth_token) {
621  DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
622
623  DVLOG(1) << "Starting OAuth login token exchange with auth_token";
624  request_body_ = MakeGetAuthCodeBody();
625  client_login_to_oauth2_gurl_ =
626      GURL(GaiaUrls::GetInstance()->client_login_to_oauth2_url());
627
628  fetcher_.reset(CreateGaiaFetcher(getter_,
629                                   request_body_,
630                                   MakeGetAuthCodeHeader(auth_token),
631                                   client_login_to_oauth2_gurl_,
632                                   kLoadFlagsIgnoreCookies,
633                                   this));
634  fetch_pending_ = true;
635  fetcher_->Start();
636}
637
638void GaiaAuthFetcher::StartCookieForOAuthLoginTokenExchange(
639    const std::string& session_index) {
640  DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
641
642  DVLOG(1) << "Starting OAuth login token fetch with cookie jar";
643  request_body_ = MakeGetAuthCodeBody();
644
645  std::string url = GaiaUrls::GetInstance()->client_login_to_oauth2_url();
646  if (!session_index.empty())
647    url += "?authuser=" + session_index;
648
649  client_login_to_oauth2_gurl_ = GURL(url);
650
651  fetcher_.reset(CreateGaiaFetcher(getter_,
652                                   request_body_,
653                                   "",
654                                   client_login_to_oauth2_gurl_,
655                                   net::LOAD_NORMAL,
656                                   this));
657  fetch_pending_ = true;
658  fetcher_->Start();
659}
660
661void GaiaAuthFetcher::StartGetUserInfo(const std::string& lsid) {
662  DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
663
664  DVLOG(1) << "Starting GetUserInfo for lsid=" << lsid;
665  request_body_ = MakeGetUserInfoBody(lsid);
666  fetcher_.reset(CreateGaiaFetcher(getter_,
667                                   request_body_,
668                                   "",
669                                   get_user_info_gurl_,
670                                   kLoadFlagsIgnoreCookies,
671                                   this));
672  fetch_pending_ = true;
673  fetcher_->Start();
674}
675
676void GaiaAuthFetcher::StartMergeSession(const std::string& uber_token) {
677  DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
678
679  DVLOG(1) << "Starting MergeSession with uber_token=" << uber_token;
680
681  // The continue URL is a required parameter of the MergeSession API, but in
682  // this case we don't actually need or want to navigate to it.  Setting it to
683  // an arbitrary Google URL.
684  //
685  // In order for the new session to be merged correctly, the server needs to
686  // know what sessions already exist in the browser.  The fetcher needs to be
687  // created such that it sends the cookies with the request, which is
688  // different from all other requests the fetcher can make.
689  std::string continue_url("http://www.google.com");
690  request_body_ = MakeMergeSessionBody(uber_token, continue_url, source_);
691  fetcher_.reset(CreateGaiaFetcher(getter_,
692                                   request_body_,
693                                   "",
694                                   merge_session_gurl_,
695                                   net::LOAD_NORMAL,
696                                   this));
697  fetch_pending_ = true;
698  fetcher_->Start();
699}
700
701void GaiaAuthFetcher::StartTokenFetchForUberAuthExchange(
702    const std::string& access_token) {
703  DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
704
705  DVLOG(1) << "Starting StartTokenFetchForUberAuthExchange with access_token="
706           << access_token;
707  std::string authentication_header =
708      base::StringPrintf(kOAuthHeaderFormat, access_token.c_str());
709  fetcher_.reset(CreateGaiaFetcher(getter_,
710                                   "",
711                                   authentication_header,
712                                   uberauth_token_gurl_,
713                                   kLoadFlagsIgnoreCookies,
714                                   this));
715  fetch_pending_ = true;
716  fetcher_->Start();
717}
718
719void GaiaAuthFetcher::StartClientOAuth(const std::string& username,
720                                       const std::string& password,
721                                       const std::vector<std::string>& scopes,
722                                       const std::string& persistent_id,
723                                       const std::string& locale) {
724  DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
725
726  request_body_ = MakeClientOAuthBody(username, password, scopes, persistent_id,
727                                      source_, locale);
728  fetcher_.reset(CreateGaiaFetcher(getter_,
729                                   request_body_,
730                                   "",
731                                   client_oauth_gurl_,
732                                   kLoadFlagsIgnoreCookies,
733                                   this));
734  fetch_pending_ = true;
735  fetcher_->Start();
736}
737
738void GaiaAuthFetcher::StartClientOAuthChallengeResponse(
739    GoogleServiceAuthError::State type,
740    const std::string& token,
741    const std::string& solution) {
742  DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
743
744  std::string name;
745  switch (type) {
746    case GoogleServiceAuthError::CAPTCHA_REQUIRED:
747      name = kCaptcha;
748      break;
749    case GoogleServiceAuthError::TWO_FACTOR:
750      name = kTwoFactor;
751      break;
752    default:
753      NOTREACHED();
754  }
755
756  request_body_ = MakeClientOAuthChallengeResponseBody(name, token, solution);
757  fetcher_.reset(CreateGaiaFetcher(getter_,
758                                   request_body_,
759                                   "",
760                                   client_oauth_gurl_,
761                                   kLoadFlagsIgnoreCookies,
762                                   this));
763  fetch_pending_ = true;
764  fetcher_->Start();
765}
766
767void GaiaAuthFetcher::StartOAuthLogin(const std::string& access_token,
768                                      const std::string& service) {
769  DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
770
771  request_body_ = MakeOAuthLoginBody(service, source_);
772  std::string authentication_header =
773      base::StringPrintf("Authorization: Bearer %s", access_token.c_str());
774  fetcher_.reset(CreateGaiaFetcher(getter_,
775                                   request_body_,
776                                   authentication_header,
777                                   oauth_login_gurl_,
778                                   kLoadFlagsIgnoreCookies,
779                                   this));
780  fetch_pending_ = true;
781  fetcher_->Start();
782}
783
784// static
785GoogleServiceAuthError GaiaAuthFetcher::GenerateAuthError(
786    const std::string& data,
787    const net::URLRequestStatus& status) {
788  if (!status.is_success()) {
789    if (status.status() == net::URLRequestStatus::CANCELED) {
790      return GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED);
791    } else {
792      DLOG(WARNING) << "Could not reach Google Accounts servers: errno "
793          << status.error();
794      return GoogleServiceAuthError::FromConnectionError(status.error());
795    }
796  } else {
797    if (IsSecondFactorSuccess(data)) {
798      return GoogleServiceAuthError(GoogleServiceAuthError::TWO_FACTOR);
799    }
800
801    std::string error;
802    std::string url;
803    std::string captcha_url;
804    std::string captcha_token;
805    ParseClientLoginFailure(data, &error, &url, &captcha_url, &captcha_token);
806    DLOG(WARNING) << "ClientLogin failed with " << error;
807
808    if (error == kCaptchaError) {
809      GURL image_url(
810          GaiaUrls::GetInstance()->captcha_url_prefix() + captcha_url);
811      GURL unlock_url(url);
812      return GoogleServiceAuthError::FromClientLoginCaptchaChallenge(
813          captcha_token, image_url, unlock_url);
814    }
815    if (error == kAccountDeletedError)
816      return GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DELETED);
817    if (error == kAccountDisabledError)
818      return GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DISABLED);
819    if (error == kBadAuthenticationError) {
820      return GoogleServiceAuthError(
821          GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);
822    }
823    if (error == kServiceUnavailableError) {
824      return GoogleServiceAuthError(
825          GoogleServiceAuthError::SERVICE_UNAVAILABLE);
826    }
827
828    DLOG(WARNING) << "Incomprehensible response from Google Accounts servers.";
829    return GoogleServiceAuthError(
830        GoogleServiceAuthError::SERVICE_UNAVAILABLE);
831  }
832
833  NOTREACHED();
834  return GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE);
835}
836
837// static
838GoogleServiceAuthError GaiaAuthFetcher::GenerateOAuthLoginError(
839    const std::string& data,
840    const net::URLRequestStatus& status) {
841  if (!status.is_success()) {
842    if (status.status() == net::URLRequestStatus::CANCELED) {
843      return GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED);
844    } else {
845      DLOG(WARNING) << "Could not reach Google Accounts servers: errno "
846          << status.error();
847      return GoogleServiceAuthError::FromConnectionError(status.error());
848    }
849  } else {
850    if (IsSecondFactorSuccess(data)) {
851      return GoogleServiceAuthError(GoogleServiceAuthError::TWO_FACTOR);
852    }
853
854    std::string error;
855    std::string url;
856    std::string captcha_url;
857    std::string captcha_token;
858    ParseClientLoginFailure(data, &error, &url, &captcha_url, &captcha_token);
859    LOG(WARNING) << "OAuthLogin failed with " << error;
860
861    if (error == kCaptchaErrorCode) {
862      GURL image_url(
863          GaiaUrls::GetInstance()->captcha_url_prefix() + captcha_url);
864      GURL unlock_url(url);
865      return GoogleServiceAuthError::FromClientLoginCaptchaChallenge(
866          captcha_token, image_url, unlock_url);
867    }
868    if (error == kAccountDeletedErrorCode)
869      return GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DELETED);
870    if (error == kAccountDisabledErrorCode)
871      return GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DISABLED);
872    if (error == kBadAuthenticationErrorCode) {
873      return GoogleServiceAuthError(
874          GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);
875    }
876    if (error == kServiceUnavailableErrorCode) {
877      return GoogleServiceAuthError(
878          GoogleServiceAuthError::SERVICE_UNAVAILABLE);
879    }
880
881    DLOG(WARNING) << "Incomprehensible response from Google Accounts servers.";
882    return GoogleServiceAuthError(
883        GoogleServiceAuthError::SERVICE_UNAVAILABLE);
884  }
885
886  NOTREACHED();
887  return GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE);
888}
889
890void GaiaAuthFetcher::OnClientLoginFetched(const std::string& data,
891                                           const net::URLRequestStatus& status,
892                                           int response_code) {
893  if (status.is_success() && response_code == net::HTTP_OK) {
894    DVLOG(1) << "ClientLogin successful!";
895    std::string sid;
896    std::string lsid;
897    std::string token;
898    ParseClientLoginResponse(data, &sid, &lsid, &token);
899    consumer_->OnClientLoginSuccess(
900        GaiaAuthConsumer::ClientLoginResult(sid, lsid, token, data));
901  } else {
902    consumer_->OnClientLoginFailure(GenerateAuthError(data, status));
903  }
904}
905
906void GaiaAuthFetcher::OnIssueAuthTokenFetched(
907    const std::string& data,
908    const net::URLRequestStatus& status,
909    int response_code) {
910  if (status.is_success() && response_code == net::HTTP_OK) {
911    // Only the bare token is returned in the body of this Gaia call
912    // without any padding.
913    consumer_->OnIssueAuthTokenSuccess(requested_service_, data);
914  } else {
915    consumer_->OnIssueAuthTokenFailure(requested_service_,
916        GenerateAuthError(data, status));
917  }
918}
919
920void GaiaAuthFetcher::OnClientLoginToOAuth2Fetched(
921    const std::string& data,
922    const net::ResponseCookies& cookies,
923    const net::URLRequestStatus& status,
924    int response_code) {
925  if (status.is_success() && response_code == net::HTTP_OK) {
926    std::string auth_code;
927    ParseClientLoginToOAuth2Response(cookies, &auth_code);
928    StartOAuth2TokenPairFetch(auth_code);
929  } else {
930    consumer_->OnClientOAuthFailure(GenerateAuthError(data, status));
931  }
932}
933
934void GaiaAuthFetcher::StartOAuth2TokenPairFetch(const std::string& auth_code) {
935  DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
936
937  DVLOG(1) << "Starting OAuth token pair fetch";
938  request_body_ = MakeGetTokenPairBody(auth_code);
939  fetcher_.reset(CreateGaiaFetcher(getter_,
940                                   request_body_,
941                                   "",
942                                   oauth2_token_gurl_,
943                                   kLoadFlagsIgnoreCookies,
944                                   this));
945  fetch_pending_ = true;
946  fetcher_->Start();
947}
948
949void GaiaAuthFetcher::OnOAuth2TokenPairFetched(
950    const std::string& data,
951    const net::URLRequestStatus& status,
952    int response_code) {
953  std::string refresh_token;
954  std::string access_token;
955  int expires_in_secs = 0;
956
957  bool success = false;
958  if (status.is_success() && response_code == net::HTTP_OK) {
959    scoped_ptr<base::Value> value(base::JSONReader::Read(data));
960    if (value.get() && value->GetType() == base::Value::TYPE_DICTIONARY) {
961      DictionaryValue* dict = static_cast<DictionaryValue*>(value.get());
962      success = ExtractOAuth2TokenPairResponse(dict, &refresh_token,
963                                               &access_token, &expires_in_secs);
964    }
965  }
966
967  if (success) {
968    consumer_->OnClientOAuthSuccess(
969        GaiaAuthConsumer::ClientOAuthResult(refresh_token, access_token,
970                                            expires_in_secs));
971  } else {
972    consumer_->OnClientOAuthFailure(GenerateAuthError(data, status));
973  }
974}
975
976void GaiaAuthFetcher::OnGetUserInfoFetched(
977    const std::string& data,
978    const net::URLRequestStatus& status,
979    int response_code) {
980  if (status.is_success() && response_code == net::HTTP_OK) {
981    std::vector<std::pair<std::string, std::string> > tokens;
982    UserInfoMap matches;
983    base::SplitStringIntoKeyValuePairs(data, '=', '\n', &tokens);
984    std::vector<std::pair<std::string, std::string> >::iterator i;
985    for (i = tokens.begin(); i != tokens.end(); ++i) {
986      matches[i->first] = i->second;
987    }
988    consumer_->OnGetUserInfoSuccess(matches);
989  } else {
990    consumer_->OnGetUserInfoFailure(GenerateAuthError(data, status));
991  }
992}
993
994void GaiaAuthFetcher::OnMergeSessionFetched(const std::string& data,
995                                            const net::URLRequestStatus& status,
996                                            int response_code) {
997  if (status.is_success() && response_code == net::HTTP_OK) {
998    consumer_->OnMergeSessionSuccess(data);
999  } else {
1000    consumer_->OnMergeSessionFailure(GenerateAuthError(data, status));
1001  }
1002}
1003
1004void GaiaAuthFetcher::OnUberAuthTokenFetch(const std::string& data,
1005                                           const net::URLRequestStatus& status,
1006                                           int response_code) {
1007  if (status.is_success() && response_code == net::HTTP_OK) {
1008    consumer_->OnUberAuthTokenSuccess(data);
1009  } else {
1010    consumer_->OnUberAuthTokenFailure(GenerateAuthError(data, status));
1011  }
1012}
1013
1014void GaiaAuthFetcher::OnClientOAuthFetched(const std::string& data,
1015                                           const net::URLRequestStatus& status,
1016                                           int response_code) {
1017  std::string refresh_token;
1018  std::string access_token;
1019  int expires_in_secs = 0;
1020
1021  bool success = false;
1022  if (status.is_success() && response_code == net::HTTP_OK) {
1023    scoped_ptr<base::Value> value(base::JSONReader::Read(data));
1024    if (value.get() && value->GetType() == base::Value::TYPE_DICTIONARY) {
1025      DictionaryValue* dict = static_cast<DictionaryValue*>(value.get());
1026      DictionaryValue* dict_oauth2;
1027      if (dict->GetDictionaryWithoutPathExpansion("oauth2", &dict_oauth2)) {
1028        success = ExtractOAuth2TokenPairResponse(dict_oauth2, &refresh_token,
1029                                                 &access_token,
1030                                                 &expires_in_secs);
1031      }
1032    }
1033  }
1034
1035  // TODO(rogerta): for now this reuses the OnOAuthLoginTokenXXX callbacks
1036  // since the data is exactly the same.  This ignores the optional
1037  // persistent_id data in the response, which we may need to handle.
1038  // If we do, we'll need to modify ExtractOAuth2TokenPairResponse() to parse
1039  // the optional data and declare new consumer callbacks to take it.
1040  if (success) {
1041    consumer_->OnClientOAuthSuccess(
1042        GaiaAuthConsumer::ClientOAuthResult(refresh_token, access_token,
1043                                            expires_in_secs));
1044  } else {
1045    consumer_->OnClientOAuthFailure(GenerateClientOAuthError(data, status));
1046  }
1047}
1048
1049void GaiaAuthFetcher::OnOAuthLoginFetched(const std::string& data,
1050                                          const net::URLRequestStatus& status,
1051                                          int response_code) {
1052  if (status.is_success() && response_code == net::HTTP_OK) {
1053    DVLOG(1) << "ClientLogin successful!";
1054    std::string sid;
1055    std::string lsid;
1056    std::string token;
1057    ParseClientLoginResponse(data, &sid, &lsid, &token);
1058    consumer_->OnClientLoginSuccess(
1059        GaiaAuthConsumer::ClientLoginResult(sid, lsid, token, data));
1060  } else {
1061    consumer_->OnClientLoginFailure(GenerateAuthError(data, status));
1062  }
1063}
1064
1065void GaiaAuthFetcher::OnURLFetchComplete(const net::URLFetcher* source) {
1066  fetch_pending_ = false;
1067  // Some of the GAIA requests perform redirects, which results in the final
1068  // URL of the fetcher not being the original URL requested.  Therefore use
1069  // the original URL when determining which OnXXX function to call.
1070  const GURL& url = source->GetOriginalURL();
1071  const net::URLRequestStatus& status = source->GetStatus();
1072  int response_code = source->GetResponseCode();
1073  std::string data;
1074  source->GetResponseAsString(&data);
1075  DVLOG(2) << "Gaia fetcher response code: " << response_code;
1076  DVLOG(2) << "Gaia fetcher response data: " << data;
1077  if (url == client_login_gurl_) {
1078    OnClientLoginFetched(data, status, response_code);
1079  } else if (url == issue_auth_token_gurl_) {
1080    OnIssueAuthTokenFetched(data, status, response_code);
1081  } else if (url == client_login_to_oauth2_gurl_) {
1082    OnClientLoginToOAuth2Fetched(
1083        data, source->GetCookies(), status, response_code);
1084  } else if (url == oauth2_token_gurl_) {
1085    OnOAuth2TokenPairFetched(data, status, response_code);
1086  } else if (url == get_user_info_gurl_) {
1087    OnGetUserInfoFetched(data, status, response_code);
1088  } else if (url == merge_session_gurl_) {
1089    OnMergeSessionFetched(data, status, response_code);
1090  } else if (url == uberauth_token_gurl_) {
1091    OnUberAuthTokenFetch(data, status, response_code);
1092  } else if (url == client_oauth_gurl_) {
1093    OnClientOAuthFetched(data, status, response_code);
1094  } else if (url == oauth_login_gurl_) {
1095    OnOAuthLoginFetched(data, status, response_code);
1096  } else {
1097    NOTREACHED();
1098  }
1099}
1100
1101// static
1102bool GaiaAuthFetcher::IsSecondFactorSuccess(
1103    const std::string& alleged_error) {
1104  return alleged_error.find(kSecondFactor) !=
1105      std::string::npos;
1106}
1107