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