• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
4 
5 #include "components/signin/core/browser/account_reconcilor.h"
6 
7 #include <algorithm>
8 
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 "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"
26 
27 
28 namespace {
29 
30 class EmailEqualToFunc : public std::equal_to<std::pair<std::string, bool> > {
31  public:
32   bool operator()(const std::pair<std::string, bool>& p1,
33                   const std::pair<std::string, bool>& p2) const;
34 };
35 
operator ()(const std::pair<std::string,bool> & p1,const std::pair<std::string,bool> & p2) const36 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);
40 }
41 
42 class AreEmailsSameFunc : public std::equal_to<std::string> {
43  public:
44   bool operator()(const std::string& p1,
45                   const std::string& p2) const;
46 };
47 
operator ()(const std::string & p1,const std::string & p2) const48 bool AreEmailsSameFunc::operator()(
49     const std::string& p1,
50     const std::string& p2) const {
51   return gaia::AreEmailsSame(p1, p2);
52 }
53 
54 }  // namespace
55 
56 
AccountReconcilor(ProfileOAuth2TokenService * token_service,SigninManagerBase * signin_manager,SigninClient * client)57 AccountReconcilor::AccountReconcilor(ProfileOAuth2TokenService* token_service,
58                                      SigninManagerBase* signin_manager,
59                                      SigninClient* client)
60     : token_service_(token_service),
61       signin_manager_(signin_manager),
62       client_(client),
63       merge_session_helper_(token_service_,
64                             client->GetURLRequestContext(),
65                             this),
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";
71 }
72 
~AccountReconcilor()73 AccountReconcilor::~AccountReconcilor() {
74   VLOG(1) << "AccountReconcilor::~AccountReconcilor";
75   // Make sure shutdown was called first.
76   DCHECK(!registered_with_token_service_);
77 }
78 
Initialize(bool start_reconcile_if_tokens_available)79 void AccountReconcilor::Initialize(bool start_reconcile_if_tokens_available) {
80   VLOG(1) << "AccountReconcilor::Initialize";
81   RegisterWithSigninManager();
82 
83   // If this user is not signed in, the reconcilor should do nothing but
84   // wait for signin.
85   if (IsProfileConnected()) {
86     RegisterForCookieChanges();
87     RegisterWithTokenService();
88 
89     // Start a reconcile if the tokens are already loaded.
90     if (start_reconcile_if_tokens_available &&
91         token_service_->GetAccounts().size() > 0) {
92       StartReconcile();
93     }
94   }
95 }
96 
Shutdown()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();
106 }
107 
AddMergeSessionObserver(MergeSessionHelper::Observer * observer)108 void AccountReconcilor::AddMergeSessionObserver(
109     MergeSessionHelper::Observer* observer) {
110   merge_session_helper_.AddObserver(observer);
111 }
112 
RemoveMergeSessionObserver(MergeSessionHelper::Observer * observer)113 void AccountReconcilor::RemoveMergeSessionObserver(
114     MergeSessionHelper::Observer* observer) {
115   merge_session_helper_.RemoveObserver(observer);
116 }
117 
RegisterForCookieChanges()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)));
124 }
125 
UnregisterForCookieChanges()126 void AccountReconcilor::UnregisterForCookieChanges() {
127   cookie_changed_subscription_.reset();
128 }
129 
RegisterWithSigninManager()130 void AccountReconcilor::RegisterWithSigninManager() {
131   signin_manager_->AddObserver(this);
132 }
133 
UnregisterWithSigninManager()134 void AccountReconcilor::UnregisterWithSigninManager() {
135   signin_manager_->RemoveObserver(this);
136 }
137 
RegisterWithTokenService()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_)
144     return;
145 
146   token_service_->AddObserver(this);
147   registered_with_token_service_ = true;
148 }
149 
UnregisterWithTokenService()150 void AccountReconcilor::UnregisterWithTokenService() {
151   if (!registered_with_token_service_)
152     return;
153 
154   token_service_->RemoveObserver(this);
155   registered_with_token_service_ = false;
156 }
157 
IsProfileConnected()158 bool AccountReconcilor::IsProfileConnected() {
159   return signin_manager_->IsAuthenticated();
160 }
161 
OnCookieChanged(const net::CanonicalCookie * cookie)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";
167 
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.";
172       return;
173     }
174 
175     StartReconcile();
176   }
177 }
178 
OnEndBatchChanges()179 void AccountReconcilor::OnEndBatchChanges() {
180   VLOG(1) << "AccountReconcilor::OnEndBatchChanges";
181   StartReconcile();
182 }
183 
GoogleSigninSucceeded(const std::string & account_id,const std::string & username,const std::string & password)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();
190 }
191 
GoogleSignedOut(const std::string & account_id,const std::string & username)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();
197   AbortReconcile();
198   UnregisterWithTokenService();
199   UnregisterForCookieChanges();
200   PerformLogoutAllAccountsAction();
201 }
202 
PerformMergeAction(const std::string & account_id)203 void AccountReconcilor::PerformMergeAction(const std::string& account_id) {
204   if (!switches::IsEnableAccountConsistency()) {
205     MarkAccountAsAddedToCookie(account_id);
206     return;
207   }
208   VLOG(1) << "AccountReconcilor::PerformMergeAction: " << account_id;
209   merge_session_helper_.LogIn(account_id);
210 }
211 
PerformLogoutAllAccountsAction()212 void AccountReconcilor::PerformLogoutAllAccountsAction() {
213   if (!switches::IsEnableAccountConsistency())
214     return;
215   VLOG(1) << "AccountReconcilor::PerformLogoutAllAccountsAction";
216   merge_session_helper_.LogOutAllAccounts();
217 }
218 
StartReconcile()219 void AccountReconcilor::StartReconcile() {
220   if (!IsProfileConnected() || is_reconcile_started_ ||
221       get_gaia_accounts_callbacks_.size() > 0 ||
222       merge_session_helper_.is_running())
223     return;
224 
225   is_reconcile_started_ = true;
226 
227   StartFetchingExternalCcResult();
228 
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)));
235 
236   // Reset state for validating oauth2 tokens.
237   primary_account_.clear();
238   chrome_accounts_.clear();
239   add_to_cookie_.clear();
240   ValidateAccountsFromTokenService();
241 }
242 
GetAccountsFromCookie(GetAccountsFromCookieCallback callback)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();
251   }
252 }
253 
StartFetchingExternalCcResult()254 void AccountReconcilor::StartFetchingExternalCcResult() {
255   merge_session_helper_.StartFetchingExternalCcResult();
256 }
257 
OnListAccountsSuccess(const std::string & data)258 void AccountReconcilor::OnListAccountsSuccess(const std::string& data) {
259   gaia_fetcher_.reset();
260 
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);
264   if (!valid_json) {
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 << "'";
270   } else {
271     VLOG(1) << "AccountReconcilor::OnListAccountsSuccess: No accounts";
272   }
273 
274   // There must be at least one callback waiting for result.
275   DCHECK(!get_gaia_accounts_callbacks_.empty());
276 
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();
283 
284   MayBeDoNextListAccounts();
285 }
286 
OnListAccountsFailure(const GoogleServiceAuthError & error)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;
292 
293   // There must be at least one callback waiting for result.
294   DCHECK(!get_gaia_accounts_callbacks_.empty());
295 
296   get_gaia_accounts_callbacks_.front().Run(error, empty_accounts);
297   get_gaia_accounts_callbacks_.pop_front();
298 
299   MayBeDoNextListAccounts();
300 }
301 
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();
307   }
308 }
309 
ContinueReconcileActionAfterGetGaiaAccounts(const GoogleServiceAuthError & error,const std::vector<std::pair<std::string,bool>> & accounts)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;
316     FinishReconcile();
317   } else {
318     AbortReconcile();
319   }
320 }
321 
ValidateAccountsFromTokenService()322 void AccountReconcilor::ValidateAccountsFromTokenService() {
323   primary_account_ = signin_manager_->GetAuthenticatedAccountId();
324   DCHECK(!primary_account_.empty());
325 
326   chrome_accounts_ = token_service_->GetAccounts();
327 
328   VLOG(1) << "AccountReconcilor::ValidateAccountsFromTokenService: "
329           << "Chrome " << chrome_accounts_.size() << " accounts, "
330           << "Primary is '" << primary_account_ << "'";
331 }
332 
OnNewProfileManagementFlagChanged(bool new_flag_status)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
338     // a restart.
339     Shutdown();
340     Initialize(true);
341   } else {
342     Shutdown();
343   }
344 }
345 
FinishReconcile()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);
353 
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;
366     }
367   }
368 
369   bool rebuild_cookie = !are_primaries_equal || removed_from_cookie > 0;
370   std::vector<std::pair<std::string, bool> > original_gaia_accounts =
371       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();
379   }
380 
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]);
388   }
389 
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],
404                                                      true)))) {
405       merge_session_helper_.SignalComplete(
406           add_to_cookie_copy[i],
407           GoogleServiceAuthError::AuthErrorNone());
408     } else {
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],
415                                                        true)))) {
416         added_to_cookie++;
417       }
418     }
419   }
420 
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);
425 
426   signin_metrics::LogSigninAccountReconciliation(chrome_accounts_.size(),
427                                                  added_to_cookie,
428                                                  removed_from_cookie,
429                                                  are_primaries_equal,
430                                                  first_execution_,
431                                                  number_gaia_accounts);
432   first_execution_ = false;
433   CalculateIfReconcileIsDone();
434   ScheduleStartReconcileIfChromeAccountsChanged();
435 }
436 
AbortReconcile()437 void AccountReconcilor::AbortReconcile() {
438   VLOG(1) << "AccountReconcilor::AbortReconcile: we'll try again later";
439   add_to_cookie_.clear();
440   CalculateIfReconcileIsDone();
441 }
442 
CalculateIfReconcileIsDone()443 void AccountReconcilor::CalculateIfReconcileIsDone() {
444   is_reconcile_started_ = !add_to_cookie_.empty();
445   if (!is_reconcile_started_)
446     VLOG(1) << "AccountReconcilor::CalculateIfReconcileIsDone: done";
447 }
448 
ScheduleStartReconcileIfChromeAccountsChanged()449 void AccountReconcilor::ScheduleStartReconcileIfChromeAccountsChanged() {
450   if (is_reconcile_started_)
451     return;
452 
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(
461         FROM_HERE,
462         base::Bind(&AccountReconcilor::StartReconcile, base::Unretained(this)));
463   }
464 }
465 
466 // Remove the account from the list that is being merged.
MarkAccountAsAddedToCookie(const std::string & account_id)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();
471        ++i) {
472     if (account_id == *i) {
473       add_to_cookie_.erase(i);
474       return true;
475     }
476   }
477   return false;
478 }
479 
MergeSessionCompleted(const std::string & account_id,const GoogleServiceAuthError & error)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();
485 
486   if (MarkAccountAsAddedToCookie(account_id)) {
487     CalculateIfReconcileIsDone();
488     ScheduleStartReconcileIfChromeAccountsChanged();
489   }
490 }
491