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 <string> 8 #include <utility> 9 #include <vector> 10 11 #include "base/json/json_reader.h" 12 #include "base/json/json_writer.h" 13 #include "base/strings/string_split.h" 14 #include "base/strings/string_util.h" 15 #include "base/strings/stringprintf.h" 16 #include "base/values.h" 17 #include "google_apis/gaia/gaia_auth_consumer.h" 18 #include "google_apis/gaia/gaia_constants.h" 19 #include "google_apis/gaia/gaia_urls.h" 20 #include "google_apis/gaia/google_service_auth_error.h" 21 #include "net/base/escape.h" 22 #include "net/base/load_flags.h" 23 #include "net/http/http_response_headers.h" 24 #include "net/http/http_status_code.h" 25 #include "net/url_request/url_fetcher.h" 26 #include "net/url_request/url_request_context_getter.h" 27 #include "net/url_request/url_request_status.h" 28 29 namespace { 30 const int kLoadFlagsIgnoreCookies = net::LOAD_DO_NOT_SEND_COOKIES | 31 net::LOAD_DO_NOT_SAVE_COOKIES; 32 33 static bool CookiePartsContains(const std::vector<std::string>& parts, 34 const char* part) { 35 for (std::vector<std::string>::const_iterator it = parts.begin(); 36 it != parts.end(); ++it) { 37 if (LowerCaseEqualsASCII(*it, part)) 38 return true; 39 } 40 return false; 41 } 42 43 bool ExtractOAuth2TokenPairResponse(base::DictionaryValue* dict, 44 std::string* refresh_token, 45 std::string* access_token, 46 int* expires_in_secs) { 47 DCHECK(refresh_token); 48 DCHECK(access_token); 49 DCHECK(expires_in_secs); 50 51 if (!dict->GetStringWithoutPathExpansion("refresh_token", refresh_token) || 52 !dict->GetStringWithoutPathExpansion("access_token", access_token) || 53 !dict->GetIntegerWithoutPathExpansion("expires_in", expires_in_secs)) { 54 return false; 55 } 56 57 return true; 58 } 59 60 } // namespace 61 62 // TODO(chron): Add sourceless version of this formatter. 63 // static 64 const char GaiaAuthFetcher::kClientLoginFormat[] = 65 "Email=%s&" 66 "Passwd=%s&" 67 "PersistentCookie=%s&" 68 "accountType=%s&" 69 "source=%s&" 70 "service=%s"; 71 // static 72 const char GaiaAuthFetcher::kClientLoginCaptchaFormat[] = 73 "Email=%s&" 74 "Passwd=%s&" 75 "PersistentCookie=%s&" 76 "accountType=%s&" 77 "source=%s&" 78 "service=%s&" 79 "logintoken=%s&" 80 "logincaptcha=%s"; 81 // static 82 const char GaiaAuthFetcher::kIssueAuthTokenFormat[] = 83 "SID=%s&" 84 "LSID=%s&" 85 "service=%s&" 86 "Session=%s"; 87 // static 88 const char GaiaAuthFetcher::kClientLoginToOAuth2BodyFormat[] = 89 "scope=%s&client_id=%s"; 90 // static 91 const char GaiaAuthFetcher::kClientLoginToOAuth2WithDeviceTypeBodyFormat[] = 92 "scope=%s&client_id=%s&device_type=chrome"; 93 // static 94 const char GaiaAuthFetcher::kOAuth2CodeToTokenPairBodyFormat[] = 95 "scope=%s&" 96 "grant_type=authorization_code&" 97 "client_id=%s&" 98 "client_secret=%s&" 99 "code=%s"; 100 // static 101 const char GaiaAuthFetcher::kOAuth2RevokeTokenBodyFormat[] = 102 "token=%s"; 103 // static 104 const char GaiaAuthFetcher::kGetUserInfoFormat[] = 105 "LSID=%s"; 106 // static 107 const char GaiaAuthFetcher::kMergeSessionFormat[] = 108 "uberauth=%s&" 109 "continue=%s&" 110 "source=%s"; 111 // static 112 const char GaiaAuthFetcher::kUberAuthTokenURLFormat[] = 113 "?source=%s&" 114 "issueuberauth=1"; 115 116 const char GaiaAuthFetcher::kOAuthLoginFormat[] = "service=%s&source=%s"; 117 118 // static 119 const char GaiaAuthFetcher::kAccountDeletedError[] = "AccountDeleted"; 120 // static 121 const char GaiaAuthFetcher::kAccountDisabledError[] = "AccountDisabled"; 122 // static 123 const char GaiaAuthFetcher::kBadAuthenticationError[] = "BadAuthentication"; 124 // static 125 const char GaiaAuthFetcher::kCaptchaError[] = "CaptchaRequired"; 126 // static 127 const char GaiaAuthFetcher::kServiceUnavailableError[] = 128 "ServiceUnavailable"; 129 // static 130 const char GaiaAuthFetcher::kErrorParam[] = "Error"; 131 // static 132 const char GaiaAuthFetcher::kErrorUrlParam[] = "Url"; 133 // static 134 const char GaiaAuthFetcher::kCaptchaUrlParam[] = "CaptchaUrl"; 135 // static 136 const char GaiaAuthFetcher::kCaptchaTokenParam[] = "CaptchaToken"; 137 138 // static 139 const char GaiaAuthFetcher::kCookiePersistence[] = "true"; 140 // static 141 // TODO(johnnyg): When hosted accounts are supported by sync, 142 // we can always use "HOSTED_OR_GOOGLE" 143 const char GaiaAuthFetcher::kAccountTypeHostedOrGoogle[] = 144 "HOSTED_OR_GOOGLE"; 145 const char GaiaAuthFetcher::kAccountTypeGoogle[] = 146 "GOOGLE"; 147 148 // static 149 const char GaiaAuthFetcher::kSecondFactor[] = "Info=InvalidSecondFactor"; 150 151 // static 152 const char GaiaAuthFetcher::kAuthHeaderFormat[] = 153 "Authorization: GoogleLogin auth=%s"; 154 // static 155 const char GaiaAuthFetcher::kOAuthHeaderFormat[] = "Authorization: OAuth %s"; 156 // static 157 const char GaiaAuthFetcher::kOAuth2BearerHeaderFormat[] = 158 "Authorization: Bearer %s"; 159 // static 160 const char GaiaAuthFetcher::kDeviceIdHeaderFormat[] = "X-Device-ID: %s"; 161 // static 162 const char GaiaAuthFetcher::kClientLoginToOAuth2CookiePartSecure[] = "secure"; 163 // static 164 const char GaiaAuthFetcher::kClientLoginToOAuth2CookiePartHttpOnly[] = 165 "httponly"; 166 // static 167 const char GaiaAuthFetcher::kClientLoginToOAuth2CookiePartCodePrefix[] = 168 "oauth_code="; 169 // static 170 const int GaiaAuthFetcher::kClientLoginToOAuth2CookiePartCodePrefixLength = 171 arraysize(GaiaAuthFetcher::kClientLoginToOAuth2CookiePartCodePrefix) - 1; 172 173 GaiaAuthFetcher::GaiaAuthFetcher(GaiaAuthConsumer* consumer, 174 const std::string& source, 175 net::URLRequestContextGetter* getter) 176 : consumer_(consumer), 177 getter_(getter), 178 source_(source), 179 client_login_gurl_(GaiaUrls::GetInstance()->client_login_url()), 180 issue_auth_token_gurl_(GaiaUrls::GetInstance()->issue_auth_token_url()), 181 oauth2_token_gurl_(GaiaUrls::GetInstance()->oauth2_token_url()), 182 oauth2_revoke_gurl_(GaiaUrls::GetInstance()->oauth2_revoke_url()), 183 get_user_info_gurl_(GaiaUrls::GetInstance()->get_user_info_url()), 184 merge_session_gurl_(GaiaUrls::GetInstance()->merge_session_url()), 185 uberauth_token_gurl_(GaiaUrls::GetInstance()->oauth1_login_url().Resolve( 186 base::StringPrintf(kUberAuthTokenURLFormat, source.c_str()))), 187 oauth_login_gurl_(GaiaUrls::GetInstance()->oauth1_login_url()), 188 list_accounts_gurl_(GaiaUrls::GetInstance()->list_accounts_url()), 189 get_check_connection_info_url_( 190 GaiaUrls::GetInstance()->get_check_connection_info_url()), 191 client_login_to_oauth2_gurl_( 192 GaiaUrls::GetInstance()->client_login_to_oauth2_url()), 193 fetch_pending_(false) {} 194 195 GaiaAuthFetcher::~GaiaAuthFetcher() {} 196 197 bool GaiaAuthFetcher::HasPendingFetch() { 198 return fetch_pending_; 199 } 200 201 void GaiaAuthFetcher::CancelRequest() { 202 fetcher_.reset(); 203 fetch_pending_ = false; 204 } 205 206 // static 207 net::URLFetcher* GaiaAuthFetcher::CreateGaiaFetcher( 208 net::URLRequestContextGetter* getter, 209 const std::string& body, 210 const std::string& headers, 211 const GURL& gaia_gurl, 212 int load_flags, 213 net::URLFetcherDelegate* delegate) { 214 net::URLFetcher* to_return = net::URLFetcher::Create( 215 0, gaia_gurl, 216 body == "" ? net::URLFetcher::GET : net::URLFetcher::POST, 217 delegate); 218 to_return->SetRequestContext(getter); 219 to_return->SetUploadData("application/x-www-form-urlencoded", body); 220 221 DVLOG(2) << "Gaia fetcher URL: " << gaia_gurl.spec(); 222 DVLOG(2) << "Gaia fetcher headers: " << headers; 223 DVLOG(2) << "Gaia fetcher body: " << body; 224 225 // The Gaia token exchange requests do not require any cookie-based 226 // identification as part of requests. We suppress sending any cookies to 227 // maintain a separation between the user's browsing and Chrome's internal 228 // services. Where such mixing is desired (MergeSession or OAuthLogin), it 229 // will be done explicitly. 230 to_return->SetLoadFlags(load_flags); 231 232 // Fetchers are sometimes cancelled because a network change was detected, 233 // especially at startup and after sign-in on ChromeOS. Retrying once should 234 // be enough in those cases; let the fetcher retry up to 3 times just in case. 235 // http://crbug.com/163710 236 to_return->SetAutomaticallyRetryOnNetworkChanges(3); 237 238 if (!headers.empty()) 239 to_return->SetExtraRequestHeaders(headers); 240 241 return to_return; 242 } 243 244 // static 245 std::string GaiaAuthFetcher::MakeClientLoginBody( 246 const std::string& username, 247 const std::string& password, 248 const std::string& source, 249 const char* service, 250 const std::string& login_token, 251 const std::string& login_captcha, 252 HostedAccountsSetting allow_hosted_accounts) { 253 std::string encoded_username = net::EscapeUrlEncodedData(username, true); 254 std::string encoded_password = net::EscapeUrlEncodedData(password, true); 255 std::string encoded_login_token = net::EscapeUrlEncodedData(login_token, 256 true); 257 std::string encoded_login_captcha = net::EscapeUrlEncodedData(login_captcha, 258 true); 259 260 const char* account_type = allow_hosted_accounts == HostedAccountsAllowed ? 261 kAccountTypeHostedOrGoogle : 262 kAccountTypeGoogle; 263 264 if (login_token.empty() || login_captcha.empty()) { 265 return base::StringPrintf(kClientLoginFormat, 266 encoded_username.c_str(), 267 encoded_password.c_str(), 268 kCookiePersistence, 269 account_type, 270 source.c_str(), 271 service); 272 } 273 274 return base::StringPrintf(kClientLoginCaptchaFormat, 275 encoded_username.c_str(), 276 encoded_password.c_str(), 277 kCookiePersistence, 278 account_type, 279 source.c_str(), 280 service, 281 encoded_login_token.c_str(), 282 encoded_login_captcha.c_str()); 283 } 284 285 // static 286 std::string GaiaAuthFetcher::MakeIssueAuthTokenBody( 287 const std::string& sid, 288 const std::string& lsid, 289 const char* const service) { 290 std::string encoded_sid = net::EscapeUrlEncodedData(sid, true); 291 std::string encoded_lsid = net::EscapeUrlEncodedData(lsid, true); 292 293 // All tokens should be session tokens except the gaia auth token. 294 bool session = true; 295 if (!strcmp(service, GaiaConstants::kGaiaService)) 296 session = false; 297 298 return base::StringPrintf(kIssueAuthTokenFormat, 299 encoded_sid.c_str(), 300 encoded_lsid.c_str(), 301 service, 302 session ? "true" : "false"); 303 } 304 305 // static 306 std::string GaiaAuthFetcher::MakeGetAuthCodeBody(bool include_device_type) { 307 std::string encoded_scope = net::EscapeUrlEncodedData( 308 GaiaConstants::kOAuth1LoginScope, true); 309 std::string encoded_client_id = net::EscapeUrlEncodedData( 310 GaiaUrls::GetInstance()->oauth2_chrome_client_id(), true); 311 if (include_device_type) { 312 return base::StringPrintf(kClientLoginToOAuth2WithDeviceTypeBodyFormat, 313 encoded_scope.c_str(), 314 encoded_client_id.c_str()); 315 } else { 316 return base::StringPrintf(kClientLoginToOAuth2BodyFormat, 317 encoded_scope.c_str(), 318 encoded_client_id.c_str()); 319 } 320 } 321 322 // static 323 std::string GaiaAuthFetcher::MakeGetTokenPairBody( 324 const std::string& auth_code) { 325 std::string encoded_scope = net::EscapeUrlEncodedData( 326 GaiaConstants::kOAuth1LoginScope, true); 327 std::string encoded_client_id = net::EscapeUrlEncodedData( 328 GaiaUrls::GetInstance()->oauth2_chrome_client_id(), true); 329 std::string encoded_client_secret = net::EscapeUrlEncodedData( 330 GaiaUrls::GetInstance()->oauth2_chrome_client_secret(), true); 331 std::string encoded_auth_code = net::EscapeUrlEncodedData(auth_code, true); 332 return base::StringPrintf(kOAuth2CodeToTokenPairBodyFormat, 333 encoded_scope.c_str(), 334 encoded_client_id.c_str(), 335 encoded_client_secret.c_str(), 336 encoded_auth_code.c_str()); 337 } 338 339 // static 340 std::string GaiaAuthFetcher::MakeRevokeTokenBody( 341 const std::string& auth_token) { 342 return base::StringPrintf(kOAuth2RevokeTokenBodyFormat, auth_token.c_str()); 343 } 344 345 // static 346 std::string GaiaAuthFetcher::MakeGetUserInfoBody(const std::string& lsid) { 347 std::string encoded_lsid = net::EscapeUrlEncodedData(lsid, true); 348 return base::StringPrintf(kGetUserInfoFormat, encoded_lsid.c_str()); 349 } 350 351 // static 352 std::string GaiaAuthFetcher::MakeMergeSessionBody( 353 const std::string& auth_token, 354 const std::string& external_cc_result, 355 const std::string& continue_url, 356 const std::string& source) { 357 std::string encoded_auth_token = net::EscapeUrlEncodedData(auth_token, true); 358 std::string encoded_continue_url = net::EscapeUrlEncodedData(continue_url, 359 true); 360 std::string encoded_source = net::EscapeUrlEncodedData(source, true); 361 std::string result = base::StringPrintf(kMergeSessionFormat, 362 encoded_auth_token.c_str(), 363 encoded_continue_url.c_str(), 364 encoded_source.c_str()); 365 if (!external_cc_result.empty()) { 366 base::StringAppendF(&result, "&externalCcResult=%s", 367 net::EscapeUrlEncodedData( 368 external_cc_result, true).c_str()); 369 } 370 371 return result; 372 } 373 374 // static 375 std::string GaiaAuthFetcher::MakeGetAuthCodeHeader( 376 const std::string& auth_token) { 377 return base::StringPrintf(kAuthHeaderFormat, auth_token.c_str()); 378 } 379 380 // Helper method that extracts tokens from a successful reply. 381 // static 382 void GaiaAuthFetcher::ParseClientLoginResponse(const std::string& data, 383 std::string* sid, 384 std::string* lsid, 385 std::string* token) { 386 using std::vector; 387 using std::pair; 388 using std::string; 389 sid->clear(); 390 lsid->clear(); 391 token->clear(); 392 vector<pair<string, string> > tokens; 393 base::SplitStringIntoKeyValuePairs(data, '=', '\n', &tokens); 394 for (vector<pair<string, string> >::iterator i = tokens.begin(); 395 i != tokens.end(); ++i) { 396 if (i->first == "SID") { 397 sid->assign(i->second); 398 } else if (i->first == "LSID") { 399 lsid->assign(i->second); 400 } else if (i->first == "Auth") { 401 token->assign(i->second); 402 } 403 } 404 // If this was a request for uberauth token, then that's all we've got in 405 // data. 406 if (sid->empty() && lsid->empty() && token->empty()) 407 token->assign(data); 408 } 409 410 // static 411 std::string GaiaAuthFetcher::MakeOAuthLoginBody(const std::string& service, 412 const std::string& source) { 413 std::string encoded_service = net::EscapeUrlEncodedData(service, true); 414 std::string encoded_source = net::EscapeUrlEncodedData(source, true); 415 return base::StringPrintf(kOAuthLoginFormat, 416 encoded_service.c_str(), 417 encoded_source.c_str()); 418 } 419 420 // static 421 void GaiaAuthFetcher::ParseClientLoginFailure(const std::string& data, 422 std::string* error, 423 std::string* error_url, 424 std::string* captcha_url, 425 std::string* captcha_token) { 426 using std::vector; 427 using std::pair; 428 using std::string; 429 430 vector<pair<string, string> > tokens; 431 base::SplitStringIntoKeyValuePairs(data, '=', '\n', &tokens); 432 for (vector<pair<string, string> >::iterator i = tokens.begin(); 433 i != tokens.end(); ++i) { 434 if (i->first == kErrorParam) { 435 error->assign(i->second); 436 } else if (i->first == kErrorUrlParam) { 437 error_url->assign(i->second); 438 } else if (i->first == kCaptchaUrlParam) { 439 captcha_url->assign(i->second); 440 } else if (i->first == kCaptchaTokenParam) { 441 captcha_token->assign(i->second); 442 } 443 } 444 } 445 446 // static 447 bool GaiaAuthFetcher::ParseClientLoginToOAuth2Response( 448 const net::ResponseCookies& cookies, 449 std::string* auth_code) { 450 DCHECK(auth_code); 451 net::ResponseCookies::const_iterator iter; 452 for (iter = cookies.begin(); iter != cookies.end(); ++iter) { 453 if (ParseClientLoginToOAuth2Cookie(*iter, auth_code)) 454 return true; 455 } 456 return false; 457 } 458 459 // static 460 bool GaiaAuthFetcher::ParseClientLoginToOAuth2Cookie(const std::string& cookie, 461 std::string* auth_code) { 462 std::vector<std::string> parts; 463 base::SplitString(cookie, ';', &parts); 464 // Per documentation, the cookie should have Secure and HttpOnly. 465 if (!CookiePartsContains(parts, kClientLoginToOAuth2CookiePartSecure) || 466 !CookiePartsContains(parts, kClientLoginToOAuth2CookiePartHttpOnly)) { 467 return false; 468 } 469 470 std::vector<std::string>::const_iterator iter; 471 for (iter = parts.begin(); iter != parts.end(); ++iter) { 472 const std::string& part = *iter; 473 if (StartsWithASCII( 474 part, kClientLoginToOAuth2CookiePartCodePrefix, false)) { 475 auth_code->assign(part.substr( 476 kClientLoginToOAuth2CookiePartCodePrefixLength)); 477 return true; 478 } 479 } 480 return false; 481 } 482 483 void GaiaAuthFetcher::StartClientLogin( 484 const std::string& username, 485 const std::string& password, 486 const char* const service, 487 const std::string& login_token, 488 const std::string& login_captcha, 489 HostedAccountsSetting allow_hosted_accounts) { 490 491 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!"; 492 493 // This class is thread agnostic, so be sure to call this only on the 494 // same thread each time. 495 DVLOG(1) << "Starting new ClientLogin fetch for:" << username; 496 497 // Must outlive fetcher_. 498 request_body_ = MakeClientLoginBody(username, 499 password, 500 source_, 501 service, 502 login_token, 503 login_captcha, 504 allow_hosted_accounts); 505 fetcher_.reset(CreateGaiaFetcher(getter_, 506 request_body_, 507 std::string(), 508 client_login_gurl_, 509 kLoadFlagsIgnoreCookies, 510 this)); 511 fetch_pending_ = true; 512 fetcher_->Start(); 513 } 514 515 void GaiaAuthFetcher::StartIssueAuthToken(const std::string& sid, 516 const std::string& lsid, 517 const char* const service) { 518 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!"; 519 520 DVLOG(1) << "Starting IssueAuthToken for: " << service; 521 requested_service_ = service; 522 request_body_ = MakeIssueAuthTokenBody(sid, lsid, service); 523 fetcher_.reset(CreateGaiaFetcher(getter_, 524 request_body_, 525 std::string(), 526 issue_auth_token_gurl_, 527 kLoadFlagsIgnoreCookies, 528 this)); 529 fetch_pending_ = true; 530 fetcher_->Start(); 531 } 532 533 void GaiaAuthFetcher::StartLsoForOAuthLoginTokenExchange( 534 const std::string& auth_token) { 535 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!"; 536 537 DVLOG(1) << "Starting OAuth login token exchange with auth_token"; 538 request_body_ = MakeGetAuthCodeBody(false); 539 client_login_to_oauth2_gurl_ = 540 GaiaUrls::GetInstance()->client_login_to_oauth2_url(); 541 542 fetcher_.reset(CreateGaiaFetcher(getter_, 543 request_body_, 544 MakeGetAuthCodeHeader(auth_token), 545 client_login_to_oauth2_gurl_, 546 kLoadFlagsIgnoreCookies, 547 this)); 548 fetch_pending_ = true; 549 fetcher_->Start(); 550 } 551 552 void GaiaAuthFetcher::StartRevokeOAuth2Token(const std::string& auth_token) { 553 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!"; 554 555 DVLOG(1) << "Starting OAuth2 token revocation"; 556 request_body_ = MakeRevokeTokenBody(auth_token); 557 fetcher_.reset(CreateGaiaFetcher(getter_, 558 request_body_, 559 std::string(), 560 oauth2_revoke_gurl_, 561 kLoadFlagsIgnoreCookies, 562 this)); 563 fetch_pending_ = true; 564 fetcher_->Start(); 565 } 566 567 void GaiaAuthFetcher::StartCookieForOAuthLoginTokenExchange( 568 const std::string& session_index) { 569 StartCookieForOAuthLoginTokenExchangeWithDeviceId(session_index, 570 std::string()); 571 } 572 573 void GaiaAuthFetcher::StartCookieForOAuthLoginTokenExchangeWithDeviceId( 574 const std::string& session_index, 575 const std::string& device_id) { 576 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!"; 577 578 DVLOG(1) << "Starting OAuth login token fetch with cookie jar"; 579 request_body_ = MakeGetAuthCodeBody(!device_id.empty()); 580 581 client_login_to_oauth2_gurl_ = 582 GaiaUrls::GetInstance()->client_login_to_oauth2_url(); 583 if (!session_index.empty()) { 584 client_login_to_oauth2_gurl_ = 585 client_login_to_oauth2_gurl_.Resolve("?authuser=" + session_index); 586 } 587 588 std::string device_id_header; 589 if (!device_id.empty()) { 590 device_id_header = 591 base::StringPrintf(kDeviceIdHeaderFormat, device_id.c_str()); 592 } 593 594 fetcher_.reset(CreateGaiaFetcher(getter_, 595 request_body_, 596 device_id_header, 597 client_login_to_oauth2_gurl_, 598 net::LOAD_NORMAL, 599 this)); 600 fetch_pending_ = true; 601 fetcher_->Start(); 602 } 603 604 void GaiaAuthFetcher::StartAuthCodeForOAuth2TokenExchange( 605 const std::string& auth_code) { 606 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!"; 607 608 DVLOG(1) << "Starting OAuth token pair fetch"; 609 request_body_ = MakeGetTokenPairBody(auth_code); 610 fetcher_.reset(CreateGaiaFetcher(getter_, 611 request_body_, 612 std::string(), 613 oauth2_token_gurl_, 614 kLoadFlagsIgnoreCookies, 615 this)); 616 fetch_pending_ = true; 617 fetcher_->Start(); 618 } 619 620 void GaiaAuthFetcher::StartGetUserInfo(const std::string& lsid) { 621 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!"; 622 623 DVLOG(1) << "Starting GetUserInfo for lsid=" << lsid; 624 request_body_ = MakeGetUserInfoBody(lsid); 625 fetcher_.reset(CreateGaiaFetcher(getter_, 626 request_body_, 627 std::string(), 628 get_user_info_gurl_, 629 kLoadFlagsIgnoreCookies, 630 this)); 631 fetch_pending_ = true; 632 fetcher_->Start(); 633 } 634 635 void GaiaAuthFetcher::StartMergeSession(const std::string& uber_token, 636 const std::string& external_cc_result) { 637 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!"; 638 639 DVLOG(1) << "Starting MergeSession with uber_token=" << uber_token; 640 641 // The continue URL is a required parameter of the MergeSession API, but in 642 // this case we don't actually need or want to navigate to it. Setting it to 643 // an arbitrary Google URL. 644 // 645 // In order for the new session to be merged correctly, the server needs to 646 // know what sessions already exist in the browser. The fetcher needs to be 647 // created such that it sends the cookies with the request, which is 648 // different from all other requests the fetcher can make. 649 std::string continue_url("http://www.google.com"); 650 request_body_ = MakeMergeSessionBody(uber_token, external_cc_result, 651 continue_url, source_); 652 fetcher_.reset(CreateGaiaFetcher(getter_, 653 request_body_, 654 std::string(), 655 merge_session_gurl_, 656 net::LOAD_NORMAL, 657 this)); 658 fetch_pending_ = true; 659 fetcher_->Start(); 660 } 661 662 void GaiaAuthFetcher::StartTokenFetchForUberAuthExchange( 663 const std::string& access_token) { 664 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!"; 665 666 DVLOG(1) << "Starting StartTokenFetchForUberAuthExchange with access_token=" 667 << access_token; 668 std::string authentication_header = 669 base::StringPrintf(kOAuthHeaderFormat, access_token.c_str()); 670 fetcher_.reset(CreateGaiaFetcher(getter_, 671 std::string(), 672 authentication_header, 673 uberauth_token_gurl_, 674 net::LOAD_NORMAL, 675 this)); 676 fetch_pending_ = true; 677 fetcher_->Start(); 678 } 679 680 void GaiaAuthFetcher::StartOAuthLogin(const std::string& access_token, 681 const std::string& service) { 682 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!"; 683 684 request_body_ = MakeOAuthLoginBody(service, source_); 685 std::string authentication_header = 686 base::StringPrintf(kOAuth2BearerHeaderFormat, access_token.c_str()); 687 fetcher_.reset(CreateGaiaFetcher(getter_, 688 request_body_, 689 authentication_header, 690 oauth_login_gurl_, 691 net::LOAD_NORMAL, 692 this)); 693 fetch_pending_ = true; 694 fetcher_->Start(); 695 } 696 697 void GaiaAuthFetcher::StartListAccounts() { 698 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!"; 699 700 fetcher_.reset(CreateGaiaFetcher(getter_, 701 " ", // To force an HTTP POST. 702 "Origin: https://www.google.com", 703 list_accounts_gurl_, 704 net::LOAD_NORMAL, 705 this)); 706 fetch_pending_ = true; 707 fetcher_->Start(); 708 } 709 710 void GaiaAuthFetcher::StartGetCheckConnectionInfo() { 711 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!"; 712 713 fetcher_.reset(CreateGaiaFetcher(getter_, 714 std::string(), 715 std::string(), 716 get_check_connection_info_url_, 717 kLoadFlagsIgnoreCookies, 718 this)); 719 fetch_pending_ = true; 720 fetcher_->Start(); 721 } 722 723 // static 724 GoogleServiceAuthError GaiaAuthFetcher::GenerateAuthError( 725 const std::string& data, 726 const net::URLRequestStatus& status) { 727 if (!status.is_success()) { 728 if (status.status() == net::URLRequestStatus::CANCELED) { 729 return GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED); 730 } 731 DLOG(WARNING) << "Could not reach Google Accounts servers: errno " 732 << status.error(); 733 return GoogleServiceAuthError::FromConnectionError(status.error()); 734 } 735 736 if (IsSecondFactorSuccess(data)) 737 return GoogleServiceAuthError(GoogleServiceAuthError::TWO_FACTOR); 738 739 std::string error; 740 std::string url; 741 std::string captcha_url; 742 std::string captcha_token; 743 ParseClientLoginFailure(data, &error, &url, &captcha_url, &captcha_token); 744 DLOG(WARNING) << "ClientLogin failed with " << error; 745 746 if (error == kCaptchaError) { 747 return GoogleServiceAuthError::FromClientLoginCaptchaChallenge( 748 captcha_token, 749 GURL(GaiaUrls::GetInstance()->captcha_base_url().Resolve(captcha_url)), 750 GURL(url)); 751 } 752 if (error == kAccountDeletedError) 753 return GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DELETED); 754 if (error == kAccountDisabledError) 755 return GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DISABLED); 756 if (error == kBadAuthenticationError) { 757 return GoogleServiceAuthError( 758 GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS); 759 } 760 if (error == kServiceUnavailableError) { 761 return GoogleServiceAuthError( 762 GoogleServiceAuthError::SERVICE_UNAVAILABLE); 763 } 764 765 DLOG(WARNING) << "Incomprehensible response from Google Accounts servers."; 766 return GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE); 767 } 768 769 void GaiaAuthFetcher::OnClientLoginFetched(const std::string& data, 770 const net::URLRequestStatus& status, 771 int response_code) { 772 if (status.is_success() && response_code == net::HTTP_OK) { 773 DVLOG(1) << "ClientLogin successful!"; 774 std::string sid; 775 std::string lsid; 776 std::string token; 777 ParseClientLoginResponse(data, &sid, &lsid, &token); 778 consumer_->OnClientLoginSuccess( 779 GaiaAuthConsumer::ClientLoginResult(sid, lsid, token, data)); 780 } else { 781 consumer_->OnClientLoginFailure(GenerateAuthError(data, status)); 782 } 783 } 784 785 void GaiaAuthFetcher::OnIssueAuthTokenFetched( 786 const std::string& data, 787 const net::URLRequestStatus& status, 788 int response_code) { 789 if (status.is_success() && response_code == net::HTTP_OK) { 790 // Only the bare token is returned in the body of this Gaia call 791 // without any padding. 792 consumer_->OnIssueAuthTokenSuccess(requested_service_, data); 793 } else { 794 consumer_->OnIssueAuthTokenFailure(requested_service_, 795 GenerateAuthError(data, status)); 796 } 797 } 798 799 void GaiaAuthFetcher::OnClientLoginToOAuth2Fetched( 800 const std::string& data, 801 const net::ResponseCookies& cookies, 802 const net::URLRequestStatus& status, 803 int response_code) { 804 if (status.is_success() && response_code == net::HTTP_OK) { 805 std::string auth_code; 806 if (ParseClientLoginToOAuth2Response(cookies, &auth_code)) { 807 StartAuthCodeForOAuth2TokenExchange(auth_code); 808 } else { 809 GoogleServiceAuthError auth_error( 810 GoogleServiceAuthError::FromUnexpectedServiceResponse( 811 "ClientLogin response cookies didn't contain an auth code")); 812 consumer_->OnClientOAuthFailure(auth_error); 813 } 814 } else { 815 GoogleServiceAuthError auth_error(GenerateAuthError(data, status)); 816 consumer_->OnClientOAuthFailure(auth_error); 817 } 818 } 819 820 void GaiaAuthFetcher::OnOAuth2TokenPairFetched( 821 const std::string& data, 822 const net::URLRequestStatus& status, 823 int response_code) { 824 std::string refresh_token; 825 std::string access_token; 826 int expires_in_secs = 0; 827 828 bool success = false; 829 if (status.is_success() && response_code == net::HTTP_OK) { 830 scoped_ptr<base::Value> value(base::JSONReader::Read(data)); 831 if (value.get() && value->GetType() == base::Value::TYPE_DICTIONARY) { 832 base::DictionaryValue* dict = 833 static_cast<base::DictionaryValue*>(value.get()); 834 success = ExtractOAuth2TokenPairResponse(dict, &refresh_token, 835 &access_token, &expires_in_secs); 836 } 837 } 838 839 if (success) { 840 consumer_->OnClientOAuthSuccess( 841 GaiaAuthConsumer::ClientOAuthResult(refresh_token, access_token, 842 expires_in_secs)); 843 } else { 844 consumer_->OnClientOAuthFailure(GenerateAuthError(data, status)); 845 } 846 } 847 848 void GaiaAuthFetcher::OnOAuth2RevokeTokenFetched( 849 const std::string& data, 850 const net::URLRequestStatus& status, 851 int response_code) { 852 consumer_->OnOAuth2RevokeTokenCompleted(); 853 } 854 855 void GaiaAuthFetcher::OnListAccountsFetched(const std::string& data, 856 const net::URLRequestStatus& status, 857 int response_code) { 858 if (status.is_success() && response_code == net::HTTP_OK) { 859 consumer_->OnListAccountsSuccess(data); 860 } else { 861 consumer_->OnListAccountsFailure(GenerateAuthError(data, status)); 862 } 863 } 864 865 void GaiaAuthFetcher::OnGetUserInfoFetched( 866 const std::string& data, 867 const net::URLRequestStatus& status, 868 int response_code) { 869 if (status.is_success() && response_code == net::HTTP_OK) { 870 base::StringPairs tokens; 871 UserInfoMap matches; 872 base::SplitStringIntoKeyValuePairs(data, '=', '\n', &tokens); 873 base::StringPairs::iterator i; 874 for (i = tokens.begin(); i != tokens.end(); ++i) { 875 matches[i->first] = i->second; 876 } 877 consumer_->OnGetUserInfoSuccess(matches); 878 } else { 879 consumer_->OnGetUserInfoFailure(GenerateAuthError(data, status)); 880 } 881 } 882 883 void GaiaAuthFetcher::OnMergeSessionFetched(const std::string& data, 884 const net::URLRequestStatus& status, 885 int response_code) { 886 if (status.is_success() && response_code == net::HTTP_OK) { 887 consumer_->OnMergeSessionSuccess(data); 888 } else { 889 consumer_->OnMergeSessionFailure(GenerateAuthError(data, status)); 890 } 891 } 892 893 void GaiaAuthFetcher::OnUberAuthTokenFetch(const std::string& data, 894 const net::URLRequestStatus& status, 895 int response_code) { 896 if (status.is_success() && response_code == net::HTTP_OK) { 897 consumer_->OnUberAuthTokenSuccess(data); 898 } else { 899 consumer_->OnUberAuthTokenFailure(GenerateAuthError(data, status)); 900 } 901 } 902 903 void GaiaAuthFetcher::OnOAuthLoginFetched(const std::string& data, 904 const net::URLRequestStatus& status, 905 int response_code) { 906 if (status.is_success() && response_code == net::HTTP_OK) { 907 DVLOG(1) << "ClientLogin successful!"; 908 std::string sid; 909 std::string lsid; 910 std::string token; 911 ParseClientLoginResponse(data, &sid, &lsid, &token); 912 consumer_->OnClientLoginSuccess( 913 GaiaAuthConsumer::ClientLoginResult(sid, lsid, token, data)); 914 } else { 915 consumer_->OnClientLoginFailure(GenerateAuthError(data, status)); 916 } 917 } 918 919 void GaiaAuthFetcher::OnGetCheckConnectionInfoFetched( 920 const std::string& data, 921 const net::URLRequestStatus& status, 922 int response_code) { 923 if (status.is_success() && response_code == net::HTTP_OK) { 924 consumer_->OnGetCheckConnectionInfoSuccess(data); 925 } else { 926 consumer_->OnGetCheckConnectionInfoError(GenerateAuthError(data, status)); 927 } 928 } 929 930 void GaiaAuthFetcher::OnURLFetchComplete(const net::URLFetcher* source) { 931 fetch_pending_ = false; 932 // Some of the GAIA requests perform redirects, which results in the final 933 // URL of the fetcher not being the original URL requested. Therefore use 934 // the original URL when determining which OnXXX function to call. 935 const GURL& url = source->GetOriginalURL(); 936 const net::URLRequestStatus& status = source->GetStatus(); 937 int response_code = source->GetResponseCode(); 938 std::string data; 939 source->GetResponseAsString(&data); 940 #ifndef NDEBUG 941 std::string headers; 942 if (source->GetResponseHeaders()) 943 source->GetResponseHeaders()->GetNormalizedHeaders(&headers); 944 DVLOG(2) << "Response " << url.spec() << ", code = " << response_code << "\n" 945 << headers << "\n"; 946 DVLOG(2) << "data: " << data << "\n"; 947 #endif 948 // Retrieve the response headers from the request. Must only be called after 949 // the OnURLFetchComplete callback has run. 950 if (url == client_login_gurl_) { 951 OnClientLoginFetched(data, status, response_code); 952 } else if (url == issue_auth_token_gurl_) { 953 OnIssueAuthTokenFetched(data, status, response_code); 954 } else if (url == client_login_to_oauth2_gurl_) { 955 OnClientLoginToOAuth2Fetched( 956 data, source->GetCookies(), status, response_code); 957 } else if (url == oauth2_token_gurl_) { 958 OnOAuth2TokenPairFetched(data, status, response_code); 959 } else if (url == get_user_info_gurl_) { 960 OnGetUserInfoFetched(data, status, response_code); 961 } else if (url == merge_session_gurl_) { 962 OnMergeSessionFetched(data, status, response_code); 963 } else if (url == uberauth_token_gurl_) { 964 OnUberAuthTokenFetch(data, status, response_code); 965 } else if (url == oauth_login_gurl_) { 966 OnOAuthLoginFetched(data, status, response_code); 967 } else if (url == oauth2_revoke_gurl_) { 968 OnOAuth2RevokeTokenFetched(data, status, response_code); 969 } else if (url == list_accounts_gurl_) { 970 OnListAccountsFetched(data, status, response_code); 971 } else if (url == get_check_connection_info_url_) { 972 OnGetCheckConnectionInfoFetched(data, status, response_code); 973 } else { 974 NOTREACHED(); 975 } 976 } 977 978 // static 979 bool GaiaAuthFetcher::IsSecondFactorSuccess( 980 const std::string& alleged_error) { 981 return alleged_error.find(kSecondFactor) != 982 std::string::npos; 983 } 984