Add abhijeet.k@samsung.com to AUTHORS list.
[chromium-blink-merge.git] / components / signin / core / browser / gaia_cookie_manager_service.cc
blob28f4c88c06ffa34f0cf3b72dfb0891e890d44e2a
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"
7 #include <vector>
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"
26 namespace {
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.
36 1000,
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.
43 0.2, // 20%
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.
50 -1,
52 // Don't use initial delay unless the last request was an error.
53 false,
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;
64 } // namespace
66 GaiaCookieManagerService::ExternalCcResultFetcher::ExternalCcResultFetcher(
67 GaiaCookieManagerService* helper)
68 : helper_(helper) {
69 DCHECK(helper_);
72 GaiaCookieManagerService::ExternalCcResultFetcher::~ExternalCcResultFetcher() {
73 CleanupTransientState();
76 std::string
77 GaiaCookieManagerService::ExternalCcResultFetcher::GetExternalCcResult() {
78 std::vector<std::string> results;
79 for (ResultMap::const_iterator it = results_.begin(); it != results_.end();
80 ++it) {
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();
90 results_.clear();
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() {
109 Timeout();
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);
119 return;
122 // If there is nothing to check, terminate immediately.
123 if (list->GetSize() == 0) {
124 CleanupTransientState();
125 FireGetCheckConnectionInfoCompleted(true);
126 return;
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)) {
133 std::string token;
134 std::string url;
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);
140 fetcher->Start();
146 void GaiaCookieManagerService::ExternalCcResultFetcher::
147 OnGetCheckConnectionInfoError(const GoogleServiceAuthError& error) {
148 CleanupTransientState();
149 FireGetCheckConnectionInfoCompleted(false);
152 net::URLFetcher*
153 GaiaCookieManagerService::ExternalCcResultFetcher::CreateFetcher(
154 const GURL& url) {
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);
164 return fetcher;
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) {
174 std::string data;
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)
179 data.resize(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);
186 delete source;
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() {
204 timer_.Stop();
205 gaia_auth_fetcher_.reset();
207 for (URLToTokenAndFetcher::const_iterator it = fetchers_.begin();
208 it != fetchers_.end(); ++it) {
209 delete it->second.second;
211 fetchers_.clear();
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),
233 source_(source) {
236 GaiaCookieManagerService::~GaiaCookieManagerService() {
237 CancelAll();
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)
247 StartFetching();
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();
262 accounts_.clear();
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();
280 if (pending) {
281 for (std::deque<std::string>::const_iterator it = accounts_.begin() + 1;
282 it != accounts_.end(); it++) {
283 if (!it->empty() &&
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
287 // a signout.
288 GoogleServiceAuthError error(GoogleServiceAuthError::REQUEST_CANCELED);
289 SignalComplete(*it, error);
293 // Remove every thing in the work list besides the one that is running.
294 accounts_.resize(1);
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);
310 if (!pending)
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());
342 fetcher->Start();
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();
359 HandleNextAccount();
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();
366 HandleNextAccount();
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);
385 return;
388 uber_token_ = std::string();
389 const std::string account_id = accounts_.front();
390 HandleNextAccount();
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";
419 HandleNextAccount();
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();
429 } else {
430 if (accounts_.front().empty()) {
431 StartLogOutUrlFetch();
432 } else {
433 StartFetching();