1 // Copyright 2014 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/merge_session_helper.h" 6 7 #include <vector> 8 9 #include "base/json/json_reader.h" 10 #include "base/stl_util.h" 11 #include "base/strings/string_util.h" 12 #include "base/strings/stringprintf.h" 13 #include "base/time/time.h" 14 #include "base/values.h" 15 #include "google_apis/gaia/gaia_auth_fetcher.h" 16 #include "google_apis/gaia/gaia_constants.h" 17 #include "google_apis/gaia/gaia_urls.h" 18 #include "google_apis/gaia/oauth2_token_service.h" 19 #include "net/base/load_flags.h" 20 #include "net/http/http_status_code.h" 21 #include "net/url_request/url_fetcher.h" 22 #include "net/url_request/url_fetcher_delegate.h" 23 24 MergeSessionHelper::ExternalCcResultFetcher::ExternalCcResultFetcher( 25 MergeSessionHelper* helper) : helper_(helper) { 26 DCHECK(helper_); 27 } 28 29 MergeSessionHelper::ExternalCcResultFetcher::~ExternalCcResultFetcher() { 30 CleanupTransientState(); 31 } 32 33 std::string MergeSessionHelper::ExternalCcResultFetcher::GetExternalCcResult() { 34 std::vector<std::string> results; 35 for (ResultMap::const_iterator it = results_.begin(); it != results_.end(); 36 ++it) { 37 results.push_back(it->first + ":" + it->second); 38 } 39 return JoinString(results, ","); 40 } 41 42 void MergeSessionHelper::ExternalCcResultFetcher::Start() { 43 CleanupTransientState(); 44 results_.clear(); 45 gaia_auth_fetcher_.reset( 46 new GaiaAuthFetcher(this, GaiaConstants::kChromeSource, 47 helper_->request_context())); 48 gaia_auth_fetcher_->StartGetCheckConnectionInfo(); 49 } 50 51 bool MergeSessionHelper::ExternalCcResultFetcher::IsRunning() { 52 return gaia_auth_fetcher_ || fetchers_.size() > 0u; 53 } 54 55 void MergeSessionHelper::ExternalCcResultFetcher::TimeoutForTests() { 56 Timeout(); 57 } 58 59 void 60 MergeSessionHelper::ExternalCcResultFetcher::OnGetCheckConnectionInfoSuccess( 61 const std::string& data) { 62 scoped_ptr<base::Value> value(base::JSONReader::Read(data)); 63 const base::ListValue* list; 64 if (!value || !value->GetAsList(&list)) 65 return; 66 67 // Start a fetcher for each connection URL that needs to be checked. 68 for (size_t i = 0; i < list->GetSize(); ++i) { 69 const base::DictionaryValue* dict; 70 if (list->GetDictionary(i, &dict)) { 71 std::string token; 72 std::string url; 73 if (dict->GetString("carryBackToken", &token) && 74 dict->GetString("url", &url)) { 75 results_[token] = "null"; 76 net::URLFetcher* fetcher = CreateFetcher(GURL(url)); 77 fetchers_[fetcher->GetOriginalURL()] = std::make_pair(token, fetcher); 78 fetcher->Start(); 79 } 80 } 81 } 82 83 // Some fetches may timeout. Start a timer to decide when the result fetcher 84 // has waited long enough. 85 // TODO(rogerta): I have no idea how long to wait before timing out. 86 // Gaia folks say this should take no more than 2 second even in mobile. 87 // This will need to be tweaked. 88 timer_.Start(FROM_HERE, base::TimeDelta::FromSeconds(5), 89 this, &MergeSessionHelper::ExternalCcResultFetcher::Timeout); 90 } 91 92 net::URLFetcher* MergeSessionHelper::ExternalCcResultFetcher::CreateFetcher( 93 const GURL& url) { 94 net::URLFetcher* fetcher = net::URLFetcher::Create( 95 0, 96 url, 97 net::URLFetcher::GET, 98 this); 99 fetcher->SetRequestContext(helper_->request_context()); 100 fetcher->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES | 101 net::LOAD_DO_NOT_SAVE_COOKIES); 102 103 // Fetchers are sometimes cancelled because a network change was detected, 104 // especially at startup and after sign-in on ChromeOS. 105 fetcher->SetAutomaticallyRetryOnNetworkChanges(1); 106 return fetcher; 107 } 108 109 void MergeSessionHelper::ExternalCcResultFetcher::OnURLFetchComplete( 110 const net::URLFetcher* source) { 111 const GURL& url = source->GetOriginalURL(); 112 const net::URLRequestStatus& status = source->GetStatus(); 113 int response_code = source->GetResponseCode(); 114 if (status.is_success() && response_code == net::HTTP_OK && 115 fetchers_.count(url) > 0) { 116 std::string data; 117 source->GetResponseAsString(&data); 118 // Only up to the first 16 characters of the response are important to GAIA. 119 // Truncate if needed to keep amount data sent back to GAIA down. 120 if (data.size() > 16) 121 data.resize(16); 122 results_[fetchers_[url].first] = data; 123 124 // Clean up tracking of this fetcher. The rest will be cleaned up after 125 // the timer expires in CleanupTransientState(). 126 DCHECK_EQ(source, fetchers_[url].second); 127 fetchers_.erase(url); 128 delete source; 129 130 // If all expected responses have been received, cancel the timer and 131 // report the result. 132 if (fetchers_.empty()) { 133 timer_.Stop(); 134 CleanupTransientState(); 135 } 136 } 137 } 138 139 void MergeSessionHelper::ExternalCcResultFetcher::Timeout() { 140 CleanupTransientState(); 141 } 142 143 void MergeSessionHelper::ExternalCcResultFetcher::CleanupTransientState() { 144 gaia_auth_fetcher_.reset(); 145 146 for (URLToTokenAndFetcher::const_iterator it = fetchers_.begin(); 147 it != fetchers_.end(); ++it) { 148 delete it->second.second; 149 } 150 fetchers_.clear(); 151 } 152 153 MergeSessionHelper::MergeSessionHelper( 154 OAuth2TokenService* token_service, 155 net::URLRequestContextGetter* request_context, 156 Observer* observer) 157 : token_service_(token_service), 158 request_context_(request_context), 159 result_fetcher_(this) { 160 if (observer) 161 AddObserver(observer); 162 } 163 164 MergeSessionHelper::~MergeSessionHelper() { 165 DCHECK(accounts_.empty()); 166 } 167 168 void MergeSessionHelper::LogIn(const std::string& account_id) { 169 DCHECK(!account_id.empty()); 170 VLOG(1) << "MergeSessionHelper::LogIn: " << account_id; 171 accounts_.push_back(account_id); 172 if (accounts_.size() == 1) 173 StartFetching(); 174 } 175 176 void MergeSessionHelper::AddObserver(Observer* observer) { 177 observer_list_.AddObserver(observer); 178 } 179 180 void MergeSessionHelper::RemoveObserver(Observer* observer) { 181 observer_list_.RemoveObserver(observer); 182 } 183 184 void MergeSessionHelper::CancelAll() { 185 VLOG(1) << "MergeSessionHelper::CancelAll"; 186 gaia_auth_fetcher_.reset(); 187 uber_token_fetcher_.reset(); 188 accounts_.clear(); 189 } 190 191 void MergeSessionHelper::LogOut( 192 const std::string& account_id, 193 const std::vector<std::string>& accounts) { 194 DCHECK(!account_id.empty()); 195 VLOG(1) << "MergeSessionHelper::LogOut: " << account_id 196 << " accounts=" << accounts.size(); 197 LogOutInternal(account_id, accounts); 198 } 199 200 void MergeSessionHelper::LogOutInternal( 201 const std::string& account_id, 202 const std::vector<std::string>& accounts) { 203 bool pending = !accounts_.empty(); 204 205 if (pending) { 206 for (std::deque<std::string>::const_iterator it = accounts_.begin() + 1; 207 it != accounts_.end(); it++) { 208 if (!it->empty() && 209 (std::find(accounts.begin(), accounts.end(), *it) == accounts.end() || 210 *it == account_id)) { 211 // We have a pending log in request for an account followed by 212 // a signout. 213 GoogleServiceAuthError error(GoogleServiceAuthError::REQUEST_CANCELED); 214 SignalComplete(*it, error); 215 } 216 } 217 218 // Remove every thing in the work list besides the one that is running. 219 accounts_.resize(1); 220 } 221 222 // Signal a logout to be the next thing to do unless the pending 223 // action is already a logout. 224 if (!pending || !accounts_.front().empty()) 225 accounts_.push_back(""); 226 227 for (std::vector<std::string>::const_iterator it = accounts.begin(); 228 it != accounts.end(); it++) { 229 if (*it != account_id) { 230 DCHECK(!it->empty()); 231 accounts_.push_back(*it); 232 } 233 } 234 235 if (!pending) 236 StartLogOutUrlFetch(); 237 } 238 239 void MergeSessionHelper::LogOutAllAccounts() { 240 VLOG(1) << "MergeSessionHelper::LogOutAllAccounts"; 241 LogOutInternal("", std::vector<std::string>()); 242 } 243 244 void MergeSessionHelper::SignalComplete( 245 const std::string& account_id, 246 const GoogleServiceAuthError& error) { 247 // Its possible for the observer to delete |this| object. Don't access 248 // access any members after this calling the observer. This method should 249 // be the last call in any other method. 250 FOR_EACH_OBSERVER(Observer, observer_list_, 251 MergeSessionCompleted(account_id, error)); 252 } 253 254 void MergeSessionHelper::StartFetchingExternalCcResult() { 255 result_fetcher_.Start(); 256 } 257 258 bool MergeSessionHelper::StillFetchingExternalCcResult() { 259 return result_fetcher_.IsRunning(); 260 } 261 262 void MergeSessionHelper::StartLogOutUrlFetch() { 263 DCHECK(accounts_.front().empty()); 264 VLOG(1) << "MergeSessionHelper::StartLogOutUrlFetch"; 265 GURL logout_url(GaiaUrls::GetInstance()->service_logout_url()); 266 net::URLFetcher* fetcher = 267 net::URLFetcher::Create(logout_url, net::URLFetcher::GET, this); 268 fetcher->SetRequestContext(request_context_); 269 fetcher->Start(); 270 } 271 272 void MergeSessionHelper::OnUbertokenSuccess(const std::string& uber_token) { 273 VLOG(1) << "MergeSessionHelper::OnUbertokenSuccess" 274 << " account=" << accounts_.front(); 275 gaia_auth_fetcher_.reset(new GaiaAuthFetcher(this, 276 GaiaConstants::kChromeSource, 277 request_context_)); 278 279 // It's possible that not all external checks have completed. 280 // GetExternalCcResult() returns results for those that have. 281 gaia_auth_fetcher_->StartMergeSession(uber_token, 282 result_fetcher_.GetExternalCcResult()); 283 } 284 285 void MergeSessionHelper::OnUbertokenFailure( 286 const GoogleServiceAuthError& error) { 287 VLOG(1) << "Failed to retrieve ubertoken" 288 << " account=" << accounts_.front() 289 << " error=" << error.ToString(); 290 const std::string account_id = accounts_.front(); 291 HandleNextAccount(); 292 SignalComplete(account_id, error); 293 } 294 295 void MergeSessionHelper::OnMergeSessionSuccess(const std::string& data) { 296 VLOG(1) << "MergeSession successful account=" << accounts_.front(); 297 const std::string account_id = accounts_.front(); 298 HandleNextAccount(); 299 SignalComplete(account_id, GoogleServiceAuthError::AuthErrorNone()); 300 } 301 302 void MergeSessionHelper::OnMergeSessionFailure( 303 const GoogleServiceAuthError& error) { 304 VLOG(1) << "Failed MergeSession" 305 << " account=" << accounts_.front() 306 << " error=" << error.ToString(); 307 const std::string account_id = accounts_.front(); 308 HandleNextAccount(); 309 SignalComplete(account_id, error); 310 } 311 312 void MergeSessionHelper::StartFetching() { 313 VLOG(1) << "MergeSessionHelper::StartFetching account_id=" 314 << accounts_.front(); 315 uber_token_fetcher_.reset(new UbertokenFetcher(token_service_, 316 this, 317 request_context_)); 318 uber_token_fetcher_->StartFetchingToken(accounts_.front()); 319 } 320 321 void MergeSessionHelper::OnURLFetchComplete(const net::URLFetcher* source) { 322 DCHECK(accounts_.front().empty()); 323 VLOG(1) << "MergeSessionHelper::OnURLFetchComplete"; 324 HandleNextAccount(); 325 } 326 327 void MergeSessionHelper::HandleNextAccount() { 328 VLOG(1) << "MergeSessionHelper::HandleNextAccount"; 329 accounts_.pop_front(); 330 gaia_auth_fetcher_.reset(); 331 if (accounts_.empty()) { 332 VLOG(1) << "MergeSessionHelper::HandleNextAccount: no more"; 333 uber_token_fetcher_.reset(); 334 } else { 335 if (accounts_.front().empty()) { 336 StartLogOutUrlFetch(); 337 } else { 338 StartFetching(); 339 } 340 } 341 } 342