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 "components/signin/core/browser/gaia_cookie_manager_service.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 "components/signin/core/browser/signin_metrics.h"
16 #include "google_apis/gaia/gaia_auth_fetcher.h"
17 #include "google_apis/gaia/gaia_constants.h"
18 #include "google_apis/gaia/gaia_urls.h"
19 #include "google_apis/gaia/oauth2_token_service.h"
20 #include "net/base/load_flags.h"
21 #include "net/http/http_status_code.h"
22 #include "net/url_request/url_fetcher.h"
23 #include "net/url_request/url_fetcher_delegate.h"
28 // In case of an error while fetching using the GaiaAuthFetcher, retry with
29 // exponential backoff. Try up to 7 times within 15 minutes.
30 const net::BackoffEntry::Policy kBackoffPolicy
= {
31 // Number of initial errors (in sequence) to ignore before applying
32 // exponential back-off rules.
35 // Initial delay for exponential backoff in ms.
38 // Factor by which the waiting time will be multiplied.
41 // Fuzzing percentage. ex: 10% will spread requests randomly
42 // between 90%-100% of the calculated time.
45 // Maximum amount of time we are willing to delay our request in ms.
46 1000 * 60 * 60 * 4, // 15 minutes.
48 // Time to keep an entry from being discarded even when it
49 // has no significant state, -1 to never discard.
52 // Don't use initial delay unless the last request was an error.
56 const int kMaxGaiaAuthFetcherRetries
= 8;
58 bool IsTransientError(const GoogleServiceAuthError
& error
) {
59 return error
.state() == GoogleServiceAuthError::CONNECTION_FAILED
||
60 error
.state() == GoogleServiceAuthError::SERVICE_UNAVAILABLE
||
61 error
.state() == GoogleServiceAuthError::REQUEST_CANCELED
;
66 GaiaCookieManagerService::ExternalCcResultFetcher::ExternalCcResultFetcher(
67 GaiaCookieManagerService
* helper
)
72 GaiaCookieManagerService::ExternalCcResultFetcher::~ExternalCcResultFetcher() {
73 CleanupTransientState();
77 GaiaCookieManagerService::ExternalCcResultFetcher::GetExternalCcResult() {
78 std::vector
<std::string
> results
;
79 for (ResultMap::const_iterator it
= results_
.begin(); it
!= results_
.end();
81 results
.push_back(it
->first
+ ":" + it
->second
);
83 return JoinString(results
, ",");
86 void GaiaCookieManagerService::ExternalCcResultFetcher::Start() {
87 m_external_cc_result_start_time_
= base::Time::Now();
89 CleanupTransientState();
91 gaia_auth_fetcher_
.reset(
92 new GaiaAuthFetcher(this, helper_
->source_
, helper_
->request_context()));
93 gaia_auth_fetcher_
->StartGetCheckConnectionInfo();
95 // Some fetches may timeout. Start a timer to decide when the result fetcher
96 // has waited long enough.
97 // TODO(rogerta): I have no idea how long to wait before timing out.
98 // Gaia folks say this should take no more than 2 second even in mobile.
99 // This will need to be tweaked.
100 timer_
.Start(FROM_HERE
, base::TimeDelta::FromSeconds(5), this,
101 &GaiaCookieManagerService::ExternalCcResultFetcher::Timeout
);
104 bool GaiaCookieManagerService::ExternalCcResultFetcher::IsRunning() {
105 return gaia_auth_fetcher_
|| fetchers_
.size() > 0u;
108 void GaiaCookieManagerService::ExternalCcResultFetcher::TimeoutForTests() {
112 void GaiaCookieManagerService::ExternalCcResultFetcher::
113 OnGetCheckConnectionInfoSuccess(const std::string
& data
) {
114 scoped_ptr
<base::Value
> value(base::JSONReader::Read(data
));
115 const base::ListValue
* list
;
116 if (!value
|| !value
->GetAsList(&list
)) {
117 CleanupTransientState();
118 FireGetCheckConnectionInfoCompleted(false);
122 // If there is nothing to check, terminate immediately.
123 if (list
->GetSize() == 0) {
124 CleanupTransientState();
125 FireGetCheckConnectionInfoCompleted(true);
129 // Start a fetcher for each connection URL that needs to be checked.
130 for (size_t i
= 0; i
< list
->GetSize(); ++i
) {
131 const base::DictionaryValue
* dict
;
132 if (list
->GetDictionary(i
, &dict
)) {
135 if (dict
->GetString("carryBackToken", &token
) &&
136 dict
->GetString("url", &url
)) {
137 results_
[token
] = "null";
138 net::URLFetcher
* fetcher
= CreateFetcher(GURL(url
));
139 fetchers_
[fetcher
->GetOriginalURL()] = std::make_pair(token
, fetcher
);
146 void GaiaCookieManagerService::ExternalCcResultFetcher::
147 OnGetCheckConnectionInfoError(const GoogleServiceAuthError
& error
) {
148 CleanupTransientState();
149 FireGetCheckConnectionInfoCompleted(false);
153 GaiaCookieManagerService::ExternalCcResultFetcher::CreateFetcher(
155 net::URLFetcher
* fetcher
=
156 net::URLFetcher::Create(0, url
, net::URLFetcher::GET
, this);
157 fetcher
->SetRequestContext(helper_
->request_context());
158 fetcher
->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES
|
159 net::LOAD_DO_NOT_SAVE_COOKIES
);
161 // Fetchers are sometimes cancelled because a network change was detected,
162 // especially at startup and after sign-in on ChromeOS.
163 fetcher
->SetAutomaticallyRetryOnNetworkChanges(1);
167 void GaiaCookieManagerService::ExternalCcResultFetcher::OnURLFetchComplete(
168 const net::URLFetcher
* source
) {
169 const GURL
& url
= source
->GetOriginalURL();
170 const net::URLRequestStatus
& status
= source
->GetStatus();
171 int response_code
= source
->GetResponseCode();
172 if (status
.is_success() && response_code
== net::HTTP_OK
&&
173 fetchers_
.count(url
) > 0) {
175 source
->GetResponseAsString(&data
);
176 // Only up to the first 16 characters of the response are important to GAIA.
177 // Truncate if needed to keep amount data sent back to GAIA down.
178 if (data
.size() > 16)
180 results_
[fetchers_
[url
].first
] = data
;
182 // Clean up tracking of this fetcher. The rest will be cleaned up after
183 // the timer expires in CleanupTransientState().
184 DCHECK_EQ(source
, fetchers_
[url
].second
);
185 fetchers_
.erase(url
);
188 // If all expected responses have been received, cancel the timer and
189 // report the result.
190 if (fetchers_
.empty()) {
191 CleanupTransientState();
192 FireGetCheckConnectionInfoCompleted(true);
197 void GaiaCookieManagerService::ExternalCcResultFetcher::Timeout() {
198 CleanupTransientState();
199 FireGetCheckConnectionInfoCompleted(false);
202 void GaiaCookieManagerService::ExternalCcResultFetcher::
203 CleanupTransientState() {
205 gaia_auth_fetcher_
.reset();
207 for (URLToTokenAndFetcher::const_iterator it
= fetchers_
.begin();
208 it
!= fetchers_
.end(); ++it
) {
209 delete it
->second
.second
;
214 void GaiaCookieManagerService::ExternalCcResultFetcher::
215 FireGetCheckConnectionInfoCompleted(bool succeeded
) {
216 base::TimeDelta time_to_check_connections
=
217 base::Time::Now() - m_external_cc_result_start_time_
;
218 signin_metrics::LogExternalCcResultFetches(succeeded
,
219 time_to_check_connections
);
220 FOR_EACH_OBSERVER(Observer
, helper_
->observer_list_
,
221 GetCheckConnectionInfoCompleted(succeeded
));
224 GaiaCookieManagerService::GaiaCookieManagerService(
225 OAuth2TokenService
* token_service
,
226 const std::string
& source
,
227 SigninClient
* signin_client
)
228 : token_service_(token_service
),
229 signin_client_(signin_client
),
230 external_cc_result_fetcher_(this),
231 gaia_auth_fetcher_backoff_(&kBackoffPolicy
),
232 gaia_auth_fetcher_retries_(0),
236 GaiaCookieManagerService::~GaiaCookieManagerService() {
238 DCHECK(accounts_
.empty());
241 void GaiaCookieManagerService::AddAccountToCookie(
242 const std::string
& account_id
) {
243 DCHECK(!account_id
.empty());
244 VLOG(1) << "GaiaCookieManagerService::AddAccountToCookie: " << account_id
;
245 accounts_
.push_back(account_id
);
246 if (accounts_
.size() == 1)
250 void GaiaCookieManagerService::AddObserver(Observer
* observer
) {
251 observer_list_
.AddObserver(observer
);
254 void GaiaCookieManagerService::RemoveObserver(Observer
* observer
) {
255 observer_list_
.RemoveObserver(observer
);
258 void GaiaCookieManagerService::CancelAll() {
259 VLOG(1) << "GaiaCookieManagerService::CancelAll";
260 gaia_auth_fetcher_
.reset();
261 uber_token_fetcher_
.reset();
263 gaia_auth_fetcher_timer_
.Stop();
266 void GaiaCookieManagerService::LogOut(
267 const std::string
& account_id
,
268 const std::vector
<std::string
>& accounts
) {
269 DCHECK(!account_id
.empty());
270 VLOG(1) << "GaiaCookieManagerService::LogOut: " << account_id
271 << " accounts=" << accounts
.size();
272 LogOutInternal(account_id
, accounts
);
275 void GaiaCookieManagerService::LogOutInternal(
276 const std::string
& account_id
,
277 const std::vector
<std::string
>& accounts
) {
278 bool pending
= !accounts_
.empty();
281 for (std::deque
<std::string
>::const_iterator it
= accounts_
.begin() + 1;
282 it
!= accounts_
.end(); it
++) {
284 (std::find(accounts
.begin(), accounts
.end(), *it
) == accounts
.end() ||
285 *it
== account_id
)) {
286 // We have a pending log in request for an account followed by
288 GoogleServiceAuthError
error(GoogleServiceAuthError::REQUEST_CANCELED
);
289 SignalComplete(*it
, error
);
293 // Remove every thing in the work list besides the one that is running.
297 // Signal a logout to be the next thing to do unless the pending
298 // action is already a logout.
299 if (!pending
|| !accounts_
.front().empty())
300 accounts_
.push_back("");
302 for (std::vector
<std::string
>::const_iterator it
= accounts
.begin();
303 it
!= accounts
.end(); it
++) {
304 if (*it
!= account_id
) {
305 DCHECK(!it
->empty());
306 accounts_
.push_back(*it
);
311 StartLogOutUrlFetch();
314 void GaiaCookieManagerService::LogOutAllAccounts() {
315 VLOG(1) << "GaiaCookieManagerService::LogOutAllAccounts";
316 LogOutInternal("", std::vector
<std::string
>());
319 void GaiaCookieManagerService::SignalComplete(
320 const std::string
& account_id
,
321 const GoogleServiceAuthError
& error
) {
322 // Its possible for the observer to delete |this| object. Don't access
323 // access any members after this calling the observer. This method should
324 // be the last call in any other method.
325 FOR_EACH_OBSERVER(Observer
, observer_list_
,
326 OnAddAccountToCookieCompleted(account_id
, error
));
329 void GaiaCookieManagerService::StartFetchingExternalCcResult() {
330 if (!external_cc_result_fetcher_
.IsRunning())
331 external_cc_result_fetcher_
.Start();
334 void GaiaCookieManagerService::StartLogOutUrlFetch() {
335 DCHECK(accounts_
.front().empty());
336 VLOG(1) << "GaiaCookieManagerService::StartLogOutUrlFetch";
337 GURL
logout_url(GaiaUrls::GetInstance()->service_logout_url().Resolve(
338 base::StringPrintf("?source=%s", source_
.c_str())));
339 net::URLFetcher
* fetcher
=
340 net::URLFetcher::Create(logout_url
, net::URLFetcher::GET
, this);
341 fetcher
->SetRequestContext(signin_client_
->GetURLRequestContext());
345 void GaiaCookieManagerService::OnUbertokenSuccess(
346 const std::string
& uber_token
) {
347 VLOG(1) << "GaiaCookieManagerService::OnUbertokenSuccess"
348 << " account=" << accounts_
.front();
349 gaia_auth_fetcher_retries_
= 0;
350 uber_token_
= uber_token
;
351 StartFetchingMergeSession();
354 void GaiaCookieManagerService::OnUbertokenFailure(
355 const GoogleServiceAuthError
& error
) {
356 VLOG(1) << "Failed to retrieve ubertoken"
357 << " account=" << accounts_
.front() << " error=" << error
.ToString();
358 const std::string account_id
= accounts_
.front();
360 SignalComplete(account_id
, error
);
363 void GaiaCookieManagerService::OnMergeSessionSuccess(const std::string
& data
) {
364 VLOG(1) << "MergeSession successful account=" << accounts_
.front();
365 const std::string account_id
= accounts_
.front();
367 SignalComplete(account_id
, GoogleServiceAuthError::AuthErrorNone());
369 gaia_auth_fetcher_backoff_
.InformOfRequest(true);
370 uber_token_
= std::string();
373 void GaiaCookieManagerService::OnMergeSessionFailure(
374 const GoogleServiceAuthError
& error
) {
375 VLOG(1) << "Failed MergeSession"
376 << " account=" << accounts_
.front() << " error=" << error
.ToString()
377 << " on retry=" << gaia_auth_fetcher_retries_
;
379 if (++gaia_auth_fetcher_retries_
< kMaxGaiaAuthFetcherRetries
&&
380 IsTransientError(error
)) {
381 gaia_auth_fetcher_backoff_
.InformOfRequest(false);
382 gaia_auth_fetcher_timer_
.Start(
383 FROM_HERE
, gaia_auth_fetcher_backoff_
.GetTimeUntilRelease(), this,
384 &GaiaCookieManagerService::StartFetchingMergeSession
);
388 uber_token_
= std::string();
389 const std::string account_id
= accounts_
.front();
391 SignalComplete(account_id
, error
);
394 void GaiaCookieManagerService::StartFetching() {
395 VLOG(1) << "GaiaCookieManagerService::StartFetching account_id="
396 << accounts_
.front();
397 uber_token_fetcher_
.reset(
398 new UbertokenFetcher(token_service_
, this, source_
,
399 signin_client_
->GetURLRequestContext()));
400 uber_token_fetcher_
->StartFetchingToken(accounts_
.front());
403 void GaiaCookieManagerService::StartFetchingMergeSession() {
404 DCHECK(!uber_token_
.empty());
405 gaia_auth_fetcher_
.reset(
406 new GaiaAuthFetcher(this, source_
,
407 signin_client_
->GetURLRequestContext()));
409 // It's possible that not all external checks have completed.
410 // GetExternalCcResult() returns results for those that have.
411 gaia_auth_fetcher_
->StartMergeSession(uber_token_
,
412 external_cc_result_fetcher_
.GetExternalCcResult());
415 void GaiaCookieManagerService::OnURLFetchComplete(
416 const net::URLFetcher
* source
) {
417 DCHECK(accounts_
.front().empty());
418 VLOG(1) << "GaiaCookieManagerService::OnURLFetchComplete";
422 void GaiaCookieManagerService::HandleNextAccount() {
423 VLOG(1) << "GaiaCookieManagerService::HandleNextAccount";
424 accounts_
.pop_front();
425 gaia_auth_fetcher_
.reset();
426 if (accounts_
.empty()) {
427 VLOG(1) << "GaiaCookieManagerService::HandleNextAccount: no more";
428 uber_token_fetcher_
.reset();
430 if (accounts_
.front().empty()) {
431 StartLogOutUrlFetch();