OLD | NEW |
| (Empty) |
1 // Copyright (c) 2006-2009 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 "chrome/browser/sync/engine/auth_watcher.h" | |
6 | |
7 #include "base/file_util.h" | |
8 #include "base/string_util.h" | |
9 #include "chrome/browser/sync/engine/all_status.h" | |
10 #include "chrome/browser/sync/engine/authenticator.h" | |
11 #include "chrome/browser/sync/engine/net/server_connection_manager.h" | |
12 #include "chrome/browser/sync/syncable/directory_manager.h" | |
13 #include "chrome/browser/sync/syncable/syncable.h" | |
14 #include "chrome/browser/sync/util/user_settings.h" | |
15 #include "chrome/common/deprecated/event_sys-inl.h" | |
16 #include "chrome/common/net/gaia/gaia_authenticator.h" | |
17 | |
18 // How authentication happens: | |
19 // | |
20 // Kick Off: | |
21 // The sync API looks to see if the user's name and | |
22 // password are stored. If so, it calls authwatcher.Authenticate() with | |
23 // them. Otherwise it fires an error event. | |
24 // | |
25 // On failed Gaia Auth: | |
26 // The AuthWatcher attempts to use saved hashes to authenticate | |
27 // locally, and on success opens the share. | |
28 // On failure, fires an error event. | |
29 // | |
30 // On successful Gaia Auth: | |
31 // AuthWatcher launches a thread to open the share and to get the | |
32 // authentication token from the sync server. | |
33 | |
34 using std::pair; | |
35 using std::string; | |
36 using std::vector; | |
37 | |
38 namespace browser_sync { | |
39 | |
40 AuthWatcher::AuthWatcher(DirectoryManager* dirman, | |
41 ServerConnectionManager* scm, | |
42 const string& user_agent, | |
43 const string& service_id, | |
44 const string& gaia_url, | |
45 UserSettings* user_settings, | |
46 gaia::GaiaAuthenticator* gaia_auth) | |
47 : gaia_(gaia_auth), | |
48 dirman_(dirman), | |
49 scm_(scm), | |
50 status_(NOT_AUTHENTICATED), | |
51 user_settings_(user_settings), | |
52 auth_backend_thread_("SyncEngine_AuthWatcherThread"), | |
53 current_attempt_trigger_(AuthWatcherEvent::USER_INITIATED) { | |
54 | |
55 if (!auth_backend_thread_.Start()) | |
56 NOTREACHED() << "Couldn't start SyncEngine_AuthWatcherThread"; | |
57 | |
58 gaia_->set_message_loop(message_loop()); | |
59 loop_proxy_ = auth_backend_thread_.message_loop_proxy(); | |
60 | |
61 connmgr_hookup_.reset( | |
62 NewEventListenerHookup(scm->channel(), this, | |
63 &AuthWatcher::HandleServerConnectionEvent)); | |
64 AuthWatcherEvent done = { AuthWatcherEvent::AUTHWATCHER_DESTROYED }; | |
65 channel_.reset(new Channel(done)); | |
66 } | |
67 | |
68 void AuthWatcher::PersistCredentials() { | |
69 DCHECK_EQ(MessageLoop::current(), message_loop()); | |
70 gaia::GaiaAuthenticator::AuthResults results = gaia_->results(); | |
71 | |
72 // We just successfully signed in again, let's clear out any residual cached | |
73 // login data from earlier sessions. | |
74 ClearAuthenticationData(); | |
75 | |
76 user_settings_->StoreEmailForSignin(results.email, results.primary_email); | |
77 results.email = results.primary_email; | |
78 gaia_->SetUsernamePassword(results.primary_email, results.password); | |
79 if (!user_settings_->VerifyAgainstStoredHash(results.email, results.password)) | |
80 user_settings_->StoreHashedPassword(results.email, results.password); | |
81 | |
82 user_settings_->SetAuthTokenForService(results.email, | |
83 SYNC_SERVICE_NAME, | |
84 gaia_->auth_token()); | |
85 } | |
86 | |
87 // TODO(chron): Full integration test suite needed. http://crbug.com/35429 | |
88 void AuthWatcher::RenewAuthToken(const std::string& updated_token) { | |
89 message_loop_proxy()->PostTask(FROM_HERE, NewRunnableMethod(this, | |
90 &AuthWatcher::DoRenewAuthToken, updated_token)); | |
91 } | |
92 | |
93 void AuthWatcher::DoRenewAuthToken(const std::string& updated_token) { | |
94 DCHECK_EQ(MessageLoop::current(), message_loop()); | |
95 // TODO(chron): We should probably only store auth token in one place. | |
96 if (scm_->auth_token() == updated_token) { | |
97 return; // This thread is the only one writing to the SCM's auth token. | |
98 } | |
99 LOG(INFO) << "Updating auth token:" << updated_token; | |
100 scm_->set_auth_token(updated_token); | |
101 gaia_->RenewAuthToken(updated_token); // Must be on AuthWatcher thread | |
102 user_settings_->SetAuthTokenForService(user_settings_->email(), | |
103 SYNC_SERVICE_NAME, | |
104 updated_token); | |
105 | |
106 NotifyAuthChanged(user_settings_->email(), updated_token, true); | |
107 } | |
108 | |
109 void AuthWatcher::AuthenticateWithLsid(const std::string& lsid) { | |
110 message_loop_proxy()->PostTask(FROM_HERE, NewRunnableMethod(this, | |
111 &AuthWatcher::DoAuthenticateWithLsid, lsid)); | |
112 } | |
113 | |
114 void AuthWatcher::DoAuthenticateWithLsid(const std::string& lsid) { | |
115 DCHECK_EQ(MessageLoop::current(), message_loop()); | |
116 | |
117 AuthWatcherEvent event = { AuthWatcherEvent::AUTHENTICATION_ATTEMPT_START }; | |
118 NotifyListeners(&event); | |
119 | |
120 if (gaia_->AuthenticateWithLsid(lsid)) { | |
121 PersistCredentials(); | |
122 DoAuthenticateWithToken(gaia_->email(), gaia_->auth_token()); | |
123 } else { | |
124 ProcessGaiaAuthFailure(); | |
125 } | |
126 } | |
127 | |
128 const char kAuthWatcher[] = "AuthWatcher"; | |
129 | |
130 void AuthWatcher::AuthenticateWithToken(const std::string& gaia_email, | |
131 const std::string& auth_token) { | |
132 message_loop_proxy()->PostTask(FROM_HERE, NewRunnableMethod(this, | |
133 &AuthWatcher::DoAuthenticateWithToken, gaia_email, auth_token)); | |
134 } | |
135 | |
136 void AuthWatcher::DoAuthenticateWithToken(const std::string& gaia_email, | |
137 const std::string& auth_token) { | |
138 DCHECK_EQ(MessageLoop::current(), message_loop()); | |
139 | |
140 Authenticator auth(scm_, user_settings_); | |
141 Authenticator::AuthenticationResult result = | |
142 auth.AuthenticateToken(auth_token); | |
143 string email = gaia_email; | |
144 if (auth.display_email() && *auth.display_email()) { | |
145 email = auth.display_email(); | |
146 LOG(INFO) << "Auth returned email " << email << " for gaia email " << | |
147 gaia_email; | |
148 } | |
149 | |
150 AuthWatcherEvent event = {AuthWatcherEvent::ILLEGAL_VALUE , 0}; | |
151 gaia_->SetUsername(email); | |
152 gaia_->SetAuthToken(auth_token); | |
153 const bool was_authenticated = NOT_AUTHENTICATED != status_; | |
154 switch (result) { | |
155 case Authenticator::SUCCESS: | |
156 { | |
157 status_ = GAIA_AUTHENTICATED; | |
158 const std::string& share_name = email; | |
159 user_settings_->SwitchUser(email); | |
160 scm_->set_auth_token(auth_token); | |
161 | |
162 if (!was_authenticated) { | |
163 LOG(INFO) << "Opening DB for AuthenticateWithToken (" | |
164 << share_name << ")"; | |
165 dirman_->Open(share_name); | |
166 } | |
167 NotifyAuthChanged(email, auth_token, false); | |
168 return; | |
169 } | |
170 case Authenticator::BAD_AUTH_TOKEN: | |
171 event.what_happened = AuthWatcherEvent::SERVICE_AUTH_FAILED; | |
172 break; | |
173 case Authenticator::CORRUPT_SERVER_RESPONSE: | |
174 case Authenticator::SERVICE_DOWN: | |
175 event.what_happened = AuthWatcherEvent::SERVICE_CONNECTION_FAILED; | |
176 break; | |
177 case Authenticator::USER_NOT_ACTIVATED: | |
178 event.what_happened = AuthWatcherEvent::SERVICE_USER_NOT_SIGNED_UP; | |
179 break; | |
180 default: | |
181 LOG(FATAL) << "Illegal return from AuthenticateToken"; | |
182 return; | |
183 } | |
184 // Always fall back to local authentication. | |
185 if (was_authenticated || AuthenticateLocally(email)) { | |
186 if (AuthWatcherEvent::SERVICE_CONNECTION_FAILED == event.what_happened) | |
187 return; | |
188 } | |
189 DCHECK_NE(event.what_happened, AuthWatcherEvent::ILLEGAL_VALUE); | |
190 NotifyListeners(&event); | |
191 } | |
192 | |
193 bool AuthWatcher::AuthenticateLocally(string email) { | |
194 DCHECK_EQ(MessageLoop::current(), message_loop()); | |
195 user_settings_->GetEmailForSignin(&email); | |
196 if (file_util::PathExists(FilePath(dirman_->GetSyncDataDatabasePath()))) { | |
197 gaia_->SetUsername(email); | |
198 status_ = LOCALLY_AUTHENTICATED; | |
199 user_settings_->SwitchUser(email); | |
200 LOG(INFO) << "Opening DB for AuthenticateLocally (" << email << ")"; | |
201 dirman_->Open(email); | |
202 NotifyAuthChanged(email, "", false); | |
203 return true; | |
204 } else { | |
205 return false; | |
206 } | |
207 } | |
208 | |
209 bool AuthWatcher::AuthenticateLocally(string email, const string& password) { | |
210 DCHECK_EQ(MessageLoop::current(), message_loop()); | |
211 user_settings_->GetEmailForSignin(&email); | |
212 return user_settings_->VerifyAgainstStoredHash(email, password) | |
213 && AuthenticateLocally(email); | |
214 } | |
215 | |
216 void AuthWatcher::ProcessGaiaAuthFailure() { | |
217 DCHECK_EQ(MessageLoop::current(), message_loop()); | |
218 gaia::GaiaAuthenticator::AuthResults results = gaia_->results(); | |
219 if (LOCALLY_AUTHENTICATED != status_ && | |
220 AuthenticateLocally(results.email, results.password)) { | |
221 // TODO(chron): Do we really want a bogus token? | |
222 const string auth_token("bogus"); | |
223 user_settings_->SetAuthTokenForService(results.email, | |
224 SYNC_SERVICE_NAME, | |
225 auth_token); | |
226 } | |
227 AuthWatcherEvent myevent = { AuthWatcherEvent::GAIA_AUTH_FAILED, &results }; | |
228 NotifyListeners(&myevent); | |
229 } | |
230 | |
231 void AuthWatcher::DoAuthenticate(const AuthRequest& request) { | |
232 DCHECK_EQ(MessageLoop::current(), message_loop()); | |
233 | |
234 AuthWatcherEvent event = { AuthWatcherEvent::AUTHENTICATION_ATTEMPT_START }; | |
235 NotifyListeners(&event); | |
236 | |
237 current_attempt_trigger_ = request.trigger; | |
238 | |
239 // We let the caller be lazy and try using the last captcha token seen by | |
240 // the gaia authenticator if they haven't provided a token but have sent | |
241 // a challenge response. Of course, if the captcha token is specified, | |
242 // we use that one instead. | |
243 std::string captcha_token(request.captcha_token); | |
244 if (!request.captcha_value.empty() && captcha_token.empty()) | |
245 captcha_token = gaia_->captcha_token(); | |
246 | |
247 if (!request.password.empty()) { | |
248 bool authenticated = false; | |
249 if (!captcha_token.empty()) { | |
250 authenticated = gaia_->Authenticate(request.email, request.password, | |
251 captcha_token, | |
252 request.captcha_value); | |
253 } else { | |
254 authenticated = gaia_->Authenticate(request.email, request.password); | |
255 } | |
256 if (authenticated) { | |
257 PersistCredentials(); | |
258 DoAuthenticateWithToken(gaia_->email(), gaia_->auth_token()); | |
259 } else { | |
260 ProcessGaiaAuthFailure(); | |
261 } | |
262 } else if (!request.auth_token.empty()) { | |
263 DoAuthenticateWithToken(request.email, request.auth_token); | |
264 } else { | |
265 LOG(ERROR) << "Attempt to authenticate with no credentials."; | |
266 } | |
267 } | |
268 | |
269 void AuthWatcher::NotifyAuthChanged(const string& email, | |
270 const string& auth_token, | |
271 bool renewed) { | |
272 DCHECK_EQ(MessageLoop::current(), message_loop()); | |
273 LOG(INFO) << "NotifyAuthSucceeded"; | |
274 AuthWatcherEvent event = { | |
275 renewed ? | |
276 AuthWatcherEvent::AUTH_RENEWED : | |
277 AuthWatcherEvent::AUTH_SUCCEEDED | |
278 }; | |
279 event.user_email = email; | |
280 event.auth_token = auth_token; | |
281 | |
282 NotifyListeners(&event); | |
283 } | |
284 | |
285 void AuthWatcher::HandleServerConnectionEvent( | |
286 const ServerConnectionEvent& event) { | |
287 message_loop_proxy()->PostTask(FROM_HERE, NewRunnableMethod(this, | |
288 &AuthWatcher::DoHandleServerConnectionEvent, event, | |
289 scm_->auth_token())); | |
290 } | |
291 | |
292 void AuthWatcher::DoHandleServerConnectionEvent( | |
293 const ServerConnectionEvent& event, | |
294 const std::string& auth_token_snapshot) { | |
295 DCHECK_EQ(MessageLoop::current(), message_loop()); | |
296 if (event.server_reachable && | |
297 // If the auth_token at the time of the event differs from the current | |
298 // one, we have authenticated since then and don't need to re-try. | |
299 (auth_token_snapshot == gaia_->auth_token()) && | |
300 (event.connection_code == HttpResponse::SYNC_AUTH_ERROR || | |
301 status_ == LOCALLY_AUTHENTICATED)) { | |
302 // We're either online or just got reconnected and want to try to | |
303 // authenticate. If we've got a saved token this should just work. If not | |
304 // the auth failure should trigger UI indications that we're not logged in. | |
305 | |
306 // METRIC: If we get a SYNC_AUTH_ERROR, our token expired. | |
307 gaia::GaiaAuthenticator::AuthResults authresults = gaia_->results(); | |
308 AuthRequest request = { authresults.email, authresults.password, | |
309 authresults.auth_token, std::string(), | |
310 std::string(), | |
311 AuthWatcherEvent::EXPIRED_CREDENTIALS }; | |
312 DoAuthenticate(request); | |
313 } | |
314 } | |
315 | |
316 AuthWatcher::~AuthWatcher() { | |
317 auth_backend_thread_.Stop(); | |
318 // The gaia authenticator takes a const MessageLoop* because it only uses it | |
319 // to ensure all methods are invoked on the given loop. Once our thread has | |
320 // stopped, the current message loop will be NULL, and no methods should be | |
321 // invoked on |gaia_| after this point. We could set it to NULL, but | |
322 // abstaining allows for even more sanity checking that nothing is invoked on | |
323 // it from now on. | |
324 } | |
325 | |
326 void AuthWatcher::Authenticate(const string& email, const string& password, | |
327 const string& captcha_token, const string& captcha_value) { | |
328 LOG(INFO) << "AuthWatcher::Authenticate called"; | |
329 | |
330 string empty; | |
331 AuthRequest request = { FormatAsEmailAddress(email), password, empty, | |
332 captcha_token, captcha_value, | |
333 AuthWatcherEvent::USER_INITIATED }; | |
334 message_loop_proxy()->PostTask(FROM_HERE, NewRunnableMethod(this, | |
335 &AuthWatcher::DoAuthenticate, request)); | |
336 } | |
337 | |
338 void AuthWatcher::ClearAuthenticationData() { | |
339 scm_->set_auth_token(std::string()); | |
340 user_settings_->ClearAllServiceTokens(); | |
341 } | |
342 | |
343 string AuthWatcher::email() const { | |
344 return gaia_->email(); | |
345 } | |
346 | |
347 void AuthWatcher::NotifyListeners(AuthWatcherEvent* event) { | |
348 event->trigger = current_attempt_trigger_; | |
349 channel_->NotifyListeners(*event); | |
350 } | |
351 | |
352 } // namespace browser_sync | |
OLD | NEW |