Home | History | Annotate | Download | only in gaia
      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 
     30 namespace {
     31 const int kLoadFlagsIgnoreCookies = net::LOAD_DO_NOT_SEND_COOKIES |
     32                                     net::LOAD_DO_NOT_SAVE_COOKIES;
     33 
     34 static 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 
     39 bool 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
     60 const char GaiaAuthFetcher::kClientLoginFormat[] =
     61     "Email=%s&"
     62     "Passwd=%s&"
     63     "PersistentCookie=%s&"
     64     "accountType=%s&"
     65     "source=%s&"
     66     "service=%s";
     67 // static
     68 const 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
     78 const char GaiaAuthFetcher::kIssueAuthTokenFormat[] =
     79     "SID=%s&"
     80     "LSID=%s&"
     81     "service=%s&"
     82     "Session=%s";
     83 // static
     84 const char GaiaAuthFetcher::kClientLoginToOAuth2BodyFormat[] =
     85     "scope=%s&client_id=%s";
     86 // static
     87 const char GaiaAuthFetcher::kOAuth2CodeToTokenPairBodyFormat[] =
     88     "scope=%s&"
     89     "grant_type=authorization_code&"
     90     "client_id=%s&"
     91     "client_secret=%s&"
     92     "code=%s";
     93 // static
     94 const char GaiaAuthFetcher::kOAuth2RevokeTokenBodyFormat[] =
     95     "token=%s";
     96 // static
     97 const char GaiaAuthFetcher::kGetUserInfoFormat[] =
     98     "LSID=%s";
     99 // static
    100 const char GaiaAuthFetcher::kMergeSessionFormat[] =
    101     "uberauth=%s&"
    102     "continue=%s&"
    103     "source=%s";
    104 // static
    105 const char GaiaAuthFetcher::kUberAuthTokenURLFormat[] =
    106     "?source=%s&"
    107     "issueuberauth=1";
    108 
    109 const char GaiaAuthFetcher::kOAuthLoginFormat[] = "service=%s&source=%s";
    110 
    111 // static
    112 const char GaiaAuthFetcher::kAccountDeletedError[] = "AccountDeleted";
    113 const char GaiaAuthFetcher::kAccountDeletedErrorCode[] = "adel";
    114 // static
    115 const char GaiaAuthFetcher::kAccountDisabledError[] = "AccountDisabled";
    116 const char GaiaAuthFetcher::kAccountDisabledErrorCode[] = "adis";
    117 // static
    118 const char GaiaAuthFetcher::kBadAuthenticationError[] = "BadAuthentication";
    119 const char GaiaAuthFetcher::kBadAuthenticationErrorCode[] = "badauth";
    120 // static
    121 const char GaiaAuthFetcher::kCaptchaError[] = "CaptchaRequired";
    122 const char GaiaAuthFetcher::kCaptchaErrorCode[] = "cr";
    123 // static
    124 const char GaiaAuthFetcher::kServiceUnavailableError[] =
    125     "ServiceUnavailable";
    126 const char GaiaAuthFetcher::kServiceUnavailableErrorCode[] =
    127     "ire";
    128 // static
    129 const char GaiaAuthFetcher::kErrorParam[] = "Error";
    130 // static
    131 const char GaiaAuthFetcher::kErrorUrlParam[] = "Url";
    132 // static
    133 const char GaiaAuthFetcher::kCaptchaUrlParam[] = "CaptchaUrl";
    134 // static
    135 const char GaiaAuthFetcher::kCaptchaTokenParam[] = "CaptchaToken";
    136 
    137 // static
    138 const char GaiaAuthFetcher::kCookiePersistence[] = "true";
    139 // static
    140 // TODO(johnnyg): When hosted accounts are supported by sync,
    141 // we can always use "HOSTED_OR_GOOGLE"
    142 const char GaiaAuthFetcher::kAccountTypeHostedOrGoogle[] =
    143     "HOSTED_OR_GOOGLE";
    144 const char GaiaAuthFetcher::kAccountTypeGoogle[] =
    145     "GOOGLE";
    146 
    147 // static
    148 const char GaiaAuthFetcher::kSecondFactor[] = "Info=InvalidSecondFactor";
    149 
    150 // static
    151 const char GaiaAuthFetcher::kAuthHeaderFormat[] =
    152     "Authorization: GoogleLogin auth=%s";
    153 // static
    154 const char GaiaAuthFetcher::kOAuthHeaderFormat[] = "Authorization: OAuth %s";
    155 // static
    156 const char GaiaAuthFetcher::kOAuth2BearerHeaderFormat[] =
    157     "Authorization: Bearer %s";
    158 // static
    159 const char GaiaAuthFetcher::kClientLoginToOAuth2CookiePartSecure[] = "Secure";
    160 // static
    161 const char GaiaAuthFetcher::kClientLoginToOAuth2CookiePartHttpOnly[] =
    162     "HttpOnly";
    163 // static
    164 const char GaiaAuthFetcher::kClientLoginToOAuth2CookiePartCodePrefix[] =
    165     "oauth_code=";
    166 // static
    167 const int GaiaAuthFetcher::kClientLoginToOAuth2CookiePartCodePrefixLength =
    168     arraysize(GaiaAuthFetcher::kClientLoginToOAuth2CookiePartCodePrefix) - 1;
    169 
    170 GaiaAuthFetcher::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 
    190 GaiaAuthFetcher::~GaiaAuthFetcher() {}
    191 
    192 bool GaiaAuthFetcher::HasPendingFetch() {
    193   return fetch_pending_;
    194 }
    195 
    196 void GaiaAuthFetcher::CancelRequest() {
    197   fetcher_.reset();
    198   fetch_pending_ = false;
    199 }
    200 
    201 // static
    202 net::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
    240 std::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
    281 std::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
    301 std::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
    312 std::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
    329 std::string GaiaAuthFetcher::MakeRevokeTokenBody(
    330     const std::string& auth_token) {
    331   return base::StringPrintf(kOAuth2RevokeTokenBodyFormat, auth_token.c_str());
    332 }
    333 
    334 // static
    335 std::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
    341 std::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
    356 std::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
    363 void 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
    392 std::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
    402 void 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
    428 bool 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
    441 bool 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 
    464 void 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 
    496 void 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 
    514 void 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 
    533 void 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 
    548 void 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 
    572 void 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 
    588 void 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 
    603 void 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 
    628 void 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 
    646 void 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 
    663 void 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
    677 GoogleServiceAuthError 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
    730 GoogleServiceAuthError 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 
    782 void 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 
    798 void 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 
    812 void 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 
    826 void 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 
    854 void GaiaAuthFetcher::OnOAuth2RevokeTokenFetched(
    855     const std::string& data,
    856     const net::URLRequestStatus& status,
    857     int response_code) {
    858   consumer_->OnOAuth2RevokeTokenCompleted();
    859 }
    860 
    861 void 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 
    871 void 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 
    889 void 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 
    899 void 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 
    909 void 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 
    925 void 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
    972 bool GaiaAuthFetcher::IsSecondFactorSuccess(
    973     const std::string& alleged_error) {
    974   return alleged_error.find(kSecondFactor) !=
    975       std::string::npos;
    976 }
    977