Home | History | Annotate | Download | only in gaia
      1 // Copyright (c) 2011 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 "chrome/common/net/gaia/gaia_auth_fetcher.h"
      6 
      7 #include <string>
      8 #include <utility>
      9 #include <vector>
     10 
     11 #include "base/string_split.h"
     12 #include "base/string_util.h"
     13 #include "chrome/common/net/gaia/gaia_auth_consumer.h"
     14 #include "chrome/common/net/gaia/gaia_constants.h"
     15 #include "chrome/common/net/gaia/google_service_auth_error.h"
     16 #include "chrome/common/net/http_return.h"
     17 #include "net/base/load_flags.h"
     18 #include "net/url_request/url_request_context_getter.h"
     19 #include "net/url_request/url_request_status.h"
     20 #include "third_party/libjingle/source/talk/base/urlencode.h"
     21 
     22 // TODO(chron): Add sourceless version of this formatter.
     23 // static
     24 const char GaiaAuthFetcher::kClientLoginFormat[] =
     25     "Email=%s&"
     26     "Passwd=%s&"
     27     "PersistentCookie=%s&"
     28     "accountType=%s&"
     29     "source=%s&"
     30     "service=%s";
     31 // static
     32 const char GaiaAuthFetcher::kClientLoginCaptchaFormat[] =
     33     "Email=%s&"
     34     "Passwd=%s&"
     35     "PersistentCookie=%s&"
     36     "accountType=%s&"
     37     "source=%s&"
     38     "service=%s&"
     39     "logintoken=%s&"
     40     "logincaptcha=%s";
     41 // static
     42 const char GaiaAuthFetcher::kIssueAuthTokenFormat[] =
     43     "SID=%s&"
     44     "LSID=%s&"
     45     "service=%s&"
     46     "Session=%s";
     47 // static
     48 const char GaiaAuthFetcher::kGetUserInfoFormat[] =
     49     "LSID=%s";
     50 
     51 // static
     52 const char GaiaAuthFetcher::kAccountDeletedError[] = "AccountDeleted";
     53 // static
     54 const char GaiaAuthFetcher::kAccountDisabledError[] = "AccountDisabled";
     55 // static
     56 const char GaiaAuthFetcher::kBadAuthenticationError[] = "BadAuthentication";
     57 // static
     58 const char GaiaAuthFetcher::kCaptchaError[] = "CaptchaRequired";
     59 // static
     60 const char GaiaAuthFetcher::kServiceUnavailableError[] =
     61     "ServiceUnavailable";
     62 // static
     63 const char GaiaAuthFetcher::kErrorParam[] = "Error";
     64 // static
     65 const char GaiaAuthFetcher::kErrorUrlParam[] = "Url";
     66 // static
     67 const char GaiaAuthFetcher::kCaptchaUrlParam[] = "CaptchaUrl";
     68 // static
     69 const char GaiaAuthFetcher::kCaptchaTokenParam[] = "CaptchaToken";
     70 // static
     71 const char GaiaAuthFetcher::kCaptchaUrlPrefix[] =
     72     "http://www.google.com/accounts/";
     73 
     74 // static
     75 const char GaiaAuthFetcher::kCookiePersistence[] = "true";
     76 // static
     77 // TODO(johnnyg): When hosted accounts are supported by sync,
     78 // we can always use "HOSTED_OR_GOOGLE"
     79 const char GaiaAuthFetcher::kAccountTypeHostedOrGoogle[] =
     80     "HOSTED_OR_GOOGLE";
     81 const char GaiaAuthFetcher::kAccountTypeGoogle[] =
     82     "GOOGLE";
     83 
     84 // static
     85 const char GaiaAuthFetcher::kSecondFactor[] = "Info=InvalidSecondFactor";
     86 
     87 // TODO(chron): These urls are also in auth_response_handler.h.
     88 // The URLs for different calls in the Google Accounts programmatic login API.
     89 const char GaiaAuthFetcher::kClientLoginUrl[] =
     90     "https://www.google.com/accounts/ClientLogin";
     91 const char GaiaAuthFetcher::kIssueAuthTokenUrl[] =
     92     "https://www.google.com/accounts/IssueAuthToken";
     93 const char GaiaAuthFetcher::kGetUserInfoUrl[] =
     94     "https://www.google.com/accounts/GetUserInfo";
     95 
     96 GaiaAuthFetcher::GaiaAuthFetcher(GaiaAuthConsumer* consumer,
     97                                        const std::string& source,
     98                                        net::URLRequestContextGetter* getter)
     99     : consumer_(consumer),
    100       getter_(getter),
    101       source_(source),
    102       client_login_gurl_(kClientLoginUrl),
    103       issue_auth_token_gurl_(kIssueAuthTokenUrl),
    104       get_user_info_gurl_(kGetUserInfoUrl),
    105       fetch_pending_(false) {}
    106 
    107 GaiaAuthFetcher::~GaiaAuthFetcher() {}
    108 
    109 bool GaiaAuthFetcher::HasPendingFetch() {
    110   return fetch_pending_;
    111 }
    112 
    113 void GaiaAuthFetcher::CancelRequest() {
    114   fetcher_.reset();
    115   fetch_pending_ = false;
    116 }
    117 
    118 // static
    119 URLFetcher* GaiaAuthFetcher::CreateGaiaFetcher(
    120     net::URLRequestContextGetter* getter,
    121     const std::string& body,
    122     const GURL& gaia_gurl,
    123     URLFetcher::Delegate* delegate) {
    124 
    125   URLFetcher* to_return =
    126       URLFetcher::Create(0,
    127                          gaia_gurl,
    128                          URLFetcher::POST,
    129                          delegate);
    130   to_return->set_request_context(getter);
    131   to_return->set_load_flags(net::LOAD_DO_NOT_SEND_COOKIES);
    132   to_return->set_upload_data("application/x-www-form-urlencoded", body);
    133   return to_return;
    134 }
    135 
    136 // static
    137 std::string GaiaAuthFetcher::MakeClientLoginBody(
    138     const std::string& username,
    139     const std::string& password,
    140     const std::string& source,
    141     const char* service,
    142     const std::string& login_token,
    143     const std::string& login_captcha,
    144     HostedAccountsSetting allow_hosted_accounts) {
    145   std::string encoded_username = UrlEncodeString(username);
    146   std::string encoded_password = UrlEncodeString(password);
    147   std::string encoded_login_token = UrlEncodeString(login_token);
    148   std::string encoded_login_captcha = UrlEncodeString(login_captcha);
    149 
    150   const char* account_type = allow_hosted_accounts == HostedAccountsAllowed ?
    151       kAccountTypeHostedOrGoogle :
    152       kAccountTypeGoogle;
    153 
    154   if (login_token.empty() || login_captcha.empty()) {
    155     return base::StringPrintf(kClientLoginFormat,
    156                               encoded_username.c_str(),
    157                               encoded_password.c_str(),
    158                               kCookiePersistence,
    159                               account_type,
    160                               source.c_str(),
    161                               service);
    162   }
    163 
    164   return base::StringPrintf(kClientLoginCaptchaFormat,
    165                             encoded_username.c_str(),
    166                             encoded_password.c_str(),
    167                             kCookiePersistence,
    168                             account_type,
    169                             source.c_str(),
    170                             service,
    171                             encoded_login_token.c_str(),
    172                             encoded_login_captcha.c_str());
    173 
    174 }
    175 
    176 // static
    177 std::string GaiaAuthFetcher::MakeIssueAuthTokenBody(
    178     const std::string& sid,
    179     const std::string& lsid,
    180     const char* const service) {
    181   std::string encoded_sid = UrlEncodeString(sid);
    182   std::string encoded_lsid = UrlEncodeString(lsid);
    183 
    184   // All tokens should be session tokens except the gaia auth token.
    185   bool session = true;
    186   if (!strcmp(service, GaiaConstants::kGaiaService))
    187     session = false;
    188 
    189   return base::StringPrintf(kIssueAuthTokenFormat,
    190                             encoded_sid.c_str(),
    191                             encoded_lsid.c_str(),
    192                             service,
    193                             session ? "true" : "false");
    194 }
    195 
    196 // static
    197 std::string GaiaAuthFetcher::MakeGetUserInfoBody(const std::string& lsid) {
    198   std::string encoded_lsid = UrlEncodeString(lsid);
    199   return base::StringPrintf(kGetUserInfoFormat, encoded_lsid.c_str());
    200 }
    201 
    202 // Helper method that extracts tokens from a successful reply.
    203 // static
    204 void GaiaAuthFetcher::ParseClientLoginResponse(const std::string& data,
    205                                                   std::string* sid,
    206                                                   std::string* lsid,
    207                                                   std::string* token) {
    208   using std::vector;
    209   using std::pair;
    210   using std::string;
    211 
    212   vector<pair<string, string> > tokens;
    213   base::SplitStringIntoKeyValuePairs(data, '=', '\n', &tokens);
    214   for (vector<pair<string, string> >::iterator i = tokens.begin();
    215       i != tokens.end(); ++i) {
    216     if (i->first == "SID") {
    217       sid->assign(i->second);
    218     } else if (i->first == "LSID") {
    219       lsid->assign(i->second);
    220     } else if (i->first == "Auth") {
    221       token->assign(i->second);
    222     }
    223   }
    224 }
    225 
    226 // static
    227 void GaiaAuthFetcher::ParseClientLoginFailure(const std::string& data,
    228                                               std::string* error,
    229                                               std::string* error_url,
    230                                               std::string* captcha_url,
    231                                               std::string* captcha_token) {
    232   using std::vector;
    233   using std::pair;
    234   using std::string;
    235 
    236   vector<pair<string, string> > tokens;
    237   base::SplitStringIntoKeyValuePairs(data, '=', '\n', &tokens);
    238   for (vector<pair<string, string> >::iterator i = tokens.begin();
    239        i != tokens.end(); ++i) {
    240     if (i->first == kErrorParam) {
    241       error->assign(i->second);
    242     } else if (i->first == kErrorUrlParam) {
    243       error_url->assign(i->second);
    244     } else if (i->first == kCaptchaUrlParam) {
    245       captcha_url->assign(i->second);
    246     } else if (i->first == kCaptchaTokenParam) {
    247       captcha_token->assign(i->second);
    248     }
    249   }
    250 }
    251 
    252 void GaiaAuthFetcher::StartClientLogin(
    253     const std::string& username,
    254     const std::string& password,
    255     const char* const service,
    256     const std::string& login_token,
    257     const std::string& login_captcha,
    258     HostedAccountsSetting allow_hosted_accounts) {
    259 
    260   DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
    261 
    262   // This class is thread agnostic, so be sure to call this only on the
    263   // same thread each time.
    264   VLOG(1) << "Starting new ClientLogin fetch for:" << username;
    265 
    266   // Must outlive fetcher_.
    267   request_body_ = MakeClientLoginBody(username,
    268                                       password,
    269                                       source_,
    270                                       service,
    271                                       login_token,
    272                                       login_captcha,
    273                                       allow_hosted_accounts);
    274   fetcher_.reset(CreateGaiaFetcher(getter_,
    275                                    request_body_,
    276                                    client_login_gurl_,
    277                                    this));
    278   fetch_pending_ = true;
    279   fetcher_->Start();
    280 }
    281 
    282 void GaiaAuthFetcher::StartIssueAuthToken(const std::string& sid,
    283                                           const std::string& lsid,
    284                                           const char* const service) {
    285 
    286   DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
    287 
    288   VLOG(1) << "Starting IssueAuthToken for: " << service;
    289   requested_service_ = service;
    290   request_body_ = MakeIssueAuthTokenBody(sid, lsid, service);
    291   fetcher_.reset(CreateGaiaFetcher(getter_,
    292                                    request_body_,
    293                                    issue_auth_token_gurl_,
    294                                    this));
    295   fetch_pending_ = true;
    296   fetcher_->Start();
    297 }
    298 
    299 void GaiaAuthFetcher::StartGetUserInfo(const std::string& lsid,
    300                                        const std::string& info_key) {
    301   DCHECK(!fetch_pending_) << "Tried to fetch two things at once!";
    302 
    303   VLOG(1) << "Starting GetUserInfo for lsid=" << lsid;
    304   request_body_ = MakeGetUserInfoBody(lsid);
    305   fetcher_.reset(CreateGaiaFetcher(getter_,
    306                                    request_body_,
    307                                    get_user_info_gurl_,
    308                                    this));
    309   fetch_pending_ = true;
    310   requested_info_key_ = info_key;
    311   fetcher_->Start();
    312 }
    313 
    314 // static
    315 GoogleServiceAuthError GaiaAuthFetcher::GenerateAuthError(
    316     const std::string& data,
    317     const net::URLRequestStatus& status) {
    318   if (!status.is_success()) {
    319     if (status.status() == net::URLRequestStatus::CANCELED) {
    320       return GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED);
    321     } else {
    322       LOG(WARNING) << "Could not reach Google Accounts servers: errno "
    323           << status.os_error();
    324       return GoogleServiceAuthError::FromConnectionError(status.os_error());
    325     }
    326   } else {
    327     if (IsSecondFactorSuccess(data)) {
    328       return GoogleServiceAuthError(GoogleServiceAuthError::TWO_FACTOR);
    329     }
    330 
    331     std::string error;
    332     std::string url;
    333     std::string captcha_url;
    334     std::string captcha_token;
    335     ParseClientLoginFailure(data, &error, &url, &captcha_url, &captcha_token);
    336     LOG(WARNING) << "ClientLogin failed with " << error;
    337 
    338     if (error == kCaptchaError) {
    339       GURL image_url(kCaptchaUrlPrefix + captcha_url);
    340       GURL unlock_url(url);
    341       return GoogleServiceAuthError::FromCaptchaChallenge(
    342           captcha_token, image_url, unlock_url);
    343     }
    344     if (error == kAccountDeletedError)
    345       return GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DELETED);
    346     if (error == kAccountDisabledError)
    347       return GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DISABLED);
    348     if (error == kBadAuthenticationError) {
    349       return GoogleServiceAuthError(
    350           GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);
    351     }
    352     if (error == kServiceUnavailableError) {
    353       return GoogleServiceAuthError(
    354           GoogleServiceAuthError::SERVICE_UNAVAILABLE);
    355     }
    356 
    357     LOG(WARNING) << "Incomprehensible response from Google Accounts servers.";
    358     return GoogleServiceAuthError(
    359         GoogleServiceAuthError::SERVICE_UNAVAILABLE);
    360   }
    361 
    362   NOTREACHED();
    363   return GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE);
    364 }
    365 
    366 void GaiaAuthFetcher::OnClientLoginFetched(const std::string& data,
    367                                            const net::URLRequestStatus& status,
    368                                            int response_code) {
    369   if (status.is_success() && response_code == RC_REQUEST_OK) {
    370     VLOG(1) << "ClientLogin successful!";
    371     std::string sid;
    372     std::string lsid;
    373     std::string token;
    374     ParseClientLoginResponse(data, &sid, &lsid, &token);
    375     consumer_->OnClientLoginSuccess(
    376         GaiaAuthConsumer::ClientLoginResult(sid, lsid, token, data));
    377   } else {
    378     consumer_->OnClientLoginFailure(GenerateAuthError(data, status));
    379   }
    380 }
    381 
    382 void GaiaAuthFetcher::OnIssueAuthTokenFetched(
    383     const std::string& data,
    384     const net::URLRequestStatus& status,
    385     int response_code) {
    386   if (status.is_success() && response_code == RC_REQUEST_OK) {
    387     // Only the bare token is returned in the body of this Gaia call
    388     // without any padding.
    389     consumer_->OnIssueAuthTokenSuccess(requested_service_, data);
    390   } else {
    391     consumer_->OnIssueAuthTokenFailure(requested_service_,
    392         GenerateAuthError(data, status));
    393   }
    394 }
    395 
    396 void GaiaAuthFetcher::OnGetUserInfoFetched(
    397     const std::string& data,
    398     const net::URLRequestStatus& status,
    399     int response_code) {
    400   using std::vector;
    401   using std::string;
    402   using std::pair;
    403 
    404   if (status.is_success() && response_code == RC_REQUEST_OK) {
    405     vector<pair<string, string> > tokens;
    406     base::SplitStringIntoKeyValuePairs(data, '=', '\n', &tokens);
    407     for (vector<pair<string, string> >::iterator i = tokens.begin();
    408          i != tokens.end(); ++i) {
    409       if (i->first == requested_info_key_) {
    410         consumer_->OnGetUserInfoSuccess(i->first, i->second);
    411         return;
    412       }
    413     }
    414     consumer_->OnGetUserInfoKeyNotFound(requested_info_key_);
    415   } else {
    416     consumer_->OnGetUserInfoFailure(GenerateAuthError(data, status));
    417   }
    418 }
    419 
    420 void GaiaAuthFetcher::OnURLFetchComplete(const URLFetcher* source,
    421                                          const GURL& url,
    422                                          const net::URLRequestStatus& status,
    423                                          int response_code,
    424                                          const ResponseCookies& cookies,
    425                                          const std::string& data) {
    426   fetch_pending_ = false;
    427   if (url == client_login_gurl_) {
    428     OnClientLoginFetched(data, status, response_code);
    429   } else if (url == issue_auth_token_gurl_) {
    430     OnIssueAuthTokenFetched(data, status, response_code);
    431   } else if (url == get_user_info_gurl_) {
    432     OnGetUserInfoFetched(data, status, response_code);
    433   } else {
    434     NOTREACHED();
    435   }
    436 }
    437 
    438 // static
    439 bool GaiaAuthFetcher::IsSecondFactorSuccess(
    440     const std::string& alleged_error) {
    441   return alleged_error.find(kSecondFactor) !=
    442       std::string::npos;
    443 }
    444