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