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/logging.h"
12 #include "base/message_loop/message_loop.h"
13 #include "base/message_loop/message_loop_proxy.h"
14 #include "base/strings/string_number_conversions.h"
15 #include "components/signin/core/browser/profile_oauth2_token_service.h"
16 #include "components/signin/core/browser/signin_client.h"
17 #include "components/signin/core/browser/signin_metrics.h"
18 #include "components/signin/core/common/profile_management_switches.h"
19 #include "google_apis/gaia/gaia_auth_fetcher.h"
20 #include "google_apis/gaia/gaia_auth_util.h"
21 #include "google_apis/gaia/gaia_constants.h"
22 #include "google_apis/gaia/gaia_oauth_client.h"
23 #include "google_apis/gaia/gaia_urls.h"
24 #include "net/cookies/canonical_cookie.h"
29 class EmailEqualToFunc
: public std::equal_to
<std::pair
<std::string
, bool> > {
31 bool operator()(const std::pair
<std::string
, bool>& p1
,
32 const std::pair
<std::string
, bool>& p2
) const;
35 bool EmailEqualToFunc::operator()(
36 const std::pair
<std::string
, bool>& p1
,
37 const std::pair
<std::string
, bool>& p2
) const {
38 return p1
.second
== p2
.second
&& gaia::AreEmailsSame(p1
.first
, p2
.first
);
41 class AreEmailsSameFunc
: public std::equal_to
<std::string
> {
43 bool operator()(const std::string
& p1
,
44 const std::string
& p2
) const;
47 bool AreEmailsSameFunc::operator()(
48 const std::string
& p1
,
49 const std::string
& p2
) const {
50 return gaia::AreEmailsSame(p1
, p2
);
56 AccountReconcilor::AccountReconcilor(
57 ProfileOAuth2TokenService
* token_service
,
58 SigninManagerBase
* signin_manager
,
60 GaiaCookieManagerService
* cookie_manager_service
)
61 : token_service_(token_service
),
62 signin_manager_(signin_manager
),
64 cookie_manager_service_(cookie_manager_service
),
65 registered_with_token_service_(false),
66 registered_with_cookie_manager_service_(false),
67 registered_with_content_settings_(false),
68 is_reconcile_started_(false),
69 first_execution_(true),
70 are_gaia_accounts_set_(false),
71 chrome_accounts_changed_(false) {
72 VLOG(1) << "AccountReconcilor::AccountReconcilor";
75 AccountReconcilor::~AccountReconcilor() {
76 VLOG(1) << "AccountReconcilor::~AccountReconcilor";
77 // Make sure shutdown was called first.
78 DCHECK(!registered_with_token_service_
);
79 DCHECK(!registered_with_cookie_manager_service_
);
82 void AccountReconcilor::Initialize(bool start_reconcile_if_tokens_available
) {
83 VLOG(1) << "AccountReconcilor::Initialize";
84 RegisterWithSigninManager();
86 // If this user is not signed in, the reconcilor should do nothing but
88 if (IsProfileConnected()) {
89 RegisterWithCookieManagerService();
90 RegisterForCookieChanges();
91 RegisterWithContentSettings();
92 RegisterWithTokenService();
94 // Start a reconcile if the tokens are already loaded.
95 if (start_reconcile_if_tokens_available
&&
96 token_service_
->GetAccounts().size() > 0) {
102 void AccountReconcilor::Shutdown() {
103 VLOG(1) << "AccountReconcilor::Shutdown";
104 gaia_fetcher_
.reset();
105 get_gaia_accounts_callbacks_
.clear();
106 UnregisterWithCookieManagerService();
107 UnregisterWithSigninManager();
108 UnregisterWithTokenService();
109 UnregisterForCookieChanges();
110 UnregisterWithContentSettings();
113 void AccountReconcilor::RegisterForCookieChanges() {
114 // First clear any existing registration to avoid DCHECKs that can otherwise
115 // go off in some embedders on reauth (e.g., ChromeSigninClient).
116 UnregisterForCookieChanges();
117 cookie_changed_subscription_
= client_
->AddCookieChangedCallback(
118 GaiaUrls::GetInstance()->gaia_url(),
120 base::Bind(&AccountReconcilor::OnCookieChanged
, base::Unretained(this)));
123 void AccountReconcilor::UnregisterForCookieChanges() {
124 cookie_changed_subscription_
.reset();
127 void AccountReconcilor::RegisterWithSigninManager() {
128 signin_manager_
->AddObserver(this);
131 void AccountReconcilor::UnregisterWithSigninManager() {
132 signin_manager_
->RemoveObserver(this);
135 void AccountReconcilor::RegisterWithContentSettings() {
136 VLOG(1) << "AccountReconcilor::RegisterWithContentSettings";
137 // During re-auth, the reconcilor will get a callback about successful signin
138 // even when the profile is already connected. Avoid re-registering
139 // with the token service since this will DCHECK.
140 if (registered_with_content_settings_
)
143 client_
->AddContentSettingsObserver(this);
144 registered_with_content_settings_
= true;
147 void AccountReconcilor::UnregisterWithContentSettings() {
148 VLOG(1) << "AccountReconcilor::UnregisterWithContentSettings";
149 if (!registered_with_content_settings_
)
152 client_
->RemoveContentSettingsObserver(this);
153 registered_with_content_settings_
= false;
156 void AccountReconcilor::RegisterWithTokenService() {
157 VLOG(1) << "AccountReconcilor::RegisterWithTokenService";
158 // During re-auth, the reconcilor will get a callback about successful signin
159 // even when the profile is already connected. Avoid re-registering
160 // with the token service since this will DCHECK.
161 if (registered_with_token_service_
)
164 token_service_
->AddObserver(this);
165 registered_with_token_service_
= true;
168 void AccountReconcilor::UnregisterWithTokenService() {
169 VLOG(1) << "AccountReconcilor::UnregisterWithTokenService";
170 if (!registered_with_token_service_
)
173 token_service_
->RemoveObserver(this);
174 registered_with_token_service_
= false;
177 void AccountReconcilor::RegisterWithCookieManagerService() {
178 VLOG(1) << "AccountReconcilor::RegisterWithCookieManagerService";
179 // During re-auth, the reconcilor will get a callback about successful signin
180 // even when the profile is already connected. Avoid re-registering
181 // with the helper since this will DCHECK.
182 if (registered_with_cookie_manager_service_
)
185 cookie_manager_service_
->AddObserver(this);
186 registered_with_cookie_manager_service_
= true;
188 void AccountReconcilor::UnregisterWithCookieManagerService() {
189 VLOG(1) << "AccountReconcilor::UnregisterWithCookieManagerService";
190 if (!registered_with_cookie_manager_service_
)
193 cookie_manager_service_
->RemoveObserver(this);
194 registered_with_cookie_manager_service_
= false;
197 bool AccountReconcilor::IsProfileConnected() {
198 return signin_manager_
->IsAuthenticated();
201 void AccountReconcilor::OnCookieChanged(const net::CanonicalCookie
& cookie
,
203 DCHECK_EQ("LSID", cookie
.Name());
204 DCHECK_EQ(GaiaUrls::GetInstance()->gaia_url().host(), cookie
.Domain());
205 if (cookie
.IsSecure() && cookie
.IsHttpOnly()) {
206 VLOG(1) << "AccountReconcilor::OnCookieChanged: LSID changed";
208 // It is possible that O2RT is not available at this moment.
209 if (!token_service_
->GetAccounts().size()) {
210 VLOG(1) << "AccountReconcilor::OnCookieChanged: cookie change is ingored"
211 "because O2RT is not available yet.";
219 void AccountReconcilor::OnContentSettingChanged(
220 const ContentSettingsPattern
& primary_pattern
,
221 const ContentSettingsPattern
& secondary_pattern
,
222 ContentSettingsType content_type
,
223 std::string resource_identifier
) {
224 // If this is not a change to cookie settings, just ignore.
225 if (content_type
!= CONTENT_SETTINGS_TYPE_COOKIES
)
228 // If this does not affect GAIA, just ignore. If the primary pattern is
229 // invalid, then assume it could affect GAIA. The secondary pattern is
231 if (primary_pattern
.IsValid() &&
232 !primary_pattern
.Matches(GaiaUrls::GetInstance()->gaia_url())) {
236 VLOG(1) << "AccountReconcilor::OnContentSettingChanged";
240 void AccountReconcilor::OnEndBatchChanges() {
241 VLOG(1) << "AccountReconcilor::OnEndBatchChanges";
242 // Remember that accounts have changed if a reconcile is already started.
243 chrome_accounts_changed_
= is_reconcile_started_
;
247 void AccountReconcilor::GoogleSigninSucceeded(const std::string
& account_id
,
248 const std::string
& username
,
249 const std::string
& password
) {
250 VLOG(1) << "AccountReconcilor::GoogleSigninSucceeded: signed in";
251 RegisterWithCookieManagerService();
252 RegisterForCookieChanges();
253 RegisterWithContentSettings();
254 RegisterWithTokenService();
257 void AccountReconcilor::GoogleSignedOut(const std::string
& account_id
,
258 const std::string
& username
) {
259 VLOG(1) << "AccountReconcilor::GoogleSignedOut: signed out";
260 gaia_fetcher_
.reset();
261 get_gaia_accounts_callbacks_
.clear();
263 UnregisterWithCookieManagerService();
264 UnregisterWithTokenService();
265 UnregisterForCookieChanges();
266 UnregisterWithContentSettings();
267 PerformLogoutAllAccountsAction();
270 void AccountReconcilor::PerformMergeAction(const std::string
& account_id
) {
271 if (!switches::IsEnableAccountConsistency()) {
272 MarkAccountAsAddedToCookie(account_id
);
275 VLOG(1) << "AccountReconcilor::PerformMergeAction: " << account_id
;
276 cookie_manager_service_
->AddAccountToCookie(account_id
);
279 void AccountReconcilor::PerformLogoutAllAccountsAction() {
280 if (!switches::IsEnableAccountConsistency())
282 VLOG(1) << "AccountReconcilor::PerformLogoutAllAccountsAction";
283 cookie_manager_service_
->LogOutAllAccounts();
286 void AccountReconcilor::StartReconcile() {
287 if (!IsProfileConnected() || !client_
->AreSigninCookiesAllowed()) {
288 VLOG(1) << "AccountReconcilor::StartReconcile: !connected or no cookies";
292 if (is_reconcile_started_
|| get_gaia_accounts_callbacks_
.size() > 0)
295 is_reconcile_started_
= true;
297 // Reset state for validating gaia cookie.
298 are_gaia_accounts_set_
= false;
299 gaia_accounts_
.clear();
301 // Reset state for validating oauth2 tokens.
302 primary_account_
.clear();
303 chrome_accounts_
.clear();
304 add_to_cookie_
.clear();
305 ValidateAccountsFromTokenService();
307 // TODO(mlerman): Call this only from within the GaiaCookieManagerService,
308 // once /ListAccounts is now called from that class instead of the
309 // reconcilor's GaiaAuthFetcher (which will be removed).
310 cookie_manager_service_
->StartFetchingExternalCcResult();
313 void AccountReconcilor::GetAccountsFromCookie(
314 GetAccountsFromCookieCallback callback
) {
315 get_gaia_accounts_callbacks_
.push_back(callback
);
317 MayBeDoNextListAccounts();
320 void AccountReconcilor::OnListAccountsSuccess(const std::string
& data
) {
321 gaia_fetcher_
.reset();
323 // Get account information from response data.
324 std::vector
<std::pair
<std::string
, bool> > gaia_accounts
;
325 bool valid_json
= gaia::ParseListAccountsData(data
, &gaia_accounts
);
327 VLOG(1) << "AccountReconcilor::OnListAccountsSuccess: parsing error";
328 } else if (gaia_accounts
.size() > 0) {
329 VLOG(1) << "AccountReconcilor::OnListAccountsSuccess: "
330 << "Gaia " << gaia_accounts
.size() << " accounts, "
331 << "Primary is '" << gaia_accounts
[0].first
<< "'";
333 VLOG(1) << "AccountReconcilor::OnListAccountsSuccess: No accounts";
336 // There must be at least one callback waiting for result.
337 DCHECK(!get_gaia_accounts_callbacks_
.empty());
339 GoogleServiceAuthError error
=
340 !valid_json
? GoogleServiceAuthError(
341 GoogleServiceAuthError::UNEXPECTED_SERVICE_RESPONSE
)
342 : GoogleServiceAuthError::AuthErrorNone();
343 get_gaia_accounts_callbacks_
.front().Run(error
, gaia_accounts
);
344 get_gaia_accounts_callbacks_
.pop_front();
346 MayBeDoNextListAccounts();
349 void AccountReconcilor::OnListAccountsFailure(
350 const GoogleServiceAuthError
& error
) {
351 gaia_fetcher_
.reset();
352 VLOG(1) << "AccountReconcilor::OnListAccountsFailure: " << error
.ToString();
353 std::vector
<std::pair
<std::string
, bool> > empty_accounts
;
355 // There must be at least one callback waiting for result.
356 DCHECK(!get_gaia_accounts_callbacks_
.empty());
358 get_gaia_accounts_callbacks_
.front().Run(error
, empty_accounts
);
359 get_gaia_accounts_callbacks_
.pop_front();
361 MayBeDoNextListAccounts();
364 void AccountReconcilor::MayBeDoNextListAccounts() {
365 if (!get_gaia_accounts_callbacks_
.empty()) {
366 gaia_fetcher_
.reset(new GaiaAuthFetcher(
367 this, GaiaConstants::kReconcilorSource
,
368 client_
->GetURLRequestContext()));
369 gaia_fetcher_
->StartListAccounts();
373 void AccountReconcilor::ContinueReconcileActionAfterGetGaiaAccounts(
374 const GoogleServiceAuthError
& error
,
375 const std::vector
<std::pair
<std::string
, bool> >& accounts
) {
376 if (error
.state() == GoogleServiceAuthError::NONE
) {
377 gaia_accounts_
= accounts
;
378 are_gaia_accounts_set_
= true;
385 void AccountReconcilor::ValidateAccountsFromTokenService() {
386 primary_account_
= signin_manager_
->GetAuthenticatedAccountId();
387 DCHECK(!primary_account_
.empty());
389 chrome_accounts_
= token_service_
->GetAccounts();
391 VLOG(1) << "AccountReconcilor::ValidateAccountsFromTokenService: "
392 << "Chrome " << chrome_accounts_
.size() << " accounts, "
393 << "Primary is '" << primary_account_
<< "'";
396 void AccountReconcilor::OnNewProfileManagementFlagChanged(
397 bool new_flag_status
) {
398 if (new_flag_status
) {
399 // The reconciler may have been newly created just before this call, or may
400 // have already existed and in mid-reconcile. To err on the safe side, force
409 void AccountReconcilor::FinishReconcile() {
410 VLOG(1) << "AccountReconcilor::FinishReconcile";
411 DCHECK(are_gaia_accounts_set_
);
412 DCHECK(add_to_cookie_
.empty());
413 int number_gaia_accounts
= gaia_accounts_
.size();
414 bool are_primaries_equal
= number_gaia_accounts
> 0 &&
415 gaia::AreEmailsSame(primary_account_
, gaia_accounts_
[0].first
);
417 // If there are any accounts in the gaia cookie but not in chrome, then
418 // those accounts need to be removed from the cookie. This means we need
419 // to blow the cookie away.
420 int removed_from_cookie
= 0;
421 for (size_t i
= 0; i
< gaia_accounts_
.size(); ++i
) {
422 const std::string
& gaia_account
= gaia_accounts_
[i
].first
;
423 if (gaia_accounts_
[i
].second
&&
424 chrome_accounts_
.end() ==
425 std::find_if(chrome_accounts_
.begin(),
426 chrome_accounts_
.end(),
427 std::bind1st(AreEmailsSameFunc(), gaia_account
))) {
428 ++removed_from_cookie
;
432 bool rebuild_cookie
= !are_primaries_equal
|| removed_from_cookie
> 0;
433 std::vector
<std::pair
<std::string
, bool> > original_gaia_accounts
=
435 if (rebuild_cookie
) {
436 VLOG(1) << "AccountReconcilor::FinishReconcile: rebuild cookie";
437 // Really messed up state. Blow away the gaia cookie completely and
438 // rebuild it, making sure the primary account as specified by the
439 // SigninManager is the first session in the gaia cookie.
440 PerformLogoutAllAccountsAction();
441 gaia_accounts_
.clear();
444 // Create a list of accounts that need to be added to the gaia cookie.
445 // The primary account must be first to make sure it becomes the default
446 // account in the case where chrome is completely rebuilding the cookie.
447 add_to_cookie_
.push_back(primary_account_
);
448 for (size_t i
= 0; i
< chrome_accounts_
.size(); ++i
) {
449 if (chrome_accounts_
[i
] != primary_account_
)
450 add_to_cookie_
.push_back(chrome_accounts_
[i
]);
453 // For each account known to chrome, PerformMergeAction() if the account is
454 // not already in the cookie jar or its state is invalid, or signal merge
455 // completed otherwise. Make a copy of |add_to_cookie_| since calls to
456 // SignalComplete() will change the array.
457 std::vector
<std::string
> add_to_cookie_copy
= add_to_cookie_
;
458 int added_to_cookie
= 0;
459 for (size_t i
= 0; i
< add_to_cookie_copy
.size(); ++i
) {
460 if (gaia_accounts_
.end() !=
461 std::find_if(gaia_accounts_
.begin(),
462 gaia_accounts_
.end(),
463 std::bind1st(EmailEqualToFunc(),
464 std::make_pair(add_to_cookie_copy
[i
],
466 cookie_manager_service_
->SignalComplete(
467 add_to_cookie_copy
[i
],
468 GoogleServiceAuthError::AuthErrorNone());
470 PerformMergeAction(add_to_cookie_copy
[i
]);
471 if (original_gaia_accounts
.end() ==
472 std::find_if(original_gaia_accounts
.begin(),
473 original_gaia_accounts
.end(),
474 std::bind1st(EmailEqualToFunc(),
475 std::make_pair(add_to_cookie_copy
[i
],
482 signin_metrics::LogSigninAccountReconciliation(chrome_accounts_
.size(),
487 number_gaia_accounts
);
488 first_execution_
= false;
489 CalculateIfReconcileIsDone();
490 ScheduleStartReconcileIfChromeAccountsChanged();
493 void AccountReconcilor::AbortReconcile() {
494 VLOG(1) << "AccountReconcilor::AbortReconcile: we'll try again later";
495 add_to_cookie_
.clear();
496 CalculateIfReconcileIsDone();
499 void AccountReconcilor::CalculateIfReconcileIsDone() {
500 is_reconcile_started_
= !add_to_cookie_
.empty();
501 if (!is_reconcile_started_
)
502 VLOG(1) << "AccountReconcilor::CalculateIfReconcileIsDone: done";
505 void AccountReconcilor::ScheduleStartReconcileIfChromeAccountsChanged() {
506 if (is_reconcile_started_
)
509 // Start a reconcile as the token accounts have changed.
510 VLOG(1) << "AccountReconcilor::StartReconcileIfChromeAccountsChanged";
511 if (chrome_accounts_changed_
) {
512 chrome_accounts_changed_
= false;
513 base::MessageLoop::current()->PostTask(
515 base::Bind(&AccountReconcilor::StartReconcile
, base::Unretained(this)));
519 // Remove the account from the list that is being merged.
520 bool AccountReconcilor::MarkAccountAsAddedToCookie(
521 const std::string
& account_id
) {
522 for (std::vector
<std::string
>::iterator i
= add_to_cookie_
.begin();
523 i
!= add_to_cookie_
.end();
525 if (account_id
== *i
) {
526 add_to_cookie_
.erase(i
);
533 void AccountReconcilor::OnAddAccountToCookieCompleted(
534 const std::string
& account_id
,
535 const GoogleServiceAuthError
& error
) {
536 // Always listens to GaiaCookieManagerService. Only proceed if reconciling.
537 if (is_reconcile_started_
&& MarkAccountAsAddedToCookie(account_id
)) {
538 CalculateIfReconcileIsDone();
539 ScheduleStartReconcileIfChromeAccountsChanged();
543 void AccountReconcilor::GetCheckConnectionInfoCompleted(bool succeeded
) {
544 if (is_reconcile_started_
) {
545 GetAccountsFromCookie(base::Bind(
546 &AccountReconcilor::ContinueReconcileActionAfterGetGaiaAccounts
,
547 base::Unretained(this)));