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