Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(22)

Side by Side Diff: chrome/browser/sync/engine/auth_watcher.cc

Issue 194065: Initial commit of sync engine code to browser/sync.... (Closed) Base URL: svn://chrome-svn/chrome/trunk/src/
Patch Set: Fixes to gtest include path, reverted syncapi. Created 11 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
Property Changes:
Added: svn:eol-style
+ LF
OLDNEW
(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/gaia_authenticator.h"
12 #include "chrome/browser/sync/engine/net/server_connection_manager.h"
13 #include "chrome/browser/sync/notifier/listener/talk_mediator.h"
14 #include "chrome/browser/sync/protocol/service_constants.h"
15 #include "chrome/browser/sync/syncable/directory_manager.h"
16 #include "chrome/browser/sync/syncable/syncable.h"
17 #include "chrome/browser/sync/util/character_set_converters.h"
18 #include "chrome/browser/sync/util/event_sys-inl.h"
19 #include "chrome/browser/sync/util/pthread_helpers.h"
20 #include "chrome/browser/sync/util/user_settings.h"
21
22 // How authentication happens:
23 //
24 // Kick Off:
25 // The sync API looks to see if the user's name and
26 // password are stored. If so, it calls authwatcher.Authenticate() with
27 // them. Otherwise it fires an error event.
28 //
29 // On failed Gaia Auth:
30 // The AuthWatcher attempts to use saved hashes to authenticate
31 // locally, and on success opens the share.
32 // On failure, fires an error event.
33 //
34 // On successful Gaia Auth:
35 // AuthWatcher launches a thread to open the share and to get the
36 // authentication token from the sync server.
37
38 using std::pair;
39 using std::string;
40 using std::vector;
41
42 namespace browser_sync {
43
44 AuthWatcher::AuthWatcher(DirectoryManager* dirman,
45 ServerConnectionManager* scm,
46 AllStatus* allstatus,
47 const string& user_agent,
48 const string& service_id,
49 const string& gaia_url,
50 UserSettings* user_settings,
51 GaiaAuthenticator* gaia_auth,
52 TalkMediator* talk_mediator)
53 : dirman_(dirman),
54 scm_(scm),
55 allstatus_(allstatus),
56 status_(NOT_AUTHENTICATED),
57 thread_handle_valid_(false),
58 authenticating_now_(false),
59 current_attempt_trigger_(AuthWatcherEvent::USER_INITIATED),
60 user_settings_(user_settings),
61 gaia_(gaia_auth),
62 talk_mediator_(talk_mediator) {
63 connmgr_hookup_.reset(
64 NewEventListenerHookup(scm->channel(), this,
65 &AuthWatcher::HandleServerConnectionEvent));
66 AuthWatcherEvent done = { AuthWatcherEvent::AUTHWATCHER_DESTROYED };
67 channel_.reset(new Channel(done));
68 }
69
70 void* AuthWatcher::AuthenticationThreadStartRoutine(void* arg) {
71 ThreadParams* args = reinterpret_cast<ThreadParams*>(arg);
72 return args->self->AuthenticationThreadMain(args);
73 }
74
75 bool AuthWatcher::ProcessGaiaAuthSuccess() {
76 GaiaAuthenticator::AuthResults results = gaia_->results();
77
78 // We just successfully signed in again, let's clear out any residual cached
79 // login data from earlier sessions.
80 ClearAuthenticationData();
81
82 user_settings_->StoreEmailForSignin(results.email, results.primary_email);
83 user_settings_->RememberSigninType(results.email, results.signin);
84 user_settings_->RememberSigninType(results.primary_email, results.signin);
85 results.email = results.primary_email;
86 gaia_->SetUsernamePassword(results.primary_email, results.password);
87 if (!user_settings_->VerifyAgainstStoredHash(results.email, results.password))
88 user_settings_->StoreHashedPassword(results.email, results.password);
89
90 if (PERSIST_TO_DISK == results.credentials_saved) {
91 user_settings_->SetAuthTokenForService(results.email,
92 SYNC_SERVICE_NAME,
93 gaia_->auth_token());
94 }
95
96 return AuthenticateWithToken(results.email, gaia_->auth_token());
97 }
98
99 bool AuthWatcher::GetAuthTokenForService(const string& service_name,
100 string* service_token) {
101 string user_name;
102
103 // We special case this one by trying to return it from memory first. We
104 // do this because the user may not have checked "Remember me" and so we
105 // may not have persisted the sync service token beyond the initial
106 // login.
107 if (SYNC_SERVICE_NAME == service_name && !sync_service_token_.empty()) {
108 *service_token = sync_service_token_;
109 return true;
110 }
111
112 if (user_settings_->GetLastUserAndServiceToken(service_name, &user_name,
113 service_token)) {
114 // The casing gets preserved in some places and not in others it seems,
115 // at least I have observed different casings persisted to different DB
116 // tables.
117 if (!base::strcasecmp(user_name.c_str(),
118 user_settings_->email().c_str())) {
119 return true;
120 } else {
121 LOG(ERROR) << "ERROR: We seem to have saved credentials for someone "
122 << " other than the current user.";
123 return false;
124 }
125 }
126
127 return false;
128 }
129
130 const char kAuthWatcher[] = "AuthWatcher";
131
132 bool AuthWatcher::AuthenticateWithToken(const string& gaia_email,
133 const string& auth_token) {
134 // Store a copy of the sync service token in memory.
135 sync_service_token_ = auth_token;
136 scm_->set_auth_token(sync_service_token_);
137
138 Authenticator auth(scm_, user_settings_);
139 Authenticator::AuthenticationResult result =
140 auth.AuthenticateToken(auth_token);
141 string email = gaia_email;
142 if (auth.display_email() && *auth.display_email()) {
143 email = auth.display_email();
144 LOG(INFO) << "Auth returned email " << email << " for gaia email " <<
145 gaia_email;
146 }
147 AuthWatcherEvent event = {AuthWatcherEvent::ILLEGAL_VALUE , 0};
148 gaia_->SetUsername(email);
149 gaia_->SetAuthToken(auth_token, SAVE_IN_MEMORY_ONLY);
150 const bool was_authenticated = NOT_AUTHENTICATED != status_;
151 switch (result) {
152 case Authenticator::SUCCESS:
153 {
154 status_ = GAIA_AUTHENTICATED;
155 PathString share_name;
156 CHECK(AppendUTF8ToPathString(email.data(), email.size(), &share_name));
157 user_settings_->SwitchUser(email);
158
159 // Set the authentication token for notifications
160 talk_mediator_->SetAuthToken(email, auth_token);
161
162 if (!was_authenticated)
163 LoadDirectoryListAndOpen(share_name);
164 NotifyAuthSucceeded(email);
165 return true;
166 }
167 case Authenticator::BAD_AUTH_TOKEN:
168 event.what_happened = AuthWatcherEvent::SERVICE_AUTH_FAILED;
169 break;
170 case Authenticator::CORRUPT_SERVER_RESPONSE:
171 case Authenticator::SERVICE_DOWN:
172 event.what_happened = AuthWatcherEvent::SERVICE_CONNECTION_FAILED;
173 break;
174 case Authenticator::USER_NOT_ACTIVATED:
175 event.what_happened = AuthWatcherEvent::SERVICE_USER_NOT_SIGNED_UP;
176 break;
177 default:
178 LOG(FATAL) << "Illegal return from AuthenticateToken";
179 return true; // keep the compiler happy
180 }
181 // Always fall back to local authentication.
182 if (was_authenticated || AuthenticateLocally(email)) {
183 if (AuthWatcherEvent::SERVICE_CONNECTION_FAILED == event.what_happened)
184 return true;
185 }
186 CHECK(event.what_happened != AuthWatcherEvent::ILLEGAL_VALUE);
187 NotifyListeners(&event);
188 return true;
189 }
190
191 bool AuthWatcher::AuthenticateLocally(string email) {
192 user_settings_->GetEmailForSignin(&email);
193 if (file_util::PathExists(dirman_->GetSyncDataDatabasePath())) {
194 gaia_->SetUsername(email);
195 status_ = LOCALLY_AUTHENTICATED;
196 user_settings_->SwitchUser(email);
197 PathString share_name;
198 CHECK(AppendUTF8ToPathString(email.data(), email.size(), &share_name));
199 LoadDirectoryListAndOpen(share_name);
200 NotifyAuthSucceeded(email);
201 return true;
202 } else {
203 return false;
204 }
205 }
206
207 bool AuthWatcher::AuthenticateLocally(string email, const string& password) {
208 user_settings_->GetEmailForSignin(&email);
209 return user_settings_->VerifyAgainstStoredHash(email, password)
210 && AuthenticateLocally(email);
211 }
212
213 void AuthWatcher::ProcessGaiaAuthFailure() {
214 GaiaAuthenticator::AuthResults results = gaia_->results();
215 if (LOCALLY_AUTHENTICATED == status_) {
216 return; // nothing todo
217 } else if (AuthenticateLocally(results.email, results.password)) {
218 // We save the "Remember me" checkbox by putting a non-null auth
219 // token into the last_user table. So if we're offline and the
220 // user checks the box, insert a bogus auth token.
221 if (PERSIST_TO_DISK == results.credentials_saved) {
222 const string auth_token("bogus");
223 user_settings_->SetAuthTokenForService(results.email,
224 SYNC_SERVICE_NAME,
225 auth_token);
226 }
227 const bool unavailable = ConnectionUnavailable == results.auth_error ||
228 Unknown == results.auth_error ||
229 ServiceUnavailable == results.auth_error;
230 if (unavailable)
231 return;
232 }
233 AuthWatcherEvent myevent = { AuthWatcherEvent::GAIA_AUTH_FAILED, &results };
234 NotifyListeners(&myevent);
235 }
236
237 void* AuthWatcher::AuthenticationThreadMain(ThreadParams* args) {
238 NameCurrentThreadForDebugging("SyncEngine_AuthWatcherThread");
239 {
240 // This short lock ensures our launching function (StartNewAuthAttempt) is
241 // done.
242 MutexLock lock(&mutex_);
243 current_attempt_trigger_ = args->trigger;
244 }
245 SaveCredentials save = args->persist_creds_to_disk ?
246 PERSIST_TO_DISK : SAVE_IN_MEMORY_ONLY;
247 int attempt = 0;
248 SignIn const signin = user_settings_->
249 RecallSigninType(args->email, GMAIL_SIGNIN);
250
251 if (!args->password.empty()) while (true) {
252 bool authenticated;
253 if (!args->captcha_token.empty() && !args->captcha_value.empty())
254 authenticated = gaia_->Authenticate(args->email, args->password,
255 save, true, args->captcha_token,
256 args->captcha_value, signin);
257 else
258 authenticated = gaia_->Authenticate(args->email, args->password,
259 save, true, signin);
260 if (authenticated) {
261 if (!ProcessGaiaAuthSuccess()) {
262 if (3 != ++attempt)
263 continue;
264 AuthWatcherEvent event =
265 { AuthWatcherEvent::SERVICE_CONNECTION_FAILED, 0 };
266 NotifyListeners(&event);
267 }
268 } else {
269 ProcessGaiaAuthFailure();
270 }
271 break;
272 } else if (!args->auth_token.empty()) {
273 AuthenticateWithToken(args->email, args->auth_token);
274 } else {
275 LOG(ERROR) << "Attempt to authenticate with no credentials.";
276 }
277 {
278 MutexLock lock(&mutex_);
279 authenticating_now_ = false;
280 }
281 delete args;
282 return 0;
283 }
284
285 void AuthWatcher::Reset() {
286 status_ = NOT_AUTHENTICATED;
287 }
288
289 void AuthWatcher::NotifyAuthSucceeded(const string& email) {
290 LOG(INFO) << "NotifyAuthSucceeded";
291 AuthWatcherEvent event = { AuthWatcherEvent::AUTH_SUCCEEDED };
292 event.user_email = email;
293
294 NotifyListeners(&event);
295 }
296
297 bool AuthWatcher::StartNewAuthAttempt(const string& email,
298 const string& password, const string& auth_token,
299 const string& captcha_token, const string& captcha_value,
300 bool persist_creds_to_disk,
301 AuthWatcherEvent::AuthenticationTrigger trigger) {
302 AuthWatcherEvent event = { AuthWatcherEvent::AUTHENTICATION_ATTEMPT_START };
303 NotifyListeners(&event);
304 MutexLock lock(&mutex_);
305 if (authenticating_now_)
306 return false;
307 if (thread_handle_valid_) {
308 int join_return = pthread_join(thread_, 0);
309 if (0 != join_return)
310 LOG(ERROR) << "pthread_join failed returning " << join_return;
311 }
312 string mail = email;
313 if (email.find('@') == string::npos) {
314 mail.push_back('@');
315 // TODO(chron): Should this be done only at the UI level?
316 mail.append(DEFAULT_SIGNIN_DOMAIN);
317 }
318 ThreadParams* args = new ThreadParams;
319 args->self = this;
320 args->email = mail;
321 args->password = password;
322 args->auth_token = auth_token;
323 args->captcha_token = captcha_token;
324 args->captcha_value = captcha_value;
325 args->persist_creds_to_disk = persist_creds_to_disk;
326 args->trigger = trigger;
327 if (0 != pthread_create(&thread_, NULL, AuthenticationThreadStartRoutine,
328 args)) {
329 LOG(ERROR) << "Failed to create auth thread.";
330 return false;
331 }
332 authenticating_now_ = true;
333 thread_handle_valid_ = true;
334 return true;
335 }
336
337 void AuthWatcher::WaitForAuthThreadFinish() {
338 {
339 MutexLock lock(&mutex_);
340 if (!thread_handle_valid_)
341 return;
342 }
343 pthread_join(thread_, 0);
344 }
345
346 void AuthWatcher::HandleServerConnectionEvent(
347 const ServerConnectionEvent& event) {
348 if (event.server_reachable &&
349 !authenticating_now_ &&
350 (event.connection_code == HttpResponse::SYNC_AUTH_ERROR ||
351 status_ == LOCALLY_AUTHENTICATED)) {
352 // We're either online or just got reconnected and want to try to
353 // authenticate. If we've got a saved token this should just work. If not
354 // the auth failure should trigger UI indications that we're not logged in.
355
356 // METRIC: If we get a SYNC_AUTH_ERROR, our token expired.
357 GaiaAuthenticator::AuthResults authresults = gaia_->results();
358 if (!StartNewAuthAttempt(authresults.email, authresults.password,
359 authresults.auth_token, "", "",
360 PERSIST_TO_DISK == authresults.credentials_saved,
361 AuthWatcherEvent::EXPIRED_CREDENTIALS))
362 LOG(INFO) << "Couldn't start a new auth attempt.";
363 }
364 }
365
366 bool AuthWatcher::LoadDirectoryListAndOpen(const PathString& login) {
367 LOG(INFO) << "LoadDirectoryListAndOpen(" << login << ")";
368 bool initial_sync_ended = false;
369
370 dirman_->Open(login);
371 syncable::ScopedDirLookup dir(dirman_, login);
372 if (dir.good() && dir->initial_sync_ended())
373 initial_sync_ended = true;
374
375 LOG(INFO) << "LoadDirectoryListAndOpen returning " << initial_sync_ended;
376 return initial_sync_ended;
377 }
378
379 AuthWatcher::~AuthWatcher() {
380 WaitForAuthThreadFinish();
381 }
382
383 void AuthWatcher::Authenticate(const string& email, const string& password,
384 const string& captcha_token, const string& captcha_value,
385 bool persist_creds_to_disk) {
386 LOG(INFO) << "AuthWatcher::Authenticate called";
387 WaitForAuthThreadFinish();
388
389 // We CHECK here because WaitForAuthThreadFinish should ensure there's no
390 // ongoing auth attempt.
391 string empty;
392 CHECK(StartNewAuthAttempt(email, password, empty, captcha_token,
393 captcha_value, persist_creds_to_disk,
394 AuthWatcherEvent::USER_INITIATED));
395 }
396
397 void AuthWatcher::Logout() {
398 scm_->ResetAuthStatus();
399 Reset();
400 WaitForAuthThreadFinish();
401 ClearAuthenticationData();
402 }
403
404 void AuthWatcher::ClearAuthenticationData() {
405 sync_service_token_.clear();
406 scm_->set_auth_token(sync_service_token());
407 user_settings_->ClearAllServiceTokens();
408 }
409
410 string AuthWatcher::email() const {
411 return gaia_->email();
412 }
413
414 void AuthWatcher::NotifyListeners(AuthWatcherEvent* event) {
415 event->trigger = current_attempt_trigger_;
416 channel_->NotifyListeners(*event);
417 }
418
419 } // namespace browser_sync
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698