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