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 "components/signin/core/browser/account_reconcilor.h"
6
7 #include <stddef.h>
8
9 #include <algorithm>
10 #include <iterator>
11 #include <set>
12 #include <utility>
13
14 #include "base/bind.h"
15 #include "base/bind_helpers.h"
16 #include "base/location.h"
17 #include "base/logging.h"
18 #include "base/memory/ptr_util.h"
19 #include "base/single_thread_task_runner.h"
20 #include "base/stl_util.h"
21 #include "base/threading/thread_task_runner_handle.h"
22 #include "build/build_config.h"
23 #include "components/signin/core/browser/account_reconcilor_delegate.h"
24 #include "components/signin/public/base/account_consistency_method.h"
25 #include "components/signin/public/base/signin_client.h"
26 #include "components/signin/public/base/signin_metrics.h"
27 #include "components/signin/public/identity_manager/accounts_cookie_mutator.h"
28 #include "components/signin/public/identity_manager/accounts_in_cookie_jar_info.h"
29 #include "components/signin/public/identity_manager/accounts_mutator.h"
30 #include "components/signin/public/identity_manager/set_accounts_in_cookie_result.h"
31 #include "google_apis/gaia/gaia_auth_util.h"
32 #include "google_apis/gaia/gaia_urls.h"
33 #include "google_apis/gaia/google_service_auth_error.h"
34
35 using signin::AccountReconcilorDelegate;
36 using signin_metrics::AccountReconcilorState;
37
38 namespace {
39
40 class AccountEqualToFunc {
41 public:
AccountEqualToFunc(const gaia::ListedAccount & account)42 explicit AccountEqualToFunc(const gaia::ListedAccount& account)
43 : account_(account) {}
44 bool operator()(const gaia::ListedAccount& other) const;
45
46 private:
47 gaia::ListedAccount account_;
48 };
49
operator ()(const gaia::ListedAccount & other) const50 bool AccountEqualToFunc::operator()(const gaia::ListedAccount& other) const {
51 return account_.valid == other.valid && account_.id == other.id;
52 }
53
AccountForId(const CoreAccountId & account_id)54 gaia::ListedAccount AccountForId(const CoreAccountId& account_id) {
55 gaia::ListedAccount account;
56 account.id = account_id;
57 return account;
58 }
59
ContainsGaiaAccount(const std::vector<gaia::ListedAccount> & gaia_accounts,const CoreAccountId & account_id)60 bool ContainsGaiaAccount(const std::vector<gaia::ListedAccount>& gaia_accounts,
61 const CoreAccountId& account_id) {
62 return gaia_accounts.end() !=
63 std::find_if(gaia_accounts.begin(), gaia_accounts.end(),
64 AccountEqualToFunc(AccountForId(account_id)));
65 }
66
67 // Returns a copy of |accounts| without the unverified accounts.
FilterUnverifiedAccounts(const std::vector<gaia::ListedAccount> & accounts)68 std::vector<gaia::ListedAccount> FilterUnverifiedAccounts(
69 const std::vector<gaia::ListedAccount>& accounts) {
70 // Ignore unverified accounts.
71 std::vector<gaia::ListedAccount> verified_gaia_accounts;
72 std::copy_if(
73 accounts.begin(), accounts.end(),
74 std::back_inserter(verified_gaia_accounts),
75 [](const gaia::ListedAccount& account) { return account.verified; });
76 return verified_gaia_accounts;
77 }
78
79 // Revokes tokens for all accounts in chrome_accounts but the primary account.
80 // Returns true if tokens were revoked, and false if the function did nothing.
RevokeAllSecondaryTokens(signin::IdentityManager * identity_manager,signin::AccountReconcilorDelegate::RevokeTokenOption revoke_option,const CoreAccountId & primary_account,bool is_account_consistency_enforced,signin_metrics::SourceForRefreshTokenOperation source)81 bool RevokeAllSecondaryTokens(
82 signin::IdentityManager* identity_manager,
83 signin::AccountReconcilorDelegate::RevokeTokenOption revoke_option,
84 const CoreAccountId& primary_account,
85 bool is_account_consistency_enforced,
86 signin_metrics::SourceForRefreshTokenOperation source) {
87 bool token_revoked = false;
88 if (revoke_option ==
89 AccountReconcilorDelegate::RevokeTokenOption::kDoNotRevoke)
90 return false;
91 for (const CoreAccountInfo& account_info :
92 identity_manager->GetAccountsWithRefreshTokens()) {
93 CoreAccountId account = account_info.account_id;
94 if (account == primary_account)
95 continue;
96 bool should_revoke = false;
97 switch (revoke_option) {
98 case AccountReconcilorDelegate::RevokeTokenOption::kRevokeIfInError:
99 if (identity_manager->HasAccountWithRefreshTokenInPersistentErrorState(
100 account)) {
101 VLOG(1) << "Revoke token for " << account;
102 should_revoke = true;
103 }
104 break;
105 case AccountReconcilorDelegate::RevokeTokenOption::kRevoke:
106 VLOG(1) << "Revoke token for " << account;
107 if (is_account_consistency_enforced) {
108 should_revoke = true;
109 }
110 break;
111 case AccountReconcilorDelegate::RevokeTokenOption::kDoNotRevoke:
112 NOTREACHED();
113 break;
114 }
115 if (should_revoke) {
116 token_revoked = true;
117 VLOG(1) << "Revoke token for " << account;
118 if (is_account_consistency_enforced) {
119 auto* accounts_mutator = identity_manager->GetAccountsMutator();
120 accounts_mutator->RemoveAccount(account, source);
121 }
122 }
123 }
124 return token_revoked;
125 }
126
127 // TODO(msalama): Move this code and |RevokeAllSecondaryTokens|
128 // to |DiceAccountReconcilorDelegate|.
RevokeTokensNotInCookies(signin::IdentityManager * identity_manager,const CoreAccountId & primary_account,const std::vector<gaia::ListedAccount> & gaia_accounts)129 signin::RevokeTokenAction RevokeTokensNotInCookies(
130 signin::IdentityManager* identity_manager,
131 const CoreAccountId& primary_account,
132 const std::vector<gaia::ListedAccount>& gaia_accounts) {
133 bool invalidated_primary_account_token = false;
134 bool revoked_token_for_secondary_account = false;
135 signin_metrics::SourceForRefreshTokenOperation source =
136 signin_metrics::SourceForRefreshTokenOperation::
137 kAccountReconcilor_RevokeTokensNotInCookies;
138
139 for (const CoreAccountInfo& account_info :
140 identity_manager->GetAccountsWithRefreshTokens()) {
141 CoreAccountId account = account_info.account_id;
142 if (ContainsGaiaAccount(gaia_accounts, account))
143 continue;
144
145 auto* accounts_mutator = identity_manager->GetAccountsMutator();
146 if (account == primary_account) {
147 invalidated_primary_account_token = true;
148 accounts_mutator->InvalidateRefreshTokenForPrimaryAccount(source);
149 } else {
150 revoked_token_for_secondary_account = true;
151 accounts_mutator->RemoveAccount(account, source);
152 }
153 }
154
155 signin::RevokeTokenAction revoke_token_action =
156 signin::RevokeTokenAction::kNone;
157 if (invalidated_primary_account_token &&
158 revoked_token_for_secondary_account) {
159 revoke_token_action =
160 signin::RevokeTokenAction::kRevokeTokensForPrimaryAndSecondaryAccounts;
161 } else if (invalidated_primary_account_token) {
162 revoke_token_action =
163 signin::RevokeTokenAction::kInvalidatePrimaryAccountToken;
164 } else if (revoked_token_for_secondary_account) {
165 revoke_token_action =
166 signin::RevokeTokenAction::kRevokeSecondaryAccountsTokens;
167 }
168 return revoke_token_action;
169 }
170
171 // Pick the account will become first after this reconcile is finished.
PickFirstGaiaAccount(const signin::MultiloginParameters & parameters,const std::vector<gaia::ListedAccount> & gaia_accounts)172 CoreAccountId PickFirstGaiaAccount(
173 const signin::MultiloginParameters& parameters,
174 const std::vector<gaia::ListedAccount>& gaia_accounts) {
175 if (parameters.mode ==
176 gaia::MultiloginMode::MULTILOGIN_PRESERVE_COOKIE_ACCOUNTS_ORDER &&
177 !gaia_accounts.empty()) {
178 return gaia_accounts[0].id;
179 }
180 return parameters.accounts_to_send.empty() ? CoreAccountId()
181 : parameters.accounts_to_send[0];
182 }
183
184 // Returns true if gaia_accounts contains an invalid account that is unknown to
185 // the identity manager.
HasUnknownInvalidAccountInCookie(signin::IdentityManager * identity_manager,const std::vector<gaia::ListedAccount> & gaia_accounts)186 bool HasUnknownInvalidAccountInCookie(
187 signin::IdentityManager* identity_manager,
188 const std::vector<gaia::ListedAccount>& gaia_accounts) {
189 for (const gaia::ListedAccount& account : gaia_accounts) {
190 if (!account.valid &&
191 !identity_manager->HasAccountWithRefreshToken(account.id)) {
192 return true;
193 }
194 }
195 return false;
196 }
197
198 } // namespace
199
Lock(AccountReconcilor * reconcilor)200 AccountReconcilor::Lock::Lock(AccountReconcilor* reconcilor)
201 : reconcilor_(reconcilor->weak_factory_.GetWeakPtr()) {
202 DCHECK(reconcilor_);
203 reconcilor_->IncrementLockCount();
204 }
205
~Lock()206 AccountReconcilor::Lock::~Lock() {
207 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
208 if (reconcilor_)
209 reconcilor_->DecrementLockCount();
210 }
211
ScopedSyncedDataDeletion(AccountReconcilor * reconcilor)212 AccountReconcilor::ScopedSyncedDataDeletion::ScopedSyncedDataDeletion(
213 AccountReconcilor* reconcilor)
214 : reconcilor_(reconcilor->weak_factory_.GetWeakPtr()) {
215 DCHECK(reconcilor_);
216 ++reconcilor_->synced_data_deletion_in_progress_count_;
217 }
218
~ScopedSyncedDataDeletion()219 AccountReconcilor::ScopedSyncedDataDeletion::~ScopedSyncedDataDeletion() {
220 if (!reconcilor_)
221 return; // The reconcilor was destroyed.
222
223 DCHECK_GT(reconcilor_->synced_data_deletion_in_progress_count_, 0);
224 --reconcilor_->synced_data_deletion_in_progress_count_;
225 }
226
AccountReconcilor(signin::IdentityManager * identity_manager,SigninClient * client,std::unique_ptr<signin::AccountReconcilorDelegate> delegate)227 AccountReconcilor::AccountReconcilor(
228 signin::IdentityManager* identity_manager,
229 SigninClient* client,
230 std::unique_ptr<signin::AccountReconcilorDelegate> delegate)
231 : delegate_(std::move(delegate)),
232 identity_manager_(identity_manager),
233 client_(client),
234 registered_with_identity_manager_(false),
235 registered_with_content_settings_(false),
236 is_reconcile_started_(false),
237 first_execution_(true),
238 error_during_last_reconcile_(GoogleServiceAuthError::AuthErrorNone()),
239 reconcile_is_noop_(true),
240 set_accounts_in_progress_(false),
241 log_out_in_progress_(false),
242 chrome_accounts_changed_(false),
243 account_reconcilor_lock_count_(0),
244 reconcile_on_unblock_(false),
245 timer_(new base::OneShotTimer),
246 state_(signin_metrics::ACCOUNT_RECONCILOR_OK) {
247 VLOG(1) << "AccountReconcilor::AccountReconcilor";
248 DCHECK(delegate_);
249 delegate_->set_reconcilor(this);
250 timeout_ = delegate_->GetReconcileTimeout();
251 }
252
~AccountReconcilor()253 AccountReconcilor::~AccountReconcilor() {
254 VLOG(1) << "AccountReconcilor::~AccountReconcilor";
255 // Make sure shutdown was called first.
256 DCHECK(!registered_with_identity_manager_);
257 }
258
RegisterWithAllDependencies()259 void AccountReconcilor::RegisterWithAllDependencies() {
260 RegisterWithContentSettings();
261 RegisterWithIdentityManager();
262 }
263
UnregisterWithAllDependencies()264 void AccountReconcilor::UnregisterWithAllDependencies() {
265 UnregisterWithIdentityManager();
266 UnregisterWithContentSettings();
267 }
268
Initialize(bool start_reconcile_if_tokens_available)269 void AccountReconcilor::Initialize(bool start_reconcile_if_tokens_available) {
270 VLOG(1) << "AccountReconcilor::Initialize";
271 if (delegate_->IsReconcileEnabled()) {
272 SetState(AccountReconcilorState::ACCOUNT_RECONCILOR_SCHEDULED);
273 RegisterWithAllDependencies();
274
275 // Start a reconcile if the tokens are already loaded.
276 if (start_reconcile_if_tokens_available && IsIdentityManagerReady())
277 StartReconcile();
278 }
279 }
280
EnableReconcile()281 void AccountReconcilor::EnableReconcile() {
282 RegisterWithAllDependencies();
283 if (IsIdentityManagerReady())
284 StartReconcile();
285 else
286 SetState(AccountReconcilorState::ACCOUNT_RECONCILOR_SCHEDULED);
287 }
288
DisableReconcile(bool logout_all_accounts)289 void AccountReconcilor::DisableReconcile(bool logout_all_accounts) {
290 AbortReconcile();
291 SetState(AccountReconcilorState::ACCOUNT_RECONCILOR_OK);
292 UnregisterWithAllDependencies();
293
294 if (logout_all_accounts)
295 PerformLogoutAllAccountsAction();
296 }
297
Shutdown()298 void AccountReconcilor::Shutdown() {
299 VLOG(1) << "AccountReconcilor::Shutdown";
300 DisableReconcile(false /* logout_all_accounts */);
301 delegate_.reset();
302 }
303
RegisterWithContentSettings()304 void AccountReconcilor::RegisterWithContentSettings() {
305 VLOG(1) << "AccountReconcilor::RegisterWithContentSettings";
306 // During re-auth, the reconcilor will get a callback about successful signin
307 // even when the profile is already connected. Avoid re-registering
308 // with the token service since this will DCHECK.
309 if (registered_with_content_settings_)
310 return;
311
312 client_->AddContentSettingsObserver(this);
313 registered_with_content_settings_ = true;
314 }
315
UnregisterWithContentSettings()316 void AccountReconcilor::UnregisterWithContentSettings() {
317 VLOG(1) << "AccountReconcilor::UnregisterWithContentSettings";
318 if (!registered_with_content_settings_)
319 return;
320
321 client_->RemoveContentSettingsObserver(this);
322 registered_with_content_settings_ = false;
323 }
324
RegisterWithIdentityManager()325 void AccountReconcilor::RegisterWithIdentityManager() {
326 VLOG(1) << "AccountReconcilor::RegisterWithIdentityManager";
327 // During re-auth, the reconcilor will get a callback about successful signin
328 // even when the profile is already connected. Avoid re-registering
329 // with the token service since this will DCHECK.
330 if (registered_with_identity_manager_)
331 return;
332
333 identity_manager_->AddObserver(this);
334 registered_with_identity_manager_ = true;
335 }
336
UnregisterWithIdentityManager()337 void AccountReconcilor::UnregisterWithIdentityManager() {
338 VLOG(1) << "AccountReconcilor::UnregisterWithIdentityManager";
339 if (!registered_with_identity_manager_)
340 return;
341
342 identity_manager_->RemoveObserver(this);
343 registered_with_identity_manager_ = false;
344 }
345
GetState()346 AccountReconcilorState AccountReconcilor::GetState() {
347 return state_;
348 }
349
350 std::unique_ptr<AccountReconcilor::ScopedSyncedDataDeletion>
GetScopedSyncDataDeletion()351 AccountReconcilor::GetScopedSyncDataDeletion() {
352 return base::WrapUnique(new ScopedSyncedDataDeletion(this));
353 }
354
AddObserver(Observer * observer)355 void AccountReconcilor::AddObserver(Observer* observer) {
356 observer_list_.AddObserver(observer);
357 }
358
RemoveObserver(Observer * observer)359 void AccountReconcilor::RemoveObserver(Observer* observer) {
360 observer_list_.RemoveObserver(observer);
361 }
362
OnContentSettingChanged(const ContentSettingsPattern & primary_pattern,const ContentSettingsPattern & secondary_pattern,ContentSettingsType content_type,const std::string & resource_identifier)363 void AccountReconcilor::OnContentSettingChanged(
364 const ContentSettingsPattern& primary_pattern,
365 const ContentSettingsPattern& secondary_pattern,
366 ContentSettingsType content_type,
367 const std::string& resource_identifier) {
368 // If this is not a change to cookie settings, just ignore.
369 if (content_type != ContentSettingsType::COOKIES)
370 return;
371
372 // If this does not affect GAIA, just ignore. If the primary pattern is
373 // invalid, then assume it could affect GAIA. The secondary pattern is
374 // not needed.
375 if (primary_pattern.IsValid() &&
376 !primary_pattern.Matches(GaiaUrls::GetInstance()->gaia_url())) {
377 return;
378 }
379
380 VLOG(1) << "AccountReconcilor::OnContentSettingChanged";
381 StartReconcile();
382 }
383
OnEndBatchOfRefreshTokenStateChanges()384 void AccountReconcilor::OnEndBatchOfRefreshTokenStateChanges() {
385 VLOG(1) << "AccountReconcilor::OnEndBatchOfRefreshTokenStateChanges. "
386 << "Reconcilor state: " << is_reconcile_started_;
387 // Remember that accounts have changed if a reconcile is already started.
388 chrome_accounts_changed_ = is_reconcile_started_;
389 StartReconcile();
390 }
391
OnRefreshTokensLoaded()392 void AccountReconcilor::OnRefreshTokensLoaded() {
393 StartReconcile();
394 }
395
OnErrorStateOfRefreshTokenUpdatedForAccount(const CoreAccountInfo & account_info,const GoogleServiceAuthError & error)396 void AccountReconcilor::OnErrorStateOfRefreshTokenUpdatedForAccount(
397 const CoreAccountInfo& account_info,
398 const GoogleServiceAuthError& error) {
399 // Gaia cookies may be invalidated server-side and the client does not get any
400 // notification when this happens.
401 // Gaia cookies derived from refresh tokens are always invalidated server-side
402 // when the tokens are revoked. Trigger a ListAccounts to Gaia when this
403 // happens to make sure that the cookies accounts are up-to-date.
404 // This should cover well the Mirror and Desktop Identity Consistency cases as
405 // the cookies are always bound to the refresh tokens in these cases.
406 if (error != GoogleServiceAuthError::AuthErrorNone())
407 identity_manager_->GetAccountsCookieMutator()->TriggerCookieJarUpdate();
408 }
409
PerformMergeAction(const CoreAccountId & account_id)410 void AccountReconcilor::PerformMergeAction(const CoreAccountId& account_id) {
411 reconcile_is_noop_ = false;
412 if (!delegate_->IsAccountConsistencyEnforced()) {
413 MarkAccountAsAddedToCookie(account_id);
414 return;
415 }
416 VLOG(1) << "AccountReconcilor::PerformMergeAction: " << account_id;
417 identity_manager_->GetAccountsCookieMutator()->AddAccountToCookie(
418 account_id, delegate_->GetGaiaApiSource(),
419 base::BindOnce(&AccountReconcilor::OnAddAccountToCookieCompleted,
420 weak_factory_.GetWeakPtr()));
421 }
422
PerformSetCookiesAction(const signin::MultiloginParameters & parameters)423 void AccountReconcilor::PerformSetCookiesAction(
424 const signin::MultiloginParameters& parameters) {
425 reconcile_is_noop_ = false;
426 if (!delegate_->IsAccountConsistencyEnforced()) {
427 OnSetAccountsInCookieCompleted(signin::SetAccountsInCookieResult::kSuccess);
428 return;
429 }
430
431 VLOG(1) << "AccountReconcilor::PerformSetCookiesAction: "
432 << base::JoinString(ToStringList(parameters.accounts_to_send), " ");
433 // TODO (https://crbug.com/890321): pass mode to GaiaCookieManagerService.
434 //
435 // Using Unretained is safe here because the CookieManagerService outlives
436 // the AccountReconcilor.
437 identity_manager_->GetAccountsCookieMutator()->SetAccountsInCookie(
438 parameters, delegate_->GetGaiaApiSource(),
439 base::BindOnce(&AccountReconcilor::OnSetAccountsInCookieCompleted,
440 base::Unretained(this)));
441 }
442
PerformLogoutAllAccountsAction()443 void AccountReconcilor::PerformLogoutAllAccountsAction() {
444 reconcile_is_noop_ = false;
445 if (!delegate_->IsAccountConsistencyEnforced())
446 return;
447 VLOG(1) << "AccountReconcilor::PerformLogoutAllAccountsAction";
448 identity_manager_->GetAccountsCookieMutator()->LogOutAllAccounts(
449 delegate_->GetGaiaApiSource(),
450 base::BindOnce(&AccountReconcilor::OnLogOutFromCookieCompleted,
451 weak_factory_.GetWeakPtr()));
452 }
453
StartReconcile()454 void AccountReconcilor::StartReconcile() {
455 if (is_reconcile_started_)
456 return;
457
458 if (IsReconcileBlocked()) {
459 VLOG(1) << "AccountReconcilor::StartReconcile: "
460 << "Reconcile is blocked, scheduling for later.";
461 // Reconcile is locked, it will be restarted when the lock count reaches 0.
462 reconcile_on_unblock_ = true;
463 SetState(AccountReconcilorState::ACCOUNT_RECONCILOR_SCHEDULED);
464 return;
465 }
466
467 // TODO(crbug.com/967603): remove when root cause is found.
468 CHECK(delegate_);
469 CHECK(client_);
470 if (!delegate_->IsReconcileEnabled() || !client_->AreSigninCookiesAllowed()) {
471 VLOG(1) << "AccountReconcilor::StartReconcile: !enabled or no cookies";
472 SetState(AccountReconcilorState::ACCOUNT_RECONCILOR_OK);
473 return;
474 }
475
476 // Do not reconcile if tokens are not loaded yet.
477 if (!IsIdentityManagerReady()) {
478 SetState(AccountReconcilorState::ACCOUNT_RECONCILOR_SCHEDULED);
479 VLOG(1)
480 << "AccountReconcilor::StartReconcile: token service *not* ready yet.";
481 return;
482 }
483
484 // Begin reconciliation. Reset initial states.
485 SetState(AccountReconcilorState::ACCOUNT_RECONCILOR_RUNNING);
486 add_to_cookie_.clear();
487 reconcile_start_time_ = base::Time::Now();
488 is_reconcile_started_ = true;
489 error_during_last_reconcile_ = GoogleServiceAuthError::AuthErrorNone();
490 reconcile_is_noop_ = true;
491
492 if (!timeout_.is_max()) {
493 timer_->Start(FROM_HERE, timeout_,
494 base::BindOnce(&AccountReconcilor::HandleReconcileTimeout,
495 base::Unretained(this)));
496 }
497
498 const CoreAccountId& account_id = identity_manager_->GetPrimaryAccountId();
499 if (identity_manager_->HasAccountWithRefreshTokenInPersistentErrorState(
500 account_id) &&
501 delegate_->ShouldAbortReconcileIfPrimaryHasError()) {
502 VLOG(1) << "AccountReconcilor::StartReconcile: primary has error, abort.";
503 SetState(AccountReconcilorState::ACCOUNT_RECONCILOR_ERROR);
504 error_during_last_reconcile_ =
505 identity_manager_->GetErrorStateOfRefreshTokenForAccount(account_id);
506 AbortReconcile();
507 return;
508 }
509
510 // Rely on the IdentityManager to manage calls to and responses from
511 // ListAccounts.
512 signin::AccountsInCookieJarInfo accounts_in_cookie_jar =
513 identity_manager_->GetAccountsInCookieJar();
514 if (accounts_in_cookie_jar.accounts_are_fresh) {
515 OnAccountsInCookieUpdated(
516 accounts_in_cookie_jar,
517 GoogleServiceAuthError(GoogleServiceAuthError::NONE));
518 }
519 }
520
FinishReconcileWithMultiloginEndpoint(const CoreAccountId & primary_account,const std::vector<CoreAccountId> & chrome_accounts,std::vector<gaia::ListedAccount> && gaia_accounts)521 void AccountReconcilor::FinishReconcileWithMultiloginEndpoint(
522 const CoreAccountId& primary_account,
523 const std::vector<CoreAccountId>& chrome_accounts,
524 std::vector<gaia::ListedAccount>&& gaia_accounts) {
525 DCHECK(IsMultiloginEndpointEnabled());
526 DCHECK(!set_accounts_in_progress_);
527 DCHECK_EQ(AccountReconcilorState::ACCOUNT_RECONCILOR_RUNNING, state_);
528
529 bool primary_has_error =
530 identity_manager_->HasAccountWithRefreshTokenInPersistentErrorState(
531 primary_account);
532
533 const signin::MultiloginParameters parameters_for_multilogin =
534 delegate_->CalculateParametersForMultilogin(
535 chrome_accounts, primary_account, gaia_accounts, first_execution_,
536 primary_has_error);
537
538 DCHECK(is_reconcile_started_);
539 if (CookieNeedsUpdate(parameters_for_multilogin, gaia_accounts)) {
540 if (parameters_for_multilogin.mode ==
541 gaia::MultiloginMode::MULTILOGIN_UPDATE_COOKIE_ACCOUNTS_ORDER &&
542 parameters_for_multilogin.accounts_to_send.empty()) {
543 // UPDATE mode does not support empty list of accounts, call logout
544 // instead.
545 PerformLogoutAllAccountsAction();
546 gaia_accounts.clear();
547 // TODO(alexilin): Asynchronously wait until the logout is complete.
548 OnSetAccountsInCookieCompleted(
549 signin::SetAccountsInCookieResult::kSuccess);
550 DCHECK(!is_reconcile_started_);
551 } else {
552 // Reconcilor has to do some calls to gaia. is_reconcile_started_ is true
553 // and any StartReconcile() calls that are made in the meantime will be
554 // aborted until OnSetAccountsInCookieCompleted is called and
555 // is_reconcile_started_ is set to false.
556 set_accounts_in_progress_ = true;
557 PerformSetCookiesAction(parameters_for_multilogin);
558 }
559 } else {
560 // Nothing to do, accounts already match.
561 OnSetAccountsInCookieCompleted(signin::SetAccountsInCookieResult::kSuccess);
562 DCHECK(!is_reconcile_started_);
563 }
564
565 signin_metrics::RecordAccountsPerProfile(chrome_accounts.size());
566 if (!is_reconcile_started_) {
567 // TODO(droger): investigate if |is_reconcile_started_| is still needed for
568 // multilogin.
569
570 // This happens only when reconcile doesn't make any changes (i.e. the state
571 // is consistent). If it is not the case, second reconcile is expected to be
572 // triggered after changes are made. For that one the state is supposed to
573 // be already consistent.
574 DCHECK(!CookieNeedsUpdate(parameters_for_multilogin, gaia_accounts));
575 DCHECK_NE(AccountReconcilorState::ACCOUNT_RECONCILOR_RUNNING, state_);
576 CoreAccountId first_gaia_account_after_reconcile =
577 PickFirstGaiaAccount(parameters_for_multilogin, gaia_accounts);
578 delegate_->OnReconcileFinished(first_gaia_account_after_reconcile);
579 }
580 first_execution_ = false;
581 }
582
OnAccountsInCookieUpdated(const signin::AccountsInCookieJarInfo & accounts_in_cookie_jar_info,const GoogleServiceAuthError & error)583 void AccountReconcilor::OnAccountsInCookieUpdated(
584 const signin::AccountsInCookieJarInfo& accounts_in_cookie_jar_info,
585 const GoogleServiceAuthError& error) {
586 const std::vector<gaia::ListedAccount>& accounts(
587 accounts_in_cookie_jar_info.signed_in_accounts);
588 VLOG(1) << "AccountReconcilor::OnAccountsInCookieUpdated: "
589 << "CookieJar " << accounts.size() << " accounts, "
590 << "Reconcilor's state is " << is_reconcile_started_ << ", "
591 << "Error was " << error.ToString();
592
593 // If cookies change while the reconcilor is running, ignore the changes and
594 // let it complete. Adding accounts to the cookie will trigger new
595 // notifications anyway, and these will be handled in a new reconciliation
596 // cycle. See https://crbug.com/923716
597 if (IsMultiloginEndpointEnabled()) {
598 if (set_accounts_in_progress_)
599 return;
600 } else {
601 if (!add_to_cookie_.empty())
602 return;
603 }
604
605 if (!is_reconcile_started_) {
606 StartReconcile();
607 return;
608 }
609
610 if (error.state() != GoogleServiceAuthError::NONE) {
611 // We may have seen a series of errors during reconciliation. Delegates may
612 // rely on the severity of the last seen error (see |OnReconcileError|) and
613 // hence do not override a persistent error, if we have seen one.
614 if (!error_during_last_reconcile_.IsPersistentError())
615 error_during_last_reconcile_ = error;
616 SetState(AccountReconcilorState::ACCOUNT_RECONCILOR_ERROR);
617 AbortReconcile();
618 return;
619 }
620
621 std::vector<gaia::ListedAccount> verified_gaia_accounts =
622 FilterUnverifiedAccounts(accounts);
623 VLOG_IF(1, verified_gaia_accounts.size() < accounts.size())
624 << "Ignore " << accounts.size() - verified_gaia_accounts.size()
625 << " unverified account(s).";
626
627 CoreAccountId primary_account = identity_manager_->GetPrimaryAccountId();
628 if (delegate_->ShouldRevokeTokensNotInCookies()) {
629 signin::RevokeTokenAction revoke_token_action = RevokeTokensNotInCookies(
630 identity_manager_, primary_account, verified_gaia_accounts);
631 delegate_->OnRevokeTokensNotInCookiesCompleted(revoke_token_action);
632 }
633
634 // Revoking tokens for secondary accounts causes the AccountTracker to
635 // completely remove them from Chrome.
636 // Revoking the token for the primary account is not supported (it should be
637 // signed out or put to auth error state instead).
638 AccountReconcilorDelegate::RevokeTokenOption revoke_option =
639 delegate_->ShouldRevokeSecondaryTokensBeforeReconcile(
640 verified_gaia_accounts);
641 RevokeAllSecondaryTokens(identity_manager_, revoke_option, primary_account,
642 true,
643 signin_metrics::SourceForRefreshTokenOperation::
644 kAccountReconcilor_GaiaCookiesUpdated);
645
646 std::vector<CoreAccountId> chrome_accounts =
647 LoadValidAccountsFromTokenService();
648
649 if (delegate_->ShouldAbortReconcileIfPrimaryHasError() &&
650 !base::Contains(chrome_accounts, primary_account)) {
651 VLOG(1) << "Primary account has error, abort.";
652 DCHECK(is_reconcile_started_);
653 AbortReconcile();
654 SetState(AccountReconcilorState::ACCOUNT_RECONCILOR_ERROR);
655 return;
656 }
657
658 if (IsMultiloginEndpointEnabled()) {
659 FinishReconcileWithMultiloginEndpoint(primary_account, chrome_accounts,
660 std::move(verified_gaia_accounts));
661 } else {
662 FinishReconcile(primary_account, chrome_accounts,
663 std::move(verified_gaia_accounts));
664 }
665 }
666
OnAccountsCookieDeletedByUserAction()667 void AccountReconcilor::OnAccountsCookieDeletedByUserAction() {
668 if (!delegate_->ShouldRevokeTokensOnCookieDeleted())
669 return;
670
671 const CoreAccountId& primary_account =
672 identity_manager_->GetPrimaryAccountId();
673 // Revoke secondary tokens.
674 RevokeAllSecondaryTokens(
675 identity_manager_, AccountReconcilorDelegate::RevokeTokenOption::kRevoke,
676 primary_account, /*account_consistency_enforced=*/true,
677 signin_metrics::SourceForRefreshTokenOperation::
678 kAccountReconcilor_GaiaCookiesDeletedByUser);
679 if (primary_account.empty())
680 return;
681 if (identity_manager_->HasAccountWithRefreshTokenInPersistentErrorState(
682 primary_account) ||
683 synced_data_deletion_in_progress_count_ == 0) {
684 // Invalidate the primary token, but do not revoke it.
685 auto* accounts_mutator = identity_manager_->GetAccountsMutator();
686 accounts_mutator->InvalidateRefreshTokenForPrimaryAccount(
687 signin_metrics::SourceForRefreshTokenOperation::
688 kAccountReconcilor_GaiaCookiesDeletedByUser);
689 }
690 }
691
692 std::vector<CoreAccountId>
LoadValidAccountsFromTokenService() const693 AccountReconcilor::LoadValidAccountsFromTokenService() const {
694 auto chrome_accounts_with_refresh_tokens =
695 identity_manager_->GetAccountsWithRefreshTokens();
696
697 std::vector<CoreAccountId> chrome_account_ids;
698
699 // Remove any accounts that have an error. There is no point in trying to
700 // reconcile them, since it won't work anyway. If the list ends up being
701 // empty then don't reconcile any accounts.
702 for (const auto& chrome_account_with_refresh_tokens :
703 chrome_accounts_with_refresh_tokens) {
704 if (identity_manager_->HasAccountWithRefreshTokenInPersistentErrorState(
705 chrome_account_with_refresh_tokens.account_id)) {
706 VLOG(1) << "AccountReconcilor::LoadValidAccountsFromTokenService: "
707 << chrome_account_with_refresh_tokens.account_id
708 << " has error, don't reconcile";
709 continue;
710 }
711 chrome_account_ids.push_back(chrome_account_with_refresh_tokens.account_id);
712 }
713
714 VLOG(1) << "AccountReconcilor::LoadValidAccountsFromTokenService: "
715 << "Chrome " << chrome_account_ids.size() << " accounts";
716
717 return chrome_account_ids;
718 }
719
OnReceivedManageAccountsResponse(signin::GAIAServiceType service_type)720 void AccountReconcilor::OnReceivedManageAccountsResponse(
721 signin::GAIAServiceType service_type) {
722 if (service_type == signin::GAIA_SERVICE_TYPE_ADDSESSION) {
723 identity_manager_->GetAccountsCookieMutator()->TriggerCookieJarUpdate();
724 }
725 }
726
FinishReconcile(const CoreAccountId & primary_account,const std::vector<CoreAccountId> & chrome_accounts,std::vector<gaia::ListedAccount> && gaia_accounts)727 void AccountReconcilor::FinishReconcile(
728 const CoreAccountId& primary_account,
729 const std::vector<CoreAccountId>& chrome_accounts,
730 std::vector<gaia::ListedAccount>&& gaia_accounts) {
731 VLOG(1) << "AccountReconcilor::FinishReconcile";
732 DCHECK(add_to_cookie_.empty());
733 DCHECK(delegate_->IsUnknownInvalidAccountInCookieAllowed())
734 << "Only supported in UPDATE mode";
735
736 size_t number_gaia_accounts = gaia_accounts.size();
737 // If there are any accounts in the gaia cookie but not in chrome, then
738 // those accounts need to be removed from the cookie. This means we need
739 // to blow the cookie away.
740 int removed_from_cookie = 0;
741 for (size_t i = 0; i < number_gaia_accounts; ++i) {
742 if (gaia_accounts[i].valid &&
743 !base::Contains(chrome_accounts, gaia_accounts[i].id)) {
744 ++removed_from_cookie;
745 }
746 }
747
748 CoreAccountId first_account = delegate_->GetFirstGaiaAccountForReconcile(
749 chrome_accounts, gaia_accounts, primary_account, first_execution_,
750 removed_from_cookie > 0);
751 bool first_account_mismatch =
752 (number_gaia_accounts > 0) && (first_account != gaia_accounts[0].id);
753
754 bool rebuild_cookie = first_account_mismatch || (removed_from_cookie > 0);
755 std::vector<gaia::ListedAccount> original_gaia_accounts = gaia_accounts;
756 if (rebuild_cookie) {
757 VLOG(1) << "AccountReconcilor::FinishReconcile: rebuild cookie";
758 // Really messed up state. Blow away the gaia cookie completely and
759 // rebuild it, making sure the primary account as specified by the
760 // IdentityManager is the first session in the gaia cookie.
761 log_out_in_progress_ = true;
762 PerformLogoutAllAccountsAction();
763 gaia_accounts.clear();
764 }
765
766 if (first_account.empty()) {
767 DCHECK(!delegate_->ShouldAbortReconcileIfPrimaryHasError());
768 auto revoke_option =
769 delegate_->ShouldRevokeTokensIfNoPrimaryAccount()
770 ? AccountReconcilorDelegate::RevokeTokenOption::kRevoke
771 : AccountReconcilorDelegate::RevokeTokenOption::kDoNotRevoke;
772 reconcile_is_noop_ = !RevokeAllSecondaryTokens(
773 identity_manager_, revoke_option, primary_account,
774 delegate_->IsAccountConsistencyEnforced(),
775 signin_metrics::SourceForRefreshTokenOperation::
776 kAccountReconcilor_Reconcile);
777 } else {
778 // Create a list of accounts that need to be added to the Gaia cookie.
779 if (base::Contains(chrome_accounts, first_account)) {
780 add_to_cookie_.push_back(first_account);
781 } else {
782 // If the first account is not empty and not in chrome_accounts, it is
783 // impossible to rebuild it. It must be already the current default
784 // account, and no logout can happen.
785 DCHECK_EQ(gaia_accounts[0].id, first_account);
786 DCHECK(!rebuild_cookie);
787 }
788 for (size_t i = 0; i < chrome_accounts.size(); ++i) {
789 if (chrome_accounts[i] != first_account)
790 add_to_cookie_.push_back(chrome_accounts[i]);
791 }
792 }
793
794 // For each account known to chrome, PerformMergeAction() if the account is
795 // not already in the cookie jar or its state is invalid, or signal merge
796 // completed otherwise. Make a copy of |add_to_cookie_| since calls
797 // to OnAddAccountToCookieCompleted() will change the array.
798 std::vector<CoreAccountId> add_to_cookie_copy = add_to_cookie_;
799 int added_to_cookie = 0;
800 for (size_t i = 0; i < add_to_cookie_copy.size(); ++i) {
801 if (ContainsGaiaAccount(gaia_accounts, add_to_cookie_copy[i])) {
802 OnAddAccountToCookieCompleted(add_to_cookie_copy[i],
803 GoogleServiceAuthError::AuthErrorNone());
804 } else {
805 PerformMergeAction(add_to_cookie_copy[i]);
806 if (!ContainsGaiaAccount(original_gaia_accounts, add_to_cookie_copy[i])) {
807 added_to_cookie++;
808 }
809 }
810 }
811
812 signin_metrics::LogSigninAccountReconciliation(
813 chrome_accounts.size(), added_to_cookie, removed_from_cookie,
814 !first_account_mismatch, first_execution_, number_gaia_accounts);
815 first_execution_ = false;
816 CalculateIfReconcileIsDone();
817 if (!is_reconcile_started_)
818 delegate_->OnReconcileFinished(first_account);
819 ScheduleStartReconcileIfChromeAccountsChanged();
820 }
821
AbortReconcile()822 void AccountReconcilor::AbortReconcile() {
823 VLOG(1) << "AccountReconcilor::AbortReconcile: try again later";
824 log_out_in_progress_ = false;
825 add_to_cookie_.clear();
826 CalculateIfReconcileIsDone();
827
828 DCHECK(!is_reconcile_started_);
829 DCHECK(!timer_->IsRunning());
830 }
831
CalculateIfReconcileIsDone()832 void AccountReconcilor::CalculateIfReconcileIsDone() {
833 base::TimeDelta duration = base::Time::Now() - reconcile_start_time_;
834 // Record the duration if reconciliation was underway and now it is over.
835 if (is_reconcile_started_ && add_to_cookie_.empty() &&
836 !log_out_in_progress_) {
837 bool was_last_reconcile_successful =
838 (error_during_last_reconcile_.state() ==
839 GoogleServiceAuthError::State::NONE);
840 signin_metrics::LogSigninAccountReconciliationDuration(
841 duration, was_last_reconcile_successful);
842
843 // Reconciliation has actually finished (and hence stop the timer), but it
844 // may have ended in some failures. Pass this information to the
845 // |delegate_|.
846 timer_->Stop();
847 if (!was_last_reconcile_successful) {
848 // Note: This is the only call to |OnReconcileError| in this file. We MUST
849 // make sure that we do not call |OnReconcileError| multiple times in the
850 // same reconciliation batch.
851 // The enclosing if-condition |is_reconcile_started_ &&
852 // add_to_cookie_.empty()| represents the halting condition for one batch
853 // of reconciliation.
854 delegate_->OnReconcileError(error_during_last_reconcile_);
855 }
856 }
857
858 is_reconcile_started_ = !add_to_cookie_.empty() || log_out_in_progress_;
859 if (!is_reconcile_started_)
860 VLOG(1) << "AccountReconcilor::CalculateIfReconcileIsDone: done";
861 }
862
ScheduleStartReconcileIfChromeAccountsChanged()863 void AccountReconcilor::ScheduleStartReconcileIfChromeAccountsChanged() {
864 if (is_reconcile_started_)
865 return;
866
867 if (GetState() == AccountReconcilorState::ACCOUNT_RECONCILOR_SCHEDULED)
868 return;
869
870 // Start a reconcile as the token accounts have changed.
871 VLOG(1) << "AccountReconcilor::StartReconcileIfChromeAccountsChanged";
872 if (chrome_accounts_changed_) {
873 chrome_accounts_changed_ = false;
874 SetState(AccountReconcilorState::ACCOUNT_RECONCILOR_SCHEDULED);
875 base::ThreadTaskRunnerHandle::Get()->PostTask(
876 FROM_HERE, base::BindOnce(&AccountReconcilor::StartReconcile,
877 base::Unretained(this)));
878 } else if (error_during_last_reconcile_.state() ==
879 GoogleServiceAuthError::NONE) {
880 SetState(AccountReconcilorState::ACCOUNT_RECONCILOR_OK);
881 } else {
882 SetState(AccountReconcilorState::ACCOUNT_RECONCILOR_ERROR);
883 }
884 }
885
886 // Remove the account from the list that is being merged.
MarkAccountAsAddedToCookie(const CoreAccountId & account_id)887 bool AccountReconcilor::MarkAccountAsAddedToCookie(
888 const CoreAccountId& account_id) {
889 for (auto i = add_to_cookie_.begin(); i != add_to_cookie_.end(); ++i) {
890 if (account_id == *i) {
891 add_to_cookie_.erase(i);
892 return true;
893 }
894 }
895 return false;
896 }
897
IsIdentityManagerReady()898 bool AccountReconcilor::IsIdentityManagerReady() {
899 return identity_manager_->AreRefreshTokensLoaded();
900 }
901
OnSetAccountsInCookieCompleted(signin::SetAccountsInCookieResult result)902 void AccountReconcilor::OnSetAccountsInCookieCompleted(
903 signin::SetAccountsInCookieResult result) {
904 VLOG(1) << "AccountReconcilor::OnSetAccountsInCookieCompleted: "
905 << "Error was " << static_cast<int>(result);
906 if (is_reconcile_started_) {
907 if (result != signin::SetAccountsInCookieResult::kSuccess &&
908 !error_during_last_reconcile_.IsPersistentError()) {
909 error_during_last_reconcile_ =
910 result == signin::SetAccountsInCookieResult::kTransientError
911 ? GoogleServiceAuthError(
912 GoogleServiceAuthError::CONNECTION_FAILED)
913 : GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_ERROR);
914 delegate_->OnReconcileError(error_during_last_reconcile_);
915 }
916
917 set_accounts_in_progress_ = false;
918 is_reconcile_started_ = false;
919
920 timer_->Stop();
921 base::TimeDelta duration = base::Time::Now() - reconcile_start_time_;
922 signin_metrics::LogSigninAccountReconciliationDuration(
923 duration, (error_during_last_reconcile_.state() ==
924 GoogleServiceAuthError::State::NONE));
925 ScheduleStartReconcileIfChromeAccountsChanged();
926 }
927 }
928
OnAddAccountToCookieCompleted(const CoreAccountId & account_id,const GoogleServiceAuthError & error)929 void AccountReconcilor::OnAddAccountToCookieCompleted(
930 const CoreAccountId& account_id,
931 const GoogleServiceAuthError& error) {
932 VLOG(1) << "AccountReconcilor::OnAddAccountToCookieCompleted: "
933 << "Account added: " << account_id << ", "
934 << "Error was " << error.ToString();
935 // Always listens to GaiaCookieManagerService. Only proceed if reconciling.
936 if (is_reconcile_started_ && MarkAccountAsAddedToCookie(account_id)) {
937 // We may have seen a series of errors during reconciliation. Delegates may
938 // rely on the severity of the last seen error (see |OnReconcileError|) and
939 // hence do not override a persistent error, if we have seen one.
940 if (error.state() != GoogleServiceAuthError::State::NONE &&
941 !error_during_last_reconcile_.IsPersistentError()) {
942 error_during_last_reconcile_ = error;
943 }
944 CalculateIfReconcileIsDone();
945 ScheduleStartReconcileIfChromeAccountsChanged();
946 }
947 }
948
OnLogOutFromCookieCompleted(const GoogleServiceAuthError & error)949 void AccountReconcilor::OnLogOutFromCookieCompleted(
950 const GoogleServiceAuthError& error) {
951 VLOG(1) << "AccountReconcilor::OnLogOutFromCookieCompleted: "
952 << "Error was " << error.ToString();
953
954 if (is_reconcile_started_) {
955 if (error.state() != GoogleServiceAuthError::State::NONE &&
956 !error_during_last_reconcile_.IsPersistentError()) {
957 error_during_last_reconcile_ = error;
958 }
959
960 log_out_in_progress_ = false;
961 CalculateIfReconcileIsDone();
962 ScheduleStartReconcileIfChromeAccountsChanged();
963 }
964 }
965
IncrementLockCount()966 void AccountReconcilor::IncrementLockCount() {
967 DCHECK_GE(account_reconcilor_lock_count_, 0);
968 ++account_reconcilor_lock_count_;
969 if (account_reconcilor_lock_count_ == 1)
970 BlockReconcile();
971 }
972
DecrementLockCount()973 void AccountReconcilor::DecrementLockCount() {
974 DCHECK_GT(account_reconcilor_lock_count_, 0);
975 --account_reconcilor_lock_count_;
976 if (account_reconcilor_lock_count_ == 0)
977 UnblockReconcile();
978 }
979
IsReconcileBlocked() const980 bool AccountReconcilor::IsReconcileBlocked() const {
981 DCHECK_GE(account_reconcilor_lock_count_, 0);
982 return account_reconcilor_lock_count_ > 0;
983 }
984
BlockReconcile()985 void AccountReconcilor::BlockReconcile() {
986 DCHECK(IsReconcileBlocked());
987 VLOG(1) << "AccountReconcilor::BlockReconcile.";
988 if (is_reconcile_started_) {
989 AbortReconcile();
990 SetState(AccountReconcilorState::ACCOUNT_RECONCILOR_SCHEDULED);
991 reconcile_on_unblock_ = true;
992 }
993 for (auto& observer : observer_list_)
994 observer.OnBlockReconcile();
995 }
996
UnblockReconcile()997 void AccountReconcilor::UnblockReconcile() {
998 DCHECK(!IsReconcileBlocked());
999 VLOG(1) << "AccountReconcilor::UnblockReconcile.";
1000 for (auto& observer : observer_list_)
1001 observer.OnUnblockReconcile();
1002 if (reconcile_on_unblock_) {
1003 reconcile_on_unblock_ = false;
1004 StartReconcile();
1005 }
1006 }
1007
set_timer_for_testing(std::unique_ptr<base::OneShotTimer> timer)1008 void AccountReconcilor::set_timer_for_testing(
1009 std::unique_ptr<base::OneShotTimer> timer) {
1010 timer_ = std::move(timer);
1011 }
1012
HandleReconcileTimeout()1013 void AccountReconcilor::HandleReconcileTimeout() {
1014 // A reconciliation was still succesfully in progress but could not complete
1015 // in the given time. For a delegate, this is equivalent to a
1016 // |GoogleServiceAuthError::State::CONNECTION_FAILED|.
1017 if (error_during_last_reconcile_.state() ==
1018 GoogleServiceAuthError::State::NONE) {
1019 error_during_last_reconcile_ = GoogleServiceAuthError(
1020 GoogleServiceAuthError::State::CONNECTION_FAILED);
1021 }
1022
1023 // Will stop reconciliation and inform |delegate_| about
1024 // |error_during_last_reconcile_|, through |CalculateIfReconcileIsDone|.
1025 AbortReconcile();
1026 DCHECK(!timer_->IsRunning());
1027 }
1028
IsMultiloginEndpointEnabled() const1029 bool AccountReconcilor::IsMultiloginEndpointEnabled() const {
1030 return delegate_->IsMultiloginEndpointEnabled();
1031 }
1032
CookieNeedsUpdate(const signin::MultiloginParameters & parameters,const std::vector<gaia::ListedAccount> & existing_accounts)1033 bool AccountReconcilor::CookieNeedsUpdate(
1034 const signin::MultiloginParameters& parameters,
1035 const std::vector<gaia::ListedAccount>& existing_accounts) {
1036 bool should_remove_unknown_account =
1037 !delegate_->IsUnknownInvalidAccountInCookieAllowed() &&
1038 HasUnknownInvalidAccountInCookie(identity_manager_, existing_accounts);
1039 if (should_remove_unknown_account) {
1040 // Removing unknown accounts in the cookie is only supported for UPDATE
1041 // mode.
1042 DCHECK_EQ(parameters.mode,
1043 gaia::MultiloginMode::MULTILOGIN_UPDATE_COOKIE_ACCOUNTS_ORDER);
1044 return true;
1045 }
1046
1047 if (parameters.mode ==
1048 gaia::MultiloginMode::MULTILOGIN_UPDATE_COOKIE_ACCOUNTS_ORDER &&
1049 !existing_accounts.empty() && !parameters.accounts_to_send.empty() &&
1050 existing_accounts[0].id != parameters.accounts_to_send[0]) {
1051 // In UPDATE mode update is needed if first accounts don't match.
1052 return true;
1053 }
1054
1055 // Maybe some accounts in cookies are not valid and need refreshing.
1056 std::set<CoreAccountId> accounts_to_send_set(
1057 parameters.accounts_to_send.begin(), parameters.accounts_to_send.end());
1058 std::set<CoreAccountId> existing_accounts_set;
1059 for (const gaia::ListedAccount& account : existing_accounts) {
1060 if (account.valid)
1061 existing_accounts_set.insert(account.id);
1062 }
1063 return (existing_accounts_set != accounts_to_send_set);
1064 }
1065
SetState(AccountReconcilorState state)1066 void AccountReconcilor::SetState(AccountReconcilorState state) {
1067 if (state == state_)
1068 return;
1069
1070 state_ = state;
1071 for (auto& observer : observer_list_)
1072 observer.OnStateChanged(state_);
1073 }
1074