summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/sync/engine/auth_watcher.cc275
-rw-r--r--chrome/browser/sync/engine/auth_watcher.h112
-rw-r--r--chrome/browser/sync/engine/auth_watcher_unittest.cc214
-rw-r--r--chrome/browser/sync/engine/net/gaia_authenticator.h9
-rw-r--r--chrome/browser/sync/engine/net/server_connection_manager.h4
-rw-r--r--chrome/browser/sync/engine/syncapi.cc23
-rw-r--r--chrome/browser/sync/notifier/listener/talk_mediator_impl.h1
-rwxr-xr-xchrome/chrome.gyp1
-rw-r--r--chrome/test/sync/engine/mock_server_connection.cc43
-rw-r--r--chrome/test/sync/engine/mock_server_connection.h14
-rw-r--r--chrome/test/sync/engine/test_directory_setter_upper.cc21
-rw-r--r--chrome/test/sync/engine/test_directory_setter_upper.h14
12 files changed, 472 insertions, 259 deletions
diff --git a/chrome/browser/sync/engine/auth_watcher.cc b/chrome/browser/sync/engine/auth_watcher.cc
index 87c899f..c722a8d 100644
--- a/chrome/browser/sync/engine/auth_watcher.cc
+++ b/chrome/browser/sync/engine/auth_watcher.cc
@@ -11,12 +11,10 @@
#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:
@@ -57,9 +55,14 @@ AuthWatcher::AuthWatcher(DirectoryManager* dirman,
status_(NOT_AUTHENTICATED),
user_settings_(user_settings),
talk_mediator_(talk_mediator),
- thread_handle_valid_(false),
- authenticating_now_(false),
+ 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));
@@ -67,12 +70,8 @@ AuthWatcher::AuthWatcher(DirectoryManager* dirman,
channel_.reset(new Channel(done));
}
-void* AuthWatcher::AuthenticationThreadStartRoutine(void* arg) {
- ThreadParams* args = reinterpret_cast<ThreadParams*>(arg);
- return args->self->AuthenticationThreadMain(args);
-}
-
-bool AuthWatcher::ProcessGaiaAuthSuccess() {
+void AuthWatcher::PersistCredentials() {
+ DCHECK_EQ(MessageLoop::current(), message_loop());
GaiaAuthenticator::AuthResults results = gaia_->results();
// We just successfully signed in again, let's clear out any residual cached
@@ -92,48 +91,19 @@ bool AuthWatcher::ProcessGaiaAuthSuccess() {
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;
- }
- }
+const char kAuthWatcher[] = "AuthWatcher";
- return false;
+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));
}
-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_);
+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 =
@@ -158,11 +128,12 @@ bool AuthWatcher::AuthenticateWithToken(const string& gaia_email,
// Set the authentication token for notifications
talk_mediator_->SetAuthToken(email, auth_token);
+ scm_->set_auth_token(auth_token);
if (!was_authenticated)
LoadDirectoryListAndOpen(share_name);
NotifyAuthSucceeded(email);
- return true;
+ return;
}
case Authenticator::BAD_AUTH_TOKEN:
event.what_happened = AuthWatcherEvent::SERVICE_AUTH_FAILED;
@@ -176,19 +147,19 @@ bool AuthWatcher::AuthenticateWithToken(const string& gaia_email,
break;
default:
LOG(FATAL) << "Illegal return from AuthenticateToken";
- return true; // keep the compiler happy
+ return;
}
// Always fall back to local authentication.
if (was_authenticated || AuthenticateLocally(email)) {
if (AuthWatcherEvent::SERVICE_CONNECTION_FAILED == event.what_happened)
- return true;
+ return;
}
- CHECK(event.what_happened != AuthWatcherEvent::ILLEGAL_VALUE);
+ DCHECK_NE(event.what_happened, AuthWatcherEvent::ILLEGAL_VALUE);
NotifyListeners(&event);
- return true;
}
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);
@@ -205,12 +176,14 @@ bool AuthWatcher::AuthenticateLocally(string email) {
}
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());
GaiaAuthenticator::AuthResults results = gaia_->results();
if (LOCALLY_AUTHENTICATED == status_) {
return; // nothing todo
@@ -234,77 +207,44 @@ void AuthWatcher::ProcessGaiaAuthFailure() {
NotifyListeners(&myevent);
}
-void* AuthWatcher::AuthenticationThreadMain(ThreadParams* args) {
- NameCurrentThreadForDebugging("SyncEngine_AuthWatcherThread");
- // TODO(timsteele): Remove this; this is temporary to satisfy code that
- // compares MessageLoop pointers until AuthWatcher uses a base::Thread.
- // We allocate a message_loop from the AuthWatcherThread so that
- // GaiaAuth and EventChannel get a valid opaque pointer to the current
- // message loop used for comparison. It gets stored in TLS as the 'current'
- // loop for this thread.
- MessageLoop message_loop;
- {
- // 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 ?
+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;
+
+ SaveCredentials save = request.persist_creds_to_disk ?
PERSIST_TO_DISK : SAVE_IN_MEMORY_ONLY;
- int attempt = 0;
SignIn const signin = user_settings_->
- RecallSigninType(args->email, GMAIL_SIGNIN);
-
- gaia_->set_message_loop(&message_loop);
-
- if (!args->password.empty()) {
- // TODO(timsteele): Split this mess up into functions.
- while (true) {
- bool authenticated;
- if (!args->captcha_token.empty() && !args->captcha_value.empty()) {
- authenticated = gaia_->Authenticate(args->email, args->password,
- save, args->captcha_token,
- args->captcha_value, signin);
- } else {
- authenticated = gaia_->Authenticate(args->email, args->password,
- save, signin);
- }
- if (authenticated) {
- if (!ProcessGaiaAuthSuccess()) {
- if (3 != ++attempt) {
- continue;
- }
- AuthWatcherEvent event =
- { AuthWatcherEvent::SERVICE_CONNECTION_FAILED, 0 };
- NotifyListeners(&event);
- }
- } else {
- ProcessGaiaAuthFailure();
- }
- break; // We are done trying to authenticate.
+ RecallSigninType(request.email, GMAIL_SIGNIN);
+
+ if (!request.password.empty()) {
+ bool authenticated = false;
+ if (!request.captcha_token.empty() && !request.captcha_value.empty()) {
+ authenticated = gaia_->Authenticate(request.email, request.password,
+ save, request.captcha_token,
+ request.captcha_value, signin);
+ } else {
+ authenticated = gaia_->Authenticate(request.email, request.password,
+ save, signin);
}
- } else if (!args->auth_token.empty()) {
- AuthenticateWithToken(args->email, args->auth_token);
+ 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.";
- }
-
- // We're done trying to authenticate. Set state and terminate the thread.
- {
- MutexLock lock(&mutex_);
- authenticating_now_ = false;
+ LOG(ERROR) << "Attempt to authenticate with no credentials.";
}
- // TODO(timsteele): Remove this; nothing ever gets posted to this loop.
- gaia_->set_message_loop(NULL);
- delete args;
- return 0;
-}
-
-void AuthWatcher::Reset() {
- status_ = NOT_AUTHENTICATED;
}
void AuthWatcher::NotifyAuthSucceeded(const string& email) {
+ DCHECK_EQ(MessageLoop::current(), message_loop());
LOG(INFO) << "NotifyAuthSucceeded";
AuthWatcherEvent event = { AuthWatcherEvent::AUTH_SUCCEEDED };
event.user_email = email;
@@ -312,76 +252,40 @@ void AuthWatcher::NotifyAuthSucceeded(const string& 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) {
+ 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 &&
- !authenticating_now_ &&
+ // 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)) {
+ 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.";
+ AuthRequest request = { authresults.email, authresults.password,
+ authresults.auth_token, std::string(),
+ std::string(),
+ PERSIST_TO_DISK == authresults.credentials_saved,
+ AuthWatcherEvent::EXPIRED_CREDENTIALS };
+ DoAuthenticate(request);
}
}
bool AuthWatcher::LoadDirectoryListAndOpen(const PathString& login) {
+ DCHECK_EQ(MessageLoop::current(), message_loop());
LOG(INFO) << "LoadDirectoryListAndOpen(" << login << ")";
bool initial_sync_ended = false;
@@ -395,33 +299,30 @@ bool AuthWatcher::LoadDirectoryListAndOpen(const PathString& login) {
}
AuthWatcher::~AuthWatcher() {
- WaitForAuthThreadFinish();
+ 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,
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();
+ AuthRequest request = { FormatAsEmailAddress(email), password, empty,
+ captcha_token, captcha_value, persist_creds_to_disk,
+ AuthWatcherEvent::USER_INITIATED };
+ message_loop()->PostTask(FROM_HERE, NewRunnableMethod(this,
+ &AuthWatcher::DoAuthenticate, request));
}
void AuthWatcher::ClearAuthenticationData() {
- sync_service_token_.clear();
- scm_->set_auth_token(sync_service_token());
+ scm_->set_auth_token(std::string());
user_settings_->ClearAllServiceTokens();
}
diff --git a/chrome/browser/sync/engine/auth_watcher.h b/chrome/browser/sync/engine/auth_watcher.h
index 00edcf9..9457e33 100644
--- a/chrome/browser/sync/engine/auth_watcher.h
+++ b/chrome/browser/sync/engine/auth_watcher.h
@@ -12,11 +12,14 @@
#include <string>
#include "base/atomicops.h"
+#include "base/ref_counted.h"
#include "base/scoped_ptr.h"
+#include "base/thread.h"
#include "chrome/browser/sync/engine/net/gaia_authenticator.h"
+#include "chrome/browser/sync/protocol/service_constants.h"
#include "chrome/browser/sync/util/event_sys.h"
-#include "chrome/browser/sync/util/pthread_helpers.h"
#include "chrome/browser/sync/util/sync_types.h"
+#include "testing/gtest/include/gtest/gtest_prod.h" // For FRIEND_TEST
namespace syncable {
struct DirectoryManagerEvent;
@@ -65,7 +68,15 @@ struct AuthWatcherEvent {
AuthenticationTrigger trigger;
};
-class AuthWatcher {
+// The mother-class of Authentication for the sync backend. Handles both gaia
+// and sync service authentication via asynchronous Authenticate* methods,
+// raising AuthWatcherEvents on success/failure. The implementation currently
+// runs its own backend thread for the actual auth processing, which means
+// the AuthWatcherEvents can be raised on a different thread than the one that
+// invoked authentication.
+class AuthWatcher : public base::RefCountedThreadSafe<AuthWatcher> {
+ friend class AuthWatcherTest;
+ FRIEND_TEST(AuthWatcherTest, Construction);
public:
// Normal progression is local -> gaia -> token.
enum Status { LOCALLY_AUTHENTICATED, GAIA_AUTHENTICATED, NOT_AUTHENTICATED };
@@ -93,6 +104,8 @@ class AuthWatcher {
return channel_.get();
}
+ // The following 3 flavors of authentication routines are asynchronous and can
+ // be called from any thread.
void Authenticate(const std::string& email, const std::string& password,
const std::string& captcha_token, const std::string& captcha_value,
bool persist_creds_to_disk);
@@ -102,11 +115,14 @@ class AuthWatcher {
Authenticate(email, password, "", "", persist_creds_to_disk);
}
- // Retrieves an auth token for a named service for which a long-lived token
- // was obtained at login time. Returns true if a long-lived token can be
- // found, false otherwise.
- bool GetAuthTokenForService(const std::string& service_name,
- std::string* service_token);
+ // Use this version when you don't need the gaia authentication step because
+ // you already have a valid token for |gaia_email|.
+ void AuthenticateWithToken(const std::string& gaia_email,
+ const std::string& auth_token);
+
+ // Joins on the backend thread. The AuthWatcher is useless after this and
+ // should be destroyed.
+ void Shutdown() { auth_backend_thread_.Stop(); }
std::string email() const;
syncable::DirectoryManager* dirman() const { return dirman_; }
@@ -115,33 +131,23 @@ class AuthWatcher {
UserSettings* settings() const { return user_settings_; }
Status status() const { return (Status)status_; }
- void Logout();
-
- // For synchronizing other destructors.
- void WaitForAuthThreadFinish();
-
- bool AuthenticateWithToken(const std::string& email,
- const std::string& auth_token);
-
protected:
- void Reset();
void ClearAuthenticationData();
void NotifyAuthSucceeded(const std::string& email);
- bool StartNewAuthAttempt(const std::string& email,
- const std::string& password,
- const std::string& auth_token, const std::string& captcha_token,
- const std::string& captcha_value, bool persist_creds_to_disk,
- AuthWatcherEvent::AuthenticationTrigger trigger);
void HandleServerConnectionEvent(const ServerConnectionEvent& event);
void SaveUserSettings(const std::string& username,
const std::string& auth_token,
const bool save_credentials);
+ MessageLoop* message_loop() { return auth_backend_thread_.message_loop(); }
+
+ private:
// These two helpers should only be called from the auth function.
- // Returns false iff we had problems and should try GAIA_AUTH again.
- bool ProcessGaiaAuthSuccess();
+ // Called when authentication with gaia succeeds, to save credential info.
+ void PersistCredentials();
+ // Called when authentication with gaia fails.
void ProcessGaiaAuthFailure();
// Just checks that the user has at least one local share cache.
@@ -152,48 +158,52 @@ class AuthWatcher {
// Sets the trigger member of the event and sends the event on channel_.
void NotifyListeners(AuthWatcherEvent* event);
- const std::string& sync_service_token() const { return sync_service_token_; }
-
- typedef PThreadScopedLock<PThreadMutex> MutexLock;
+ inline std::string FormatAsEmailAddress(const std::string& email) const {
+ std::string mail(email);
+ if (email.find('@') == std::string::npos) {
+ mail.push_back('@');
+ // TODO(chron): Should this be done only at the UI level?
+ mail.append(DEFAULT_SIGNIN_DOMAIN);
+ }
+ return mail;
+ }
- // Passed to newly created threads.
- struct ThreadParams {
- AuthWatcher* self;
- std::string email;
- std::string password;
- std::string auth_token;
- std::string captcha_token;
- std::string captcha_value;
- bool persist_creds_to_disk;
- AuthWatcherEvent::AuthenticationTrigger trigger;
+ // A struct to marshal various data across to the auth_backend_thread_ on
+ // Authenticate() and AuthenticateWithToken calls.
+ struct AuthRequest {
+ std::string email;
+ std::string password;
+ std::string auth_token;
+ std::string captcha_token;
+ std::string captcha_value;
+ bool persist_creds_to_disk;
+ AuthWatcherEvent::AuthenticationTrigger trigger;
};
- // Initial function passed to pthread_create.
- static void* AuthenticationThreadStartRoutine(void* arg);
- // Member function called by AuthenticationThreadStartRoutine.
- void* AuthenticationThreadMain(struct ThreadParams* arg);
+ // The public interface Authenticate methods are proxies to these, which
+ // can only be called from |auth_backend_thread_|.
+ void DoAuthenticate(const AuthRequest& request);
+ void DoAuthenticateWithToken(const std::string& email,
+ const std::string& auth_token);
+
+ // The public HandleServerConnectionEvent method proxies to this method, which
+ // can only be called on |auth_backend_thread_|.
+ void DoHandleServerConnectionEvent(
+ const ServerConnectionEvent& event,
+ const std::string& auth_token_snapshot);
scoped_ptr<GaiaAuthenticator> const gaia_;
syncable::DirectoryManager* const dirman_;
ServerConnectionManager* const scm_;
scoped_ptr<EventListenerHookup> connmgr_hookup_;
AllStatus* const allstatus_;
- // TODO(chron): It is incorrect to make assignments to AtomicWord.
- volatile base::subtle::AtomicWord status_;
+ Status status_;
UserSettings* const user_settings_;
TalkMediator* talk_mediator_; // Interface to the notifications engine.
scoped_ptr<Channel> channel_;
- // We store our service token in memory as a workaround to the fact that we
- // don't persist it when the user unchecks "remember me".
- // We also include it on outgoing requests.
- std::string sync_service_token_;
+ base::Thread auth_backend_thread_;
- PThreadMutex mutex_;
- // All members below are protected by the above mutex
- pthread_t thread_;
- bool thread_handle_valid_;
- bool authenticating_now_;
AuthWatcherEvent::AuthenticationTrigger current_attempt_trigger_;
DISALLOW_COPY_AND_ASSIGN(AuthWatcher);
};
diff --git a/chrome/browser/sync/engine/auth_watcher_unittest.cc b/chrome/browser/sync/engine/auth_watcher_unittest.cc
new file mode 100644
index 0000000..ca5192181
--- /dev/null
+++ b/chrome/browser/sync/engine/auth_watcher_unittest.cc
@@ -0,0 +1,214 @@
+// Copyright (c) 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 entry.
+
+#include "base/scoped_ptr.h"
+#include "base/scoped_temp_dir.h"
+#include "base/test_file_util.h"
+#include "base/waitable_event.h"
+#include "chrome/browser/sync/engine/all_status.h"
+#include "chrome/browser/sync/engine/auth_watcher.h"
+#include "chrome/browser/sync/engine/net/gaia_authenticator.h"
+#include "chrome/browser/sync/engine/net/http_return.h"
+#include "chrome/browser/sync/notifier/listener/talk_mediator_impl.h"
+#include "chrome/browser/sync/util/user_settings.h"
+#include "chrome/browser/sync/util/event_sys-inl.h"
+#include "chrome/test/sync/engine/mock_server_connection.h"
+#include "chrome/test/sync/engine/test_directory_setter_upper.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+static const wchar_t* kUserSettingsDB = L"Settings.sqlite3";
+static const char* kTestUserAgent = "useragent";
+static const char* kTestServiceId = "serviceid";
+static const char* kTestGaiaURL = "http://gaia_url";
+static const char* kUserDisplayName = "Mr. Auth Watcher";
+static const char* kUserDisplayEmail = "authwatcherdisplay@gmail.com";
+static const char* kTestEmail = "authwatchertest@gmail.com";
+static const char* kWrongPassword = "wrongpassword";
+static const char* kCorrectPassword = "correctpassword";
+static const char* kValidSID = "validSID";
+static const char* kValidLSID = "validLSID";
+static const char* kInvalidAuthToken = "invalidAuthToken";
+static const char* kValidAuthToken = "validAuthToken";
+
+namespace {
+
+class GaiaAuthMock : public browser_sync::GaiaAuthenticator {
+ public:
+ GaiaAuthMock() : browser_sync::GaiaAuthenticator(
+ kTestUserAgent, kTestServiceId, kTestGaiaURL),
+ use_bad_auth_token_(false) {}
+ virtual ~GaiaAuthMock() {}
+
+ void SendBadAuthTokenForNextRequest() { use_bad_auth_token_ = true; }
+
+ protected:
+ bool PerformGaiaRequest(const AuthParams& params, AuthResults* results) {
+ if (params.password == kWrongPassword) {
+ results->auth_error = browser_sync::BadAuthentication;
+ return false;
+ }
+ if (params.password == kCorrectPassword) {
+ results->sid = kValidSID;
+ results->lsid = kValidLSID;
+ results->auth_token = kValidAuthToken;
+ }
+ if (use_bad_auth_token_) {
+ results->auth_token = kInvalidAuthToken;
+ use_bad_auth_token_ = false;
+ }
+ return true;
+ }
+
+ bool LookupEmail(AuthResults* results) {
+ results->signin = GMAIL_SIGNIN;
+ return true;
+ }
+
+ private:
+ // Whether we should send an invalid auth token on the next request.
+ bool use_bad_auth_token_;
+};
+
+} // namespace
+
+namespace browser_sync {
+
+class AuthWatcherTest : public testing::Test {
+ public:
+ AuthWatcherTest() : consumer_ready(false, false),
+ event_produced(false, false),
+ metadb_(kUserDisplayEmail) {}
+ virtual void SetUp() {
+ metadb_.SetUp();
+ connection_.reset(new MockConnectionManager(metadb_.manager(),
+ metadb_.name()));
+ // Mock out data that would normally be sent back from a server.
+ connection()->SetAuthenticationResponseInfo(kValidAuthToken,
+ kUserDisplayName, kUserDisplayEmail, "ID");
+ allstatus_.reset(new AllStatus());
+ user_settings_.reset(new UserSettings());
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ PathString user_settings_path = temp_dir_.path().value() + kUserSettingsDB;
+ user_settings_->Init(user_settings_path);
+ gaia_auth_ = new GaiaAuthMock();
+ talk_mediator_.reset(new TalkMediatorImpl());
+ auth_watcher_ = new AuthWatcher(metadb_.manager(), connection_.get(),
+ allstatus_.get(), kTestUserAgent, kTestServiceId, kTestGaiaURL,
+ user_settings_.get(), gaia_auth_, talk_mediator_.get());
+ authwatcher_hookup_.reset(NewEventListenerHookup(auth_watcher_->channel(),
+ this, &AuthWatcherTest::HandleAuthWatcherEvent));
+ }
+
+ virtual void TearDown() {
+ metadb_.TearDown();
+ auth_watcher_->Shutdown();
+ EXPECT_FALSE(auth_watcher()->message_loop());
+ }
+
+ void HandleAuthWatcherEvent(const AuthWatcherEvent& event) {
+ if (event.what_happened == AuthWatcherEvent::AUTHWATCHER_DESTROYED)
+ return;
+ consumer_ready.Wait(); // Block progress until the test is ready.
+
+ last_event_reason_ = event.what_happened;
+ if (event.what_happened == AuthWatcherEvent::AUTH_SUCCEEDED)
+ user_email_ = event.user_email;
+
+ event_produced.Signal();
+ }
+
+ AuthWatcherEvent::WhatHappened ConsumeNextEvent() {
+ consumer_ready.Signal();
+ event_produced.Wait();
+ return last_event_reason_;
+ }
+
+ AuthWatcher* auth_watcher() { return auth_watcher_.get(); }
+ MockConnectionManager* connection() { return connection_.get(); }
+ GaiaAuthMock* gaia_auth() { return gaia_auth_; }
+ const std::string& user_email() { return user_email_; }
+
+ private:
+ // Responsible for creating / deleting a temp dir containing user settings DB.
+ ScopedTempDir temp_dir_;
+
+ // The event listener hookup registered for HandleAuthWatcherEvent.
+ scoped_ptr<EventListenerHookup> authwatcher_hookup_;
+
+ // The sync engine pieces necessary to run an AuthWatcher.
+ TriggeredOpenTestDirectorySetterUpper metadb_;
+ scoped_ptr<MockConnectionManager> connection_;
+ scoped_ptr<AllStatus> allstatus_;
+ scoped_ptr<UserSettings> user_settings_;
+ GaiaAuthMock* gaia_auth_; // Owned by auth_watcher_.
+ scoped_ptr<TalkMediator> talk_mediator_;
+ scoped_refptr<AuthWatcher> auth_watcher_;
+
+ // This is used to block the AuthWatcherThread when it raises events until we
+ // are ready to read the event. It is not a manual-reset event, so it goes
+ // straight back to non-signaled after one thread (the main thread) is
+ // signaled (or "consumes" the signaled state).
+ base::WaitableEvent consumer_ready;
+
+ // This is signaled by the AuthWatcherThread after it sets last_event_reason_
+ // and possibly user_email_ for us to read.
+ base::WaitableEvent event_produced;
+
+ // The 'WhatHappened' value from the last AuthWatcherEvent we handled.
+ AuthWatcherEvent::WhatHappened last_event_reason_;
+
+ // Set when we receive an AUTH_SUCCEEDED event.
+ std::string user_email_;
+
+ DISALLOW_COPY_AND_ASSIGN(AuthWatcherTest);
+};
+
+TEST_F(AuthWatcherTest, Construction) {
+ EXPECT_TRUE(auth_watcher()->message_loop());
+ EXPECT_EQ("SyncEngine_AuthWatcherThread",
+ auth_watcher()->message_loop()->thread_name());
+ EXPECT_TRUE(auth_watcher()->auth_backend_thread_.IsRunning());
+ EXPECT_EQ(AuthWatcher::NOT_AUTHENTICATED, auth_watcher()->status());
+}
+
+TEST_F(AuthWatcherTest, AuthenticateGaiaAuthFailure) {
+ auth_watcher()->Authenticate(kTestEmail, kWrongPassword,
+ std::string(), // captcha_token
+ std::string(), // captcha_value
+ false); // persist_creds_to_disk
+ EXPECT_EQ(AuthWatcherEvent::AUTHENTICATION_ATTEMPT_START, ConsumeNextEvent());
+ EXPECT_EQ(AuthWatcherEvent::GAIA_AUTH_FAILED, ConsumeNextEvent());
+}
+
+TEST_F(AuthWatcherTest, AuthenticateBadAuthToken) {
+ gaia_auth()->SendBadAuthTokenForNextRequest();
+ auth_watcher()->Authenticate(kTestEmail, kCorrectPassword, std::string(),
+ std::string(), false);
+ EXPECT_EQ(AuthWatcherEvent::AUTHENTICATION_ATTEMPT_START, ConsumeNextEvent());
+ EXPECT_EQ(AuthWatcherEvent::SERVICE_AUTH_FAILED, ConsumeNextEvent());
+}
+
+TEST_F(AuthWatcherTest, AuthenticateSuccess) {
+ auth_watcher()->Authenticate(kTestEmail, kCorrectPassword, std::string(),
+ std::string(), false);
+ EXPECT_EQ(AuthWatcherEvent::AUTHENTICATION_ATTEMPT_START, ConsumeNextEvent());
+ EXPECT_EQ(AuthWatcherEvent::AUTH_SUCCEEDED, ConsumeNextEvent());
+
+ // The server responds with a different email than what we used in the call
+ // to Authenticate, and the AuthWatcher should have told us about.
+ EXPECT_EQ(kUserDisplayEmail, user_email());
+}
+
+TEST_F(AuthWatcherTest, AuthenticateWithTokenBadAuthToken) {
+ auth_watcher()->AuthenticateWithToken(kTestEmail, kInvalidAuthToken);
+ EXPECT_EQ(AuthWatcherEvent::SERVICE_AUTH_FAILED, ConsumeNextEvent());
+}
+
+TEST_F(AuthWatcherTest, AuthenticateWithTokenSuccess) {
+ auth_watcher()->AuthenticateWithToken(kTestEmail, kValidAuthToken);
+ EXPECT_EQ(AuthWatcherEvent::AUTH_SUCCEEDED, ConsumeNextEvent());
+ EXPECT_EQ(kUserDisplayEmail, user_email());
+}
+
+} // namespace browser_sync
diff --git a/chrome/browser/sync/engine/net/gaia_authenticator.h b/chrome/browser/sync/engine/net/gaia_authenticator.h
index b490da8..264e499 100644
--- a/chrome/browser/sync/engine/net/gaia_authenticator.h
+++ b/chrome/browser/sync/engine/net/gaia_authenticator.h
@@ -105,7 +105,7 @@ class GaiaAuthenticator {
// This object should only be invoked from the AuthWatcherThread message
// loop, which is injected here.
- void set_message_loop(MessageLoop* loop) {
+ void set_message_loop(const MessageLoop* loop) {
message_loop_ = loop;
}
@@ -181,9 +181,10 @@ class GaiaAuthenticator {
// The real Authenticate implementations.
bool AuthenticateImpl(const AuthParams& params);
bool AuthenticateImpl(const AuthParams& params, AuthResults* results);
- bool PerformGaiaRequest(const AuthParams& params, AuthResults* results);
// virtual for testing purposes.
+ virtual bool PerformGaiaRequest(const AuthParams& params,
+ AuthResults* results);
virtual bool Post(const GURL& url, const std::string& post_body,
unsigned long* response_code, std::string* response_body) {
return false;
@@ -191,7 +192,7 @@ class GaiaAuthenticator {
// Caller should fill in results->LSID before calling. Result in
// results->primary_email.
- bool LookupEmail(AuthResults* results);
+ virtual bool LookupEmail(AuthResults* results);
public:
// Retrieve email.
@@ -288,7 +289,7 @@ class GaiaAuthenticator {
// The message loop all our methods are invoked on. Generally this is the
// SyncEngine_AuthWatcherThread's message loop.
- MessageLoop* message_loop_;
+ const MessageLoop* message_loop_;
};
} // namespace browser_sync
diff --git a/chrome/browser/sync/engine/net/server_connection_manager.h b/chrome/browser/sync/engine/net/server_connection_manager.h
index 768c7e3..92b7b28 100644
--- a/chrome/browser/sync/engine/net/server_connection_manager.h
+++ b/chrome/browser/sync/engine/net/server_connection_manager.h
@@ -256,6 +256,10 @@ class ServerConnectionManager {
auth_token_.assign(auth_token);
}
+ const std::string& auth_token() const {
+ return auth_token_;
+ }
+
protected:
PThreadMutex shutdown_event_mutex_;
diff --git a/chrome/browser/sync/engine/syncapi.cc b/chrome/browser/sync/engine/syncapi.cc
index 5a72fd4..35081c5 100644
--- a/chrome/browser/sync/engine/syncapi.cc
+++ b/chrome/browser/sync/engine/syncapi.cc
@@ -882,7 +882,7 @@ class SyncManager::SyncInternal {
// the initial connectivity and causes the server connection event to be
// broadcast, which signals the syncer thread to start syncing.
// It has a heavy duty constructor requiring boilerplate so we heap allocate.
- scoped_ptr<AuthWatcher> auth_watcher_;
+ scoped_refptr<AuthWatcher> auth_watcher_;
// A store of change records produced by HandleChangeEvent during the
// CALCULATE_CHANGES step, and to be processed, and forwarded to the
@@ -1033,15 +1033,15 @@ bool SyncManager::SyncInternal::Init(
BridgedGaiaAuthenticator* gaia_auth = new BridgedGaiaAuthenticator(
gaia_source, service_id, gaia_url, auth_post_factory);
- auth_watcher_.reset(new AuthWatcher(dir_manager(),
- connection_manager(),
- &allstatus_,
- gaia_source,
- service_id,
- gaia_url,
- user_settings_.get(),
- gaia_auth,
- talk_mediator()));
+ auth_watcher_ = new AuthWatcher(dir_manager(),
+ connection_manager(),
+ &allstatus_,
+ gaia_source,
+ service_id,
+ gaia_url,
+ user_settings_.get(),
+ gaia_auth,
+ talk_mediator());
talk_mediator()->WatchAuthWatcher(auth_watcher());
allstatus()->WatchAuthWatcher(auth_watcher());
@@ -1172,7 +1172,8 @@ void SyncManager::SyncInternal::Shutdown() {
// it terminates gracefully before we shutdown and close other components.
// Otherwise the attempt can complete after we've closed the directory, for
// example, and cause initialization to continue, which is bad.
- auth_watcher_.reset();
+ auth_watcher_->Shutdown();
+ auth_watcher_ = NULL;
if (syncer_thread()) {
if (!syncer_thread()->Stop(kThreadExitTimeoutMsec))
diff --git a/chrome/browser/sync/notifier/listener/talk_mediator_impl.h b/chrome/browser/sync/notifier/listener/talk_mediator_impl.h
index 33cff04..84c45f2 100644
--- a/chrome/browser/sync/notifier/listener/talk_mediator_impl.h
+++ b/chrome/browser/sync/notifier/listener/talk_mediator_impl.h
@@ -15,6 +15,7 @@
#include "chrome/browser/sync/engine/auth_watcher.h"
#include "chrome/browser/sync/notifier/listener/mediator_thread.h"
#include "chrome/browser/sync/notifier/listener/talk_mediator.h"
+#include "chrome/browser/sync/util/pthread_helpers.h"
#include "talk/xmpp/xmppclientsettings.h"
#include "testing/gtest/include/gtest/gtest_prod.h" // For FRIEND_TEST
diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp
index d0cff67..06c769b 100755
--- a/chrome/chrome.gyp
+++ b/chrome/chrome.gyp
@@ -6633,6 +6633,7 @@
'sources': [
'browser/sync/engine/all_status_unittest.cc',
'browser/sync/engine/apply_updates_command_unittest.cc',
+ 'browser/sync/engine/auth_watcher_unittest.cc',
'browser/sync/engine/net/gaia_authenticator_unittest.cc',
'browser/sync/engine/syncer_proto_util_unittest.cc',
'browser/sync/engine/syncer_thread_unittest.cc',
diff --git a/chrome/test/sync/engine/mock_server_connection.cc b/chrome/test/sync/engine/mock_server_connection.cc
index c4595bd..46ec870 100644
--- a/chrome/test/sync/engine/mock_server_connection.cc
+++ b/chrome/test/sync/engine/mock_server_connection.cc
@@ -70,22 +70,23 @@ void MockConnectionManager::SetMidCommitObserver(
bool MockConnectionManager::PostBufferToPath(const PostBufferParams* params,
const string& path,
const string& auth_token) {
-
- ScopedDirLookup directory(directory_manager_, directory_name_);
- CHECK(directory.good());
-
ClientToServerMessage post;
CHECK(post.ParseFromString(params->buffer_in));
client_stuck_ = post.sync_problem_detected();
ClientToServerResponse response;
response.Clear();
+
+ ScopedDirLookup directory(directory_manager_, directory_name_);
+ // For any non-AUTHENTICATE requests, a valid directory should be set up.
// If the Directory's locked when we do this, it's a problem as in normal
// use this function could take a while to return because it accesses the
// network. As we can't test this we do the next best thing and hang here
// when there's an issue.
- {
+ if (post.message_contents() != ClientToServerMessage::AUTHENTICATE) {
+ CHECK(directory.good());
WriteTransaction wt(directory, syncable::UNITTEST, __FILE__, __LINE__);
}
+
if (fail_next_postbuffer_) {
fail_next_postbuffer_ = false;
return false;
@@ -101,12 +102,15 @@ bool MockConnectionManager::PostBufferToPath(const PostBufferParams* params,
return true;
}
bool result = true;
- EXPECT_TRUE(!store_birthday_sent_ || post.has_store_birthday());
+ EXPECT_TRUE(!store_birthday_sent_ || post.has_store_birthday() ||
+ post.message_contents() == ClientToServerMessage::AUTHENTICATE);
store_birthday_sent_ = true;
if (post.message_contents() == ClientToServerMessage::COMMIT) {
ProcessCommit(&post, &response);
} else if (post.message_contents() == ClientToServerMessage::GET_UPDATES) {
ProcessGetUpdates(&post, &response);
+ } else if (post.message_contents() == ClientToServerMessage::AUTHENTICATE) {
+ ProcessAuthenticate(&post, &response, auth_token);
} else {
EXPECT_TRUE(false) << "Unknown/unsupported ClientToServerMessage";
return false;
@@ -249,6 +253,33 @@ void MockConnectionManager::ProcessGetUpdates(ClientToServerMessage* csm,
ResetUpdates();
}
+void MockConnectionManager::ProcessAuthenticate(ClientToServerMessage* csm,
+ ClientToServerResponse* response, const std::string& auth_token){
+ ASSERT_EQ(csm->message_contents(), ClientToServerMessage::AUTHENTICATE);
+ EXPECT_FALSE(auth_token.empty());
+
+ if (auth_token != valid_auth_token_) {
+ response->set_error_code(ClientToServerResponse::AUTH_INVALID);
+ return;
+ }
+
+ response->set_error_code(ClientToServerResponse::SUCCESS);
+ response->mutable_authenticate()->CopyFrom(auth_response_);
+ auth_response_.Clear();
+}
+
+void MockConnectionManager::SetAuthenticationResponseInfo(
+ const std::string& valid_auth_token,
+ const std::string& user_display_name,
+ const std::string& user_display_email,
+ const std::string& user_obfuscated_id) {
+ valid_auth_token_ = valid_auth_token;
+ sync_pb::UserIdentification* user = auth_response_.mutable_user();
+ user->set_display_name(user_display_name);
+ user->set_email(user_display_email);
+ user->set_obfuscated_id(user_obfuscated_id);
+}
+
bool MockConnectionManager::ShouldConflictThisCommit() {
bool conflict = false;
if (conflict_all_commits_) {
diff --git a/chrome/test/sync/engine/mock_server_connection.h b/chrome/test/sync/engine/mock_server_connection.h
index 8c0cca7..1c19a5b 100644
--- a/chrome/test/sync/engine/mock_server_connection.h
+++ b/chrome/test/sync/engine/mock_server_connection.h
@@ -109,6 +109,12 @@ class MockConnectionManager : public browser_sync::ServerConnectionManager {
void SetNewTimestamp(int64 ts);
void SetNewestTimestamp(int64 ts);
+ // For AUTHENTICATE responses.
+ void SetAuthenticationResponseInfo(const std::string& valid_auth_token,
+ const std::string& user_display_name,
+ const std::string& user_display_email,
+ const std::string& user_obfuscated_id);
+
void FailNextPostBufferToPathCall() { fail_next_postbuffer_ = true; }
// Simple inspectors.
@@ -144,6 +150,9 @@ class MockConnectionManager : public browser_sync::ServerConnectionManager {
// Functions to handle the various types of server request.
void ProcessGetUpdates(sync_pb::ClientToServerMessage* csm,
sync_pb::ClientToServerResponse* response);
+ void ProcessAuthenticate(sync_pb::ClientToServerMessage* csm,
+ sync_pb::ClientToServerResponse* response,
+ const std::string& auth_token);
void ProcessCommit(sync_pb::ClientToServerMessage* csm,
sync_pb::ClientToServerResponse* response_buffer);
// Locate the most recent update message for purpose of alteration.
@@ -190,6 +199,11 @@ class MockConnectionManager : public browser_sync::ServerConnectionManager {
TestCallbackFunction mid_commit_callback_function_;
MidCommitObserver* mid_commit_observer_;
+ // The AUTHENTICATE response we'll return for auth requests.
+ sync_pb::AuthenticateResponse auth_response_;
+ // What we use to determine if we should return SUCCESS or BAD_AUTH_TOKEN.
+ std::string valid_auth_token_;
+
scoped_ptr<sync_pb::ClientCommand> client_command_;
// The next value to use for the position_in_parent property.
diff --git a/chrome/test/sync/engine/test_directory_setter_upper.cc b/chrome/test/sync/engine/test_directory_setter_upper.cc
index 0a7b32d..919ce2a 100644
--- a/chrome/test/sync/engine/test_directory_setter_upper.cc
+++ b/chrome/test/sync/engine/test_directory_setter_upper.cc
@@ -4,6 +4,7 @@
#include "chrome/test/sync/engine/test_directory_setter_upper.h"
+#include "base/string_util.h"
#include "chrome/browser/sync/syncable/directory_manager.h"
#include "chrome/browser/sync/syncable/syncable.h"
#include "chrome/browser/sync/util/compat_file.h"
@@ -17,6 +18,8 @@ using syncable::ScopedDirLookup;
namespace browser_sync {
TestDirectorySetterUpper::TestDirectorySetterUpper() : name_(PSTR("Test")) {}
+TestDirectorySetterUpper::TestDirectorySetterUpper(const PathString& name)
+ : name_(name) {}
TestDirectorySetterUpper::~TestDirectorySetterUpper() {}
@@ -82,4 +85,22 @@ void ManuallyOpenedTestDirectorySetterUpper::TearDown() {
}
}
+TriggeredOpenTestDirectorySetterUpper::TriggeredOpenTestDirectorySetterUpper(
+ const std::string& name) : TestDirectorySetterUpper(UTF8ToWide(name)) {
+}
+
+void TriggeredOpenTestDirectorySetterUpper::SetUp() {
+ Init();
+}
+
+void TriggeredOpenTestDirectorySetterUpper::TearDown() {
+ DirectoryManager::DirNames names;
+ manager()->GetOpenDirectories(&names);
+ if (!names.empty()) {
+ ASSERT_EQ(1, names.size());
+ ASSERT_EQ(name(), names[0]);
+ TestDirectorySetterUpper::TearDown();
+ }
+}
+
} // namespace browser_sync
diff --git a/chrome/test/sync/engine/test_directory_setter_upper.h b/chrome/test/sync/engine/test_directory_setter_upper.h
index 0cfa3e6..3c8936a 100644
--- a/chrome/test/sync/engine/test_directory_setter_upper.h
+++ b/chrome/test/sync/engine/test_directory_setter_upper.h
@@ -59,6 +59,8 @@ class TestDirectorySetterUpper {
const PathString& name() const { return name_; }
protected:
+ // Subclasses may want to use a different directory name.
+ explicit TestDirectorySetterUpper(const PathString& name);
virtual void Init();
private:
@@ -82,6 +84,18 @@ class ManuallyOpenedTestDirectorySetterUpper : public TestDirectorySetterUpper {
bool was_opened_;
};
+// Use this flavor if you have a test that will trigger the opening event to
+// happen automagically. It is careful on teardown to close only if needed.
+class TriggeredOpenTestDirectorySetterUpper : public TestDirectorySetterUpper {
+ public:
+ // A triggered open is typically in response to a successful auth event just
+ // as in "real life". In this case, the name that will be used should be
+ // deterministically known at construction, and is passed in |name|.
+ TriggeredOpenTestDirectorySetterUpper(const std::string& name);
+ virtual void SetUp();
+ virtual void TearDown();
+};
+
} // namespace browser_sync
#endif // CHROME_TEST_SYNC_ENGINE_TEST_DIRECTORY_SETTER_UPPER_H_