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