| // Copyright 2014 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 "google_apis/gaia/merge_session_helper.h" |
| |
| #include <vector> |
| |
| #include "base/json/json_reader.h" |
| #include "base/stl_util.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/time/time.h" |
| #include "base/values.h" |
| #include "google_apis/gaia/gaia_auth_fetcher.h" |
| #include "google_apis/gaia/gaia_constants.h" |
| #include "google_apis/gaia/gaia_urls.h" |
| #include "google_apis/gaia/oauth2_token_service.h" |
| #include "net/base/load_flags.h" |
| #include "net/http/http_status_code.h" |
| #include "net/url_request/url_fetcher.h" |
| #include "net/url_request/url_fetcher_delegate.h" |
| |
| MergeSessionHelper::ExternalCcResultFetcher::ExternalCcResultFetcher( |
| MergeSessionHelper* helper) : helper_(helper) { |
| DCHECK(helper_); |
| } |
| |
| MergeSessionHelper::ExternalCcResultFetcher::~ExternalCcResultFetcher() { |
| CleanupTransientState(); |
| } |
| |
| std::string MergeSessionHelper::ExternalCcResultFetcher::GetExternalCcResult() { |
| std::vector<std::string> results; |
| for (ResultMap::const_iterator it = results_.begin(); it != results_.end(); |
| ++it) { |
| results.push_back(it->first + ":" + it->second); |
| } |
| return JoinString(results, ","); |
| } |
| |
| void MergeSessionHelper::ExternalCcResultFetcher::Start() { |
| CleanupTransientState(); |
| results_.clear(); |
| gaia_auth_fetcher_.reset( |
| new GaiaAuthFetcher(this, helper_->source_, |
| helper_->request_context())); |
| gaia_auth_fetcher_->StartGetCheckConnectionInfo(); |
| |
| // Some fetches may timeout. Start a timer to decide when the result fetcher |
| // has waited long enough. |
| // TODO(rogerta): I have no idea how long to wait before timing out. |
| // Gaia folks say this should take no more than 2 second even in mobile. |
| // This will need to be tweaked. |
| timer_.Start(FROM_HERE, base::TimeDelta::FromSeconds(5), |
| this, &MergeSessionHelper::ExternalCcResultFetcher::Timeout); |
| } |
| |
| bool MergeSessionHelper::ExternalCcResultFetcher::IsRunning() { |
| return gaia_auth_fetcher_ || fetchers_.size() > 0u; |
| } |
| |
| void MergeSessionHelper::ExternalCcResultFetcher::TimeoutForTests() { |
| Timeout(); |
| } |
| |
| void |
| MergeSessionHelper::ExternalCcResultFetcher::OnGetCheckConnectionInfoSuccess( |
| const std::string& data) { |
| scoped_ptr<base::Value> value(base::JSONReader::Read(data)); |
| const base::ListValue* list; |
| if (!value || !value->GetAsList(&list)) { |
| CleanupTransientState(); |
| FireGetCheckConnectionInfoCompleted(false); |
| return; |
| } |
| |
| // If there is nothing to check, terminate immediately. |
| if (list->GetSize() == 0) { |
| CleanupTransientState(); |
| FireGetCheckConnectionInfoCompleted(true); |
| return; |
| } |
| |
| // Start a fetcher for each connection URL that needs to be checked. |
| for (size_t i = 0; i < list->GetSize(); ++i) { |
| const base::DictionaryValue* dict; |
| if (list->GetDictionary(i, &dict)) { |
| std::string token; |
| std::string url; |
| if (dict->GetString("carryBackToken", &token) && |
| dict->GetString("url", &url)) { |
| results_[token] = "null"; |
| net::URLFetcher* fetcher = CreateFetcher(GURL(url)); |
| fetchers_[fetcher->GetOriginalURL()] = std::make_pair(token, fetcher); |
| fetcher->Start(); |
| } |
| } |
| } |
| } |
| |
| void |
| MergeSessionHelper::ExternalCcResultFetcher::OnGetCheckConnectionInfoError( |
| const GoogleServiceAuthError& error) { |
| CleanupTransientState(); |
| FireGetCheckConnectionInfoCompleted(false); |
| } |
| |
| net::URLFetcher* MergeSessionHelper::ExternalCcResultFetcher::CreateFetcher( |
| const GURL& url) { |
| net::URLFetcher* fetcher = net::URLFetcher::Create( |
| 0, |
| url, |
| net::URLFetcher::GET, |
| this); |
| fetcher->SetRequestContext(helper_->request_context()); |
| fetcher->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES | |
| net::LOAD_DO_NOT_SAVE_COOKIES); |
| |
| // Fetchers are sometimes cancelled because a network change was detected, |
| // especially at startup and after sign-in on ChromeOS. |
| fetcher->SetAutomaticallyRetryOnNetworkChanges(1); |
| return fetcher; |
| } |
| |
| void MergeSessionHelper::ExternalCcResultFetcher::OnURLFetchComplete( |
| const net::URLFetcher* source) { |
| const GURL& url = source->GetOriginalURL(); |
| const net::URLRequestStatus& status = source->GetStatus(); |
| int response_code = source->GetResponseCode(); |
| if (status.is_success() && response_code == net::HTTP_OK && |
| fetchers_.count(url) > 0) { |
| std::string data; |
| source->GetResponseAsString(&data); |
| // Only up to the first 16 characters of the response are important to GAIA. |
| // Truncate if needed to keep amount data sent back to GAIA down. |
| if (data.size() > 16) |
| data.resize(16); |
| results_[fetchers_[url].first] = data; |
| |
| // Clean up tracking of this fetcher. The rest will be cleaned up after |
| // the timer expires in CleanupTransientState(). |
| DCHECK_EQ(source, fetchers_[url].second); |
| fetchers_.erase(url); |
| delete source; |
| |
| // If all expected responses have been received, cancel the timer and |
| // report the result. |
| if (fetchers_.empty()) { |
| CleanupTransientState(); |
| FireGetCheckConnectionInfoCompleted(true); |
| } |
| } |
| } |
| |
| void MergeSessionHelper::ExternalCcResultFetcher::Timeout() { |
| CleanupTransientState(); |
| FireGetCheckConnectionInfoCompleted(false); |
| } |
| |
| void MergeSessionHelper::ExternalCcResultFetcher::CleanupTransientState() { |
| timer_.Stop(); |
| gaia_auth_fetcher_.reset(); |
| |
| for (URLToTokenAndFetcher::const_iterator it = fetchers_.begin(); |
| it != fetchers_.end(); ++it) { |
| delete it->second.second; |
| } |
| fetchers_.clear(); |
| } |
| |
| void MergeSessionHelper::ExternalCcResultFetcher:: |
| FireGetCheckConnectionInfoCompleted(bool succeeded) { |
| FOR_EACH_OBSERVER(Observer, helper_->observer_list_, |
| GetCheckConnectionInfoCompleted(succeeded)); |
| } |
| |
| MergeSessionHelper::MergeSessionHelper( |
| OAuth2TokenService* token_service, |
| const std::string& source, |
| net::URLRequestContextGetter* request_context, |
| Observer* observer) |
| : token_service_(token_service), |
| request_context_(request_context), |
| result_fetcher_(this), |
| source_(source) { |
| if (observer) |
| AddObserver(observer); |
| } |
| |
| MergeSessionHelper::~MergeSessionHelper() { |
| DCHECK(accounts_.empty()); |
| } |
| |
| void MergeSessionHelper::LogIn(const std::string& account_id) { |
| DCHECK(!account_id.empty()); |
| VLOG(1) << "MergeSessionHelper::LogIn: " << account_id; |
| accounts_.push_back(account_id); |
| if (accounts_.size() == 1) |
| StartFetching(); |
| } |
| |
| void MergeSessionHelper::AddObserver(Observer* observer) { |
| observer_list_.AddObserver(observer); |
| } |
| |
| void MergeSessionHelper::RemoveObserver(Observer* observer) { |
| observer_list_.RemoveObserver(observer); |
| } |
| |
| void MergeSessionHelper::CancelAll() { |
| VLOG(1) << "MergeSessionHelper::CancelAll"; |
| gaia_auth_fetcher_.reset(); |
| uber_token_fetcher_.reset(); |
| accounts_.clear(); |
| } |
| |
| void MergeSessionHelper::LogOut( |
| const std::string& account_id, |
| const std::vector<std::string>& accounts) { |
| DCHECK(!account_id.empty()); |
| VLOG(1) << "MergeSessionHelper::LogOut: " << account_id |
| << " accounts=" << accounts.size(); |
| LogOutInternal(account_id, accounts); |
| } |
| |
| void MergeSessionHelper::LogOutInternal( |
| const std::string& account_id, |
| const std::vector<std::string>& accounts) { |
| bool pending = !accounts_.empty(); |
| |
| if (pending) { |
| for (std::deque<std::string>::const_iterator it = accounts_.begin() + 1; |
| it != accounts_.end(); it++) { |
| if (!it->empty() && |
| (std::find(accounts.begin(), accounts.end(), *it) == accounts.end() || |
| *it == account_id)) { |
| // We have a pending log in request for an account followed by |
| // a signout. |
| GoogleServiceAuthError error(GoogleServiceAuthError::REQUEST_CANCELED); |
| SignalComplete(*it, error); |
| } |
| } |
| |
| // Remove every thing in the work list besides the one that is running. |
| accounts_.resize(1); |
| } |
| |
| // Signal a logout to be the next thing to do unless the pending |
| // action is already a logout. |
| if (!pending || !accounts_.front().empty()) |
| accounts_.push_back(""); |
| |
| for (std::vector<std::string>::const_iterator it = accounts.begin(); |
| it != accounts.end(); it++) { |
| if (*it != account_id) { |
| DCHECK(!it->empty()); |
| accounts_.push_back(*it); |
| } |
| } |
| |
| if (!pending) |
| StartLogOutUrlFetch(); |
| } |
| |
| void MergeSessionHelper::LogOutAllAccounts() { |
| VLOG(1) << "MergeSessionHelper::LogOutAllAccounts"; |
| LogOutInternal("", std::vector<std::string>()); |
| } |
| |
| void MergeSessionHelper::SignalComplete( |
| const std::string& account_id, |
| const GoogleServiceAuthError& error) { |
| // Its possible for the observer to delete |this| object. Don't access |
| // access any members after this calling the observer. This method should |
| // be the last call in any other method. |
| FOR_EACH_OBSERVER(Observer, observer_list_, |
| MergeSessionCompleted(account_id, error)); |
| } |
| |
| void MergeSessionHelper::StartFetchingExternalCcResult() { |
| result_fetcher_.Start(); |
| } |
| |
| bool MergeSessionHelper::StillFetchingExternalCcResult() { |
| return result_fetcher_.IsRunning(); |
| } |
| |
| void MergeSessionHelper::StartLogOutUrlFetch() { |
| DCHECK(accounts_.front().empty()); |
| VLOG(1) << "MergeSessionHelper::StartLogOutUrlFetch"; |
| GURL logout_url(GaiaUrls::GetInstance()->service_logout_url().Resolve( |
| base::StringPrintf("?source=%s", source_.c_str()))); |
| net::URLFetcher* fetcher = |
| net::URLFetcher::Create(logout_url, net::URLFetcher::GET, this); |
| fetcher->SetRequestContext(request_context_); |
| fetcher->Start(); |
| } |
| |
| void MergeSessionHelper::OnUbertokenSuccess(const std::string& uber_token) { |
| VLOG(1) << "MergeSessionHelper::OnUbertokenSuccess" |
| << " account=" << accounts_.front(); |
| gaia_auth_fetcher_.reset(new GaiaAuthFetcher(this, |
| source_, |
| request_context_)); |
| |
| // It's possible that not all external checks have completed. |
| // GetExternalCcResult() returns results for those that have. |
| gaia_auth_fetcher_->StartMergeSession(uber_token, |
| result_fetcher_.GetExternalCcResult()); |
| } |
| |
| void MergeSessionHelper::OnUbertokenFailure( |
| const GoogleServiceAuthError& error) { |
| VLOG(1) << "Failed to retrieve ubertoken" |
| << " account=" << accounts_.front() |
| << " error=" << error.ToString(); |
| const std::string account_id = accounts_.front(); |
| HandleNextAccount(); |
| SignalComplete(account_id, error); |
| } |
| |
| void MergeSessionHelper::OnMergeSessionSuccess(const std::string& data) { |
| VLOG(1) << "MergeSession successful account=" << accounts_.front(); |
| const std::string account_id = accounts_.front(); |
| HandleNextAccount(); |
| SignalComplete(account_id, GoogleServiceAuthError::AuthErrorNone()); |
| } |
| |
| void MergeSessionHelper::OnMergeSessionFailure( |
| const GoogleServiceAuthError& error) { |
| VLOG(1) << "Failed MergeSession" |
| << " account=" << accounts_.front() |
| << " error=" << error.ToString(); |
| const std::string account_id = accounts_.front(); |
| HandleNextAccount(); |
| SignalComplete(account_id, error); |
| } |
| |
| void MergeSessionHelper::StartFetching() { |
| VLOG(1) << "MergeSessionHelper::StartFetching account_id=" |
| << accounts_.front(); |
| uber_token_fetcher_.reset(new UbertokenFetcher(token_service_, |
| this, |
| source_, |
| request_context_)); |
| uber_token_fetcher_->StartFetchingToken(accounts_.front()); |
| } |
| |
| void MergeSessionHelper::OnURLFetchComplete(const net::URLFetcher* source) { |
| DCHECK(accounts_.front().empty()); |
| VLOG(1) << "MergeSessionHelper::OnURLFetchComplete"; |
| HandleNextAccount(); |
| } |
| |
| void MergeSessionHelper::HandleNextAccount() { |
| VLOG(1) << "MergeSessionHelper::HandleNextAccount"; |
| accounts_.pop_front(); |
| gaia_auth_fetcher_.reset(); |
| if (accounts_.empty()) { |
| VLOG(1) << "MergeSessionHelper::HandleNextAccount: no more"; |
| uber_token_fetcher_.reset(); |
| } else { |
| if (accounts_.front().empty()) { |
| StartLogOutUrlFetch(); |
| } else { |
| StartFetching(); |
| } |
| } |
| } |