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