gaia_auth_fetcher.cc revision eb525c5499e34cc9c4b825d6d9e75bb07cc06ace
1579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson// Use of this source code is governed by a BSD-style license that can be 3579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson// found in the LICENSE file. 4579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson 5579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson#include "google_apis/gaia/gaia_auth_fetcher.h" 6579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson 7579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson#include <algorithm> 8579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson#include <string> 9579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson#include <utility> 10579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson#include <vector> 11579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson 12579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson#include "base/json/json_reader.h" 13579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson#include "base/json/json_writer.h" 14579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson#include "base/strings/string_split.h" 15579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson#include "base/strings/string_util.h" 16579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson#include "base/strings/stringprintf.h" 17579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson#include "base/values.h" 18579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson#include "google_apis/gaia/gaia_auth_consumer.h" 19579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson#include "google_apis/gaia/gaia_constants.h" 20579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson#include "google_apis/gaia/gaia_urls.h" 21579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson#include "google_apis/gaia/google_service_auth_error.h" 22579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson#include "net/base/escape.h" 23579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson#include "net/base/load_flags.h" 24579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson#include "net/http/http_response_headers.h" 25579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson#include "net/http/http_status_code.h" 26579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson#include "net/url_request/url_fetcher.h" 27579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson#include "net/url_request/url_request_context_getter.h" 28579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson#include "net/url_request/url_request_status.h" 29579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson 30579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilsonnamespace { 31579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilsonconst int kLoadFlagsIgnoreCookies = net::LOAD_DO_NOT_SEND_COOKIES | 32579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson net::LOAD_DO_NOT_SAVE_COOKIES; 33579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson 34579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilsonstatic bool CookiePartsContains(const std::vector<std::string>& parts, 35579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson const char* part) { 36579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson return std::find(parts.begin(), parts.end(), part) != parts.end(); 37579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson} 38579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson 39579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilsonbool ExtractOAuth2TokenPairResponse(base::DictionaryValue* dict, 40579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson std::string* refresh_token, 41579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson std::string* access_token, 42579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson int* expires_in_secs) { 43579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson DCHECK(refresh_token); 44579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson DCHECK(access_token); 45579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson DCHECK(expires_in_secs); 46579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson 47579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson if (!dict->GetStringWithoutPathExpansion("refresh_token", refresh_token) || 48579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson !dict->GetStringWithoutPathExpansion("access_token", access_token) || 49579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson !dict->GetIntegerWithoutPathExpansion("expires_in", expires_in_secs)) { 50579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson return false; 51579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson } 52579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson 53579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson return true; 54579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson} 55579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson 56579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson} // namespace 57579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson 58579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson// TODO(chron): Add sourceless version of this formatter. 59579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson// static 60579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilsonconst char GaiaAuthFetcher::kClientLoginFormat[] = 61579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson "Email=%s&" 62579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson "Passwd=%s&" 63579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson "PersistentCookie=%s&" 64579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson "accountType=%s&" 65579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson "source=%s&" 66579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson "service=%s"; 67579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson// static 68579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilsonconst char GaiaAuthFetcher::kClientLoginCaptchaFormat[] = 69579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson "Email=%s&" 70579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson "Passwd=%s&" 71579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson "PersistentCookie=%s&" 72579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson "accountType=%s&" 73579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson "source=%s&" 74579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson "service=%s&" 75579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson "logintoken=%s&" 76579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson "logincaptcha=%s"; 77579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson// static 78579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilsonconst char GaiaAuthFetcher::kIssueAuthTokenFormat[] = 79579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson "SID=%s&" 80579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson "LSID=%s&" 81579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson "service=%s&" 82579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson "Session=%s"; 83579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson// static 84579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilsonconst char GaiaAuthFetcher::kClientLoginToOAuth2BodyFormat[] = 85579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson "scope=%s&client_id=%s"; 86579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson// static 87579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilsonconst char GaiaAuthFetcher::kOAuth2CodeToTokenPairBodyFormat[] = 88579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson "scope=%s&" 89579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson "grant_type=authorization_code&" 90579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson "client_id=%s&" 91579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson "client_secret=%s&" 92579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson "code=%s"; 93579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson// static 94579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilsonconst char GaiaAuthFetcher::kOAuth2RevokeTokenBodyFormat[] = 95579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson "token=%s"; 96579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson// static 97579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilsonconst char GaiaAuthFetcher::kGetUserInfoFormat[] = 98579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson "LSID=%s"; 99579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson// static 100579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilsonconst char GaiaAuthFetcher::kMergeSessionFormat[] = 101579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson "uberauth=%s&" 102579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson "continue=%s&" 103579d7739c53a2707ad711a2d2cae46d7d782f06Jesse Wilson "source=%s"; 104// static 105const char GaiaAuthFetcher::kUberAuthTokenURLFormat[] = 106 "%s?source=%s&" 107 "issueuberauth=1"; 108 109const char GaiaAuthFetcher::kOAuthLoginFormat[] = "service=%s&source=%s"; 110 111// static 112const char GaiaAuthFetcher::kAccountDeletedError[] = "AccountDeleted"; 113const char GaiaAuthFetcher::kAccountDeletedErrorCode[] = "adel"; 114// static 115const char GaiaAuthFetcher::kAccountDisabledError[] = "AccountDisabled"; 116const char GaiaAuthFetcher::kAccountDisabledErrorCode[] = "adis"; 117// static 118const char GaiaAuthFetcher::kBadAuthenticationError[] = "BadAuthentication"; 119const char GaiaAuthFetcher::kBadAuthenticationErrorCode[] = "badauth"; 120// static 121const char GaiaAuthFetcher::kCaptchaError[] = "CaptchaRequired"; 122const char GaiaAuthFetcher::kCaptchaErrorCode[] = "cr"; 123// static 124const char GaiaAuthFetcher::kServiceUnavailableError[] = 125 "ServiceUnavailable"; 126const char GaiaAuthFetcher::kServiceUnavailableErrorCode[] = 127 "ire"; 128// static 129const char GaiaAuthFetcher::kErrorParam[] = "Error"; 130// static 131const char GaiaAuthFetcher::kErrorUrlParam[] = "Url"; 132// static 133const char GaiaAuthFetcher::kCaptchaUrlParam[] = "CaptchaUrl"; 134// static 135const char GaiaAuthFetcher::kCaptchaTokenParam[] = "CaptchaToken"; 136 137// static 138const char GaiaAuthFetcher::kCookiePersistence[] = "true"; 139// static 140// TODO(johnnyg): When hosted accounts are supported by sync, 141// we can always use "HOSTED_OR_GOOGLE" 142const char GaiaAuthFetcher::kAccountTypeHostedOrGoogle[] = 143 "HOSTED_OR_GOOGLE"; 144const char GaiaAuthFetcher::kAccountTypeGoogle[] = 145 "GOOGLE"; 146 147// static 148const char GaiaAuthFetcher::kSecondFactor[] = "Info=InvalidSecondFactor"; 149 150// static 151const char GaiaAuthFetcher::kAuthHeaderFormat[] = 152 "Authorization: GoogleLogin auth=%s"; 153// static 154const char GaiaAuthFetcher::kOAuthHeaderFormat[] = "Authorization: OAuth %s"; 155// static 156const char GaiaAuthFetcher::kOAuth2BearerHeaderFormat[] = 157 "Authorization: Bearer %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 oauth2_revoke_gurl_(GaiaUrls::GetInstance()->oauth2_revoke_url()), 180 get_user_info_gurl_(GaiaUrls::GetInstance()->get_user_info_url()), 181 merge_session_gurl_(GaiaUrls::GetInstance()->merge_session_url()), 182 uberauth_token_gurl_(base::StringPrintf(kUberAuthTokenURLFormat, 183 GaiaUrls::GetInstance()->oauth1_login_url().c_str(), source.c_str())), 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 // Fetchers are sometimes cancelled because a network change was detected, 227 // especially at startup and after sign-in on ChromeOS. Retrying once should 228 // be enough in those cases; let the fetcher retry up to 3 times just in case. 229 // http://crbug.com/163710 230 to_return->SetAutomaticallyRetryOnNetworkChanges(3); 231 232 if (!headers.empty()) 233 to_return->SetExtraRequestHeaders(headers); 234 235 return to_return; 236} 237 238// static 239std::string GaiaAuthFetcher::MakeClientLoginBody( 240 const std::string& username, 241 const std::string& password, 242 const std::string& source, 243 const char* service, 244 const std::string& login_token, 245 const std::string& login_captcha, 246 HostedAccountsSetting allow_hosted_accounts) { 247 std::string encoded_username = net::EscapeUrlEncodedData(username, true); 248 std::string encoded_password = net::EscapeUrlEncodedData(password, true); 249 std::string encoded_login_token = net::EscapeUrlEncodedData(login_token, 250 true); 251 std::string encoded_login_captcha = net::EscapeUrlEncodedData(login_captcha, 252 true); 253 254 const char* account_type = allow_hosted_accounts == HostedAccountsAllowed ? 255 kAccountTypeHostedOrGoogle : 256 kAccountTypeGoogle; 257 258 if (login_token.empty() || login_captcha.empty()) { 259 return base::StringPrintf(kClientLoginFormat, 260 encoded_username.c_str(), 261 encoded_password.c_str(), 262 kCookiePersistence, 263 account_type, 264 source.c_str(), 265 service); 266 } 267 268 return base::StringPrintf(kClientLoginCaptchaFormat, 269 encoded_username.c_str(), 270 encoded_password.c_str(), 271 kCookiePersistence, 272 account_type, 273 source.c_str(), 274 service, 275 encoded_login_token.c_str(), 276 encoded_login_captcha.c_str()); 277} 278 279// static 280std::string GaiaAuthFetcher::MakeIssueAuthTokenBody( 281 const std::string& sid, 282 const std::string& lsid, 283 const char* const service) { 284 std::string encoded_sid = net::EscapeUrlEncodedData(sid, true); 285 std::string encoded_lsid = net::EscapeUrlEncodedData(lsid, true); 286 287 // All tokens should be session tokens except the gaia auth token. 288 bool session = true; 289 if (!strcmp(service, GaiaConstants::kGaiaService)) 290 session = false; 291 292 return base::StringPrintf(kIssueAuthTokenFormat, 293 encoded_sid.c_str(), 294 encoded_lsid.c_str(), 295 service, 296 session ? "true" : "false"); 297} 298 299// static 300std::string GaiaAuthFetcher::MakeGetAuthCodeBody() { 301 std::string encoded_scope = net::EscapeUrlEncodedData( 302 GaiaUrls::GetInstance()->oauth1_login_scope(), true); 303 std::string encoded_client_id = net::EscapeUrlEncodedData( 304 GaiaUrls::GetInstance()->oauth2_chrome_client_id(), true); 305 return base::StringPrintf(kClientLoginToOAuth2BodyFormat, 306 encoded_scope.c_str(), 307 encoded_client_id.c_str()); 308} 309 310// static 311std::string GaiaAuthFetcher::MakeGetTokenPairBody( 312 const std::string& auth_code) { 313 std::string encoded_scope = net::EscapeUrlEncodedData( 314 GaiaUrls::GetInstance()->oauth1_login_scope(), true); 315 std::string encoded_client_id = net::EscapeUrlEncodedData( 316 GaiaUrls::GetInstance()->oauth2_chrome_client_id(), true); 317 std::string encoded_client_secret = net::EscapeUrlEncodedData( 318 GaiaUrls::GetInstance()->oauth2_chrome_client_secret(), true); 319 std::string encoded_auth_code = net::EscapeUrlEncodedData(auth_code, true); 320 return base::StringPrintf(kOAuth2CodeToTokenPairBodyFormat, 321 encoded_scope.c_str(), 322 encoded_client_id.c_str(), 323 encoded_client_secret.c_str(), 324 encoded_auth_code.c_str()); 325} 326 327// static 328std::string GaiaAuthFetcher::MakeRevokeTokenBody( 329 const std::string& auth_token) { 330 return base::StringPrintf(kOAuth2RevokeTokenBodyFormat, auth_token.c_str()); 331} 332 333// static 334std::string GaiaAuthFetcher::MakeGetUserInfoBody(const std::string& lsid) { 335 std::string encoded_lsid = net::EscapeUrlEncodedData(lsid, true); 336 return base::StringPrintf(kGetUserInfoFormat, encoded_lsid.c_str()); 337} 338 339// static 340std::string GaiaAuthFetcher::MakeMergeSessionBody( 341 const std::string& auth_token, 342 const std::string& continue_url, 343 const std::string& source) { 344 std::string encoded_auth_token = net::EscapeUrlEncodedData(auth_token, true); 345 std::string encoded_continue_url = net::EscapeUrlEncodedData(continue_url, 346 true); 347 std::string encoded_source = net::EscapeUrlEncodedData(source, true); 348 return base::StringPrintf(kMergeSessionFormat, 349 encoded_auth_token.c_str(), 350 encoded_continue_url.c_str(), 351 encoded_source.c_str()); 352} 353 354// static 355std::string GaiaAuthFetcher::MakeGetAuthCodeHeader( 356 const std::string& auth_token) { 357 return base::StringPrintf(kAuthHeaderFormat, auth_token.c_str()); 358} 359 360// Helper method that extracts tokens from a successful reply. 361// static 362void GaiaAuthFetcher::ParseClientLoginResponse(const std::string& data, 363 std::string* sid, 364 std::string* lsid, 365 std::string* token) { 366 using std::vector; 367 using std::pair; 368 using std::string; 369 sid->clear(); 370 lsid->clear(); 371 token->clear(); 372 vector<pair<string, string> > tokens; 373 base::SplitStringIntoKeyValuePairs(data, '=', '\n', &tokens); 374 for (vector<pair<string, string> >::iterator i = tokens.begin(); 375 i != tokens.end(); ++i) { 376 if (i->first == "SID") { 377 sid->assign(i->second); 378 } else if (i->first == "LSID") { 379 lsid->assign(i->second); 380 } else if (i->first == "Auth") { 381 token->assign(i->second); 382 } 383 } 384 // If this was a request for uberauth token, then that's all we've got in 385 // data. 386 if (sid->empty() && lsid->empty() && token->empty()) 387 token->assign(data); 388} 389 390// static 391std::string GaiaAuthFetcher::MakeOAuthLoginBody(const std::string& service, 392 const std::string& source) { 393 std::string encoded_service = net::EscapeUrlEncodedData(service, true); 394 std::string encoded_source = net::EscapeUrlEncodedData(source, true); 395 return base::StringPrintf(kOAuthLoginFormat, 396 encoded_service.c_str(), 397 encoded_source.c_str()); 398} 399 400// static 401void GaiaAuthFetcher::ParseClientLoginFailure(const std::string& data, 402 std::string* error, 403 std::string* error_url, 404 std::string* captcha_url, 405 std::string* captcha_token) { 406 using std::vector; 407 using std::pair; 408 using std::string; 409 410 vector<pair<string, string> > tokens; 411 base::SplitStringIntoKeyValuePairs(data, '=', '\n', &tokens); 412 for (vector<pair<string, string> >::iterator i = tokens.begin(); 413 i != tokens.end(); ++i) { 414 if (i->first == kErrorParam) { 415 error->assign(i->second); 416 } else if (i->first == kErrorUrlParam) { 417 error_url->assign(i->second); 418 } else if (i->first == kCaptchaUrlParam) { 419 captcha_url->assign(i->second); 420 } else if (i->first == kCaptchaTokenParam) { 421 captcha_token->assign(i->second); 422 } 423 } 424} 425 426// static 427bool GaiaAuthFetcher::ParseClientLoginToOAuth2Response( 428 const net::ResponseCookies& cookies, 429 std::string* auth_code) { 430 DCHECK(auth_code); 431 net::ResponseCookies::const_iterator iter; 432 for (iter = cookies.begin(); iter != cookies.end(); ++iter) { 433 if (ParseClientLoginToOAuth2Cookie(*iter, auth_code)) 434 return true; 435 } 436 return false; 437} 438 439// static 440bool GaiaAuthFetcher::ParseClientLoginToOAuth2Cookie(const std::string& cookie, 441 std::string* auth_code) { 442 std::vector<std::string> parts; 443 base::SplitString(cookie, ';', &parts); 444 // Per documentation, the cookie should have Secure and HttpOnly. 445 if (!CookiePartsContains(parts, kClientLoginToOAuth2CookiePartSecure) || 446 !CookiePartsContains(parts, kClientLoginToOAuth2CookiePartHttpOnly)) { 447 return false; 448 } 449 450 std::vector<std::string>::const_iterator iter; 451 for (iter = parts.begin(); iter != parts.end(); ++iter) { 452 const std::string& part = *iter; 453 if (StartsWithASCII( 454 part, kClientLoginToOAuth2CookiePartCodePrefix, false)) { 455 auth_code->assign(part.substr( 456 kClientLoginToOAuth2CookiePartCodePrefixLength)); 457 return true; 458 } 459 } 460 return false; 461} 462 463void GaiaAuthFetcher::StartClientLogin( 464 const std::string& username, 465 const std::string& password, 466 const char* const service, 467 const std::string& login_token, 468 const std::string& login_captcha, 469 HostedAccountsSetting allow_hosted_accounts) { 470 471 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!"; 472 473 // This class is thread agnostic, so be sure to call this only on the 474 // same thread each time. 475 DVLOG(1) << "Starting new ClientLogin fetch for:" << username; 476 477 // Must outlive fetcher_. 478 request_body_ = MakeClientLoginBody(username, 479 password, 480 source_, 481 service, 482 login_token, 483 login_captcha, 484 allow_hosted_accounts); 485 fetcher_.reset(CreateGaiaFetcher(getter_, 486 request_body_, 487 std::string(), 488 client_login_gurl_, 489 kLoadFlagsIgnoreCookies, 490 this)); 491 fetch_pending_ = true; 492 fetcher_->Start(); 493} 494 495void GaiaAuthFetcher::StartIssueAuthToken(const std::string& sid, 496 const std::string& lsid, 497 const char* const service) { 498 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!"; 499 500 DVLOG(1) << "Starting IssueAuthToken for: " << service; 501 requested_service_ = service; 502 request_body_ = MakeIssueAuthTokenBody(sid, lsid, service); 503 fetcher_.reset(CreateGaiaFetcher(getter_, 504 request_body_, 505 std::string(), 506 issue_auth_token_gurl_, 507 kLoadFlagsIgnoreCookies, 508 this)); 509 fetch_pending_ = true; 510 fetcher_->Start(); 511} 512 513void GaiaAuthFetcher::StartLsoForOAuthLoginTokenExchange( 514 const std::string& auth_token) { 515 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!"; 516 517 DVLOG(1) << "Starting OAuth login token exchange with auth_token"; 518 request_body_ = MakeGetAuthCodeBody(); 519 client_login_to_oauth2_gurl_ = 520 GURL(GaiaUrls::GetInstance()->client_login_to_oauth2_url()); 521 522 fetcher_.reset(CreateGaiaFetcher(getter_, 523 request_body_, 524 MakeGetAuthCodeHeader(auth_token), 525 client_login_to_oauth2_gurl_, 526 kLoadFlagsIgnoreCookies, 527 this)); 528 fetch_pending_ = true; 529 fetcher_->Start(); 530} 531 532void GaiaAuthFetcher::StartRevokeOAuth2Token(const std::string& auth_token) { 533 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!"; 534 535 DVLOG(1) << "Starting OAuth2 token revocation"; 536 request_body_ = MakeRevokeTokenBody(auth_token); 537 fetcher_.reset(CreateGaiaFetcher(getter_, 538 request_body_, 539 std::string(), 540 oauth2_revoke_gurl_, 541 kLoadFlagsIgnoreCookies, 542 this)); 543 fetch_pending_ = true; 544 fetcher_->Start(); 545} 546 547void GaiaAuthFetcher::StartCookieForOAuthLoginTokenExchange( 548 const std::string& session_index) { 549 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!"; 550 551 DVLOG(1) << "Starting OAuth login token fetch with cookie jar"; 552 request_body_ = MakeGetAuthCodeBody(); 553 554 std::string url = GaiaUrls::GetInstance()->client_login_to_oauth2_url(); 555 if (!session_index.empty()) 556 url += "?authuser=" + session_index; 557 558 client_login_to_oauth2_gurl_ = GURL(url); 559 560 fetcher_.reset(CreateGaiaFetcher(getter_, 561 request_body_, 562 std::string(), 563 client_login_to_oauth2_gurl_, 564 net::LOAD_NORMAL, 565 this)); 566 fetch_pending_ = true; 567 fetcher_->Start(); 568} 569 570void GaiaAuthFetcher::StartAuthCodeForOAuth2TokenExchange( 571 const std::string& auth_code) { 572 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!"; 573 574 DVLOG(1) << "Starting OAuth token pair fetch"; 575 request_body_ = MakeGetTokenPairBody(auth_code); 576 fetcher_.reset(CreateGaiaFetcher(getter_, 577 request_body_, 578 std::string(), 579 oauth2_token_gurl_, 580 kLoadFlagsIgnoreCookies, 581 this)); 582 fetch_pending_ = true; 583 fetcher_->Start(); 584} 585 586void GaiaAuthFetcher::StartGetUserInfo(const std::string& lsid) { 587 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!"; 588 589 DVLOG(1) << "Starting GetUserInfo for lsid=" << lsid; 590 request_body_ = MakeGetUserInfoBody(lsid); 591 fetcher_.reset(CreateGaiaFetcher(getter_, 592 request_body_, 593 std::string(), 594 get_user_info_gurl_, 595 kLoadFlagsIgnoreCookies, 596 this)); 597 fetch_pending_ = true; 598 fetcher_->Start(); 599} 600 601void GaiaAuthFetcher::StartMergeSession(const std::string& uber_token) { 602 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!"; 603 604 DVLOG(1) << "Starting MergeSession with uber_token=" << uber_token; 605 606 // The continue URL is a required parameter of the MergeSession API, but in 607 // this case we don't actually need or want to navigate to it. Setting it to 608 // an arbitrary Google URL. 609 // 610 // In order for the new session to be merged correctly, the server needs to 611 // know what sessions already exist in the browser. The fetcher needs to be 612 // created such that it sends the cookies with the request, which is 613 // different from all other requests the fetcher can make. 614 std::string continue_url("http://www.google.com"); 615 request_body_ = MakeMergeSessionBody(uber_token, continue_url, source_); 616 fetcher_.reset(CreateGaiaFetcher(getter_, 617 request_body_, 618 std::string(), 619 merge_session_gurl_, 620 net::LOAD_NORMAL, 621 this)); 622 fetch_pending_ = true; 623 fetcher_->Start(); 624} 625 626void GaiaAuthFetcher::StartTokenFetchForUberAuthExchange( 627 const std::string& access_token) { 628 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!"; 629 630 DVLOG(1) << "Starting StartTokenFetchForUberAuthExchange with access_token=" 631 << access_token; 632 std::string authentication_header = 633 base::StringPrintf(kOAuthHeaderFormat, access_token.c_str()); 634 fetcher_.reset(CreateGaiaFetcher(getter_, 635 std::string(), 636 authentication_header, 637 uberauth_token_gurl_, 638 kLoadFlagsIgnoreCookies, 639 this)); 640 fetch_pending_ = true; 641 fetcher_->Start(); 642} 643 644void GaiaAuthFetcher::StartOAuthLogin(const std::string& access_token, 645 const std::string& service) { 646 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!"; 647 648 request_body_ = MakeOAuthLoginBody(service, source_); 649 std::string authentication_header = 650 base::StringPrintf(kOAuth2BearerHeaderFormat, access_token.c_str()); 651 fetcher_.reset(CreateGaiaFetcher(getter_, 652 request_body_, 653 authentication_header, 654 oauth_login_gurl_, 655 kLoadFlagsIgnoreCookies, 656 this)); 657 fetch_pending_ = true; 658 fetcher_->Start(); 659} 660 661// static 662GoogleServiceAuthError GaiaAuthFetcher::GenerateAuthError( 663 const std::string& data, 664 const net::URLRequestStatus& status) { 665 if (!status.is_success()) { 666 if (status.status() == net::URLRequestStatus::CANCELED) { 667 return GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED); 668 } else { 669 DLOG(WARNING) << "Could not reach Google Accounts servers: errno " 670 << status.error(); 671 return GoogleServiceAuthError::FromConnectionError(status.error()); 672 } 673 } else { 674 if (IsSecondFactorSuccess(data)) { 675 return GoogleServiceAuthError(GoogleServiceAuthError::TWO_FACTOR); 676 } 677 678 std::string error; 679 std::string url; 680 std::string captcha_url; 681 std::string captcha_token; 682 ParseClientLoginFailure(data, &error, &url, &captcha_url, &captcha_token); 683 DLOG(WARNING) << "ClientLogin failed with " << error; 684 685 if (error == kCaptchaError) { 686 GURL image_url( 687 GaiaUrls::GetInstance()->captcha_url_prefix() + captcha_url); 688 GURL unlock_url(url); 689 return GoogleServiceAuthError::FromClientLoginCaptchaChallenge( 690 captcha_token, image_url, unlock_url); 691 } 692 if (error == kAccountDeletedError) 693 return GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DELETED); 694 if (error == kAccountDisabledError) 695 return GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DISABLED); 696 if (error == kBadAuthenticationError) { 697 return GoogleServiceAuthError( 698 GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS); 699 } 700 if (error == kServiceUnavailableError) { 701 return GoogleServiceAuthError( 702 GoogleServiceAuthError::SERVICE_UNAVAILABLE); 703 } 704 705 DLOG(WARNING) << "Incomprehensible response from Google Accounts servers."; 706 return GoogleServiceAuthError( 707 GoogleServiceAuthError::SERVICE_UNAVAILABLE); 708 } 709 710 NOTREACHED(); 711 return GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE); 712} 713 714// static 715GoogleServiceAuthError GaiaAuthFetcher::GenerateOAuthLoginError( 716 const std::string& data, 717 const net::URLRequestStatus& status) { 718 if (!status.is_success()) { 719 if (status.status() == net::URLRequestStatus::CANCELED) { 720 return GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED); 721 } else { 722 DLOG(WARNING) << "Could not reach Google Accounts servers: errno " 723 << status.error(); 724 return GoogleServiceAuthError::FromConnectionError(status.error()); 725 } 726 } else { 727 if (IsSecondFactorSuccess(data)) { 728 return GoogleServiceAuthError(GoogleServiceAuthError::TWO_FACTOR); 729 } 730 731 std::string error; 732 std::string url; 733 std::string captcha_url; 734 std::string captcha_token; 735 ParseClientLoginFailure(data, &error, &url, &captcha_url, &captcha_token); 736 LOG(WARNING) << "OAuthLogin failed with " << error; 737 738 if (error == kCaptchaErrorCode) { 739 GURL image_url( 740 GaiaUrls::GetInstance()->captcha_url_prefix() + captcha_url); 741 GURL unlock_url(url); 742 return GoogleServiceAuthError::FromClientLoginCaptchaChallenge( 743 captcha_token, image_url, unlock_url); 744 } 745 if (error == kAccountDeletedErrorCode) 746 return GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DELETED); 747 if (error == kAccountDisabledErrorCode) 748 return GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DISABLED); 749 if (error == kBadAuthenticationErrorCode) { 750 return GoogleServiceAuthError( 751 GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS); 752 } 753 if (error == kServiceUnavailableErrorCode) { 754 return GoogleServiceAuthError( 755 GoogleServiceAuthError::SERVICE_UNAVAILABLE); 756 } 757 758 DLOG(WARNING) << "Incomprehensible response from Google Accounts servers."; 759 return GoogleServiceAuthError( 760 GoogleServiceAuthError::SERVICE_UNAVAILABLE); 761 } 762 763 NOTREACHED(); 764 return GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE); 765} 766 767void GaiaAuthFetcher::OnClientLoginFetched(const std::string& data, 768 const net::URLRequestStatus& status, 769 int response_code) { 770 if (status.is_success() && response_code == net::HTTP_OK) { 771 DVLOG(1) << "ClientLogin successful!"; 772 std::string sid; 773 std::string lsid; 774 std::string token; 775 ParseClientLoginResponse(data, &sid, &lsid, &token); 776 consumer_->OnClientLoginSuccess( 777 GaiaAuthConsumer::ClientLoginResult(sid, lsid, token, data)); 778 } else { 779 consumer_->OnClientLoginFailure(GenerateAuthError(data, status)); 780 } 781} 782 783void GaiaAuthFetcher::OnIssueAuthTokenFetched( 784 const std::string& data, 785 const net::URLRequestStatus& status, 786 int response_code) { 787 if (status.is_success() && response_code == net::HTTP_OK) { 788 // Only the bare token is returned in the body of this Gaia call 789 // without any padding. 790 consumer_->OnIssueAuthTokenSuccess(requested_service_, data); 791 } else { 792 consumer_->OnIssueAuthTokenFailure(requested_service_, 793 GenerateAuthError(data, status)); 794 } 795} 796 797void GaiaAuthFetcher::OnClientLoginToOAuth2Fetched( 798 const std::string& data, 799 const net::ResponseCookies& cookies, 800 const net::URLRequestStatus& status, 801 int response_code) { 802 if (status.is_success() && response_code == net::HTTP_OK) { 803 std::string auth_code; 804 ParseClientLoginToOAuth2Response(cookies, &auth_code); 805 StartAuthCodeForOAuth2TokenExchange(auth_code); 806 } else { 807 consumer_->OnClientOAuthFailure(GenerateAuthError(data, status)); 808 } 809} 810 811void GaiaAuthFetcher::OnOAuth2TokenPairFetched( 812 const std::string& data, 813 const net::URLRequestStatus& status, 814 int response_code) { 815 std::string refresh_token; 816 std::string access_token; 817 int expires_in_secs = 0; 818 819 bool success = false; 820 if (status.is_success() && response_code == net::HTTP_OK) { 821 scoped_ptr<base::Value> value(base::JSONReader::Read(data)); 822 if (value.get() && value->GetType() == base::Value::TYPE_DICTIONARY) { 823 base::DictionaryValue* dict = 824 static_cast<base::DictionaryValue*>(value.get()); 825 success = ExtractOAuth2TokenPairResponse(dict, &refresh_token, 826 &access_token, &expires_in_secs); 827 } 828 } 829 830 if (success) { 831 consumer_->OnClientOAuthSuccess( 832 GaiaAuthConsumer::ClientOAuthResult(refresh_token, access_token, 833 expires_in_secs)); 834 } else { 835 consumer_->OnClientOAuthFailure(GenerateAuthError(data, status)); 836 } 837} 838 839void GaiaAuthFetcher::OnOAuth2RevokeTokenFetched( 840 const std::string& data, 841 const net::URLRequestStatus& status, 842 int response_code) { 843 consumer_->OnOAuth2RevokeTokenCompleted(); 844} 845 846void GaiaAuthFetcher::OnGetUserInfoFetched( 847 const std::string& data, 848 const net::URLRequestStatus& status, 849 int response_code) { 850 if (status.is_success() && response_code == net::HTTP_OK) { 851 std::vector<std::pair<std::string, std::string> > tokens; 852 UserInfoMap matches; 853 base::SplitStringIntoKeyValuePairs(data, '=', '\n', &tokens); 854 std::vector<std::pair<std::string, std::string> >::iterator i; 855 for (i = tokens.begin(); i != tokens.end(); ++i) { 856 matches[i->first] = i->second; 857 } 858 consumer_->OnGetUserInfoSuccess(matches); 859 } else { 860 consumer_->OnGetUserInfoFailure(GenerateAuthError(data, status)); 861 } 862} 863 864void GaiaAuthFetcher::OnMergeSessionFetched(const std::string& data, 865 const net::URLRequestStatus& status, 866 int response_code) { 867 if (status.is_success() && response_code == net::HTTP_OK) { 868 consumer_->OnMergeSessionSuccess(data); 869 } else { 870 consumer_->OnMergeSessionFailure(GenerateAuthError(data, status)); 871 } 872} 873 874void GaiaAuthFetcher::OnUberAuthTokenFetch(const std::string& data, 875 const net::URLRequestStatus& status, 876 int response_code) { 877 if (status.is_success() && response_code == net::HTTP_OK) { 878 consumer_->OnUberAuthTokenSuccess(data); 879 } else { 880 consumer_->OnUberAuthTokenFailure(GenerateAuthError(data, status)); 881 } 882} 883 884void GaiaAuthFetcher::OnOAuthLoginFetched(const std::string& data, 885 const net::URLRequestStatus& status, 886 int response_code) { 887 if (status.is_success() && response_code == net::HTTP_OK) { 888 DVLOG(1) << "ClientLogin successful!"; 889 std::string sid; 890 std::string lsid; 891 std::string token; 892 ParseClientLoginResponse(data, &sid, &lsid, &token); 893 consumer_->OnClientLoginSuccess( 894 GaiaAuthConsumer::ClientLoginResult(sid, lsid, token, data)); 895 } else { 896 consumer_->OnClientLoginFailure(GenerateAuthError(data, status)); 897 } 898} 899 900void GaiaAuthFetcher::OnURLFetchComplete(const net::URLFetcher* source) { 901 fetch_pending_ = false; 902 // Some of the GAIA requests perform redirects, which results in the final 903 // URL of the fetcher not being the original URL requested. Therefore use 904 // the original URL when determining which OnXXX function to call. 905 const GURL& url = source->GetOriginalURL(); 906 const net::URLRequestStatus& status = source->GetStatus(); 907 int response_code = source->GetResponseCode(); 908 std::string data; 909 source->GetResponseAsString(&data); 910#ifndef NDEBUG 911 std::string headers; 912 if (source->GetResponseHeaders()) 913 source->GetResponseHeaders()->GetNormalizedHeaders(&headers); 914 DVLOG(2) << "Response " << url.spec() << ", code = " << response_code << "\n" 915 << headers << "\n"; 916 DVLOG(2) << "data: " << data << "\n"; 917#endif 918 // Retrieve the response headers from the request. Must only be called after 919 // the OnURLFetchComplete callback has run. 920 if (url == client_login_gurl_) { 921 OnClientLoginFetched(data, status, response_code); 922 } else if (url == issue_auth_token_gurl_) { 923 OnIssueAuthTokenFetched(data, status, response_code); 924 } else if (url == client_login_to_oauth2_gurl_) { 925 OnClientLoginToOAuth2Fetched( 926 data, source->GetCookies(), status, response_code); 927 } else if (url == oauth2_token_gurl_) { 928 OnOAuth2TokenPairFetched(data, status, response_code); 929 } else if (url == get_user_info_gurl_) { 930 OnGetUserInfoFetched(data, status, response_code); 931 } else if (url == merge_session_gurl_) { 932 OnMergeSessionFetched(data, status, response_code); 933 } else if (url == uberauth_token_gurl_) { 934 OnUberAuthTokenFetch(data, status, response_code); 935 } else if (url == oauth_login_gurl_) { 936 OnOAuthLoginFetched(data, status, response_code); 937 } else if (url == oauth2_revoke_gurl_) { 938 OnOAuth2RevokeTokenFetched(data, status, response_code); 939 } else { 940 NOTREACHED(); 941 } 942} 943 944// static 945bool GaiaAuthFetcher::IsSecondFactorSuccess( 946 const std::string& alleged_error) { 947 return alleged_error.find(kSecondFactor) != 948 std::string::npos; 949} 950