// Copyright 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "components/autofill/browser/wallet/wallet_signin_helper.h" #include "base/callback_helpers.h" #include "base/json/json_reader.h" #include "base/logging.h" #include "base/rand_util.h" #include "base/stringprintf.h" #include "base/time.h" #include "base/values.h" #include "components/autofill/browser/wallet/wallet_service_url.h" #include "components/autofill/browser/wallet/wallet_signin_helper_delegate.h" #include "google_apis/gaia/gaia_auth_fetcher.h" #include "google_apis/gaia/gaia_auth_util.h" #include "google_apis/gaia/gaia_constants.h" #include "google_apis/gaia/gaia_urls.h" #include "google_apis/gaia/google_service_auth_error.h" #include "net/base/escape.h" #include "net/url_request/url_fetcher.h" #include "net/url_request/url_request_context.h" namespace autofill { namespace wallet { namespace { // Toolbar::GetAccountInfo API URL (JSON). const char kGetAccountInfoUrlFormat[] = "https://clients1.google.com/tbproxy/getaccountinfo?key=%d&rv=2"; } // namespace WalletSigninHelper::WalletSigninHelper( WalletSigninHelperDelegate* delegate, net::URLRequestContextGetter* getter) : delegate_(delegate), getter_(getter), state_(IDLE) { DCHECK(delegate_); } WalletSigninHelper::~WalletSigninHelper() { } void WalletSigninHelper::StartPassiveSignin() { DCHECK_EQ(IDLE, state_); DCHECK(!url_fetcher_); DCHECK(!gaia_fetcher_); state_ = PASSIVE_EXECUTING_SIGNIN; sid_.clear(); lsid_.clear(); username_.clear(); const GURL& url = wallet::GetPassiveAuthUrl(); url_fetcher_.reset(net::URLFetcher::Create( 0, url, net::URLFetcher::GET, this)); url_fetcher_->SetRequestContext(getter_); url_fetcher_->Start(); } void WalletSigninHelper::StartAutomaticSignin( const std::string& sid, const std::string& lsid) { DCHECK(!sid.empty()); DCHECK(!lsid.empty()); DCHECK_EQ(state_, IDLE); DCHECK(!url_fetcher_); DCHECK(!gaia_fetcher_); state_ = AUTOMATIC_FETCHING_USERINFO; sid_ = sid; lsid_ = lsid; username_.clear(); gaia_fetcher_.reset(new GaiaAuthFetcher( this, GaiaConstants::kChromeSource, getter_)); gaia_fetcher_->StartGetUserInfo(lsid_); } void WalletSigninHelper::StartUserNameFetch() { DCHECK_EQ(state_, IDLE); DCHECK(!url_fetcher_); DCHECK(!gaia_fetcher_); state_ = USERNAME_FETCHING_USERINFO; sid_.clear(); lsid_.clear(); username_.clear(); StartFetchingUserNameFromSession(); } std::string WalletSigninHelper::GetGetAccountInfoUrlForTesting() const { return base::StringPrintf(kGetAccountInfoUrlFormat, 0); } void WalletSigninHelper::OnServiceError(const GoogleServiceAuthError& error) { const State state_with_error = state_; state_ = IDLE; url_fetcher_.reset(); gaia_fetcher_.reset(); switch(state_with_error) { case IDLE: NOTREACHED(); break; case PASSIVE_EXECUTING_SIGNIN: /*FALLTHROUGH*/ case PASSIVE_FETCHING_USERINFO: delegate_->OnPassiveSigninFailure(error); break; case AUTOMATIC_FETCHING_USERINFO: /*FALLTHROUGH*/ case AUTOMATIC_ISSUING_AUTH_TOKEN: /*FALLTHROUGH*/ case AUTOMATIC_EXECUTING_SIGNIN: delegate_->OnAutomaticSigninFailure(error); break; case USERNAME_FETCHING_USERINFO: delegate_->OnUserNameFetchFailure(error); break; } } void WalletSigninHelper::OnOtherError() { OnServiceError(GoogleServiceAuthError::AuthErrorNone()); } void WalletSigninHelper::OnGetUserInfoSuccess( const UserInfoMap& data) { DCHECK_EQ(AUTOMATIC_FETCHING_USERINFO, state_); UserInfoMap::const_iterator email_iter = data.find(GaiaConstants::kClientOAuthEmailKey); if (email_iter != data.end()) { username_ = email_iter->second; DCHECK(!url_fetcher_); state_ = AUTOMATIC_ISSUING_AUTH_TOKEN; gaia_fetcher_.reset(new GaiaAuthFetcher( this, GaiaConstants::kChromeSource, getter_)); gaia_fetcher_->StartIssueAuthToken( sid_, lsid_, GaiaConstants::kGaiaService); } else { LOG(ERROR) << "GetUserInfoFailure: email field not found"; OnOtherError(); } } void WalletSigninHelper::OnGetUserInfoFailure( const GoogleServiceAuthError& error) { LOG(ERROR) << "GetUserInfoFailure: " << error.ToString(); DCHECK_EQ(AUTOMATIC_FETCHING_USERINFO, state_); OnServiceError(error); } void WalletSigninHelper::OnIssueAuthTokenSuccess( const std::string& service, const std::string& auth_token) { DCHECK_EQ(AUTOMATIC_ISSUING_AUTH_TOKEN, state_); state_ = AUTOMATIC_EXECUTING_SIGNIN; std::string encoded_auth_token = net::EscapeUrlEncodedData(auth_token, true); std::string encoded_continue_url = net::EscapeUrlEncodedData(wallet::GetPassiveAuthUrl().spec(), true); std::string encoded_source = net::EscapeUrlEncodedData( GaiaConstants::kChromeSource, true); std::string body = base::StringPrintf( "auth=%s&" "continue=%s&" "source=%s", encoded_auth_token.c_str(), encoded_continue_url.c_str(), encoded_source.c_str()); gaia_fetcher_.reset(); DCHECK(!url_fetcher_); url_fetcher_.reset(net::URLFetcher::Create( 0, GURL(GaiaUrls::GetInstance()->token_auth_url()), net::URLFetcher::POST, this)); url_fetcher_->SetUploadData("application/x-www-form-urlencoded", body); url_fetcher_->SetRequestContext(getter_); url_fetcher_->Start(); // This will result in OnURLFetchComplete callback. } void WalletSigninHelper::OnIssueAuthTokenFailure( const std::string& service, const GoogleServiceAuthError& error) { LOG(ERROR) << "IssueAuthTokenFailure: " << error.ToString(); DCHECK_EQ(AUTOMATIC_ISSUING_AUTH_TOKEN, state_); OnServiceError(error); } void WalletSigninHelper::OnURLFetchComplete( const net::URLFetcher* fetcher) { DCHECK_EQ(url_fetcher_.get(), fetcher); DCHECK(!gaia_fetcher_); if (!fetcher->GetStatus().is_success() || fetcher->GetResponseCode() < 200 || fetcher->GetResponseCode() >= 300) { LOG(ERROR) << "URLFetchFailure: state=" << state_ << " r=" << fetcher->GetResponseCode() << " s=" << fetcher->GetStatus().status() << " e=" << fetcher->GetStatus().error(); OnOtherError(); return; } switch (state_) { case USERNAME_FETCHING_USERINFO: /*FALLTHROUGH*/ case PASSIVE_FETCHING_USERINFO: ProcessGetAccountInfoResponseAndFinish(); break; case PASSIVE_EXECUTING_SIGNIN: url_fetcher_.reset(); state_ = PASSIVE_FETCHING_USERINFO; StartFetchingUserNameFromSession(); break; case AUTOMATIC_EXECUTING_SIGNIN: state_ = IDLE; url_fetcher_.reset(); delegate_->OnAutomaticSigninSuccess(username_); break; default: NOTREACHED() << "unexpected state_=" << state_; } } void WalletSigninHelper::StartFetchingUserNameFromSession() { DCHECK(!gaia_fetcher_); const int random_number = static_cast(base::RandUint64() % INT_MAX); url_fetcher_.reset( net::URLFetcher::Create( 0, GURL(base::StringPrintf(kGetAccountInfoUrlFormat, random_number)), net::URLFetcher::GET, this)); url_fetcher_->SetRequestContext(getter_); url_fetcher_->Start(); // This will result in OnURLFetchComplete callback. } void WalletSigninHelper::ProcessGetAccountInfoResponseAndFinish() { std::string email; if (!ParseGetAccountInfoResponse(url_fetcher_.get(), &email)) { LOG(ERROR) << "failed to get the user email"; OnOtherError(); return; } username_ = email; const State finishing_state = state_; state_ = IDLE; url_fetcher_.reset(); switch(finishing_state) { case USERNAME_FETCHING_USERINFO: delegate_->OnUserNameFetchSuccess(username_); break; case PASSIVE_FETCHING_USERINFO: delegate_->OnPassiveSigninSuccess(username_); break; default: NOTREACHED() << "unexpected state_=" << finishing_state; } } bool WalletSigninHelper::ParseGetAccountInfoResponse( const net::URLFetcher* fetcher, std::string* email) { DCHECK(email); std::string data; if (!fetcher->GetResponseAsString(&data)) { LOG(ERROR) << "failed to GetResponseAsString"; return false; } scoped_ptr value(base::JSONReader::Read(data)); if (!value.get() || value->GetType() != base::Value::TYPE_DICTIONARY) { LOG(ERROR) << "failed to parse JSON response"; return false; } DictionaryValue* dict = static_cast(value.get()); if (!dict->GetStringWithoutPathExpansion("email", email)) { LOG(ERROR) << "no email in JSON response"; return false; } return !email->empty(); } } // namespace wallet } // namespace autofill