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