gaia_auth_fetcher.cc revision 72a454cd3513ac24fbdd0e0cb9ad70b86a99b801
1// Copyright (c) 2011 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 "chrome/common/net/gaia/gaia_auth_fetcher.h" 6 7#include <string> 8#include <utility> 9#include <vector> 10 11#include "base/string_split.h" 12#include "base/string_util.h" 13#include "chrome/common/net/gaia/gaia_auth_consumer.h" 14#include "chrome/common/net/gaia/gaia_constants.h" 15#include "chrome/common/net/gaia/google_service_auth_error.h" 16#include "chrome/common/net/http_return.h" 17#include "chrome/common/net/url_request_context_getter.h" 18#include "net/base/load_flags.h" 19#include "net/url_request/url_request_status.h" 20#include "third_party/libjingle/source/talk/base/urlencode.h" 21 22// TODO(chron): Add sourceless version of this formatter. 23// static 24const char GaiaAuthFetcher::kClientLoginFormat[] = 25 "Email=%s&" 26 "Passwd=%s&" 27 "PersistentCookie=%s&" 28 "accountType=%s&" 29 "source=%s&" 30 "service=%s"; 31// static 32const char GaiaAuthFetcher::kClientLoginCaptchaFormat[] = 33 "Email=%s&" 34 "Passwd=%s&" 35 "PersistentCookie=%s&" 36 "accountType=%s&" 37 "source=%s&" 38 "service=%s&" 39 "logintoken=%s&" 40 "logincaptcha=%s"; 41// static 42const char GaiaAuthFetcher::kIssueAuthTokenFormat[] = 43 "SID=%s&" 44 "LSID=%s&" 45 "service=%s&" 46 "Session=%s"; 47// static 48const char GaiaAuthFetcher::kGetUserInfoFormat[] = 49 "LSID=%s"; 50 51// static 52const char GaiaAuthFetcher::kAccountDeletedError[] = "AccountDeleted"; 53// static 54const char GaiaAuthFetcher::kAccountDisabledError[] = "AccountDisabled"; 55// static 56const char GaiaAuthFetcher::kBadAuthenticationError[] = "BadAuthentication"; 57// static 58const char GaiaAuthFetcher::kCaptchaError[] = "CaptchaRequired"; 59// static 60const char GaiaAuthFetcher::kServiceUnavailableError[] = 61 "ServiceUnavailable"; 62// static 63const char GaiaAuthFetcher::kErrorParam[] = "Error"; 64// static 65const char GaiaAuthFetcher::kErrorUrlParam[] = "Url"; 66// static 67const char GaiaAuthFetcher::kCaptchaUrlParam[] = "CaptchaUrl"; 68// static 69const char GaiaAuthFetcher::kCaptchaTokenParam[] = "CaptchaToken"; 70// static 71const char GaiaAuthFetcher::kCaptchaUrlPrefix[] = 72 "http://www.google.com/accounts/"; 73 74// static 75const char GaiaAuthFetcher::kCookiePersistence[] = "true"; 76// static 77// TODO(johnnyg): When hosted accounts are supported by sync, 78// we can always use "HOSTED_OR_GOOGLE" 79const char GaiaAuthFetcher::kAccountTypeHostedOrGoogle[] = 80 "HOSTED_OR_GOOGLE"; 81const char GaiaAuthFetcher::kAccountTypeGoogle[] = 82 "GOOGLE"; 83 84// static 85const char GaiaAuthFetcher::kSecondFactor[] = "Info=InvalidSecondFactor"; 86 87// TODO(chron): These urls are also in auth_response_handler.h. 88// The URLs for different calls in the Google Accounts programmatic login API. 89const char GaiaAuthFetcher::kClientLoginUrl[] = 90 "https://www.google.com/accounts/ClientLogin"; 91const char GaiaAuthFetcher::kIssueAuthTokenUrl[] = 92 "https://www.google.com/accounts/IssueAuthToken"; 93const char GaiaAuthFetcher::kGetUserInfoUrl[] = 94 "https://www.google.com/accounts/GetUserInfo"; 95 96GaiaAuthFetcher::GaiaAuthFetcher(GaiaAuthConsumer* consumer, 97 const std::string& source, 98 URLRequestContextGetter* getter) 99 : consumer_(consumer), 100 getter_(getter), 101 source_(source), 102 client_login_gurl_(kClientLoginUrl), 103 issue_auth_token_gurl_(kIssueAuthTokenUrl), 104 get_user_info_gurl_(kGetUserInfoUrl), 105 fetch_pending_(false) {} 106 107GaiaAuthFetcher::~GaiaAuthFetcher() {} 108 109bool GaiaAuthFetcher::HasPendingFetch() { 110 return fetch_pending_; 111} 112 113void GaiaAuthFetcher::CancelRequest() { 114 fetcher_.reset(); 115 fetch_pending_ = false; 116} 117 118// static 119URLFetcher* GaiaAuthFetcher::CreateGaiaFetcher( 120 URLRequestContextGetter* getter, 121 const std::string& body, 122 const GURL& gaia_gurl, 123 URLFetcher::Delegate* delegate) { 124 125 URLFetcher* to_return = 126 URLFetcher::Create(0, 127 gaia_gurl, 128 URLFetcher::POST, 129 delegate); 130 to_return->set_request_context(getter); 131 to_return->set_load_flags(net::LOAD_DO_NOT_SEND_COOKIES); 132 to_return->set_upload_data("application/x-www-form-urlencoded", body); 133 return to_return; 134} 135 136// static 137std::string GaiaAuthFetcher::MakeClientLoginBody( 138 const std::string& username, 139 const std::string& password, 140 const std::string& source, 141 const char* service, 142 const std::string& login_token, 143 const std::string& login_captcha, 144 HostedAccountsSetting allow_hosted_accounts) { 145 std::string encoded_username = UrlEncodeString(username); 146 std::string encoded_password = UrlEncodeString(password); 147 std::string encoded_login_token = UrlEncodeString(login_token); 148 std::string encoded_login_captcha = UrlEncodeString(login_captcha); 149 150 const char* account_type = allow_hosted_accounts == HostedAccountsAllowed ? 151 kAccountTypeHostedOrGoogle : 152 kAccountTypeGoogle; 153 154 if (login_token.empty() || login_captcha.empty()) { 155 return base::StringPrintf(kClientLoginFormat, 156 encoded_username.c_str(), 157 encoded_password.c_str(), 158 kCookiePersistence, 159 account_type, 160 source.c_str(), 161 service); 162 } 163 164 return base::StringPrintf(kClientLoginCaptchaFormat, 165 encoded_username.c_str(), 166 encoded_password.c_str(), 167 kCookiePersistence, 168 account_type, 169 source.c_str(), 170 service, 171 encoded_login_token.c_str(), 172 encoded_login_captcha.c_str()); 173 174} 175 176// static 177std::string GaiaAuthFetcher::MakeIssueAuthTokenBody( 178 const std::string& sid, 179 const std::string& lsid, 180 const char* const service) { 181 std::string encoded_sid = UrlEncodeString(sid); 182 std::string encoded_lsid = UrlEncodeString(lsid); 183 184 // All tokens should be session tokens except the gaia auth token. 185 bool session = true; 186 if (!strcmp(service, GaiaConstants::kGaiaService)) 187 session = false; 188 189 return base::StringPrintf(kIssueAuthTokenFormat, 190 encoded_sid.c_str(), 191 encoded_lsid.c_str(), 192 service, 193 session ? "true" : "false"); 194} 195 196// static 197std::string GaiaAuthFetcher::MakeGetUserInfoBody(const std::string& lsid) { 198 std::string encoded_lsid = UrlEncodeString(lsid); 199 return base::StringPrintf(kGetUserInfoFormat, encoded_lsid.c_str()); 200} 201 202// Helper method that extracts tokens from a successful reply. 203// static 204void GaiaAuthFetcher::ParseClientLoginResponse(const std::string& data, 205 std::string* sid, 206 std::string* lsid, 207 std::string* token) { 208 using std::vector; 209 using std::pair; 210 using std::string; 211 212 vector<pair<string, string> > tokens; 213 base::SplitStringIntoKeyValuePairs(data, '=', '\n', &tokens); 214 for (vector<pair<string, string> >::iterator i = tokens.begin(); 215 i != tokens.end(); ++i) { 216 if (i->first == "SID") { 217 sid->assign(i->second); 218 } else if (i->first == "LSID") { 219 lsid->assign(i->second); 220 } else if (i->first == "Auth") { 221 token->assign(i->second); 222 } 223 } 224} 225 226// static 227void GaiaAuthFetcher::ParseClientLoginFailure(const std::string& data, 228 std::string* error, 229 std::string* error_url, 230 std::string* captcha_url, 231 std::string* captcha_token) { 232 using std::vector; 233 using std::pair; 234 using std::string; 235 236 vector<pair<string, string> > tokens; 237 base::SplitStringIntoKeyValuePairs(data, '=', '\n', &tokens); 238 for (vector<pair<string, string> >::iterator i = tokens.begin(); 239 i != tokens.end(); ++i) { 240 if (i->first == kErrorParam) { 241 error->assign(i->second); 242 } else if (i->first == kErrorUrlParam) { 243 error_url->assign(i->second); 244 } else if (i->first == kCaptchaUrlParam) { 245 captcha_url->assign(i->second); 246 } else if (i->first == kCaptchaTokenParam) { 247 captcha_token->assign(i->second); 248 } 249 } 250} 251 252void GaiaAuthFetcher::StartClientLogin( 253 const std::string& username, 254 const std::string& password, 255 const char* const service, 256 const std::string& login_token, 257 const std::string& login_captcha, 258 HostedAccountsSetting allow_hosted_accounts) { 259 260 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!"; 261 262 // This class is thread agnostic, so be sure to call this only on the 263 // same thread each time. 264 VLOG(1) << "Starting new ClientLogin fetch for:" << username; 265 266 // Must outlive fetcher_. 267 request_body_ = MakeClientLoginBody(username, 268 password, 269 source_, 270 service, 271 login_token, 272 login_captcha, 273 allow_hosted_accounts); 274 fetcher_.reset(CreateGaiaFetcher(getter_, 275 request_body_, 276 client_login_gurl_, 277 this)); 278 fetch_pending_ = true; 279 fetcher_->Start(); 280} 281 282void GaiaAuthFetcher::StartIssueAuthToken(const std::string& sid, 283 const std::string& lsid, 284 const char* const service) { 285 286 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!"; 287 288 VLOG(1) << "Starting IssueAuthToken for: " << service; 289 requested_service_ = service; 290 request_body_ = MakeIssueAuthTokenBody(sid, lsid, service); 291 fetcher_.reset(CreateGaiaFetcher(getter_, 292 request_body_, 293 issue_auth_token_gurl_, 294 this)); 295 fetch_pending_ = true; 296 fetcher_->Start(); 297} 298 299void GaiaAuthFetcher::StartGetUserInfo(const std::string& lsid, 300 const std::string& info_key) { 301 DCHECK(!fetch_pending_) << "Tried to fetch two things at once!"; 302 303 VLOG(1) << "Starting GetUserInfo for lsid=" << lsid; 304 request_body_ = MakeGetUserInfoBody(lsid); 305 fetcher_.reset(CreateGaiaFetcher(getter_, 306 request_body_, 307 get_user_info_gurl_, 308 this)); 309 fetch_pending_ = true; 310 requested_info_key_ = info_key; 311 fetcher_->Start(); 312} 313 314// static 315GoogleServiceAuthError GaiaAuthFetcher::GenerateAuthError( 316 const std::string& data, 317 const net::URLRequestStatus& status) { 318 319 if (!status.is_success()) { 320 if (status.status() == net::URLRequestStatus::CANCELED) { 321 return GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED); 322 } else { 323 LOG(WARNING) << "Could not reach Google Accounts servers: errno " 324 << status.os_error(); 325 return GoogleServiceAuthError::FromConnectionError(status.os_error()); 326 } 327 } else { 328 if (IsSecondFactorSuccess(data)) { 329 return GoogleServiceAuthError(GoogleServiceAuthError::TWO_FACTOR); 330 } 331 332 std::string error; 333 std::string url; 334 std::string captcha_url; 335 std::string captcha_token; 336 ParseClientLoginFailure(data, &error, &url, &captcha_url, &captcha_token); 337 LOG(WARNING) << "ClientLogin failed with " << error; 338 339 if (error == kCaptchaError) { 340 GURL image_url(kCaptchaUrlPrefix + captcha_url); 341 GURL unlock_url(url); 342 return GoogleServiceAuthError::FromCaptchaChallenge( 343 captcha_token, image_url, unlock_url); 344 } 345 if (error == kAccountDeletedError) 346 return GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DELETED); 347 if (error == kAccountDisabledError) 348 return GoogleServiceAuthError(GoogleServiceAuthError::ACCOUNT_DISABLED); 349 if (error == kBadAuthenticationError) { 350 return GoogleServiceAuthError( 351 GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS); 352 } 353 if (error == kServiceUnavailableError) { 354 return GoogleServiceAuthError( 355 GoogleServiceAuthError::SERVICE_UNAVAILABLE); 356 } 357 358 LOG(WARNING) << "Incomprehensible response from Google Accounts servers."; 359 return GoogleServiceAuthError( 360 GoogleServiceAuthError::SERVICE_UNAVAILABLE); 361 } 362 363 NOTREACHED(); 364 return GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_UNAVAILABLE); 365} 366 367void GaiaAuthFetcher::OnClientLoginFetched(const std::string& data, 368 const net::URLRequestStatus& status, 369 int response_code) { 370 371 if (status.is_success() && response_code == RC_REQUEST_OK) { 372 VLOG(1) << "ClientLogin successful!"; 373 std::string sid; 374 std::string lsid; 375 std::string token; 376 ParseClientLoginResponse(data, &sid, &lsid, &token); 377 consumer_->OnClientLoginSuccess( 378 GaiaAuthConsumer::ClientLoginResult(sid, lsid, token, data)); 379 } else { 380 consumer_->OnClientLoginFailure(GenerateAuthError(data, status)); 381 } 382} 383 384void GaiaAuthFetcher::OnIssueAuthTokenFetched( 385 const std::string& data, 386 const net::URLRequestStatus& status, 387 int response_code) { 388 if (status.is_success() && response_code == RC_REQUEST_OK) { 389 // Only the bare token is returned in the body of this Gaia call 390 // without any padding. 391 consumer_->OnIssueAuthTokenSuccess(requested_service_, data); 392 } else { 393 consumer_->OnIssueAuthTokenFailure(requested_service_, 394 GenerateAuthError(data, status)); 395 } 396} 397 398void GaiaAuthFetcher::OnGetUserInfoFetched( 399 const std::string& data, 400 const net::URLRequestStatus& status, 401 int response_code) { 402 using std::vector; 403 using std::string; 404 using std::pair; 405 406 if (status.is_success() && response_code == RC_REQUEST_OK) { 407 vector<pair<string, string> > tokens; 408 base::SplitStringIntoKeyValuePairs(data, '=', '\n', &tokens); 409 for (vector<pair<string, string> >::iterator i = tokens.begin(); 410 i != tokens.end(); ++i) { 411 if (i->first == requested_info_key_) { 412 consumer_->OnGetUserInfoSuccess(i->first, i->second); 413 return; 414 } 415 } 416 consumer_->OnGetUserInfoKeyNotFound(requested_info_key_); 417 } else { 418 consumer_->OnGetUserInfoFailure(GenerateAuthError(data, status)); 419 } 420} 421 422void GaiaAuthFetcher::OnURLFetchComplete(const URLFetcher* source, 423 const GURL& url, 424 const net::URLRequestStatus& status, 425 int response_code, 426 const ResponseCookies& cookies, 427 const std::string& data) { 428 fetch_pending_ = false; 429 if (url == client_login_gurl_) { 430 OnClientLoginFetched(data, status, response_code); 431 } else if (url == issue_auth_token_gurl_) { 432 OnIssueAuthTokenFetched(data, status, response_code); 433 } else if (url == get_user_info_gurl_) { 434 OnGetUserInfoFetched(data, status, response_code); 435 } else { 436 NOTREACHED(); 437 } 438} 439 440// static 441bool GaiaAuthFetcher::IsSecondFactorSuccess( 442 const std::string& alleged_error) { 443 return alleged_error.find(kSecondFactor) != 444 std::string::npos; 445} 446