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