Port Android relocation packer to chromium build
[chromium-blink-merge.git] / components / signin / core / browser / account_reconcilor.cc
blobb39062b6409714cd911dc065af8353293b0b9166
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/account_reconcilor.h"
7 #include <algorithm>
9 #include "base/bind.h"
10 #include "base/json/json_reader.h"
11 #include "base/logging.h"
12 #include "base/message_loop/message_loop.h"
13 #include "base/message_loop/message_loop_proxy.h"
14 #include "base/strings/string_number_conversions.h"
15 #include "components/signin/core/browser/profile_oauth2_token_service.h"
16 #include "components/signin/core/browser/signin_client.h"
17 #include "components/signin/core/browser/signin_metrics.h"
18 #include "components/signin/core/common/profile_management_switches.h"
19 #include "google_apis/gaia/gaia_auth_fetcher.h"
20 #include "google_apis/gaia/gaia_auth_util.h"
21 #include "google_apis/gaia/gaia_constants.h"
22 #include "google_apis/gaia/gaia_oauth_client.h"
23 #include "google_apis/gaia/gaia_urls.h"
24 #include "net/cookies/canonical_cookie.h"
27 namespace {
29 class EmailEqualToFunc : public std::equal_to<std::pair<std::string, bool> > {
30 public:
31 bool operator()(const std::pair<std::string, bool>& p1,
32 const std::pair<std::string, bool>& p2) const;
35 bool EmailEqualToFunc::operator()(
36 const std::pair<std::string, bool>& p1,
37 const std::pair<std::string, bool>& p2) const {
38 return p1.second == p2.second && gaia::AreEmailsSame(p1.first, p2.first);
41 class AreEmailsSameFunc : public std::equal_to<std::string> {
42 public:
43 bool operator()(const std::string& p1,
44 const std::string& p2) const;
47 bool AreEmailsSameFunc::operator()(
48 const std::string& p1,
49 const std::string& p2) const {
50 return gaia::AreEmailsSame(p1, p2);
53 } // namespace
56 AccountReconcilor::AccountReconcilor(ProfileOAuth2TokenService* token_service,
57 SigninManagerBase* signin_manager,
58 SigninClient* client)
59 : token_service_(token_service),
60 signin_manager_(signin_manager),
61 client_(client),
62 merge_session_helper_(token_service_,
63 GaiaConstants::kReconcilorSource,
64 client->GetURLRequestContext(),
65 NULL),
66 registered_with_token_service_(false),
67 registered_with_merge_session_helper_(false),
68 registered_with_content_settings_(false),
69 is_reconcile_started_(false),
70 first_execution_(true),
71 are_gaia_accounts_set_(false),
72 chrome_accounts_changed_(false) {
73 VLOG(1) << "AccountReconcilor::AccountReconcilor";
76 AccountReconcilor::~AccountReconcilor() {
77 VLOG(1) << "AccountReconcilor::~AccountReconcilor";
78 // Make sure shutdown was called first.
79 DCHECK(!registered_with_token_service_);
80 DCHECK(!registered_with_merge_session_helper_);
83 void AccountReconcilor::Initialize(bool start_reconcile_if_tokens_available) {
84 VLOG(1) << "AccountReconcilor::Initialize";
85 RegisterWithSigninManager();
87 // If this user is not signed in, the reconcilor should do nothing but
88 // wait for signin.
89 if (IsProfileConnected()) {
90 RegisterWithMergeSessionHelper();
91 RegisterForCookieChanges();
92 RegisterWithContentSettings();
93 RegisterWithTokenService();
95 // Start a reconcile if the tokens are already loaded.
96 if (start_reconcile_if_tokens_available &&
97 token_service_->GetAccounts().size() > 0) {
98 StartReconcile();
103 void AccountReconcilor::Shutdown() {
104 VLOG(1) << "AccountReconcilor::Shutdown";
105 merge_session_helper_.CancelAll();
106 gaia_fetcher_.reset();
107 get_gaia_accounts_callbacks_.clear();
108 UnregisterWithMergeSessionHelper();
109 UnregisterWithSigninManager();
110 UnregisterWithTokenService();
111 UnregisterForCookieChanges();
112 UnregisterWithContentSettings();
115 void AccountReconcilor::AddMergeSessionObserver(
116 MergeSessionHelper::Observer* observer) {
117 merge_session_helper_.AddObserver(observer);
120 void AccountReconcilor::RemoveMergeSessionObserver(
121 MergeSessionHelper::Observer* observer) {
122 merge_session_helper_.RemoveObserver(observer);
125 void AccountReconcilor::RegisterForCookieChanges() {
126 // First clear any existing registration to avoid DCHECKs that can otherwise
127 // go off in some embedders on reauth (e.g., ChromeSigninClient).
128 UnregisterForCookieChanges();
129 cookie_changed_subscription_ = client_->AddCookieChangedCallback(
130 GaiaUrls::GetInstance()->gaia_url(),
131 "LSID",
132 base::Bind(&AccountReconcilor::OnCookieChanged, base::Unretained(this)));
135 void AccountReconcilor::UnregisterForCookieChanges() {
136 cookie_changed_subscription_.reset();
139 void AccountReconcilor::RegisterWithSigninManager() {
140 signin_manager_->AddObserver(this);
143 void AccountReconcilor::UnregisterWithSigninManager() {
144 signin_manager_->RemoveObserver(this);
147 void AccountReconcilor::RegisterWithContentSettings() {
148 VLOG(1) << "AccountReconcilor::RegisterWithContentSettings";
149 // During re-auth, the reconcilor will get a callback about successful signin
150 // even when the profile is already connected. Avoid re-registering
151 // with the token service since this will DCHECK.
152 if (registered_with_content_settings_)
153 return;
155 client_->AddContentSettingsObserver(this);
156 registered_with_content_settings_ = true;
159 void AccountReconcilor::UnregisterWithContentSettings() {
160 VLOG(1) << "AccountReconcilor::UnregisterWithContentSettings";
161 if (!registered_with_content_settings_)
162 return;
164 client_->RemoveContentSettingsObserver(this);
165 registered_with_content_settings_ = false;
168 void AccountReconcilor::RegisterWithTokenService() {
169 VLOG(1) << "AccountReconcilor::RegisterWithTokenService";
170 // During re-auth, the reconcilor will get a callback about successful signin
171 // even when the profile is already connected. Avoid re-registering
172 // with the token service since this will DCHECK.
173 if (registered_with_token_service_)
174 return;
176 token_service_->AddObserver(this);
177 registered_with_token_service_ = true;
180 void AccountReconcilor::UnregisterWithTokenService() {
181 VLOG(1) << "AccountReconcilor::UnregisterWithTokenService";
182 if (!registered_with_token_service_)
183 return;
185 token_service_->RemoveObserver(this);
186 registered_with_token_service_ = false;
189 void AccountReconcilor::RegisterWithMergeSessionHelper() {
190 VLOG(1) << "AccountReconcilor::RegisterWithMergeSessionHelper";
191 // During re-auth, the reconcilor will get a callback about successful signin
192 // even when the profile is already connected. Avoid re-registering
193 // with the helper since this will DCHECK.
194 if (registered_with_merge_session_helper_)
195 return;
197 merge_session_helper_.AddObserver(this);
198 registered_with_merge_session_helper_ = true;
200 void AccountReconcilor::UnregisterWithMergeSessionHelper() {
201 VLOG(1) << "AccountReconcilor::UnregisterWithMergeSessionHelper";
202 if (!registered_with_merge_session_helper_)
203 return;
205 merge_session_helper_.RemoveObserver(this);
206 registered_with_merge_session_helper_ = false;
209 bool AccountReconcilor::IsProfileConnected() {
210 return signin_manager_->IsAuthenticated();
213 void AccountReconcilor::OnCookieChanged(const net::CanonicalCookie& cookie,
214 bool removed) {
215 DCHECK_EQ("LSID", cookie.Name());
216 DCHECK_EQ(GaiaUrls::GetInstance()->gaia_url().host(), cookie.Domain());
217 if (cookie.IsSecure() && cookie.IsHttpOnly()) {
218 VLOG(1) << "AccountReconcilor::OnCookieChanged: LSID changed";
220 // It is possible that O2RT is not available at this moment.
221 if (!token_service_->GetAccounts().size()) {
222 VLOG(1) << "AccountReconcilor::OnCookieChanged: cookie change is ingored"
223 "because O2RT is not available yet.";
224 return;
227 StartReconcile();
231 void AccountReconcilor::OnContentSettingChanged(
232 const ContentSettingsPattern& primary_pattern,
233 const ContentSettingsPattern& secondary_pattern,
234 ContentSettingsType content_type,
235 std::string resource_identifier) {
236 // If this is not a change to cookie settings, just ignore.
237 if (content_type != CONTENT_SETTINGS_TYPE_COOKIES)
238 return;
240 // If this does not affect GAIA, just ignore. If the primary pattern is
241 // invalid, then assume it could affect GAIA. The secondary pattern is
242 // not needed.
243 if (primary_pattern.IsValid() &&
244 !primary_pattern.Matches(GaiaUrls::GetInstance()->gaia_url())) {
245 return;
248 VLOG(1) << "AccountReconcilor::OnContentSettingChanged";
249 StartReconcile();
252 void AccountReconcilor::OnEndBatchChanges() {
253 VLOG(1) << "AccountReconcilor::OnEndBatchChanges";
254 // Remember that accounts have changed if a reconcile is already started.
255 chrome_accounts_changed_ = is_reconcile_started_;
256 StartReconcile();
259 void AccountReconcilor::GoogleSigninSucceeded(const std::string& account_id,
260 const std::string& username,
261 const std::string& password) {
262 VLOG(1) << "AccountReconcilor::GoogleSigninSucceeded: signed in";
263 RegisterWithMergeSessionHelper();
264 RegisterForCookieChanges();
265 RegisterWithContentSettings();
266 RegisterWithTokenService();
269 void AccountReconcilor::GoogleSignedOut(const std::string& account_id,
270 const std::string& username) {
271 VLOG(1) << "AccountReconcilor::GoogleSignedOut: signed out";
272 gaia_fetcher_.reset();
273 get_gaia_accounts_callbacks_.clear();
274 AbortReconcile();
275 UnregisterWithMergeSessionHelper();
276 UnregisterWithTokenService();
277 UnregisterForCookieChanges();
278 UnregisterWithContentSettings();
279 PerformLogoutAllAccountsAction();
282 void AccountReconcilor::PerformMergeAction(const std::string& account_id) {
283 if (!switches::IsEnableAccountConsistency()) {
284 MarkAccountAsAddedToCookie(account_id);
285 return;
287 VLOG(1) << "AccountReconcilor::PerformMergeAction: " << account_id;
288 merge_session_helper_.LogIn(account_id);
291 void AccountReconcilor::PerformLogoutAllAccountsAction() {
292 if (!switches::IsEnableAccountConsistency())
293 return;
294 VLOG(1) << "AccountReconcilor::PerformLogoutAllAccountsAction";
295 merge_session_helper_.LogOutAllAccounts();
298 void AccountReconcilor::StartReconcile() {
299 if (!IsProfileConnected() || !client_->AreSigninCookiesAllowed()) {
300 VLOG(1) << "AccountReconcilor::StartReconcile: !connected or no cookies";
301 return;
304 if (is_reconcile_started_ || get_gaia_accounts_callbacks_.size() > 0)
305 return;
307 is_reconcile_started_ = true;
308 m_reconcile_start_time_ = base::Time::Now();
310 // Reset state for validating gaia cookie.
311 are_gaia_accounts_set_ = false;
312 gaia_accounts_.clear();
314 // Reset state for validating oauth2 tokens.
315 primary_account_.clear();
316 chrome_accounts_.clear();
317 add_to_cookie_.clear();
318 ValidateAccountsFromTokenService();
320 // Start process by checking connections to external sites.
321 merge_session_helper_.StartFetchingExternalCcResult();
324 void AccountReconcilor::GetAccountsFromCookie(
325 GetAccountsFromCookieCallback callback) {
326 get_gaia_accounts_callbacks_.push_back(callback);
327 if (!gaia_fetcher_)
328 MayBeDoNextListAccounts();
331 void AccountReconcilor::OnListAccountsSuccess(const std::string& data) {
332 gaia_fetcher_.reset();
334 // Get account information from response data.
335 std::vector<std::pair<std::string, bool> > gaia_accounts;
336 bool valid_json = gaia::ParseListAccountsData(data, &gaia_accounts);
337 if (!valid_json) {
338 VLOG(1) << "AccountReconcilor::OnListAccountsSuccess: parsing error";
339 } else if (gaia_accounts.size() > 0) {
340 VLOG(1) << "AccountReconcilor::OnListAccountsSuccess: "
341 << "Gaia " << gaia_accounts.size() << " accounts, "
342 << "Primary is '" << gaia_accounts[0].first << "'";
343 } else {
344 VLOG(1) << "AccountReconcilor::OnListAccountsSuccess: No accounts";
347 // There must be at least one callback waiting for result.
348 DCHECK(!get_gaia_accounts_callbacks_.empty());
350 GoogleServiceAuthError error =
351 !valid_json ? GoogleServiceAuthError(
352 GoogleServiceAuthError::UNEXPECTED_SERVICE_RESPONSE)
353 : GoogleServiceAuthError::AuthErrorNone();
354 get_gaia_accounts_callbacks_.front().Run(error, gaia_accounts);
355 get_gaia_accounts_callbacks_.pop_front();
357 MayBeDoNextListAccounts();
360 void AccountReconcilor::OnListAccountsFailure(
361 const GoogleServiceAuthError& error) {
362 gaia_fetcher_.reset();
363 VLOG(1) << "AccountReconcilor::OnListAccountsFailure: " << error.ToString();
364 std::vector<std::pair<std::string, bool> > empty_accounts;
366 // There must be at least one callback waiting for result.
367 DCHECK(!get_gaia_accounts_callbacks_.empty());
369 get_gaia_accounts_callbacks_.front().Run(error, empty_accounts);
370 get_gaia_accounts_callbacks_.pop_front();
372 MayBeDoNextListAccounts();
375 void AccountReconcilor::MayBeDoNextListAccounts() {
376 if (!get_gaia_accounts_callbacks_.empty()) {
377 gaia_fetcher_.reset(new GaiaAuthFetcher(
378 this, GaiaConstants::kReconcilorSource,
379 client_->GetURLRequestContext()));
380 gaia_fetcher_->StartListAccounts();
384 void AccountReconcilor::ContinueReconcileActionAfterGetGaiaAccounts(
385 const GoogleServiceAuthError& error,
386 const std::vector<std::pair<std::string, bool> >& accounts) {
387 if (error.state() == GoogleServiceAuthError::NONE) {
388 gaia_accounts_ = accounts;
389 are_gaia_accounts_set_ = true;
390 FinishReconcile();
391 } else {
392 AbortReconcile();
396 void AccountReconcilor::ValidateAccountsFromTokenService() {
397 primary_account_ = signin_manager_->GetAuthenticatedAccountId();
398 DCHECK(!primary_account_.empty());
400 chrome_accounts_ = token_service_->GetAccounts();
402 VLOG(1) << "AccountReconcilor::ValidateAccountsFromTokenService: "
403 << "Chrome " << chrome_accounts_.size() << " accounts, "
404 << "Primary is '" << primary_account_ << "'";
407 void AccountReconcilor::OnNewProfileManagementFlagChanged(
408 bool new_flag_status) {
409 if (new_flag_status) {
410 // The reconciler may have been newly created just before this call, or may
411 // have already existed and in mid-reconcile. To err on the safe side, force
412 // a restart.
413 Shutdown();
414 Initialize(true);
415 } else {
416 Shutdown();
420 void AccountReconcilor::FinishReconcile() {
421 VLOG(1) << "AccountReconcilor::FinishReconcile";
422 DCHECK(are_gaia_accounts_set_);
423 DCHECK(add_to_cookie_.empty());
424 int number_gaia_accounts = gaia_accounts_.size();
425 bool are_primaries_equal = number_gaia_accounts > 0 &&
426 gaia::AreEmailsSame(primary_account_, gaia_accounts_[0].first);
428 // If there are any accounts in the gaia cookie but not in chrome, then
429 // those accounts need to be removed from the cookie. This means we need
430 // to blow the cookie away.
431 int removed_from_cookie = 0;
432 for (size_t i = 0; i < gaia_accounts_.size(); ++i) {
433 const std::string& gaia_account = gaia_accounts_[i].first;
434 if (gaia_accounts_[i].second &&
435 chrome_accounts_.end() ==
436 std::find_if(chrome_accounts_.begin(),
437 chrome_accounts_.end(),
438 std::bind1st(AreEmailsSameFunc(), gaia_account))) {
439 ++removed_from_cookie;
443 bool rebuild_cookie = !are_primaries_equal || removed_from_cookie > 0;
444 std::vector<std::pair<std::string, bool> > original_gaia_accounts =
445 gaia_accounts_;
446 if (rebuild_cookie) {
447 VLOG(1) << "AccountReconcilor::FinishReconcile: rebuild cookie";
448 // Really messed up state. Blow away the gaia cookie completely and
449 // rebuild it, making sure the primary account as specified by the
450 // SigninManager is the first session in the gaia cookie.
451 PerformLogoutAllAccountsAction();
452 gaia_accounts_.clear();
455 // Create a list of accounts that need to be added to the gaia cookie.
456 // The primary account must be first to make sure it becomes the default
457 // account in the case where chrome is completely rebuilding the cookie.
458 add_to_cookie_.push_back(primary_account_);
459 for (size_t i = 0; i < chrome_accounts_.size(); ++i) {
460 if (chrome_accounts_[i] != primary_account_)
461 add_to_cookie_.push_back(chrome_accounts_[i]);
464 // For each account known to chrome, PerformMergeAction() if the account is
465 // not already in the cookie jar or its state is invalid, or signal merge
466 // completed otherwise. Make a copy of |add_to_cookie_| since calls to
467 // SignalComplete() will change the array.
468 std::vector<std::string> add_to_cookie_copy = add_to_cookie_;
469 int added_to_cookie = 0;
470 for (size_t i = 0; i < add_to_cookie_copy.size(); ++i) {
471 if (gaia_accounts_.end() !=
472 std::find_if(gaia_accounts_.begin(),
473 gaia_accounts_.end(),
474 std::bind1st(EmailEqualToFunc(),
475 std::make_pair(add_to_cookie_copy[i],
476 true)))) {
477 merge_session_helper_.SignalComplete(
478 add_to_cookie_copy[i],
479 GoogleServiceAuthError::AuthErrorNone());
480 } else {
481 PerformMergeAction(add_to_cookie_copy[i]);
482 if (original_gaia_accounts.end() ==
483 std::find_if(original_gaia_accounts.begin(),
484 original_gaia_accounts.end(),
485 std::bind1st(EmailEqualToFunc(),
486 std::make_pair(add_to_cookie_copy[i],
487 true)))) {
488 added_to_cookie++;
493 signin_metrics::LogSigninAccountReconciliation(chrome_accounts_.size(),
494 added_to_cookie,
495 removed_from_cookie,
496 are_primaries_equal,
497 first_execution_,
498 number_gaia_accounts);
499 first_execution_ = false;
500 CalculateIfReconcileIsDone();
501 ScheduleStartReconcileIfChromeAccountsChanged();
504 void AccountReconcilor::AbortReconcile() {
505 VLOG(1) << "AccountReconcilor::AbortReconcile: we'll try again later";
506 add_to_cookie_.clear();
507 CalculateIfReconcileIsDone();
510 void AccountReconcilor::CalculateIfReconcileIsDone() {
511 is_reconcile_started_ = !add_to_cookie_.empty();
512 if (!is_reconcile_started_)
513 VLOG(1) << "AccountReconcilor::CalculateIfReconcileIsDone: done";
516 void AccountReconcilor::ScheduleStartReconcileIfChromeAccountsChanged() {
517 if (is_reconcile_started_)
518 return;
520 // Start a reconcile as the token accounts have changed.
521 VLOG(1) << "AccountReconcilor::StartReconcileIfChromeAccountsChanged";
522 if (chrome_accounts_changed_) {
523 chrome_accounts_changed_ = false;
524 base::MessageLoop::current()->PostTask(
525 FROM_HERE,
526 base::Bind(&AccountReconcilor::StartReconcile, base::Unretained(this)));
530 // Remove the account from the list that is being merged.
531 bool AccountReconcilor::MarkAccountAsAddedToCookie(
532 const std::string& account_id) {
533 for (std::vector<std::string>::iterator i = add_to_cookie_.begin();
534 i != add_to_cookie_.end();
535 ++i) {
536 if (account_id == *i) {
537 add_to_cookie_.erase(i);
538 return true;
541 return false;
544 void AccountReconcilor::MergeSessionCompleted(
545 const std::string& account_id,
546 const GoogleServiceAuthError& error) {
547 VLOG(1) << "AccountReconcilor::MergeSessionCompleted: account_id="
548 << account_id << " error=" << error.ToString();
549 DCHECK(is_reconcile_started_);
551 if (MarkAccountAsAddedToCookie(account_id)) {
552 CalculateIfReconcileIsDone();
553 ScheduleStartReconcileIfChromeAccountsChanged();
557 void AccountReconcilor::GetCheckConnectionInfoCompleted(bool succeeded) {
558 base::TimeDelta time_to_check_connections =
559 base::Time::Now() - m_reconcile_start_time_;
560 signin_metrics::LogExternalCcResultFetches(succeeded,
561 time_to_check_connections);
562 GetAccountsFromCookie(base::Bind(
563 &AccountReconcilor::ContinueReconcileActionAfterGetGaiaAccounts,
564 base::Unretained(this)));