gaia_auth_fetcher.cc revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
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 <algorithm> 8#include <string> 9#include <utility> 10#include <vector> 11 12#include "base/json/json_reader.h" 13#include "base/json/json_writer.h" 14#include "base/string_util.h" 15#include "base/stringprintf.h" 16#include "base/strings/string_split.h" 17#include "base/values.h" 18#include "google_apis/gaia/gaia_auth_consumer.h" 19#include "google_apis/gaia/gaia_constants.h" 20#include "google_apis/gaia/gaia_urls.h" 21#include "google_apis/gaia/google_service_auth_error.h" 22#include "net/base/escape.h" 23#include "net/base/load_flags.h" 24#include "net/http/http_response_headers.h" 25#include "net/http/http_status_code.h" 26#include "net/url_request/url_fetcher.h" 27#include "net/url_request/url_request_context_getter.h" 28#include "net/url_request/url_request_status.h" 29 30namespace { 31const int kLoadFlagsIgnoreCookies = net::LOAD_DO_NOT_SEND_COOKIES | 32 net::LOAD_DO_NOT_SAVE_COOKIES; 33 34static bool CookiePartsContains(const std::vector<std::string>& parts, 35 const char* part) { 36 return std::find(parts.begin(), parts.end(), part) != parts.end(); 37} 38 39bool ExtractOAuth2TokenPairResponse(DictionaryValue* dict, 40 std::string* refresh_token, 41 std::string* access_token, 42 int* expires_in_secs) { 43 DCHECK(refresh_token); 44 DCHECK(access_token); 45 DCHECK(expires_in_secs); 46 47 if (!dict->GetStringWithoutPathExpansion("refresh_token", refresh_token) || 48 !dict->GetStringWithoutPathExpansion("access_token", access_token) || 49 !dict->GetIntegerWithoutPathExpansion("expires_in", expires_in_secs)) { 50 return false; 51 } 52 53 return true; 54} 55 56} // namespace 57 58// TODO(chron): Add sourceless version of this formatter. 59// static 60const char GaiaAuthFetcher::kClientLoginFormat[] = 61 "Email=%s&" 62 "Passwd=%s&" 63 "PersistentCookie=%s&" 64 "accountType=%s&" 65 "source=%s&" 66 "service=%s"; 67// static 68const char GaiaAuthFetcher::kClientLoginCaptchaFormat[] = 69 "Email=%s&" 70 "Passwd=%s&" 71 "PersistentCookie=%s&" 72 "accountType=%s&" 73 "source=%s&" 74 "service=%s&" 75 "logintoken=%s&" 76 "logincaptcha=%s"; 77// static 78const char GaiaAuthFetcher::kIssueAuthTokenFormat[] = 79 "SID=%s&" 80 "LSID=%s&" 81 "service=%s&" 82 "Session=%s"; 83// static 84const char GaiaAuthFetcher::kClientLoginToOAuth2BodyFormat[] = 85 "scope=%s&client_id=%s"; 86// static 87const char GaiaAuthFetcher::kOAuth2CodeToTokenPairBodyFormat[] = 88 "scope=%s&" 89 "grant_type=authorization_code&" 90 "client_id=%s&" 91 "client_secret=%s&" 92 "code=%s"; 93// static 94const char GaiaAuthFetcher::kGetUserInfoFormat[] = 95 "LSID=%s"; 96// static 97const char GaiaAuthFetcher::kMergeSessionFormat[] = 98 "uberauth=%s&" 99 "continue=%s&" 100 "source=%s"; 101// static 102const char GaiaAuthFetcher::kUberAuthTokenURLFormat[] = 103 "%s?source=%s&" 104 "issueuberauth=1"; 105 106const char GaiaAuthFetcher::kOAuthLoginFormat[] = "service=%s&source=%s"; 107 108// static 109const char GaiaAuthFetcher::kAccountDeletedError[] = "AccountDeleted"; 110const char GaiaAuthFetcher::kAccountDeletedErrorCode[] = "adel"; 111// static 112const char GaiaAuthFetcher::kAccountDisabledError[] = "AccountDisabled"; 113const char GaiaAuthFetcher::kAccountDisabledErrorCode[] = "adis"; 114// static 115const char GaiaAuthFetcher::kBadAuthenticationError[] = "BadAuthentication"; 116const char GaiaAuthFetcher::kBadAuthenticationErrorCode[] = "badauth"; 117// static 118const char GaiaAuthFetcher::kCaptchaError[] = "CaptchaRequired"; 119const char GaiaAuthFetcher::kCaptchaErrorCode[] = "cr"; 120// static 121const char GaiaAuthFetcher::kServiceUnavailableError[] = 122 "ServiceUnavailable"; 123const char GaiaAuthFetcher::kServiceUnavailableErrorCode[] = 124 "ire"; 125// static 126const char GaiaAuthFetcher::kErrorParam[] = "Error"; 127// static 128const char GaiaAuthFetcher::kErrorUrlParam[] = "Url"; 129// static 130const char GaiaAuthFetcher::kCaptchaUrlParam[] = "CaptchaUrl"; 131// static 132const char GaiaAuthFetcher::kCaptchaTokenParam[] = "CaptchaToken"; 133 134// static 135const char GaiaAuthFetcher::kNeedsAdditional[] = "NeedsAdditional"; 136// static 137const char GaiaAuthFetcher::kCaptcha[] = "Captcha"; 138// static 139const char GaiaAuthFetcher::kTwoFactor[] = "TwoStep"; 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 get_user_info_gurl_(GaiaUrls::GetInstance()->get_user_info_url()), 184 merge_session_gurl_(GaiaUrls::GetInstance()->merge_session_url()), 185 uberauth_token_gurl_(base::StringPrintf(kUberAuthTokenURLFormat, 186 GaiaUrls::GetInstance()->oauth1_login_url().c_str(), source.c_str())), 187 client_oauth_gurl_(GaiaUrls::GetInstance()->client_oauth_url()), 188 oauth_login_gurl_(GaiaUrls::GetInstance()->oauth1_login_url()), 189 client_login_to_oauth2_gurl_( 190 GaiaUrls::GetInstance()->client_login_to_oauth2_url()), 191 fetch_pending_(false) {} 192 193GaiaAuthFetcher::~GaiaAuthFetcher() {} 194 195bool GaiaAuthFetcher::HasPendingFetch() { 196 return fetch_pending_; 197} 198 199void GaiaAuthFetcher::CancelRequest() { 200 fetcher_.reset(); 201 fetch_pending_ = false; 202} 203 204// static 205net::URLFetcher* GaiaAuthFetcher::CreateGaiaFetcher( 206 net::URLRequestContextGetter* getter, 207 const std::string& body, 208 const std::string& headers, 209 const GURL& gaia_gurl, 210 int load_flags, 211 net::URLFetcherDelegate* delegate) { 212 net::URLFetcher* to_return = net::URLFetcher::Create( 213 0, gaia_gurl, 214 body == "" ? net::URLFetcher::GET : net::URLFetcher::POST, 215 delegate); 216 to_return->SetRequestContext(getter); 217 to_return->SetUploadData("application/x-www-form-urlencoded", body); 218 219 DVLOG(2) << "Gaia fetcher URL: " << gaia_gurl.spec(); 220 DVLOG(2) << "Gaia fetcher headers: " << headers; 221 DVLOG(2) << "Gaia fetcher body: " << body; 222 223 // The Gaia token exchange requests do not require any cookie-based 224 // identification as part of requests. We suppress sending any cookies to 225 // maintain a separation between the user's browsing and Chrome's internal 226 // services. Where such mixing is desired (MergeSession), it will be done 227 // explicitly. 228 to_return->SetLoadFlags(load_flags); 229 230 // Fetchers are sometimes cancelled because a network change was detected, 231 // especially at startup and after sign-in on ChromeOS. Retrying once should 232 // be enough in those cases; let the fetcher retry up to 3 times just in case. 233 // http://crbug.com/163710 234 to_return->SetAutomaticallyRetryOnNetworkChanges(3); 235 236 if (!headers.empty()) 237 to_return->SetExtraRequestHeaders(headers); 238 239 return to_return; 240} 241 242// static 243std::string GaiaAuthFetcher::MakeClientLoginBody( 244 const std::string& username, 245 const std::string& password, 246 const std::string& source, 247 const char* service, 248 const std::string& login_token, 249 const std::string& login_captcha, 250 HostedAccountsSetting allow_hosted_accounts) { 251 std::string encoded_username = net::EscapeUrlEncodedData(username, true); 252 std::string encoded_password = net::EscapeUrlEncodedData(password, true); 253 std::string encoded_login_token = net::EscapeUrlEncodedData(login_token, 254 true); 255 std::string encoded_login_captcha = net::EscapeUrlEncodedData(login_captcha, 256 true); 257 258 const char* account_type = allow_hosted_accounts == HostedAccountsAllowed ? 259 kAccountTypeHostedOrGoogle : 260 kAccountTypeGoogle; 261 262 if (login_token.empty() || login_captcha.empty()) { 263 return base::StringPrintf(kClientLoginFormat, 264 encoded_username.c_str(), 265 encoded_password.c_str(), 266 kCookiePersistence, 267 account_type, 268 source.c_str(), 269 service); 270 } 271 272 return base::StringPrintf(kClientLoginCaptchaFormat, 273 encoded_username.c_str(), 274 encoded_password.c_str(), 275 kCookiePersistence, 276 account_type, 277 source.c_str(), 278 service, 279 encoded_login_token.c_str(), 280 encoded_login_captcha.c_str()); 281} 282 283// static 284std::string GaiaAuthFetcher::MakeIssueAuthTokenBody( 285 const std::string& sid, 286 const std::string& lsid, 287 const char* const service) { 288 std::string encoded_sid = net::EscapeUrlEncodedData(sid, true); 289 std::string encoded_lsid = net::EscapeUrlEncodedData(lsid, true); 290 291 // All tokens should be session tokens except the gaia auth token. 292 bool session = true; 293 if (!strcmp(service, GaiaConstants::kGaiaService)) 294 session = false; 295 296 return base::StringPrintf(kIssueAuthTokenFormat, 297 encoded_sid.c_str(), 298 encoded_lsid.c_str(), 299 service, 300 session ? "true" : "false"); 301} 302 303// static 304std::string GaiaAuthFetcher::MakeGetAuthCodeBody() { 305 std::string encoded_scope = net::EscapeUrlEncodedData( 306 GaiaUrls::GetInstance()->oauth1_login_scope(), true); 307 std::string encoded_client_id = net::EscapeUrlEncodedData( 308 GaiaUrls::GetInstance()->oauth2_chrome_client_id(), true); 309 return base::StringPrintf(kClientLoginToOAuth2BodyFormat, 310 encoded_scope.c_str(), 311 encoded_client_id.c_str()); 312} 313 314// static 315std::string GaiaAuthFetcher::MakeGetTokenPairBody( 316 const std::string& auth_code) { 317 std::string encoded_scope = net::EscapeUrlEncodedData( 318 GaiaUrls::GetInstance()->oauth1_login_scope(), true); 319 std::string encoded_client_id = net::EscapeUrlEncodedData( 320 GaiaUrls::GetInstance()->oauth2_chrome_client_id(), true); 321 std::string encoded_client_secret = net::EscapeUrlEncodedData( 322 GaiaUrls::GetInstance()->oauth2_chrome_client_secret(), true); 323 std::string encoded_auth_code = net::EscapeUrlEncodedData(auth_code, true); 324 return base::StringPrintf(kOAuth2CodeToTokenPairBodyFormat, 325 encoded_scope.c_str(), 326 encoded_client_id.c_str(), 327 encoded_client_secret.c_str(), 328 encoded_auth_code.c_str()); 329} 330 331// static 332std::string GaiaAuthFetcher::MakeGetUserInfoBody(const std::string& lsid) { 333 std::string encoded_lsid = net::EscapeUrlEncodedData(lsid, true); 334 return base::StringPrintf(kGetUserInfoFormat, encoded_lsid.c_str()); 335} 336 337// static 338std::string GaiaAuthFetcher::MakeMergeSessionBody( 339 const std::string& auth_token, 340 const std::string& continue_url, 341 const std::string& source) { 342 std::string encoded_auth_token = net::EscapeUrlEncodedData(auth_token, true); 343 std::string encoded_continue_url = net::EscapeUrlEncodedData(continue_url, 344 true); 345 std::string encoded_source = net::EscapeUrlEncodedData(source, true); 346 return base::StringPrintf(kMergeSessionFormat, 347 encoded_auth_token.c_str(), 348 encoded_continue_url.c_str(), 349 encoded_source.c_str()); 350} 351 352// static 353std::string GaiaAuthFetcher::MakeGetAuthCodeHeader( 354 const std::string& auth_token) { 355 return base::StringPrintf(kAuthHeaderFormat, auth_token.c_str()); 356} 357 358// Helper method that extracts tokens from a successful reply. 359// static 360void GaiaAuthFetcher::ParseClientLoginResponse(const std::string& data, 361 std::string* sid, 362 std::string* lsid, 363 std::string* token) { 364 using std::vector; 365 using std::pair; 366 using std::string; 367 sid->clear(); 368 lsid->clear(); 369 token->clear(); 370 vector<pair<string, string> > tokens; 371 base::SplitStringIntoKeyValuePairs(data, '=', '\n', &tokens); 372 for (vector<pair<string, string> >::iterator i = tokens.begin(); 373 i != tokens.end(); ++i) { 374 if (i->first == "SID") { 375 sid->assign(i->second); 376 } else if (i->first == "LSID") { 377 lsid->assign(i->second); 378 } else if (i->first == "Auth") { 379 token->assign(i->second); 380 } 381 } 382 // If this was a request for uberauth token, then that's all we've got in 383 // data. 384 if (sid->empty() && lsid->empty() && token->empty()) 385 token->assign(data); 386} 387 388// static 389std::string GaiaAuthFetcher::MakeClientOAuthBody( 390 const std::string& username, 391 const std::string& password, 392 const std::vector<std::string>& scopes, 393 const std::string& persistent_id, 394 const std::string& friendly_name, 395 const std::string& locale) { 396 scoped_ptr<base::DictionaryValue> dict(new base::DictionaryValue); 397 dict->SetString(GaiaConstants::kClientOAuthEmailKey, username); 398 dict->SetString(GaiaConstants::kClientOAuthPasswordKey, password); 399 400 scoped_ptr<base::ListValue> scope_list(new base::ListValue); 401 for (size_t i = 0; i < scopes.size(); ++i) 402 scope_list->Append(base::Value::CreateStringValue(scopes[i])); 403 dict->Set(GaiaConstants::kClientOAuthScopesKey, scope_list.release()); 404 405 dict->SetString(GaiaConstants::kClientOAuthOAuth2ClientIdKey, 406 GaiaUrls::GetInstance()->oauth2_chrome_client_id()); 407 // crbug.com/129600: use a less generic friendly name. 408 dict->SetString(GaiaConstants::kClientOAuthFriendlyDeviceNameKey, 409 friendly_name); 410 411 scoped_ptr<base::ListValue> accepts_challenge_list(new base::ListValue); 412 accepts_challenge_list->Append(base::Value::CreateStringValue(kCaptcha)); 413 accepts_challenge_list->Append(base::Value::CreateStringValue(kTwoFactor)); 414 dict->Set(GaiaConstants::kClientOAuthAcceptsChallengesKey, 415 accepts_challenge_list.release()); 416 417 dict->SetString(GaiaConstants::kClientOAuthLocaleKey, locale); 418 // Chrome presently does not not support a web-fallback for ClientOAuth, 419 // but need to hardcode an arbitrary one here since the endpoint expects it. 420 dict->SetString(GaiaConstants::kClientOAuthFallbackNameKey, "GetOAuth2Token"); 421 422 std::string json_string; 423 base::JSONWriter::Write(dict.get(), &json_string); 424 return json_string; 425} 426 427// static 428std::string GaiaAuthFetcher::MakeClientOAuthChallengeResponseBody( 429 const std::string& name, 430 const std::string& token, 431 const std::string& solution) { 432 scoped_ptr<base::DictionaryValue> dict(new base::DictionaryValue); 433 std::string field_name = name == kTwoFactor ? "otp" : "solution"; 434 435 scoped_ptr<base::DictionaryValue> challenge_reply(new base::DictionaryValue); 436 challenge_reply->SetString(GaiaConstants::kClientOAuthNameKey, name); 437 challenge_reply->SetString(GaiaConstants::kClientOAuthChallengeTokenKey, 438 token); 439 challenge_reply->SetString(field_name, solution); 440 dict->Set(GaiaConstants::kClientOAuthchallengeReplyKey, 441 challenge_reply.release()); 442 443 std::string json_string; 444 base::JSONWriter::Write(dict.get(), &json_string); 445 return json_string; 446} 447 448// static 449std::string GaiaAuthFetcher::MakeOAuthLoginBody(const std::string& service, 450 const std::string& source) { 451 std::string encoded_service = net::EscapeUrlEncodedData(service, true); 452 std::string encoded_source = net::EscapeUrlEncodedData(source, true); 453 return base::StringPrintf(kOAuthLoginFormat, 454 encoded_service.c_str(), 455 encoded_source.c_str()); 456} 457 458// static 459void GaiaAuthFetcher::ParseClientLoginFailure(const std::string& data, 460 std::string* error, 461 std::string* error_url, 462 std::string* captcha_url, 463 std::string* captcha_token) { 464 using std::vector; 465 using std::pair; 466 using std::string; 467 468 vector<pair<string, string> > tokens; 469 base::SplitStringIntoKeyValuePairs(data, '=', '\n', &tokens); 470 for (vector<pair<string, string> >::iterator i = tokens.begin(); 471 i != tokens.end(); ++i) { 472 if (i->first == kErrorParam) { 473 error->assign(i->second); 474 } else if (i->first == kErrorUrlParam) { 475 error_url->assign(i->second); 476 } else if (i->first == kCaptchaUrlParam) { 477 captcha_url->assign(i->second); 478 } else if (i->first == kCaptchaTokenParam) { 479 captcha_token->assign(i->second); 480 } 481 } 482} 483 484// static 485bool GaiaAuthFetcher::ParseClientLoginToOAuth2Response( 486 const net::ResponseCookies& cookies, 487 std::string* auth_code) { 488 DCHECK(auth_code); 489 net::ResponseCookies::const_iterator iter; 490 for (iter = cookies.begin(); iter != cookies.end(); ++iter) { 491 if (ParseClientLoginToOAuth2Cookie(*iter, auth_code)) 492 return true; 493 } 494 return false; 495} 496 497// static 498bool GaiaAuthFetcher::ParseClientLoginToOAuth2Cookie(const std::string& cookie, 499 std::string* auth_code) { 500 std::vector<std::string> parts; 501 base::SplitString(cookie, ';', &parts); 502 // Per documentation, the cookie should have Secure and HttpOnly. 503 if (!CookiePartsContains(parts, kClientLoginToOAuth2CookiePartSecure) || 504 !CookiePartsContains(parts, kClientLoginToOAuth2CookiePartHttpOnly)) { 505 return false; 506 } 507 508 std::vector<std::string>::const_iterator iter; 509 for (iter = parts.begin(); iter != parts.end(); ++iter) { 510 const std::string& part = *iter; 511 if (StartsWithASCII( 512 part, kClientLoginToOAuth2CookiePartCodePrefix, false)) { 513 auth_code->assign(part.substr( 514 kClientLoginToOAuth2CookiePartCodePrefixLength)); 515 return true; 516 } 517 } 518 return false; 519} 520 521// static 522GoogleServiceAuthError 523GaiaAuthFetcher::GenerateClientOAuthError(const std::string& data, 524 const net::URLRequestStatus& status) { 525 scoped_ptr<base::Value> value(base::JSONReader::Read(data)); 526 if (!value.get() || value->GetType() != base::Value::TYPE_DICTIONARY) 527 return GenerateAuthError(data, status); 528 DictionaryValue* dict = static_cast<DictionaryValue*>(value.get()); 529 530 std::string cause; 531 if (!dict->GetStringWithoutPathExpansion("cause", &cause)) 532 return GoogleServiceAuthError::FromClientOAuthError(data); 533 534 if (cause != kNeedsAdditional) 535 return GoogleServiceAuthError::FromClientOAuthError(data); 536 537 DictionaryValue* challenge; 538 if (!dict->GetDictionaryWithoutPathExpansion("challenge", &challenge)) 539 return GoogleServiceAuthError::FromClientOAuthError(data); 540 541 std::string name; 542 if (!challenge->GetStringWithoutPathExpansion("name", &name)) 543 return GoogleServiceAuthError::FromClientOAuthError(data); 544 545 if (name == kCaptcha) { 546 std::string token; 547 std::string audio_url; 548 std::string image_url; 549 int image_width; 550 int image_height; 551 if (!challenge->GetStringWithoutPathExpansion("challenge_token", &token) || 552 !challenge->GetStringWithoutPathExpansion("audio_url", &audio_url) || 553 !challenge->GetStringWithoutPathExpansion("image_url", &image_url) || 554 !challenge->GetIntegerWithoutPathExpansion("image_width", 555 &image_width) || 556 !challenge->GetIntegerWithoutPathExpansion("image_height", 557 &image_height)) { 558 return GoogleServiceAuthError::FromClientOAuthError(data); 559 } 560 return GoogleServiceAuthError::FromCaptchaChallenge(token, GURL(audio_url), 561 GURL(image_url), 562 image_width, 563 image_height); 564 } else if (name == kTwoFactor) { 565 std::string token; 566 std::string prompt_text; 567 std::string alternate_text; 568 int field_length; 569 570 // The protocol doc says these are required, but in practice they are not 571 // returned. So only a missing challenge token will cause an error here. 572 challenge->GetStringWithoutPathExpansion("prompt_text", &prompt_text); 573 challenge->GetStringWithoutPathExpansion("alternate_text", &alternate_text); 574 challenge->GetIntegerWithoutPathExpansion("field_length", &field_length); 575 if (!challenge->GetStringWithoutPathExpansion("challenge_token", &token)) 576 return GoogleServiceAuthError::FromClientOAuthError(data); 577 578 return GoogleServiceAuthError::FromSecondFactorChallenge(token, prompt_text, 579 alternate_text, 580 field_length); 581 } 582 583 return GoogleServiceAuthError::FromClientOAuthError(data); 584} 585 586void GaiaAuthFetcher::StartClientLogin( 587 const std::string& username, 588 const std::string& password, 589 const char* const service, 590 const std::string& login_token, 591 const std::string& login_captcha, 592 HostedAccountsSetting allow_hosted_accounts) { 593 594 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!"; 595 596 // This class is thread agnostic, so be sure to call this only on the 597 // same thread each time. 598 DVLOG(1) << "Starting new ClientLogin fetch for:" << username; 599 600 // Must outlive fetcher_. 601 request_body_ = MakeClientLoginBody(username, 602 password, 603 source_, 604 service, 605 login_token, 606 login_captcha, 607 allow_hosted_accounts); 608 fetcher_.reset(CreateGaiaFetcher(getter_, 609 request_body_, 610 "", 611 client_login_gurl_, 612 kLoadFlagsIgnoreCookies, 613 this)); 614 fetch_pending_ = true; 615 fetcher_->Start(); 616} 617 618void GaiaAuthFetcher::StartIssueAuthToken(const std::string& sid, 619 const std::string& lsid, 620 const char* const service) { 621 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!"; 622 623 DVLOG(1) << "Starting IssueAuthToken for: " << service; 624 requested_service_ = service; 625 request_body_ = MakeIssueAuthTokenBody(sid, lsid, service); 626 fetcher_.reset(CreateGaiaFetcher(getter_, 627 request_body_, 628 "", 629 issue_auth_token_gurl_, 630 kLoadFlagsIgnoreCookies, 631 this)); 632 fetch_pending_ = true; 633 fetcher_->Start(); 634} 635 636void GaiaAuthFetcher::StartLsoForOAuthLoginTokenExchange( 637 const std::string& auth_token) { 638 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!"; 639 640 DVLOG(1) << "Starting OAuth login token exchange with auth_token"; 641 request_body_ = MakeGetAuthCodeBody(); 642 client_login_to_oauth2_gurl_ = 643 GURL(GaiaUrls::GetInstance()->client_login_to_oauth2_url()); 644 645 fetcher_.reset(CreateGaiaFetcher(getter_, 646 request_body_, 647 MakeGetAuthCodeHeader(auth_token), 648 client_login_to_oauth2_gurl_, 649 kLoadFlagsIgnoreCookies, 650 this)); 651 fetch_pending_ = true; 652 fetcher_->Start(); 653} 654 655void GaiaAuthFetcher::StartCookieForOAuthLoginTokenExchange( 656 const std::string& session_index) { 657 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!"; 658 659 DVLOG(1) << "Starting OAuth login token fetch with cookie jar"; 660 request_body_ = MakeGetAuthCodeBody(); 661 662 std::string url = GaiaUrls::GetInstance()->client_login_to_oauth2_url(); 663 if (!session_index.empty()) 664 url += "?authuser=" + session_index; 665 666 client_login_to_oauth2_gurl_ = GURL(url); 667 668 fetcher_.reset(CreateGaiaFetcher(getter_, 669 request_body_, 670 "", 671 client_login_to_oauth2_gurl_, 672 net::LOAD_NORMAL, 673 this)); 674 fetch_pending_ = true; 675 fetcher_->Start(); 676} 677 678void GaiaAuthFetcher::StartAuthCodeForOAuth2TokenExchange( 679 const std::string& auth_code) { 680 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!"; 681 682 DVLOG(1) << "Starting OAuth token pair fetch"; 683 request_body_ = MakeGetTokenPairBody(auth_code); 684 fetcher_.reset(CreateGaiaFetcher(getter_, 685 request_body_, 686 "", 687 oauth2_token_gurl_, 688 kLoadFlagsIgnoreCookies, 689 this)); 690 fetch_pending_ = true; 691 fetcher_->Start(); 692} 693 694void GaiaAuthFetcher::StartGetUserInfo(const std::string& lsid) { 695 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!"; 696 697 DVLOG(1) << "Starting GetUserInfo for lsid=" << lsid; 698 request_body_ = MakeGetUserInfoBody(lsid); 699 fetcher_.reset(CreateGaiaFetcher(getter_, 700 request_body_, 701 "", 702 get_user_info_gurl_, 703 kLoadFlagsIgnoreCookies, 704 this)); 705 fetch_pending_ = true; 706 fetcher_->Start(); 707} 708 709void GaiaAuthFetcher::StartMergeSession(const std::string& uber_token) { 710 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!"; 711 712 DVLOG(1) << "Starting MergeSession with uber_token=" << uber_token; 713 714 // The continue URL is a required parameter of the MergeSession API, but in 715 // this case we don't actually need or want to navigate to it. Setting it to 716 // an arbitrary Google URL. 717 // 718 // In order for the new session to be merged correctly, the server needs to 719 // know what sessions already exist in the browser. The fetcher needs to be 720 // created such that it sends the cookies with the request, which is 721 // different from all other requests the fetcher can make. 722 std::string continue_url("http://www.google.com"); 723 request_body_ = MakeMergeSessionBody(uber_token, continue_url, source_); 724 fetcher_.reset(CreateGaiaFetcher(getter_, 725 request_body_, 726 "", 727 merge_session_gurl_, 728 net::LOAD_NORMAL, 729 this)); 730 fetch_pending_ = true; 731 fetcher_->Start(); 732} 733 734void GaiaAuthFetcher::StartTokenFetchForUberAuthExchange( 735 const std::string& access_token) { 736 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!"; 737 738 DVLOG(1) << "Starting StartTokenFetchForUberAuthExchange with access_token=" 739 << access_token; 740 std::string authentication_header = 741 base::StringPrintf(kOAuthHeaderFormat, access_token.c_str()); 742 fetcher_.reset(CreateGaiaFetcher(getter_, 743 "", 744 authentication_header, 745 uberauth_token_gurl_, 746 kLoadFlagsIgnoreCookies, 747 this)); 748 fetch_pending_ = true; 749 fetcher_->Start(); 750} 751 752void GaiaAuthFetcher::StartClientOAuth(const std::string& username, 753 const std::string& password, 754 const std::vector<std::string>& scopes, 755 const std::string& persistent_id, 756 const std::string& locale) { 757 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!"; 758 759 request_body_ = MakeClientOAuthBody(username, password, scopes, persistent_id, 760 source_, locale); 761 fetcher_.reset(CreateGaiaFetcher(getter_, 762 request_body_, 763 "", 764 client_oauth_gurl_, 765 kLoadFlagsIgnoreCookies, 766 this)); 767 fetch_pending_ = true; 768 fetcher_->Start(); 769} 770 771void GaiaAuthFetcher::StartClientOAuthChallengeResponse( 772 GoogleServiceAuthError::State type, 773 const std::string& token, 774 const std::string& solution) { 775 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!"; 776 777 std::string name; 778 switch (type) { 779 case GoogleServiceAuthError::CAPTCHA_REQUIRED: 780 name = kCaptcha; 781 break; 782 case GoogleServiceAuthError::TWO_FACTOR: 783 name = kTwoFactor; 784 break; 785 default: 786 NOTREACHED(); 787 } 788 789 request_body_ = MakeClientOAuthChallengeResponseBody(name, token, solution); 790 fetcher_.reset(CreateGaiaFetcher(getter_, 791 request_body_, 792 "", 793 client_oauth_gurl_, 794 kLoadFlagsIgnoreCookies, 795 this)); 796 fetch_pending_ = true; 797 fetcher_->Start(); 798} 799 800void GaiaAuthFetcher::StartOAuthLogin(const std::string& access_token, 801 const std::string& service) { 802 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!"; 803 804 request_body_ = MakeOAuthLoginBody(service, source_); 805 std::string authentication_header = 806 base::StringPrintf(kOAuth2BearerHeaderFormat, access_token.c_str()); 807 fetcher_.reset(CreateGaiaFetcher(getter_, 808 request_body_, 809 authentication_header, 810 oauth_login_gurl_, 811 kLoadFlagsIgnoreCookies, 812 this)); 813 fetch_pending_ = true; 814 fetcher_->Start(); 815} 816 817// static 818GoogleServiceAuthError GaiaAuthFetcher::GenerateAuthError( 819 const std::string& data, 820 const net::URLRequestStatus& status) { 821 if (!status.is_success()) { 822 if (status.status() == net::URLRequestStatus::CANCELED) { 823 return GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED); 824 } else { 825 DLOG(WARNING) << "Could not reach Google Accounts servers: errno " 826 << status.error(); 827 return GoogleServiceAuthError::FromConnectionError(status.error()); 828 } 829 } else { 830 if (IsSecondFactorSuccess(data)) { 831 return GoogleServiceAuthError(GoogleServiceAuthError::TWO_FACTOR); 832 } 833 834 std::string error; 835 std::string url; 836 std::string captcha_url; 837 std::string captcha_token; 838 ParseClientLoginFailure(data, &error, &url, &captcha_url, &captcha_token); 839 DLOG(WARNING) << "ClientLogin failed with " << error; 840 841 if (error == kCaptchaError) { 842 GURL image_url( 843 GaiaUrls::GetInstance()->captcha_url_prefix() + captcha_url); 844 GURL unlock_url(url); 845 return GoogleServiceAuthError::FromClientLoginCaptchaChallenge( 846 captcha_token, image_url, unlock_url); 847 } 848 if (error == kAccountDeletedError) 849 return GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DELETED); 850 if (error == kAccountDisabledError) 851 return GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DISABLED); 852 if (error == kBadAuthenticationError) { 853 return GoogleServiceAuthError( 854 GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS); 855 } 856 if (error == kServiceUnavailableError) { 857 return GoogleServiceAuthError( 858 GoogleServiceAuthError::SERVICE_UNAVAILABLE); 859 } 860 861 DLOG(WARNING) << "Incomprehensible response from Google Accounts servers."; 862 return GoogleServiceAuthError( 863 GoogleServiceAuthError::SERVICE_UNAVAILABLE); 864 } 865 866 NOTREACHED(); 867 return GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE); 868} 869 870// static 871GoogleServiceAuthError GaiaAuthFetcher::GenerateOAuthLoginError( 872 const std::string& data, 873 const net::URLRequestStatus& status) { 874 if (!status.is_success()) { 875 if (status.status() == net::URLRequestStatus::CANCELED) { 876 return GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED); 877 } else { 878 DLOG(WARNING) << "Could not reach Google Accounts servers: errno " 879 << status.error(); 880 return GoogleServiceAuthError::FromConnectionError(status.error()); 881 } 882 } else { 883 if (IsSecondFactorSuccess(data)) { 884 return GoogleServiceAuthError(GoogleServiceAuthError::TWO_FACTOR); 885 } 886 887 std::string error; 888 std::string url; 889 std::string captcha_url; 890 std::string captcha_token; 891 ParseClientLoginFailure(data, &error, &url, &captcha_url, &captcha_token); 892 LOG(WARNING) << "OAuthLogin failed with " << error; 893 894 if (error == kCaptchaErrorCode) { 895 GURL image_url( 896 GaiaUrls::GetInstance()->captcha_url_prefix() + captcha_url); 897 GURL unlock_url(url); 898 return GoogleServiceAuthError::FromClientLoginCaptchaChallenge( 899 captcha_token, image_url, unlock_url); 900 } 901 if (error == kAccountDeletedErrorCode) 902 return GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DELETED); 903 if (error == kAccountDisabledErrorCode) 904 return GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DISABLED); 905 if (error == kBadAuthenticationErrorCode) { 906 return GoogleServiceAuthError( 907 GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS); 908 } 909 if (error == kServiceUnavailableErrorCode) { 910 return GoogleServiceAuthError( 911 GoogleServiceAuthError::SERVICE_UNAVAILABLE); 912 } 913 914 DLOG(WARNING) << "Incomprehensible response from Google Accounts servers."; 915 return GoogleServiceAuthError( 916 GoogleServiceAuthError::SERVICE_UNAVAILABLE); 917 } 918 919 NOTREACHED(); 920 return GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE); 921} 922 923void GaiaAuthFetcher::OnClientLoginFetched(const std::string& data, 924 const net::URLRequestStatus& status, 925 int response_code) { 926 if (status.is_success() && response_code == net::HTTP_OK) { 927 DVLOG(1) << "ClientLogin successful!"; 928 std::string sid; 929 std::string lsid; 930 std::string token; 931 ParseClientLoginResponse(data, &sid, &lsid, &token); 932 consumer_->OnClientLoginSuccess( 933 GaiaAuthConsumer::ClientLoginResult(sid, lsid, token, data)); 934 } else { 935 consumer_->OnClientLoginFailure(GenerateAuthError(data, status)); 936 } 937} 938 939void GaiaAuthFetcher::OnIssueAuthTokenFetched( 940 const std::string& data, 941 const net::URLRequestStatus& status, 942 int response_code) { 943 if (status.is_success() && response_code == net::HTTP_OK) { 944 // Only the bare token is returned in the body of this Gaia call 945 // without any padding. 946 consumer_->OnIssueAuthTokenSuccess(requested_service_, data); 947 } else { 948 consumer_->OnIssueAuthTokenFailure(requested_service_, 949 GenerateAuthError(data, status)); 950 } 951} 952 953void GaiaAuthFetcher::OnClientLoginToOAuth2Fetched( 954 const std::string& data, 955 const net::ResponseCookies& cookies, 956 const net::URLRequestStatus& status, 957 int response_code) { 958 if (status.is_success() && response_code == net::HTTP_OK) { 959 std::string auth_code; 960 ParseClientLoginToOAuth2Response(cookies, &auth_code); 961 StartAuthCodeForOAuth2TokenExchange(auth_code); 962 } else { 963 consumer_->OnClientOAuthFailure(GenerateAuthError(data, status)); 964 } 965} 966 967void GaiaAuthFetcher::OnOAuth2TokenPairFetched( 968 const std::string& data, 969 const net::URLRequestStatus& status, 970 int response_code) { 971 std::string refresh_token; 972 std::string access_token; 973 int expires_in_secs = 0; 974 975 bool success = false; 976 if (status.is_success() && response_code == net::HTTP_OK) { 977 scoped_ptr<base::Value> value(base::JSONReader::Read(data)); 978 if (value.get() && value->GetType() == base::Value::TYPE_DICTIONARY) { 979 DictionaryValue* dict = static_cast<DictionaryValue*>(value.get()); 980 success = ExtractOAuth2TokenPairResponse(dict, &refresh_token, 981 &access_token, &expires_in_secs); 982 } 983 } 984 985 if (success) { 986 consumer_->OnClientOAuthSuccess( 987 GaiaAuthConsumer::ClientOAuthResult(refresh_token, access_token, 988 expires_in_secs)); 989 } else { 990 consumer_->OnClientOAuthFailure(GenerateAuthError(data, status)); 991 } 992} 993 994void GaiaAuthFetcher::OnGetUserInfoFetched( 995 const std::string& data, 996 const net::URLRequestStatus& status, 997 int response_code) { 998 if (status.is_success() && response_code == net::HTTP_OK) { 999 std::vector<std::pair<std::string, std::string> > tokens; 1000 UserInfoMap matches; 1001 base::SplitStringIntoKeyValuePairs(data, '=', '\n', &tokens); 1002 std::vector<std::pair<std::string, std::string> >::iterator i; 1003 for (i = tokens.begin(); i != tokens.end(); ++i) { 1004 matches[i->first] = i->second; 1005 } 1006 consumer_->OnGetUserInfoSuccess(matches); 1007 } else { 1008 consumer_->OnGetUserInfoFailure(GenerateAuthError(data, status)); 1009 } 1010} 1011 1012void GaiaAuthFetcher::OnMergeSessionFetched(const std::string& data, 1013 const net::URLRequestStatus& status, 1014 int response_code) { 1015 if (status.is_success() && response_code == net::HTTP_OK) { 1016 consumer_->OnMergeSessionSuccess(data); 1017 } else { 1018 consumer_->OnMergeSessionFailure(GenerateAuthError(data, status)); 1019 } 1020} 1021 1022void GaiaAuthFetcher::OnUberAuthTokenFetch(const std::string& data, 1023 const net::URLRequestStatus& status, 1024 int response_code) { 1025 if (status.is_success() && response_code == net::HTTP_OK) { 1026 consumer_->OnUberAuthTokenSuccess(data); 1027 } else { 1028 consumer_->OnUberAuthTokenFailure(GenerateAuthError(data, status)); 1029 } 1030} 1031 1032void GaiaAuthFetcher::OnClientOAuthFetched(const std::string& data, 1033 const net::URLRequestStatus& status, 1034 int response_code) { 1035 std::string refresh_token; 1036 std::string access_token; 1037 int expires_in_secs = 0; 1038 1039 bool success = false; 1040 if (status.is_success() && response_code == net::HTTP_OK) { 1041 scoped_ptr<base::Value> value(base::JSONReader::Read(data)); 1042 if (value.get() && value->GetType() == base::Value::TYPE_DICTIONARY) { 1043 DictionaryValue* dict = static_cast<DictionaryValue*>(value.get()); 1044 DictionaryValue* dict_oauth2; 1045 if (dict->GetDictionaryWithoutPathExpansion("oauth2", &dict_oauth2)) { 1046 success = ExtractOAuth2TokenPairResponse(dict_oauth2, &refresh_token, 1047 &access_token, 1048 &expires_in_secs); 1049 } 1050 } 1051 } 1052 1053 // TODO(rogerta): for now this reuses the OnOAuthLoginTokenXXX callbacks 1054 // since the data is exactly the same. This ignores the optional 1055 // persistent_id data in the response, which we may need to handle. 1056 // If we do, we'll need to modify ExtractOAuth2TokenPairResponse() to parse 1057 // the optional data and declare new consumer callbacks to take it. 1058 if (success) { 1059 consumer_->OnClientOAuthSuccess( 1060 GaiaAuthConsumer::ClientOAuthResult(refresh_token, access_token, 1061 expires_in_secs)); 1062 } else { 1063 consumer_->OnClientOAuthFailure(GenerateClientOAuthError(data, status)); 1064 } 1065} 1066 1067void GaiaAuthFetcher::OnOAuthLoginFetched(const std::string& data, 1068 const net::URLRequestStatus& status, 1069 int response_code) { 1070 if (status.is_success() && response_code == net::HTTP_OK) { 1071 DVLOG(1) << "ClientLogin successful!"; 1072 std::string sid; 1073 std::string lsid; 1074 std::string token; 1075 ParseClientLoginResponse(data, &sid, &lsid, &token); 1076 consumer_->OnClientLoginSuccess( 1077 GaiaAuthConsumer::ClientLoginResult(sid, lsid, token, data)); 1078 } else { 1079 consumer_->OnClientLoginFailure(GenerateAuthError(data, status)); 1080 } 1081} 1082 1083void GaiaAuthFetcher::OnURLFetchComplete(const net::URLFetcher* source) { 1084 fetch_pending_ = false; 1085 // Some of the GAIA requests perform redirects, which results in the final 1086 // URL of the fetcher not being the original URL requested. Therefore use 1087 // the original URL when determining which OnXXX function to call. 1088 const GURL& url = source->GetOriginalURL(); 1089 const net::URLRequestStatus& status = source->GetStatus(); 1090 int response_code = source->GetResponseCode(); 1091 std::string data; 1092 source->GetResponseAsString(&data); 1093#ifndef NDEBUG 1094 std::string headers; 1095 if (source->GetResponseHeaders()) 1096 source->GetResponseHeaders()->GetNormalizedHeaders(&headers); 1097 DVLOG(2) << "Response " << url.spec() << ", code = " << response_code << "\n" 1098 << headers << "\n"; 1099 DVLOG(2) << "data: " << data << "\n"; 1100#endif 1101 // Retrieve the response headers from the request. Must only be called after 1102 // the OnURLFetchComplete callback has run. 1103 if (url == client_login_gurl_) { 1104 OnClientLoginFetched(data, status, response_code); 1105 } else if (url == issue_auth_token_gurl_) { 1106 OnIssueAuthTokenFetched(data, status, response_code); 1107 } else if (url == client_login_to_oauth2_gurl_) { 1108 OnClientLoginToOAuth2Fetched( 1109 data, source->GetCookies(), status, response_code); 1110 } else if (url == oauth2_token_gurl_) { 1111 OnOAuth2TokenPairFetched(data, status, response_code); 1112 } else if (url == get_user_info_gurl_) { 1113 OnGetUserInfoFetched(data, status, response_code); 1114 } else if (url == merge_session_gurl_) { 1115 OnMergeSessionFetched(data, status, response_code); 1116 } else if (url == uberauth_token_gurl_) { 1117 OnUberAuthTokenFetch(data, status, response_code); 1118 } else if (url == client_oauth_gurl_) { 1119 OnClientOAuthFetched(data, status, response_code); 1120 } else if (url == oauth_login_gurl_) { 1121 OnOAuthLoginFetched(data, status, response_code); 1122 } else { 1123 NOTREACHED(); 1124 } 1125} 1126 1127// static 1128bool GaiaAuthFetcher::IsSecondFactorSuccess( 1129 const std::string& alleged_error) { 1130 return alleged_error.find(kSecondFactor) != 1131 std::string::npos; 1132} 1133