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.
5 #include "components/signin/core/browser/account_reconcilor.h"
10 #include "base/json/json_reader.h"
11 #include "base/location.h"
12 #include "base/logging.h"
13 #include "base/single_thread_task_runner.h"
14 #include "base/strings/string_number_conversions.h"
15 #include "base/thread_task_runner_handle.h"
16 #include "components/signin/core/browser/profile_oauth2_token_service.h"
17 #include "components/signin/core/browser/signin_client.h"
18 #include "components/signin/core/browser/signin_metrics.h"
19 #include "components/signin/core/common/profile_management_switches.h"
20 #include "google_apis/gaia/gaia_auth_util.h"
21 #include "google_apis/gaia/gaia_oauth_client.h"
22 #include "google_apis/gaia/gaia_urls.h"
26 class AccountEqualToFunc
: public std::equal_to
<gaia::ListedAccount
> {
28 bool operator()(const gaia::ListedAccount
& p1
,
29 const gaia::ListedAccount
& p2
) const;
32 bool AccountEqualToFunc::operator()(
33 const gaia::ListedAccount
& p1
,
34 const gaia::ListedAccount
& p2
) const {
35 return p1
.valid
== p2
.valid
&& p1
.id
== p2
.id
;
38 gaia::ListedAccount
AccountForId(const std::string
& account_id
) {
39 gaia::ListedAccount account
;
40 account
.id
= account_id
;
41 account
.gaia_id
= std::string();
42 account
.email
= std::string();
50 AccountReconcilor::AccountReconcilor(
51 ProfileOAuth2TokenService
* token_service
,
52 SigninManagerBase
* signin_manager
,
54 GaiaCookieManagerService
* cookie_manager_service
)
55 : token_service_(token_service
),
56 signin_manager_(signin_manager
),
58 cookie_manager_service_(cookie_manager_service
),
59 registered_with_token_service_(false),
60 registered_with_cookie_manager_service_(false),
61 registered_with_content_settings_(false),
62 is_reconcile_started_(false),
63 first_execution_(true),
64 error_during_last_reconcile_(false),
65 chrome_accounts_changed_(false) {
66 VLOG(1) << "AccountReconcilor::AccountReconcilor";
69 AccountReconcilor::~AccountReconcilor() {
70 VLOG(1) << "AccountReconcilor::~AccountReconcilor";
71 // Make sure shutdown was called first.
72 DCHECK(!registered_with_token_service_
);
73 DCHECK(!registered_with_cookie_manager_service_
);
76 void AccountReconcilor::Initialize(bool start_reconcile_if_tokens_available
) {
77 VLOG(1) << "AccountReconcilor::Initialize";
78 RegisterWithSigninManager();
80 // If this user is not signed in, the reconcilor should do nothing but
82 if (IsProfileConnected()) {
83 RegisterWithCookieManagerService();
84 RegisterWithContentSettings();
85 RegisterWithTokenService();
87 // Start a reconcile if the tokens are already loaded.
88 if (start_reconcile_if_tokens_available
&&
89 token_service_
->GetAccounts().size() > 0) {
95 void AccountReconcilor::Shutdown() {
96 VLOG(1) << "AccountReconcilor::Shutdown";
97 UnregisterWithCookieManagerService();
98 UnregisterWithSigninManager();
99 UnregisterWithTokenService();
100 UnregisterWithContentSettings();
103 void AccountReconcilor::RegisterWithSigninManager() {
104 signin_manager_
->AddObserver(this);
107 void AccountReconcilor::UnregisterWithSigninManager() {
108 signin_manager_
->RemoveObserver(this);
111 void AccountReconcilor::RegisterWithContentSettings() {
112 VLOG(1) << "AccountReconcilor::RegisterWithContentSettings";
113 // During re-auth, the reconcilor will get a callback about successful signin
114 // even when the profile is already connected. Avoid re-registering
115 // with the token service since this will DCHECK.
116 if (registered_with_content_settings_
)
119 client_
->AddContentSettingsObserver(this);
120 registered_with_content_settings_
= true;
123 void AccountReconcilor::UnregisterWithContentSettings() {
124 VLOG(1) << "AccountReconcilor::UnregisterWithContentSettings";
125 if (!registered_with_content_settings_
)
128 client_
->RemoveContentSettingsObserver(this);
129 registered_with_content_settings_
= false;
132 void AccountReconcilor::RegisterWithTokenService() {
133 VLOG(1) << "AccountReconcilor::RegisterWithTokenService";
134 // During re-auth, the reconcilor will get a callback about successful signin
135 // even when the profile is already connected. Avoid re-registering
136 // with the token service since this will DCHECK.
137 if (registered_with_token_service_
)
140 token_service_
->AddObserver(this);
141 registered_with_token_service_
= true;
144 void AccountReconcilor::UnregisterWithTokenService() {
145 VLOG(1) << "AccountReconcilor::UnregisterWithTokenService";
146 if (!registered_with_token_service_
)
149 token_service_
->RemoveObserver(this);
150 registered_with_token_service_
= false;
153 void AccountReconcilor::RegisterWithCookieManagerService() {
154 VLOG(1) << "AccountReconcilor::RegisterWithCookieManagerService";
155 // During re-auth, the reconcilor will get a callback about successful signin
156 // even when the profile is already connected. Avoid re-registering
157 // with the helper since this will DCHECK.
158 if (registered_with_cookie_manager_service_
)
161 cookie_manager_service_
->AddObserver(this);
162 registered_with_cookie_manager_service_
= true;
164 void AccountReconcilor::UnregisterWithCookieManagerService() {
165 VLOG(1) << "AccountReconcilor::UnregisterWithCookieManagerService";
166 if (!registered_with_cookie_manager_service_
)
169 cookie_manager_service_
->RemoveObserver(this);
170 registered_with_cookie_manager_service_
= false;
173 bool AccountReconcilor::IsProfileConnected() {
174 return signin_manager_
->IsAuthenticated();
177 signin_metrics::AccountReconcilorState
AccountReconcilor::GetState() {
178 if (!is_reconcile_started_
) {
179 return error_during_last_reconcile_
180 ? signin_metrics::ACCOUNT_RECONCILOR_ERROR
181 : signin_metrics::ACCOUNT_RECONCILOR_OK
;
184 return signin_metrics::ACCOUNT_RECONCILOR_RUNNING
;
187 void AccountReconcilor::OnContentSettingChanged(
188 const ContentSettingsPattern
& primary_pattern
,
189 const ContentSettingsPattern
& secondary_pattern
,
190 ContentSettingsType content_type
,
191 std::string resource_identifier
) {
192 // If this is not a change to cookie settings, just ignore.
193 if (content_type
!= CONTENT_SETTINGS_TYPE_COOKIES
)
196 // If this does not affect GAIA, just ignore. If the primary pattern is
197 // invalid, then assume it could affect GAIA. The secondary pattern is
199 if (primary_pattern
.IsValid() &&
200 !primary_pattern
.Matches(GaiaUrls::GetInstance()->gaia_url())) {
204 VLOG(1) << "AccountReconcilor::OnContentSettingChanged";
208 void AccountReconcilor::OnEndBatchChanges() {
209 VLOG(1) << "AccountReconcilor::OnEndBatchChanges. "
210 << "Reconcilor state: " << is_reconcile_started_
;
211 // Remember that accounts have changed if a reconcile is already started.
212 chrome_accounts_changed_
= is_reconcile_started_
;
216 void AccountReconcilor::GoogleSigninSucceeded(const std::string
& account_id
,
217 const std::string
& username
,
218 const std::string
& password
) {
219 VLOG(1) << "AccountReconcilor::GoogleSigninSucceeded: signed in";
220 RegisterWithCookieManagerService();
221 RegisterWithContentSettings();
222 RegisterWithTokenService();
225 void AccountReconcilor::GoogleSignedOut(const std::string
& account_id
,
226 const std::string
& username
) {
227 VLOG(1) << "AccountReconcilor::GoogleSignedOut: signed out";
229 UnregisterWithCookieManagerService();
230 UnregisterWithTokenService();
231 UnregisterWithContentSettings();
232 PerformLogoutAllAccountsAction();
235 void AccountReconcilor::PerformMergeAction(const std::string
& account_id
) {
236 if (!switches::IsEnableAccountConsistency()) {
237 MarkAccountAsAddedToCookie(account_id
);
240 VLOG(1) << "AccountReconcilor::PerformMergeAction: " << account_id
;
241 cookie_manager_service_
->AddAccountToCookie(account_id
);
244 void AccountReconcilor::PerformLogoutAllAccountsAction() {
245 if (!switches::IsEnableAccountConsistency())
247 VLOG(1) << "AccountReconcilor::PerformLogoutAllAccountsAction";
248 cookie_manager_service_
->LogOutAllAccounts();
251 void AccountReconcilor::StartReconcile() {
252 if (!IsProfileConnected() || !client_
->AreSigninCookiesAllowed()) {
253 VLOG(1) << "AccountReconcilor::StartReconcile: !connected or no cookies";
257 if (is_reconcile_started_
)
260 // Reset state for validating gaia cookie.
261 gaia_accounts_
.clear();
263 // Reset state for validating oauth2 tokens.
264 primary_account_
.clear();
265 chrome_accounts_
.clear();
266 add_to_cookie_
.clear();
267 ValidateAccountsFromTokenService();
269 if (primary_account_
.empty()) {
270 VLOG(1) << "AccountReconcilor::StartReconcile: primary has error";
274 is_reconcile_started_
= true;
275 error_during_last_reconcile_
= false;
277 // Rely on the GCMS to manage calls to and responses from ListAccounts.
278 if (cookie_manager_service_
->ListAccounts(&gaia_accounts_
)) {
279 OnGaiaAccountsInCookieUpdated(
280 gaia_accounts_
, GoogleServiceAuthError(GoogleServiceAuthError::NONE
));
284 void AccountReconcilor::OnGaiaAccountsInCookieUpdated(
285 const std::vector
<gaia::ListedAccount
>& accounts
,
286 const GoogleServiceAuthError
& error
) {
287 VLOG(1) << "AccountReconcilor::OnGaiaAccountsInCookieUpdated: "
288 << "CookieJar " << accounts
.size() << " accounts, "
289 << "Reconcilor's state is " << is_reconcile_started_
<< ", "
290 << "Error was " << error
.ToString();
291 if (error
.state() == GoogleServiceAuthError::NONE
) {
292 gaia_accounts_
= accounts
;
294 // It is possible that O2RT is not available at this moment.
295 if (token_service_
->GetAccounts().empty())
298 is_reconcile_started_
? FinishReconcile() : StartReconcile();
300 if (is_reconcile_started_
)
301 error_during_last_reconcile_
= true;
306 void AccountReconcilor::ValidateAccountsFromTokenService() {
307 primary_account_
= signin_manager_
->GetAuthenticatedAccountId();
308 DCHECK(!primary_account_
.empty());
310 chrome_accounts_
= token_service_
->GetAccounts();
312 // Remove any accounts that have an error. There is no point in trying to
313 // reconcile them, since it won't work anyway. If the list ends up being
314 // empty, or if the primary account is in error, then don't reconcile any
316 for (auto i
= chrome_accounts_
.begin(); i
!= chrome_accounts_
.end(); ++i
) {
317 if (token_service_
->GetDelegate()->RefreshTokenHasError(*i
)) {
318 if (primary_account_
== *i
) {
319 primary_account_
.clear();
320 chrome_accounts_
.clear();
323 VLOG(1) << "AccountReconcilor::ValidateAccountsFromTokenService: "
324 << *i
<< " has error, won't reconcile";
329 chrome_accounts_
.erase(std::remove(chrome_accounts_
.begin(),
330 chrome_accounts_
.end(),
332 chrome_accounts_
.end());
334 VLOG(1) << "AccountReconcilor::ValidateAccountsFromTokenService: "
335 << "Chrome " << chrome_accounts_
.size() << " accounts, "
336 << "Primary is '" << primary_account_
<< "'";
339 void AccountReconcilor::OnNewProfileManagementFlagChanged(
340 bool new_flag_status
) {
341 if (new_flag_status
) {
342 // The reconciler may have been newly created just before this call, or may
343 // have already existed and in mid-reconcile. To err on the safe side, force
352 void AccountReconcilor::FinishReconcile() {
353 VLOG(1) << "AccountReconcilor::FinishReconcile";
354 DCHECK(add_to_cookie_
.empty());
355 int number_gaia_accounts
= gaia_accounts_
.size();
356 bool are_primaries_equal
= number_gaia_accounts
> 0 &&
357 primary_account_
== gaia_accounts_
[0].id
;
359 // If there are any accounts in the gaia cookie but not in chrome, then
360 // those accounts need to be removed from the cookie. This means we need
361 // to blow the cookie away.
362 int removed_from_cookie
= 0;
363 for (size_t i
= 0; i
< gaia_accounts_
.size(); ++i
) {
364 if (gaia_accounts_
[i
].valid
&&
365 chrome_accounts_
.end() == std::find(chrome_accounts_
.begin(),
366 chrome_accounts_
.end(),
367 gaia_accounts_
[i
].id
)) {
368 ++removed_from_cookie
;
372 bool rebuild_cookie
= !are_primaries_equal
|| removed_from_cookie
> 0;
373 std::vector
<gaia::ListedAccount
> original_gaia_accounts
=
375 if (rebuild_cookie
) {
376 VLOG(1) << "AccountReconcilor::FinishReconcile: rebuild cookie";
377 // Really messed up state. Blow away the gaia cookie completely and
378 // rebuild it, making sure the primary account as specified by the
379 // SigninManager is the first session in the gaia cookie.
380 PerformLogoutAllAccountsAction();
381 gaia_accounts_
.clear();
384 // Create a list of accounts that need to be added to the gaia cookie.
385 // The primary account must be first to make sure it becomes the default
386 // account in the case where chrome is completely rebuilding the cookie.
387 add_to_cookie_
.push_back(primary_account_
);
388 for (size_t i
= 0; i
< chrome_accounts_
.size(); ++i
) {
389 if (chrome_accounts_
[i
] != primary_account_
)
390 add_to_cookie_
.push_back(chrome_accounts_
[i
]);
393 // For each account known to chrome, PerformMergeAction() if the account is
394 // not already in the cookie jar or its state is invalid, or signal merge
395 // completed otherwise. Make a copy of |add_to_cookie_| since calls to
396 // SignalComplete() will change the array.
397 std::vector
<std::string
> add_to_cookie_copy
= add_to_cookie_
;
398 int added_to_cookie
= 0;
399 for (size_t i
= 0; i
< add_to_cookie_copy
.size(); ++i
) {
400 if (gaia_accounts_
.end() !=
401 std::find_if(gaia_accounts_
.begin(),
402 gaia_accounts_
.end(),
403 std::bind1st(AccountEqualToFunc(),
404 AccountForId(add_to_cookie_copy
[i
])))) {
405 cookie_manager_service_
->SignalComplete(
406 add_to_cookie_copy
[i
],
407 GoogleServiceAuthError::AuthErrorNone());
409 PerformMergeAction(add_to_cookie_copy
[i
]);
410 if (original_gaia_accounts
.end() ==
411 std::find_if(original_gaia_accounts
.begin(),
412 original_gaia_accounts
.end(),
413 std::bind1st(AccountEqualToFunc(),
414 AccountForId(add_to_cookie_copy
[i
])))) {
420 signin_metrics::LogSigninAccountReconciliation(chrome_accounts_
.size(),
425 number_gaia_accounts
);
426 first_execution_
= false;
427 CalculateIfReconcileIsDone();
428 ScheduleStartReconcileIfChromeAccountsChanged();
431 void AccountReconcilor::AbortReconcile() {
432 VLOG(1) << "AccountReconcilor::AbortReconcile: we'll try again later";
433 add_to_cookie_
.clear();
434 CalculateIfReconcileIsDone();
437 void AccountReconcilor::CalculateIfReconcileIsDone() {
438 is_reconcile_started_
= !add_to_cookie_
.empty();
439 if (!is_reconcile_started_
)
440 VLOG(1) << "AccountReconcilor::CalculateIfReconcileIsDone: done";
443 void AccountReconcilor::ScheduleStartReconcileIfChromeAccountsChanged() {
444 if (is_reconcile_started_
)
447 // Start a reconcile as the token accounts have changed.
448 VLOG(1) << "AccountReconcilor::StartReconcileIfChromeAccountsChanged";
449 if (chrome_accounts_changed_
) {
450 chrome_accounts_changed_
= false;
451 base::ThreadTaskRunnerHandle::Get()->PostTask(
453 base::Bind(&AccountReconcilor::StartReconcile
, base::Unretained(this)));
457 // Remove the account from the list that is being merged.
458 bool AccountReconcilor::MarkAccountAsAddedToCookie(
459 const std::string
& account_id
) {
460 for (std::vector
<std::string
>::iterator i
= add_to_cookie_
.begin();
461 i
!= add_to_cookie_
.end();
463 if (account_id
== *i
) {
464 add_to_cookie_
.erase(i
);
471 void AccountReconcilor::OnAddAccountToCookieCompleted(
472 const std::string
& account_id
,
473 const GoogleServiceAuthError
& error
) {
474 VLOG(1) << "AccountReconcilor::OnAddAccountToCookieCompleted: "
475 << "Account added: " << account_id
<< ", "
476 << "Error was " << error
.ToString();
477 // Always listens to GaiaCookieManagerService. Only proceed if reconciling.
478 if (is_reconcile_started_
&& MarkAccountAsAddedToCookie(account_id
)) {
479 if (error
.state() != GoogleServiceAuthError::State::NONE
)
480 error_during_last_reconcile_
= true;
481 CalculateIfReconcileIsDone();
482 ScheduleStartReconcileIfChromeAccountsChanged();