summaryrefslogtreecommitdiffstats
path: root/chrome/browser/sync/engine/auth_watcher.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/sync/engine/auth_watcher.cc')
-rw-r--r--chrome/browser/sync/engine/auth_watcher.cc419
1 files changed, 419 insertions, 0 deletions
diff --git a/chrome/browser/sync/engine/auth_watcher.cc b/chrome/browser/sync/engine/auth_watcher.cc
new file mode 100644
index 0000000..0c999dd
--- /dev/null
+++ b/chrome/browser/sync/engine/auth_watcher.cc
@@ -0,0 +1,419 @@
+// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/sync/engine/auth_watcher.h"
+
+#include "base/file_util.h"
+#include "base/string_util.h"
+#include "chrome/browser/sync/engine/all_status.h"
+#include "chrome/browser/sync/engine/authenticator.h"
+#include "chrome/browser/sync/engine/net/gaia_authenticator.h"
+#include "chrome/browser/sync/engine/net/server_connection_manager.h"
+#include "chrome/browser/sync/notifier/listener/talk_mediator.h"
+#include "chrome/browser/sync/protocol/service_constants.h"
+#include "chrome/browser/sync/syncable/directory_manager.h"
+#include "chrome/browser/sync/syncable/syncable.h"
+#include "chrome/browser/sync/util/character_set_converters.h"
+#include "chrome/browser/sync/util/event_sys-inl.h"
+#include "chrome/browser/sync/util/pthread_helpers.h"
+#include "chrome/browser/sync/util/user_settings.h"
+
+// How authentication happens:
+//
+// Kick Off:
+// The sync API looks to see if the user's name and
+// password are stored. If so, it calls authwatcher.Authenticate() with
+// them. Otherwise it fires an error event.
+//
+// On failed Gaia Auth:
+// The AuthWatcher attempts to use saved hashes to authenticate
+// locally, and on success opens the share.
+// On failure, fires an error event.
+//
+// On successful Gaia Auth:
+// AuthWatcher launches a thread to open the share and to get the
+// authentication token from the sync server.
+
+using std::pair;
+using std::string;
+using std::vector;
+
+namespace browser_sync {
+
+AuthWatcher::AuthWatcher(DirectoryManager* dirman,
+ ServerConnectionManager* scm,
+ AllStatus* allstatus,
+ const string& user_agent,
+ const string& service_id,
+ const string& gaia_url,
+ UserSettings* user_settings,
+ GaiaAuthenticator* gaia_auth,
+ TalkMediator* talk_mediator)
+ : dirman_(dirman),
+ scm_(scm),
+ allstatus_(allstatus),
+ status_(NOT_AUTHENTICATED),
+ thread_handle_valid_(false),
+ authenticating_now_(false),
+ current_attempt_trigger_(AuthWatcherEvent::USER_INITIATED),
+ user_settings_(user_settings),
+ gaia_(gaia_auth),
+ talk_mediator_(talk_mediator) {
+ connmgr_hookup_.reset(
+ NewEventListenerHookup(scm->channel(), this,
+ &AuthWatcher::HandleServerConnectionEvent));
+ AuthWatcherEvent done = { AuthWatcherEvent::AUTHWATCHER_DESTROYED };
+ channel_.reset(new Channel(done));
+}
+
+void* AuthWatcher::AuthenticationThreadStartRoutine(void* arg) {
+ ThreadParams* args = reinterpret_cast<ThreadParams*>(arg);
+ return args->self->AuthenticationThreadMain(args);
+}
+
+bool AuthWatcher::ProcessGaiaAuthSuccess() {
+ GaiaAuthenticator::AuthResults results = gaia_->results();
+
+ // We just successfully signed in again, let's clear out any residual cached
+ // login data from earlier sessions.
+ ClearAuthenticationData();
+
+ user_settings_->StoreEmailForSignin(results.email, results.primary_email);
+ user_settings_->RememberSigninType(results.email, results.signin);
+ user_settings_->RememberSigninType(results.primary_email, results.signin);
+ results.email = results.primary_email;
+ gaia_->SetUsernamePassword(results.primary_email, results.password);
+ if (!user_settings_->VerifyAgainstStoredHash(results.email, results.password))
+ user_settings_->StoreHashedPassword(results.email, results.password);
+
+ if (PERSIST_TO_DISK == results.credentials_saved) {
+ user_settings_->SetAuthTokenForService(results.email,
+ SYNC_SERVICE_NAME,
+ gaia_->auth_token());
+ }
+
+ return AuthenticateWithToken(results.email, gaia_->auth_token());
+}
+
+bool AuthWatcher::GetAuthTokenForService(const string& service_name,
+ string* service_token) {
+ string user_name;
+
+ // We special case this one by trying to return it from memory first. We
+ // do this because the user may not have checked "Remember me" and so we
+ // may not have persisted the sync service token beyond the initial
+ // login.
+ if (SYNC_SERVICE_NAME == service_name && !sync_service_token_.empty()) {
+ *service_token = sync_service_token_;
+ return true;
+ }
+
+ if (user_settings_->GetLastUserAndServiceToken(service_name, &user_name,
+ service_token)) {
+ // The casing gets preserved in some places and not in others it seems,
+ // at least I have observed different casings persisted to different DB
+ // tables.
+ if (!base::strcasecmp(user_name.c_str(),
+ user_settings_->email().c_str())) {
+ return true;
+ } else {
+ LOG(ERROR) << "ERROR: We seem to have saved credentials for someone "
+ << " other than the current user.";
+ return false;
+ }
+ }
+
+ return false;
+}
+
+const char kAuthWatcher[] = "AuthWatcher";
+
+bool AuthWatcher::AuthenticateWithToken(const string& gaia_email,
+ const string& auth_token) {
+ // Store a copy of the sync service token in memory.
+ sync_service_token_ = auth_token;
+ scm_->set_auth_token(sync_service_token_);
+
+ Authenticator auth(scm_, user_settings_);
+ Authenticator::AuthenticationResult result =
+ auth.AuthenticateToken(auth_token);
+ string email = gaia_email;
+ if (auth.display_email() && *auth.display_email()) {
+ email = auth.display_email();
+ LOG(INFO) << "Auth returned email " << email << " for gaia email " <<
+ gaia_email;
+ }
+ AuthWatcherEvent event = {AuthWatcherEvent::ILLEGAL_VALUE , 0};
+ gaia_->SetUsername(email);
+ gaia_->SetAuthToken(auth_token, SAVE_IN_MEMORY_ONLY);
+ const bool was_authenticated = NOT_AUTHENTICATED != status_;
+ switch (result) {
+ case Authenticator::SUCCESS:
+ {
+ status_ = GAIA_AUTHENTICATED;
+ PathString share_name;
+ CHECK(AppendUTF8ToPathString(email.data(), email.size(), &share_name));
+ user_settings_->SwitchUser(email);
+
+ // Set the authentication token for notifications
+ talk_mediator_->SetAuthToken(email, auth_token);
+
+ if (!was_authenticated)
+ LoadDirectoryListAndOpen(share_name);
+ NotifyAuthSucceeded(email);
+ return true;
+ }
+ case Authenticator::BAD_AUTH_TOKEN:
+ event.what_happened = AuthWatcherEvent::SERVICE_AUTH_FAILED;
+ break;
+ case Authenticator::CORRUPT_SERVER_RESPONSE:
+ case Authenticator::SERVICE_DOWN:
+ event.what_happened = AuthWatcherEvent::SERVICE_CONNECTION_FAILED;
+ break;
+ case Authenticator::USER_NOT_ACTIVATED:
+ event.what_happened = AuthWatcherEvent::SERVICE_USER_NOT_SIGNED_UP;
+ break;
+ default:
+ LOG(FATAL) << "Illegal return from AuthenticateToken";
+ return true; // keep the compiler happy
+ }
+ // Always fall back to local authentication.
+ if (was_authenticated || AuthenticateLocally(email)) {
+ if (AuthWatcherEvent::SERVICE_CONNECTION_FAILED == event.what_happened)
+ return true;
+ }
+ CHECK(event.what_happened != AuthWatcherEvent::ILLEGAL_VALUE);
+ NotifyListeners(&event);
+ return true;
+}
+
+bool AuthWatcher::AuthenticateLocally(string email) {
+ user_settings_->GetEmailForSignin(&email);
+ if (file_util::PathExists(dirman_->GetSyncDataDatabasePath())) {
+ gaia_->SetUsername(email);
+ status_ = LOCALLY_AUTHENTICATED;
+ user_settings_->SwitchUser(email);
+ PathString share_name;
+ CHECK(AppendUTF8ToPathString(email.data(), email.size(), &share_name));
+ LoadDirectoryListAndOpen(share_name);
+ NotifyAuthSucceeded(email);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+bool AuthWatcher::AuthenticateLocally(string email, const string& password) {
+ user_settings_->GetEmailForSignin(&email);
+ return user_settings_->VerifyAgainstStoredHash(email, password)
+ && AuthenticateLocally(email);
+}
+
+void AuthWatcher::ProcessGaiaAuthFailure() {
+ GaiaAuthenticator::AuthResults results = gaia_->results();
+ if (LOCALLY_AUTHENTICATED == status_) {
+ return; // nothing todo
+ } else if (AuthenticateLocally(results.email, results.password)) {
+ // We save the "Remember me" checkbox by putting a non-null auth
+ // token into the last_user table. So if we're offline and the
+ // user checks the box, insert a bogus auth token.
+ if (PERSIST_TO_DISK == results.credentials_saved) {
+ const string auth_token("bogus");
+ user_settings_->SetAuthTokenForService(results.email,
+ SYNC_SERVICE_NAME,
+ auth_token);
+ }
+ const bool unavailable = ConnectionUnavailable == results.auth_error ||
+ Unknown == results.auth_error ||
+ ServiceUnavailable == results.auth_error;
+ if (unavailable)
+ return;
+ }
+ AuthWatcherEvent myevent = { AuthWatcherEvent::GAIA_AUTH_FAILED, &results };
+ NotifyListeners(&myevent);
+}
+
+void* AuthWatcher::AuthenticationThreadMain(ThreadParams* args) {
+ NameCurrentThreadForDebugging("SyncEngine_AuthWatcherThread");
+ {
+ // This short lock ensures our launching function (StartNewAuthAttempt) is
+ // done.
+ MutexLock lock(&mutex_);
+ current_attempt_trigger_ = args->trigger;
+ }
+ SaveCredentials save = args->persist_creds_to_disk ?
+ PERSIST_TO_DISK : SAVE_IN_MEMORY_ONLY;
+ int attempt = 0;
+ SignIn const signin = user_settings_->
+ RecallSigninType(args->email, GMAIL_SIGNIN);
+
+ if (!args->password.empty()) while (true) {
+ bool authenticated;
+ if (!args->captcha_token.empty() && !args->captcha_value.empty())
+ authenticated = gaia_->Authenticate(args->email, args->password,
+ save, true, args->captcha_token,
+ args->captcha_value, signin);
+ else
+ authenticated = gaia_->Authenticate(args->email, args->password,
+ save, true, signin);
+ if (authenticated) {
+ if (!ProcessGaiaAuthSuccess()) {
+ if (3 != ++attempt)
+ continue;
+ AuthWatcherEvent event =
+ { AuthWatcherEvent::SERVICE_CONNECTION_FAILED, 0 };
+ NotifyListeners(&event);
+ }
+ } else {
+ ProcessGaiaAuthFailure();
+ }
+ break;
+ } else if (!args->auth_token.empty()) {
+ AuthenticateWithToken(args->email, args->auth_token);
+ } else {
+ LOG(ERROR) << "Attempt to authenticate with no credentials.";
+ }
+ {
+ MutexLock lock(&mutex_);
+ authenticating_now_ = false;
+ }
+ delete args;
+ return 0;
+}
+
+void AuthWatcher::Reset() {
+ status_ = NOT_AUTHENTICATED;
+}
+
+void AuthWatcher::NotifyAuthSucceeded(const string& email) {
+ LOG(INFO) << "NotifyAuthSucceeded";
+ AuthWatcherEvent event = { AuthWatcherEvent::AUTH_SUCCEEDED };
+ event.user_email = email;
+
+ NotifyListeners(&event);
+}
+
+bool AuthWatcher::StartNewAuthAttempt(const string& email,
+ const string& password, const string& auth_token,
+ const string& captcha_token, const string& captcha_value,
+ bool persist_creds_to_disk,
+ AuthWatcherEvent::AuthenticationTrigger trigger) {
+ AuthWatcherEvent event = { AuthWatcherEvent::AUTHENTICATION_ATTEMPT_START };
+ NotifyListeners(&event);
+ MutexLock lock(&mutex_);
+ if (authenticating_now_)
+ return false;
+ if (thread_handle_valid_) {
+ int join_return = pthread_join(thread_, 0);
+ if (0 != join_return)
+ LOG(ERROR) << "pthread_join failed returning " << join_return;
+ }
+ string mail = email;
+ if (email.find('@') == string::npos) {
+ mail.push_back('@');
+ // TODO(chron): Should this be done only at the UI level?
+ mail.append(DEFAULT_SIGNIN_DOMAIN);
+ }
+ ThreadParams* args = new ThreadParams;
+ args->self = this;
+ args->email = mail;
+ args->password = password;
+ args->auth_token = auth_token;
+ args->captcha_token = captcha_token;
+ args->captcha_value = captcha_value;
+ args->persist_creds_to_disk = persist_creds_to_disk;
+ args->trigger = trigger;
+ if (0 != pthread_create(&thread_, NULL, AuthenticationThreadStartRoutine,
+ args)) {
+ LOG(ERROR) << "Failed to create auth thread.";
+ return false;
+ }
+ authenticating_now_ = true;
+ thread_handle_valid_ = true;
+ return true;
+}
+
+void AuthWatcher::WaitForAuthThreadFinish() {
+ {
+ MutexLock lock(&mutex_);
+ if (!thread_handle_valid_)
+ return;
+ }
+ pthread_join(thread_, 0);
+}
+
+void AuthWatcher::HandleServerConnectionEvent(
+ const ServerConnectionEvent& event) {
+ if (event.server_reachable &&
+ !authenticating_now_ &&
+ (event.connection_code == HttpResponse::SYNC_AUTH_ERROR ||
+ status_ == LOCALLY_AUTHENTICATED)) {
+ // We're either online or just got reconnected and want to try to
+ // authenticate. If we've got a saved token this should just work. If not
+ // the auth failure should trigger UI indications that we're not logged in.
+
+ // METRIC: If we get a SYNC_AUTH_ERROR, our token expired.
+ GaiaAuthenticator::AuthResults authresults = gaia_->results();
+ if (!StartNewAuthAttempt(authresults.email, authresults.password,
+ authresults.auth_token, "", "",
+ PERSIST_TO_DISK == authresults.credentials_saved,
+ AuthWatcherEvent::EXPIRED_CREDENTIALS))
+ LOG(INFO) << "Couldn't start a new auth attempt.";
+ }
+}
+
+bool AuthWatcher::LoadDirectoryListAndOpen(const PathString& login) {
+ LOG(INFO) << "LoadDirectoryListAndOpen(" << login << ")";
+ bool initial_sync_ended = false;
+
+ dirman_->Open(login);
+ syncable::ScopedDirLookup dir(dirman_, login);
+ if (dir.good() && dir->initial_sync_ended())
+ initial_sync_ended = true;
+
+ LOG(INFO) << "LoadDirectoryListAndOpen returning " << initial_sync_ended;
+ return initial_sync_ended;
+}
+
+AuthWatcher::~AuthWatcher() {
+ WaitForAuthThreadFinish();
+}
+
+void AuthWatcher::Authenticate(const string& email, const string& password,
+ const string& captcha_token, const string& captcha_value,
+ bool persist_creds_to_disk) {
+ LOG(INFO) << "AuthWatcher::Authenticate called";
+ WaitForAuthThreadFinish();
+
+ // We CHECK here because WaitForAuthThreadFinish should ensure there's no
+ // ongoing auth attempt.
+ string empty;
+ CHECK(StartNewAuthAttempt(email, password, empty, captcha_token,
+ captcha_value, persist_creds_to_disk,
+ AuthWatcherEvent::USER_INITIATED));
+}
+
+void AuthWatcher::Logout() {
+ scm_->ResetAuthStatus();
+ Reset();
+ WaitForAuthThreadFinish();
+ ClearAuthenticationData();
+}
+
+void AuthWatcher::ClearAuthenticationData() {
+ sync_service_token_.clear();
+ scm_->set_auth_token(sync_service_token());
+ user_settings_->ClearAllServiceTokens();
+}
+
+string AuthWatcher::email() const {
+ return gaia_->email();
+}
+
+void AuthWatcher::NotifyListeners(AuthWatcherEvent* event) {
+ event->trigger = current_attempt_trigger_;
+ channel_->NotifyListeners(*event);
+}
+
+} // namespace browser_sync