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
AccountReconcilor(Profile * profile)23 AccountReconcilor::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
~AccountReconcilor()39 AccountReconcilor::~AccountReconcilor() {
40 // Make sure shutdown was called first.
41 DCHECK(registrar_.IsEmpty());
42 DCHECK(!reconciliation_timer_.IsRunning());
43 DCHECK(!requests_);
44 }
45
Shutdown()46 void AccountReconcilor::Shutdown() {
47 DVLOG(1) << "AccountReconcilor::Shutdown";
48 DeleteAccessTokenRequests();
49 UnregisterWithSigninManager();
50 UnregisterWithTokenService();
51 UnregisterWithCookieMonster();
52 StopPeriodicReconciliation();
53 }
54
DeleteAccessTokenRequests()55 void AccountReconcilor::DeleteAccessTokenRequests() {
56 delete[] requests_;
57 requests_ = NULL;
58 }
59
RegisterWithCookieMonster()60 void AccountReconcilor::RegisterWithCookieMonster() {
61 content::Source<Profile> source(profile_);
62 registrar_.Add(this, chrome::NOTIFICATION_COOKIE_CHANGED, source);
63 }
64
UnregisterWithCookieMonster()65 void AccountReconcilor::UnregisterWithCookieMonster() {
66 content::Source<Profile> source(profile_);
67 registrar_.Remove(this, chrome::NOTIFICATION_COOKIE_CHANGED, source);
68 }
69
RegisterWithSigninManager()70 void 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
UnregisterWithSigninManager()76 void 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
RegisterWithTokenService()83 void AccountReconcilor::RegisterWithTokenService() {
84 DVLOG(1) << "AccountReconcilor::RegisterWithTokenService";
85 ProfileOAuth2TokenService* token_service =
86 ProfileOAuth2TokenServiceFactory::GetForProfile(profile_);
87 token_service->AddObserver(this);
88 }
89
UnregisterWithTokenService()90 void AccountReconcilor::UnregisterWithTokenService() {
91 ProfileOAuth2TokenService* token_service =
92 ProfileOAuth2TokenServiceFactory::GetForProfile(profile_);
93 token_service->RemoveObserver(this);
94 }
95
IsProfileConnected()96 bool AccountReconcilor::IsProfileConnected() {
97 return !SigninManagerFactory::GetForProfile(profile_)->
98 GetAuthenticatedUsername().empty();
99 }
100
StartPeriodicReconciliation()101 void 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
StopPeriodicReconciliation()111 void AccountReconcilor::StopPeriodicReconciliation() {
112 DVLOG(1) << "AccountReconcilor::StopPeriodicReconciliation";
113 reconciliation_timer_.Stop();
114 }
115
PeriodicReconciliation()116 void AccountReconcilor::PeriodicReconciliation() {
117 DVLOG(1) << "AccountReconcilor::PeriodicReconciliation";
118 StartReconcileAction();
119 }
120
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)121 void 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
OnCookieChanged(ChromeCookieDetails * details)144 void AccountReconcilor::OnCookieChanged(ChromeCookieDetails* details) {
145 // TODO(acleung): Filter out cookies by looking at the domain.
146 // StartReconcileAction();
147 }
148
OnRefreshTokenAvailable(const std::string & account_id)149 void AccountReconcilor::OnRefreshTokenAvailable(const std::string& account_id) {
150 DVLOG(1) << "AccountReconcilor::OnRefreshTokenAvailable: " << account_id;
151 PerformMergeAction(account_id);
152 }
153
OnRefreshTokenRevoked(const std::string & account_id)154 void AccountReconcilor::OnRefreshTokenRevoked(const std::string& account_id) {
155 DVLOG(1) << "AccountReconcilor::OnRefreshTokenRevoked: " << account_id;
156 PerformRemoveAction(account_id);
157 }
158
OnRefreshTokensLoaded()159 void AccountReconcilor::OnRefreshTokensLoaded() {}
160
PerformMergeAction(const std::string & account_id)161 void 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
PerformRemoveAction(const std::string & account_id)167 void AccountReconcilor::PerformRemoveAction(const std::string& account_id) {
168 // TODO(acleung): Implement this:
169 }
170
StartReconcileAction()171 void 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
GetAccountsFromCookie()189 void AccountReconcilor::GetAccountsFromCookie() {
190 gaia_fetcher_.reset(new GaiaAuthFetcher(this, GaiaConstants::kChromeSource,
191 profile_->GetRequestContext()));
192 gaia_fetcher_->StartListAccounts();
193 }
194
OnListAccountsSuccess(const std::string & data)195 void 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
OnListAccountsFailure(const GoogleServiceAuthError & error)212 void 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
ValidateAccountsFromTokenService()221 void 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
OnGetTokenSuccess(const OAuth2TokenService::Request * request,const std::string & access_token,const base::Time & expiration_time)245 void 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
OnGetTokenFailure(const OAuth2TokenService::Request * request,const GoogleServiceAuthError & error)255 void 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
FinishReconcileAction()264 void 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