| Index: chrome/browser/sync/engine/auth_watcher.cc
|
| ===================================================================
|
| --- chrome/browser/sync/engine/auth_watcher.cc (revision 0)
|
| +++ chrome/browser/sync/engine/auth_watcher.cc (revision 0)
|
| @@ -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
|
|
|
| Property changes on: chrome\browser\sync\engine\auth_watcher.cc
|
| ___________________________________________________________________
|
| Added: svn:eol-style
|
| + LF
|
|
|
|
|