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