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"
27 class AccountEqualToFunc
: public std::equal_to
<gaia::ListedAccount
> {
29 bool operator()(const gaia::ListedAccount
& p1
,
30 const gaia::ListedAccount
& p2
) const;
33 bool AccountEqualToFunc::operator()(
34 const gaia::ListedAccount
& p1
,
35 const gaia::ListedAccount
& p2
) const {
36 return p1
.valid
== p2
.valid
&& p1
.id
== p2
.id
;
39 gaia::ListedAccount
AccountForId(const std::string
& account_id
) {
40 gaia::ListedAccount account
;
41 account
.id
= account_id
;
42 account
.gaia_id
= std::string();
43 account
.email
= std::string();
51 AccountReconcilor::AccountReconcilor(
52 ProfileOAuth2TokenService
* token_service
,
53 SigninManagerBase
* signin_manager
,
55 GaiaCookieManagerService
* cookie_manager_service
)
56 : token_service_(token_service
),
57 signin_manager_(signin_manager
),
59 cookie_manager_service_(cookie_manager_service
),
60 registered_with_token_service_(false),
61 registered_with_cookie_manager_service_(false),
62 registered_with_content_settings_(false),
63 is_reconcile_started_(false),
64 first_execution_(true),
65 error_during_last_reconcile_(false),
66 chrome_accounts_changed_(false) {
67 VLOG(1) << "AccountReconcilor::AccountReconcilor";
70 AccountReconcilor::~AccountReconcilor() {
71 VLOG(1) << "AccountReconcilor::~AccountReconcilor";
72 // Make sure shutdown was called first.
73 DCHECK(!registered_with_token_service_
);
74 DCHECK(!registered_with_cookie_manager_service_
);
77 void AccountReconcilor::Initialize(bool start_reconcile_if_tokens_available
) {
78 VLOG(1) << "AccountReconcilor::Initialize";
79 RegisterWithSigninManager();
81 // If this user is not signed in, the reconcilor should do nothing but
83 if (IsProfileConnected()) {
84 RegisterWithCookieManagerService();
85 RegisterWithContentSettings();
86 RegisterWithTokenService();
88 // Start a reconcile if the tokens are already loaded.
89 if (start_reconcile_if_tokens_available
&&
90 token_service_
->GetAccounts().size() > 0) {
96 void AccountReconcilor::Shutdown() {
97 VLOG(1) << "AccountReconcilor::Shutdown";
98 UnregisterWithCookieManagerService();
99 UnregisterWithSigninManager();
100 UnregisterWithTokenService();
101 UnregisterWithContentSettings();
104 void AccountReconcilor::RegisterWithSigninManager() {
105 signin_manager_
->AddObserver(this);
108 void AccountReconcilor::UnregisterWithSigninManager() {
109 signin_manager_
->RemoveObserver(this);
112 void AccountReconcilor::RegisterWithContentSettings() {
113 VLOG(1) << "AccountReconcilor::RegisterWithContentSettings";
114 // During re-auth, the reconcilor will get a callback about successful signin
115 // even when the profile is already connected. Avoid re-registering
116 // with the token service since this will DCHECK.
117 if (registered_with_content_settings_
)
120 client_
->AddContentSettingsObserver(this);
121 registered_with_content_settings_
= true;
124 void AccountReconcilor::UnregisterWithContentSettings() {
125 VLOG(1) << "AccountReconcilor::UnregisterWithContentSettings";
126 if (!registered_with_content_settings_
)
129 client_
->RemoveContentSettingsObserver(this);
130 registered_with_content_settings_
= false;
133 void AccountReconcilor::RegisterWithTokenService() {
134 VLOG(1) << "AccountReconcilor::RegisterWithTokenService";
135 // During re-auth, the reconcilor will get a callback about successful signin
136 // even when the profile is already connected. Avoid re-registering
137 // with the token service since this will DCHECK.
138 if (registered_with_token_service_
)
141 token_service_
->AddObserver(this);
142 registered_with_token_service_
= true;
145 void AccountReconcilor::UnregisterWithTokenService() {
146 VLOG(1) << "AccountReconcilor::UnregisterWithTokenService";
147 if (!registered_with_token_service_
)
150 token_service_
->RemoveObserver(this);
151 registered_with_token_service_
= false;
154 void AccountReconcilor::RegisterWithCookieManagerService() {
155 VLOG(1) << "AccountReconcilor::RegisterWithCookieManagerService";
156 // During re-auth, the reconcilor will get a callback about successful signin
157 // even when the profile is already connected. Avoid re-registering
158 // with the helper since this will DCHECK.
159 if (registered_with_cookie_manager_service_
)
162 cookie_manager_service_
->AddObserver(this);
163 registered_with_cookie_manager_service_
= true;
165 void AccountReconcilor::UnregisterWithCookieManagerService() {
166 VLOG(1) << "AccountReconcilor::UnregisterWithCookieManagerService";
167 if (!registered_with_cookie_manager_service_
)
170 cookie_manager_service_
->RemoveObserver(this);
171 registered_with_cookie_manager_service_
= false;
174 bool AccountReconcilor::IsProfileConnected() {
175 return signin_manager_
->IsAuthenticated();
178 signin_metrics::AccountReconcilorState
AccountReconcilor::GetState() {
179 if (!is_reconcile_started_
) {
180 return error_during_last_reconcile_
181 ? signin_metrics::ACCOUNT_RECONCILOR_ERROR
182 : signin_metrics::ACCOUNT_RECONCILOR_OK
;
185 return signin_metrics::ACCOUNT_RECONCILOR_RUNNING
;
188 void AccountReconcilor::OnContentSettingChanged(
189 const ContentSettingsPattern
& primary_pattern
,
190 const ContentSettingsPattern
& secondary_pattern
,
191 ContentSettingsType content_type
,
192 std::string resource_identifier
) {
193 // If this is not a change to cookie settings, just ignore.
194 if (content_type
!= CONTENT_SETTINGS_TYPE_COOKIES
)
197 // If this does not affect GAIA, just ignore. If the primary pattern is
198 // invalid, then assume it could affect GAIA. The secondary pattern is
200 if (primary_pattern
.IsValid() &&
201 !primary_pattern
.Matches(GaiaUrls::GetInstance()->gaia_url())) {
205 VLOG(1) << "AccountReconcilor::OnContentSettingChanged";
209 void AccountReconcilor::OnEndBatchChanges() {
210 VLOG(1) << "AccountReconcilor::OnEndBatchChanges. "
211 << "Reconcilor state: " << is_reconcile_started_
;
212 // Remember that accounts have changed if a reconcile is already started.
213 chrome_accounts_changed_
= is_reconcile_started_
;
217 void AccountReconcilor::GoogleSigninSucceeded(const std::string
& account_id
,
218 const std::string
& username
,
219 const std::string
& password
) {
220 VLOG(1) << "AccountReconcilor::GoogleSigninSucceeded: signed in";
221 RegisterWithCookieManagerService();
222 RegisterWithContentSettings();
223 RegisterWithTokenService();
226 void AccountReconcilor::GoogleSignedOut(const std::string
& account_id
,
227 const std::string
& username
) {
228 VLOG(1) << "AccountReconcilor::GoogleSignedOut: signed out";
230 UnregisterWithCookieManagerService();
231 UnregisterWithTokenService();
232 UnregisterWithContentSettings();
233 PerformLogoutAllAccountsAction();
236 void AccountReconcilor::PerformMergeAction(const std::string
& account_id
) {
237 if (!switches::IsEnableAccountConsistency()) {
238 MarkAccountAsAddedToCookie(account_id
);
241 VLOG(1) << "AccountReconcilor::PerformMergeAction: " << account_id
;
242 cookie_manager_service_
->AddAccountToCookie(account_id
);
245 void AccountReconcilor::PerformLogoutAllAccountsAction() {
246 if (!switches::IsEnableAccountConsistency())
248 VLOG(1) << "AccountReconcilor::PerformLogoutAllAccountsAction";
249 cookie_manager_service_
->LogOutAllAccounts();
252 void AccountReconcilor::StartReconcile() {
253 if (!IsProfileConnected() || !client_
->AreSigninCookiesAllowed()) {
254 VLOG(1) << "AccountReconcilor::StartReconcile: !connected or no cookies";
258 if (is_reconcile_started_
)
261 is_reconcile_started_
= true;
262 error_during_last_reconcile_
= false;
264 // Reset state for validating gaia cookie.
265 gaia_accounts_
.clear();
267 // Reset state for validating oauth2 tokens.
268 primary_account_
.clear();
269 chrome_accounts_
.clear();
270 add_to_cookie_
.clear();
271 ValidateAccountsFromTokenService();
273 // Rely on the GCMS to manage calls to and responses from ListAccounts.
274 if (cookie_manager_service_
->ListAccounts(&gaia_accounts_
)) {
275 OnGaiaAccountsInCookieUpdated(
276 gaia_accounts_
, GoogleServiceAuthError(GoogleServiceAuthError::NONE
));
280 void AccountReconcilor::OnGaiaAccountsInCookieUpdated(
281 const std::vector
<gaia::ListedAccount
>& accounts
,
282 const GoogleServiceAuthError
& error
) {
283 VLOG(1) << "AccountReconcilor::OnGaiaAccountsInCookieUpdated: "
284 << "CookieJar " << accounts
.size() << " accounts, "
285 << "Reconcilor's state is " << is_reconcile_started_
<< ", "
286 << "Error was " << error
.ToString();
287 if (error
.state() == GoogleServiceAuthError::NONE
) {
288 gaia_accounts_
= accounts
;
290 // It is possible that O2RT is not available at this moment.
291 if (token_service_
->GetAccounts().empty())
294 is_reconcile_started_
? FinishReconcile() : StartReconcile();
296 if (is_reconcile_started_
)
297 error_during_last_reconcile_
= true;
302 void AccountReconcilor::ValidateAccountsFromTokenService() {
303 primary_account_
= signin_manager_
->GetAuthenticatedAccountId();
304 DCHECK(!primary_account_
.empty());
306 chrome_accounts_
= token_service_
->GetAccounts();
308 VLOG(1) << "AccountReconcilor::ValidateAccountsFromTokenService: "
309 << "Chrome " << chrome_accounts_
.size() << " accounts, "
310 << "Primary is '" << primary_account_
<< "'";
313 void AccountReconcilor::OnNewProfileManagementFlagChanged(
314 bool new_flag_status
) {
315 if (new_flag_status
) {
316 // The reconciler may have been newly created just before this call, or may
317 // have already existed and in mid-reconcile. To err on the safe side, force
326 void AccountReconcilor::FinishReconcile() {
327 VLOG(1) << "AccountReconcilor::FinishReconcile";
328 DCHECK(add_to_cookie_
.empty());
329 int number_gaia_accounts
= gaia_accounts_
.size();
330 bool are_primaries_equal
= number_gaia_accounts
> 0 &&
331 primary_account_
== gaia_accounts_
[0].id
;
333 // If there are any accounts in the gaia cookie but not in chrome, then
334 // those accounts need to be removed from the cookie. This means we need
335 // to blow the cookie away.
336 int removed_from_cookie
= 0;
337 for (size_t i
= 0; i
< gaia_accounts_
.size(); ++i
) {
338 if (gaia_accounts_
[i
].valid
&&
339 chrome_accounts_
.end() == std::find(chrome_accounts_
.begin(),
340 chrome_accounts_
.end(),
341 gaia_accounts_
[i
].id
)) {
342 ++removed_from_cookie
;
346 bool rebuild_cookie
= !are_primaries_equal
|| removed_from_cookie
> 0;
347 std::vector
<gaia::ListedAccount
> original_gaia_accounts
=
349 if (rebuild_cookie
) {
350 VLOG(1) << "AccountReconcilor::FinishReconcile: rebuild cookie";
351 // Really messed up state. Blow away the gaia cookie completely and
352 // rebuild it, making sure the primary account as specified by the
353 // SigninManager is the first session in the gaia cookie.
354 PerformLogoutAllAccountsAction();
355 gaia_accounts_
.clear();
358 // Create a list of accounts that need to be added to the gaia cookie.
359 // The primary account must be first to make sure it becomes the default
360 // account in the case where chrome is completely rebuilding the cookie.
361 add_to_cookie_
.push_back(primary_account_
);
362 for (size_t i
= 0; i
< chrome_accounts_
.size(); ++i
) {
363 if (chrome_accounts_
[i
] != primary_account_
)
364 add_to_cookie_
.push_back(chrome_accounts_
[i
]);
367 // For each account known to chrome, PerformMergeAction() if the account is
368 // not already in the cookie jar or its state is invalid, or signal merge
369 // completed otherwise. Make a copy of |add_to_cookie_| since calls to
370 // SignalComplete() will change the array.
371 std::vector
<std::string
> add_to_cookie_copy
= add_to_cookie_
;
372 int added_to_cookie
= 0;
373 for (size_t i
= 0; i
< add_to_cookie_copy
.size(); ++i
) {
374 if (gaia_accounts_
.end() !=
375 std::find_if(gaia_accounts_
.begin(),
376 gaia_accounts_
.end(),
377 std::bind1st(AccountEqualToFunc(),
378 AccountForId(add_to_cookie_copy
[i
])))) {
379 cookie_manager_service_
->SignalComplete(
380 add_to_cookie_copy
[i
],
381 GoogleServiceAuthError::AuthErrorNone());
383 PerformMergeAction(add_to_cookie_copy
[i
]);
384 if (original_gaia_accounts
.end() ==
385 std::find_if(original_gaia_accounts
.begin(),
386 original_gaia_accounts
.end(),
387 std::bind1st(AccountEqualToFunc(),
388 AccountForId(add_to_cookie_copy
[i
])))) {
394 signin_metrics::LogSigninAccountReconciliation(chrome_accounts_
.size(),
399 number_gaia_accounts
);
400 first_execution_
= false;
401 CalculateIfReconcileIsDone();
402 ScheduleStartReconcileIfChromeAccountsChanged();
405 void AccountReconcilor::AbortReconcile() {
406 VLOG(1) << "AccountReconcilor::AbortReconcile: we'll try again later";
407 add_to_cookie_
.clear();
408 CalculateIfReconcileIsDone();
411 void AccountReconcilor::CalculateIfReconcileIsDone() {
412 is_reconcile_started_
= !add_to_cookie_
.empty();
413 if (!is_reconcile_started_
)
414 VLOG(1) << "AccountReconcilor::CalculateIfReconcileIsDone: done";
417 void AccountReconcilor::ScheduleStartReconcileIfChromeAccountsChanged() {
418 if (is_reconcile_started_
)
421 // Start a reconcile as the token accounts have changed.
422 VLOG(1) << "AccountReconcilor::StartReconcileIfChromeAccountsChanged";
423 if (chrome_accounts_changed_
) {
424 chrome_accounts_changed_
= false;
425 base::ThreadTaskRunnerHandle::Get()->PostTask(
427 base::Bind(&AccountReconcilor::StartReconcile
, base::Unretained(this)));
431 // Remove the account from the list that is being merged.
432 bool AccountReconcilor::MarkAccountAsAddedToCookie(
433 const std::string
& account_id
) {
434 for (std::vector
<std::string
>::iterator i
= add_to_cookie_
.begin();
435 i
!= add_to_cookie_
.end();
437 if (account_id
== *i
) {
438 add_to_cookie_
.erase(i
);
445 void AccountReconcilor::OnAddAccountToCookieCompleted(
446 const std::string
& account_id
,
447 const GoogleServiceAuthError
& error
) {
448 VLOG(1) << "AccountReconcilor::OnAddAccountToCookieCompleted: "
449 << "Account added: " << account_id
<< ", "
450 << "Error was " << error
.ToString();
451 // Always listens to GaiaCookieManagerService. Only proceed if reconciling.
452 if (is_reconcile_started_
&& MarkAccountAsAddedToCookie(account_id
)) {
453 if (error
.state() != GoogleServiceAuthError::State::NONE
)
454 error_during_last_reconcile_
= true;
455 CalculateIfReconcileIsDone();
456 ScheduleStartReconcileIfChromeAccountsChanged();