summaryrefslogtreecommitdiffstats
path: root/chrome/browser
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser')
-rw-r--r--chrome/browser/chromeos/login/login_utils.cc13
-rw-r--r--chrome/browser/net/gaia/token_service.cc176
-rw-r--r--chrome/browser/net/gaia/token_service.h123
-rw-r--r--chrome/browser/net/gaia/token_service_unittest.cc226
-rw-r--r--chrome/browser/password_manager/password_store_default_unittest.cc15
-rw-r--r--chrome/browser/password_manager/password_store_win_unittest.cc12
-rw-r--r--chrome/browser/password_manager/password_store_x_unittest.cc12
-rw-r--r--chrome/browser/sync/profile_sync_service_startup_unittest.cc4
-rw-r--r--chrome/browser/webdata/web_data_service.cc83
-rw-r--r--chrome/browser/webdata/web_data_service.h28
-rw-r--r--chrome/browser/webdata/web_database.cc76
-rw-r--r--chrome/browser/webdata/web_database.h21
-rw-r--r--chrome/browser/webdata/web_database_unittest.cc60
13 files changed, 750 insertions, 99 deletions
diff --git a/chrome/browser/chromeos/login/login_utils.cc b/chrome/browser/chromeos/login/login_utils.cc
index 33c212e..c3ef2f3 100644
--- a/chrome/browser/chromeos/login/login_utils.cc
+++ b/chrome/browser/chromeos/login/login_utils.cc
@@ -153,10 +153,15 @@ void LoginUtilsImpl::CompleteLogin(const std::string& username,
*(CommandLine::ForCurrentProcess()),
logging::DELETE_OLD_LOG_FILE);
- // Supply credentials for sync and others to use
- profile->GetTokenService()->Initialize(GaiaConstants::kChromeOSSource,
- profile->GetRequestContext(),
- credentials);
+ // Supply credentials for sync and others to use. Load tokens from disk.
+ TokenService* token_service = profile->GetTokenService();
+ token_service->Initialize(GaiaConstants::kChromeOSSource,
+ profile);
+ token_service->LoadTokensFromDB();
+ token_service->UpdateCredentials(credentials);
+ if (token_service->AreCredentialsValid()) {
+ token_service->StartFetchingTokens();
+ }
// Take the credentials passed in and try to exchange them for
// full-fledged Google authentication cookies. This is
diff --git a/chrome/browser/net/gaia/token_service.cc b/chrome/browser/net/gaia/token_service.cc
index 0a87dfe..eaa7a1ae 100644
--- a/chrome/browser/net/gaia/token_service.cc
+++ b/chrome/browser/net/gaia/token_service.cc
@@ -5,26 +5,86 @@
#include "chrome/browser/net/gaia/token_service.h"
#include "base/string_util.h"
+#include "chrome/browser/profile.h"
#include "chrome/common/net/gaia/gaia_authenticator2.h"
#include "chrome/common/net/gaia/gaia_constants.h"
#include "chrome/common/notification_service.h"
-void TokenService::Initialize(
- const char* const source,
- URLRequestContextGetter* getter,
- const GaiaAuthConsumer::ClientLoginResult& credentials) {
+// Unfortunately kNumServices must be defined in the .h.
+const char* TokenService::kServices[] = {GaiaConstants::kSyncService,
+ GaiaConstants::kTalkService};
+TokenService::TokenService()
+ : token_loading_query_(NULL) {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+}
- credentials_ = credentials;
+TokenService::~TokenService() {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+ ResetCredentialsInMemory();
+}
+
+void TokenService::Initialize(const char* const source,
+ Profile* profile) {
+
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+
+ getter_ = profile->GetRequestContext();
+ // Since the user can create a bookmark in incognito, sync may be running.
+ // Thus we have to go for explicit access.
+ web_data_service_ = profile->GetWebDataService(Profile::EXPLICIT_ACCESS);
source_ = std::string(source);
- sync_token_fetcher_.reset(new GaiaAuthenticator2(this, source_, getter));
- talk_token_fetcher_.reset(new GaiaAuthenticator2(this, source_, getter));
}
-bool TokenService::AreCredentialsValid() const {
+void TokenService::ResetCredentialsInMemory() {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+
+ // Terminate any running fetchers. Callbacks will not return.
+ for (int i = 0; i < kNumServices; i++) {
+ fetchers_[i].reset();
+ }
+
+ // Cancel pending loads. Callbacks will not return.
+ if (token_loading_query_) {
+ web_data_service_->CancelRequest(token_loading_query_);
+ token_loading_query_ = NULL;
+ }
+
+ token_map_.clear();
+ credentials_ = GaiaAuthConsumer::ClientLoginResult();
+}
+
+void TokenService::UpdateCredentials(
+ const GaiaAuthConsumer::ClientLoginResult& credentials) {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+ credentials_ = credentials;
+
+ // Cancels any currently running requests.
+ for (int i = 0; i < kNumServices; i++) {
+ fetchers_[i].reset(new GaiaAuthenticator2(this, source_, getter_));
+ }
+}
+
+void TokenService::LoadTokensFromDB() {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+ token_loading_query_ = web_data_service_->GetAllTokens(this);
+}
+
+void TokenService::SaveAuthTokenToDB(const std::string& service,
+ const std::string& auth_token) {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+ web_data_service_->SetTokenForService(service, auth_token);
+}
+
+void TokenService::EraseTokensFromDB() {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+ web_data_service_->RemoveAllTokens();
+}
+
+const bool TokenService::AreCredentialsValid() const {
return !credentials_.lsid.empty() && !credentials_.sid.empty();
}
-bool TokenService::HasLsid() const {
+const bool TokenService::HasLsid() const {
return !credentials_.lsid.empty();
}
@@ -33,18 +93,18 @@ const std::string& TokenService::GetLsid() const {
}
void TokenService::StartFetchingTokens() {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
DCHECK(AreCredentialsValid());
- sync_token_fetcher_->StartIssueAuthToken(credentials_.sid,
- credentials_.lsid,
- GaiaConstants::kSyncService);
- talk_token_fetcher_->StartIssueAuthToken(credentials_.sid,
- credentials_.lsid,
- GaiaConstants::kTalkService);
+ for (int i = 0; i < kNumServices; i++) {
+ fetchers_[i]->StartIssueAuthToken(credentials_.sid,
+ credentials_.lsid,
+ kServices[i]);
+ }
}
// Services dependent on a token will check if a token is available.
// If it isn't, they'll go to sleep until they get a token event.
-bool TokenService::HasTokenForService(const char* const service) const {
+const bool TokenService::HasTokenForService(const char* const service) const {
return token_map_.count(service) > 0;
}
@@ -52,28 +112,96 @@ const std::string& TokenService::GetTokenForService(
const char* const service) const {
if (token_map_.count(service) > 0) {
- // map[key] is not const
+ // Note map[key] is not const.
return (*token_map_.find(service)).second;
}
return EmptyString();
}
-void TokenService::OnIssueAuthTokenSuccess(const std::string& service,
- const std::string& auth_token) {
- LOG(INFO) << "Got an authorization token for " << service;
- token_map_[service] = auth_token;
+// Note that this can fire twice or more for any given service.
+// It can fire once from the DB read, and then once from the initial
+// fetcher. Future fetches can cause more notification firings.
+// The DB read will not however fire a notification if the fetcher
+// returned first. So it's always safe to use the latest notification.
+void TokenService::FireTokenAvailableNotification(
+ const std::string& service,
+ const std::string& auth_token) {
+
TokenAvailableDetails details(service, auth_token);
NotificationService::current()->Notify(
NotificationType::TOKEN_AVAILABLE,
Source<TokenService>(this),
Details<const TokenAvailableDetails>(&details));
}
-void TokenService::OnIssueAuthTokenFailure(const std::string& service,
- const GaiaAuthError& error) {
- LOG(WARNING) << "Auth token issuing failed for service:" << service;
+
+void TokenService::FireTokenRequestFailedNotification(
+ const std::string& service,
+ const GaiaAuthError& error) {
+
TokenRequestFailedDetails details(service, error);
NotificationService::current()->Notify(
NotificationType::TOKEN_REQUEST_FAILED,
Source<TokenService>(this),
Details<const TokenRequestFailedDetails>(&details));
}
+
+void TokenService::OnIssueAuthTokenSuccess(const std::string& service,
+ const std::string& auth_token) {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+ LOG(INFO) << "Got an authorization token for " << service;
+ token_map_[service] = auth_token;
+ FireTokenAvailableNotification(service, auth_token);
+ SaveAuthTokenToDB(service, auth_token);
+}
+
+void TokenService::OnIssueAuthTokenFailure(const std::string& service,
+ const GaiaAuthError& error) {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+ LOG(WARNING) << "Auth token issuing failed for service:" << service;
+ FireTokenRequestFailedNotification(service, error);
+}
+
+void TokenService::OnWebDataServiceRequestDone(WebDataService::Handle h,
+ const WDTypedResult* result) {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+ DCHECK(token_loading_query_);
+ token_loading_query_ = NULL;
+
+ // If the fetch failed, there will be no result. In that case, we just don't
+ // load any tokens at all from the DB.
+ if (result) {
+ DCHECK(result->GetType() == TOKEN_RESULT);
+ const WDResult<std::map<std::string, std::string> > * token_result =
+ static_cast<const WDResult<std::map<std::string, std::string> > * > (
+ result);
+ LoadTokensIntoMemory(token_result->GetValue(), &token_map_);
+ }
+}
+
+// Load tokens from the db_token map into the in memory token map.
+void TokenService::LoadTokensIntoMemory(
+ const std::map<std::string, std::string>& db_tokens,
+ std::map<std::string, std::string>* in_memory_tokens) {
+
+ for (int i = 0; i < kNumServices; i++) {
+ // OnIssueAuthTokenSuccess should come from the same thread.
+ // If a token is already present in the map, it could only have
+ // come from a DB read or from IssueAuthToken. Since we should never
+ // fetch from the DB twice in a browser session, it must be from
+ // OnIssueAuthTokenSuccess, which is a live fetcher.
+ //
+ // Network fetched tokens take priority over DB tokens, so exclude tokens
+ // which have already been loaded by the fetcher.
+ if (!in_memory_tokens->count(kServices[i]) &&
+ db_tokens.count(kServices[i])) {
+ std::string db_token = db_tokens.find(kServices[i])->second;
+ if (!db_token.empty()) {
+ LOG(INFO) << "Loading " << kServices[i] << "token from DB:"
+ << db_token;
+ (*in_memory_tokens)[kServices[i]] = db_token;
+ FireTokenAvailableNotification(kServices[i], db_token);
+ // Failures are only for network errors.
+ }
+ }
+ }
+}
diff --git a/chrome/browser/net/gaia/token_service.h b/chrome/browser/net/gaia/token_service.h
index cf13699..42cb0ea 100644
--- a/chrome/browser/net/gaia/token_service.h
+++ b/chrome/browser/net/gaia/token_service.h
@@ -3,7 +3,33 @@
// found in the LICENSE file.
//
// The TokenService will supply authentication tokens for any service that
-// needs it, such as sync.
+// needs it, such as sync. Whenever the user logs in, a controller watching
+// the token service is expected to call ClientLogin to derive a new SID and
+// LSID. Whenever such credentials are available, the TokenService should be
+// updated with new credentials. The controller should then start fetching
+// tokens, which will be written to the database after retrieval, as well as
+// provided to listeners.
+//
+// A token service controller like the ChromiumOS login is expected to:
+//
+// Initialize() // Soon as you can
+// LoadTokensFromDB() // When it's OK to talk to the database
+// UpdateCredentials() // When user logs in
+// StartFetchingTokens() // When it's safe to start fetching
+//
+// Typically a user of the TokenService is expected just to call:
+//
+// if (token_service.HasTokenForService(servicename)) {
+// SetMyToken(token_service.GetTokenForService(servicename));
+// }
+// RegisterSomeObserver(token_service);
+//
+// Whenever a token update occurs:
+// OnTokenAvailable(...) {
+// if (IsServiceICareAbout(notification.service())) {
+// SetMyToken(notification.token())
+// }
+// }
#ifndef CHROME_BROWSER_NET_GAIA_TOKEN_SERVICE_H_
#define CHROME_BROWSER_NET_GAIA_TOKEN_SERVICE_H_
@@ -11,18 +37,22 @@
#include <map>
#include <string>
+#include <vector>
#include "base/scoped_ptr.h"
#include "chrome/common/net/gaia/gaia_auth_consumer.h"
#include "chrome/common/net/gaia/gaia_authenticator2.h"
#include "chrome/test/testing_profile.h"
+#include "base/gtest_prod_util.h"
class URLRequestContextGetter;
// The TokenService is a Profile member, so all calls are expected
// from the UI thread.
-class TokenService : public GaiaAuthConsumer {
+class TokenService : public GaiaAuthConsumer,
+ public WebDataServiceConsumer {
public:
- TokenService() {}
+ TokenService();
+ virtual ~TokenService();
// Notification classes
class TokenAvailableDetails {
@@ -52,45 +82,94 @@ class TokenService : public GaiaAuthConsumer {
};
// Initialize this token service with a request source
- // (usually from a GaiaAuthConsumer constant), a getter and
- // results from ClientLogin.
- void Initialize(
- const char* const source,
- URLRequestContextGetter* getter,
+ // (usually from a GaiaAuthConsumer constant), and the profile.
+ // Typically you'd then update the credentials.
+ void Initialize(const char* const source, Profile* profile);
+
+ // Update the credentials in the token service.
+ // Afterwards you can StartFetchingTokens.
+ void UpdateCredentials(
const GaiaAuthConsumer::ClientLoginResult& credentials);
+ // Terminate any running requests and reset the TokenService to a clean
+ // slate. Resets in memory structures. Does not modify the DB.
+ // When this is done, no tokens will be left in memory and no
+ // user credentials will be left. Useful if a user is logging out.
+ // Initialize doesn't need to be called again but UpdateCredentials does.
+ void ResetCredentialsInMemory();
+
+ // Async load all tokens for services we know of from the DB.
+ // You should do this at startup. Optionally you can do it again
+ // after you reset in memory credentials.
+ void LoadTokensFromDB();
+
+ // Clear all DB stored tokens for the current profile. Tokens may still be
+ // available in memory. If a DB load is pending it may still be serviced.
+ void EraseTokensFromDB();
+
// For legacy services with their own auth routines, they can just read
// the LSID out directly. Deprecated.
- bool HasLsid() const;
+ const bool HasLsid() const;
const std::string& GetLsid() const;
+ // Did we get a proper LSID?
+ const bool AreCredentialsValid() const;
- // On login, StartFetchingTokens() should be called to kick off token
- // fetching.
// Tokens will be fetched for all services(sync, talk) in the background.
- // Results come back via event channel. Services can also poll.
+ // Results come back via event channel. Services can also poll before events
+ // are issued.
void StartFetchingTokens();
- bool HasTokenForService(const char* const service) const;
+ const bool HasTokenForService(const char* const service) const;
const std::string& GetTokenForService(const char* const service) const;
- // Callbacks from the fetchers.
- void OnIssueAuthTokenSuccess(const std::string& service,
- const std::string& auth_token);
- void OnIssueAuthTokenFailure(const std::string& service,
- const GaiaAuthError& error);
+ // GaiaAuthConsumer implementation.
+ virtual void OnIssueAuthTokenSuccess(const std::string& service,
+ const std::string& auth_token);
+ virtual void OnIssueAuthTokenFailure(const std::string& service,
+ const GaiaAuthError& error);
+
+ // WebDataServiceConsumer implementation.
+ virtual void OnWebDataServiceRequestDone(WebDataService::Handle h,
+ const WDTypedResult* result);
private:
- // Did we get a proper LSID?
- bool AreCredentialsValid() const;
+
+ void FireTokenAvailableNotification(const std::string& service,
+ const std::string& auth_token);
+
+ void FireTokenRequestFailedNotification(const std::string& service,
+ const GaiaAuthError& error);
+
+ void LoadTokensIntoMemory(const std::map<std::string, std::string>& in_toks,
+ std::map<std::string, std::string>* out_toks);
+
+ void SaveAuthTokenToDB(const std::string& service,
+ const std::string& auth_token);
+
+ // Web data service to access tokens from.
+ scoped_refptr<WebDataService> web_data_service_;
+ // Getter to use for fetchers.
+ scoped_refptr<URLRequestContextGetter> getter_;
+ // Request handle to load Gaia tokens from DB.
+ WebDataService::Handle token_loading_query_;
// Gaia request source for Gaia accounting.
std::string source_;
// Credentials from ClientLogin for Issuing auth tokens.
GaiaAuthConsumer::ClientLoginResult credentials_;
- scoped_ptr<GaiaAuthenticator2> sync_token_fetcher_;
- scoped_ptr<GaiaAuthenticator2> talk_token_fetcher_;
+
+ // Size of array of services (must be defined here).
+ static const int kNumServices = 2;
+ // List of services that we're performing operations for.
+ static const char* kServices[kNumServices];
+ // A bunch of fetchers suitable for token issuing. We don't care about
+ // the ordering, nor do we care which is for which service.
+ scoped_ptr<GaiaAuthenticator2> fetchers_[kNumServices];
// Map from service to token.
std::map<std::string, std::string> token_map_;
+ FRIEND_TEST_ALL_PREFIXES(TokenServiceTest, LoadTokensIntoMemoryBasic);
+ FRIEND_TEST_ALL_PREFIXES(TokenServiceTest, LoadTokensIntoMemoryAdvanced);
+
DISALLOW_COPY_AND_ASSIGN(TokenService);
};
diff --git a/chrome/browser/net/gaia/token_service_unittest.cc b/chrome/browser/net/gaia/token_service_unittest.cc
index 0bd7e56..a9c6863 100644
--- a/chrome/browser/net/gaia/token_service_unittest.cc
+++ b/chrome/browser/net/gaia/token_service_unittest.cc
@@ -4,12 +4,17 @@
//
// This file defines a unit test for the profile's token service.
+#include "base/scoped_temp_dir.h"
#include "chrome/browser/net/gaia/token_service.h"
-#include "chrome/common/net/gaia/gaia_authenticator2_unittest.h"
+#include "chrome/browser/password_manager/encryptor.h"
+#include "chrome/browser/webdata/web_data_service.h"
#include "chrome/common/net/gaia/gaia_auth_consumer.h"
+#include "chrome/common/net/gaia/gaia_authenticator2_unittest.h"
#include "chrome/common/net/gaia/gaia_constants.h"
-#include "testing/gtest/include/gtest/gtest.h"
+#include "chrome/common/net/test_url_fetcher_factory.h"
+#include "chrome/test/signaling_task.h"
#include "chrome/test/test_notification_tracker.h"
+#include "testing/gtest/include/gtest/gtest.h"
// TestNotificationTracker doesn't do a deep copy on the notification details.
// We have to in order to read it out, or we have a bad ptr, since the details
@@ -56,27 +61,73 @@ class TokenFailedTracker : public TestNotificationTracker {
class TokenServiceTest : public testing::Test {
public:
- TokenServiceTest() {
+ TokenServiceTest()
+ : ui_thread_(ChromeThread::UI, &message_loop_),
+ db_thread_(ChromeThread::DB) {
+ }
+
+ virtual void SetUp() {
+#if defined(OS_MACOSX)
+ Encryptor::UseMockKeychain(true);
+#endif
credentials_.sid = "sid";
credentials_.lsid = "lsid";
credentials_.token = "token";
credentials_.data = "data";
+ ASSERT_TRUE(temp_.CreateUniqueTempDir());
+ ASSERT_TRUE(db_thread_.Start());
+
+ // Testing profile responsible for deleting the temp dir.
+ profile_.reset(new TestingProfile(temp_.Take()));
+ profile_->CreateWebDataService(false);
+ WaitForDBLoadCompletion();
+
success_tracker_.ListenFor(NotificationType::TOKEN_AVAILABLE,
Source<TokenService>(&service_));
failure_tracker_.ListenFor(NotificationType::TOKEN_REQUEST_FAILED,
Source<TokenService>(&service_));
- service_.Initialize("test", profile_.GetRequestContext(), credentials_);
+ service_.Initialize("test", profile_.get());
+ service_.UpdateCredentials(credentials_);
+
+ URLFetcher::set_factory(NULL);
+ }
+
+ virtual void TearDown() {
+ // You have to destroy the profile before the db_thread_ stops.
+ if (profile_.get()) {
+ profile_.reset(NULL);
+ }
+
+ db_thread_.Stop();
+ MessageLoop::current()->PostTask(FROM_HERE, new MessageLoop::QuitTask);
+ MessageLoop::current()->Run();
}
+ void WaitForDBLoadCompletion() {
+ // The WebDB does all work on the DB thread. This will add an event
+ // to the end of the DB thread, so when we reach this task, all DB
+ // operations should be complete.
+ WaitableEvent done(false, false);
+ ChromeThread::PostTask(
+ ChromeThread::DB, FROM_HERE, new SignalingTask(&done));
+ done.Wait();
+
+ // Notifications should be returned from the DB thread onto the UI thread.
+ message_loop_.RunAllPending();
+ }
+
+ MessageLoopForUI message_loop_;
+ ChromeThread ui_thread_; // Mostly so DCHECKS pass.
+ ChromeThread db_thread_; // WDS on here
+
TokenService service_;
TokenAvailableTracker success_tracker_;
TokenFailedTracker failure_tracker_;
GaiaAuthConsumer::ClientLoginResult credentials_;
-
- private:
- TestingProfile profile_;
+ scoped_ptr<TestingProfile> profile_;
+ ScopedTempDir temp_;
};
TEST_F(TokenServiceTest, SanityCheck) {
@@ -99,7 +150,7 @@ TEST_F(TokenServiceTest, NotificationSuccess) {
TokenService::TokenAvailableDetails details =
success_tracker_.get_last_token_details();
- // MSVC doesn't like this comparison as EQ
+ // MSVC doesn't like this comparison as EQ.
EXPECT_TRUE(details.service() == GaiaConstants::kSyncService);
EXPECT_EQ(details.token(), "token");
}
@@ -116,9 +167,9 @@ TEST_F(TokenServiceTest, NotificationFailed) {
TokenService::TokenRequestFailedDetails details =
failure_tracker_.get_last_token_details();
- // MSVC doesn't like this comparison as EQ
+ // MSVC doesn't like this comparison as EQ.
EXPECT_TRUE(details.service() == GaiaConstants::kSyncService);
- EXPECT_TRUE(details.error() == error); // Struct has no print function
+ EXPECT_TRUE(details.error() == error); // Struct has no print function.
}
TEST_F(TokenServiceTest, OnTokenSuccessUpdate) {
@@ -144,15 +195,54 @@ TEST_F(TokenServiceTest, OnTokenSuccess) {
// result with ClientLogin, it doesn't matter, we should still get it back.
EXPECT_EQ(service_.GetTokenForService(GaiaConstants::kSyncService), "token");
- // Check 2nd service
+ // Check the second service.
service_.OnIssueAuthTokenSuccess(GaiaConstants::kTalkService, "token2");
EXPECT_TRUE(service_.HasTokenForService(GaiaConstants::kTalkService));
EXPECT_EQ(service_.GetTokenForService(GaiaConstants::kTalkService), "token2");
- // Didn't change
+ // It didn't change.
EXPECT_EQ(service_.GetTokenForService(GaiaConstants::kSyncService), "token");
}
+TEST_F(TokenServiceTest, ResetSimple) {
+ service_.OnIssueAuthTokenSuccess(GaiaConstants::kSyncService, "token");
+ EXPECT_TRUE(service_.HasTokenForService(GaiaConstants::kSyncService));
+ EXPECT_TRUE(service_.HasLsid());
+
+ service_.ResetCredentialsInMemory();
+
+ EXPECT_FALSE(service_.HasTokenForService(GaiaConstants::kSyncService));
+ EXPECT_FALSE(service_.HasLsid());
+}
+
+TEST_F(TokenServiceTest, ResetComplex) {
+ TestURLFetcherFactory factory;
+ URLFetcher::set_factory(&factory);
+ service_.StartFetchingTokens();
+ // You have to call delegates by hand with the test fetcher,
+ // Let's pretend only one returned.
+
+ service_.OnIssueAuthTokenSuccess(GaiaConstants::kSyncService, "eraseme");
+ EXPECT_TRUE(service_.HasTokenForService(GaiaConstants::kSyncService));
+ EXPECT_EQ(service_.GetTokenForService(GaiaConstants::kSyncService),
+ "eraseme");
+ EXPECT_FALSE(service_.HasTokenForService(GaiaConstants::kTalkService));
+
+ service_.ResetCredentialsInMemory();
+ EXPECT_FALSE(service_.HasTokenForService(GaiaConstants::kSyncService));
+ EXPECT_FALSE(service_.HasTokenForService(GaiaConstants::kTalkService));
+
+ // Now start using it again.
+ service_.UpdateCredentials(credentials_);
+ service_.StartFetchingTokens();
+
+ service_.OnIssueAuthTokenSuccess(GaiaConstants::kSyncService, "token");
+ service_.OnIssueAuthTokenSuccess(GaiaConstants::kTalkService, "token2");
+
+ EXPECT_EQ(service_.GetTokenForService(GaiaConstants::kSyncService), "token");
+ EXPECT_EQ(service_.GetTokenForService(GaiaConstants::kTalkService), "token2");
+}
+
TEST_F(TokenServiceTest, FullIntegration) {
MockFactory<MockFetcher> factory;
std::string result = "SID=sid\nLSID=lsid\nAuth=auth\n";
@@ -169,4 +259,116 @@ TEST_F(TokenServiceTest, FullIntegration) {
// result with ClientLogin, it doesn't matter, we should still get it back.
EXPECT_EQ(service_.GetTokenForService(GaiaConstants::kSyncService), result);
EXPECT_EQ(service_.GetTokenForService(GaiaConstants::kTalkService), result);
+
+ service_.ResetCredentialsInMemory();
+ EXPECT_FALSE(service_.HasTokenForService(GaiaConstants::kSyncService));
+ EXPECT_FALSE(service_.HasTokenForService(GaiaConstants::kTalkService));
+}
+
+TEST_F(TokenServiceTest, LoadTokensIntoMemoryBasic) {
+ // Validate that the method sets proper data in notifications and map.
+ std::map<std::string, std::string> db_tokens;
+ std::map<std::string, std::string> memory_tokens;
+
+ service_.LoadTokensIntoMemory(db_tokens, &memory_tokens);
+ EXPECT_TRUE(db_tokens.empty());
+ EXPECT_TRUE(memory_tokens.empty());
+ EXPECT_EQ(0U, success_tracker_.size());
+
+ db_tokens[GaiaConstants::kSyncService] = "token";
+ service_.LoadTokensIntoMemory(db_tokens, &memory_tokens);
+ EXPECT_EQ(1U, success_tracker_.size());
+
+ TokenService::TokenAvailableDetails details =
+ success_tracker_.get_last_token_details();
+ // MSVC doesn't like this comparison as EQ.
+ EXPECT_TRUE(details.service() == GaiaConstants::kSyncService);
+ EXPECT_EQ(details.token(), "token");
+ EXPECT_EQ(1U, memory_tokens.count(GaiaConstants::kSyncService));
+ EXPECT_EQ(memory_tokens[GaiaConstants::kSyncService], "token");
+}
+
+TEST_F(TokenServiceTest, LoadTokensIntoMemoryAdvanced) {
+ // LoadTokensIntoMemory should avoid setting tokens already in the
+ // token map.
+ std::map<std::string, std::string> db_tokens;
+ std::map<std::string, std::string> memory_tokens;
+
+ db_tokens["ignore"] = "token";
+
+ service_.LoadTokensIntoMemory(db_tokens, &memory_tokens);
+ EXPECT_TRUE(memory_tokens.empty());
+ db_tokens[GaiaConstants::kSyncService] = "pepper";
+
+ service_.LoadTokensIntoMemory(db_tokens, &memory_tokens);
+ EXPECT_EQ(1U, memory_tokens.count(GaiaConstants::kSyncService));
+ EXPECT_EQ(memory_tokens[GaiaConstants::kSyncService], "pepper");
+ EXPECT_EQ(1U, success_tracker_.size());
+ success_tracker_.Reset();
+
+ // SyncService token is already in memory. Pretend we got it off
+ // the disk as well, but an older token.
+ db_tokens[GaiaConstants::kSyncService] = "ignoreme";
+ db_tokens[GaiaConstants::kTalkService] = "tomato";
+ service_.LoadTokensIntoMemory(db_tokens, &memory_tokens);
+
+ EXPECT_EQ(2U, memory_tokens.size());
+ EXPECT_EQ(1U, memory_tokens.count(GaiaConstants::kTalkService));
+ EXPECT_EQ(memory_tokens[GaiaConstants::kTalkService], "tomato");
+ EXPECT_EQ(1U, success_tracker_.size());
+ EXPECT_EQ(1U, memory_tokens.count(GaiaConstants::kSyncService));
+ EXPECT_EQ(memory_tokens[GaiaConstants::kSyncService], "pepper");
+}
+
+TEST_F(TokenServiceTest, WebDBLoadIntegration) {
+ service_.LoadTokensFromDB();
+ WaitForDBLoadCompletion();
+ EXPECT_EQ(0U, success_tracker_.size());
+
+ // Should result in DB write.
+ service_.OnIssueAuthTokenSuccess(GaiaConstants::kSyncService, "token");
+ EXPECT_EQ(1U, success_tracker_.size());
+
+ EXPECT_TRUE(service_.HasTokenForService(GaiaConstants::kSyncService));
+ // Clean slate.
+ service_.ResetCredentialsInMemory();
+ success_tracker_.Reset();
+ EXPECT_FALSE(service_.HasTokenForService(GaiaConstants::kSyncService));
+
+ service_.LoadTokensFromDB();
+ WaitForDBLoadCompletion();
+
+ EXPECT_EQ(1U, success_tracker_.size());
+ EXPECT_TRUE(service_.HasTokenForService(GaiaConstants::kSyncService));
+ EXPECT_FALSE(service_.HasTokenForService(GaiaConstants::kTalkService));
+ EXPECT_FALSE(service_.HasLsid());
+}
+
+TEST_F(TokenServiceTest, MultipleLoadResetIntegration) {
+ // Should result in DB write.
+ service_.OnIssueAuthTokenSuccess(GaiaConstants::kSyncService, "token");
+ service_.ResetCredentialsInMemory();
+ success_tracker_.Reset();
+ EXPECT_FALSE(service_.HasTokenForService(GaiaConstants::kSyncService));
+
+ service_.LoadTokensFromDB();
+ WaitForDBLoadCompletion();
+
+ service_.LoadTokensFromDB(); // Should do nothing.
+ WaitForDBLoadCompletion();
+
+ EXPECT_EQ(1U, success_tracker_.size());
+ EXPECT_TRUE(service_.HasTokenForService(GaiaConstants::kSyncService));
+ EXPECT_FALSE(service_.HasTokenForService(GaiaConstants::kTalkService));
+ EXPECT_FALSE(service_.HasLsid());
+
+ // Reset it one more time so there's no surprises.
+ service_.ResetCredentialsInMemory();
+ success_tracker_.Reset();
+
+ service_.LoadTokensFromDB();
+ WaitForDBLoadCompletion();
+
+ EXPECT_EQ(1U, success_tracker_.size());
+ EXPECT_TRUE(service_.HasTokenForService(GaiaConstants::kSyncService));
}
diff --git a/chrome/browser/password_manager/password_store_default_unittest.cc b/chrome/browser/password_manager/password_store_default_unittest.cc
index f5629a4..7b4ef0b 100644
--- a/chrome/browser/password_manager/password_store_default_unittest.cc
+++ b/chrome/browser/password_manager/password_store_default_unittest.cc
@@ -14,6 +14,7 @@
#include "chrome/browser/webdata/web_data_service.h"
#include "chrome/common/notification_service.h"
#include "chrome/common/pref_names.h"
+#include "chrome/test/signaling_task.h"
#include "chrome/test/testing_profile.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -41,17 +42,6 @@ class MockWebDataServiceConsumer : public WebDataServiceConsumer {
const WDTypedResult*));
};
-class SignalingTask : public Task {
- public:
- explicit SignalingTask(WaitableEvent* event) : event_(event) {
- }
- virtual void Run() {
- event_->Signal();
- }
- private:
- WaitableEvent* event_;
-};
-
class MockNotificationObserver : public NotificationObserver {
public:
MOCK_METHOD3(Observe, void(NotificationType,
@@ -243,7 +233,8 @@ TEST_F(PasswordStoreDefaultTest, Migration) {
// Initializing the PasswordStore should trigger a migration.
scoped_refptr<PasswordStoreDefault> store(
- new PasswordStoreDefault(login_db_.release(), profile_.get(), wds_.get()));
+ new PasswordStoreDefault(login_db_.release(),
+ profile_.get(), wds_.get()));
store->Init();
// Check that the migration preference has not been initialized;
diff --git a/chrome/browser/password_manager/password_store_win_unittest.cc b/chrome/browser/password_manager/password_store_win_unittest.cc
index 987c95e..6bdea02 100644
--- a/chrome/browser/password_manager/password_store_win_unittest.cc
+++ b/chrome/browser/password_manager/password_store_win_unittest.cc
@@ -17,6 +17,7 @@
#include "chrome/browser/password_manager/password_store_win.h"
#include "chrome/browser/password_manager/ie7_password.h"
#include "chrome/common/pref_names.h"
+#include "chrome/test/signaling_task.h"
#include "chrome/test/testing_profile.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -41,17 +42,6 @@ public:
void(WebDataService::Handle, const WDTypedResult*));
};
-class SignalingTask : public Task {
- public:
- explicit SignalingTask(WaitableEvent* event) : event_(event) {
- }
- virtual void Run() {
- event_->Signal();
- }
- private:
- WaitableEvent* event_;
-};
-
} // anonymous namespace
typedef std::vector<PasswordForm*> VectorOfForms;
diff --git a/chrome/browser/password_manager/password_store_x_unittest.cc b/chrome/browser/password_manager/password_store_x_unittest.cc
index a172503..b8f56db 100644
--- a/chrome/browser/password_manager/password_store_x_unittest.cc
+++ b/chrome/browser/password_manager/password_store_x_unittest.cc
@@ -14,6 +14,7 @@
#include "chrome/browser/webdata/web_data_service.h"
#include "chrome/common/notification_service.h"
#include "chrome/common/pref_names.h"
+#include "chrome/test/signaling_task.h"
#include "chrome/test/testing_profile.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -43,17 +44,6 @@ class MockWebDataServiceConsumer : public WebDataServiceConsumer {
const WDTypedResult*));
};
-class SignalingTask : public Task {
- public:
- explicit SignalingTask(WaitableEvent* event) : event_(event) {
- }
- virtual void Run() {
- event_->Signal();
- }
- private:
- WaitableEvent* event_;
-};
-
class MockNotificationObserver : public NotificationObserver {
public:
MOCK_METHOD3(Observe, void(NotificationType,
diff --git a/chrome/browser/sync/profile_sync_service_startup_unittest.cc b/chrome/browser/sync/profile_sync_service_startup_unittest.cc
index f49354d..9d915c2 100644
--- a/chrome/browser/sync/profile_sync_service_startup_unittest.cc
+++ b/chrome/browser/sync/profile_sync_service_startup_unittest.cc
@@ -204,8 +204,8 @@ TEST_F(ProfileSyncServiceStartupBootstrapTest, SKIP_MACOSX(StartFirstTime)) {
result.sid = "sid";
result.lsid = "lsid";
profile_.GetTokenService()->Initialize("test",
- profile_.GetRequestContext(),
- result);
+ &profile_);
+ profile_.GetTokenService()->UpdateCredentials(result);
// Will start sync even though setup hasn't been completed (since
// setup is bypassed when bootstrapping is enabled).
diff --git a/chrome/browser/webdata/web_data_service.cc b/chrome/browser/webdata/web_data_service.cc
index 4b7f93e..384128d 100644
--- a/chrome/browser/webdata/web_data_service.cc
+++ b/chrome/browser/webdata/web_data_service.cc
@@ -190,6 +190,46 @@ WebDataService::Handle WebDataService::GetWebAppImages(
////////////////////////////////////////////////////////////////////////////////
//
+// Token Service
+//
+////////////////////////////////////////////////////////////////////////////////
+
+void WebDataService::SetTokenForService(const std::string& service,
+ const std::string& token) {
+ GenericRequest2<std::string, std::string>* request =
+ new GenericRequest2<std::string, std::string>(
+ this, GetNextRequestHandle(), NULL, service, token);
+ RegisterRequest(request);
+ ScheduleTask(NewRunnableMethod(this, &WebDataService::SetTokenForServiceImpl,
+ request));
+}
+
+void WebDataService::RemoveAllTokens() {
+ GenericRequest<std::string>* request =
+ new GenericRequest<std::string>(
+ this, GetNextRequestHandle(), NULL, std::string());
+ RegisterRequest(request);
+ ScheduleTask(NewRunnableMethod(this,
+ &WebDataService::RemoveAllTokensImpl,
+ request));
+}
+
+// Null on failure. Success is WDResult<std::string>
+WebDataService::Handle WebDataService::GetAllTokens(
+ WebDataServiceConsumer* consumer) {
+
+ GenericRequest<std::string>* request =
+ new GenericRequest<std::string>(
+ this, GetNextRequestHandle(), consumer, std::string());
+ RegisterRequest(request);
+ ScheduleTask(NewRunnableMethod(this,
+ &WebDataService::GetAllTokensImpl,
+ request));
+ return request->GetHandle();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
// Password manager.
//
////////////////////////////////////////////////////////////////////////////////
@@ -687,6 +727,49 @@ void WebDataService::GetWebAppImagesImpl(GenericRequest<GURL>* request) {
////////////////////////////////////////////////////////////////////////////////
//
+// Token Service implementation.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+// argument std::string is unused
+void WebDataService::RemoveAllTokensImpl(
+ GenericRequest<std::string>* request) {
+ InitializeDatabaseIfNecessary();
+ if (db_ && !request->IsCancelled()) {
+ if (db_->RemoveAllTokens()) {
+ ScheduleCommit();
+ }
+ }
+ request->RequestComplete();
+}
+
+void WebDataService::SetTokenForServiceImpl(
+ GenericRequest2<std::string, std::string>* request) {
+ InitializeDatabaseIfNecessary();
+ if (db_ && !request->IsCancelled()) {
+ if (db_->SetTokenForService(request->GetArgument1(),
+ request->GetArgument2())) {
+ ScheduleCommit();
+ }
+ }
+ request->RequestComplete();
+}
+
+// argument is unused
+void WebDataService::GetAllTokensImpl(
+ GenericRequest<std::string>* request) {
+ InitializeDatabaseIfNecessary();
+ if (db_ && !request->IsCancelled()) {
+ std::map<std::string, std::string> map;
+ db_->GetAllTokens(&map);
+ request->SetResult(
+ new WDResult<std::map<std::string, std::string> >(TOKEN_RESULT, map));
+ }
+ request->RequestComplete();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
// Password manager implementation.
//
////////////////////////////////////////////////////////////////////////////////
diff --git a/chrome/browser/webdata/web_data_service.h b/chrome/browser/webdata/web_data_service.h
index 63cd3b29..120fdc4 100644
--- a/chrome/browser/webdata/web_data_service.h
+++ b/chrome/browser/webdata/web_data_service.h
@@ -64,6 +64,7 @@ typedef enum {
PASSWORD_IE7_RESULT, // WDResult<IE7PasswordInfo>
#endif
WEB_APP_IMAGES, // WDResult<WDAppImagesResult>
+ TOKEN_RESULT, // WDResult<std::vector<std::string>>
AUTOFILL_VALUE_RESULT, // WDResult<std::vector<string16>>
AUTOFILL_CHANGES, // WDResult<std::vector<AutofillChange>>
AUTOFILL_PROFILE_RESULT, // WDResult<AutoFillProfile>
@@ -331,6 +332,22 @@ class WebDataService
//////////////////////////////////////////////////////////////////////////////
//
+ // Token Service
+ //
+ //////////////////////////////////////////////////////////////////////////////
+
+ // Set a token to use for a specified service.
+ void SetTokenForService(const std::string& service,
+ const std::string& token);
+
+ // Remove all tokens stored in the web database.
+ void RemoveAllTokens();
+
+ // Null on failure. Success is WDResult<std::vector<std::string> >
+ Handle GetAllTokens(WebDataServiceConsumer* consumer);
+
+ //////////////////////////////////////////////////////////////////////////////
+ //
// Password manager
// NOTE: These methods are all deprecated; new clients should use
// PasswordStore. These are only still here because Windows is (temporarily)
@@ -529,6 +546,17 @@ class WebDataService
//////////////////////////////////////////////////////////////////////////////
//
+ // Token Service.
+ //
+ //////////////////////////////////////////////////////////////////////////////
+
+ void RemoveAllTokensImpl(GenericRequest<std::string>* request);
+ void SetTokenForServiceImpl(
+ GenericRequest2<std::string, std::string>* request);
+ void GetAllTokensImpl(GenericRequest<std::string>* request);
+
+ //////////////////////////////////////////////////////////////////////////////
+ //
// Password manager.
//
//////////////////////////////////////////////////////////////////////////////
diff --git a/chrome/browser/webdata/web_database.cc b/chrome/browser/webdata/web_database.cc
index 948dc8c..005c8b2 100644
--- a/chrome/browser/webdata/web_database.cc
+++ b/chrome/browser/webdata/web_database.cc
@@ -444,7 +444,7 @@ sql::InitStatus WebDatabase::Init(const FilePath& db_name) {
if (!InitKeywordsTable() || !InitLoginsTable() || !InitWebAppIconsTable() ||
!InitWebAppsTable() || !InitAutofillTable() ||
!InitAutofillDatesTable() || !InitAutoFillProfilesTable() ||
- !InitCreditCardsTable()) {
+ !InitCreditCardsTable() || !InitTokenServiceTable()) {
LOG(WARNING) << "Unable to initialize the web database.";
return sql::INIT_FAILURE;
}
@@ -545,6 +545,68 @@ bool WebDatabase::RemoveWebApp(const GURL& url) {
return delete_s2.Run();
}
+bool WebDatabase::RemoveAllTokens() {
+ sql::Statement s(db_.GetUniqueStatement(
+ "DELETE FROM token_service"));
+ if (!s) {
+ NOTREACHED() << "Statement prepare failed";
+ return false;
+ }
+
+ return s.Run();
+}
+
+bool WebDatabase::SetTokenForService(const std::string& service,
+ const std::string& token) {
+ // Don't bother with a cached statement since this will be a relatively
+ // infrequent operation.
+ sql::Statement s(db_.GetUniqueStatement(
+ "INSERT OR REPLACE INTO token_service "
+ "(service, encrypted_token) VALUES (?, ?)"));
+ if (!s) {
+ NOTREACHED() << "Statement prepare failed";
+ return false;
+ }
+
+ std::string encrypted_token;
+
+ bool encrypted = Encryptor::EncryptString(token, &encrypted_token);
+ if (!encrypted) {
+ return false;
+ }
+
+ s.BindString(0, service);
+ s.BindBlob(1, encrypted_token.data(),
+ static_cast<int>(encrypted_token.length()));
+ return s.Run();
+}
+
+bool WebDatabase::GetAllTokens(std::map<std::string, std::string>* tokens) {
+ sql::Statement s(db_.GetUniqueStatement(
+ "SELECT service, encrypted_token FROM token_service"));
+ if (!s) {
+ NOTREACHED() << "Statement prepare failed";
+ return false;
+ }
+
+ while (s.Step()) {
+ std::string encrypted_token;
+ std::string decrypted_token;
+ std::string service;
+ service = s.ColumnString(0);
+ bool entry_ok = !service.empty() &&
+ s.ColumnBlobAsString(1, &encrypted_token);
+ if (entry_ok) {
+ Encryptor::DecryptString(encrypted_token, &decrypted_token);
+ (*tokens)[service] = decrypted_token;
+ } else {
+ NOTREACHED();
+ return false;
+ }
+ }
+ return true;
+}
+
bool WebDatabase::InitKeywordsTable() {
if (!db_.DoesTableExist("keywords")) {
if (!db_.Execute("CREATE TABLE keywords ("
@@ -748,6 +810,18 @@ bool WebDatabase::InitWebAppsTable() {
return true;
}
+bool WebDatabase::InitTokenServiceTable() {
+ if (!db_.DoesTableExist("token_service")) {
+ if (!db_.Execute("CREATE TABLE token_service ("
+ "service VARCHAR PRIMARY KEY NOT NULL,"
+ "encrypted_token BLOB)")) {
+ NOTREACHED();
+ return false;
+ }
+ }
+ return true;
+}
+
bool WebDatabase::AddKeyword(const TemplateURL& url) {
DCHECK(url.id());
sql::Statement s(db_.GetCachedStatement(SQL_FROM_HERE,
diff --git a/chrome/browser/webdata/web_database.h b/chrome/browser/webdata/web_database.h
index ca24cf9..1a8d83b8 100644
--- a/chrome/browser/webdata/web_database.h
+++ b/chrome/browser/webdata/web_database.h
@@ -273,6 +273,26 @@ class WebDatabase {
bool RemoveWebApp(const GURL& url);
+ //////////////////////////////////////////////////////////////////////////////
+ //
+ // Token Service
+ //
+ //////////////////////////////////////////////////////////////////////////////
+
+ // Remove all tokens previously set with SetTokenForService.
+ bool RemoveAllTokens();
+
+ // Retrives all tokens previously set with SetTokenForService.
+ // Returns true if there were tokens and we decrypted them,
+ // false if there was a failure somehow
+ bool GetAllTokens(std::map<std::string, std::string>* tokens);
+
+ // Store a token in the token_service table. Stored encrypted. May cause
+ // a mac keychain popup.
+ // True if we encrypted a token and stored it, false otherwise.
+ bool SetTokenForService(const std::string& service,
+ const std::string& token);
+
private:
FRIEND_TEST_ALL_PREFIXES(WebDatabaseTest, Autofill);
FRIEND_TEST_ALL_PREFIXES(WebDatabaseTest, Autofill_AddChanges);
@@ -310,6 +330,7 @@ class WebDatabase {
bool InitAutofillDatesTable();
bool InitAutoFillProfilesTable();
bool InitCreditCardsTable();
+ bool InitTokenServiceTable();
bool InitWebAppIconsTable();
bool InitWebAppsTable();
diff --git a/chrome/browser/webdata/web_database_unittest.cc b/chrome/browser/webdata/web_database_unittest.cc
index 566d042..25109a7 100644
--- a/chrome/browser/webdata/web_database_unittest.cc
+++ b/chrome/browser/webdata/web_database_unittest.cc
@@ -1148,6 +1148,66 @@ TEST_F(WebDatabaseTest, WebAppImages) {
}
}
+TEST_F(WebDatabaseTest, TokenServiceGetAllRemoveAll) {
+ WebDatabase db;
+ ASSERT_EQ(sql::INIT_OK, db.Init(file_));
+
+ std::map<std::string, std::string> out_map;
+ std::string service;
+ std::string service2;
+ service = "testservice";
+ service2 = "othertestservice";
+
+ EXPECT_TRUE(db.GetAllTokens(&out_map));
+ EXPECT_TRUE(out_map.empty());
+
+ // Check that get all tokens works
+ EXPECT_TRUE(db.SetTokenForService(service, "pepperoni"));
+ EXPECT_TRUE(db.SetTokenForService(service2, "steak"));
+ EXPECT_TRUE(db.GetAllTokens(&out_map));
+ EXPECT_EQ(out_map.find(service)->second, "pepperoni");
+ EXPECT_EQ(out_map.find(service2)->second, "steak");
+ out_map.clear();
+
+ // Purge
+ EXPECT_TRUE(db.RemoveAllTokens());
+ EXPECT_TRUE(db.GetAllTokens(&out_map));
+ EXPECT_TRUE(out_map.empty());
+
+ // Check that you can still add it back in
+ EXPECT_TRUE(db.SetTokenForService(service, "cheese"));
+ EXPECT_TRUE(db.GetAllTokens(&out_map));
+ EXPECT_EQ(out_map.find(service)->second, "cheese");
+}
+
+TEST_F(WebDatabaseTest, TokenServiceGetSet) {
+ WebDatabase db;
+ ASSERT_EQ(sql::INIT_OK, db.Init(file_));
+
+ std::map<std::string, std::string> out_map;
+ std::string service;
+ service = "testservice";
+
+ EXPECT_TRUE(db.GetAllTokens(&out_map));
+ EXPECT_TRUE(out_map.empty());
+
+ EXPECT_TRUE(db.SetTokenForService(service, "pepperoni"));
+ EXPECT_TRUE(db.GetAllTokens(&out_map));
+ EXPECT_EQ(out_map.find(service)->second, "pepperoni");
+ out_map.clear();
+
+ // try blanking it - won't remove it from the db though!
+ EXPECT_TRUE(db.SetTokenForService(service, ""));
+ EXPECT_TRUE(db.GetAllTokens(&out_map));
+ EXPECT_EQ(out_map.find(service)->second, "");
+ out_map.clear();
+
+ // try mutating it
+ EXPECT_TRUE(db.SetTokenForService(service, "ham"));
+ EXPECT_TRUE(db.GetAllTokens(&out_map));
+ EXPECT_EQ(out_map.find(service)->second, "ham");
+}
+
TEST_F(WebDatabaseTest, AutoFillProfile) {
WebDatabase db;