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 "base/time/time.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_fetcher.h"
21 #include "google_apis/gaia/gaia_auth_util.h"
22 #include "google_apis/gaia/gaia_constants.h"
23 #include "google_apis/gaia/gaia_oauth_client.h"
24 #include "google_apis/gaia/gaia_urls.h"
25 #include "net/cookies/canonical_cookie.h"
30 class EmailEqualToFunc
: public std::equal_to
<std::pair
<std::string
, bool> > {
32 bool operator()(const std::pair
<std::string
, bool>& p1
,
33 const std::pair
<std::string
, bool>& p2
) const;
36 bool EmailEqualToFunc::operator()(
37 const std::pair
<std::string
, bool>& p1
,
38 const std::pair
<std::string
, bool>& p2
) const {
39 return p1
.second
== p2
.second
&& gaia::AreEmailsSame(p1
.first
, p2
.first
);
42 class AreEmailsSameFunc
: public std::equal_to
<std::string
> {
44 bool operator()(const std::string
& p1
,
45 const std::string
& p2
) const;
48 bool AreEmailsSameFunc::operator()(
49 const std::string
& p1
,
50 const std::string
& p2
) const {
51 return gaia::AreEmailsSame(p1
, p2
);
57 AccountReconcilor::AccountReconcilor(ProfileOAuth2TokenService
* token_service
,
58 SigninManagerBase
* signin_manager
,
60 : token_service_(token_service
),
61 signin_manager_(signin_manager
),
63 merge_session_helper_(token_service_
,
64 client
->GetURLRequestContext(),
66 registered_with_token_service_(false),
67 is_reconcile_started_(false),
68 first_execution_(true),
69 are_gaia_accounts_set_(false) {
70 VLOG(1) << "AccountReconcilor::AccountReconcilor";
73 AccountReconcilor::~AccountReconcilor() {
74 VLOG(1) << "AccountReconcilor::~AccountReconcilor";
75 // Make sure shutdown was called first.
76 DCHECK(!registered_with_token_service_
);
79 void AccountReconcilor::Initialize(bool start_reconcile_if_tokens_available
) {
80 VLOG(1) << "AccountReconcilor::Initialize";
81 RegisterWithSigninManager();
83 // If this user is not signed in, the reconcilor should do nothing but
85 if (IsProfileConnected()) {
86 RegisterForCookieChanges();
87 RegisterWithTokenService();
89 // Start a reconcile if the tokens are already loaded.
90 if (start_reconcile_if_tokens_available
&&
91 token_service_
->GetAccounts().size() > 0) {
97 void AccountReconcilor::Shutdown() {
98 VLOG(1) << "AccountReconcilor::Shutdown";
99 merge_session_helper_
.CancelAll();
100 merge_session_helper_
.RemoveObserver(this);
101 gaia_fetcher_
.reset();
102 get_gaia_accounts_callbacks_
.clear();
103 UnregisterWithSigninManager();
104 UnregisterWithTokenService();
105 UnregisterForCookieChanges();
108 void AccountReconcilor::AddMergeSessionObserver(
109 MergeSessionHelper::Observer
* observer
) {
110 merge_session_helper_
.AddObserver(observer
);
113 void AccountReconcilor::RemoveMergeSessionObserver(
114 MergeSessionHelper::Observer
* observer
) {
115 merge_session_helper_
.RemoveObserver(observer
);
118 void AccountReconcilor::RegisterForCookieChanges() {
119 // First clear any existing registration to avoid DCHECKs that can otherwise
120 // go off in some embedders on reauth (e.g., ChromeSigninClient).
121 UnregisterForCookieChanges();
122 cookie_changed_subscription_
= client_
->AddCookieChangedCallback(
123 base::Bind(&AccountReconcilor::OnCookieChanged
, base::Unretained(this)));
126 void AccountReconcilor::UnregisterForCookieChanges() {
127 cookie_changed_subscription_
.reset();
130 void AccountReconcilor::RegisterWithSigninManager() {
131 signin_manager_
->AddObserver(this);
134 void AccountReconcilor::UnregisterWithSigninManager() {
135 signin_manager_
->RemoveObserver(this);
138 void AccountReconcilor::RegisterWithTokenService() {
139 VLOG(1) << "AccountReconcilor::RegisterWithTokenService";
140 // During re-auth, the reconcilor will get a callback about successful signin
141 // even when the profile is already connected. Avoid re-registering
142 // with the token service since this will DCHECK.
143 if (registered_with_token_service_
)
146 token_service_
->AddObserver(this);
147 registered_with_token_service_
= true;
150 void AccountReconcilor::UnregisterWithTokenService() {
151 if (!registered_with_token_service_
)
154 token_service_
->RemoveObserver(this);
155 registered_with_token_service_
= false;
158 bool AccountReconcilor::IsProfileConnected() {
159 return signin_manager_
->IsAuthenticated();
162 void AccountReconcilor::OnCookieChanged(const net::CanonicalCookie
* cookie
) {
163 if (cookie
->Name() == "LSID" &&
164 cookie
->Domain() == GaiaUrls::GetInstance()->gaia_url().host() &&
165 cookie
->IsSecure() && cookie
->IsHttpOnly()) {
166 VLOG(1) << "AccountReconcilor::OnCookieChanged: LSID changed";
168 // It is possible that O2RT is not available at this moment.
169 if (!token_service_
->GetAccounts().size()) {
170 VLOG(1) << "AccountReconcilor::OnCookieChanged: cookie change is ingored"
171 "because O2RT is not available yet.";
179 void AccountReconcilor::OnEndBatchChanges() {
180 VLOG(1) << "AccountReconcilor::OnEndBatchChanges";
184 void AccountReconcilor::GoogleSigninSucceeded(const std::string
& account_id
,
185 const std::string
& username
,
186 const std::string
& password
) {
187 VLOG(1) << "AccountReconcilor::GoogleSigninSucceeded: signed in";
188 RegisterForCookieChanges();
189 RegisterWithTokenService();
192 void AccountReconcilor::GoogleSignedOut(const std::string
& account_id
,
193 const std::string
& username
) {
194 VLOG(1) << "AccountReconcilor::GoogleSignedOut: signed out";
195 gaia_fetcher_
.reset();
196 get_gaia_accounts_callbacks_
.clear();
198 UnregisterWithTokenService();
199 UnregisterForCookieChanges();
200 PerformLogoutAllAccountsAction();
203 void AccountReconcilor::PerformMergeAction(const std::string
& account_id
) {
204 if (!switches::IsEnableAccountConsistency()) {
205 MarkAccountAsAddedToCookie(account_id
);
208 VLOG(1) << "AccountReconcilor::PerformMergeAction: " << account_id
;
209 merge_session_helper_
.LogIn(account_id
);
212 void AccountReconcilor::PerformLogoutAllAccountsAction() {
213 if (!switches::IsEnableAccountConsistency())
215 VLOG(1) << "AccountReconcilor::PerformLogoutAllAccountsAction";
216 merge_session_helper_
.LogOutAllAccounts();
219 void AccountReconcilor::StartReconcile() {
220 if (!IsProfileConnected() || is_reconcile_started_
||
221 get_gaia_accounts_callbacks_
.size() > 0 ||
222 merge_session_helper_
.is_running())
225 is_reconcile_started_
= true;
227 StartFetchingExternalCcResult();
229 // Reset state for validating gaia cookie.
230 are_gaia_accounts_set_
= false;
231 gaia_accounts_
.clear();
232 GetAccountsFromCookie(base::Bind(
233 &AccountReconcilor::ContinueReconcileActionAfterGetGaiaAccounts
,
234 base::Unretained(this)));
236 // Reset state for validating oauth2 tokens.
237 primary_account_
.clear();
238 chrome_accounts_
.clear();
239 add_to_cookie_
.clear();
240 ValidateAccountsFromTokenService();
243 void AccountReconcilor::GetAccountsFromCookie(
244 GetAccountsFromCookieCallback callback
) {
245 get_gaia_accounts_callbacks_
.push_back(callback
);
246 if (!gaia_fetcher_
) {
247 // There is no list account request in flight.
248 gaia_fetcher_
.reset(new GaiaAuthFetcher(
249 this, GaiaConstants::kChromeSource
, client_
->GetURLRequestContext()));
250 gaia_fetcher_
->StartListAccounts();
254 void AccountReconcilor::StartFetchingExternalCcResult() {
255 merge_session_helper_
.StartFetchingExternalCcResult();
258 void AccountReconcilor::OnListAccountsSuccess(const std::string
& data
) {
259 gaia_fetcher_
.reset();
261 // Get account information from response data.
262 std::vector
<std::pair
<std::string
, bool> > gaia_accounts
;
263 bool valid_json
= gaia::ParseListAccountsData(data
, &gaia_accounts
);
265 VLOG(1) << "AccountReconcilor::OnListAccountsSuccess: parsing error";
266 } else if (gaia_accounts
.size() > 0) {
267 VLOG(1) << "AccountReconcilor::OnListAccountsSuccess: "
268 << "Gaia " << gaia_accounts
.size() << " accounts, "
269 << "Primary is '" << gaia_accounts
[0].first
<< "'";
271 VLOG(1) << "AccountReconcilor::OnListAccountsSuccess: No accounts";
274 // There must be at least one callback waiting for result.
275 DCHECK(!get_gaia_accounts_callbacks_
.empty());
277 GoogleServiceAuthError error
=
278 !valid_json
? GoogleServiceAuthError(
279 GoogleServiceAuthError::UNEXPECTED_SERVICE_RESPONSE
)
280 : GoogleServiceAuthError::AuthErrorNone();
281 get_gaia_accounts_callbacks_
.front().Run(error
, gaia_accounts
);
282 get_gaia_accounts_callbacks_
.pop_front();
284 MayBeDoNextListAccounts();
287 void AccountReconcilor::OnListAccountsFailure(
288 const GoogleServiceAuthError
& error
) {
289 gaia_fetcher_
.reset();
290 VLOG(1) << "AccountReconcilor::OnListAccountsFailure: " << error
.ToString();
291 std::vector
<std::pair
<std::string
, bool> > empty_accounts
;
293 // There must be at least one callback waiting for result.
294 DCHECK(!get_gaia_accounts_callbacks_
.empty());
296 get_gaia_accounts_callbacks_
.front().Run(error
, empty_accounts
);
297 get_gaia_accounts_callbacks_
.pop_front();
299 MayBeDoNextListAccounts();
302 void AccountReconcilor::MayBeDoNextListAccounts() {
303 if (!get_gaia_accounts_callbacks_
.empty()) {
304 gaia_fetcher_
.reset(new GaiaAuthFetcher(
305 this, GaiaConstants::kChromeSource
, client_
->GetURLRequestContext()));
306 gaia_fetcher_
->StartListAccounts();
310 void AccountReconcilor::ContinueReconcileActionAfterGetGaiaAccounts(
311 const GoogleServiceAuthError
& error
,
312 const std::vector
<std::pair
<std::string
, bool> >& accounts
) {
313 if (error
.state() == GoogleServiceAuthError::NONE
) {
314 gaia_accounts_
= accounts
;
315 are_gaia_accounts_set_
= true;
322 void AccountReconcilor::ValidateAccountsFromTokenService() {
323 primary_account_
= signin_manager_
->GetAuthenticatedAccountId();
324 DCHECK(!primary_account_
.empty());
326 chrome_accounts_
= token_service_
->GetAccounts();
328 VLOG(1) << "AccountReconcilor::ValidateAccountsFromTokenService: "
329 << "Chrome " << chrome_accounts_
.size() << " accounts, "
330 << "Primary is '" << primary_account_
<< "'";
333 void AccountReconcilor::OnNewProfileManagementFlagChanged(
334 bool new_flag_status
) {
335 if (new_flag_status
) {
336 // The reconciler may have been newly created just before this call, or may
337 // have already existed and in mid-reconcile. To err on the safe side, force
346 void AccountReconcilor::FinishReconcile() {
347 VLOG(1) << "AccountReconcilor::FinishReconcile";
348 DCHECK(are_gaia_accounts_set_
);
349 DCHECK(add_to_cookie_
.empty());
350 int number_gaia_accounts
= gaia_accounts_
.size();
351 bool are_primaries_equal
= number_gaia_accounts
> 0 &&
352 gaia::AreEmailsSame(primary_account_
, gaia_accounts_
[0].first
);
354 // If there are any accounts in the gaia cookie but not in chrome, then
355 // those accounts need to be removed from the cookie. This means we need
356 // to blow the cookie away.
357 int removed_from_cookie
= 0;
358 for (size_t i
= 0; i
< gaia_accounts_
.size(); ++i
) {
359 const std::string
& gaia_account
= gaia_accounts_
[i
].first
;
360 if (gaia_accounts_
[i
].second
&&
361 chrome_accounts_
.end() ==
362 std::find_if(chrome_accounts_
.begin(),
363 chrome_accounts_
.end(),
364 std::bind1st(AreEmailsSameFunc(), gaia_account
))) {
365 ++removed_from_cookie
;
369 bool rebuild_cookie
= !are_primaries_equal
|| removed_from_cookie
> 0;
370 std::vector
<std::pair
<std::string
, bool> > original_gaia_accounts
=
372 if (rebuild_cookie
) {
373 VLOG(1) << "AccountReconcilor::FinishReconcile: rebuild cookie";
374 // Really messed up state. Blow away the gaia cookie completely and
375 // rebuild it, making sure the primary account as specified by the
376 // SigninManager is the first session in the gaia cookie.
377 PerformLogoutAllAccountsAction();
378 gaia_accounts_
.clear();
381 // Create a list of accounts that need to be added to the gaia cookie.
382 // The primary account must be first to make sure it becomes the default
383 // account in the case where chrome is completely rebuilding the cookie.
384 add_to_cookie_
.push_back(primary_account_
);
385 for (size_t i
= 0; i
< chrome_accounts_
.size(); ++i
) {
386 if (chrome_accounts_
[i
] != primary_account_
)
387 add_to_cookie_
.push_back(chrome_accounts_
[i
]);
390 // For each account known to chrome, PerformMergeAction() if the account is
391 // not already in the cookie jar or its state is invalid, or signal merge
392 // completed otherwise. Make a copy of |add_to_cookie_| since calls to
393 // SignalComplete() will change the array.
394 std::vector
<std::string
> add_to_cookie_copy
= add_to_cookie_
;
395 int added_to_cookie
= 0;
396 bool external_cc_result_completed
=
397 !merge_session_helper_
.StillFetchingExternalCcResult();
398 for (size_t i
= 0; i
< add_to_cookie_copy
.size(); ++i
) {
399 if (gaia_accounts_
.end() !=
400 std::find_if(gaia_accounts_
.begin(),
401 gaia_accounts_
.end(),
402 std::bind1st(EmailEqualToFunc(),
403 std::make_pair(add_to_cookie_copy
[i
],
405 merge_session_helper_
.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(EmailEqualToFunc(),
414 std::make_pair(add_to_cookie_copy
[i
],
421 // Log whether the external connection checks were completed when we tried
422 // to add the accounts to the cookie.
423 if (rebuild_cookie
|| added_to_cookie
> 0)
424 signin_metrics::LogExternalCcResultFetches(external_cc_result_completed
);
426 signin_metrics::LogSigninAccountReconciliation(chrome_accounts_
.size(),
431 number_gaia_accounts
);
432 first_execution_
= false;
433 CalculateIfReconcileIsDone();
434 ScheduleStartReconcileIfChromeAccountsChanged();
437 void AccountReconcilor::AbortReconcile() {
438 VLOG(1) << "AccountReconcilor::AbortReconcile: we'll try again later";
439 add_to_cookie_
.clear();
440 CalculateIfReconcileIsDone();
443 void AccountReconcilor::CalculateIfReconcileIsDone() {
444 is_reconcile_started_
= !add_to_cookie_
.empty();
445 if (!is_reconcile_started_
)
446 VLOG(1) << "AccountReconcilor::CalculateIfReconcileIsDone: done";
449 void AccountReconcilor::ScheduleStartReconcileIfChromeAccountsChanged() {
450 if (is_reconcile_started_
)
453 // Start a reconcile as the token accounts have changed.
454 VLOG(1) << "AccountReconcilor::StartReconcileIfChromeAccountsChanged";
455 std::vector
<std::string
> reconciled_accounts(chrome_accounts_
);
456 std::vector
<std::string
> new_chrome_accounts(token_service_
->GetAccounts());
457 std::sort(reconciled_accounts
.begin(), reconciled_accounts
.end());
458 std::sort(new_chrome_accounts
.begin(), new_chrome_accounts
.end());
459 if (reconciled_accounts
!= new_chrome_accounts
) {
460 base::MessageLoop::current()->PostTask(
462 base::Bind(&AccountReconcilor::StartReconcile
, base::Unretained(this)));
466 // Remove the account from the list that is being merged.
467 bool AccountReconcilor::MarkAccountAsAddedToCookie(
468 const std::string
& account_id
) {
469 for (std::vector
<std::string
>::iterator i
= add_to_cookie_
.begin();
470 i
!= add_to_cookie_
.end();
472 if (account_id
== *i
) {
473 add_to_cookie_
.erase(i
);
480 void AccountReconcilor::MergeSessionCompleted(
481 const std::string
& account_id
,
482 const GoogleServiceAuthError
& error
) {
483 VLOG(1) << "AccountReconcilor::MergeSessionCompleted: account_id="
484 << account_id
<< " error=" << error
.ToString();
486 if (MarkAccountAsAddedToCookie(account_id
)) {
487 CalculateIfReconcileIsDone();
488 ScheduleStartReconcileIfChromeAccountsChanged();