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.cc351
1 files changed, 351 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..46b3852
--- /dev/null
+++ b/chrome/browser/sync/engine/auth_watcher.cc
@@ -0,0 +1,351 @@
+// 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/server_connection_manager.h"
+#include "chrome/browser/sync/syncable/directory_manager.h"
+#include "chrome/browser/sync/syncable/syncable.h"
+#include "chrome/browser/sync/util/user_settings.h"
+#include "chrome/common/deprecated/event_sys-inl.h"
+#include "chrome/common/net/gaia/gaia_authenticator.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,
+ const string& user_agent,
+ const string& service_id,
+ const string& gaia_url,
+ UserSettings* user_settings,
+ gaia::GaiaAuthenticator* gaia_auth)
+ : gaia_(gaia_auth),
+ dirman_(dirman),
+ scm_(scm),
+ status_(NOT_AUTHENTICATED),
+ user_settings_(user_settings),
+ auth_backend_thread_("SyncEngine_AuthWatcherThread"),
+ current_attempt_trigger_(AuthWatcherEvent::USER_INITIATED) {
+
+ if (!auth_backend_thread_.Start())
+ NOTREACHED() << "Couldn't start SyncEngine_AuthWatcherThread";
+
+ gaia_->set_message_loop(message_loop());
+
+ connmgr_hookup_.reset(
+ NewEventListenerHookup(scm->channel(), this,
+ &AuthWatcher::HandleServerConnectionEvent));
+ AuthWatcherEvent done = { AuthWatcherEvent::AUTHWATCHER_DESTROYED };
+ channel_.reset(new Channel(done));
+}
+
+void AuthWatcher::PersistCredentials() {
+ DCHECK_EQ(MessageLoop::current(), message_loop());
+ gaia::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);
+ 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);
+
+ user_settings_->SetAuthTokenForService(results.email,
+ SYNC_SERVICE_NAME,
+ gaia_->auth_token());
+}
+
+// TODO(chron): Full integration test suite needed. http://crbug.com/35429
+void AuthWatcher::RenewAuthToken(const std::string& updated_token) {
+ message_loop()->PostTask(FROM_HERE, NewRunnableMethod(this,
+ &AuthWatcher::DoRenewAuthToken, updated_token));
+}
+
+void AuthWatcher::DoRenewAuthToken(const std::string& updated_token) {
+ DCHECK_EQ(MessageLoop::current(), message_loop());
+ // TODO(chron): We should probably only store auth token in one place.
+ if (scm_->auth_token() == updated_token) {
+ return; // This thread is the only one writing to the SCM's auth token.
+ }
+ LOG(INFO) << "Updating auth token:" << updated_token;
+ scm_->set_auth_token(updated_token);
+ gaia_->RenewAuthToken(updated_token); // Must be on AuthWatcher thread
+ user_settings_->SetAuthTokenForService(user_settings_->email(),
+ SYNC_SERVICE_NAME,
+ updated_token);
+
+ NotifyAuthChanged(user_settings_->email(), updated_token, true);
+}
+
+void AuthWatcher::AuthenticateWithLsid(const std::string& lsid) {
+ message_loop()->PostTask(FROM_HERE, NewRunnableMethod(this,
+ &AuthWatcher::DoAuthenticateWithLsid, lsid));
+}
+
+void AuthWatcher::DoAuthenticateWithLsid(const std::string& lsid) {
+ DCHECK_EQ(MessageLoop::current(), message_loop());
+
+ AuthWatcherEvent event = { AuthWatcherEvent::AUTHENTICATION_ATTEMPT_START };
+ NotifyListeners(&event);
+
+ if (gaia_->AuthenticateWithLsid(lsid)) {
+ PersistCredentials();
+ DoAuthenticateWithToken(gaia_->email(), gaia_->auth_token());
+ } else {
+ ProcessGaiaAuthFailure();
+ }
+}
+
+const char kAuthWatcher[] = "AuthWatcher";
+
+void AuthWatcher::AuthenticateWithToken(const std::string& gaia_email,
+ const std::string& auth_token) {
+ message_loop()->PostTask(FROM_HERE, NewRunnableMethod(this,
+ &AuthWatcher::DoAuthenticateWithToken, gaia_email, auth_token));
+}
+
+void AuthWatcher::DoAuthenticateWithToken(const std::string& gaia_email,
+ const std::string& auth_token) {
+ DCHECK_EQ(MessageLoop::current(), message_loop());
+
+ 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);
+ const bool was_authenticated = NOT_AUTHENTICATED != status_;
+ switch (result) {
+ case Authenticator::SUCCESS:
+ {
+ status_ = GAIA_AUTHENTICATED;
+ const std::string& share_name = email;
+ user_settings_->SwitchUser(email);
+ scm_->set_auth_token(auth_token);
+
+ if (!was_authenticated) {
+ LOG(INFO) << "Opening DB for AuthenticateWithToken ("
+ << share_name << ")";
+ dirman_->Open(share_name);
+ }
+ NotifyAuthChanged(email, auth_token, false);
+ return;
+ }
+ 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;
+ }
+ // Always fall back to local authentication.
+ if (was_authenticated || AuthenticateLocally(email)) {
+ if (AuthWatcherEvent::SERVICE_CONNECTION_FAILED == event.what_happened)
+ return;
+ }
+ DCHECK_NE(event.what_happened, AuthWatcherEvent::ILLEGAL_VALUE);
+ NotifyListeners(&event);
+}
+
+bool AuthWatcher::AuthenticateLocally(string email) {
+ DCHECK_EQ(MessageLoop::current(), message_loop());
+ user_settings_->GetEmailForSignin(&email);
+ if (file_util::PathExists(FilePath(dirman_->GetSyncDataDatabasePath()))) {
+ gaia_->SetUsername(email);
+ status_ = LOCALLY_AUTHENTICATED;
+ user_settings_->SwitchUser(email);
+ LOG(INFO) << "Opening DB for AuthenticateLocally (" << email << ")";
+ dirman_->Open(email);
+ NotifyAuthChanged(email, "", false);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+bool AuthWatcher::AuthenticateLocally(string email, const string& password) {
+ DCHECK_EQ(MessageLoop::current(), message_loop());
+ user_settings_->GetEmailForSignin(&email);
+ return user_settings_->VerifyAgainstStoredHash(email, password)
+ && AuthenticateLocally(email);
+}
+
+void AuthWatcher::ProcessGaiaAuthFailure() {
+ DCHECK_EQ(MessageLoop::current(), message_loop());
+ gaia::GaiaAuthenticator::AuthResults results = gaia_->results();
+ if (LOCALLY_AUTHENTICATED != status_ &&
+ AuthenticateLocally(results.email, results.password)) {
+ // TODO(chron): Do we really want a bogus token?
+ const string auth_token("bogus");
+ user_settings_->SetAuthTokenForService(results.email,
+ SYNC_SERVICE_NAME,
+ auth_token);
+ }
+ AuthWatcherEvent myevent = { AuthWatcherEvent::GAIA_AUTH_FAILED, &results };
+ NotifyListeners(&myevent);
+}
+
+void AuthWatcher::DoAuthenticate(const AuthRequest& request) {
+ DCHECK_EQ(MessageLoop::current(), message_loop());
+
+ AuthWatcherEvent event = { AuthWatcherEvent::AUTHENTICATION_ATTEMPT_START };
+ NotifyListeners(&event);
+
+ current_attempt_trigger_ = request.trigger;
+
+ // We let the caller be lazy and try using the last captcha token seen by
+ // the gaia authenticator if they haven't provided a token but have sent
+ // a challenge response. Of course, if the captcha token is specified,
+ // we use that one instead.
+ std::string captcha_token(request.captcha_token);
+ if (!request.captcha_value.empty() && captcha_token.empty())
+ captcha_token = gaia_->captcha_token();
+
+ if (!request.password.empty()) {
+ bool authenticated = false;
+ if (!captcha_token.empty()) {
+ authenticated = gaia_->Authenticate(request.email, request.password,
+ captcha_token,
+ request.captcha_value);
+ } else {
+ authenticated = gaia_->Authenticate(request.email, request.password);
+ }
+ if (authenticated) {
+ PersistCredentials();
+ DoAuthenticateWithToken(gaia_->email(), gaia_->auth_token());
+ } else {
+ ProcessGaiaAuthFailure();
+ }
+ } else if (!request.auth_token.empty()) {
+ DoAuthenticateWithToken(request.email, request.auth_token);
+ } else {
+ LOG(ERROR) << "Attempt to authenticate with no credentials.";
+ }
+}
+
+void AuthWatcher::NotifyAuthChanged(const string& email,
+ const string& auth_token,
+ bool renewed) {
+ DCHECK_EQ(MessageLoop::current(), message_loop());
+ LOG(INFO) << "NotifyAuthSucceeded";
+ AuthWatcherEvent event = {
+ renewed ?
+ AuthWatcherEvent::AUTH_RENEWED :
+ AuthWatcherEvent::AUTH_SUCCEEDED
+ };
+ event.user_email = email;
+ event.auth_token = auth_token;
+
+ NotifyListeners(&event);
+}
+
+void AuthWatcher::HandleServerConnectionEvent(
+ const ServerConnectionEvent& event) {
+ message_loop()->PostTask(FROM_HERE, NewRunnableMethod(this,
+ &AuthWatcher::DoHandleServerConnectionEvent, event,
+ scm_->auth_token()));
+}
+
+void AuthWatcher::DoHandleServerConnectionEvent(
+ const ServerConnectionEvent& event,
+ const std::string& auth_token_snapshot) {
+ DCHECK_EQ(MessageLoop::current(), message_loop());
+ if (event.server_reachable &&
+ // If the auth_token at the time of the event differs from the current
+ // one, we have authenticated since then and don't need to re-try.
+ (auth_token_snapshot == gaia_->auth_token()) &&
+ (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.
+ gaia::GaiaAuthenticator::AuthResults authresults = gaia_->results();
+ AuthRequest request = { authresults.email, authresults.password,
+ authresults.auth_token, std::string(),
+ std::string(),
+ AuthWatcherEvent::EXPIRED_CREDENTIALS };
+ DoAuthenticate(request);
+ }
+}
+
+AuthWatcher::~AuthWatcher() {
+ auth_backend_thread_.Stop();
+ // The gaia authenticator takes a const MessageLoop* because it only uses it
+ // to ensure all methods are invoked on the given loop. Once our thread has
+ // stopped, the current message loop will be NULL, and no methods should be
+ // invoked on |gaia_| after this point. We could set it to NULL, but
+ // abstaining allows for even more sanity checking that nothing is invoked on
+ // it from now on.
+}
+
+void AuthWatcher::Authenticate(const string& email, const string& password,
+ const string& captcha_token, const string& captcha_value) {
+ LOG(INFO) << "AuthWatcher::Authenticate called";
+
+ string empty;
+ AuthRequest request = { FormatAsEmailAddress(email), password, empty,
+ captcha_token, captcha_value,
+ AuthWatcherEvent::USER_INITIATED };
+ message_loop()->PostTask(FROM_HERE, NewRunnableMethod(this,
+ &AuthWatcher::DoAuthenticate, request));
+}
+
+void AuthWatcher::ClearAuthenticationData() {
+ scm_->set_auth_token(std::string());
+ 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