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
ExternalCcResultFetcher(MergeSessionHelper * helper)24 MergeSessionHelper::ExternalCcResultFetcher::ExternalCcResultFetcher(
25 MergeSessionHelper* helper) : helper_(helper) {
26 DCHECK(helper_);
27 }
28
~ExternalCcResultFetcher()29 MergeSessionHelper::ExternalCcResultFetcher::~ExternalCcResultFetcher() {
30 CleanupTransientState();
31 }
32
GetExternalCcResult()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
Start()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
IsRunning()51 bool MergeSessionHelper::ExternalCcResultFetcher::IsRunning() {
52 return gaia_auth_fetcher_ || fetchers_.size() > 0u;
53 }
54
TimeoutForTests()55 void MergeSessionHelper::ExternalCcResultFetcher::TimeoutForTests() {
56 Timeout();
57 }
58
59 void
OnGetCheckConnectionInfoSuccess(const std::string & data)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
CreateFetcher(const GURL & url)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
OnURLFetchComplete(const net::URLFetcher * source)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
Timeout()139 void MergeSessionHelper::ExternalCcResultFetcher::Timeout() {
140 CleanupTransientState();
141 }
142
CleanupTransientState()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
MergeSessionHelper(OAuth2TokenService * token_service,net::URLRequestContextGetter * request_context,Observer * observer)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
~MergeSessionHelper()164 MergeSessionHelper::~MergeSessionHelper() {
165 DCHECK(accounts_.empty());
166 }
167
LogIn(const std::string & account_id)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
AddObserver(Observer * observer)176 void MergeSessionHelper::AddObserver(Observer* observer) {
177 observer_list_.AddObserver(observer);
178 }
179
RemoveObserver(Observer * observer)180 void MergeSessionHelper::RemoveObserver(Observer* observer) {
181 observer_list_.RemoveObserver(observer);
182 }
183
CancelAll()184 void MergeSessionHelper::CancelAll() {
185 VLOG(1) << "MergeSessionHelper::CancelAll";
186 gaia_auth_fetcher_.reset();
187 uber_token_fetcher_.reset();
188 accounts_.clear();
189 }
190
LogOut(const std::string & account_id,const std::vector<std::string> & accounts)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
LogOutInternal(const std::string & account_id,const std::vector<std::string> & accounts)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
LogOutAllAccounts()239 void MergeSessionHelper::LogOutAllAccounts() {
240 VLOG(1) << "MergeSessionHelper::LogOutAllAccounts";
241 LogOutInternal("", std::vector<std::string>());
242 }
243
SignalComplete(const std::string & account_id,const GoogleServiceAuthError & error)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
StartFetchingExternalCcResult()254 void MergeSessionHelper::StartFetchingExternalCcResult() {
255 result_fetcher_.Start();
256 }
257
StillFetchingExternalCcResult()258 bool MergeSessionHelper::StillFetchingExternalCcResult() {
259 return result_fetcher_.IsRunning();
260 }
261
StartLogOutUrlFetch()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
OnUbertokenSuccess(const std::string & uber_token)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
OnUbertokenFailure(const GoogleServiceAuthError & error)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
OnMergeSessionSuccess(const std::string & data)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
OnMergeSessionFailure(const GoogleServiceAuthError & error)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
StartFetching()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
OnURLFetchComplete(const net::URLFetcher * source)321 void MergeSessionHelper::OnURLFetchComplete(const net::URLFetcher* source) {
322 DCHECK(accounts_.front().empty());
323 VLOG(1) << "MergeSessionHelper::OnURLFetchComplete";
324 HandleNextAccount();
325 }
326
HandleNextAccount()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