gaia_auth_fetcher.cc revision a1401311d1ab56c4ed0a474bd38c108f75cb0cd9
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"; 117const char GaiaAuthFetcher::kAccountDeletedErrorCode[] = "adel"; 118// static 119const char GaiaAuthFetcher::kAccountDisabledError[] = "AccountDisabled"; 120const char GaiaAuthFetcher::kAccountDisabledErrorCode[] = "adis"; 121// static 122const char GaiaAuthFetcher::kBadAuthenticationError[] = "BadAuthentication"; 123const char GaiaAuthFetcher::kBadAuthenticationErrorCode[] = "badauth"; 124// static 125const char GaiaAuthFetcher::kCaptchaError[] = "CaptchaRequired"; 126const char GaiaAuthFetcher::kCaptchaErrorCode[] = "cr"; 127// static 128const char GaiaAuthFetcher::kServiceUnavailableError[] = 129 "ServiceUnavailable"; 130const char GaiaAuthFetcher::kServiceUnavailableErrorCode[] = 131 "ire"; 132// static 133const char GaiaAuthFetcher::kErrorParam[] = "Error"; 134// static 135const char GaiaAuthFetcher::kErrorUrlParam[] = "Url"; 136// static 137const char GaiaAuthFetcher::kCaptchaUrlParam[] = "CaptchaUrl"; 138// static 139const char GaiaAuthFetcher::kCaptchaTokenParam[] = "CaptchaToken"; 140 141// static 142const char GaiaAuthFetcher::kCookiePersistence[] = "true"; 143// static 144// TODO(johnnyg): When hosted accounts are supported by sync, 145// we can always use "HOSTED_OR_GOOGLE" 146const char GaiaAuthFetcher::kAccountTypeHostedOrGoogle[] = 147 "HOSTED_OR_GOOGLE"; 148const char GaiaAuthFetcher::kAccountTypeGoogle[] = 149 "GOOGLE"; 150 151// static 152const char GaiaAuthFetcher::kSecondFactor[] = "Info=InvalidSecondFactor"; 153 154// static 155const char GaiaAuthFetcher::kAuthHeaderFormat[] = 156 "Authorization: GoogleLogin auth=%s"; 157// static 158const char GaiaAuthFetcher::kOAuthHeaderFormat[] = "Authorization: OAuth %s"; 159// static 160const char GaiaAuthFetcher::kOAuth2BearerHeaderFormat[] = 161 "Authorization: Bearer %s"; 162// static 163const char GaiaAuthFetcher::kClientLoginToOAuth2CookiePartSecure[] = "secure"; 164// static 165const char GaiaAuthFetcher::kClientLoginToOAuth2CookiePartHttpOnly[] = 166 "httponly"; 167// static 168const char GaiaAuthFetcher::kClientLoginToOAuth2CookiePartCodePrefix[] = 169 "oauth_code="; 170// static 171const int GaiaAuthFetcher::kClientLoginToOAuth2CookiePartCodePrefixLength = 172 arraysize(GaiaAuthFetcher::kClientLoginToOAuth2CookiePartCodePrefix) - 1; 173 174GaiaAuthFetcher::GaiaAuthFetcher(GaiaAuthConsumer* consumer, 175 const std::string& source, 176 net::URLRequestContextGetter* getter) 177 : consumer_(consumer), 178 getter_(getter), 179 source_(source), 180 client_login_gurl_(GaiaUrls::GetInstance()->client_login_url()), 181 issue_auth_token_gurl_(GaiaUrls::GetInstance()->issue_auth_token_url()), 182 oauth2_token_gurl_(GaiaUrls::GetInstance()->oauth2_token_url()), 183 oauth2_revoke_gurl_(GaiaUrls::GetInstance()->oauth2_revoke_url()), 184 get_user_info_gurl_(GaiaUrls::GetInstance()->get_user_info_url()), 185 merge_session_gurl_(GaiaUrls::GetInstance()->merge_session_url()), 186 uberauth_token_gurl_(GaiaUrls::GetInstance()->oauth1_login_url().Resolve( 187 base::StringPrintf(kUberAuthTokenURLFormat, source.c_str()))), 188 oauth_login_gurl_(GaiaUrls::GetInstance()->oauth1_login_url()), 189 list_accounts_gurl_(GaiaUrls::GetInstance()->list_accounts_url()), 190 client_login_to_oauth2_gurl_( 191 GaiaUrls::GetInstance()->client_login_to_oauth2_url()), 192 fetch_pending_(false), 193 fetch_code_only_(false) {} 194 195GaiaAuthFetcher::~GaiaAuthFetcher() {} 196 197bool GaiaAuthFetcher::HasPendingFetch() { 198 return fetch_pending_; 199} 200 201void GaiaAuthFetcher::CancelRequest() { 202 fetcher_.reset(); 203 fetch_pending_ = false; 204} 205 206// static 207net::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 245std::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 286std::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 306std::string GaiaAuthFetcher::MakeGetAuthCodeBody() { 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 return base::StringPrintf(kClientLoginToOAuth2BodyFormat, 312 encoded_scope.c_str(), 313 encoded_client_id.c_str()); 314} 315 316// static 317std::string GaiaAuthFetcher::MakeGetTokenPairBody( 318 const std::string& auth_code) { 319 std::string encoded_scope = net::EscapeUrlEncodedData( 320 GaiaConstants::kOAuth1LoginScope, true); 321 std::string encoded_client_id = net::EscapeUrlEncodedData( 322 GaiaUrls::GetInstance()->oauth2_chrome_client_id(), true); 323 std::string encoded_client_secret = net::EscapeUrlEncodedData( 324 GaiaUrls::GetInstance()->oauth2_chrome_client_secret(), true); 325 std::string encoded_auth_code = net::EscapeUrlEncodedData(auth_code, true); 326 return base::StringPrintf(kOAuth2CodeToTokenPairBodyFormat, 327 encoded_scope.c_str(), 328 encoded_client_id.c_str(), 329 encoded_client_secret.c_str(), 330 encoded_auth_code.c_str()); 331} 332 333// static 334std::string GaiaAuthFetcher::MakeRevokeTokenBody( 335 const std::string& auth_token) { 336 return base::StringPrintf(kOAuth2RevokeTokenBodyFormat, auth_token.c_str()); 337} 338 339// static 340std::string GaiaAuthFetcher::MakeGetUserInfoBody(const std::string& lsid) { 341 std::string encoded_lsid = net::EscapeUrlEncodedData(lsid, true); 342 return base::StringPrintf(kGetUserInfoFormat, encoded_lsid.c_str()); 343} 344 345// static 346std::string GaiaAuthFetcher::MakeMergeSessionBody( 347 const std::string& auth_token, 348 const std::string& continue_url, 349 const std::string& source) { 350 std::string encoded_auth_token = net::EscapeUrlEncodedData(auth_token, true); 351 std::string encoded_continue_url = net::EscapeUrlEncodedData(continue_url, 352 true); 353 std::string encoded_source = net::EscapeUrlEncodedData(source, true); 354 return base::StringPrintf(kMergeSessionFormat, 355 encoded_auth_token.c_str(), 356 encoded_continue_url.c_str(), 357 encoded_source.c_str()); 358} 359 360// static 361std::string GaiaAuthFetcher::MakeGetAuthCodeHeader( 362 const std::string& auth_token) { 363 return base::StringPrintf(kAuthHeaderFormat, auth_token.c_str()); 364} 365 366// Helper method that extracts tokens from a successful reply. 367// static 368void GaiaAuthFetcher::ParseClientLoginResponse(const std::string& data, 369 std::string* sid, 370 std::string* lsid, 371 std::string* token) { 372 using std::vector; 373 using std::pair; 374 using std::string; 375 sid->clear(); 376 lsid->clear(); 377 token->clear(); 378 vector<pair<string, string> > tokens; 379 base::SplitStringIntoKeyValuePairs(data, '=', '\n', &tokens); 380 for (vector<pair<string, string> >::iterator i = tokens.begin(); 381 i != tokens.end(); ++i) { 382 if (i->first == "SID") { 383 sid->assign(i->second); 384 } else if (i->first == "LSID") { 385 lsid->assign(i->second); 386 } else if (i->first == "Auth") { 387 token->assign(i->second); 388 } 389 } 390 // If this was a request for uberauth token, then that's all we've got in 391 // data. 392 if (sid->empty() && lsid->empty() && token->empty()) 393 token->assign(data); 394} 395 396// static 397std::string GaiaAuthFetcher::MakeOAuthLoginBody(const std::string& service, 398 const std::string& source) { 399 std::string encoded_service = net::EscapeUrlEncodedData(service, true); 400 std::string encoded_source = net::EscapeUrlEncodedData(source, true); 401 return base::StringPrintf(kOAuthLoginFormat, 402 encoded_service.c_str(), 403 encoded_source.c_str()); 404} 405 406// static 407void GaiaAuthFetcher::ParseClientLoginFailure(const std::string& data, 408 std::string* error, 409 std::string* error_url, 410 std::string* captcha_url, 411 std::string* captcha_token) { 412 using std::vector; 413 using std::pair; 414 using std::string; 415 416 vector<pair<string, string> > tokens; 417 base::SplitStringIntoKeyValuePairs(data, '=', '\n', &tokens); 418 for (vector<pair<string, string> >::iterator i = tokens.begin(); 419 i != tokens.end(); ++i) { 420 if (i->first == kErrorParam) { 421 error->assign(i->second); 422 } else if (i->first == kErrorUrlParam) { 423 error_url->assign(i->second); 424 } else if (i->first == kCaptchaUrlParam) { 425 captcha_url->assign(i->second); 426 } else if (i->first == kCaptchaTokenParam) { 427 captcha_token->assign(i->second); 428 } 429 } 430} 431 432// static 433bool GaiaAuthFetcher::ParseClientLoginToOAuth2Response( 434 const net::ResponseCookies& cookies, 435 std::string* auth_code) { 436 DCHECK(auth_code); 437 net::ResponseCookies::const_iterator iter; 438 for (iter = cookies.begin(); iter != cookies.end(); ++iter) { 439 if (ParseClientLoginToOAuth2Cookie(*iter, auth_code)) 440 return true; 441 } 442 return false; 443} 444 445// static 446bool GaiaAuthFetcher::ParseClientLoginToOAuth2Cookie(const std::string& cookie, 447 std::string* auth_code) { 448 std::vector<std::string> parts; 449 base::SplitString(cookie, ';', &parts); 450 // Per documentation, the cookie should have Secure and HttpOnly. 451 if (!CookiePartsContains(parts, kClientLoginToOAuth2CookiePartSecure) || 452 !CookiePartsContains(parts, kClientLoginToOAuth2CookiePartHttpOnly)) { 453 return false; 454 } 455 456 std::vector<std::string>::const_iterator iter; 457 for (iter = parts.begin(); iter != parts.end(); ++iter) { 458 const std::string& part = *iter; 459 if (StartsWithASCII( 460 part, kClientLoginToOAuth2CookiePartCodePrefix, false)) { 461 auth_code->assign(part.substr( 462 kClientLoginToOAuth2CookiePartCodePrefixLength)); 463 return true; 464 } 465 } 466 return false; 467} 468 469void GaiaAuthFetcher::StartClientLogin( 470 const std::string& username, 471 const std::string& password, 472 const char* const service, 473 const std::string& login_token, 474 const std::string& login_captcha, 475 HostedAccountsSetting allow_hosted_accounts) { 476 477 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!"; 478 479 // This class is thread agnostic, so be sure to call this only on the 480 // same thread each time. 481 DVLOG(1) << "Starting new ClientLogin fetch for:" << username; 482 483 // Must outlive fetcher_. 484 request_body_ = MakeClientLoginBody(username, 485 password, 486 source_, 487 service, 488 login_token, 489 login_captcha, 490 allow_hosted_accounts); 491 fetcher_.reset(CreateGaiaFetcher(getter_, 492 request_body_, 493 std::string(), 494 client_login_gurl_, 495 kLoadFlagsIgnoreCookies, 496 this)); 497 fetch_pending_ = true; 498 fetcher_->Start(); 499} 500 501void GaiaAuthFetcher::StartIssueAuthToken(const std::string& sid, 502 const std::string& lsid, 503 const char* const service) { 504 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!"; 505 506 DVLOG(1) << "Starting IssueAuthToken for: " << service; 507 requested_service_ = service; 508 request_body_ = MakeIssueAuthTokenBody(sid, lsid, service); 509 fetcher_.reset(CreateGaiaFetcher(getter_, 510 request_body_, 511 std::string(), 512 issue_auth_token_gurl_, 513 kLoadFlagsIgnoreCookies, 514 this)); 515 fetch_pending_ = true; 516 fetcher_->Start(); 517} 518 519void GaiaAuthFetcher::StartLsoForOAuthLoginTokenExchange( 520 const std::string& auth_token) { 521 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!"; 522 523 DVLOG(1) << "Starting OAuth login token exchange with auth_token"; 524 request_body_ = MakeGetAuthCodeBody(); 525 client_login_to_oauth2_gurl_ = 526 GaiaUrls::GetInstance()->client_login_to_oauth2_url(); 527 528 fetcher_.reset(CreateGaiaFetcher(getter_, 529 request_body_, 530 MakeGetAuthCodeHeader(auth_token), 531 client_login_to_oauth2_gurl_, 532 kLoadFlagsIgnoreCookies, 533 this)); 534 fetch_pending_ = true; 535 fetcher_->Start(); 536} 537 538void GaiaAuthFetcher::StartRevokeOAuth2Token(const std::string& auth_token) { 539 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!"; 540 541 DVLOG(1) << "Starting OAuth2 token revocation"; 542 request_body_ = MakeRevokeTokenBody(auth_token); 543 fetcher_.reset(CreateGaiaFetcher(getter_, 544 request_body_, 545 std::string(), 546 oauth2_revoke_gurl_, 547 kLoadFlagsIgnoreCookies, 548 this)); 549 fetch_pending_ = true; 550 fetcher_->Start(); 551} 552 553void GaiaAuthFetcher::StartCookieForOAuthCodeExchange( 554 const std::string& session_index) { 555 // Same as the first step of StartCookieForOAuthLoginTokenExchange; 556 StartCookieForOAuthLoginTokenExchange(session_index); 557 fetch_code_only_ = true; 558} 559 560void GaiaAuthFetcher::StartCookieForOAuthLoginTokenExchange( 561 const std::string& session_index) { 562 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!"; 563 564 DVLOG(1) << "Starting OAuth login token fetch with cookie jar"; 565 request_body_ = MakeGetAuthCodeBody(); 566 567 client_login_to_oauth2_gurl_ = 568 GaiaUrls::GetInstance()->client_login_to_oauth2_url(); 569 if (!session_index.empty()) { 570 client_login_to_oauth2_gurl_ = 571 client_login_to_oauth2_gurl_.Resolve("?authuser=" + session_index); 572 } 573 574 fetcher_.reset(CreateGaiaFetcher(getter_, 575 request_body_, 576 std::string(), 577 client_login_to_oauth2_gurl_, 578 net::LOAD_NORMAL, 579 this)); 580 fetch_pending_ = true; 581 fetch_code_only_ = false; 582 fetcher_->Start(); 583} 584 585void GaiaAuthFetcher::StartAuthCodeForOAuth2TokenExchange( 586 const std::string& auth_code) { 587 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!"; 588 589 DVLOG(1) << "Starting OAuth token pair fetch"; 590 request_body_ = MakeGetTokenPairBody(auth_code); 591 fetcher_.reset(CreateGaiaFetcher(getter_, 592 request_body_, 593 std::string(), 594 oauth2_token_gurl_, 595 kLoadFlagsIgnoreCookies, 596 this)); 597 fetch_pending_ = true; 598 fetcher_->Start(); 599} 600 601void GaiaAuthFetcher::StartGetUserInfo(const std::string& lsid) { 602 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!"; 603 604 DVLOG(1) << "Starting GetUserInfo for lsid=" << lsid; 605 request_body_ = MakeGetUserInfoBody(lsid); 606 fetcher_.reset(CreateGaiaFetcher(getter_, 607 request_body_, 608 std::string(), 609 get_user_info_gurl_, 610 kLoadFlagsIgnoreCookies, 611 this)); 612 fetch_pending_ = true; 613 fetcher_->Start(); 614} 615 616void GaiaAuthFetcher::StartMergeSession(const std::string& uber_token) { 617 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!"; 618 619 DVLOG(1) << "Starting MergeSession with uber_token=" << uber_token; 620 621 // The continue URL is a required parameter of the MergeSession API, but in 622 // this case we don't actually need or want to navigate to it. Setting it to 623 // an arbitrary Google URL. 624 // 625 // In order for the new session to be merged correctly, the server needs to 626 // know what sessions already exist in the browser. The fetcher needs to be 627 // created such that it sends the cookies with the request, which is 628 // different from all other requests the fetcher can make. 629 std::string continue_url("http://www.google.com"); 630 request_body_ = MakeMergeSessionBody(uber_token, continue_url, source_); 631 fetcher_.reset(CreateGaiaFetcher(getter_, 632 request_body_, 633 std::string(), 634 merge_session_gurl_, 635 net::LOAD_NORMAL, 636 this)); 637 fetch_pending_ = true; 638 fetcher_->Start(); 639} 640 641void GaiaAuthFetcher::StartTokenFetchForUberAuthExchange( 642 const std::string& access_token) { 643 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!"; 644 645 DVLOG(1) << "Starting StartTokenFetchForUberAuthExchange with access_token=" 646 << access_token; 647 std::string authentication_header = 648 base::StringPrintf(kOAuthHeaderFormat, access_token.c_str()); 649 fetcher_.reset(CreateGaiaFetcher(getter_, 650 std::string(), 651 authentication_header, 652 uberauth_token_gurl_, 653 net::LOAD_NORMAL, 654 this)); 655 fetch_pending_ = true; 656 fetcher_->Start(); 657} 658 659void GaiaAuthFetcher::StartOAuthLogin(const std::string& access_token, 660 const std::string& service) { 661 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!"; 662 663 request_body_ = MakeOAuthLoginBody(service, source_); 664 std::string authentication_header = 665 base::StringPrintf(kOAuth2BearerHeaderFormat, access_token.c_str()); 666 fetcher_.reset(CreateGaiaFetcher(getter_, 667 request_body_, 668 authentication_header, 669 oauth_login_gurl_, 670 net::LOAD_NORMAL, 671 this)); 672 fetch_pending_ = true; 673 fetcher_->Start(); 674} 675 676void GaiaAuthFetcher::StartListAccounts() { 677 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!"; 678 679 fetcher_.reset(CreateGaiaFetcher(getter_, 680 " ", // To force an HTTP POST. 681 "Origin: https://www.google.com", 682 list_accounts_gurl_, 683 net::LOAD_NORMAL, 684 this)); 685 fetch_pending_ = true; 686 fetcher_->Start(); 687} 688 689// static 690GoogleServiceAuthError GaiaAuthFetcher::GenerateAuthError( 691 const std::string& data, 692 const net::URLRequestStatus& status) { 693 if (!status.is_success()) { 694 if (status.status() == net::URLRequestStatus::CANCELED) { 695 return GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED); 696 } else { 697 DLOG(WARNING) << "Could not reach Google Accounts servers: errno " 698 << status.error(); 699 return GoogleServiceAuthError::FromConnectionError(status.error()); 700 } 701 } else { 702 if (IsSecondFactorSuccess(data)) { 703 return GoogleServiceAuthError(GoogleServiceAuthError::TWO_FACTOR); 704 } 705 706 std::string error; 707 std::string url; 708 std::string captcha_url; 709 std::string captcha_token; 710 ParseClientLoginFailure(data, &error, &url, &captcha_url, &captcha_token); 711 DLOG(WARNING) << "ClientLogin failed with " << error; 712 713 if (error == kCaptchaError) { 714 GURL image_url( 715 GaiaUrls::GetInstance()->captcha_base_url().Resolve(captcha_url)); 716 GURL unlock_url(url); 717 return GoogleServiceAuthError::FromClientLoginCaptchaChallenge( 718 captcha_token, image_url, unlock_url); 719 } 720 if (error == kAccountDeletedError) 721 return GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DELETED); 722 if (error == kAccountDisabledError) 723 return GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DISABLED); 724 if (error == kBadAuthenticationError) { 725 return GoogleServiceAuthError( 726 GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS); 727 } 728 if (error == kServiceUnavailableError) { 729 return GoogleServiceAuthError( 730 GoogleServiceAuthError::SERVICE_UNAVAILABLE); 731 } 732 733 DLOG(WARNING) << "Incomprehensible response from Google Accounts servers."; 734 return GoogleServiceAuthError( 735 GoogleServiceAuthError::SERVICE_UNAVAILABLE); 736 } 737 738 NOTREACHED(); 739 return GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE); 740} 741 742// static 743GoogleServiceAuthError GaiaAuthFetcher::GenerateOAuthLoginError( 744 const std::string& data, 745 const net::URLRequestStatus& status) { 746 if (!status.is_success()) { 747 if (status.status() == net::URLRequestStatus::CANCELED) { 748 return GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED); 749 } else { 750 DLOG(WARNING) << "Could not reach Google Accounts servers: errno " 751 << status.error(); 752 return GoogleServiceAuthError::FromConnectionError(status.error()); 753 } 754 } else { 755 if (IsSecondFactorSuccess(data)) { 756 return GoogleServiceAuthError(GoogleServiceAuthError::TWO_FACTOR); 757 } 758 759 std::string error; 760 std::string url; 761 std::string captcha_url; 762 std::string captcha_token; 763 ParseClientLoginFailure(data, &error, &url, &captcha_url, &captcha_token); 764 LOG(WARNING) << "OAuthLogin failed with " << error; 765 766 if (error == kCaptchaErrorCode) { 767 GURL image_url( 768 GaiaUrls::GetInstance()->captcha_base_url().Resolve(captcha_url)); 769 GURL unlock_url(url); 770 return GoogleServiceAuthError::FromClientLoginCaptchaChallenge( 771 captcha_token, image_url, unlock_url); 772 } 773 if (error == kAccountDeletedErrorCode) 774 return GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DELETED); 775 if (error == kAccountDisabledErrorCode) 776 return GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DISABLED); 777 if (error == kBadAuthenticationErrorCode) { 778 return GoogleServiceAuthError( 779 GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS); 780 } 781 if (error == kServiceUnavailableErrorCode) { 782 return GoogleServiceAuthError( 783 GoogleServiceAuthError::SERVICE_UNAVAILABLE); 784 } 785 786 DLOG(WARNING) << "Incomprehensible response from Google Accounts servers."; 787 return GoogleServiceAuthError( 788 GoogleServiceAuthError::SERVICE_UNAVAILABLE); 789 } 790 791 NOTREACHED(); 792 return GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE); 793} 794 795void GaiaAuthFetcher::OnClientLoginFetched(const std::string& data, 796 const net::URLRequestStatus& status, 797 int response_code) { 798 if (status.is_success() && response_code == net::HTTP_OK) { 799 DVLOG(1) << "ClientLogin successful!"; 800 std::string sid; 801 std::string lsid; 802 std::string token; 803 ParseClientLoginResponse(data, &sid, &lsid, &token); 804 consumer_->OnClientLoginSuccess( 805 GaiaAuthConsumer::ClientLoginResult(sid, lsid, token, data)); 806 } else { 807 consumer_->OnClientLoginFailure(GenerateAuthError(data, status)); 808 } 809} 810 811void GaiaAuthFetcher::OnIssueAuthTokenFetched( 812 const std::string& data, 813 const net::URLRequestStatus& status, 814 int response_code) { 815 if (status.is_success() && response_code == net::HTTP_OK) { 816 // Only the bare token is returned in the body of this Gaia call 817 // without any padding. 818 consumer_->OnIssueAuthTokenSuccess(requested_service_, data); 819 } else { 820 consumer_->OnIssueAuthTokenFailure(requested_service_, 821 GenerateAuthError(data, status)); 822 } 823} 824 825void GaiaAuthFetcher::OnClientLoginToOAuth2Fetched( 826 const std::string& data, 827 const net::ResponseCookies& cookies, 828 const net::URLRequestStatus& status, 829 int response_code) { 830 if (status.is_success() && response_code == net::HTTP_OK) { 831 std::string auth_code; 832 ParseClientLoginToOAuth2Response(cookies, &auth_code); 833 if (fetch_code_only_) 834 consumer_->OnClientOAuthCodeSuccess(auth_code); 835 else 836 StartAuthCodeForOAuth2TokenExchange(auth_code); 837 } else { 838 GoogleServiceAuthError auth_error(GenerateAuthError(data, status)); 839 if (fetch_code_only_) 840 consumer_->OnClientOAuthCodeFailure(auth_error); 841 else 842 consumer_->OnClientOAuthFailure(auth_error); 843 } 844} 845 846void GaiaAuthFetcher::OnOAuth2TokenPairFetched( 847 const std::string& data, 848 const net::URLRequestStatus& status, 849 int response_code) { 850 std::string refresh_token; 851 std::string access_token; 852 int expires_in_secs = 0; 853 854 bool success = false; 855 if (status.is_success() && response_code == net::HTTP_OK) { 856 scoped_ptr<base::Value> value(base::JSONReader::Read(data)); 857 if (value.get() && value->GetType() == base::Value::TYPE_DICTIONARY) { 858 base::DictionaryValue* dict = 859 static_cast<base::DictionaryValue*>(value.get()); 860 success = ExtractOAuth2TokenPairResponse(dict, &refresh_token, 861 &access_token, &expires_in_secs); 862 } 863 } 864 865 if (success) { 866 consumer_->OnClientOAuthSuccess( 867 GaiaAuthConsumer::ClientOAuthResult(refresh_token, access_token, 868 expires_in_secs)); 869 } else { 870 consumer_->OnClientOAuthFailure(GenerateAuthError(data, status)); 871 } 872} 873 874void GaiaAuthFetcher::OnOAuth2RevokeTokenFetched( 875 const std::string& data, 876 const net::URLRequestStatus& status, 877 int response_code) { 878 consumer_->OnOAuth2RevokeTokenCompleted(); 879} 880 881void GaiaAuthFetcher::OnListAccountsFetched(const std::string& data, 882 const net::URLRequestStatus& status, 883 int response_code) { 884 if (status.is_success() && response_code == net::HTTP_OK) { 885 consumer_->OnListAccountsSuccess(data); 886 } else { 887 consumer_->OnListAccountsFailure(GenerateAuthError(data, status)); 888 } 889} 890 891void GaiaAuthFetcher::OnGetUserInfoFetched( 892 const std::string& data, 893 const net::URLRequestStatus& status, 894 int response_code) { 895 if (status.is_success() && response_code == net::HTTP_OK) { 896 std::vector<std::pair<std::string, std::string> > tokens; 897 UserInfoMap matches; 898 base::SplitStringIntoKeyValuePairs(data, '=', '\n', &tokens); 899 std::vector<std::pair<std::string, std::string> >::iterator i; 900 for (i = tokens.begin(); i != tokens.end(); ++i) { 901 matches[i->first] = i->second; 902 } 903 consumer_->OnGetUserInfoSuccess(matches); 904 } else { 905 consumer_->OnGetUserInfoFailure(GenerateAuthError(data, status)); 906 } 907} 908 909void GaiaAuthFetcher::OnMergeSessionFetched(const std::string& data, 910 const net::URLRequestStatus& status, 911 int response_code) { 912 if (status.is_success() && response_code == net::HTTP_OK) { 913 consumer_->OnMergeSessionSuccess(data); 914 } else { 915 consumer_->OnMergeSessionFailure(GenerateAuthError(data, status)); 916 } 917} 918 919void GaiaAuthFetcher::OnUberAuthTokenFetch(const std::string& data, 920 const net::URLRequestStatus& status, 921 int response_code) { 922 if (status.is_success() && response_code == net::HTTP_OK) { 923 consumer_->OnUberAuthTokenSuccess(data); 924 } else { 925 consumer_->OnUberAuthTokenFailure(GenerateAuthError(data, status)); 926 } 927} 928 929void GaiaAuthFetcher::OnOAuthLoginFetched(const std::string& data, 930 const net::URLRequestStatus& status, 931 int response_code) { 932 if (status.is_success() && response_code == net::HTTP_OK) { 933 DVLOG(1) << "ClientLogin successful!"; 934 std::string sid; 935 std::string lsid; 936 std::string token; 937 ParseClientLoginResponse(data, &sid, &lsid, &token); 938 consumer_->OnClientLoginSuccess( 939 GaiaAuthConsumer::ClientLoginResult(sid, lsid, token, data)); 940 } else { 941 consumer_->OnClientLoginFailure(GenerateAuthError(data, status)); 942 } 943} 944 945void GaiaAuthFetcher::OnURLFetchComplete(const net::URLFetcher* source) { 946 fetch_pending_ = false; 947 // Some of the GAIA requests perform redirects, which results in the final 948 // URL of the fetcher not being the original URL requested. Therefore use 949 // the original URL when determining which OnXXX function to call. 950 const GURL& url = source->GetOriginalURL(); 951 const net::URLRequestStatus& status = source->GetStatus(); 952 int response_code = source->GetResponseCode(); 953 std::string data; 954 source->GetResponseAsString(&data); 955#ifndef NDEBUG 956 std::string headers; 957 if (source->GetResponseHeaders()) 958 source->GetResponseHeaders()->GetNormalizedHeaders(&headers); 959 DVLOG(2) << "Response " << url.spec() << ", code = " << response_code << "\n" 960 << headers << "\n"; 961 DVLOG(2) << "data: " << data << "\n"; 962#endif 963 // Retrieve the response headers from the request. Must only be called after 964 // the OnURLFetchComplete callback has run. 965 if (url == client_login_gurl_) { 966 OnClientLoginFetched(data, status, response_code); 967 } else if (url == issue_auth_token_gurl_) { 968 OnIssueAuthTokenFetched(data, status, response_code); 969 } else if (url == client_login_to_oauth2_gurl_) { 970 OnClientLoginToOAuth2Fetched( 971 data, source->GetCookies(), status, response_code); 972 } else if (url == oauth2_token_gurl_) { 973 OnOAuth2TokenPairFetched(data, status, response_code); 974 } else if (url == get_user_info_gurl_) { 975 OnGetUserInfoFetched(data, status, response_code); 976 } else if (url == merge_session_gurl_) { 977 OnMergeSessionFetched(data, status, response_code); 978 } else if (url == uberauth_token_gurl_) { 979 OnUberAuthTokenFetch(data, status, response_code); 980 } else if (url == oauth_login_gurl_) { 981 OnOAuthLoginFetched(data, status, response_code); 982 } else if (url == oauth2_revoke_gurl_) { 983 OnOAuth2RevokeTokenFetched(data, status, response_code); 984 } else if (url == list_accounts_gurl_) { 985 OnListAccountsFetched(data, status, response_code); 986 } else { 987 NOTREACHED(); 988 } 989} 990 991// static 992bool GaiaAuthFetcher::IsSecondFactorSuccess( 993 const std::string& alleged_error) { 994 return alleged_error.find(kSecondFactor) != 995 std::string::npos; 996} 997