Logo Search packages:      
Sourcecode: chromium-browser version File versions  Download package

auth_watcher.cc

// 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());
  loop_proxy_ = auth_backend_thread_.message_loop_proxy();

  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_proxy()->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_proxy()->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_proxy()->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_proxy()->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_proxy()->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

Generated by  Doxygen 1.6.0   Back to index