1 // Copyright 2017 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/dice_account_reconcilor_delegate.h"
6 
7 #include <vector>
8 
9 #include "base/logging.h"
10 #include "base/metrics/histogram_macros.h"
11 #include "base/stl_util.h"
12 #include "components/prefs/pref_service.h"
13 #include "components/signin/public/base/signin_client.h"
14 #include "components/signin/public/base/signin_pref_names.h"
15 
16 const base::Feature kUseMultiloginEndpoint{"UseMultiloginEndpoint",
17                                            base::FEATURE_DISABLED_BY_DEFAULT};
18 
19 namespace signin {
20 
DiceAccountReconcilorDelegate(SigninClient * signin_client,bool migration_completed)21 DiceAccountReconcilorDelegate::DiceAccountReconcilorDelegate(
22     SigninClient* signin_client,
23     bool migration_completed)
24     : signin_client_(signin_client),
25       migration_completed_(migration_completed) {
26   DCHECK(signin_client_);
27 }
28 
IsReconcileEnabled() const29 bool DiceAccountReconcilorDelegate::IsReconcileEnabled() const {
30   return true;
31 }
32 
IsMultiloginEndpointEnabled() const33 bool DiceAccountReconcilorDelegate::IsMultiloginEndpointEnabled() const {
34   return base::FeatureList::IsEnabled(kUseMultiloginEndpoint);
35 }
36 
IsAccountConsistencyEnforced() const37 bool DiceAccountReconcilorDelegate::IsAccountConsistencyEnforced() const {
38   return true;
39 }
40 
41 DiceAccountReconcilorDelegate::InconsistencyReason
GetInconsistencyReason(const CoreAccountId & primary_account,const std::vector<CoreAccountId> & chrome_accounts,const std::vector<gaia::ListedAccount> & gaia_accounts,bool first_execution) const42 DiceAccountReconcilorDelegate::GetInconsistencyReason(
43     const CoreAccountId& primary_account,
44     const std::vector<CoreAccountId>& chrome_accounts,
45     const std::vector<gaia::ListedAccount>& gaia_accounts,
46     bool first_execution) const {
47   std::vector<CoreAccountId> valid_gaia_accounts_ids;
48   for (const gaia::ListedAccount& gaia_account : gaia_accounts) {
49     if (gaia_account.valid)
50       valid_gaia_accounts_ids.push_back(gaia_account.id);
51   }
52 
53   bool primary_account_has_token = false;
54   if (!primary_account.empty()) {
55     primary_account_has_token =
56         base::Contains(chrome_accounts, primary_account);
57     bool primary_account_has_cookie =
58         base::Contains(valid_gaia_accounts_ids, primary_account);
59     if (primary_account_has_token && !primary_account_has_cookie)
60       return InconsistencyReason::kMissingSyncCookie;
61 
62     if (!primary_account_has_token && primary_account_has_cookie)
63       return InconsistencyReason::kSyncAccountAuthError;
64   }
65 
66   bool missing_first_web_account_token =
67       primary_account.empty() && !gaia_accounts.empty() &&
68       gaia_accounts[0].valid &&
69       !base::Contains(chrome_accounts, gaia_accounts[0].id);
70 
71   if (missing_first_web_account_token)
72     return InconsistencyReason::kMissingFirstWebAccountToken;
73 
74   std::sort(valid_gaia_accounts_ids.begin(), valid_gaia_accounts_ids.end());
75   std::vector<CoreAccountId> sorted_chrome_accounts(chrome_accounts);
76   std::sort(sorted_chrome_accounts.begin(), sorted_chrome_accounts.end());
77   bool missing_token =
78       !base::STLIncludes(sorted_chrome_accounts, valid_gaia_accounts_ids);
79   bool missing_cookie =
80       !base::STLIncludes(valid_gaia_accounts_ids, sorted_chrome_accounts);
81 
82   if (missing_token && missing_cookie)
83     return InconsistencyReason::kCookieTokenMismatch;
84 
85   if (missing_token)
86     return InconsistencyReason::kMissingSecondaryToken;
87 
88   if (missing_cookie)
89     return InconsistencyReason::kMissingSecondaryCookie;
90 
91   if (first_execution && primary_account_has_token &&
92       gaia_accounts[0].id != primary_account && gaia_accounts[0].valid)
93     return InconsistencyReason::kSyncCookieNotFirst;
94 
95   return InconsistencyReason::kNone;
96 }
97 
GetGaiaApiSource() const98 gaia::GaiaSource DiceAccountReconcilorDelegate::GetGaiaApiSource() const {
99   return gaia::GaiaSource::kAccountReconcilorDice;
100 }
101 
102 // - On first execution, the candidates are examined in this order:
103 //   1. The primary account
104 //   2. The current first Gaia account
105 //   3. The last known first Gaia account
106 //   4. The first account in the token service
107 // - On subsequent executions, the order is:
108 //   1. The current first Gaia account
109 //   2. The primary account
110 //   3. The last known first Gaia account
111 //   4. The first account in the token service
GetFirstGaiaAccountForReconcile(const std::vector<CoreAccountId> & chrome_accounts,const std::vector<gaia::ListedAccount> & gaia_accounts,const CoreAccountId & primary_account,bool first_execution,bool will_logout) const112 CoreAccountId DiceAccountReconcilorDelegate::GetFirstGaiaAccountForReconcile(
113     const std::vector<CoreAccountId>& chrome_accounts,
114     const std::vector<gaia::ListedAccount>& gaia_accounts,
115     const CoreAccountId& primary_account,
116     bool first_execution,
117     bool will_logout) const {
118   bool primary_account_has_token =
119       !primary_account.empty() &&
120       base::Contains(chrome_accounts, primary_account);
121 
122   if (gaia_accounts.empty()) {
123     if (primary_account_has_token)
124       return primary_account;
125 
126     // Try the last known account. This happens when the cookies are cleared
127     // while Sync is disabled.
128     if (base::Contains(chrome_accounts, last_known_first_account_))
129       return last_known_first_account_;
130 
131     // As a last resort, use the first Chrome account.
132     return chrome_accounts.empty() ? CoreAccountId() : chrome_accounts[0];
133   }
134 
135   const CoreAccountId& first_gaia_account = gaia_accounts[0].id;
136   bool first_gaia_account_has_token =
137       base::Contains(chrome_accounts, first_gaia_account);
138 
139   if (!first_gaia_account_has_token &&
140       (primary_account == first_gaia_account) && gaia_accounts[0].valid) {
141     // The primary account is also the first Gaia account, and has no token.
142     // Logout everything.
143     return CoreAccountId();
144   }
145 
146   // If the primary Chrome account and the default Gaia account are both in
147   // error, then the first gaia account can be kept, to avoid logging the user
148   // out of their other accounts.
149   // It's only possible when the reconcilor will not perform a logout, because
150   // that account cannot be rebuilt.
151   if (!first_gaia_account_has_token && !gaia_accounts[0].valid && !will_logout)
152     return first_gaia_account;
153 
154   if (first_execution) {
155     // On first execution, try the primary account, and then the first Gaia
156     // account.
157     if (primary_account_has_token)
158       return primary_account;
159     if (first_gaia_account_has_token)
160       return first_gaia_account;
161     // As a last resort, use the first Chrome account.
162     return chrome_accounts.empty() ? CoreAccountId() : chrome_accounts[0];
163   }
164 
165   // While Chrome is running, try the first Gaia account, and then the
166   // primary account.
167   if (first_gaia_account_has_token)
168     return first_gaia_account;
169   if (primary_account_has_token)
170     return primary_account;
171 
172   // Changing the first Gaia account while Chrome is running would be
173   // confusing for the user. Logout everything.
174   return CoreAccountId();
175 }
176 
CalculateModeForReconcile(const std::vector<gaia::ListedAccount> & gaia_accounts,const CoreAccountId & primary_account,bool first_execution,bool primary_has_error) const177 gaia::MultiloginMode DiceAccountReconcilorDelegate::CalculateModeForReconcile(
178     const std::vector<gaia::ListedAccount>& gaia_accounts,
179     const CoreAccountId& primary_account,
180     bool first_execution,
181     bool primary_has_error) const {
182   const bool sync_enabled = !primary_account.empty();
183   const bool first_gaia_is_primary =
184       !gaia_accounts.empty() && (gaia_accounts[0].id == primary_account);
185   return sync_enabled && first_execution && !primary_has_error &&
186                  !first_gaia_is_primary
187              ? gaia::MultiloginMode::MULTILOGIN_UPDATE_COOKIE_ACCOUNTS_ORDER
188              : gaia::MultiloginMode::MULTILOGIN_PRESERVE_COOKIE_ACCOUNTS_ORDER;
189 }
190 
191 std::vector<CoreAccountId>
GetChromeAccountsForReconcile(const std::vector<CoreAccountId> & chrome_accounts,const CoreAccountId & primary_account,const std::vector<gaia::ListedAccount> & gaia_accounts,const gaia::MultiloginMode mode) const192 DiceAccountReconcilorDelegate::GetChromeAccountsForReconcile(
193     const std::vector<CoreAccountId>& chrome_accounts,
194     const CoreAccountId& primary_account,
195     const std::vector<gaia::ListedAccount>& gaia_accounts,
196     const gaia::MultiloginMode mode) const {
197   if (mode == gaia::MultiloginMode::MULTILOGIN_UPDATE_COOKIE_ACCOUNTS_ORDER) {
198     return ReorderChromeAccountsForReconcile(chrome_accounts, primary_account,
199                                              gaia_accounts);
200   }
201   if (gaia_accounts.empty() &&
202       base::Contains(chrome_accounts, last_known_first_account_)) {
203     // In PRESERVE mode in case accounts in cookies are accidentally lost we
204     // should put cached first account first since Gaia has no information about
205     // it.
206     return ReorderChromeAccountsForReconcile(
207         chrome_accounts, last_known_first_account_, gaia_accounts);
208   }
209   return chrome_accounts;
210 }
211 
212 AccountReconcilorDelegate::RevokeTokenOption
ShouldRevokeSecondaryTokensBeforeReconcile(const std::vector<gaia::ListedAccount> & gaia_accounts)213 DiceAccountReconcilorDelegate::ShouldRevokeSecondaryTokensBeforeReconcile(
214     const std::vector<gaia::ListedAccount>& gaia_accounts) {
215   return RevokeTokenOption::kRevokeIfInError;
216 }
217 
ShouldRevokeTokensNotInCookies() const218 bool DiceAccountReconcilorDelegate::ShouldRevokeTokensNotInCookies() const {
219   return !migration_completed_;
220 }
221 
OnRevokeTokensNotInCookiesCompleted(RevokeTokenAction revoke_token_action)222 void DiceAccountReconcilorDelegate::OnRevokeTokensNotInCookiesCompleted(
223     RevokeTokenAction revoke_token_action) {
224   migration_completed_ = true;
225   signin_client_->SetDiceMigrationCompleted();
226   UMA_HISTOGRAM_ENUMERATION("ForceDiceMigration.RevokeTokenAction",
227                             revoke_token_action);
228 }
229 
ShouldRevokeTokensOnCookieDeleted()230 bool DiceAccountReconcilorDelegate::ShouldRevokeTokensOnCookieDeleted() {
231   return true;
232 }
233 
OnReconcileFinished(const CoreAccountId & first_account)234 void DiceAccountReconcilorDelegate::OnReconcileFinished(
235     const CoreAccountId& first_account) {
236   last_known_first_account_ = first_account;
237 }
238 
239 }  // namespace signin
240