Add abhijeet.k@samsung.com to AUTHORS list.
[chromium-blink-merge.git] / components / signin / core / browser / account_reconcilor.cc
blobe4e2e3d4dd38cb4baebe23b73164a511ba7356bc
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"
7 #include <algorithm>
9 #include "base/bind.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"
27 namespace {
29 class EmailEqualToFunc : public std::equal_to<std::pair<std::string, bool> > {
30 public:
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> {
42 public:
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);
53 } // namespace
56 AccountReconcilor::AccountReconcilor(
57 ProfileOAuth2TokenService* token_service,
58 SigninManagerBase* signin_manager,
59 SigninClient* client,
60 GaiaCookieManagerService* cookie_manager_service)
61 : token_service_(token_service),
62 signin_manager_(signin_manager),
63 client_(client),
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
87 // wait for signin.
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) {
97 StartReconcile();
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(),
119 "LSID",
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_)
141 return;
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_)
150 return;
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_)
162 return;
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_)
171 return;
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_)
183 return;
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_)
191 return;
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,
202 bool removed) {
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.";
212 return;
215 StartReconcile();
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)
226 return;
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
230 // not needed.
231 if (primary_pattern.IsValid() &&
232 !primary_pattern.Matches(GaiaUrls::GetInstance()->gaia_url())) {
233 return;
236 VLOG(1) << "AccountReconcilor::OnContentSettingChanged";
237 StartReconcile();
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_;
244 StartReconcile();
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();
262 AbortReconcile();
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);
273 return;
275 VLOG(1) << "AccountReconcilor::PerformMergeAction: " << account_id;
276 cookie_manager_service_->AddAccountToCookie(account_id);
279 void AccountReconcilor::PerformLogoutAllAccountsAction() {
280 if (!switches::IsEnableAccountConsistency())
281 return;
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";
289 return;
292 if (is_reconcile_started_ || get_gaia_accounts_callbacks_.size() > 0)
293 return;
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);
316 if (!gaia_fetcher_)
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);
326 if (!valid_json) {
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 << "'";
332 } else {
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;
379 FinishReconcile();
380 } else {
381 AbortReconcile();
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
401 // a restart.
402 Shutdown();
403 Initialize(true);
404 } else {
405 Shutdown();
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 =
434 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],
465 true)))) {
466 cookie_manager_service_->SignalComplete(
467 add_to_cookie_copy[i],
468 GoogleServiceAuthError::AuthErrorNone());
469 } else {
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],
476 true)))) {
477 added_to_cookie++;
482 signin_metrics::LogSigninAccountReconciliation(chrome_accounts_.size(),
483 added_to_cookie,
484 removed_from_cookie,
485 are_primaries_equal,
486 first_execution_,
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_)
507 return;
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(
514 FROM_HERE,
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();
524 ++i) {
525 if (account_id == *i) {
526 add_to_cookie_.erase(i);
527 return true;
530 return false;
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)));