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