1// Copyright 2013 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 "base/logging.h"
6#include "base/time/time.h"
7#include "chrome/browser/chrome_notification_types.h"
8#include "chrome/browser/net/chrome_cookie_notification_details.h"
9#include "chrome/browser/profiles/profile.h"
10#include "chrome/browser/signin/account_reconcilor.h"
11#include "chrome/browser/signin/google_auto_login_helper.h"
12#include "chrome/browser/signin/profile_oauth2_token_service.h"
13#include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
14#include "chrome/browser/signin/signin_manager.h"
15#include "chrome/browser/signin/signin_manager_factory.h"
16#include "content/public/browser/browser_thread.h"
17#include "content/public/browser/notification_details.h"
18#include "content/public/browser/notification_source.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
23AccountReconcilor::AccountReconcilor(Profile* profile)
24    : profile_(profile),
25      are_gaia_accounts_set_(false),
26      requests_(NULL) {
27  DVLOG(1) << "AccountReconcilor::AccountReconcilor";
28  RegisterWithSigninManager();
29  RegisterWithCookieMonster();
30
31  // If this profile is not connected, the reconcilor should do nothing but
32  // wait for the connection.
33  if (IsProfileConnected()) {
34    RegisterWithTokenService();
35    StartPeriodicReconciliation();
36  }
37}
38
39AccountReconcilor::~AccountReconcilor() {
40  // Make sure shutdown was called first.
41  DCHECK(registrar_.IsEmpty());
42  DCHECK(!reconciliation_timer_.IsRunning());
43  DCHECK(!requests_);
44}
45
46void AccountReconcilor::Shutdown() {
47  DVLOG(1) << "AccountReconcilor::Shutdown";
48  DeleteAccessTokenRequests();
49  UnregisterWithSigninManager();
50  UnregisterWithTokenService();
51  UnregisterWithCookieMonster();
52  StopPeriodicReconciliation();
53}
54
55void AccountReconcilor::DeleteAccessTokenRequests() {
56  delete[] requests_;
57  requests_ = NULL;
58}
59
60void AccountReconcilor::RegisterWithCookieMonster() {
61  content::Source<Profile> source(profile_);
62  registrar_.Add(this, chrome::NOTIFICATION_COOKIE_CHANGED, source);
63}
64
65void AccountReconcilor::UnregisterWithCookieMonster() {
66  content::Source<Profile> source(profile_);
67  registrar_.Remove(this, chrome::NOTIFICATION_COOKIE_CHANGED, source);
68}
69
70void AccountReconcilor::RegisterWithSigninManager() {
71  content::Source<Profile> source(profile_);
72  registrar_.Add(this, chrome::NOTIFICATION_GOOGLE_SIGNIN_SUCCESSFUL, source);
73  registrar_.Add(this, chrome::NOTIFICATION_GOOGLE_SIGNED_OUT, source);
74}
75
76void AccountReconcilor::UnregisterWithSigninManager() {
77  content::Source<Profile> source(profile_);
78  registrar_.Remove(
79      this, chrome::NOTIFICATION_GOOGLE_SIGNIN_SUCCESSFUL, source);
80  registrar_.Remove(this, chrome::NOTIFICATION_GOOGLE_SIGNED_OUT, source);
81}
82
83void AccountReconcilor::RegisterWithTokenService() {
84  DVLOG(1) << "AccountReconcilor::RegisterWithTokenService";
85  ProfileOAuth2TokenService* token_service =
86      ProfileOAuth2TokenServiceFactory::GetForProfile(profile_);
87  token_service->AddObserver(this);
88}
89
90void AccountReconcilor::UnregisterWithTokenService() {
91  ProfileOAuth2TokenService* token_service =
92      ProfileOAuth2TokenServiceFactory::GetForProfile(profile_);
93  token_service->RemoveObserver(this);
94}
95
96bool AccountReconcilor::IsProfileConnected() {
97  return !SigninManagerFactory::GetForProfile(profile_)->
98      GetAuthenticatedUsername().empty();
99}
100
101void AccountReconcilor::StartPeriodicReconciliation() {
102  DVLOG(1) << "AccountReconcilor::StartPeriodicReconciliation";
103  // TODO(rogerta): pick appropriate thread and timeout value.
104  reconciliation_timer_.Start(
105      FROM_HERE,
106      base::TimeDelta::FromSeconds(300),
107      this,
108      &AccountReconcilor::PeriodicReconciliation);
109}
110
111void AccountReconcilor::StopPeriodicReconciliation() {
112  DVLOG(1) << "AccountReconcilor::StopPeriodicReconciliation";
113  reconciliation_timer_.Stop();
114}
115
116void AccountReconcilor::PeriodicReconciliation() {
117  DVLOG(1) << "AccountReconcilor::PeriodicReconciliation";
118  StartReconcileAction();
119}
120
121void AccountReconcilor::Observe(int type,
122                                const content::NotificationSource& source,
123                                const content::NotificationDetails& details) {
124  switch (type) {
125    case chrome::NOTIFICATION_GOOGLE_SIGNIN_SUCCESSFUL:
126      DVLOG(1) << "AccountReconcilor::Observe: signed in";
127      RegisterWithTokenService();
128      StartPeriodicReconciliation();
129      break;
130    case chrome::NOTIFICATION_GOOGLE_SIGNED_OUT:
131      DVLOG(1) << "AccountReconcilor::Observe: signed out";
132      UnregisterWithTokenService();
133      StopPeriodicReconciliation();
134      break;
135    case chrome::NOTIFICATION_COOKIE_CHANGED:
136      OnCookieChanged(content::Details<ChromeCookieDetails>(details).ptr());
137      break;
138    default:
139      NOTREACHED();
140      break;
141  }
142}
143
144void AccountReconcilor::OnCookieChanged(ChromeCookieDetails* details) {
145  // TODO(acleung): Filter out cookies by looking at the domain.
146  // StartReconcileAction();
147}
148
149void AccountReconcilor::OnRefreshTokenAvailable(const std::string& account_id) {
150  DVLOG(1) << "AccountReconcilor::OnRefreshTokenAvailable: " << account_id;
151  PerformMergeAction(account_id);
152}
153
154void AccountReconcilor::OnRefreshTokenRevoked(const std::string& account_id) {
155  DVLOG(1) << "AccountReconcilor::OnRefreshTokenRevoked: " << account_id;
156  PerformRemoveAction(account_id);
157}
158
159void AccountReconcilor::OnRefreshTokensLoaded() {}
160
161void AccountReconcilor::PerformMergeAction(const std::string& account_id) {
162  // GoogleAutoLoginHelper deletes itself upon success / failure.
163  GoogleAutoLoginHelper* helper = new GoogleAutoLoginHelper(profile_);
164  helper->LogIn(account_id);
165}
166
167void AccountReconcilor::PerformRemoveAction(const std::string& account_id) {
168  // TODO(acleung): Implement this:
169}
170
171void AccountReconcilor::StartReconcileAction() {
172  if (!IsProfileConnected())
173    return;
174
175  // Reset state for validating gaia cookie.
176  are_gaia_accounts_set_ = false;
177  gaia_accounts_.clear();
178  GetAccountsFromCookie();
179
180  // Reset state for validating oauth2 tokens.
181  primary_account_.clear();
182  chrome_accounts_.clear();
183  DeleteAccessTokenRequests();
184  valid_chrome_accounts_.clear();
185  invalid_chrome_accounts_.clear();
186  ValidateAccountsFromTokenService();
187}
188
189void AccountReconcilor::GetAccountsFromCookie() {
190  gaia_fetcher_.reset(new GaiaAuthFetcher(this, GaiaConstants::kChromeSource,
191                                          profile_->GetRequestContext()));
192  gaia_fetcher_->StartListAccounts();
193}
194
195void AccountReconcilor::OnListAccountsSuccess(const std::string& data) {
196  gaia_fetcher_.reset();
197
198  // Get account information from response data.
199  gaia_accounts_ = gaia::ParseListAccountsData(data);
200  if (gaia_accounts_.size() > 0) {
201    DVLOG(1) << "AccountReconcilor::OnListAccountsSuccess: "
202             << "Gaia " << gaia_accounts_.size() << " accounts, "
203             << "Primary is '" << gaia_accounts_[0] << "'";
204  } else {
205    DVLOG(1) << "AccountReconcilor::OnListAccountsSuccess: No accounts";
206  }
207
208  are_gaia_accounts_set_ = true;
209  FinishReconcileAction();
210}
211
212void AccountReconcilor::OnListAccountsFailure(
213    const GoogleServiceAuthError& error) {
214  gaia_fetcher_.reset();
215  DVLOG(1) << "AccountReconcilor::OnListAccountsFailure: " << error.ToString();
216
217  are_gaia_accounts_set_ = true;
218  FinishReconcileAction();
219}
220
221void AccountReconcilor::ValidateAccountsFromTokenService() {
222  primary_account_ =
223      SigninManagerFactory::GetForProfile(profile_)->GetAuthenticatedUsername();
224  DCHECK(!primary_account_.empty());
225
226  ProfileOAuth2TokenService* token_service =
227      ProfileOAuth2TokenServiceFactory::GetForProfile(profile_);
228  chrome_accounts_ = token_service->GetAccounts();
229  DCHECK(chrome_accounts_.size() > 0);
230
231  DVLOG(1) << "AccountReconcilor::ValidateAccountsFromTokenService: "
232            << "Chrome " << chrome_accounts_.size() << " accounts, "
233            << "Primary is '" << primary_account_ << "'";
234
235  DCHECK(!requests_);
236  requests_ =
237      new scoped_ptr<OAuth2TokenService::Request>[chrome_accounts_.size()];
238  for (size_t i = 0; i < chrome_accounts_.size(); ++i) {
239    requests_[i] = token_service->StartRequest(chrome_accounts_[i],
240                                               OAuth2TokenService::ScopeSet(),
241                                               this);
242  }
243}
244
245void AccountReconcilor::OnGetTokenSuccess(
246    const OAuth2TokenService::Request* request,
247    const std::string& access_token,
248    const base::Time& expiration_time) {
249  DVLOG(1) << "AccountReconcilor::OnGetTokenSuccess: valid "
250           << request->GetAccountId();
251  valid_chrome_accounts_.insert(request->GetAccountId());
252  FinishReconcileAction();
253}
254
255void AccountReconcilor::OnGetTokenFailure(
256    const OAuth2TokenService::Request* request,
257    const GoogleServiceAuthError& error) {
258  DVLOG(1) << "AccountReconcilor::OnGetTokenSuccess: invalid "
259           << request->GetAccountId();
260  invalid_chrome_accounts_.insert(request->GetAccountId());
261  FinishReconcileAction();
262}
263
264void AccountReconcilor::FinishReconcileAction() {
265  // Make sure that the process of validating the gaia cookie and the oauth2
266  // tokens individually is done before proceeding with reconciliation.
267  if (!are_gaia_accounts_set_ ||
268      (chrome_accounts_.size() != (valid_chrome_accounts_.size() +
269                                   invalid_chrome_accounts_.size()))) {
270    return;
271  }
272
273  DVLOG(1) << "AccountReconcilor::FinishReconcileAction";
274
275  bool are_primaries_equal =
276      gaia_accounts_.size() > 0 && primary_account_ == gaia_accounts_[0];
277  bool have_same_accounts = chrome_accounts_.size() == gaia_accounts_.size();
278  if (have_same_accounts) {
279    for (size_t i = 0; i < gaia_accounts_.size(); ++i) {
280      if (std::find(chrome_accounts_.begin(), chrome_accounts_.end(),
281              gaia_accounts_[i]) == chrome_accounts_.end()) {
282        have_same_accounts = false;
283        break;
284      }
285    }
286  }
287
288  if (!are_primaries_equal || !have_same_accounts) {
289    // TODO(rogerta): fix things up.
290  }
291}
292