Home | History | Annotate | Download | only in gaia
      1 // Copyright (c) 2009 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_authenticator.h"
      6 
      7 #include <string>
      8 #include <utility>
      9 #include <vector>
     10 
     11 #include "base/basictypes.h"
     12 #include "base/port.h"
     13 #include "base/string_split.h"
     14 #include "chrome/common/deprecated/event_sys-inl.h"
     15 #include "chrome/common/net/http_return.h"
     16 #include "googleurl/src/gurl.h"
     17 #include "net/base/escape.h"
     18 
     19 using std::pair;
     20 using std::string;
     21 using std::vector;
     22 
     23 namespace gaia {
     24 
     25 static const char kGaiaV1IssueAuthTokenPath[] = "/accounts/IssueAuthToken";
     26 
     27 static const char kGetUserInfoPath[] = "/accounts/GetUserInfo";
     28 
     29 GaiaAuthenticator::AuthResults::AuthResults() : auth_error(None) {}
     30 
     31 GaiaAuthenticator::AuthResults::~AuthResults() {}
     32 
     33 GaiaAuthenticator::AuthParams::AuthParams() : authenticator(NULL),
     34                                               request_id(0) {}
     35 
     36 GaiaAuthenticator::AuthParams::~AuthParams() {}
     37 
     38 // Sole constructor with initializers for all fields.
     39 GaiaAuthenticator::GaiaAuthenticator(const string& user_agent,
     40                                      const string& service_id,
     41                                      const string& gaia_url)
     42     : user_agent_(user_agent),
     43       service_id_(service_id),
     44       gaia_url_(gaia_url),
     45       request_count_(0),
     46       delay_(0),
     47       next_allowed_auth_attempt_time_(0),
     48       early_auth_attempt_count_(0),
     49       message_loop_(NULL) {
     50   GaiaAuthEvent done = { GaiaAuthEvent::GAIA_AUTHENTICATOR_DESTROYED, None,
     51                          this };
     52   channel_ = new Channel(done);
     53 }
     54 
     55 GaiaAuthenticator::~GaiaAuthenticator() {
     56   delete channel_;
     57 }
     58 
     59 // mutex_ must be entered before calling this function.
     60 GaiaAuthenticator::AuthParams GaiaAuthenticator::MakeParams(
     61     const string& user_name,
     62     const string& password,
     63     const string& captcha_token,
     64     const string& captcha_value) {
     65   AuthParams params;
     66   params.request_id = ++request_count_;
     67   params.email = user_name;
     68   params.password = password;
     69   params.captcha_token = captcha_token;
     70   params.captcha_value = captcha_value;
     71   params.authenticator = this;
     72   return params;
     73 }
     74 
     75 bool GaiaAuthenticator::Authenticate(const string& user_name,
     76                                      const string& password,
     77                                      const string& captcha_token,
     78                                      const string& captcha_value) {
     79   DCHECK_EQ(MessageLoop::current(), message_loop_);
     80 
     81   AuthParams const params =
     82       MakeParams(user_name, password, captcha_token, captcha_value);
     83   return AuthenticateImpl(params);
     84 }
     85 
     86 bool GaiaAuthenticator::AuthenticateWithLsid(const string& lsid) {
     87   auth_results_.lsid = lsid;
     88   // We need to lookup the email associated with this LSID cookie in order to
     89   // update |auth_results_| with the correct values.
     90   if (LookupEmail(&auth_results_)) {
     91     auth_results_.email = auth_results_.primary_email;
     92     return IssueAuthToken(&auth_results_, service_id_);
     93   }
     94   return false;
     95 }
     96 
     97 bool GaiaAuthenticator::AuthenticateImpl(const AuthParams& params) {
     98   DCHECK_EQ(MessageLoop::current(), message_loop_);
     99   AuthResults results;
    100   const bool succeeded = AuthenticateImpl(params, &results);
    101   if (params.request_id == request_count_) {
    102     auth_results_ = results;
    103     GaiaAuthEvent event = { succeeded ? GaiaAuthEvent::GAIA_AUTH_SUCCEEDED
    104                                       : GaiaAuthEvent::GAIA_AUTH_FAILED,
    105                                       results.auth_error, this };
    106     channel_->NotifyListeners(event);
    107   }
    108   return succeeded;
    109 }
    110 
    111 // This method makes an HTTP request to the Gaia server, and calls other
    112 // methods to help parse the response. If authentication succeeded, then
    113 // Gaia-issued cookies are available in the respective variables; if
    114 // authentication failed, then the exact error is available as an enum. If the
    115 // client wishes to save the credentials, the last parameter must be true.
    116 // If a subsequent request is made with fresh credentials, the saved credentials
    117 // are wiped out; any subsequent request to the zero-parameter overload of this
    118 // method preserves the saved credentials.
    119 bool GaiaAuthenticator::AuthenticateImpl(const AuthParams& params,
    120                                          AuthResults* results) {
    121   DCHECK_EQ(MessageLoop::current(), message_loop_);
    122   results->auth_error = ConnectionUnavailable;
    123   results->email = params.email.data();
    124   results->password = params.password;
    125 
    126   // The aim of this code is to start failing requests if due to a logic error
    127   // in the program we're hammering GAIA.
    128 #if defined(OS_WIN)
    129   __time32_t now = _time32(0);
    130 #else  // defined(OS_WIN)
    131   time_t now = time(0);
    132 #endif  // defined(OS_WIN)
    133 
    134   if (now > next_allowed_auth_attempt_time_) {
    135     next_allowed_auth_attempt_time_ = now + 1;
    136     // If we're more than 2 minutes past the allowed time we reset the early
    137     // attempt count.
    138     if (now - next_allowed_auth_attempt_time_ > 2 * 60) {
    139       delay_ = 1;
    140       early_auth_attempt_count_ = 0;
    141     }
    142   } else {
    143     ++early_auth_attempt_count_;
    144     // Allow 3 attempts, but then limit.
    145     if (early_auth_attempt_count_ > 3) {
    146       delay_ = GetBackoffDelaySeconds(delay_);
    147       next_allowed_auth_attempt_time_ = now + delay_;
    148       return false;
    149     }
    150   }
    151 
    152   return PerformGaiaRequest(params, results);
    153 }
    154 
    155 bool GaiaAuthenticator::PerformGaiaRequest(const AuthParams& params,
    156                                            AuthResults* results) {
    157   DCHECK_EQ(MessageLoop::current(), message_loop_);
    158   GURL gaia_auth_url(gaia_url_);
    159 
    160   string post_body;
    161   post_body += "Email=" + EscapeUrlEncodedData(params.email);
    162   post_body += "&Passwd=" + EscapeUrlEncodedData(params.password);
    163   post_body += "&source=" + EscapeUrlEncodedData(user_agent_);
    164   post_body += "&service=" + service_id_;
    165   if (!params.captcha_token.empty() && !params.captcha_value.empty()) {
    166     post_body += "&logintoken=" + EscapeUrlEncodedData(params.captcha_token);
    167     post_body += "&logincaptcha=" + EscapeUrlEncodedData(params.captcha_value);
    168   }
    169   post_body += "&PersistentCookie=true";
    170   // We set it to GOOGLE (and not HOSTED or HOSTED_OR_GOOGLE) because we only
    171   // allow consumer logins.
    172   post_body += "&accountType=GOOGLE";
    173 
    174   string message_text;
    175   unsigned long server_response_code;
    176   if (!Post(gaia_auth_url, post_body, &server_response_code, &message_text)) {
    177     results->auth_error = ConnectionUnavailable;
    178     return false;
    179   }
    180 
    181   // Parse reply in two different ways, depending on if request failed or
    182   // succeeded.
    183   if (RC_FORBIDDEN == server_response_code) {
    184     ExtractAuthErrorFrom(message_text, results);
    185     return false;
    186   } else if (RC_REQUEST_OK == server_response_code) {
    187     ExtractTokensFrom(message_text, results);
    188     if (!IssueAuthToken(results, service_id_)) {
    189       return false;
    190     }
    191 
    192     return LookupEmail(results);
    193   } else {
    194     results->auth_error = Unknown;
    195     return false;
    196   }
    197 }
    198 
    199 bool GaiaAuthenticator::Post(const GURL& url,
    200                              const std::string& post_body,
    201                              unsigned long* response_code,
    202                              std::string* response_body) {
    203   return false;
    204 }
    205 
    206 bool GaiaAuthenticator::LookupEmail(AuthResults* results) {
    207   DCHECK_EQ(MessageLoop::current(), message_loop_);
    208   // Use the provided Gaia server, but change the path to what V1 expects.
    209   GURL url(gaia_url_);  // Gaia server.
    210   GURL::Replacements repl;
    211   // Needs to stay in scope till GURL is out of scope.
    212   string path(kGetUserInfoPath);
    213   repl.SetPathStr(path);
    214   url = url.ReplaceComponents(repl);
    215 
    216   string post_body;
    217   post_body += "LSID=";
    218   post_body += EscapeUrlEncodedData(results->lsid);
    219 
    220   unsigned long server_response_code;
    221   string message_text;
    222   if (!Post(url, post_body, &server_response_code, &message_text)) {
    223     return false;
    224   }
    225 
    226   // Check if we received a valid AuthToken; if not, ignore it.
    227   if (RC_FORBIDDEN == server_response_code) {
    228     // Server says we're not authenticated.
    229     ExtractAuthErrorFrom(message_text, results);
    230     return false;
    231   } else if (RC_REQUEST_OK == server_response_code) {
    232     typedef vector<pair<string, string> > Tokens;
    233     Tokens tokens;
    234     base::SplitStringIntoKeyValuePairs(message_text, '=', '\n', &tokens);
    235     for (Tokens::iterator i = tokens.begin(); i != tokens.end(); ++i) {
    236       if ("accountType" == i->first) {
    237         // We never authenticate an email as a hosted account.
    238         DCHECK_EQ("GOOGLE", i->second);
    239       } else if ("email" == i->first) {
    240         results->primary_email = i->second;
    241       }
    242     }
    243     return true;
    244   }
    245   return false;
    246 }
    247 
    248 int GaiaAuthenticator::GetBackoffDelaySeconds(int current_backoff_delay) {
    249   NOTREACHED();
    250   return current_backoff_delay;
    251 }
    252 
    253 // We need to call this explicitly when we need to obtain a long-lived session
    254 // token.
    255 bool GaiaAuthenticator::IssueAuthToken(AuthResults* results,
    256                                        const string& service_id) {
    257   DCHECK_EQ(MessageLoop::current(), message_loop_);
    258   // Use the provided Gaia server, but change the path to what V1 expects.
    259   GURL url(gaia_url_);  // Gaia server.
    260   GURL::Replacements repl;
    261   // Needs to stay in scope till GURL is out of scope.
    262   string path(kGaiaV1IssueAuthTokenPath);
    263   repl.SetPathStr(path);
    264   url = url.ReplaceComponents(repl);
    265 
    266   string post_body;
    267   post_body += "LSID=";
    268   post_body += EscapeUrlEncodedData(results->lsid);
    269   post_body += "&service=" + service_id;
    270   post_body += "&Session=true";
    271 
    272   unsigned long server_response_code;
    273   string message_text;
    274   if (!Post(url, post_body, &server_response_code, &message_text)) {
    275     return false;
    276   }
    277 
    278   // Check if we received a valid AuthToken; if not, ignore it.
    279   if (RC_FORBIDDEN == server_response_code) {
    280     // Server says we're not authenticated.
    281     ExtractAuthErrorFrom(message_text, results);
    282     return false;
    283   } else if (RC_REQUEST_OK == server_response_code) {
    284     // Note that the format of message_text is different from what is returned
    285     // in the first request, or to the sole request that is made to Gaia V2.
    286     // Specifically, the entire string is the AuthToken, and looks like:
    287     // "<token>" rather than "AuthToken=<token>". Thus, we need not use
    288     // ExtractTokensFrom(...), but simply assign the token.
    289     int last_index = message_text.length() - 1;
    290     if ('\n' == message_text[last_index])
    291       message_text.erase(last_index);
    292     results->auth_token = message_text;
    293     return true;
    294   }
    295   return false;
    296 }
    297 
    298 // Helper method that extracts tokens from a successful reply, and saves them
    299 // in the right fields.
    300 void GaiaAuthenticator::ExtractTokensFrom(const string& response,
    301                                           AuthResults* results) {
    302   vector<pair<string, string> > tokens;
    303   base::SplitStringIntoKeyValuePairs(response, '=', '\n', &tokens);
    304   for (vector<pair<string, string> >::iterator i = tokens.begin();
    305       i != tokens.end(); ++i) {
    306     if (i->first == "SID") {
    307       results->sid = i->second;
    308     } else if (i->first == "LSID") {
    309       results->lsid = i->second;
    310     } else if (i->first == "Auth") {
    311       results->auth_token = i->second;
    312     }
    313   }
    314 }
    315 
    316 // Helper method that extracts tokens from a failure response, and saves them
    317 // in the right fields.
    318 void GaiaAuthenticator::ExtractAuthErrorFrom(const string& response,
    319                                              AuthResults* results) {
    320   vector<pair<string, string> > tokens;
    321   base::SplitStringIntoKeyValuePairs(response, '=', '\n', &tokens);
    322   for (vector<pair<string, string> >::iterator i = tokens.begin();
    323       i != tokens.end(); ++i) {
    324     if (i->first == "Error") {
    325       results->error_msg = i->second;
    326     } else if (i->first == "Url") {
    327       results->auth_error_url = i->second;
    328     } else if (i->first == "CaptchaToken") {
    329       results->captcha_token = i->second;
    330     } else if (i->first == "CaptchaUrl") {
    331       results->captcha_url = i->second;
    332     }
    333   }
    334 
    335   // Convert string error messages to enum values. Each case has two different
    336   // strings; the first one is the most current and the second one is
    337   // deprecated, but available.
    338   const string& error_msg = results->error_msg;
    339   if (error_msg == "BadAuthentication" || error_msg == "badauth") {
    340     results->auth_error = BadAuthentication;
    341   } else if (error_msg == "NotVerified" || error_msg == "nv") {
    342     results->auth_error = NotVerified;
    343   } else if (error_msg == "TermsNotAgreed" || error_msg == "tna") {
    344     results->auth_error = TermsNotAgreed;
    345   } else if (error_msg == "Unknown" || error_msg == "unknown") {
    346     results->auth_error = Unknown;
    347   } else if (error_msg == "AccountDeleted" || error_msg == "adel") {
    348     results->auth_error = AccountDeleted;
    349   } else if (error_msg == "AccountDisabled" || error_msg == "adis") {
    350     results->auth_error = AccountDisabled;
    351   } else if (error_msg == "CaptchaRequired" || error_msg == "cr") {
    352     results->auth_error = CaptchaRequired;
    353   } else if (error_msg == "ServiceUnavailable" || error_msg == "ire") {
    354     results->auth_error = ServiceUnavailable;
    355   }
    356 }
    357 
    358 // Reset all stored credentials, perhaps in preparation for letting a different
    359 // user sign in.
    360 void GaiaAuthenticator::ResetCredentials() {
    361   DCHECK_EQ(MessageLoop::current(), message_loop_);
    362   AuthResults blank;
    363   auth_results_ = blank;
    364 }
    365 
    366 void GaiaAuthenticator::SetUsernamePassword(const string& username,
    367                                             const string& password) {
    368   DCHECK_EQ(MessageLoop::current(), message_loop_);
    369   auth_results_.password = password;
    370   auth_results_.email = username;
    371 }
    372 
    373 void GaiaAuthenticator::SetUsername(const string& username) {
    374   DCHECK_EQ(MessageLoop::current(), message_loop_);
    375   auth_results_.email = username;
    376 }
    377 
    378 void GaiaAuthenticator::RenewAuthToken(const string& auth_token) {
    379   DCHECK_EQ(MessageLoop::current(), message_loop_);
    380   DCHECK(!this->auth_token().empty());
    381   auth_results_.auth_token = auth_token;
    382 }
    383 void GaiaAuthenticator::SetAuthToken(const string& auth_token) {
    384   DCHECK_EQ(MessageLoop::current(), message_loop_);
    385   auth_results_.auth_token = auth_token;
    386 }
    387 
    388 bool GaiaAuthenticator::Authenticate(const string& user_name,
    389                                      const string& password) {
    390   DCHECK_EQ(MessageLoop::current(), message_loop_);
    391   const string empty;
    392   return Authenticate(user_name, password, empty,
    393                       empty);
    394 }
    395 
    396 }  // namepace gaia
    397 
    398