summaryrefslogtreecommitdiffstats
path: root/chrome/browser/sync/util/user_settings.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/sync/util/user_settings.cc')
-rw-r--r--chrome/browser/sync/util/user_settings.cc350
1 files changed, 350 insertions, 0 deletions
diff --git a/chrome/browser/sync/util/user_settings.cc b/chrome/browser/sync/util/user_settings.cc
new file mode 100644
index 0000000..573365a
--- /dev/null
+++ b/chrome/browser/sync/util/user_settings.cc
@@ -0,0 +1,350 @@
+// 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.
+
+// This class isn't pretty. It's just a step better than globals, which is what
+// these were previously.
+
+#include "chrome/browser/sync/util/user_settings.h"
+
+#if defined(OS_WINDOWS)
+#include <windows.h>
+#endif
+
+#include <string>
+#include <limits>
+#include <vector>
+
+#include "base/file_util.h"
+#include "base/string_util.h"
+#include "chrome/browser/sync/syncable/directory_manager.h" // For migration.
+#include "chrome/browser/sync/util/crypto_helpers.h"
+#include "chrome/browser/sync/util/data_encryption.h"
+#include "chrome/browser/sync/util/path_helpers.h"
+#include "chrome/browser/sync/util/query_helpers.h"
+
+using std::numeric_limits;
+using std::string;
+using std::vector;
+
+using syncable::DirectoryManager;
+
+namespace browser_sync {
+
+static const char PASSWORD_HASH[] = "password_hash2";
+static const char SALT[] = "salt2";
+
+static const int kSaltSize = 20;
+static const int kCurrentDBVersion = 11;
+
+UserSettings::ScopedDBHandle::ScopedDBHandle(UserSettings* settings) :
+ mutex_lock_(&settings->dbhandle_mutex_), handle_(&settings->dbhandle_) {
+}
+
+UserSettings::UserSettings() :
+ dbhandle_(NULL) {
+}
+
+string UserSettings::email() const {
+ ScopedLock lock(&mutex_);
+ return email_;
+}
+
+static void MakeSigninsTable(sqlite3* const dbhandle) {
+ // Multiple email addresses can map to the same Google Account.
+ // This table keeps a map of sign-in email addresses to primary
+ // Google Account email addresses.
+ ExecOrDie(dbhandle, "CREATE TABLE signins"
+ " (signin, primary_email, "
+ " PRIMARY KEY(signin, primary_email) ON CONFLICT REPLACE)");
+}
+
+void UserSettings::MigrateOldVersionsAsNeeded(sqlite3* const handle,
+ int current_version) {
+ switch (current_version) {
+ // Versions 1-9 are unhandled. Version numbers greater than
+ // kCurrentDBVersion should have already been weeded out by the caller.
+ default:
+ // When the version is too old, we just try to continue anyway. There
+ // should not be a released product that makes a database too old for us
+ // to handle.
+ LOG(WARNING) << "UserSettings database version " << current_version <<
+ " is too old to handle.";
+ return;
+ case 10:
+ {
+ // Scrape the 'shares' table to find the syncable DB. 'shares'
+ // had a pair of string columns that mapped the username to the filename
+ // of the sync data sqlite3 file. Version 11 switched to a constant
+ // filename, so here we read the string, copy the file to the new name,
+ // delete the old one, and then drop the unused shares table.
+ ScopedStatement share_query(PrepareQuery(handle,
+ "SELECT share_name, file_name FROM shares"));
+ int query_result = sqlite3_step(share_query.get());
+ CHECK(SQLITE_ROW == query_result);
+ PathString share_name, file_name;
+ GetColumn(share_query.get(), 0, &share_name);
+ GetColumn(share_query.get(), 1, &file_name);
+
+ if (!file_util::Move(file_name,
+ DirectoryManager::GetSyncDataDatabaseFilename())) {
+ LOG(WARNING) << "Unable to upgrade UserSettings from v10";
+ return;
+ }
+ }
+ ExecOrDie(handle, "DROP TABLE shares");
+ ExecOrDie(handle, "UPDATE db_version SET version = 11");
+ // FALL THROUGH
+ case kCurrentDBVersion:
+ // Nothing to migrate.
+ return;
+ }
+}
+
+static void MakeCookiesTable(sqlite3* const dbhandle) {
+ // This table keeps a list of auth tokens for each signed in account. There
+ // will be as many rows as there are auth tokens per sign in.
+ // The service_token column will store encrypted values.
+ ExecOrDie(dbhandle, "CREATE TABLE cookies"
+ " (email, service_name, service_token, "
+ " PRIMARY KEY(email, service_name) ON CONFLICT REPLACE)");
+}
+
+static void MakeSigninTypesTable(sqlite3* const dbhandle) {
+ // With every successful gaia authentication, remember if it was
+ // a hosted domain or not.
+ ExecOrDie(dbhandle, "CREATE TABLE signin_types"
+ " (signin, signin_type, "
+ " PRIMARY KEY(signin, signin_type) ON CONFLICT REPLACE)");
+}
+
+static void MakeClientIDTable(sqlite3* const dbhandle) {
+ // Stores a single client ID value that can be used as the client id,
+ // if there's not another such ID provided on the install.
+ ExecOrDie(dbhandle, "CREATE TABLE client_id (id) ");
+ ExecOrDie(dbhandle, "INSERT INTO client_id values ( ? )",
+ Generate128BitRandomHexString());
+}
+
+bool UserSettings::Init(const PathString& settings_path) {
+ { // Scope the handle
+ ScopedDBHandle dbhandle(this);
+ if (dbhandle_)
+ sqlite3_close(dbhandle_);
+ CHECK(SQLITE_OK == SqliteOpen(settings_path.c_str(), &dbhandle_));
+ // In the worst case scenario, the user may hibernate his computer during
+ // one of our transactions.
+ sqlite3_busy_timeout(dbhandle_, numeric_limits<int>::max());
+
+ int sqlite_result = Exec(dbhandle.get(), "BEGIN EXCLUSIVE TRANSACTION");
+ CHECK(SQLITE_DONE == sqlite_result);
+ ScopedStatement table_query(PrepareQuery(dbhandle.get(),
+ "select count(*) from sqlite_master where type = 'table'"
+ " and name = 'db_version'"));
+ int query_result = sqlite3_step(table_query.get());
+ CHECK(SQLITE_ROW == query_result);
+ int table_count = 0;
+ GetColumn(table_query.get(), 0, &table_count);
+ table_query.reset(NULL);
+ if (table_count > 0) {
+ ScopedStatement version_query(PrepareQuery(dbhandle.get(),
+ "SELECT version FROM db_version"));
+ query_result = sqlite3_step(version_query.get());
+ CHECK(SQLITE_ROW == query_result);
+ const int version = sqlite3_column_int(version_query.get(), 0);
+ version_query.reset(NULL);
+ if (version > kCurrentDBVersion) {
+ LOG(WARNING) << "UserSettings database is too new.";
+ return false;
+ }
+
+ MigrateOldVersionsAsNeeded(dbhandle.get(), version);
+ } else {
+ // Create settings table.
+ ExecOrDie(dbhandle.get(), "CREATE TABLE settings"
+ " (email, key, value, "
+ " PRIMARY KEY(email, key) ON CONFLICT REPLACE)");
+
+ // Create and populate version table.
+ ExecOrDie(dbhandle.get(), "CREATE TABLE db_version ( version )");
+ ExecOrDie(dbhandle.get(), "INSERT INTO db_version values ( ? )",
+ kCurrentDBVersion);
+
+ MakeSigninsTable(dbhandle.get());
+ MakeCookiesTable(dbhandle.get());
+ MakeSigninTypesTable(dbhandle.get());
+ MakeClientIDTable(dbhandle.get());
+ }
+ ExecOrDie(dbhandle.get(), "COMMIT TRANSACTION");
+ }
+#ifdef OS_WINDOWS
+ // Do not index this file. Scanning can occur every time we close the file,
+ // which causes long delays in SQLite's file locking.
+ const DWORD attrs = GetFileAttributes(settings_path.c_str());
+ const BOOL attrs_set =
+ SetFileAttributes(settings_path.c_str(),
+ attrs | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED);
+#endif
+ return true;
+}
+
+
+UserSettings::~UserSettings() {
+ if (dbhandle_)
+ sqlite3_close(dbhandle_);
+}
+
+const int32 kInvalidHash = 0xFFFFFFFF;
+
+// We use 10 bits of data from the MD5 digest as the hash.
+const int32 kHashMask = 0x3FF;
+
+int32 GetHashFromDigest(const vector<uint8>& digest) {
+ int32 hash = 0;
+ int32 mask = kHashMask;
+ for (vector<uint8>::const_iterator i = digest.begin(); i != digest.end();
+ ++i) {
+ hash = hash << 8;
+ hash = hash | (*i & kHashMask);
+ mask = mask >> 8;
+ if (0 == mask)
+ break;
+ }
+ return hash;
+}
+
+void UserSettings::StoreEmailForSignin(const string& signin,
+ const string& primary_email) {
+ ScopedDBHandle dbhandle(this);
+ ExecOrDie(dbhandle.get(), "BEGIN TRANSACTION");
+ ScopedStatement query(PrepareQuery(dbhandle.get(),
+ "SELECT COUNT(*) FROM signins"
+ " WHERE signin = ? AND primary_email = ?",
+ signin, primary_email));
+ int query_result = sqlite3_step(query.get());
+ CHECK(SQLITE_ROW == query_result);
+ int32 count = 0;
+ GetColumn(query.get(), 0, &count);
+ query.reset(NULL);
+ if (0 == count) {
+ // Migrate any settings the user might have from earlier versions.
+ ExecOrDie(dbhandle.get(), "UPDATE settings SET email = ? WHERE email = ?",
+ primary_email, signin);
+ // Store this signin:email mapping.
+ ExecOrDie(dbhandle.get(), "INSERT INTO signins(signin, primary_email)"
+ " values ( ?, ? )", signin, primary_email);
+ }
+ ExecOrDie(dbhandle.get(), "COMMIT TRANSACTION");
+}
+
+bool UserSettings::GetEmailForSignin(/*in, out*/string* signin) {
+ ScopedDBHandle dbhandle(this);
+ string result;
+ ScopedStatement query(PrepareQuery(dbhandle.get(),
+ "SELECT primary_email FROM signins"
+ " WHERE signin = ?", *signin));
+ int query_result = sqlite3_step(query.get());
+ if (SQLITE_ROW == query_result) {
+ GetColumn(query.get(), 0, &result);
+ if (!result.empty()) {
+ swap(result, *signin);
+ return true;
+ }
+ }
+ return false;
+}
+
+void UserSettings::StoreHashedPassword(const string& email,
+ const string& password) {
+ // Save one-way hashed password:
+ char binary_salt[kSaltSize];
+ {
+ ScopedLock lock(&mutex_);
+ GetRandomBytes(binary_salt, sizeof(binary_salt));
+ }
+ const string salt = APEncode(string(binary_salt, sizeof(binary_salt)));
+ MD5Calculator md5;
+ md5.AddData(salt.data(), salt.size());
+ md5.AddData(password.data(), password.size());
+ ScopedDBHandle dbhandle(this);
+ ExecOrDie(dbhandle.get(), "BEGIN TRANSACTION");
+ ExecOrDie(dbhandle.get(), "INSERT INTO settings(email, key, value )"
+ " values ( ?, ?, ? )", email, PASSWORD_HASH,
+ GetHashFromDigest(md5.GetDigest()));
+ ExecOrDie(dbhandle.get(), "INSERT INTO settings(email, key, value )"
+ " values ( ?, ?, ? )", email, SALT, salt);
+ ExecOrDie(dbhandle.get(), "COMMIT TRANSACTION");
+}
+
+bool UserSettings::VerifyAgainstStoredHash(const string& email,
+ const string& password) {
+ ScopedDBHandle dbhandle(this);
+ string salt_and_digest;
+
+ ScopedStatement query(PrepareQuery(dbhandle.get(),
+ "SELECT key, value FROM settings"
+ " WHERE email = ? AND"
+ " (key = ? OR key = ?)",
+ email, PASSWORD_HASH, SALT));
+ int query_result = sqlite3_step(query.get());
+ string salt;
+ int32 hash = kInvalidHash;
+ while (SQLITE_ROW == query_result) {
+ string key;
+ GetColumn(query.get(), 0, &key);
+ if (key == SALT)
+ GetColumn(query.get(), 1, &salt);
+ else
+ GetColumn(query.get(), 1, &hash);
+ query_result = sqlite3_step(query.get());
+ }
+ CHECK(SQLITE_DONE == query_result);
+ if (salt.empty() || hash == kInvalidHash)
+ return false;
+ MD5Calculator md5;
+ md5.AddData(salt.data(), salt.size());
+ md5.AddData(password.data(), password.size());
+ return hash == GetHashFromDigest(md5.GetDigest());
+}
+
+void UserSettings::SwitchUser(const string& username) {
+ {
+ ScopedLock lock(&mutex_);
+ email_ = username;
+ }
+}
+
+void UserSettings::RememberSigninType(const string& signin, SignIn signin_type)
+{
+ ScopedDBHandle dbhandle(this);
+ ExecOrDie(dbhandle.get(), "INSERT INTO signin_types(signin, signin_type)"
+ " values ( ?, ? )", signin, static_cast<int>(signin_type));
+}
+
+SignIn UserSettings::RecallSigninType(const string& signin, SignIn default_type)
+{
+ ScopedDBHandle dbhandle(this);
+ ScopedStatement query(PrepareQuery(dbhandle.get(),
+ "SELECT signin_type from signin_types"
+ " WHERE signin = ?", signin));
+ int query_result = sqlite3_step(query.get());
+ if (SQLITE_ROW == query_result) {
+ int signin_type;
+ GetColumn(query.get(), 0, &signin_type);
+ return static_cast<SignIn>(signin_type);
+ }
+ return default_type;
+}
+
+string UserSettings::GetClientId() {
+ ScopedDBHandle dbhandle(this);
+ ScopedStatement query(PrepareQuery(dbhandle.get(),
+ "SELECT id FROM client_id"));
+ int query_result = sqlite3_step(query.get());
+ string client_id;
+ if (query_result == SQLITE_ROW)
+ GetColumn(query.get(), 0, &client_id);
+ return client_id;
+}
+
+} // namespace browser_sync