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.
5 #include "google_apis/gaia/merge_session_helper.h"
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"
24 MergeSessionHelper::ExternalCcResultFetcher::ExternalCcResultFetcher(
25 MergeSessionHelper
* helper
) : helper_(helper
) {
29 MergeSessionHelper::ExternalCcResultFetcher::~ExternalCcResultFetcher() {
30 CleanupTransientState();
33 std::string
MergeSessionHelper::ExternalCcResultFetcher::GetExternalCcResult() {
34 std::vector
<std::string
> results
;
35 for (ResultMap::const_iterator it
= results_
.begin(); it
!= results_
.end();
37 results
.push_back(it
->first
+ ":" + it
->second
);
39 return JoinString(results
, ",");
42 void MergeSessionHelper::ExternalCcResultFetcher::Start() {
43 CleanupTransientState();
45 gaia_auth_fetcher_
.reset(
46 new GaiaAuthFetcher(this, GaiaConstants::kChromeSource
,
47 helper_
->request_context()));
48 gaia_auth_fetcher_
->StartGetCheckConnectionInfo();
51 bool MergeSessionHelper::ExternalCcResultFetcher::IsRunning() {
52 return gaia_auth_fetcher_
|| fetchers_
.size() > 0u;
55 void MergeSessionHelper::ExternalCcResultFetcher::TimeoutForTests() {
60 MergeSessionHelper::ExternalCcResultFetcher::OnGetCheckConnectionInfoSuccess(
61 const std::string
& data
) {
62 base::Value
* value
= base::JSONReader::Read(data
);
63 const base::ListValue
* list
;
64 if (!value
|| !value
->GetAsList(&list
))
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
)) {
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
);
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
);
92 net::URLFetcher
* MergeSessionHelper::ExternalCcResultFetcher::CreateFetcher(
94 net::URLFetcher
* fetcher
= net::URLFetcher::Create(
99 fetcher
->SetRequestContext(helper_
->request_context());
100 fetcher
->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES
|
101 net::LOAD_DO_NOT_SAVE_COOKIES
);
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);
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) {
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)
122 results_
[fetchers_
[url
].first
] = data
;
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
);
130 // If all expected responses have been received, cancel the timer and
131 // report the result.
132 if (fetchers_
.empty()) {
134 CleanupTransientState();
139 void MergeSessionHelper::ExternalCcResultFetcher::Timeout() {
140 CleanupTransientState();
143 void MergeSessionHelper::ExternalCcResultFetcher::CleanupTransientState() {
144 gaia_auth_fetcher_
.reset();
146 for (URLToTokenAndFetcher::const_iterator it
= fetchers_
.begin();
147 it
!= fetchers_
.end(); ++it
) {
148 delete it
->second
.second
;
153 MergeSessionHelper::MergeSessionHelper(
154 OAuth2TokenService
* token_service
,
155 net::URLRequestContextGetter
* request_context
,
157 : token_service_(token_service
),
158 request_context_(request_context
),
159 result_fetcher_(this) {
161 AddObserver(observer
);
164 MergeSessionHelper::~MergeSessionHelper() {
165 DCHECK(accounts_
.empty());
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)
176 void MergeSessionHelper::AddObserver(Observer
* observer
) {
177 observer_list_
.AddObserver(observer
);
180 void MergeSessionHelper::RemoveObserver(Observer
* observer
) {
181 observer_list_
.RemoveObserver(observer
);
184 void MergeSessionHelper::CancelAll() {
185 VLOG(1) << "MergeSessionHelper::CancelAll";
186 gaia_auth_fetcher_
.reset();
187 uber_token_fetcher_
.reset();
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
);
200 void MergeSessionHelper::LogOutInternal(
201 const std::string
& account_id
,
202 const std::vector
<std::string
>& accounts
) {
203 bool pending
= !accounts_
.empty();
206 for (std::deque
<std::string
>::const_iterator it
= accounts_
.begin() + 1;
207 it
!= accounts_
.end(); it
++) {
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
213 GoogleServiceAuthError
error(GoogleServiceAuthError::REQUEST_CANCELED
);
214 SignalComplete(*it
, error
);
218 // Remove every thing in the work list besides the one that is running.
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("");
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
);
236 StartLogOutUrlFetch();
239 void MergeSessionHelper::LogOutAllAccounts() {
240 VLOG(1) << "MergeSessionHelper::LogOutAllAccounts";
241 LogOutInternal("", std::vector
<std::string
>());
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
));
254 void MergeSessionHelper::StartFetchingExternalCcResult() {
255 result_fetcher_
.Start();
258 bool MergeSessionHelper::StillFetchingExternalCcResult() {
259 return result_fetcher_
.IsRunning();
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_
);
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
,
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());
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();
292 SignalComplete(account_id
, error
);
295 void MergeSessionHelper::OnMergeSessionSuccess(const std::string
& data
) {
296 VLOG(1) << "MergeSession successful account=" << accounts_
.front();
297 const std::string account_id
= accounts_
.front();
299 SignalComplete(account_id
, GoogleServiceAuthError::AuthErrorNone());
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();
309 SignalComplete(account_id
, error
);
312 void MergeSessionHelper::StartFetching() {
313 VLOG(1) << "MergeSessionHelper::StartFetching account_id="
314 << accounts_
.front();
315 uber_token_fetcher_
.reset(new UbertokenFetcher(token_service_
,
318 uber_token_fetcher_
->StartFetchingToken(accounts_
.front());
321 void MergeSessionHelper::OnURLFetchComplete(const net::URLFetcher
* source
) {
322 DCHECK(accounts_
.front().empty());
323 VLOG(1) << "MergeSessionHelper::OnURLFetchComplete";
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();
335 if (accounts_
.front().empty()) {
336 StartLogOutUrlFetch();