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(ProfileOAuth2TokenService
* token_service
,
57 SigninManagerBase
* signin_manager
,
59 : token_service_(token_service
),
60 signin_manager_(signin_manager
),
62 merge_session_helper_(token_service_
,
63 GaiaConstants::kReconcilorSource
,
64 client
->GetURLRequestContext(),
66 registered_with_token_service_(false),
67 registered_with_merge_session_helper_(false),
68 registered_with_content_settings_(false),
69 is_reconcile_started_(false),
70 first_execution_(true),
71 are_gaia_accounts_set_(false),
72 chrome_accounts_changed_(false) {
73 VLOG(1) << "AccountReconcilor::AccountReconcilor";
76 AccountReconcilor::~AccountReconcilor() {
77 VLOG(1) << "AccountReconcilor::~AccountReconcilor";
78 // Make sure shutdown was called first.
79 DCHECK(!registered_with_token_service_
);
80 DCHECK(!registered_with_merge_session_helper_
);
83 void AccountReconcilor::Initialize(bool start_reconcile_if_tokens_available
) {
84 VLOG(1) << "AccountReconcilor::Initialize";
85 RegisterWithSigninManager();
87 // If this user is not signed in, the reconcilor should do nothing but
89 if (IsProfileConnected()) {
90 RegisterWithMergeSessionHelper();
91 RegisterForCookieChanges();
92 RegisterWithContentSettings();
93 RegisterWithTokenService();
95 // Start a reconcile if the tokens are already loaded.
96 if (start_reconcile_if_tokens_available
&&
97 token_service_
->GetAccounts().size() > 0) {
103 void AccountReconcilor::Shutdown() {
104 VLOG(1) << "AccountReconcilor::Shutdown";
105 merge_session_helper_
.CancelAll();
106 gaia_fetcher_
.reset();
107 get_gaia_accounts_callbacks_
.clear();
108 UnregisterWithMergeSessionHelper();
109 UnregisterWithSigninManager();
110 UnregisterWithTokenService();
111 UnregisterForCookieChanges();
112 UnregisterWithContentSettings();
115 void AccountReconcilor::AddMergeSessionObserver(
116 MergeSessionHelper::Observer
* observer
) {
117 merge_session_helper_
.AddObserver(observer
);
120 void AccountReconcilor::RemoveMergeSessionObserver(
121 MergeSessionHelper::Observer
* observer
) {
122 merge_session_helper_
.RemoveObserver(observer
);
125 void AccountReconcilor::RegisterForCookieChanges() {
126 // First clear any existing registration to avoid DCHECKs that can otherwise
127 // go off in some embedders on reauth (e.g., ChromeSigninClient).
128 UnregisterForCookieChanges();
129 cookie_changed_subscription_
= client_
->AddCookieChangedCallback(
130 GaiaUrls::GetInstance()->gaia_url(),
132 base::Bind(&AccountReconcilor::OnCookieChanged
, base::Unretained(this)));
135 void AccountReconcilor::UnregisterForCookieChanges() {
136 cookie_changed_subscription_
.reset();
139 void AccountReconcilor::RegisterWithSigninManager() {
140 signin_manager_
->AddObserver(this);
143 void AccountReconcilor::UnregisterWithSigninManager() {
144 signin_manager_
->RemoveObserver(this);
147 void AccountReconcilor::RegisterWithContentSettings() {
148 VLOG(1) << "AccountReconcilor::RegisterWithContentSettings";
149 // During re-auth, the reconcilor will get a callback about successful signin
150 // even when the profile is already connected. Avoid re-registering
151 // with the token service since this will DCHECK.
152 if (registered_with_content_settings_
)
155 client_
->AddContentSettingsObserver(this);
156 registered_with_content_settings_
= true;
159 void AccountReconcilor::UnregisterWithContentSettings() {
160 VLOG(1) << "AccountReconcilor::UnregisterWithContentSettings";
161 if (!registered_with_content_settings_
)
164 client_
->RemoveContentSettingsObserver(this);
165 registered_with_content_settings_
= false;
168 void AccountReconcilor::RegisterWithTokenService() {
169 VLOG(1) << "AccountReconcilor::RegisterWithTokenService";
170 // During re-auth, the reconcilor will get a callback about successful signin
171 // even when the profile is already connected. Avoid re-registering
172 // with the token service since this will DCHECK.
173 if (registered_with_token_service_
)
176 token_service_
->AddObserver(this);
177 registered_with_token_service_
= true;
180 void AccountReconcilor::UnregisterWithTokenService() {
181 VLOG(1) << "AccountReconcilor::UnregisterWithTokenService";
182 if (!registered_with_token_service_
)
185 token_service_
->RemoveObserver(this);
186 registered_with_token_service_
= false;
189 void AccountReconcilor::RegisterWithMergeSessionHelper() {
190 VLOG(1) << "AccountReconcilor::RegisterWithMergeSessionHelper";
191 // During re-auth, the reconcilor will get a callback about successful signin
192 // even when the profile is already connected. Avoid re-registering
193 // with the helper since this will DCHECK.
194 if (registered_with_merge_session_helper_
)
197 merge_session_helper_
.AddObserver(this);
198 registered_with_merge_session_helper_
= true;
200 void AccountReconcilor::UnregisterWithMergeSessionHelper() {
201 VLOG(1) << "AccountReconcilor::UnregisterWithMergeSessionHelper";
202 if (!registered_with_merge_session_helper_
)
205 merge_session_helper_
.RemoveObserver(this);
206 registered_with_merge_session_helper_
= false;
209 bool AccountReconcilor::IsProfileConnected() {
210 return signin_manager_
->IsAuthenticated();
213 void AccountReconcilor::OnCookieChanged(const net::CanonicalCookie
& cookie
,
215 DCHECK_EQ("LSID", cookie
.Name());
216 DCHECK_EQ(GaiaUrls::GetInstance()->gaia_url().host(), cookie
.Domain());
217 if (cookie
.IsSecure() && cookie
.IsHttpOnly()) {
218 VLOG(1) << "AccountReconcilor::OnCookieChanged: LSID changed";
220 // It is possible that O2RT is not available at this moment.
221 if (!token_service_
->GetAccounts().size()) {
222 VLOG(1) << "AccountReconcilor::OnCookieChanged: cookie change is ingored"
223 "because O2RT is not available yet.";
231 void AccountReconcilor::OnContentSettingChanged(
232 const ContentSettingsPattern
& primary_pattern
,
233 const ContentSettingsPattern
& secondary_pattern
,
234 ContentSettingsType content_type
,
235 std::string resource_identifier
) {
236 // If this is not a change to cookie settings, just ignore.
237 if (content_type
!= CONTENT_SETTINGS_TYPE_COOKIES
)
240 // If this does not affect GAIA, just ignore. If the primary pattern is
241 // invalid, then assume it could affect GAIA. The secondary pattern is
243 if (primary_pattern
.IsValid() &&
244 !primary_pattern
.Matches(GaiaUrls::GetInstance()->gaia_url())) {
248 VLOG(1) << "AccountReconcilor::OnContentSettingChanged";
252 void AccountReconcilor::OnEndBatchChanges() {
253 VLOG(1) << "AccountReconcilor::OnEndBatchChanges";
254 // Remember that accounts have changed if a reconcile is already started.
255 chrome_accounts_changed_
= is_reconcile_started_
;
259 void AccountReconcilor::GoogleSigninSucceeded(const std::string
& account_id
,
260 const std::string
& username
,
261 const std::string
& password
) {
262 VLOG(1) << "AccountReconcilor::GoogleSigninSucceeded: signed in";
263 RegisterWithMergeSessionHelper();
264 RegisterForCookieChanges();
265 RegisterWithContentSettings();
266 RegisterWithTokenService();
269 void AccountReconcilor::GoogleSignedOut(const std::string
& account_id
,
270 const std::string
& username
) {
271 VLOG(1) << "AccountReconcilor::GoogleSignedOut: signed out";
272 gaia_fetcher_
.reset();
273 get_gaia_accounts_callbacks_
.clear();
275 UnregisterWithMergeSessionHelper();
276 UnregisterWithTokenService();
277 UnregisterForCookieChanges();
278 UnregisterWithContentSettings();
279 PerformLogoutAllAccountsAction();
282 void AccountReconcilor::PerformMergeAction(const std::string
& account_id
) {
283 if (!switches::IsEnableAccountConsistency()) {
284 MarkAccountAsAddedToCookie(account_id
);
287 VLOG(1) << "AccountReconcilor::PerformMergeAction: " << account_id
;
288 merge_session_helper_
.LogIn(account_id
);
291 void AccountReconcilor::PerformLogoutAllAccountsAction() {
292 if (!switches::IsEnableAccountConsistency())
294 VLOG(1) << "AccountReconcilor::PerformLogoutAllAccountsAction";
295 merge_session_helper_
.LogOutAllAccounts();
298 void AccountReconcilor::StartReconcile() {
299 if (!IsProfileConnected() || !client_
->AreSigninCookiesAllowed()) {
300 VLOG(1) << "AccountReconcilor::StartReconcile: !connected or no cookies";
304 if (is_reconcile_started_
|| get_gaia_accounts_callbacks_
.size() > 0)
307 is_reconcile_started_
= true;
308 m_reconcile_start_time_
= base::Time::Now();
310 // Reset state for validating gaia cookie.
311 are_gaia_accounts_set_
= false;
312 gaia_accounts_
.clear();
314 // Reset state for validating oauth2 tokens.
315 primary_account_
.clear();
316 chrome_accounts_
.clear();
317 add_to_cookie_
.clear();
318 ValidateAccountsFromTokenService();
320 // Start process by checking connections to external sites.
321 merge_session_helper_
.StartFetchingExternalCcResult();
324 void AccountReconcilor::GetAccountsFromCookie(
325 GetAccountsFromCookieCallback callback
) {
326 get_gaia_accounts_callbacks_
.push_back(callback
);
328 MayBeDoNextListAccounts();
331 void AccountReconcilor::OnListAccountsSuccess(const std::string
& data
) {
332 gaia_fetcher_
.reset();
334 // Get account information from response data.
335 std::vector
<std::pair
<std::string
, bool> > gaia_accounts
;
336 bool valid_json
= gaia::ParseListAccountsData(data
, &gaia_accounts
);
338 VLOG(1) << "AccountReconcilor::OnListAccountsSuccess: parsing error";
339 } else if (gaia_accounts
.size() > 0) {
340 VLOG(1) << "AccountReconcilor::OnListAccountsSuccess: "
341 << "Gaia " << gaia_accounts
.size() << " accounts, "
342 << "Primary is '" << gaia_accounts
[0].first
<< "'";
344 VLOG(1) << "AccountReconcilor::OnListAccountsSuccess: No accounts";
347 // There must be at least one callback waiting for result.
348 DCHECK(!get_gaia_accounts_callbacks_
.empty());
350 GoogleServiceAuthError error
=
351 !valid_json
? GoogleServiceAuthError(
352 GoogleServiceAuthError::UNEXPECTED_SERVICE_RESPONSE
)
353 : GoogleServiceAuthError::AuthErrorNone();
354 get_gaia_accounts_callbacks_
.front().Run(error
, gaia_accounts
);
355 get_gaia_accounts_callbacks_
.pop_front();
357 MayBeDoNextListAccounts();
360 void AccountReconcilor::OnListAccountsFailure(
361 const GoogleServiceAuthError
& error
) {
362 gaia_fetcher_
.reset();
363 VLOG(1) << "AccountReconcilor::OnListAccountsFailure: " << error
.ToString();
364 std::vector
<std::pair
<std::string
, bool> > empty_accounts
;
366 // There must be at least one callback waiting for result.
367 DCHECK(!get_gaia_accounts_callbacks_
.empty());
369 get_gaia_accounts_callbacks_
.front().Run(error
, empty_accounts
);
370 get_gaia_accounts_callbacks_
.pop_front();
372 MayBeDoNextListAccounts();
375 void AccountReconcilor::MayBeDoNextListAccounts() {
376 if (!get_gaia_accounts_callbacks_
.empty()) {
377 gaia_fetcher_
.reset(new GaiaAuthFetcher(
378 this, GaiaConstants::kReconcilorSource
,
379 client_
->GetURLRequestContext()));
380 gaia_fetcher_
->StartListAccounts();
384 void AccountReconcilor::ContinueReconcileActionAfterGetGaiaAccounts(
385 const GoogleServiceAuthError
& error
,
386 const std::vector
<std::pair
<std::string
, bool> >& accounts
) {
387 if (error
.state() == GoogleServiceAuthError::NONE
) {
388 gaia_accounts_
= accounts
;
389 are_gaia_accounts_set_
= true;
396 void AccountReconcilor::ValidateAccountsFromTokenService() {
397 primary_account_
= signin_manager_
->GetAuthenticatedAccountId();
398 DCHECK(!primary_account_
.empty());
400 chrome_accounts_
= token_service_
->GetAccounts();
402 VLOG(1) << "AccountReconcilor::ValidateAccountsFromTokenService: "
403 << "Chrome " << chrome_accounts_
.size() << " accounts, "
404 << "Primary is '" << primary_account_
<< "'";
407 void AccountReconcilor::OnNewProfileManagementFlagChanged(
408 bool new_flag_status
) {
409 if (new_flag_status
) {
410 // The reconciler may have been newly created just before this call, or may
411 // have already existed and in mid-reconcile. To err on the safe side, force
420 void AccountReconcilor::FinishReconcile() {
421 VLOG(1) << "AccountReconcilor::FinishReconcile";
422 DCHECK(are_gaia_accounts_set_
);
423 DCHECK(add_to_cookie_
.empty());
424 int number_gaia_accounts
= gaia_accounts_
.size();
425 bool are_primaries_equal
= number_gaia_accounts
> 0 &&
426 gaia::AreEmailsSame(primary_account_
, gaia_accounts_
[0].first
);
428 // If there are any accounts in the gaia cookie but not in chrome, then
429 // those accounts need to be removed from the cookie. This means we need
430 // to blow the cookie away.
431 int removed_from_cookie
= 0;
432 for (size_t i
= 0; i
< gaia_accounts_
.size(); ++i
) {
433 const std::string
& gaia_account
= gaia_accounts_
[i
].first
;
434 if (gaia_accounts_
[i
].second
&&
435 chrome_accounts_
.end() ==
436 std::find_if(chrome_accounts_
.begin(),
437 chrome_accounts_
.end(),
438 std::bind1st(AreEmailsSameFunc(), gaia_account
))) {
439 ++removed_from_cookie
;
443 bool rebuild_cookie
= !are_primaries_equal
|| removed_from_cookie
> 0;
444 std::vector
<std::pair
<std::string
, bool> > original_gaia_accounts
=
446 if (rebuild_cookie
) {
447 VLOG(1) << "AccountReconcilor::FinishReconcile: rebuild cookie";
448 // Really messed up state. Blow away the gaia cookie completely and
449 // rebuild it, making sure the primary account as specified by the
450 // SigninManager is the first session in the gaia cookie.
451 PerformLogoutAllAccountsAction();
452 gaia_accounts_
.clear();
455 // Create a list of accounts that need to be added to the gaia cookie.
456 // The primary account must be first to make sure it becomes the default
457 // account in the case where chrome is completely rebuilding the cookie.
458 add_to_cookie_
.push_back(primary_account_
);
459 for (size_t i
= 0; i
< chrome_accounts_
.size(); ++i
) {
460 if (chrome_accounts_
[i
] != primary_account_
)
461 add_to_cookie_
.push_back(chrome_accounts_
[i
]);
464 // For each account known to chrome, PerformMergeAction() if the account is
465 // not already in the cookie jar or its state is invalid, or signal merge
466 // completed otherwise. Make a copy of |add_to_cookie_| since calls to
467 // SignalComplete() will change the array.
468 std::vector
<std::string
> add_to_cookie_copy
= add_to_cookie_
;
469 int added_to_cookie
= 0;
470 for (size_t i
= 0; i
< add_to_cookie_copy
.size(); ++i
) {
471 if (gaia_accounts_
.end() !=
472 std::find_if(gaia_accounts_
.begin(),
473 gaia_accounts_
.end(),
474 std::bind1st(EmailEqualToFunc(),
475 std::make_pair(add_to_cookie_copy
[i
],
477 merge_session_helper_
.SignalComplete(
478 add_to_cookie_copy
[i
],
479 GoogleServiceAuthError::AuthErrorNone());
481 PerformMergeAction(add_to_cookie_copy
[i
]);
482 if (original_gaia_accounts
.end() ==
483 std::find_if(original_gaia_accounts
.begin(),
484 original_gaia_accounts
.end(),
485 std::bind1st(EmailEqualToFunc(),
486 std::make_pair(add_to_cookie_copy
[i
],
493 signin_metrics::LogSigninAccountReconciliation(chrome_accounts_
.size(),
498 number_gaia_accounts
);
499 first_execution_
= false;
500 CalculateIfReconcileIsDone();
501 ScheduleStartReconcileIfChromeAccountsChanged();
504 void AccountReconcilor::AbortReconcile() {
505 VLOG(1) << "AccountReconcilor::AbortReconcile: we'll try again later";
506 add_to_cookie_
.clear();
507 CalculateIfReconcileIsDone();
510 void AccountReconcilor::CalculateIfReconcileIsDone() {
511 is_reconcile_started_
= !add_to_cookie_
.empty();
512 if (!is_reconcile_started_
)
513 VLOG(1) << "AccountReconcilor::CalculateIfReconcileIsDone: done";
516 void AccountReconcilor::ScheduleStartReconcileIfChromeAccountsChanged() {
517 if (is_reconcile_started_
)
520 // Start a reconcile as the token accounts have changed.
521 VLOG(1) << "AccountReconcilor::StartReconcileIfChromeAccountsChanged";
522 if (chrome_accounts_changed_
) {
523 chrome_accounts_changed_
= false;
524 base::MessageLoop::current()->PostTask(
526 base::Bind(&AccountReconcilor::StartReconcile
, base::Unretained(this)));
530 // Remove the account from the list that is being merged.
531 bool AccountReconcilor::MarkAccountAsAddedToCookie(
532 const std::string
& account_id
) {
533 for (std::vector
<std::string
>::iterator i
= add_to_cookie_
.begin();
534 i
!= add_to_cookie_
.end();
536 if (account_id
== *i
) {
537 add_to_cookie_
.erase(i
);
544 void AccountReconcilor::MergeSessionCompleted(
545 const std::string
& account_id
,
546 const GoogleServiceAuthError
& error
) {
547 VLOG(1) << "AccountReconcilor::MergeSessionCompleted: account_id="
548 << account_id
<< " error=" << error
.ToString();
549 DCHECK(is_reconcile_started_
);
551 if (MarkAccountAsAddedToCookie(account_id
)) {
552 CalculateIfReconcileIsDone();
553 ScheduleStartReconcileIfChromeAccountsChanged();
557 void AccountReconcilor::GetCheckConnectionInfoCompleted(bool succeeded
) {
558 base::TimeDelta time_to_check_connections
=
559 base::Time::Now() - m_reconcile_start_time_
;
560 signin_metrics::LogExternalCcResultFetches(succeeded
,
561 time_to_check_connections
);
562 GetAccountsFromCookie(base::Bind(
563 &AccountReconcilor::ContinueReconcileActionAfterGetGaiaAccounts
,
564 base::Unretained(this)));