diff options
Diffstat (limited to 'chrome/browser/chromeos/settings')
45 files changed, 6173 insertions, 0 deletions
diff --git a/chrome/browser/chromeos/settings/OWNERS b/chrome/browser/chromeos/settings/OWNERS new file mode 100644 index 0000000..cd1c574 --- /dev/null +++ b/chrome/browser/chromeos/settings/OWNERS @@ -0,0 +1,2 @@ +mnissler@chromium.org +pastarmovj@chromium.org diff --git a/chrome/browser/chromeos/settings/cros_settings.cc b/chrome/browser/chromeos/settings/cros_settings.cc new file mode 100644 index 0000000..a0622b4 --- /dev/null +++ b/chrome/browser/chromeos/settings/cros_settings.cc @@ -0,0 +1,315 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/chromeos/settings/cros_settings.h" + +#include "base/bind.h" +#include "base/command_line.h" +#include "base/lazy_instance.h" +#include "base/stl_util.h" +#include "base/string_util.h" +#include "base/values.h" +#include "chrome/browser/chromeos/settings/device_settings_provider.h" +#include "chrome/browser/chromeos/settings/signed_settings_helper.h" +#include "chrome/browser/chromeos/settings/stub_cros_settings_provider.h" +#include "chrome/browser/chromeos/settings/system_settings_provider.h" +#include "chrome/common/chrome_notification_types.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/net/gaia/gaia_auth_util.h" +#include "content/public/browser/notification_details.h" +#include "content/public/browser/notification_source.h" +#include "content/public/browser/notification_types.h" + +namespace chromeos { + +static base::LazyInstance<CrosSettings> g_cros_settings = + LAZY_INSTANCE_INITIALIZER; + +CrosSettings* CrosSettings::Get() { + // TODO(xiyaun): Use real stuff when underlying libcros is ready. + return g_cros_settings.Pointer(); +} + +bool CrosSettings::IsCrosSettings(const std::string& path) { + return StartsWithASCII(path, kCrosSettingsPrefix, true); +} + +void CrosSettings::FireObservers(const std::string& path) { + DCHECK(CalledOnValidThread()); + SettingsObserverMap::iterator observer_iterator = + settings_observers_.find(path); + if (observer_iterator == settings_observers_.end()) + return; + + NotificationObserverList::Iterator it(*(observer_iterator->second)); + content::NotificationObserver* observer; + while ((observer = it.GetNext()) != NULL) { + observer->Observe(chrome::NOTIFICATION_SYSTEM_SETTING_CHANGED, + content::Source<CrosSettings>(this), + content::Details<const std::string>(&path)); + } +} + +void CrosSettings::Set(const std::string& path, const base::Value& in_value) { + DCHECK(CalledOnValidThread()); + CrosSettingsProvider* provider; + provider = GetProvider(path); + if (provider) + provider->Set(path, in_value); +} + +void CrosSettings::SetBoolean(const std::string& path, bool in_value) { + DCHECK(CalledOnValidThread()); + base::FundamentalValue value(in_value); + Set(path, value); +} + +void CrosSettings::SetInteger(const std::string& path, int in_value) { + DCHECK(CalledOnValidThread()); + base::FundamentalValue value(in_value); + Set(path, value); +} + +void CrosSettings::SetDouble(const std::string& path, double in_value) { + DCHECK(CalledOnValidThread()); + base::FundamentalValue value(in_value); + Set(path, value); +} + +void CrosSettings::SetString(const std::string& path, + const std::string& in_value) { + DCHECK(CalledOnValidThread()); + base::StringValue value(in_value); + Set(path, value); +} + +void CrosSettings::AppendToList(const std::string& path, + const base::Value* value) { + DCHECK(CalledOnValidThread()); + const base::Value* old_value = GetPref(path); + scoped_ptr<base::Value> new_value( + old_value ? old_value->DeepCopy() : new base::ListValue()); + static_cast<base::ListValue*>(new_value.get())->Append(value->DeepCopy()); + Set(path, *new_value); +} + +void CrosSettings::RemoveFromList(const std::string& path, + const base::Value* value) { + DCHECK(CalledOnValidThread()); + const base::Value* old_value = GetPref(path); + scoped_ptr<base::Value> new_value( + old_value ? old_value->DeepCopy() : new base::ListValue()); + static_cast<base::ListValue*>(new_value.get())->Remove(*value, NULL); + Set(path, *new_value); +} + +bool CrosSettings::FindEmailInList(const std::string& path, + const std::string& email) const { + DCHECK(CalledOnValidThread()); + std::string canonicalized_email( + gaia::CanonicalizeEmail(gaia::SanitizeEmail(email))); + std::string wildcard_email; + std::string::size_type at_pos = canonicalized_email.find('@'); + if (at_pos != std::string::npos) { + wildcard_email = + std::string("*").append(canonicalized_email.substr(at_pos)); + } + + const base::ListValue* list; + if (!GetList(path, &list)) + return false; + for (base::ListValue::const_iterator entry(list->begin()); + entry != list->end(); + ++entry) { + std::string entry_string; + if (!(*entry)->GetAsString(&entry_string)) { + NOTREACHED(); + continue; + } + std::string canonicalized_entry( + gaia::CanonicalizeEmail(gaia::SanitizeEmail(entry_string))); + + if (canonicalized_entry == canonicalized_email || + canonicalized_entry == wildcard_email) { + return true; + } + } + return false; +} + +bool CrosSettings::AddSettingsProvider(CrosSettingsProvider* provider) { + DCHECK(CalledOnValidThread()); + providers_.push_back(provider); + + // Allow the provider to notify this object when settings have changed. + // Providers instantiated inside this class will have the same callback + // passed to their constructor, but doing it here allows for providers + // to be instantiated outside this class. + CrosSettingsProvider::NotifyObserversCallback notify_cb( + base::Bind(&CrosSettings::FireObservers, base::Unretained(this))); + provider->SetNotifyObserversCallback(notify_cb); + return true; +} + +bool CrosSettings::RemoveSettingsProvider(CrosSettingsProvider* provider) { + DCHECK(CalledOnValidThread()); + std::vector<CrosSettingsProvider*>::iterator it = + std::find(providers_.begin(), providers_.end(), provider); + if (it != providers_.end()) { + providers_.erase(it); + return true; + } + return false; +} + +void CrosSettings::AddSettingsObserver(const char* path, + content::NotificationObserver* obs) { + DCHECK(path); + DCHECK(obs); + DCHECK(CalledOnValidThread()); + + if (!GetProvider(std::string(path))) { + NOTREACHED() << "Trying to add an observer for an unregistered setting: " + << path; + return; + } + + // Get the settings observer list associated with the path. + NotificationObserverList* observer_list = NULL; + SettingsObserverMap::iterator observer_iterator = + settings_observers_.find(path); + if (observer_iterator == settings_observers_.end()) { + observer_list = new NotificationObserverList; + settings_observers_[path] = observer_list; + } else { + observer_list = observer_iterator->second; + } + + // Verify that this observer doesn't already exist. + NotificationObserverList::Iterator it(*observer_list); + content::NotificationObserver* existing_obs; + while ((existing_obs = it.GetNext()) != NULL) { + if (existing_obs == obs) + return; + } + + // Ok, safe to add the pref observer. + observer_list->AddObserver(obs); +} + +void CrosSettings::RemoveSettingsObserver(const char* path, + content::NotificationObserver* obs) { + DCHECK(CalledOnValidThread()); + + SettingsObserverMap::iterator observer_iterator = + settings_observers_.find(path); + if (observer_iterator == settings_observers_.end()) + return; + + NotificationObserverList* observer_list = observer_iterator->second; + observer_list->RemoveObserver(obs); +} + +CrosSettingsProvider* CrosSettings::GetProvider( + const std::string& path) const { + for (size_t i = 0; i < providers_.size(); ++i) { + if (providers_[i]->HandlesSetting(path)) + return providers_[i]; + } + return NULL; +} + +void CrosSettings::ReloadProviders() { + for (size_t i = 0; i < providers_.size(); ++i) + providers_[i]->Reload(); +} + +const base::Value* CrosSettings::GetPref(const std::string& path) const { + DCHECK(CalledOnValidThread()); + CrosSettingsProvider* provider = GetProvider(path); + if (provider) + return provider->Get(path); + NOTREACHED() << path << " preference was not found in the signed settings."; + return NULL; +} + +CrosSettingsProvider::TrustedStatus CrosSettings::PrepareTrustedValues( + const base::Closure& callback) const { + DCHECK(CalledOnValidThread()); + for (size_t i = 0; i < providers_.size(); ++i) { + CrosSettingsProvider::TrustedStatus status = + providers_[i]->PrepareTrustedValues(callback); + if (status != CrosSettingsProvider::TRUSTED) + return status; + } + return CrosSettingsProvider::TRUSTED; +} + +bool CrosSettings::GetBoolean(const std::string& path, + bool* bool_value) const { + DCHECK(CalledOnValidThread()); + const base::Value* value = GetPref(path); + if (value) + return value->GetAsBoolean(bool_value); + return false; +} + +bool CrosSettings::GetInteger(const std::string& path, + int* out_value) const { + DCHECK(CalledOnValidThread()); + const base::Value* value = GetPref(path); + if (value) + return value->GetAsInteger(out_value); + return false; +} + +bool CrosSettings::GetDouble(const std::string& path, + double* out_value) const { + DCHECK(CalledOnValidThread()); + const base::Value* value = GetPref(path); + if (value) + return value->GetAsDouble(out_value); + return false; +} + +bool CrosSettings::GetString(const std::string& path, + std::string* out_value) const { + DCHECK(CalledOnValidThread()); + const base::Value* value = GetPref(path); + if (value) + return value->GetAsString(out_value); + return false; +} + +bool CrosSettings::GetList(const std::string& path, + const base::ListValue** out_value) const { + DCHECK(CalledOnValidThread()); + const base::Value* value = GetPref(path); + if (value) + return value->GetAsList(out_value); + return false; +} + +CrosSettings::CrosSettings() { + CrosSettingsProvider::NotifyObserversCallback notify_cb( + base::Bind(&CrosSettings::FireObservers, + // This is safe since |this| is never deleted. + base::Unretained(this))); + if (CommandLine::ForCurrentProcess()->HasSwitch( + switches::kStubCrosSettings)) { + AddSettingsProvider(new StubCrosSettingsProvider(notify_cb)); + } else { + AddSettingsProvider( + new DeviceSettingsProvider(notify_cb, SignedSettingsHelper::Get())); + } + // System settings are not mocked currently. + AddSettingsProvider(new SystemSettingsProvider(notify_cb)); +} + +CrosSettings::~CrosSettings() { + STLDeleteElements(&providers_); + STLDeleteValues(&settings_observers_); +} + +} // namespace chromeos diff --git a/chrome/browser/chromeos/settings/cros_settings.h b/chrome/browser/chromeos/settings/cros_settings.h new file mode 100644 index 0000000..128184c --- /dev/null +++ b/chrome/browser/chromeos/settings/cros_settings.h @@ -0,0 +1,123 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_CHROMEOS_SETTINGS_CROS_SETTINGS_H_ +#define CHROME_BROWSER_CHROMEOS_SETTINGS_CROS_SETTINGS_H_ + +#include <string> +#include <vector> + +#include "base/callback_forward.h" +#include "base/hash_tables.h" +#include "base/observer_list.h" +#include "base/threading/non_thread_safe.h" +#include "chrome/browser/chromeos/settings/cros_settings_names.h" +#include "chrome/browser/chromeos/settings/cros_settings_provider.h" +#include "content/public/browser/notification_observer.h" + +namespace base { +template <typename T> struct DefaultLazyInstanceTraits; +class ListValue; +class Value; +} + +namespace chromeos { + +// This class manages per-device/global settings. +class CrosSettings : public base::NonThreadSafe { + public: + // Class factory. + static CrosSettings* Get(); + + // Helper function to test if the given |path| is a valid cros setting. + static bool IsCrosSettings(const std::string& path); + + // Sets |in_value| to given |path| in cros settings. + void Set(const std::string& path, const base::Value& in_value); + + // Returns setting value for the given |path|. + const base::Value* GetPref(const std::string& path) const; + + // Requests all providers to fetch their values from a trusted store, if they + // haven't done so yet. Returns true if the cros settings returned by |this| + // are trusted during the current loop cycle; otherwise returns false, and + // |callback| will be invoked later when trusted values become available. + // PrepareTrustedValues() should be tried again in that case. + virtual CrosSettingsProvider::TrustedStatus PrepareTrustedValues( + const base::Closure& callback) const; + + // Convenience forms of Set(). These methods will replace any existing + // value at that |path|, even if it has a different type. + void SetBoolean(const std::string& path, bool in_value); + void SetInteger(const std::string& path, int in_value); + void SetDouble(const std::string& path, double in_value); + void SetString(const std::string& path, const std::string& in_value); + + // Convenience functions for manipulating lists. Note that the following + // functions employs a read, modify and write pattern. If underlying settings + // provider updates its value asynchronously such as DeviceSettingsProvider, + // value cache they read from might not be fresh and multiple calls to those + // function would lose data. See http://crbug.com/127215 + void AppendToList(const std::string& path, const base::Value* value); + void RemoveFromList(const std::string& path, const base::Value* value); + + // These are convenience forms of Get(). The value will be retrieved + // and the return value will be true if the |path| is valid and the value at + // the end of the path can be returned in the form specified. + bool GetBoolean(const std::string& path, bool* out_value) const; + bool GetInteger(const std::string& path, int* out_value) const; + bool GetDouble(const std::string& path, double* out_value) const; + bool GetString(const std::string& path, std::string* out_value) const; + bool GetList(const std::string& path, + const base::ListValue** out_value) const; + + // Helper function for the whitelist op. Implemented here because we will need + // this in a few places. The functions searches for |email| in the pref |path| + // It respects whitelists so foo@bar.baz will match *@bar.baz too. + bool FindEmailInList(const std::string& path, const std::string& email) const; + + // Adding/removing of providers. + bool AddSettingsProvider(CrosSettingsProvider* provider); + bool RemoveSettingsProvider(CrosSettingsProvider* provider); + + // If the pref at the given |path| changes, we call the observer's Observe + // method with NOTIFICATION_SYSTEM_SETTING_CHANGED. + void AddSettingsObserver(const char* path, + content::NotificationObserver* obs); + void RemoveSettingsObserver(const char* path, + content::NotificationObserver* obs); + + // Returns the provider that handles settings with the |path| or prefix. + CrosSettingsProvider* GetProvider(const std::string& path) const; + + // Forces all providers to reload their caches from the respective backing + // stores if they have any. + void ReloadProviders(); + + private: + friend struct base::DefaultLazyInstanceTraits<CrosSettings>; + + // List of ChromeOS system settings providers. + std::vector<CrosSettingsProvider*> providers_; + + // A map from settings names to a list of observers. Observers get fired in + // the order they are added. + typedef ObserverList<content::NotificationObserver, true> + NotificationObserverList; + typedef base::hash_map<std::string, NotificationObserverList*> + SettingsObserverMap; + SettingsObserverMap settings_observers_; + + CrosSettings(); + ~CrosSettings(); + + // Fires system setting change notification. + void FireObservers(const std::string& path); + + DISALLOW_COPY_AND_ASSIGN(CrosSettings); +}; + +} // namespace chromeos + +#endif // CHROME_BROWSER_CHROMEOS_SETTINGS_CROS_SETTINGS_H_ diff --git a/chrome/browser/chromeos/settings/cros_settings_names.cc b/chrome/browser/chromeos/settings/cros_settings_names.cc new file mode 100644 index 0000000..0cb3aad --- /dev/null +++ b/chrome/browser/chromeos/settings/cros_settings_names.cc @@ -0,0 +1,79 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/chromeos/settings/cros_settings_names.h" + +namespace chromeos { + +const char kCrosSettingsPrefix[] = "cros."; + +// All cros.accounts.* settings are stored in SignedSettings. +const char kAccountsPrefAllowGuest[] = "cros.accounts.allowBWSI"; +const char kAccountsPrefAllowNewUser[] = "cros.accounts.allowGuest"; +const char kAccountsPrefShowUserNamesOnSignIn[] + = "cros.accounts.showUserNamesOnSignIn"; +const char kAccountsPrefUsers[] = "cros.accounts.users"; +const char kAccountsPrefEphemeralUsersEnabled[] = + "cros.accounts.ephemeralUsersEnabled"; + +// Name of signed setting persisted on device, writeable only by owner. +const char kSettingProxyEverywhere[] = "cros.proxy.everywhere"; + +// All cros.signed.* settings are stored in SignedSettings. +const char kSignedDataRoamingEnabled[] = "cros.signed.data_roaming_enabled"; + +const char kSystemTimezone[] = "cros.system.timezone"; + +const char kDeviceOwner[] = "cros.device.owner"; + +const char kStatsReportingPref[] = "cros.metrics.reportingEnabled"; + +const char kReleaseChannel[] = "cros.system.releaseChannel"; +const char kReleaseChannelDelegated[] = "cros.system.releaseChannelDelegated"; + +// A boolean pref that indicates whether OS & firmware version info should be +// reported along with device policy requests. +const char kReportDeviceVersionInfo[] = + "cros.device_status.report_version_info"; + +// A boolean pref that indicates whether device activity times should be +// recorded and reported along with device policy requests. +const char kReportDeviceActivityTimes[] = + "cros.device_status.report_activity_times"; + +// A boolean pref that indicates whether the state of the dev mode switch at +// boot should be reported along with device policy requests. +const char kReportDeviceBootMode[] = "cros.device_status.report_boot_mode"; + +// A boolean pref that indicates whether the current location should be reported +// along with device policy requests. +const char kReportDeviceLocation[] = "cros.device_status.report_location"; + +// A list of dictionaries, each detailing one extension to install as part of +// the AppPack and including the following fields: +// "extension-id": ID of the extension to install +// "update-url": URL to check the extension's version and download location +// "key-checksum": checksum of the extension's CRX public key, encoded in hex. +const char kAppPack[] = "cros.app_pack"; + +// Values from the ScreenSaver proto. Defines the extension ID of the screen +// saver extension and the timeout before the screen saver should be started. +const char kScreenSaverExtensionId[] = "cros.screen_saver.extension_id"; +const char kScreenSaverTimeout[] = "cros.screen_saver.timeout"; + +// Values from the ForcedLogoutTimeouts proto. Defines the timeouts before a +// user is logged out after some period of inactivity as well as the duration of +// a warning message informing the user about the pending logout. +const char kIdleLogoutTimeout[] = "cros.idle_logout.timeout"; +const char kIdleLogoutWarningDuration[] = "cros.idle_logout.warning_duration"; + +// Defines the set of URLs to be opened on login to the anonymous account used +// if the device is in KIOSK mode. +const char kStartUpUrls[] = "cros.start_up_urls"; + +// This policy should not appear in the protobuf ever but is used internally to +// signal that we are running in a "safe-mode" for policy recovery. +const char kPolicyMissingMitigationMode[] = + "cros.internal.policy_mitigation_mode"; +} // namespace chromeos diff --git a/chrome/browser/chromeos/settings/cros_settings_names.h b/chrome/browser/chromeos/settings/cros_settings_names.h new file mode 100644 index 0000000..c75c834a --- /dev/null +++ b/chrome/browser/chromeos/settings/cros_settings_names.h @@ -0,0 +1,50 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_CHROMEOS_SETTINGS_CROS_SETTINGS_NAMES_H_ +#define CHROME_BROWSER_CHROMEOS_SETTINGS_CROS_SETTINGS_NAMES_H_ + +namespace chromeos { + +extern const char kCrosSettingsPrefix[]; + +extern const char kAccountsPrefAllowGuest[]; +extern const char kAccountsPrefAllowNewUser[]; +extern const char kAccountsPrefShowUserNamesOnSignIn[]; +extern const char kAccountsPrefUsers[]; +extern const char kAccountsPrefEphemeralUsersEnabled[]; + +extern const char kSettingProxyEverywhere[]; + +extern const char kSignedDataRoamingEnabled[]; + +extern const char kSystemTimezone[]; + +extern const char kDeviceOwner[]; + +extern const char kStatsReportingPref[]; + +extern const char kReleaseChannel[]; +extern const char kReleaseChannelDelegated[]; + +extern const char kReportDeviceVersionInfo[]; +extern const char kReportDeviceActivityTimes[]; +extern const char kReportDeviceBootMode[]; +extern const char kReportDeviceLocation[]; + +extern const char kAppPack[]; + +extern const char kScreenSaverExtensionId[]; +extern const char kScreenSaverTimeout[]; + +extern const char kIdleLogoutTimeout[]; +extern const char kIdleLogoutWarningDuration[]; + +extern const char kStartUpUrls[]; + +extern const char kPolicyMissingMitigationMode[]; + +} // namespace chromeos + +#endif // CHROME_BROWSER_CHROMEOS_SETTINGS_CROS_SETTINGS_NAMES_H_ diff --git a/chrome/browser/chromeos/settings/cros_settings_provider.cc b/chrome/browser/chromeos/settings/cros_settings_provider.cc new file mode 100644 index 0000000..e5810c3 --- /dev/null +++ b/chrome/browser/chromeos/settings/cros_settings_provider.cc @@ -0,0 +1,46 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/chromeos/settings/cros_settings_provider.h" + +#include "base/command_line.h" +#include "base/logging.h" +#include "base/string_util.h" +#include "base/values.h" +#include "chrome/common/chrome_switches.h" + +namespace chromeos { + +CrosSettingsProvider::CrosSettingsProvider( + const NotifyObserversCallback& notify_cb) + : notify_cb_(notify_cb) { +} + +CrosSettingsProvider::~CrosSettingsProvider() { +} + +void CrosSettingsProvider::Set(const std::string& path, + const base::Value& value) { + // We don't allow changing any of the cros settings without prefix + // "cros.session." in the guest mode. + // It should not reach here from UI in the guest mode, but just in case. + if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kGuestSession) && + !::StartsWithASCII(path, "cros.session.", true)) { + LOG(ERROR) << "Ignoring the guest request to change: " << path; + return; + } + DoSet(path, value); +} + +void CrosSettingsProvider::NotifyObservers(const std::string& path) { + if (!notify_cb_.is_null()) + notify_cb_.Run(path); +} + +void CrosSettingsProvider::SetNotifyObserversCallback( + const NotifyObserversCallback& notify_cb) { + notify_cb_ = notify_cb; +} + +}; // namespace chromeos diff --git a/chrome/browser/chromeos/settings/cros_settings_provider.h b/chrome/browser/chromeos/settings/cros_settings_provider.h new file mode 100644 index 0000000..2477f34 --- /dev/null +++ b/chrome/browser/chromeos/settings/cros_settings_provider.h @@ -0,0 +1,81 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_CHROMEOS_SETTINGS_CROS_SETTINGS_PROVIDER_H_ +#define CHROME_BROWSER_CHROMEOS_SETTINGS_CROS_SETTINGS_PROVIDER_H_ + +#include <string> + +#include "base/callback.h" + +namespace base { +class Value; +} + +namespace chromeos { + +class CrosSettingsProvider { + public: + // The callback type that is called to notify the CrosSettings observers + // about a setting change. + typedef base::Callback<void(const std::string&)> NotifyObserversCallback; + + // Possible results of a trusted check. + enum TrustedStatus { + // The trusted values were populated in the cache and can be accessed + // until the next iteration of the message loop. + TRUSTED, + // Either a store or a load operation is in progress. The provided + // callback will be invoked once the verification has finished. + TEMPORARILY_UNTRUSTED, + // The verification of the trusted store has failed permanently. The + // client should assume this state final and further checks for + // trustedness will fail at least until the browser restarts. + PERMANENTLY_UNTRUSTED, + }; + + // Creates a new provider instance. |notify_cb| will be used to notify + // about setting changes. + explicit CrosSettingsProvider(const NotifyObserversCallback& notify_cb); + virtual ~CrosSettingsProvider(); + + // Sets |in_value| to given |path| in cros settings. + void Set(const std::string& path, const base::Value& in_value); + + // Gets settings value of given |path| to |out_value|. + virtual const base::Value* Get(const std::string& path) const = 0; + + // Requests the provider to fetch its values from a trusted store, if it + // hasn't done so yet. Returns TRUSTED if the values returned by this provider + // are trusted during the current loop cycle. Otherwise returns + // TEMPORARILY_UNTRUSTED, and |callback| will be invoked later when trusted + // values become available, PrepareTrustedValues() should be tried again in + // that case. Returns PERMANENTLY_UNTRUSTED if a permanent error has occurred. + virtual TrustedStatus PrepareTrustedValues( + const base::Closure& callback) = 0; + + // Gets the namespace prefix provided by this provider. + virtual bool HandlesSetting(const std::string& path) const = 0; + + // Reloads the caches if the provider has any. + virtual void Reload() = 0; + + void SetNotifyObserversCallback(const NotifyObserversCallback& notify_cb); + + protected: + // Notifies the observers about a setting change. + void NotifyObservers(const std::string& path); + + private: + // Does the real job for Set(). + virtual void DoSet(const std::string& path, + const base::Value& in_value) = 0; + + // Callback used to notify about setting changes. + NotifyObserversCallback notify_cb_; +}; + +} // namespace chromeos + +#endif // CHROME_BROWSER_CHROMEOS_SETTINGS_CROS_SETTINGS_PROVIDER_H_ diff --git a/chrome/browser/chromeos/settings/cros_settings_unittest.cc b/chrome/browser/chromeos/settings/cros_settings_unittest.cc new file mode 100644 index 0000000..f5fb133 --- /dev/null +++ b/chrome/browser/chromeos/settings/cros_settings_unittest.cc @@ -0,0 +1,276 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/chromeos/settings/signed_settings.h" + +#include <map> +#include <string> + +#include "base/bind.h" +#include "base/memory/weak_ptr.h" +#include "base/message_loop.h" +#include "base/stl_util.h" +#include "base/values.h" +#include "chrome/browser/chromeos/cros/cros_library.h" +#include "chrome/browser/chromeos/login/mock_user_manager.h" +#include "chrome/browser/chromeos/settings/cros_settings.h" +#include "chrome/browser/chromeos/settings/cros_settings_names.h" +#include "chrome/browser/chromeos/settings/signed_settings_cache.h" +#include "chrome/browser/policy/proto/chrome_device_policy.pb.h" +#include "chrome/browser/policy/proto/device_management_backend.pb.h" +#include "chrome/test/base/testing_browser_process.h" +#include "chrome/test/base/testing_pref_service.h" +#include "content/public/test/test_browser_thread.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::AnyNumber; +using ::testing::Return; + +namespace em = enterprise_management; +namespace chromeos { + +class CrosSettingsTest : public testing::Test { + protected: + CrosSettingsTest() + : message_loop_(MessageLoop::TYPE_UI), + ui_thread_(content::BrowserThread::UI, &message_loop_), + file_thread_(content::BrowserThread::FILE, &message_loop_), + pointer_factory_(this), + local_state_(static_cast<TestingBrowserProcess*>(g_browser_process)) { + } + + virtual ~CrosSettingsTest() { + } + + virtual void SetUp() { + EXPECT_CALL(*mock_user_manager_.user_manager(), IsCurrentUserOwner()) + .Times(AnyNumber()) + .WillRepeatedly(Return(true)); + // Reset the cache between tests. + ApplyEmptyPolicy(); + } + + virtual void TearDown() { + message_loop_.RunAllPending(); + ASSERT_TRUE(expected_props_.empty()); + // Reset the cache between tests. + ApplyEmptyPolicy(); + STLDeleteValues(&expected_props_); + } + + void FetchPref(const std::string& pref) { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + if (expected_props_.find(pref) == expected_props_.end()) + return; + + if (CrosSettingsProvider::TRUSTED == + CrosSettings::Get()->PrepareTrustedValues( + base::Bind(&CrosSettingsTest::FetchPref, + pointer_factory_.GetWeakPtr(), pref))) { + scoped_ptr<base::Value> expected_value( + expected_props_.find(pref)->second); + const base::Value* pref_value = CrosSettings::Get()->GetPref(pref); + if (expected_value.get()) { + ASSERT_TRUE(pref_value); + ASSERT_TRUE(expected_value->Equals(pref_value)); + } else { + ASSERT_FALSE(pref_value); + } + expected_props_.erase(pref); + } + } + + void SetPref(const std::string& pref_name, const base::Value* value) { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + CrosSettings::Get()->Set(pref_name, *value); + } + + void AddExpectation(const std::string& pref_name, base::Value* value) { + base::Value*& entry = expected_props_[pref_name]; + delete entry; + entry = value; + } + + void PrepareEmptyPolicy(em::PolicyData* policy) { + // Prepare some policy blob. + em::PolicyFetchResponse response; + em::ChromeDeviceSettingsProto pol; + policy->set_policy_type(chromeos::kDevicePolicyType); + policy->set_username("me@owner"); + policy->set_policy_value(pol.SerializeAsString()); + // Wipe the signed settings store. + response.set_policy_data(policy->SerializeAsString()); + response.set_policy_data_signature("false"); + } + + void ApplyEmptyPolicy() { + em::PolicyData fake_pol; + PrepareEmptyPolicy(&fake_pol); + signed_settings_cache::Store(fake_pol, local_state_.Get()); + CrosSettings::Get()->ReloadProviders(); + } + + std::map<std::string, base::Value*> expected_props_; + + MessageLoop message_loop_; + content::TestBrowserThread ui_thread_; + content::TestBrowserThread file_thread_; + + base::WeakPtrFactory<CrosSettingsTest> pointer_factory_; + + ScopedTestingLocalState local_state_; + + ScopedMockUserManagerEnabler mock_user_manager_; + ScopedStubCrosEnabler stub_cros_enabler_; +}; + +TEST_F(CrosSettingsTest, SetPref) { + // Change to something that is not the default. + AddExpectation(kAccountsPrefAllowGuest, + base::Value::CreateBooleanValue(false)); + SetPref(kAccountsPrefAllowGuest, expected_props_[kAccountsPrefAllowGuest]); + FetchPref(kAccountsPrefAllowGuest); + message_loop_.RunAllPending(); + ASSERT_TRUE(expected_props_.empty()); +} + +TEST_F(CrosSettingsTest, GetPref) { + // We didn't change the default so look for it. + AddExpectation(kAccountsPrefAllowGuest, + base::Value::CreateBooleanValue(true)); + FetchPref(kAccountsPrefAllowGuest); +} + +TEST_F(CrosSettingsTest, SetWhitelist) { + // Setting the whitelist should also switch the value of + // kAccountsPrefAllowNewUser to false. + base::ListValue whitelist; + whitelist.Append(base::Value::CreateStringValue("me@owner")); + AddExpectation(kAccountsPrefAllowNewUser, + base::Value::CreateBooleanValue(false)); + AddExpectation(kAccountsPrefUsers, whitelist.DeepCopy()); + SetPref(kAccountsPrefUsers, &whitelist); + FetchPref(kAccountsPrefAllowNewUser); + FetchPref(kAccountsPrefUsers); +} + +TEST_F(CrosSettingsTest, SetWhitelistWithListOps) { + base::ListValue* whitelist = new base::ListValue(); + base::StringValue hacky_user("h@xxor"); + whitelist->Append(hacky_user.DeepCopy()); + AddExpectation(kAccountsPrefAllowNewUser, + base::Value::CreateBooleanValue(false)); + AddExpectation(kAccountsPrefUsers, whitelist); + // Add some user to the whitelist. + CrosSettings::Get()->AppendToList(kAccountsPrefUsers, &hacky_user); + FetchPref(kAccountsPrefAllowNewUser); + FetchPref(kAccountsPrefUsers); +} + +TEST_F(CrosSettingsTest, SetWhitelistWithListOps2) { + base::ListValue whitelist; + base::StringValue hacky_user("h@xxor"); + base::StringValue lamy_user("l@mer"); + whitelist.Append(hacky_user.DeepCopy()); + base::ListValue* expected_list = whitelist.DeepCopy(); + whitelist.Append(lamy_user.DeepCopy()); + AddExpectation(kAccountsPrefAllowNewUser, + base::Value::CreateBooleanValue(false)); + AddExpectation(kAccountsPrefUsers, whitelist.DeepCopy()); + SetPref(kAccountsPrefUsers, &whitelist); + FetchPref(kAccountsPrefAllowNewUser); + FetchPref(kAccountsPrefUsers); + message_loop_.RunAllPending(); + ASSERT_TRUE(expected_props_.empty()); + // Now try to remove one element from that list. + AddExpectation(kAccountsPrefUsers, expected_list); + CrosSettings::Get()->RemoveFromList(kAccountsPrefUsers, &lamy_user); + FetchPref(kAccountsPrefAllowNewUser); + FetchPref(kAccountsPrefUsers); +} + +TEST_F(CrosSettingsTest, SetEmptyWhitelist) { + // Setting the whitelist empty should switch the value of + // kAccountsPrefAllowNewUser to true. + base::ListValue whitelist; + base::FundamentalValue disallow_new(false); + AddExpectation(kAccountsPrefAllowNewUser, + base::Value::CreateBooleanValue(true)); + SetPref(kAccountsPrefUsers, &whitelist); + SetPref(kAccountsPrefAllowNewUser, &disallow_new); + FetchPref(kAccountsPrefAllowNewUser); + FetchPref(kAccountsPrefUsers); +} + +TEST_F(CrosSettingsTest, SetWhitelistAndNoNewUsers) { + // Setting the whitelist should allow us to set kAccountsPrefAllowNewUser to + // false (which is the implicit value too). + base::ListValue whitelist; + whitelist.Append(base::Value::CreateStringValue("me@owner")); + AddExpectation(kAccountsPrefUsers, whitelist.DeepCopy()); + AddExpectation(kAccountsPrefAllowNewUser, + base::Value::CreateBooleanValue(false)); + SetPref(kAccountsPrefUsers, &whitelist); + SetPref(kAccountsPrefAllowNewUser, + expected_props_[kAccountsPrefAllowNewUser]); + FetchPref(kAccountsPrefAllowNewUser); + FetchPref(kAccountsPrefUsers); +} + +TEST_F(CrosSettingsTest, SetAllowNewUsers) { + // Setting kAccountsPrefAllowNewUser to true with no whitelist should be ok. + AddExpectation(kAccountsPrefAllowNewUser, + base::Value::CreateBooleanValue(true)); + SetPref(kAccountsPrefAllowNewUser, + expected_props_[kAccountsPrefAllowNewUser]); + FetchPref(kAccountsPrefAllowNewUser); +} + +TEST_F(CrosSettingsTest, SetOwner) { + base::StringValue hacky_owner("h@xxor"); + AddExpectation(kDeviceOwner, base::Value::CreateStringValue("h@xxor")); + SetPref(kDeviceOwner, &hacky_owner); + FetchPref(kDeviceOwner); +} + +TEST_F(CrosSettingsTest, SetEphemeralUsersEnabled) { + base::FundamentalValue ephemeral_users_enabled(true); + AddExpectation(kAccountsPrefEphemeralUsersEnabled, + base::Value::CreateBooleanValue(true)); + SetPref(kAccountsPrefEphemeralUsersEnabled, &ephemeral_users_enabled); + FetchPref(kAccountsPrefEphemeralUsersEnabled); +} + +TEST_F(CrosSettingsTest, FindEmailInList) { + base::ListValue list; + list.Append(base::Value::CreateStringValue("user@example.com")); + list.Append(base::Value::CreateStringValue("nodomain")); + list.Append(base::Value::CreateStringValue("with.dots@gmail.com")); + list.Append(base::Value::CreateStringValue("Upper@example.com")); + + CrosSettings* cs = CrosSettings::Get(); + cs->Set(kAccountsPrefUsers, list); + + EXPECT_TRUE(cs->FindEmailInList(kAccountsPrefUsers, "user@example.com")); + EXPECT_FALSE(cs->FindEmailInList(kAccountsPrefUsers, "us.er@example.com")); + EXPECT_TRUE(cs->FindEmailInList(kAccountsPrefUsers, "USER@example.com")); + EXPECT_FALSE(cs->FindEmailInList(kAccountsPrefUsers, "user")); + + EXPECT_TRUE(cs->FindEmailInList(kAccountsPrefUsers, "nodomain")); + EXPECT_TRUE(cs->FindEmailInList(kAccountsPrefUsers, "nodomain@gmail.com")); + EXPECT_TRUE(cs->FindEmailInList(kAccountsPrefUsers, "no.domain@gmail.com")); + EXPECT_TRUE(cs->FindEmailInList(kAccountsPrefUsers, "NO.DOMAIN")); + + EXPECT_TRUE(cs->FindEmailInList(kAccountsPrefUsers, "with.dots@gmail.com")); + EXPECT_TRUE(cs->FindEmailInList(kAccountsPrefUsers, "withdots@gmail.com")); + EXPECT_TRUE(cs->FindEmailInList(kAccountsPrefUsers, "WITH.DOTS@gmail.com")); + EXPECT_TRUE(cs->FindEmailInList(kAccountsPrefUsers, "WITHDOTS")); + + EXPECT_TRUE(cs->FindEmailInList(kAccountsPrefUsers, "Upper@example.com")); + EXPECT_FALSE(cs->FindEmailInList(kAccountsPrefUsers, "U.pper@example.com")); + EXPECT_FALSE(cs->FindEmailInList(kAccountsPrefUsers, "Upper")); + EXPECT_TRUE(cs->FindEmailInList(kAccountsPrefUsers, "upper@example.com")); +} + +} // namespace chromeos diff --git a/chrome/browser/chromeos/settings/device_settings_provider.cc b/chrome/browser/chromeos/settings/device_settings_provider.cc new file mode 100644 index 0000000..0c1432e --- /dev/null +++ b/chrome/browser/chromeos/settings/device_settings_provider.cc @@ -0,0 +1,748 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/chromeos/settings/device_settings_provider.h" + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/callback.h" +#include "base/file_util.h" +#include "base/logging.h" +#include "base/string_util.h" +#include "base/threading/thread_restrictions.h" +#include "base/values.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/chromeos/cros/cros_library.h" +#include "chrome/browser/chromeos/cros/network_library.h" +#include "chrome/browser/chromeos/login/user_manager.h" +#include "chrome/browser/chromeos/settings/cros_settings.h" +#include "chrome/browser/chromeos/settings/cros_settings_names.h" +#include "chrome/browser/chromeos/settings/signed_settings_cache.h" +#include "chrome/browser/chromeos/settings/signed_settings_helper.h" +#include "chrome/browser/policy/app_pack_updater.h" +#include "chrome/browser/policy/browser_policy_connector.h" +#include "chrome/browser/policy/cloud_policy_constants.h" +#include "chrome/browser/policy/proto/chrome_device_policy.pb.h" +#include "chrome/browser/ui/options/options_util.h" +#include "chrome/common/chrome_notification_types.h" +#include "chrome/installer/util/google_update_settings.h" +#include "content/public/browser/notification_service.h" + +using google::protobuf::RepeatedPtrField; + +namespace em = enterprise_management; + +namespace chromeos { + +namespace { + +// List of settings handled by the DeviceSettingsProvider. +const char* kKnownSettings[] = { + kAccountsPrefAllowGuest, + kAccountsPrefAllowNewUser, + kAccountsPrefEphemeralUsersEnabled, + kAccountsPrefShowUserNamesOnSignIn, + kAccountsPrefUsers, + kAppPack, + kDeviceOwner, + kIdleLogoutTimeout, + kIdleLogoutWarningDuration, + kPolicyMissingMitigationMode, + kReleaseChannel, + kReleaseChannelDelegated, + kReportDeviceActivityTimes, + kReportDeviceBootMode, + kReportDeviceLocation, + kReportDeviceVersionInfo, + kScreenSaverExtensionId, + kScreenSaverTimeout, + kSettingProxyEverywhere, + kSignedDataRoamingEnabled, + kStartUpUrls, + kStatsReportingPref, +}; + +// Upper bound for number of retries to fetch a signed setting. +static const int kNumRetriesLimit = 9; + +// Legacy policy file location. Used to detect migration from pre v12 ChromeOS. +const char kLegacyPolicyFile[] = "/var/lib/whitelist/preferences"; + +bool IsControlledSetting(const std::string& pref_path) { + const char** end = kKnownSettings + arraysize(kKnownSettings); + return std::find(kKnownSettings, end, pref_path) != end; +} + +bool HasOldMetricsFile() { + // TODO(pastarmovj): Remove this once migration is not needed anymore. + // If the value is not set we should try to migrate legacy consent file. + // Loading consent file state causes us to do blocking IO on UI thread. + // Temporarily allow it until we fix http://crbug.com/62626 + base::ThreadRestrictions::ScopedAllowIO allow_io; + return GoogleUpdateSettings::GetCollectStatsConsent(); +} + +} // namespace + +DeviceSettingsProvider::DeviceSettingsProvider( + const NotifyObserversCallback& notify_cb, + SignedSettingsHelper* signed_settings_helper) + : CrosSettingsProvider(notify_cb), + signed_settings_helper_(signed_settings_helper), + ownership_status_(OwnershipService::GetSharedInstance()->GetStatus(true)), + migration_helper_(new SignedSettingsMigrationHelper()), + retries_left_(kNumRetriesLimit), + trusted_status_(TEMPORARILY_UNTRUSTED) { + // Register for notification when ownership is taken so that we can update + // the |ownership_status_| and reload if needed. + registrar_.Add(this, chrome::NOTIFICATION_OWNER_KEY_FETCH_ATTEMPT_SUCCEEDED, + content::NotificationService::AllSources()); + // Make sure we have at least the cache data immediately. + RetrieveCachedData(); + // Start prefetching preferences. + Reload(); +} + +DeviceSettingsProvider::~DeviceSettingsProvider() { +} + +void DeviceSettingsProvider::Reload() { + // While fetching we can't trust the cache anymore. + trusted_status_ = TEMPORARILY_UNTRUSTED; + if (ownership_status_ == OwnershipService::OWNERSHIP_NONE) { + RetrieveCachedData(); + } else { + // Retrieve the real data. + signed_settings_helper_->StartRetrievePolicyOp( + base::Bind(&DeviceSettingsProvider::OnRetrievePolicyCompleted, + base::Unretained(this))); + } +} + +void DeviceSettingsProvider::DoSet(const std::string& path, + const base::Value& in_value) { + if (!UserManager::Get()->IsCurrentUserOwner() && + ownership_status_ != OwnershipService::OWNERSHIP_NONE) { + LOG(WARNING) << "Changing settings from non-owner, setting=" << path; + + // Revert UI change. + NotifyObservers(path); + return; + } + + if (IsControlledSetting(path)) { + pending_changes_.push_back(PendingQueueElement(path, in_value.DeepCopy())); + if (pending_changes_.size() == 1) + SetInPolicy(); + } else { + NOTREACHED() << "Try to set unhandled cros setting " << path; + } +} + +void DeviceSettingsProvider::Observe( + int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) { + if (type == chrome::NOTIFICATION_OWNER_KEY_FETCH_ATTEMPT_SUCCEEDED) { + // Reload the policy blob once the owner key has been loaded or updated. + ownership_status_ = OwnershipService::OWNERSHIP_TAKEN; + Reload(); + } +} + +const em::PolicyData DeviceSettingsProvider::policy() const { + return policy_; +} + +void DeviceSettingsProvider::RetrieveCachedData() { + // If there is no owner yet, this function will pull the policy cache from the + // temp storage and use that instead. + em::PolicyData policy; + if (!signed_settings_cache::Retrieve(&policy, + g_browser_process->local_state())) { + VLOG(1) << "Can't retrieve temp store possibly not created yet."; + // Prepare empty data for the case we don't have temp cache yet. + policy.set_policy_type(kDevicePolicyType); + em::ChromeDeviceSettingsProto pol; + policy.set_policy_value(pol.SerializeAsString()); + } + + policy_ = policy; + UpdateValuesCache(); +} + +void DeviceSettingsProvider::SetInPolicy() { + if (pending_changes_.empty()) { + NOTREACHED(); + return; + } + + const std::string& prop = pending_changes_[0].first; + base::Value* value = pending_changes_[0].second; + if (prop == kDeviceOwner) { + // Just store it in the memory cache without trusted checks or persisting. + std::string owner; + if (value->GetAsString(&owner)) { + policy_.set_username(owner); + // In this case the |value_cache_| takes the ownership of |value|. + values_cache_.SetValue(prop, value); + NotifyObservers(prop); + // We can't trust this value anymore until we reload the real username. + trusted_status_ = TEMPORARILY_UNTRUSTED; + pending_changes_.erase(pending_changes_.begin()); + if (!pending_changes_.empty()) + SetInPolicy(); + } else { + NOTREACHED(); + } + return; + } + + if (RequestTrustedEntity() != TRUSTED) { + // Otherwise we should first reload and apply on top of that. + signed_settings_helper_->StartRetrievePolicyOp( + base::Bind(&DeviceSettingsProvider::FinishSetInPolicy, + base::Unretained(this))); + return; + } + + trusted_status_ = TEMPORARILY_UNTRUSTED; + em::PolicyData data = policy(); + em::ChromeDeviceSettingsProto pol; + pol.ParseFromString(data.policy_value()); + if (prop == kAccountsPrefAllowNewUser) { + em::AllowNewUsersProto* allow = pol.mutable_allow_new_users(); + bool allow_value; + if (value->GetAsBoolean(&allow_value)) + allow->set_allow_new_users(allow_value); + else + NOTREACHED(); + } else if (prop == kAccountsPrefAllowGuest) { + em::GuestModeEnabledProto* guest = pol.mutable_guest_mode_enabled(); + bool guest_value; + if (value->GetAsBoolean(&guest_value)) + guest->set_guest_mode_enabled(guest_value); + else + NOTREACHED(); + } else if (prop == kAccountsPrefShowUserNamesOnSignIn) { + em::ShowUserNamesOnSigninProto* show = pol.mutable_show_user_names(); + bool show_value; + if (value->GetAsBoolean(&show_value)) + show->set_show_user_names(show_value); + else + NOTREACHED(); + } else if (prop == kSignedDataRoamingEnabled) { + em::DataRoamingEnabledProto* roam = pol.mutable_data_roaming_enabled(); + bool roaming_value = false; + if (value->GetAsBoolean(&roaming_value)) + roam->set_data_roaming_enabled(roaming_value); + else + NOTREACHED(); + ApplyRoamingSetting(roaming_value); + } else if (prop == kSettingProxyEverywhere) { + // TODO(cmasone): NOTIMPLEMENTED() once http://crosbug.com/13052 is fixed. + std::string proxy_value; + if (value->GetAsString(&proxy_value)) { + bool success = + pol.mutable_device_proxy_settings()->ParseFromString(proxy_value); + DCHECK(success); + } else { + NOTREACHED(); + } + } else if (prop == kReleaseChannel) { + em::ReleaseChannelProto* release_channel = pol.mutable_release_channel(); + std::string channel_value; + if (value->GetAsString(&channel_value)) + release_channel->set_release_channel(channel_value); + else + NOTREACHED(); + } else if (prop == kStatsReportingPref) { + em::MetricsEnabledProto* metrics = pol.mutable_metrics_enabled(); + bool metrics_value = false; + if (value->GetAsBoolean(&metrics_value)) + metrics->set_metrics_enabled(metrics_value); + else + NOTREACHED(); + ApplyMetricsSetting(false, metrics_value); + } else if (prop == kAccountsPrefUsers) { + em::UserWhitelistProto* whitelist_proto = pol.mutable_user_whitelist(); + whitelist_proto->clear_user_whitelist(); + base::ListValue& users = static_cast<base::ListValue&>(*value); + for (base::ListValue::const_iterator i = users.begin(); + i != users.end(); ++i) { + std::string email; + if ((*i)->GetAsString(&email)) + whitelist_proto->add_user_whitelist(email.c_str()); + } + } else if (prop == kAccountsPrefEphemeralUsersEnabled) { + em::EphemeralUsersEnabledProto* ephemeral_users_enabled = + pol.mutable_ephemeral_users_enabled(); + bool ephemeral_users_enabled_value = false; + if (value->GetAsBoolean(&ephemeral_users_enabled_value)) + ephemeral_users_enabled->set_ephemeral_users_enabled( + ephemeral_users_enabled_value); + else + NOTREACHED(); + } else { + // The remaining settings don't support Set(), since they are not + // intended to be customizable by the user: + // kAppPack + // kIdleLogoutTimeout + // kIdleLogoutWarningDuration + // kReleaseChannelDelegated + // kReportDeviceVersionInfo + // kReportDeviceActivityTimes + // kReportDeviceBootMode + // kReportDeviceLocation + // kScreenSaverExtensionId + // kScreenSaverTimeout + // kStartUpUrls + + NOTREACHED(); + } + data.set_policy_value(pol.SerializeAsString()); + // Set the cache to the updated value. + policy_ = data; + UpdateValuesCache(); + + if (!signed_settings_cache::Store(data, g_browser_process->local_state())) + LOG(ERROR) << "Couldn't store to the temp storage."; + + if (ownership_status_ == OwnershipService::OWNERSHIP_TAKEN) { + em::PolicyFetchResponse policy_envelope; + policy_envelope.set_policy_data(policy_.SerializeAsString()); + signed_settings_helper_->StartStorePolicyOp( + policy_envelope, + base::Bind(&DeviceSettingsProvider::OnStorePolicyCompleted, + base::Unretained(this))); + } else { + // OnStorePolicyCompleted won't get called in this case so proceed with any + // pending operations immediately. + delete pending_changes_[0].second; + pending_changes_.erase(pending_changes_.begin()); + if (!pending_changes_.empty()) + SetInPolicy(); + } +} + +void DeviceSettingsProvider::FinishSetInPolicy( + SignedSettings::ReturnCode code, + const em::PolicyFetchResponse& policy) { + if (code != SignedSettings::SUCCESS) { + LOG(ERROR) << "Can't serialize to policy error code: " << code; + Reload(); + return; + } + // Update the internal caches and set the trusted flag to true so that we + // can pass the trustedness check in the second call to SetInPolicy. + OnRetrievePolicyCompleted(code, policy); + + SetInPolicy(); +} + +void DeviceSettingsProvider::DecodeLoginPolicies( + const em::ChromeDeviceSettingsProto& policy, + PrefValueMap* new_values_cache) const { + // For all our boolean settings the following is applicable: + // true is default permissive value and false is safe prohibitive value. + // Exceptions: + // kSignedDataRoamingEnabled has a default value of false. + // kAccountsPrefEphemeralUsersEnabled has a default value of false. + if (policy.has_allow_new_users() && + policy.allow_new_users().has_allow_new_users() && + policy.allow_new_users().allow_new_users()) { + // New users allowed, user_whitelist() ignored. + new_values_cache->SetBoolean(kAccountsPrefAllowNewUser, true); + } else if (!policy.has_user_whitelist()) { + // If we have the allow_new_users bool, and it is true, we honor that above. + // In all other cases (don't have it, have it and it is set to false, etc), + // We will honor the user_whitelist() if it is there and populated. + // Otherwise we default to allowing new users. + new_values_cache->SetBoolean(kAccountsPrefAllowNewUser, true); + } else { + new_values_cache->SetBoolean( + kAccountsPrefAllowNewUser, + policy.user_whitelist().user_whitelist_size() == 0); + } + + new_values_cache->SetBoolean( + kAccountsPrefAllowGuest, + !policy.has_guest_mode_enabled() || + !policy.guest_mode_enabled().has_guest_mode_enabled() || + policy.guest_mode_enabled().guest_mode_enabled()); + + new_values_cache->SetBoolean( + kAccountsPrefShowUserNamesOnSignIn, + !policy.has_show_user_names() || + !policy.show_user_names().has_show_user_names() || + policy.show_user_names().show_user_names()); + + new_values_cache->SetBoolean( + kAccountsPrefEphemeralUsersEnabled, + policy.has_ephemeral_users_enabled() && + policy.ephemeral_users_enabled().has_ephemeral_users_enabled() && + policy.ephemeral_users_enabled().ephemeral_users_enabled()); + + base::ListValue* list = new base::ListValue(); + const em::UserWhitelistProto& whitelist_proto = policy.user_whitelist(); + const RepeatedPtrField<std::string>& whitelist = + whitelist_proto.user_whitelist(); + for (RepeatedPtrField<std::string>::const_iterator it = whitelist.begin(); + it != whitelist.end(); ++it) { + list->Append(base::Value::CreateStringValue(*it)); + } + new_values_cache->SetValue(kAccountsPrefUsers, list); +} + +void DeviceSettingsProvider::DecodeKioskPolicies( + const em::ChromeDeviceSettingsProto& policy, + PrefValueMap* new_values_cache) const { + if (policy.has_forced_logout_timeouts()) { + if (policy.forced_logout_timeouts().has_idle_logout_timeout()) { + new_values_cache->SetInteger( + kIdleLogoutTimeout, + policy.forced_logout_timeouts().idle_logout_timeout()); + } + + if (policy.forced_logout_timeouts().has_idle_logout_warning_duration()) { + new_values_cache->SetInteger( + kIdleLogoutWarningDuration, + policy.forced_logout_timeouts().idle_logout_warning_duration()); + } + } + + if (policy.has_login_screen_saver()) { + if (policy.login_screen_saver().has_screen_saver_timeout()) { + new_values_cache->SetInteger( + kScreenSaverTimeout, + policy.login_screen_saver().screen_saver_timeout()); + } + + if (policy.login_screen_saver().has_screen_saver_extension_id()) { + new_values_cache->SetString( + kScreenSaverExtensionId, + policy.login_screen_saver().screen_saver_extension_id()); + } + } + + if (policy.has_app_pack()) { + typedef RepeatedPtrField<em::AppPackEntryProto> proto_type; + base::ListValue* list = new base::ListValue; + const proto_type& app_pack = policy.app_pack().app_pack(); + for (proto_type::const_iterator it = app_pack.begin(); + it != app_pack.end(); ++it) { + base::DictionaryValue* entry = new base::DictionaryValue; + if (it->has_extension_id()) { + entry->SetString(policy::AppPackUpdater::kExtensionId, + it->extension_id()); + } + if (it->has_update_url()) + entry->SetString(policy::AppPackUpdater::kUpdateUrl, it->update_url()); + list->Append(entry); + } + new_values_cache->SetValue(kAppPack, list); + } + + if (policy.has_start_up_urls()) { + base::ListValue* list = new base::ListValue(); + const em::StartUpUrlsProto& urls_proto = policy.start_up_urls(); + const RepeatedPtrField<std::string>& urls = urls_proto.start_up_urls(); + for (RepeatedPtrField<std::string>::const_iterator it = urls.begin(); + it != urls.end(); ++it) { + list->Append(base::Value::CreateStringValue(*it)); + } + new_values_cache->SetValue(kStartUpUrls, list); + } +} + +void DeviceSettingsProvider::DecodeNetworkPolicies( + const em::ChromeDeviceSettingsProto& policy, + PrefValueMap* new_values_cache) const { + new_values_cache->SetBoolean( + kSignedDataRoamingEnabled, + policy.has_data_roaming_enabled() && + policy.data_roaming_enabled().has_data_roaming_enabled() && + policy.data_roaming_enabled().data_roaming_enabled()); + + // TODO(cmasone): NOTIMPLEMENTED() once http://crosbug.com/13052 is fixed. + std::string serialized; + if (policy.has_device_proxy_settings() && + policy.device_proxy_settings().SerializeToString(&serialized)) { + new_values_cache->SetString(kSettingProxyEverywhere, serialized); + } +} + +void DeviceSettingsProvider::DecodeReportingPolicies( + const em::ChromeDeviceSettingsProto& policy, + PrefValueMap* new_values_cache) const { + if (policy.has_device_reporting()) { + if (policy.device_reporting().has_report_version_info()) { + new_values_cache->SetBoolean( + kReportDeviceVersionInfo, + policy.device_reporting().report_version_info()); + } + if (policy.device_reporting().has_report_activity_times()) { + new_values_cache->SetBoolean( + kReportDeviceActivityTimes, + policy.device_reporting().report_activity_times()); + } + if (policy.device_reporting().has_report_boot_mode()) { + new_values_cache->SetBoolean( + kReportDeviceBootMode, + policy.device_reporting().report_boot_mode()); + } + // Device location reporting needs to pass privacy review before it can be + // enabled. crosbug.com/24681 + // if (policy.device_reporting().has_report_location()) { + // new_values_cache->SetBoolean( + // kReportDeviceLocation, + // policy.device_reporting().report_location()); + // } + } +} + +void DeviceSettingsProvider::DecodeGenericPolicies( + const em::ChromeDeviceSettingsProto& policy, + PrefValueMap* new_values_cache) const { + if (policy.has_metrics_enabled()) { + new_values_cache->SetBoolean(kStatsReportingPref, + policy.metrics_enabled().metrics_enabled()); + } else { + new_values_cache->SetBoolean(kStatsReportingPref, HasOldMetricsFile()); + } + + if (!policy.has_release_channel() || + !policy.release_channel().has_release_channel()) { + // Default to an invalid channel (will be ignored). + new_values_cache->SetString(kReleaseChannel, ""); + } else { + new_values_cache->SetString(kReleaseChannel, + policy.release_channel().release_channel()); + } + + new_values_cache->SetBoolean( + kReleaseChannelDelegated, + policy.has_release_channel() && + policy.release_channel().has_release_channel_delegated() && + policy.release_channel().release_channel_delegated()); +} + +void DeviceSettingsProvider::UpdateValuesCache() { + const em::PolicyData data = policy(); + PrefValueMap new_values_cache; + + if (data.has_username() && !data.has_request_token()) + new_values_cache.SetString(kDeviceOwner, data.username()); + + em::ChromeDeviceSettingsProto pol; + pol.ParseFromString(data.policy_value()); + + DecodeLoginPolicies(pol, &new_values_cache); + DecodeKioskPolicies(pol, &new_values_cache); + DecodeNetworkPolicies(pol, &new_values_cache); + DecodeReportingPolicies(pol, &new_values_cache); + DecodeGenericPolicies(pol, &new_values_cache); + + // Collect all notifications but send them only after we have swapped the + // cache so that if somebody actually reads the cache will be already valid. + std::vector<std::string> notifications; + // Go through the new values and verify in the old ones. + PrefValueMap::iterator iter = new_values_cache.begin(); + for (; iter != new_values_cache.end(); ++iter) { + const base::Value* old_value; + if (!values_cache_.GetValue(iter->first, &old_value) || + !old_value->Equals(iter->second)) { + notifications.push_back(iter->first); + } + } + // Now check for values that have been removed from the policy blob. + for (iter = values_cache_.begin(); iter != values_cache_.end(); ++iter) { + const base::Value* value; + if (!new_values_cache.GetValue(iter->first, &value)) + notifications.push_back(iter->first); + } + // Swap and notify. + values_cache_.Swap(&new_values_cache); + for (size_t i = 0; i < notifications.size(); ++i) + NotifyObservers(notifications[i]); +} + +void DeviceSettingsProvider::ApplyMetricsSetting(bool use_file, + bool new_value) const { + // TODO(pastarmovj): Remove this once migration is not needed anymore. + // If the value is not set we should try to migrate legacy consent file. + if (use_file) { + new_value = HasOldMetricsFile(); + // Make sure the values will get eventually written to the policy file. + migration_helper_->AddMigrationValue( + kStatsReportingPref, base::Value::CreateBooleanValue(new_value)); + migration_helper_->MigrateValues(); + LOG(INFO) << "No metrics policy set will revert to checking " + << "consent file which is " + << (new_value ? "on." : "off."); + } + VLOG(1) << "Metrics policy is being set to : " << new_value + << "(use file : " << use_file << ")"; + // TODO(pastarmovj): Remove this once we don't need to regenerate the + // consent file for the GUID anymore. + OptionsUtil::ResolveMetricsReportingEnabled(new_value); +} + +void DeviceSettingsProvider::ApplyRoamingSetting(bool new_value) const { + NetworkLibrary* cros = CrosLibrary::Get()->GetNetworkLibrary(); + const NetworkDevice* cellular = cros->FindCellularDevice(); + if (cellular) { + bool device_value = cellular->data_roaming_allowed(); + if (!device_value && cros->IsCellularAlwaysInRoaming()) { + // If operator requires roaming always enabled, ignore supplied value + // and set data roaming allowed in true always. + cros->SetCellularDataRoamingAllowed(true); + } else if (device_value != new_value) { + cros->SetCellularDataRoamingAllowed(new_value); + } + } +} + +void DeviceSettingsProvider::ApplySideEffects() const { + const em::PolicyData data = policy(); + em::ChromeDeviceSettingsProto pol; + pol.ParseFromString(data.policy_value()); + // First migrate metrics settings as needed. + if (pol.has_metrics_enabled()) + ApplyMetricsSetting(false, pol.metrics_enabled().metrics_enabled()); + else + ApplyMetricsSetting(true, false); + // Next set the roaming setting as needed. + ApplyRoamingSetting(pol.has_data_roaming_enabled() ? + pol.data_roaming_enabled().data_roaming_enabled() : false); +} + +bool DeviceSettingsProvider::MitigateMissingPolicy() { + // First check if the device has been owned already and if not exit + // immediately. + if (g_browser_process->browser_policy_connector()->GetDeviceMode() != + policy::DEVICE_MODE_CONSUMER) { + return false; + } + + // If we are here the policy file were corrupted or missing. This can happen + // because we are migrating Pre R11 device to the new secure policies or there + // was an attempt to circumvent policy system. In this case we should populate + // the policy cache with "safe-mode" defaults which should allow the owner to + // log in but lock the device for anyone else until the policy blob has been + // recreated by the session manager. + LOG(ERROR) << "Corruption of the policy data has been detected." + << "Switching to \"safe-mode\" policies until the owner logs in " + << "to regenerate the policy data."; + values_cache_.SetBoolean(kAccountsPrefAllowNewUser, true); + values_cache_.SetBoolean(kAccountsPrefAllowGuest, true); + values_cache_.SetBoolean(kPolicyMissingMitigationMode, true); + trusted_status_ = TRUSTED; + // Make sure we will recreate the policy once the owner logs in. + // Any value not in this list will be left to the default which is fine as + // we repopulate the whitelist with the owner and all other existing users + // every time the owner enables whitelist filtering on the UI. + migration_helper_->AddMigrationValue( + kAccountsPrefAllowNewUser, base::Value::CreateBooleanValue(true)); + migration_helper_->MigrateValues(); + return true; +} + +const base::Value* DeviceSettingsProvider::Get(const std::string& path) const { + if (IsControlledSetting(path)) { + const base::Value* value; + if (values_cache_.GetValue(path, &value)) + return value; + } else { + NOTREACHED() << "Trying to get non cros setting."; + } + + return NULL; +} + +DeviceSettingsProvider::TrustedStatus + DeviceSettingsProvider::PrepareTrustedValues(const base::Closure& cb) { + TrustedStatus status = RequestTrustedEntity(); + if (status == TEMPORARILY_UNTRUSTED && !cb.is_null()) + callbacks_.push_back(cb); + return status; +} + +bool DeviceSettingsProvider::HandlesSetting(const std::string& path) const { + return IsControlledSetting(path); +} + +DeviceSettingsProvider::TrustedStatus + DeviceSettingsProvider::RequestTrustedEntity() { + if (ownership_status_ == OwnershipService::OWNERSHIP_NONE) + return TRUSTED; + return trusted_status_; +} + +void DeviceSettingsProvider::OnStorePolicyCompleted( + SignedSettings::ReturnCode code) { + // In any case reload the policy cache to now. + if (code != SignedSettings::SUCCESS) + Reload(); + else + trusted_status_ = TRUSTED; + + // Clear the finished task and proceed with any other stores that could be + // pending by now. + delete pending_changes_[0].second; + pending_changes_.erase(pending_changes_.begin()); + if (!pending_changes_.empty()) + SetInPolicy(); +} + +void DeviceSettingsProvider::OnRetrievePolicyCompleted( + SignedSettings::ReturnCode code, + const em::PolicyFetchResponse& policy_data) { + VLOG(1) << "OnRetrievePolicyCompleted. Error code: " << code + << ", trusted status : " << trusted_status_ + << ", ownership status : " << ownership_status_; + switch (code) { + case SignedSettings::SUCCESS: { + DCHECK(policy_data.has_policy_data()); + policy_.ParseFromString(policy_data.policy_data()); + signed_settings_cache::Store(policy(), + g_browser_process->local_state()); + UpdateValuesCache(); + trusted_status_ = TRUSTED; + // TODO(pastarmovj): Make those side effects responsibility of the + // respective subsystems. + ApplySideEffects(); + break; + } + case SignedSettings::NOT_FOUND: + if (MitigateMissingPolicy()) + break; + case SignedSettings::KEY_UNAVAILABLE: { + if (ownership_status_ != OwnershipService::OWNERSHIP_TAKEN) + NOTREACHED() << "No policies present yet, will use the temp storage."; + trusted_status_ = PERMANENTLY_UNTRUSTED; + break; + } + case SignedSettings::BAD_SIGNATURE: + case SignedSettings::OPERATION_FAILED: { + LOG(ERROR) << "Failed to retrieve cros policies. Reason:" << code; + if (retries_left_ > 0) { + trusted_status_ = TEMPORARILY_UNTRUSTED; + retries_left_ -= 1; + Reload(); + return; + } + LOG(ERROR) << "No retries left"; + trusted_status_ = PERMANENTLY_UNTRUSTED; + break; + } + } + // Notify the observers we are done. + std::vector<base::Closure> callbacks; + callbacks.swap(callbacks_); + for (size_t i = 0; i < callbacks.size(); ++i) + callbacks[i].Run(); +} + +} // namespace chromeos diff --git a/chrome/browser/chromeos/settings/device_settings_provider.h b/chrome/browser/chromeos/settings/device_settings_provider.h new file mode 100644 index 0000000..03735b5 --- /dev/null +++ b/chrome/browser/chromeos/settings/device_settings_provider.h @@ -0,0 +1,173 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_CHROMEOS_SETTINGS_DEVICE_SETTINGS_PROVIDER_H_ +#define CHROME_BROWSER_CHROMEOS_SETTINGS_DEVICE_SETTINGS_PROVIDER_H_ + +#include <string> +#include <utility> +#include <vector> + +#include "base/basictypes.h" +#include "base/callback_forward.h" +#include "chrome/browser/chromeos/settings/cros_settings_provider.h" +#include "chrome/browser/chromeos/settings/ownership_service.h" +#include "chrome/browser/chromeos/settings/signed_settings_migration_helper.h" +#include "chrome/browser/policy/proto/device_management_backend.pb.h" +#include "chrome/browser/prefs/pref_value_map.h" +#include "content/public/browser/notification_registrar.h" + +namespace base { +class Value; +} + +namespace enterprise_management { +class ChromeDeviceSettingsProto; +} // namespace enterprise_management + +namespace chromeos { + +// CrosSettingsProvider implementation that works with SignedSettings. +class DeviceSettingsProvider : public CrosSettingsProvider, + public content::NotificationObserver { + public: + DeviceSettingsProvider(const NotifyObserversCallback& notify_cb, + SignedSettingsHelper* signed_settings_helper); + virtual ~DeviceSettingsProvider(); + + // CrosSettingsProvider implementation. + virtual const base::Value* Get(const std::string& path) const OVERRIDE; + virtual TrustedStatus PrepareTrustedValues( + const base::Closure& callback) OVERRIDE; + virtual bool HandlesSetting(const std::string& path) const OVERRIDE; + virtual void Reload() OVERRIDE; + + private: + // CrosSettingsProvider implementation: + virtual void DoSet(const std::string& path, + const base::Value& value) OVERRIDE; + + // content::NotificationObserver implementation: + virtual void Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) OVERRIDE; + + const enterprise_management::PolicyData policy() const; + + // Populates in-memory cache from the local_state cache that is used to store + // signed settings before the device is owned and to speed up policy + // availability before the policy blob is fetched on boot. + void RetrieveCachedData(); + + // Stores a value from the |pending_changes_| queue in the signed settings. + // If the device is not owned yet the data ends up only in the local_state + // cache and is serialized once ownership is acquired. + void SetInPolicy(); + + // Finalizes stores to the policy file if the cache is dirty. + void FinishSetInPolicy( + SignedSettings::ReturnCode code, + const enterprise_management::PolicyFetchResponse& policy); + + // Decode the various groups of policies. + void DecodeLoginPolicies( + const enterprise_management::ChromeDeviceSettingsProto& policy, + PrefValueMap* new_values_cache) const; + void DecodeKioskPolicies( + const enterprise_management::ChromeDeviceSettingsProto& policy, + PrefValueMap* new_values_cache) const; + void DecodeNetworkPolicies( + const enterprise_management::ChromeDeviceSettingsProto& policy, + PrefValueMap* new_values_cache) const; + void DecodeReportingPolicies( + const enterprise_management::ChromeDeviceSettingsProto& policy, + PrefValueMap* new_values_cache) const; + void DecodeGenericPolicies( + const enterprise_management::ChromeDeviceSettingsProto& policy, + PrefValueMap* new_values_cache) const; + + // Parses the policy cache and fills the cache of base::Value objects. + void UpdateValuesCache(); + + // Applies the metrics policy and if not set migrates the legacy file. + void ApplyMetricsSetting(bool use_file, bool new_value) const; + + // Applies the data roaming policy. + void ApplyRoamingSetting(bool new_value) const; + + // Applies any changes of the policies that are not handled by the respective + // subsystems. + void ApplySideEffects() const; + + // In case of missing policy blob we should verify if this is upgrade of + // machine owned from pre version 12 OS and the user never touched the device + // settings. In this case revert to defaults and let people in until the owner + // comes and changes that. + bool MitigateMissingPolicy(); + + // Called right before boolean property is changed. + void OnBooleanPropertyChange(const std::string& path, bool new_value); + + // Checks if the current cache value can be trusted for being representative + // for the disk cache. + TrustedStatus RequestTrustedEntity(); + + // Called right after signed value was checked. + void OnPropertyRetrieve(const std::string& path, + const base::Value* value, + bool use_default_value); + + // Callback of StorePolicyOp for ordinary policy stores. + void OnStorePolicyCompleted(SignedSettings::ReturnCode code); + + // Callback of RetrievePolicyOp for ordinary policy [re]loads. + void OnRetrievePolicyCompleted( + SignedSettings::ReturnCode code, + const enterprise_management::PolicyFetchResponse& policy); + + // These setters are for test use only. + void set_ownership_status(OwnershipService::Status status) { + ownership_status_ = status; + } + void set_trusted_status(TrustedStatus status) { + trusted_status_ = status; + } + void set_retries_left(int retries) { + retries_left_ = retries; + } + + // Pending callbacks that need to be invoked after settings verification. + std::vector<base::Closure> callbacks_; + + SignedSettingsHelper* signed_settings_helper_; + OwnershipService::Status ownership_status_; + mutable scoped_ptr<SignedSettingsMigrationHelper> migration_helper_; + + content::NotificationRegistrar registrar_; + + // In order to guard against occasional failure to fetch a property + // we allow for some number of retries. + int retries_left_; + + enterprise_management::PolicyData policy_; + TrustedStatus trusted_status_; + + PrefValueMap values_cache_; + + // This is a queue for set requests, because those need to be sequential. + typedef std::pair<std::string, base::Value*> PendingQueueElement; + std::vector<PendingQueueElement> pending_changes_; + + friend class DeviceSettingsProviderTest; + FRIEND_TEST_ALL_PREFIXES(DeviceSettingsProviderTest, + InitializationTestUnowned); + FRIEND_TEST_ALL_PREFIXES(DeviceSettingsProviderTest, + PolicyFailedPermanentlyNotification); + FRIEND_TEST_ALL_PREFIXES(DeviceSettingsProviderTest, PolicyLoadNotification); + DISALLOW_COPY_AND_ASSIGN(DeviceSettingsProvider); +}; + +} // namespace chromeos + +#endif // CHROME_BROWSER_CHROMEOS_SETTINGS_DEVICE_SETTINGS_PROVIDER_H_ diff --git a/chrome/browser/chromeos/settings/device_settings_provider_unittest.cc b/chrome/browser/chromeos/settings/device_settings_provider_unittest.cc new file mode 100644 index 0000000..f8abd165 --- /dev/null +++ b/chrome/browser/chromeos/settings/device_settings_provider_unittest.cc @@ -0,0 +1,257 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/chromeos/settings/device_settings_provider.h" + +#include <string> + +#include "base/bind.h" +#include "base/message_loop.h" +#include "base/values.h" +#include "chrome/browser/chromeos/cros/cros_library.h" +#include "chrome/browser/chromeos/login/mock_user_manager.h" +#include "chrome/browser/chromeos/settings/cros_settings_names.h" +#include "chrome/browser/chromeos/settings/mock_signed_settings_helper.h" +#include "chrome/browser/chromeos/settings/ownership_service.h" +#include "chrome/browser/policy/proto/chrome_device_policy.pb.h" +#include "chrome/browser/policy/proto/device_management_backend.pb.h" +#include "chrome/test/base/testing_browser_process.h" +#include "chrome/test/base/testing_pref_service.h" +#include "content/public/test/test_browser_thread.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace em = enterprise_management; +namespace chromeos { + +using ::testing::_; +using ::testing::AnyNumber; +using ::testing::Mock; +using ::testing::Return; +using ::testing::SaveArg; + +class DeviceSettingsProviderTest: public testing::Test { +public: + MOCK_METHOD1(SettingChanged, void(const std::string&)); + MOCK_METHOD0(GetTrustedCallback, void(void)); + +protected: + DeviceSettingsProviderTest() + : message_loop_(MessageLoop::TYPE_UI), + ui_thread_(content::BrowserThread::UI, &message_loop_), + file_thread_(content::BrowserThread::FILE, &message_loop_), + local_state_(static_cast<TestingBrowserProcess*>(g_browser_process)) { + } + + virtual ~DeviceSettingsProviderTest() { + } + + virtual void SetUp() OVERRIDE { + PrepareEmptyPolicy(); + + EXPECT_CALL(*this, SettingChanged(_)) + .Times(AnyNumber()); + + EXPECT_CALL(signed_settings_helper_, StartRetrievePolicyOp(_)) + .WillRepeatedly( + MockSignedSettingsHelperRetrievePolicy(SignedSettings::SUCCESS, + policy_blob_)); + EXPECT_CALL(signed_settings_helper_, StartStorePolicyOp(_,_)) + .WillRepeatedly(DoAll( + SaveArg<0>(&policy_blob_), + MockSignedSettingsHelperStorePolicy(SignedSettings::SUCCESS))); + + EXPECT_CALL(*mock_user_manager_.user_manager(), IsCurrentUserOwner()) + .WillRepeatedly(Return(true)); + + provider_.reset( + new DeviceSettingsProvider( + base::Bind(&DeviceSettingsProviderTest::SettingChanged, + base::Unretained(this)), + &signed_settings_helper_)); + provider_->set_ownership_status(OwnershipService::OWNERSHIP_TAKEN); + // To prevent flooding the logs. + provider_->set_retries_left(1); + provider_->Reload(); + } + + void PrepareEmptyPolicy() { + em::PolicyData policy; + em::ChromeDeviceSettingsProto pol; + // Set metrics to disabled to prevent us from running into code that is not + // mocked. + pol.mutable_metrics_enabled()->set_metrics_enabled(false); + policy.set_policy_type(chromeos::kDevicePolicyType); + policy.set_username("me@owner"); + policy.set_policy_value(pol.SerializeAsString()); + // Wipe the signed settings store. + policy_blob_.set_policy_data(policy.SerializeAsString()); + policy_blob_.set_policy_data_signature("false"); + } + + em::PolicyFetchResponse policy_blob_; + + scoped_ptr<DeviceSettingsProvider> provider_; + + MessageLoop message_loop_; + content::TestBrowserThread ui_thread_; + content::TestBrowserThread file_thread_; + + ScopedTestingLocalState local_state_; + + MockSignedSettingsHelper signed_settings_helper_; + + ScopedStubCrosEnabler stub_cros_enabler_; + ScopedMockUserManagerEnabler mock_user_manager_; +}; + +TEST_F(DeviceSettingsProviderTest, InitializationTest) { + // Verify that the policy blob has been correctly parsed and trusted. + // The trusted flag should be set before the call to PrepareTrustedValues. + EXPECT_EQ(CrosSettingsProvider::TRUSTED, + provider_->PrepareTrustedValues( + base::Bind(&DeviceSettingsProviderTest::GetTrustedCallback, + base::Unretained(this)))); + const base::Value* value = provider_->Get(kStatsReportingPref); + ASSERT_TRUE(value); + bool bool_value; + EXPECT_TRUE(value->GetAsBoolean(&bool_value)); + EXPECT_FALSE(bool_value); +} + +TEST_F(DeviceSettingsProviderTest, InitializationTestUnowned) { + // No calls to the SignedSettingsHelper should occur in this case! + Mock::VerifyAndClear(&signed_settings_helper_); + + provider_->set_ownership_status(OwnershipService::OWNERSHIP_NONE); + provider_->Reload(); + // The trusted flag should be set before the call to PrepareTrustedValues. + EXPECT_EQ(CrosSettingsProvider::TRUSTED, + provider_->PrepareTrustedValues( + base::Bind(&DeviceSettingsProviderTest::GetTrustedCallback, + base::Unretained(this)))); + const base::Value* value = provider_->Get(kReleaseChannel); + ASSERT_TRUE(value); + std::string string_value; + EXPECT_TRUE(value->GetAsString(&string_value)); + EXPECT_TRUE(string_value.empty()); + + // Sets should succeed though and be readable from the cache. + base::StringValue new_value("stable-channel"); + provider_->Set(kReleaseChannel, new_value); + // Do one more reload here to make sure we don't flip randomly between stores. + provider_->Reload(); + // Verify the change has not been applied. + const base::Value* saved_value = provider_->Get(kReleaseChannel); + ASSERT_TRUE(saved_value); + EXPECT_TRUE(saved_value->GetAsString(&string_value)); + ASSERT_EQ("stable-channel", string_value); +} + +TEST_F(DeviceSettingsProviderTest, SetPrefFailed) { + // If we are not the owner no sets should work. + EXPECT_CALL(*mock_user_manager_.user_manager(), IsCurrentUserOwner()) + .WillOnce(Return(false)); + base::FundamentalValue value(true); + provider_->Set(kStatsReportingPref, value); + // Verify the change has not been applied. + const base::Value* saved_value = provider_->Get(kStatsReportingPref); + ASSERT_TRUE(saved_value); + bool bool_value; + EXPECT_TRUE(saved_value->GetAsBoolean(&bool_value)); + EXPECT_FALSE(bool_value); +} + +TEST_F(DeviceSettingsProviderTest, SetPrefSucceed) { + base::FundamentalValue value(true); + provider_->Set(kStatsReportingPref, value); + // Verify the change has not been applied. + const base::Value* saved_value = provider_->Get(kStatsReportingPref); + ASSERT_TRUE(saved_value); + bool bool_value; + EXPECT_TRUE(saved_value->GetAsBoolean(&bool_value)); + EXPECT_TRUE(bool_value); +} + +TEST_F(DeviceSettingsProviderTest, PolicyRetrievalFailedBadSingature) { + // No calls to the SignedSettingsHelper should occur in this case! + Mock::VerifyAndClear(&signed_settings_helper_); + EXPECT_CALL(signed_settings_helper_, StartRetrievePolicyOp(_)) + .WillRepeatedly( + MockSignedSettingsHelperRetrievePolicy( + SignedSettings::BAD_SIGNATURE, + policy_blob_)); + provider_->Reload(); + // Verify that the cache policy blob is not "trusted". + EXPECT_EQ(CrosSettingsProvider::PERMANENTLY_UNTRUSTED, + provider_->PrepareTrustedValues( + base::Bind(&DeviceSettingsProviderTest::GetTrustedCallback, + base::Unretained(this)))); +} + +TEST_F(DeviceSettingsProviderTest, PolicyRetrievalOperationFailedPermanently) { + // No calls to the SignedSettingsHelper should occur in this case! + Mock::VerifyAndClear(&signed_settings_helper_); + EXPECT_CALL(signed_settings_helper_, StartRetrievePolicyOp(_)) + .WillRepeatedly( + MockSignedSettingsHelperRetrievePolicy( + SignedSettings::OPERATION_FAILED, + policy_blob_)); + provider_->Reload(); + // Verify that the cache policy blob is not "trusted". + EXPECT_EQ(CrosSettingsProvider::PERMANENTLY_UNTRUSTED, + provider_->PrepareTrustedValues( + base::Bind(&DeviceSettingsProviderTest::GetTrustedCallback, + base::Unretained(this)))); +} + +TEST_F(DeviceSettingsProviderTest, PolicyRetrievalOperationFailedOnce) { + // No calls to the SignedSettingsHelper should occur in this case! + Mock::VerifyAndClear(&signed_settings_helper_); + EXPECT_CALL(signed_settings_helper_, StartRetrievePolicyOp(_)) + .WillOnce( + MockSignedSettingsHelperRetrievePolicy( + SignedSettings::OPERATION_FAILED, + policy_blob_)) + .WillRepeatedly( + MockSignedSettingsHelperRetrievePolicy( + SignedSettings::SUCCESS, + policy_blob_)); + // Should be trusted after an automatic reload. + provider_->Reload(); + // Verify that the cache policy blob is not "trusted". + EXPECT_EQ(CrosSettingsProvider::TRUSTED, + provider_->PrepareTrustedValues( + base::Bind(&DeviceSettingsProviderTest::GetTrustedCallback, + base::Unretained(this)))); +} + +TEST_F(DeviceSettingsProviderTest, PolicyFailedPermanentlyNotification) { + Mock::VerifyAndClear(&signed_settings_helper_); + EXPECT_CALL(signed_settings_helper_, StartRetrievePolicyOp(_)) + .WillRepeatedly( + MockSignedSettingsHelperRetrievePolicy( + SignedSettings::OPERATION_FAILED, + policy_blob_)); + + provider_->set_trusted_status(CrosSettingsProvider::TEMPORARILY_UNTRUSTED); + EXPECT_CALL(*this, GetTrustedCallback()); + EXPECT_EQ(CrosSettingsProvider::TEMPORARILY_UNTRUSTED, + provider_->PrepareTrustedValues( + base::Bind(&DeviceSettingsProviderTest::GetTrustedCallback, + base::Unretained(this)))); + provider_->Reload(); +} + +TEST_F(DeviceSettingsProviderTest, PolicyLoadNotification) { + provider_->set_trusted_status(CrosSettingsProvider::TEMPORARILY_UNTRUSTED); + EXPECT_CALL(*this, GetTrustedCallback()); + EXPECT_EQ(CrosSettingsProvider::TEMPORARILY_UNTRUSTED, + provider_->PrepareTrustedValues( + base::Bind(&DeviceSettingsProviderTest::GetTrustedCallback, + base::Unretained(this)))); + provider_->Reload(); +} + +} // namespace chromeos diff --git a/chrome/browser/chromeos/settings/mock_owner_key_utils.cc b/chrome/browser/chromeos/settings/mock_owner_key_utils.cc new file mode 100644 index 0000000..55ed5d4 --- /dev/null +++ b/chrome/browser/chromeos/settings/mock_owner_key_utils.cc @@ -0,0 +1,20 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/chromeos/settings/mock_owner_key_utils.h" + +namespace chromeos { + +MockKeyUtils::MockKeyUtils() {} + +MockKeyUtils::~MockKeyUtils() {} + +MockInjector::MockInjector(MockKeyUtils* mock) : transient_(mock) {} + +MockInjector::~MockInjector() {} + +OwnerKeyUtils* MockInjector::CreateOwnerKeyUtils() { + return transient_.get(); +} +} // namespace chromeos diff --git a/chrome/browser/chromeos/settings/mock_owner_key_utils.h b/chrome/browser/chromeos/settings/mock_owner_key_utils.h new file mode 100644 index 0000000..1a55179 --- /dev/null +++ b/chrome/browser/chromeos/settings/mock_owner_key_utils.h @@ -0,0 +1,61 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_CHROMEOS_SETTINGS_MOCK_OWNER_KEY_UTILS_H_ +#define CHROME_BROWSER_CHROMEOS_SETTINGS_MOCK_OWNER_KEY_UTILS_H_ + +#include <vector> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/file_path.h" +#include "chrome/browser/chromeos/settings/owner_key_utils.h" +#include "crypto/rsa_private_key.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace chromeos { + +class MockKeyUtils : public OwnerKeyUtils { + public: + MockKeyUtils(); + + MOCK_METHOD2(ImportPublicKey, bool(const FilePath& key_file, + std::vector<uint8>* output)); + MOCK_METHOD3(Verify, bool(const std::string& data, + const std::vector<uint8> signature, + const std::vector<uint8> public_key)); + MOCK_METHOD3(Sign, bool(const std::string& data, + std::vector<uint8>* OUT_signature, + crypto::RSAPrivateKey* key)); + MOCK_METHOD1(FindPrivateKey, + crypto::RSAPrivateKey*(const std::vector<uint8>& key)); + MOCK_METHOD0(GetOwnerKeyFilePath, FilePath()); + MOCK_METHOD2(ExportPublicKeyToFile, bool(crypto::RSAPrivateKey* pair, + const FilePath& key_file)); + protected: + virtual ~MockKeyUtils(); + + private: + DISALLOW_COPY_AND_ASSIGN(MockKeyUtils); +}; + +class MockInjector : public OwnerKeyUtils::Factory { + public: + // Takes ownership of |mock|. + explicit MockInjector(MockKeyUtils* mock); + virtual ~MockInjector(); + + // If this is called, its caller takes ownership of |transient_|. + // If it's never called, |transient_| remains our problem. + virtual OwnerKeyUtils* CreateOwnerKeyUtils() OVERRIDE; + + private: + scoped_refptr<MockKeyUtils> transient_; + DISALLOW_COPY_AND_ASSIGN(MockInjector); +}; + +} // namespace chromeos + +#endif // CHROME_BROWSER_CHROMEOS_SETTINGS_MOCK_OWNER_KEY_UTILS_H_ diff --git a/chrome/browser/chromeos/settings/mock_ownership_service.cc b/chrome/browser/chromeos/settings/mock_ownership_service.cc new file mode 100644 index 0000000..6af5061 --- /dev/null +++ b/chrome/browser/chromeos/settings/mock_ownership_service.cc @@ -0,0 +1,13 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/chromeos/settings/mock_ownership_service.h" + +namespace chromeos { + +MockOwnershipService::MockOwnershipService() {} + +MockOwnershipService::~MockOwnershipService() {} + +} // namespace chromeos diff --git a/chrome/browser/chromeos/settings/mock_ownership_service.h b/chrome/browser/chromeos/settings/mock_ownership_service.h new file mode 100644 index 0000000..df8c17f --- /dev/null +++ b/chrome/browser/chromeos/settings/mock_ownership_service.h @@ -0,0 +1,33 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_CHROMEOS_SETTINGS_MOCK_OWNERSHIP_SERVICE_H_ +#define CHROME_BROWSER_CHROMEOS_SETTINGS_MOCK_OWNERSHIP_SERVICE_H_ + +#include <string> + +#include "chrome/browser/chromeos/settings/ownership_service.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace chromeos { + +class MockOwnershipService : public OwnershipService { + public: + MockOwnershipService(); + virtual ~MockOwnershipService(); + + MOCK_METHOD0(IsAlreadyOwned, bool(void)); + MOCK_METHOD1(GetStatus, OwnershipService::Status(bool)); + MOCK_METHOD0(StartLoadOwnerKeyAttempt, void(void)); + MOCK_METHOD2(StartSigningAttempt, void(const std::string&, + OwnerManager::Delegate*)); + MOCK_METHOD3(StartVerifyAttempt, void(const std::string&, + const std::vector<uint8>&, + OwnerManager::Delegate*)); + MOCK_METHOD0(CurrentUserIsOwner, bool(void)); +}; + +} // namespace chromeos + +#endif // CHROME_BROWSER_CHROMEOS_SETTINGS_MOCK_OWNERSHIP_SERVICE_H_ diff --git a/chrome/browser/chromeos/settings/mock_signed_settings_helper.cc b/chrome/browser/chromeos/settings/mock_signed_settings_helper.cc new file mode 100644 index 0000000..2386b6d --- /dev/null +++ b/chrome/browser/chromeos/settings/mock_signed_settings_helper.cc @@ -0,0 +1,17 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/chromeos/settings/mock_signed_settings_helper.h" + +#include "chrome/browser/policy/proto/device_management_backend.pb.h" + +namespace chromeos { + +MockSignedSettingsHelper::MockSignedSettingsHelper() { +} + +MockSignedSettingsHelper::~MockSignedSettingsHelper() { +} + +} // namespace chromeos diff --git a/chrome/browser/chromeos/settings/mock_signed_settings_helper.h b/chrome/browser/chromeos/settings/mock_signed_settings_helper.h new file mode 100644 index 0000000..aad2f5a --- /dev/null +++ b/chrome/browser/chromeos/settings/mock_signed_settings_helper.h @@ -0,0 +1,39 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_CHROMEOS_SETTINGS_MOCK_SIGNED_SETTINGS_HELPER_H_ +#define CHROME_BROWSER_CHROMEOS_SETTINGS_MOCK_SIGNED_SETTINGS_HELPER_H_ + +#include "base/basictypes.h" +#include "chrome/browser/chromeos/settings/signed_settings_helper.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace chromeos { + +class MockSignedSettingsHelper : public chromeos::SignedSettingsHelper { + public: + MockSignedSettingsHelper(); + virtual ~MockSignedSettingsHelper(); + + MOCK_METHOD2(StartStorePolicyOp, + void(const enterprise_management::PolicyFetchResponse&, + SignedSettingsHelper::StorePolicyCallback)); + MOCK_METHOD1(StartRetrievePolicyOp, + void(SignedSettingsHelper::RetrievePolicyCallback)); + + private: + DISALLOW_COPY_AND_ASSIGN(MockSignedSettingsHelper); +}; + +ACTION_P(MockSignedSettingsHelperStorePolicy, status_code) { + arg1.Run(status_code); +} + +ACTION_P2(MockSignedSettingsHelperRetrievePolicy, status_code, policy) { + arg0.Run(status_code, policy); +} + +} // namespace chromeos + +#endif // CHROME_BROWSER_CHROMEOS_SETTINGS_MOCK_SIGNED_SETTINGS_HELPER_H_ diff --git a/chrome/browser/chromeos/settings/owner_key_utils.cc b/chrome/browser/chromeos/settings/owner_key_utils.cc new file mode 100644 index 0000000..e211877 --- /dev/null +++ b/chrome/browser/chromeos/settings/owner_key_utils.cc @@ -0,0 +1,175 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/chromeos/settings/owner_key_utils.h" + +#include <limits> + +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/string_util.h" +#include "chrome/browser/chromeos/cros/cros_library.h" +#include "chrome/common/extensions/extension_constants.h" +#include "crypto/rsa_private_key.h" +#include "crypto/signature_creator.h" +#include "crypto/signature_verifier.h" + +using extension_misc::kSignatureAlgorithm; + +namespace chromeos { + +/////////////////////////////////////////////////////////////////////////// +// OwnerKeyUtils + +// static +OwnerKeyUtils::Factory* OwnerKeyUtils::factory_ = NULL; + +OwnerKeyUtils::OwnerKeyUtils() {} + +OwnerKeyUtils::~OwnerKeyUtils() {} + +/////////////////////////////////////////////////////////////////////////// +// OwnerKeyUtilsImpl + +class OwnerKeyUtilsImpl : public OwnerKeyUtils { + public: + OwnerKeyUtilsImpl(); + + bool ImportPublicKey(const FilePath& key_file, + std::vector<uint8>* output); + + bool Verify(const std::string& data, + const std::vector<uint8> signature, + const std::vector<uint8> public_key); + + bool Sign(const std::string& data, + std::vector<uint8>* OUT_signature, + crypto::RSAPrivateKey* key); + + crypto::RSAPrivateKey* FindPrivateKey(const std::vector<uint8>& key); + + FilePath GetOwnerKeyFilePath(); + + protected: + virtual ~OwnerKeyUtilsImpl(); + + bool ExportPublicKeyToFile(crypto::RSAPrivateKey* pair, + const FilePath& key_file); + + private: + // The file outside the owner's encrypted home directory where her + // key will live. + static const char kOwnerKeyFile[]; + + DISALLOW_COPY_AND_ASSIGN(OwnerKeyUtilsImpl); +}; + +// Defined here, instead of up above, because we need OwnerKeyUtilsImpl. +OwnerKeyUtils* OwnerKeyUtils::Create() { + if (!factory_) + return new OwnerKeyUtilsImpl(); + else + return factory_->CreateOwnerKeyUtils(); +} + +// static +const char OwnerKeyUtilsImpl::kOwnerKeyFile[] = "/var/lib/whitelist/owner.key"; + +OwnerKeyUtilsImpl::OwnerKeyUtilsImpl() {} + +OwnerKeyUtilsImpl::~OwnerKeyUtilsImpl() {} + +bool OwnerKeyUtilsImpl::ExportPublicKeyToFile(crypto::RSAPrivateKey* pair, + const FilePath& key_file) { + DCHECK(pair); + bool ok = false; + int safe_file_size = 0; + + std::vector<uint8> to_export; + if (!pair->ExportPublicKey(&to_export)) { + LOG(ERROR) << "Formatting key for export failed!"; + return false; + } + + if (to_export.size() > static_cast<uint>(INT_MAX)) { + LOG(ERROR) << "key is too big! " << to_export.size(); + } else { + safe_file_size = static_cast<int>(to_export.size()); + + ok = (safe_file_size == + file_util::WriteFile(key_file, + reinterpret_cast<char*>(&to_export.front()), + safe_file_size)); + } + return ok; +} + +bool OwnerKeyUtilsImpl::ImportPublicKey(const FilePath& key_file, + std::vector<uint8>* output) { + // Get the file size (must fit in a 32 bit int for NSS). + int64 file_size; + if (!file_util::GetFileSize(key_file, &file_size)) { + LOG(ERROR) << "Could not get size of " << key_file.value(); + return false; + } + if (file_size > static_cast<int64>(std::numeric_limits<int>::max())) { + LOG(ERROR) << key_file.value() << "is " + << file_size << "bytes!!! Too big!"; + return false; + } + int32 safe_file_size = static_cast<int32>(file_size); + + output->resize(safe_file_size); + + if (safe_file_size == 0) { + LOG(WARNING) << "Public key file is empty. This seems wrong."; + return false; + } + + // Get the key data off of disk + int data_read = file_util::ReadFile(key_file, + reinterpret_cast<char*>(&(output->at(0))), + safe_file_size); + return data_read == safe_file_size; +} + +bool OwnerKeyUtilsImpl::Verify(const std::string& data, + const std::vector<uint8> signature, + const std::vector<uint8> public_key) { + crypto::SignatureVerifier verifier; + if (!verifier.VerifyInit(kSignatureAlgorithm, sizeof(kSignatureAlgorithm), + &signature[0], signature.size(), + &public_key[0], public_key.size())) { + return false; + } + + verifier.VerifyUpdate(reinterpret_cast<const uint8*>(data.c_str()), + data.length()); + return (verifier.VerifyFinal()); +} + +bool OwnerKeyUtilsImpl::Sign(const std::string& data, + std::vector<uint8>* OUT_signature, + crypto::RSAPrivateKey* key) { + scoped_ptr<crypto::SignatureCreator> signer( + crypto::SignatureCreator::Create(key)); + if (!signer->Update(reinterpret_cast<const uint8*>(data.c_str()), + data.length())) { + return false; + } + return signer->Final(OUT_signature); +} + +crypto::RSAPrivateKey* OwnerKeyUtilsImpl::FindPrivateKey( + const std::vector<uint8>& key) { + return crypto::RSAPrivateKey::FindFromPublicKeyInfo(key); +} + +FilePath OwnerKeyUtilsImpl::GetOwnerKeyFilePath() { + return FilePath(OwnerKeyUtilsImpl::kOwnerKeyFile); +} + +} // namespace chromeos diff --git a/chrome/browser/chromeos/settings/owner_key_utils.h b/chrome/browser/chromeos/settings/owner_key_utils.h new file mode 100644 index 0000000..1c9dde5 --- /dev/null +++ b/chrome/browser/chromeos/settings/owner_key_utils.h @@ -0,0 +1,90 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_CHROMEOS_SETTINGS_OWNER_KEY_UTILS_H_ +#define CHROME_BROWSER_CHROMEOS_SETTINGS_OWNER_KEY_UTILS_H_ + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/gtest_prod_util.h" +#include "base/memory/ref_counted.h" + +class FilePath; + +namespace crypto { +class RSAPrivateKey; +} + +namespace chromeos { + +class OwnerKeyUtilsTest; + +class OwnerKeyUtils : public base::RefCounted<OwnerKeyUtils> { + public: + class Factory { + public: + virtual OwnerKeyUtils* CreateOwnerKeyUtils() = 0; + }; + + OwnerKeyUtils(); + + // Sets the factory used by the static method Create to create an + // OwnerKeyUtils. OwnerKeyUtils does not take ownership of + // |factory|. A value of NULL results in an OwnerKeyUtils being + // created directly. +#if defined(UNIT_TEST) + static void set_factory(Factory* factory) { factory_ = factory; } +#endif + + // Creates an OwnerKeyUtils, ownership returns to the caller. If there is no + // Factory (the default) this creates and returns a new OwnerKeyUtils. + static OwnerKeyUtils* Create(); + + // Assumes that the file at |key_file| exists. + // Upon success, returns true and populates |output|. False on failure. + virtual bool ImportPublicKey(const FilePath& key_file, + std::vector<uint8>* output) = 0; + + // Verfiy that |signature| is a Sha1-with-RSA signature over |data| with + // |public_key| + // Returns true if so, false on bad signature or other error. + virtual bool Verify(const std::string& data, + const std::vector<uint8> signature, + const std::vector<uint8> public_key) = 0; + + // Sign |data| with |key| using Sha1 with RSA. If successful, return true + // and populate |OUT_signature|. + virtual bool Sign(const std::string& data, + std::vector<uint8>* OUT_signature, + crypto::RSAPrivateKey* key) = 0; + + // Looks for the private key associated with |key| in the default slot, + // and returns it if it can be found. Returns NULL otherwise. + // Caller takes ownership. + virtual crypto::RSAPrivateKey* FindPrivateKey( + const std::vector<uint8>& key) = 0; + + virtual FilePath GetOwnerKeyFilePath() = 0; + + protected: + virtual ~OwnerKeyUtils(); + + // DER encodes public half of |pair| and writes it out to |key_file|. + // The blob on disk is a DER-encoded X509 SubjectPublicKeyInfo object. + // Returns false on error. + virtual bool ExportPublicKeyToFile(crypto::RSAPrivateKey* pair, + const FilePath& key_file) = 0; + + private: + friend class base::RefCounted<OwnerKeyUtils>; + static Factory* factory_; + + FRIEND_TEST_ALL_PREFIXES(OwnerKeyUtilsTest, ExportImportPublicKey); +}; + +} // namespace chromeos + +#endif // CHROME_BROWSER_CHROMEOS_SETTINGS_OWNER_KEY_UTILS_H_ diff --git a/chrome/browser/chromeos/settings/owner_key_utils_unittest.cc b/chrome/browser/chromeos/settings/owner_key_utils_unittest.cc new file mode 100644 index 0000000..0632b6b --- /dev/null +++ b/chrome/browser/chromeos/settings/owner_key_utils_unittest.cc @@ -0,0 +1,84 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/chromeos/settings/owner_key_utils.h" + +#include <string> +#include <vector> + +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/memory/ref_counted.h" +#include "base/scoped_temp_dir.h" +#include "crypto/nss_util.h" +#include "crypto/nss_util_internal.h" +#include "crypto/rsa_private_key.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace chromeos { + +class OwnerKeyUtilsTest : public ::testing::Test { + public: + OwnerKeyUtilsTest() : utils_(OwnerKeyUtils::Create()) {} + virtual ~OwnerKeyUtilsTest() {} + + virtual void SetUp() { + crypto::OpenPersistentNSSDB(); + } + + // Key generation parameters. + static const uint16 kKeySizeInBits; + + scoped_refptr<OwnerKeyUtils> utils_; +}; + +// We're generating and using 2048-bit RSA keys. +// static +const uint16 OwnerKeyUtilsTest::kKeySizeInBits = 2048; + +TEST_F(OwnerKeyUtilsTest, ExportImportPublicKey) { + scoped_ptr<crypto::RSAPrivateKey> pair( + crypto::RSAPrivateKey::CreateSensitive(kKeySizeInBits)); + ASSERT_NE(pair.get(), reinterpret_cast<crypto::RSAPrivateKey*>(NULL)); + + // Export public key to file. + ScopedTempDir tmpdir; + FilePath tmpfile; + ASSERT_TRUE(tmpdir.CreateUniqueTempDir()); + ASSERT_TRUE(file_util::CreateTemporaryFileInDir(tmpdir.path(), &tmpfile)); + ASSERT_TRUE(utils_->ExportPublicKeyToFile(pair.get(), tmpfile)); + + // Export public key, so that we can compare it to the one we get off disk. + std::vector<uint8> public_key; + ASSERT_TRUE(pair->ExportPublicKey(&public_key)); + std::vector<uint8> from_disk; + ASSERT_TRUE(utils_->ImportPublicKey(tmpfile, &from_disk)); + + std::vector<uint8>::iterator pubkey_it; + std::vector<uint8>::iterator disk_it; + for (pubkey_it = public_key.begin(), disk_it = from_disk.begin(); + pubkey_it < public_key.end(); + pubkey_it++, disk_it++) { + EXPECT_EQ(*pubkey_it, *disk_it); + } +} + +TEST_F(OwnerKeyUtilsTest, ImportPublicKeyFailed) { + ScopedTempDir tmpdir; + FilePath tmpfile; + ASSERT_TRUE(tmpdir.CreateUniqueTempDir()); + + // First test the case where the file is missing which should fail. + std::vector<uint8> from_disk; + ASSERT_FALSE(utils_->ImportPublicKey(tmpfile, &from_disk)); + + // Next try empty file. This should fail and the array should be empty. + from_disk.resize(10); + ASSERT_TRUE(file_util::CreateTemporaryFileInDir(tmpdir.path(), &tmpfile)); + ASSERT_FALSE(utils_->ImportPublicKey(tmpfile, &from_disk)); + ASSERT_FALSE(from_disk.size()); +} + +} // namespace chromeos diff --git a/chrome/browser/chromeos/settings/owner_manager.cc b/chrome/browser/chromeos/settings/owner_manager.cc new file mode 100644 index 0000000..385af76 --- /dev/null +++ b/chrome/browser/chromeos/settings/owner_manager.cc @@ -0,0 +1,147 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/chromeos/settings/owner_manager.h" + +#include <string> +#include <vector> + +#include "base/bind.h" +#include "base/file_path.h" +#include "base/file_util.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/chromeos/boot_times_loader.h" +#include "chrome/browser/chromeos/settings/signed_settings_cache.h" +#include "chrome/common/chrome_notification_types.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/notification_service.h" + +using content::BrowserThread; + +namespace chromeos { + +OwnerManager::OwnerManager() + : private_key_(NULL), + public_key_(0), + utils_(OwnerKeyUtils::Create()) { +} + +void OwnerManager::UpdateOwnerKey(const BrowserThread::ID thread_id, + const std::vector<uint8>& key, + KeyUpdateDelegate* d) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + + public_key_ = key; + + BrowserThread::PostTask( + thread_id, FROM_HERE, + base::Bind(&OwnerManager::CallKeyUpdateDelegate, this, d)); +} + +void OwnerManager::LoadOwnerKey() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + VLOG(1) << "Loading owner key"; + int result = chrome::NOTIFICATION_OWNER_KEY_FETCH_ATTEMPT_SUCCEEDED; + + // If |public_key_| isn't empty, we already have the key, so don't + // try to import again. + if (public_key_.empty() && + !utils_->ImportPublicKey(utils_->GetOwnerKeyFilePath(), &public_key_)) { + result = chrome::NOTIFICATION_OWNER_KEY_FETCH_ATTEMPT_FAILED; + } + + // Whether we loaded the public key or not, send a notification indicating + // that we're done with this attempt. + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&OwnerManager::SendNotification, this, result, + content::NotificationService::NoDetails())); +} + +bool OwnerManager::EnsurePublicKey() { + if (public_key_.empty()) + LoadOwnerKey(); + + return !public_key_.empty(); +} + +bool OwnerManager::EnsurePrivateKey() { + if (!EnsurePublicKey()) + return false; + + if (!private_key_.get()) + private_key_.reset(utils_->FindPrivateKey(public_key_)); + + return private_key_.get() != NULL; +} + +void OwnerManager::Sign(const BrowserThread::ID thread_id, + const std::string& data, + Delegate* d) { + BootTimesLoader::Get()->AddLoginTimeMarker("SignStart", false); + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + + // If it's not the case that we can get both keys... + if (!(EnsurePublicKey() && EnsurePrivateKey())) { + BrowserThread::PostTask( + thread_id, FROM_HERE, + base::Bind(&OwnerManager::CallDelegate, this, d, KEY_UNAVAILABLE, + std::vector<uint8>())); + BootTimesLoader::Get()->AddLoginTimeMarker("SignEnd", false); + return; + } + + VLOG(1) << "Starting signing attempt"; + KeyOpCode return_code = SUCCESS; + std::vector<uint8> signature; + if (!utils_->Sign(data, &signature, private_key_.get())) { + return_code = OPERATION_FAILED; + } + + BrowserThread::PostTask( + thread_id, FROM_HERE, + base::Bind(&OwnerManager::CallDelegate, this, d, return_code, signature)); + BootTimesLoader::Get()->AddLoginTimeMarker("SignEnd", false); +} + +void OwnerManager::Verify(const BrowserThread::ID thread_id, + const std::string& data, + const std::vector<uint8>& signature, + Delegate* d) { + BootTimesLoader::Get()->AddLoginTimeMarker("VerifyStart", false); + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + + if (!EnsurePublicKey()) { + BrowserThread::PostTask( + thread_id, FROM_HERE, + base::Bind(&OwnerManager::CallDelegate, this, d, KEY_UNAVAILABLE, + std::vector<uint8>())); + BootTimesLoader::Get()->AddLoginTimeMarker("VerifyEnd", false); + return; + } + + VLOG(1) << "Starting verify attempt"; + KeyOpCode return_code = SUCCESS; + if (!utils_->Verify(data, signature, public_key_)) { + return_code = OPERATION_FAILED; + } + BrowserThread::PostTask( + thread_id, FROM_HERE, + base::Bind(&OwnerManager::CallDelegate, this, d, return_code, + std::vector<uint8>())); + BootTimesLoader::Get()->AddLoginTimeMarker("VerifyEnd", false); +} + +OwnerManager::~OwnerManager() {} + +void OwnerManager::SendNotification( + int type, + const content::NotificationDetails& details) { + content::NotificationService::current()->Notify( + type, + content::NotificationService::AllSources(), + details); +} + +} // namespace chromeos diff --git a/chrome/browser/chromeos/settings/owner_manager.h b/chrome/browser/chromeos/settings/owner_manager.h new file mode 100644 index 0000000..d3c0ad5 --- /dev/null +++ b/chrome/browser/chromeos/settings/owner_manager.h @@ -0,0 +1,125 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_CHROMEOS_SETTINGS_OWNER_MANAGER_H_ +#define CHROME_BROWSER_CHROMEOS_SETTINGS_OWNER_MANAGER_H_ + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "chrome/browser/chromeos/settings/owner_key_utils.h" +#include "content/public/browser/browser_thread.h" +#include "crypto/rsa_private_key.h" + +namespace content { +class NotificationDetails; +} + +namespace chromeos { + +// This class allows the registration of an Owner of a Chromium OS device. +// It handles generating the appropriate keys and storing them in the +// appropriate locations. +class OwnerManager : public base::RefCountedThreadSafe<OwnerManager> { + public: + // Return codes for public/private key operations. + enum KeyOpCode { + SUCCESS, + KEY_UNAVAILABLE, // The necessary key isn't available yet. + OPERATION_FAILED // The crypto operation failed. + }; + + class Delegate { + public: + // Upon completion of a key operation, this method will be called. + // |return_code| indicates what happened, |payload| will be used to pass + // back any artifacts of the operation. For example, if the operation + // was a signature attempt, the signature blob would come back in |payload|. + virtual void OnKeyOpComplete(const KeyOpCode return_code, + const std::vector<uint8>& payload) = 0; + }; + + class KeyUpdateDelegate { + public: + // Called upon completion of a key update operation. + virtual void OnKeyUpdated() = 0; + }; + + OwnerManager(); + + // Sets a new owner key from a provided memory buffer. + void UpdateOwnerKey(const content::BrowserThread::ID thread_id, + const std::vector<uint8>& key, + KeyUpdateDelegate* d); + + // Pulls the owner's public key off disk and into memory. + // + // Call this on the FILE thread. + void LoadOwnerKey(); + + bool EnsurePublicKey(); + bool EnsurePrivateKey(); + + // Do the actual work of signing |data| with |private_key_|. First, + // ensures that we have the keys we need. Then, computes the signature. + // + // On success, calls d->OnKeyOpComplete() on |thread_id| with a + // successful return code, passing the signaure blob in |payload|. + // On failure, calls d->OnKeyOpComplete() on |thread_id| with an appropriate + // error and passes an empty string for |payload|. + void Sign(const content::BrowserThread::ID thread_id, + const std::string& data, + Delegate* d); + + // Do the actual work of verifying that |signature| is valid over + // |data| with |public_key_|. First, ensures we have the key we + // need, then does the verify. + // + // On success, calls d->OnKeyOpComplete() on |thread_id| with a + // successful return code, passing an empty string for |payload|. + // On failure, calls d->OnKeyOpComplete() on |thread_id| with an appropriate + // error code, passing an empty string for |payload|. + void Verify(const content::BrowserThread::ID thread_id, + const std::string& data, + const std::vector<uint8>& signature, + Delegate* d); + + protected: + virtual ~OwnerManager(); + + private: + friend class base::RefCountedThreadSafe<OwnerManager>; + + // A helper method to send a notification on another thread. + void SendNotification(int type, + const content::NotificationDetails& details); + + // Calls back a key update delegate on a given thread. + void CallKeyUpdateDelegate(KeyUpdateDelegate* d) { + d->OnKeyUpdated(); + } + + // A helper method to call back a delegte on another thread. + void CallDelegate(Delegate* d, + const KeyOpCode return_code, + const std::vector<uint8>& payload) { + d->OnKeyOpComplete(return_code, payload); + } + + scoped_ptr<crypto::RSAPrivateKey> private_key_; + std::vector<uint8> public_key_; + + scoped_refptr<OwnerKeyUtils> utils_; + + friend class OwnerManagerTest; + + DISALLOW_COPY_AND_ASSIGN(OwnerManager); +}; + +} // namespace chromeos + +#endif // CHROME_BROWSER_CHROMEOS_SETTINGS_OWNER_MANAGER_H_ diff --git a/chrome/browser/chromeos/settings/owner_manager_unittest.cc b/chrome/browser/chromeos/settings/owner_manager_unittest.cc new file mode 100644 index 0000000..d1893fd --- /dev/null +++ b/chrome/browser/chromeos/settings/owner_manager_unittest.cc @@ -0,0 +1,347 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/chromeos/settings/owner_manager.h" +#include "chrome/browser/chromeos/settings/owner_manager_unittest.h" + +#include <string> + +#include "base/bind.h" +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/logging.h" +#include "base/message_loop.h" +#include "base/scoped_temp_dir.h" +#include "chrome/browser/chromeos/settings/mock_owner_key_utils.h" +#include "chrome/common/chrome_notification_types.h" +#include "content/public/browser/notification_service.h" +#include "content/public/test/test_browser_thread.h" +#include "crypto/nss_util.h" +#include "crypto/rsa_private_key.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using content::BrowserThread; +using ::crypto::RSAPrivateKey; +using ::testing::DoAll; +using ::testing::Eq; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::SetArgumentPointee; +using ::testing::_; + +namespace chromeos { + +//////////////////////////////////////////////////////////////////////////////// +// MockKeyLoadObserver +MockKeyLoadObserver::MockKeyLoadObserver(base::WaitableEvent* e) + : success_expected_(false), + event_(e), + observed_(false) { + registrar_.Add( + this, + chrome::NOTIFICATION_OWNER_KEY_FETCH_ATTEMPT_FAILED, + content::NotificationService::AllSources()); + registrar_.Add( + this, + chrome::NOTIFICATION_OWNER_KEY_FETCH_ATTEMPT_SUCCEEDED, + content::NotificationService::AllSources()); +} + +MockKeyLoadObserver::~MockKeyLoadObserver() { + DCHECK(observed_); +} + +void MockKeyLoadObserver::Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) { + LOG(INFO) << "Observed key fetch event"; + if (type == chrome::NOTIFICATION_OWNER_KEY_FETCH_ATTEMPT_SUCCEEDED) { + DCHECK(success_expected_); + observed_ = true; + if (event_) + event_->Signal(); + } else if (type == chrome::NOTIFICATION_OWNER_KEY_FETCH_ATTEMPT_FAILED) { + DCHECK(!success_expected_); + observed_ = true; + if (event_) + event_->Signal(); + } +} + +void MockKeyLoadObserver::ExpectKeyFetchSuccess(bool should_succeed) { + success_expected_ = should_succeed; +} + +//////////////////////////////////////////////////////////////////////////////// +// MockKeyUser +MockKeyUser::MockKeyUser(const OwnerManager::KeyOpCode expected, + base::WaitableEvent* e) + : expected_(expected), + event_(e) { +} + +void MockKeyUser::OnKeyOpComplete(const OwnerManager::KeyOpCode return_code, + const std::vector<uint8>& payload) { + DCHECK_EQ(expected_, return_code); + if (event_) + event_->Signal(); +} + +//////////////////////////////////////////////////////////////////////////////// +// MockKeyUpdateUser + +void MockKeyUpdateUser::OnKeyUpdated() { + if (event_) + event_->Signal(); +} + +//////////////////////////////////////////////////////////////////////////////// +// MockSigner + +MockSigner::MockSigner(const OwnerManager::KeyOpCode expected, + const std::vector<uint8>& sig, + base::WaitableEvent* e) + : expected_code_(expected), + expected_sig_(sig), + event_(e) { +} + +MockSigner::~MockSigner() {} + +void MockSigner::OnKeyOpComplete(const OwnerManager::KeyOpCode return_code, + const std::vector<uint8>& payload) { + DCHECK_EQ(expected_code_, return_code); + for (uint32 i = 0; i < payload.size(); ++i) + DCHECK_EQ(expected_sig_[i], payload[i]); + if (event_) + event_->Signal(); +} + +//////////////////////////////////////////////////////////////////////////////// +// OwnerManagerTest + +class OwnerManagerTest : public testing::Test { + public: + OwnerManagerTest() + : message_loop_(MessageLoop::TYPE_UI), + ui_thread_(BrowserThread::UI, &message_loop_), + file_thread_(BrowserThread::FILE), + mock_(new MockKeyUtils), + injector_(mock_) /* injector_ takes ownership of mock_ */ { + } + virtual ~OwnerManagerTest() {} + + virtual void SetUp() { + crypto::OpenPersistentNSSDB(); // TODO(cmasone): use test DB instead + fake_private_key_.reset(RSAPrivateKey::Create(256)); + ASSERT_TRUE(fake_private_key_->ExportPublicKey(&fake_public_key_)); + + // Mimic ownership. + ASSERT_TRUE(tmpdir_.CreateUniqueTempDir()); + ASSERT_TRUE(file_util::CreateTemporaryFileInDir(tmpdir_.path(), &tmpfile_)); + + file_thread_.Start(); + OwnerKeyUtils::set_factory(&injector_); + } + + virtual void TearDown() { + OwnerKeyUtils::set_factory(NULL); + } + + void StartUnowned() { + file_util::Delete(tmpfile_, false); + } + + void InjectKeys(OwnerManager* manager) { + manager->public_key_ = fake_public_key_; + manager->private_key_.reset(fake_private_key_.release()); + } + + ScopedTempDir tmpdir_; + FilePath tmpfile_; + + MessageLoop message_loop_; + content::TestBrowserThread ui_thread_; + content::TestBrowserThread file_thread_; + + std::vector<uint8> fake_public_key_; + scoped_ptr<RSAPrivateKey> fake_private_key_; + + MockKeyUtils* mock_; + MockInjector injector_; +}; + +TEST_F(OwnerManagerTest, UpdateOwnerKey) { + scoped_refptr<OwnerManager> manager(new OwnerManager); + + base::WaitableEvent event(true, false); + MockKeyUpdateUser delegate(&event); + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, + base::Bind(&OwnerManager::UpdateOwnerKey, manager.get(), + BrowserThread::UI, std::vector<uint8>(), &delegate)); + while (!event.IsSignaled()) + message_loop_.RunAllPending(); +} + +TEST_F(OwnerManagerTest, LoadOwnerKeyFail) { + StartUnowned(); + base::WaitableEvent event(true, false); + MockKeyLoadObserver loader(&event); + scoped_refptr<OwnerManager> manager(new OwnerManager); + + EXPECT_CALL(*mock_, GetOwnerKeyFilePath()) + .WillRepeatedly(Return(tmpfile_)); + EXPECT_CALL(*mock_, ImportPublicKey(tmpfile_, _)) + .WillOnce(Return(false)) + .RetiresOnSaturation(); + + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, + base::Bind(&OwnerManager::LoadOwnerKey, manager.get())); + while (!event.IsSignaled()) + message_loop_.RunAllPending(); +} + +TEST_F(OwnerManagerTest, AlreadyLoadedOwnerKey) { + base::WaitableEvent event(true, false); + MockKeyLoadObserver loader(&event); + loader.ExpectKeyFetchSuccess(true); + scoped_refptr<OwnerManager> manager(new OwnerManager); + + EXPECT_CALL(*mock_, GetOwnerKeyFilePath()) + .WillRepeatedly(Return(tmpfile_)); + + InjectKeys(manager.get()); + + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, + base::Bind(&OwnerManager::LoadOwnerKey, manager.get())); + while (!event.IsSignaled()) + message_loop_.RunAllPending(); +} + +TEST_F(OwnerManagerTest, LoadOwnerKey) { + base::WaitableEvent event(true, false); + MockKeyLoadObserver loader(&event); + loader.ExpectKeyFetchSuccess(true); + scoped_refptr<OwnerManager> manager(new OwnerManager); + + EXPECT_CALL(*mock_, GetOwnerKeyFilePath()) + .WillRepeatedly(Return(tmpfile_)); + EXPECT_CALL(*mock_, ImportPublicKey(tmpfile_, _)) + .WillOnce(DoAll(SetArgumentPointee<1>(fake_public_key_), + Return(true))) + .RetiresOnSaturation(); + + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, + base::Bind(&OwnerManager::LoadOwnerKey, manager.get())); + while (!event.IsSignaled()) + message_loop_.RunAllPending(); +} + +TEST_F(OwnerManagerTest, GetKeyFailDuringVerify) { + StartUnowned(); + MockKeyLoadObserver loader(NULL); + loader.ExpectKeyFetchSuccess(false); + scoped_refptr<OwnerManager> manager(new OwnerManager); + + EXPECT_CALL(*mock_, GetOwnerKeyFilePath()) + .WillRepeatedly(Return(tmpfile_)); + EXPECT_CALL(*mock_, ImportPublicKey(tmpfile_, _)) + .WillOnce(Return(false)) + .RetiresOnSaturation(); + + base::WaitableEvent event(true, false); + MockKeyUser delegate(OwnerManager::KEY_UNAVAILABLE, &event); + + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, + base::Bind(&OwnerManager::Verify, manager.get(), BrowserThread::UI, + std::string(), std::vector<uint8>(), &delegate)); + while (!event.IsSignaled()) + message_loop_.RunAllPending(); +} + +TEST_F(OwnerManagerTest, AlreadyHaveKeysVerify) { + scoped_refptr<OwnerManager> manager(new OwnerManager); + + std::string data; + std::vector<uint8> sig(0, 2); + + EXPECT_CALL(*mock_, GetOwnerKeyFilePath()) + .WillRepeatedly(Return(tmpfile_)); + EXPECT_CALL(*mock_, Verify(Eq(data), Eq(sig), Eq(fake_public_key_))) + .WillOnce(Return(true)) + .RetiresOnSaturation(); + + InjectKeys(manager.get()); + base::WaitableEvent event(true, false); + MockKeyUser delegate(OwnerManager::SUCCESS, &event); + + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, + base::Bind(&OwnerManager::Verify, manager.get(), BrowserThread::UI, data, + sig, &delegate)); + while (!event.IsSignaled()) + message_loop_.RunAllPending(); +} + +TEST_F(OwnerManagerTest, GetKeyAndVerify) { + MockKeyLoadObserver loader(NULL); + loader.ExpectKeyFetchSuccess(true); + scoped_refptr<OwnerManager> manager(new OwnerManager); + + std::string data; + std::vector<uint8> sig(0, 2); + + EXPECT_CALL(*mock_, GetOwnerKeyFilePath()) + .WillRepeatedly(Return(tmpfile_)); + EXPECT_CALL(*mock_, ImportPublicKey(tmpfile_, _)) + .WillOnce(DoAll(SetArgumentPointee<1>(fake_public_key_), + Return(true))) + .RetiresOnSaturation(); + EXPECT_CALL(*mock_, Verify(Eq(data), Eq(sig), Eq(fake_public_key_))) + .WillOnce(Return(true)) + .RetiresOnSaturation(); + + base::WaitableEvent event(true, false); + MockKeyUser delegate(OwnerManager::SUCCESS, &event); + + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, + base::Bind(&OwnerManager::Verify, manager.get(), BrowserThread::UI, data, + sig, &delegate)); + while (!event.IsSignaled()) + message_loop_.RunAllPending(); +} + +TEST_F(OwnerManagerTest, AlreadyHaveKeysSign) { + scoped_refptr<OwnerManager> manager(new OwnerManager); + + std::string data; + std::vector<uint8> sig(0, 2); + + EXPECT_CALL(*mock_, GetOwnerKeyFilePath()) + .WillRepeatedly(Return(tmpfile_)); + EXPECT_CALL(*mock_, Sign(Eq(data), _, Eq(fake_private_key_.get()))) + .WillOnce(DoAll(SetArgumentPointee<1>(sig), + Return(true))) + .RetiresOnSaturation(); + + InjectKeys(manager.get()); + base::WaitableEvent event(true, false); + MockSigner delegate(OwnerManager::SUCCESS, sig, &event); + + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, + base::Bind(&OwnerManager::Sign, manager.get(), BrowserThread::UI, data, + &delegate)); + while (!event.IsSignaled()) + message_loop_.RunAllPending(); +} + +} // namespace chromeos diff --git a/chrome/browser/chromeos/settings/owner_manager_unittest.h b/chrome/browser/chromeos/settings/owner_manager_unittest.h new file mode 100644 index 0000000..7377a76 --- /dev/null +++ b/chrome/browser/chromeos/settings/owner_manager_unittest.h @@ -0,0 +1,93 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_CHROMEOS_SETTINGS_OWNER_MANAGER_UNITTEST_H_ +#define CHROME_BROWSER_CHROMEOS_SETTINGS_OWNER_MANAGER_UNITTEST_H_ + +#include "chrome/browser/chromeos/settings/owner_manager.h" + +#include <string> +#include <vector> + +#include "base/compiler_specific.h" +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/logging.h" +#include "base/synchronization/waitable_event.h" +#include "content/public/browser/notification_observer.h" +#include "content/public/browser/notification_registrar.h" +#include "content/public/browser/notification_types.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + + +namespace chromeos { +class MockKeyLoadObserver : public content::NotificationObserver { + public: + explicit MockKeyLoadObserver(base::WaitableEvent* e); + virtual ~MockKeyLoadObserver(); + + // content::NotificationObserver implementation. + virtual void Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) OVERRIDE; + + void ExpectKeyFetchSuccess(bool should_succeed); + + private: + content::NotificationRegistrar registrar_; + bool success_expected_; + base::WaitableEvent* event_; + bool observed_; + DISALLOW_COPY_AND_ASSIGN(MockKeyLoadObserver); +}; + +class MockKeyUser : public OwnerManager::Delegate { + public: + MockKeyUser(const OwnerManager::KeyOpCode expected, base::WaitableEvent* e); + virtual ~MockKeyUser() {} + + virtual void OnKeyOpComplete(const OwnerManager::KeyOpCode return_code, + const std::vector<uint8>& payload) OVERRIDE; + + const OwnerManager::KeyOpCode expected_; + private: + base::WaitableEvent* event_; + DISALLOW_COPY_AND_ASSIGN(MockKeyUser); +}; + +class MockKeyUpdateUser : public OwnerManager::KeyUpdateDelegate { + public: + explicit MockKeyUpdateUser(base::WaitableEvent* e) : event_(e) {} + virtual ~MockKeyUpdateUser() {} + + virtual void OnKeyUpdated() OVERRIDE; + + private: + base::WaitableEvent* event_; + DISALLOW_COPY_AND_ASSIGN(MockKeyUpdateUser); +}; + + +class MockSigner : public OwnerManager::Delegate { + public: + MockSigner(const OwnerManager::KeyOpCode expected, + const std::vector<uint8>& sig, + base::WaitableEvent* e); + virtual ~MockSigner(); + + virtual void OnKeyOpComplete(const OwnerManager::KeyOpCode return_code, + const std::vector<uint8>& payload) OVERRIDE; + + const OwnerManager::KeyOpCode expected_code_; + const std::vector<uint8> expected_sig_; + + private: + base::WaitableEvent* event_; + DISALLOW_COPY_AND_ASSIGN(MockSigner); +}; + +} // namespace chromeos + +#endif // CHROME_BROWSER_CHROMEOS_SETTINGS_OWNER_MANAGER_UNITTEST_H_ diff --git a/chrome/browser/chromeos/settings/ownership_service.cc b/chrome/browser/chromeos/settings/ownership_service.cc new file mode 100644 index 0000000..6343fa3 --- /dev/null +++ b/chrome/browser/chromeos/settings/ownership_service.cc @@ -0,0 +1,259 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/chromeos/settings/ownership_service.h" + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/command_line.h" +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/lazy_instance.h" +#include "base/synchronization/lock.h" +#include "base/task_runner_util.h" +#include "chrome/browser/browser_process.h" +#include "chrome/common/chrome_notification_types.h" +#include "chrome/common/chrome_switches.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/notification_service.h" + +using content::BrowserThread; + +namespace chromeos { + +namespace { + +typedef std::pair<OwnershipService::Status, bool> OwnershipStatusReturnType; + +// Makes the check for ownership on the FILE thread and stores the result in the +// provided pointers. +OwnershipStatusReturnType CheckStatusOnFileThread() { + bool current_user_is_owner; + OwnershipService::Status status; + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + status = OwnershipService::GetSharedInstance()->IsAlreadyOwned() ? + OwnershipService::OWNERSHIP_TAKEN : OwnershipService::OWNERSHIP_NONE; + current_user_is_owner = + OwnershipService::GetSharedInstance()->IsCurrentUserOwner(); + return OwnershipStatusReturnType(status, current_user_is_owner); +} + +} // namespace + +static base::LazyInstance<OwnershipService> g_ownership_service = + LAZY_INSTANCE_INITIALIZER; + +// static +OwnershipService* OwnershipService::GetSharedInstance() { + return g_ownership_service.Pointer(); +} + +OwnershipService::OwnershipService() + : manager_(new OwnerManager), + utils_(OwnerKeyUtils::Create()), + ownership_status_(OWNERSHIP_UNKNOWN), + force_ownership_(CommandLine::ForCurrentProcess()->HasSwitch( + switches::kStubCrosSettings)) { + notification_registrar_.Add( + this, + chrome::NOTIFICATION_OWNER_KEY_FETCH_ATTEMPT_SUCCEEDED, + content::NotificationService::AllSources()); +} + +OwnershipService::~OwnershipService() {} + +void OwnershipService::Prewarm() { + // Note that we cannot prewarm in constructor because in current codebase + // object is created before spawning threads. + if (g_ownership_service == this) { + // Start getting ownership status. + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, + base::Bind(&OwnershipService::FetchStatus, base::Unretained(this))); + } else { + // This can happen only for particular test: OwnershipServiceTest. It uses + // mocks and for that uses OwnershipService not as a regular singleton but + // as a resurrecting object. This behaviour conflicts with + // base::Unretained(). So avoid posting task in those circumstances + // in order to avoid accessing already deleted object. + } +} + +bool OwnershipService::IsAlreadyOwned() { + return file_util::PathExists(utils_->GetOwnerKeyFilePath()); +} + +OwnershipService::Status OwnershipService::GetStatus(bool blocking) { + if (force_ownership_) + return OWNERSHIP_TAKEN; + Status status = OWNERSHIP_UNKNOWN; + bool is_owned = false; + if (BrowserThread::CurrentlyOn(BrowserThread::UI)) { + ownership_status_lock_.Acquire(); + status = ownership_status_; + ownership_status_lock_.Release(); + if (status != OWNERSHIP_UNKNOWN || !blocking) + return status; + // Under common usage there is very short lapse of time when ownership + // status is still unknown after constructing OwnershipService. + LOG(ERROR) << "Blocking on UI thread in OwnershipService::GetStatus"; + base::ThreadRestrictions::ScopedAllowIO allow_io; + is_owned = IsAlreadyOwned(); + } else { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + is_owned = IsAlreadyOwned(); + } + status = is_owned ? OWNERSHIP_TAKEN : OWNERSHIP_NONE; + SetStatus(status); + return status; +} + +void OwnershipService::StartLoadOwnerKeyAttempt() { + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, + base::Bind(&TryLoadOwnerKeyAttempt, base::Unretained(this))); +} + +void OwnershipService::StartUpdateOwnerKey(const std::vector<uint8>& new_key, + OwnerManager::KeyUpdateDelegate* d) { + BrowserThread::ID thread_id; + if (!BrowserThread::GetCurrentThreadIdentifier(&thread_id)) + thread_id = BrowserThread::UI; + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, + base::Bind(&OwnershipService::UpdateOwnerKey, base::Unretained(this), + thread_id, new_key, d)); + return; +} + +void OwnershipService::StartSigningAttempt(const std::string& data, + OwnerManager::Delegate* d) { + BrowserThread::ID thread_id; + if (!BrowserThread::GetCurrentThreadIdentifier(&thread_id)) + thread_id = BrowserThread::UI; + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, + base::Bind(&OwnershipService::TrySigningAttempt, base::Unretained(this), + thread_id, data, d)); + return; +} + +void OwnershipService::StartVerifyAttempt(const std::string& data, + const std::vector<uint8>& signature, + OwnerManager::Delegate* d) { + BrowserThread::ID thread_id; + if (!BrowserThread::GetCurrentThreadIdentifier(&thread_id)) + thread_id = BrowserThread::UI; + BrowserThread::PostTask( + BrowserThread::FILE, FROM_HERE, + base::Bind(&OwnershipService::TryVerifyAttempt, base::Unretained(this), + thread_id, data, signature, d)); + return; +} + +void OwnershipService::Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) { + if (type == chrome::NOTIFICATION_OWNER_KEY_FETCH_ATTEMPT_SUCCEEDED) { + SetStatus(OWNERSHIP_TAKEN); + notification_registrar_.RemoveAll(); + } else { + NOTREACHED(); + } +} + +bool OwnershipService::IsCurrentUserOwner() { + if (force_ownership_) + return true; + // If this user has the private key associated with the owner's + // public key, this user is the owner. + return IsAlreadyOwned() && manager_->EnsurePrivateKey(); +} + +void OwnershipService::GetStatusAsync(const Callback& callback) { + PostTaskAndReplyWithResult( + BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE), + FROM_HERE, + base::Bind(&CheckStatusOnFileThread), + base::Bind(&OwnershipService::ReturnStatus, + callback)); +} + + +// static +void OwnershipService::UpdateOwnerKey(OwnershipService* service, + const BrowserThread::ID thread_id, + const std::vector<uint8>& new_key, + OwnerManager::KeyUpdateDelegate* d) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + service->manager()->UpdateOwnerKey(thread_id, new_key, d); +} + +// static +void OwnershipService::TryLoadOwnerKeyAttempt(OwnershipService* service) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + if (!service->IsAlreadyOwned()) { + VLOG(1) << "Device not yet owned"; + return; + } + service->manager()->LoadOwnerKey(); +} + +// static +void OwnershipService::TrySigningAttempt(OwnershipService* service, + const BrowserThread::ID thread_id, + const std::string& data, + OwnerManager::Delegate* d) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + if (!service->IsAlreadyOwned()) { + LOG(ERROR) << "Device not yet owned"; + BrowserThread::PostTask( + thread_id, FROM_HERE, + base::Bind(&OwnershipService::FailAttempt, d)); + return; + } + service->manager()->Sign(thread_id, data, d); +} + +// static +void OwnershipService::TryVerifyAttempt(OwnershipService* service, + const BrowserThread::ID thread_id, + const std::string& data, + const std::vector<uint8>& signature, + OwnerManager::Delegate* d) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + if (!service->IsAlreadyOwned()) { + LOG(ERROR) << "Device not yet owned"; + BrowserThread::PostTask( + thread_id, FROM_HERE, + base::Bind(&OwnershipService::FailAttempt, d)); + return; + } + service->manager()->Verify(thread_id, data, signature, d); +} + +// static +void OwnershipService::FailAttempt(OwnerManager::Delegate* d) { + d->OnKeyOpComplete(OwnerManager::KEY_UNAVAILABLE, std::vector<uint8>()); +} + +void OwnershipService::FetchStatus() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); + Status status = IsAlreadyOwned() ? OWNERSHIP_TAKEN : OWNERSHIP_NONE; + SetStatus(status); +} + +void OwnershipService::SetStatus(Status new_status) { + DCHECK(new_status == OWNERSHIP_TAKEN || new_status == OWNERSHIP_NONE); + base::AutoLock lk(ownership_status_lock_); + ownership_status_ = new_status; +} + +// static +void OwnershipService::ReturnStatus(const Callback& callback, + OwnershipStatusReturnType status) { + callback.Run(status.first, status.second); +} + +} // namespace chromeos diff --git a/chrome/browser/chromeos/settings/ownership_service.h b/chrome/browser/chromeos/settings/ownership_service.h new file mode 100644 index 0000000..13a68d5 --- /dev/null +++ b/chrome/browser/chromeos/settings/ownership_service.h @@ -0,0 +1,150 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_CHROMEOS_SETTINGS_OWNERSHIP_SERVICE_H_ +#define CHROME_BROWSER_CHROMEOS_SETTINGS_OWNERSHIP_SERVICE_H_ + +#include <string> +#include <vector> + +#include "base/callback.h" +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "base/synchronization/lock.h" +#include "chrome/browser/chromeos/settings/owner_key_utils.h" +#include "chrome/browser/chromeos/settings/owner_manager.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/notification_observer.h" +#include "content/public/browser/notification_registrar.h" + +namespace base { +template <typename T> struct DefaultLazyInstanceTraits; +} + +namespace chromeos { + +class OwnershipService : public content::NotificationObserver { + public: + enum Status { + // Listed in upgrade order. + OWNERSHIP_UNKNOWN = 0, + OWNERSHIP_NONE, + OWNERSHIP_TAKEN + }; + + // Callback function type. The status code is guaranteed to be different from + // OWNERSHIP_UNKNOWN. The bool parameter is true iff the currently logged in + // user is the owner. + typedef base::Callback<void(OwnershipService::Status, bool)> Callback; + + // Returns the singleton instance of the OwnershipService. + static OwnershipService* GetSharedInstance(); + virtual ~OwnershipService(); + + // Called after FILE thread is created to prefetch ownership status and avoid + // blocking on UI thread. + void Prewarm(); + + // Sets a new owner key. This will _not_ load the key material from disk, but + // rather update Chrome's in-memory copy of the key. |callback| will be + // invoked once the operation completes. + virtual void StartUpdateOwnerKey(const std::vector<uint8>& new_key, + OwnerManager::KeyUpdateDelegate* d); + + // If the device has been owned already, posts a task to the FILE thread to + // fetch the public key off disk. + // + // Sends out a OWNER_KEY_FETCH_ATTEMPT_SUCCESS notification on success, + // OWNER_KEY_FETCH_ATTEMPT_FAILED on failure. + virtual void StartLoadOwnerKeyAttempt(); + + // Initiate an attempt to sign |data| with |private_key_|. Will call + // d->OnKeyOpComplete() when done. Upon success, the signature will be passed + // as the |payload| argument to d->OnKeyOpComplete(). + // + // If you call this on a well-known thread, you'll be called back on that + // thread. Otherwise, you'll get called back on the UI thread. + virtual void StartSigningAttempt(const std::string& data, + OwnerManager::Delegate* d); + + // Initiate an attempt to verify that |signature| is valid over |data| with + // |public_key_|. When the attempt is completed, an appropriate KeyOpCode + // will be passed to d->OnKeyOpComplete(). + // + // If you call this on a well-known thread, you'll be called back on that + // thread. Otherwise, you'll get called back on the UI thread. + virtual void StartVerifyAttempt(const std::string& data, + const std::vector<uint8>& signature, + OwnerManager::Delegate* d); + + // This method must be run on the FILE thread. + virtual bool IsCurrentUserOwner(); + + // This method should be run on FILE thread. + // Note: not static, for better mocking. + virtual bool IsAlreadyOwned(); + + // This method can be run either on FILE or UI threads. If |blocking| flag + // is specified then it is guaranteed to return either OWNERSHIP_NONE or + // OWNERSHIP_TAKEN (and not OWNERSHIP_UNKNOWN), however in this case it may + // occasionally block doing i/o. + virtual Status GetStatus(bool blocking); + + // Determines the ownership status on the FILE thread and calls the |callback| + // with the result. + virtual void GetStatusAsync(const Callback& callback); + + protected: + OwnershipService(); + + // content::NotificationObserver implementation. + virtual void Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) OVERRIDE; + + private: + friend struct base::DefaultLazyInstanceTraits<OwnershipService>; + friend class OwnershipServiceTest; + + // Task posted on FILE thread on startup to prefetch ownership status. + void FetchStatus(); + + // Sets ownership status. May be called on either thread. + void SetStatus(Status new_status); + + // Used by |CheckOwnershipAsync| to call the callback with the result. + static void ReturnStatus(const Callback& callback, + std::pair<OwnershipService::Status, bool> status); + + static void UpdateOwnerKey(OwnershipService* service, + const content::BrowserThread::ID thread_id, + const std::vector<uint8>& new_key, + OwnerManager::KeyUpdateDelegate* d); + static void TryLoadOwnerKeyAttempt(OwnershipService* service); + static void TrySigningAttempt(OwnershipService* service, + const content::BrowserThread::ID thread_id, + const std::string& data, + OwnerManager::Delegate* d); + static void TryVerifyAttempt(OwnershipService* service, + const content::BrowserThread::ID thread_id, + const std::string& data, + const std::vector<uint8>& signature, + OwnerManager::Delegate* d); + static void FailAttempt(OwnerManager::Delegate* d); + + OwnerManager* manager() { return manager_.get(); } + + scoped_refptr<OwnerManager> manager_; + scoped_refptr<OwnerKeyUtils> utils_; + content::NotificationRegistrar notification_registrar_; + volatile Status ownership_status_; + base::Lock ownership_status_lock_; + + // If true, current user is regarded as owner (for testing only). + bool force_ownership_; +}; + +} // namespace chromeos + +#endif // CHROME_BROWSER_CHROMEOS_SETTINGS_OWNERSHIP_SERVICE_H_ diff --git a/chrome/browser/chromeos/settings/ownership_service_unittest.cc b/chrome/browser/chromeos/settings/ownership_service_unittest.cc new file mode 100644 index 0000000..6a88d9f --- /dev/null +++ b/chrome/browser/chromeos/settings/ownership_service_unittest.cc @@ -0,0 +1,238 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/chromeos/settings/ownership_service.h" + +#include <string> + +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop.h" +#include "base/scoped_temp_dir.h" +#include "chrome/browser/chromeos/settings/mock_owner_key_utils.h" +#include "chrome/browser/chromeos/settings/owner_manager_unittest.h" +#include "content/public/browser/notification_service.h" +#include "content/public/test/test_browser_thread.h" +#include "crypto/nss_util.h" +#include "crypto/rsa_private_key.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using content::BrowserThread; +using ::crypto::RSAPrivateKey; +using ::testing::DoAll; +using ::testing::Eq; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::SetArgumentPointee; +using ::testing::_; + + +namespace chromeos { + +class OwnershipServiceTest : public testing::Test { + public: + OwnershipServiceTest() + : message_loop_(MessageLoop::TYPE_UI), + ui_thread_(BrowserThread::UI, &message_loop_), + file_thread_(BrowserThread::FILE), + mock_(new MockKeyUtils), + injector_(mock_) /* injector_ takes ownership of mock_ */ { + } + virtual ~OwnershipServiceTest() {} + + virtual void SetUp() { + crypto::OpenPersistentNSSDB(); // TODO(cmasone): use test DB instead + fake_private_key_.reset(RSAPrivateKey::Create(256)); + ASSERT_TRUE(fake_private_key_->ExportPublicKey(&fake_public_key_)); + + // Mimic ownership. + ASSERT_TRUE(tmpdir_.CreateUniqueTempDir()); + ASSERT_TRUE(file_util::CreateTemporaryFileInDir(tmpdir_.path(), &tmpfile_)); + + file_thread_.Start(); + OwnerKeyUtils::set_factory(&injector_); + service_.reset(new OwnershipService); // must happen AFTER set_factory(). + service_->Prewarm(); + } + + virtual void TearDown() { + OwnerKeyUtils::set_factory(NULL); + service_.reset(NULL); + } + + void StartUnowned() { + file_util::Delete(tmpfile_, false); + } + + ScopedTempDir tmpdir_; + FilePath tmpfile_; + + MessageLoop message_loop_; + content::TestBrowserThread ui_thread_; + content::TestBrowserThread file_thread_; + + std::vector<uint8> fake_public_key_; + scoped_ptr<RSAPrivateKey> fake_private_key_; + + MockKeyUtils* mock_; + MockInjector injector_; + scoped_ptr<OwnershipService> service_; +}; + +TEST_F(OwnershipServiceTest, IsOwned) { + EXPECT_CALL(*mock_, GetOwnerKeyFilePath()) + .WillRepeatedly(Return(tmpfile_)); + EXPECT_TRUE(service_->IsAlreadyOwned()); +} + +TEST_F(OwnershipServiceTest, IsOwnershipTaken) { + EXPECT_CALL(*mock_, GetOwnerKeyFilePath()) + .WillRepeatedly(Return(tmpfile_)); + EXPECT_TRUE(service_->GetStatus(true) == OwnershipService::OWNERSHIP_TAKEN); +} + +TEST_F(OwnershipServiceTest, IsUnowned) { + StartUnowned(); + + EXPECT_CALL(*mock_, GetOwnerKeyFilePath()) + .WillRepeatedly(Return(tmpfile_)); + EXPECT_FALSE(service_->IsAlreadyOwned()); +} + +TEST_F(OwnershipServiceTest, IsOwnershipNone) { + StartUnowned(); + + EXPECT_CALL(*mock_, GetOwnerKeyFilePath()) + .WillRepeatedly(Return(tmpfile_)); + EXPECT_TRUE(service_->GetStatus(true) == OwnershipService::OWNERSHIP_NONE); +} + +TEST_F(OwnershipServiceTest, LoadOwnerKeyFail) { + base::WaitableEvent event(true, false); + MockKeyLoadObserver loader(&event); + EXPECT_CALL(*mock_, GetOwnerKeyFilePath()) + .WillRepeatedly(Return(tmpfile_)); + EXPECT_CALL(*mock_, ImportPublicKey(tmpfile_, _)) + .WillOnce(Return(false)) + .RetiresOnSaturation(); + + service_->StartLoadOwnerKeyAttempt(); + + // Run remaining events, until ExportPublicKeyViaDbus(). + while (!event.IsSignaled()) + message_loop_.RunAllPending(); +} + +TEST_F(OwnershipServiceTest, UpdateOwnerKey) { + base::WaitableEvent event(true, false); + MockKeyUpdateUser delegate(&event); + service_->StartUpdateOwnerKey(std::vector<uint8>(), &delegate); + + while (!event.IsSignaled()) + message_loop_.RunAllPending(); +} + +TEST_F(OwnershipServiceTest, LoadOwnerKey) { + base::WaitableEvent event(true, false); + MockKeyLoadObserver loader(&event); + loader.ExpectKeyFetchSuccess(true); + + EXPECT_CALL(*mock_, GetOwnerKeyFilePath()) + .WillRepeatedly(Return(tmpfile_)); + EXPECT_CALL(*mock_, ImportPublicKey(tmpfile_, _)) + .WillOnce(DoAll(SetArgumentPointee<1>(fake_public_key_), + Return(true))) + .RetiresOnSaturation(); + service_->StartLoadOwnerKeyAttempt(); + + while (!event.IsSignaled()) + message_loop_.RunAllPending(); +} + +TEST_F(OwnershipServiceTest, NotYetOwnedVerify) { + StartUnowned(); + + EXPECT_CALL(*mock_, GetOwnerKeyFilePath()) + .WillRepeatedly(Return(tmpfile_)); + + base::WaitableEvent event(true, false); + MockKeyUser delegate(OwnerManager::KEY_UNAVAILABLE, &event); + service_->StartVerifyAttempt("", std::vector<uint8>(), &delegate); + + while (!event.IsSignaled()) + message_loop_.RunAllPending(); +} + +TEST_F(OwnershipServiceTest, GetKeyFailDuringVerify) { + MockKeyLoadObserver loader(NULL); + loader.ExpectKeyFetchSuccess(false); + + EXPECT_CALL(*mock_, GetOwnerKeyFilePath()) + .WillRepeatedly(Return(tmpfile_)); + EXPECT_CALL(*mock_, ImportPublicKey(tmpfile_, _)) + .WillOnce(Return(false)) + .RetiresOnSaturation(); + + base::WaitableEvent event(true, false); + MockKeyUser delegate(OwnerManager::KEY_UNAVAILABLE, &event); + service_->StartVerifyAttempt("", std::vector<uint8>(), &delegate); + + while (!event.IsSignaled()) + message_loop_.RunAllPending(); +} + +TEST_F(OwnershipServiceTest, GetKeyAndVerify) { + MockKeyLoadObserver loader(NULL); + loader.ExpectKeyFetchSuccess(true); + + std::string data; + std::vector<uint8> sig(0, 2); + + EXPECT_CALL(*mock_, GetOwnerKeyFilePath()) + .WillRepeatedly(Return(tmpfile_)); + EXPECT_CALL(*mock_, ImportPublicKey(tmpfile_, _)) + .WillOnce(DoAll(SetArgumentPointee<1>(fake_public_key_), + Return(true))) + .RetiresOnSaturation(); + EXPECT_CALL(*mock_, Verify(Eq(data), Eq(sig), Eq(fake_public_key_))) + .WillOnce(Return(true)) + .RetiresOnSaturation(); + + base::WaitableEvent event(true, false); + MockKeyUser delegate(OwnerManager::SUCCESS, &event); + service_->StartVerifyAttempt(data, sig, &delegate); + + while (!event.IsSignaled()) + message_loop_.RunAllPending(); +} + +TEST_F(OwnershipServiceTest, GetKeyAndFailVerify) { + MockKeyLoadObserver loader(NULL); + loader.ExpectKeyFetchSuccess(true); + + std::string data; + std::vector<uint8> sig(0, 2); + + EXPECT_CALL(*mock_, GetOwnerKeyFilePath()) + .WillRepeatedly(Return(tmpfile_)); + EXPECT_CALL(*mock_, ImportPublicKey(tmpfile_, _)) + .WillOnce(DoAll(SetArgumentPointee<1>(fake_public_key_), + Return(true))) + .RetiresOnSaturation(); + EXPECT_CALL(*mock_, Verify(Eq(data), Eq(sig), Eq(fake_public_key_))) + .WillOnce(Return(false)) + .RetiresOnSaturation(); + + base::WaitableEvent event(true, false); + MockKeyUser delegate(OwnerManager::OPERATION_FAILED, &event); + service_->StartVerifyAttempt(data, sig, &delegate); + + while (!event.IsSignaled()) + message_loop_.RunAllPending(); +} + +} // namespace chromeos diff --git a/chrome/browser/chromeos/settings/session_manager_observer.cc b/chrome/browser/chromeos/settings/session_manager_observer.cc new file mode 100644 index 0000000..6bb828c --- /dev/null +++ b/chrome/browser/chromeos/settings/session_manager_observer.cc @@ -0,0 +1,81 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/chromeos/settings/session_manager_observer.h" + +#include "chrome/browser/browser_process.h" +#include "chrome/browser/chromeos/settings/signed_settings.h" +#include "chrome/browser/chromeos/settings/signed_settings_cache.h" +#include "chrome/common/chrome_notification_types.h" +#include "chromeos/dbus/dbus_thread_manager.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/notification_service.h" + +namespace chromeos { + +namespace { + +class StubDelegate + : public SignedSettings::Delegate< + const enterprise_management::PolicyFetchResponse&> { + public: + StubDelegate() : policy_fetcher_(NULL) {} + virtual ~StubDelegate() {} + + void set_fetcher(SignedSettings* fetcher) { policy_fetcher_ = fetcher; } + SignedSettings* fetcher() { return policy_fetcher_.get(); } + + // Implementation of SignedSettings::Delegate + virtual void OnSettingsOpCompleted( + SignedSettings::ReturnCode code, + const enterprise_management::PolicyFetchResponse& value) { + VLOG(1) << "Done Fetching Policy"; + delete this; + } + + private: + scoped_refptr<SignedSettings> policy_fetcher_; + DISALLOW_COPY_AND_ASSIGN(StubDelegate); +}; + +} // namespace + +SessionManagerObserver::SessionManagerObserver() { + DBusThreadManager::Get()->GetSessionManagerClient()->AddObserver(this); +} + +SessionManagerObserver::~SessionManagerObserver() { + DBusThreadManager::Get()->GetSessionManagerClient()->RemoveObserver(this); +} + +void SessionManagerObserver::OwnerKeySet(bool success) { + VLOG(1) << "Owner key generation: " << (success ? "success" : "fail"); + int result = + chrome::NOTIFICATION_OWNER_KEY_FETCH_ATTEMPT_SUCCEEDED; + if (!success) + result = chrome::NOTIFICATION_OWNER_KEY_FETCH_ATTEMPT_FAILED; + + // We stored some settings in transient storage before owner was assigned. + // Now owner is assigned and key is generated and we should persist + // those settings into signed storage. + if (success && g_browser_process && g_browser_process->local_state()) + signed_settings_cache::Finalize(g_browser_process->local_state()); + + // Whether we exported the public key or not, send a notification + // indicating that we're done with this attempt. + content::NotificationService::current()->Notify( + result, + content::NotificationService::AllSources(), + content::NotificationService::NoDetails()); +} + +void SessionManagerObserver::PropertyChangeComplete(bool success) { + if (success) { + StubDelegate* stub = new StubDelegate(); // Manages its own lifetime. + stub->set_fetcher(SignedSettings::CreateRetrievePolicyOp(stub)); + stub->fetcher()->Execute(); + } +} + +} // namespace chromeos diff --git a/chrome/browser/chromeos/settings/session_manager_observer.h b/chrome/browser/chromeos/settings/session_manager_observer.h new file mode 100644 index 0000000..b1e3adb --- /dev/null +++ b/chrome/browser/chromeos/settings/session_manager_observer.h @@ -0,0 +1,32 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_CHROMEOS_SETTINGS_SESSION_MANAGER_OBSERVER_H_ +#define CHROME_BROWSER_CHROMEOS_SETTINGS_SESSION_MANAGER_OBSERVER_H_ + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "chromeos/dbus/session_manager_client.h" + +namespace chromeos { + +// SessionManagerObserver is used to take actions per signals sent from the +// session manager. +class SessionManagerObserver : public SessionManagerClient::Observer { + public: + SessionManagerObserver(); + virtual ~SessionManagerObserver(); + + private: + // SessionManagerClient::Observer override. + virtual void OwnerKeySet(bool success) OVERRIDE; + // SessionManagerClient::Observer override. + virtual void PropertyChangeComplete(bool success) OVERRIDE; + + DISALLOW_COPY_AND_ASSIGN(SessionManagerObserver); +}; + +} // namespace chromeos + +#endif // CHROME_BROWSER_CHROMEOS_SETTINGS_SESSION_MANAGER_OBSERVER_H_ diff --git a/chrome/browser/chromeos/settings/signed_settings.cc b/chrome/browser/chromeos/settings/signed_settings.cc new file mode 100644 index 0000000..9cdb826 --- /dev/null +++ b/chrome/browser/chromeos/settings/signed_settings.cc @@ -0,0 +1,290 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/chromeos/settings/signed_settings.h" + +#include <string> +#include <vector> + +#include "base/bind.h" +#include "base/memory/ref_counted.h" +#include "base/threading/thread_restrictions.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/chromeos/cros/cros_library.h" +#include "chrome/browser/chromeos/login/authenticator.h" +#include "chrome/browser/chromeos/settings/ownership_service.h" +#include "chrome/browser/policy/proto/chrome_device_policy.pb.h" +#include "chrome/browser/policy/proto/device_management_backend.pb.h" +#include "chromeos/dbus/dbus_thread_manager.h" +#include "chromeos/dbus/session_manager_client.h" +#include "content/public/browser/browser_thread.h" + +namespace em = enterprise_management; + +namespace chromeos { +using content::BrowserThread; + +const char kDevicePolicyType[] = "google/chromeos/device"; + +SignedSettings::SignedSettings() + : service_(OwnershipService::GetSharedInstance()) { +} + +SignedSettings::~SignedSettings() {} + +// static +bool SignedSettings::PolicyIsSane(const em::PolicyFetchResponse& value, + em::PolicyData* poldata) { + if (value.has_policy_data()) { + poldata->ParseFromString(value.policy_data()); + if (poldata->has_policy_type() && + poldata->policy_type() == kDevicePolicyType && + poldata->has_policy_value()) { + return true; + } + } + return false; +} + +// static +SignedSettings::ReturnCode SignedSettings::MapKeyOpCode( + OwnerManager::KeyOpCode return_code) { + return (return_code == OwnerManager::KEY_UNAVAILABLE ? + KEY_UNAVAILABLE : BAD_SIGNATURE); +} + +class StorePolicyOp : public SignedSettings { + public: + StorePolicyOp(em::PolicyFetchResponse* policy, + SignedSettings::Delegate<bool>* d); + void Succeed(bool value); + // Implementation of OwnerManager::Delegate + virtual void Execute() OVERRIDE; + virtual void Fail(SignedSettings::ReturnCode code) OVERRIDE; + virtual void OnKeyOpComplete(const OwnerManager::KeyOpCode return_code, + const std::vector<uint8>& payload) OVERRIDE; + + protected: + virtual ~StorePolicyOp(); + + private: + void RequestStorePolicy(); + + void OnBoolComplete(bool success); + // Always call d_->OnSettingOpCompleted() via this call. + // It guarantees that the callback will not be triggered until _after_ + // Execute() returns, which is implicitly assumed by SignedSettingsHelper + // in some cases. + void PerformCallback(SignedSettings::ReturnCode code, bool value); + + em::PolicyFetchResponse* policy_; + SignedSettings::Delegate<bool>* d_; +}; + +class RetrievePolicyOp : public SignedSettings { + public: + explicit RetrievePolicyOp( + SignedSettings::Delegate<const em::PolicyFetchResponse&>* d); + void Succeed(const em::PolicyFetchResponse& value); + // Implementation of OwnerManager::Delegate + virtual void Execute() OVERRIDE; + virtual void Fail(SignedSettings::ReturnCode code) OVERRIDE; + virtual void OnKeyOpComplete(const OwnerManager::KeyOpCode return_code, + const std::vector<uint8>& payload) OVERRIDE; + + protected: + virtual ~RetrievePolicyOp(); + + private: + void OnStringComplete(const std::string& serialized_proto); + // Always call d_->OnSettingOpCompleted() via this call. + // It guarantees that the callback will not be triggered until _after_ + // Execute() returns, which is implicitly assumed by SignedSettingsHelper + // in some cases. + void PerformCallback(SignedSettings::ReturnCode code, + const em::PolicyFetchResponse& value); + + void ProcessPolicy(const std::string& serialized_proto); + + em::PolicyFetchResponse policy_; + SignedSettings::Delegate<const em::PolicyFetchResponse&>* d_; +}; + +// static +SignedSettings* SignedSettings::CreateStorePolicyOp( + em::PolicyFetchResponse* policy, + SignedSettings::Delegate<bool>* d) { + DCHECK(d != NULL); + DCHECK(policy != NULL); + return new StorePolicyOp(policy, d); +} + +// static +SignedSettings* SignedSettings::CreateRetrievePolicyOp( + SignedSettings::Delegate<const em::PolicyFetchResponse&>* d) { + DCHECK(d != NULL); + return new RetrievePolicyOp(d); +} + + +StorePolicyOp::StorePolicyOp(em::PolicyFetchResponse* policy, + SignedSettings::Delegate<bool>* d) + : policy_(policy), + d_(d) { +} + +void StorePolicyOp::Succeed(bool ignored) { + SignedSettings::ReturnCode code = SUCCESS; + bool to_ret = true; + em::PolicyData poldata; + if (SignedSettings::PolicyIsSane(*policy_, &poldata)) { + } else { + code = NOT_FOUND; + to_ret = false; + } + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&StorePolicyOp::PerformCallback, this, code, to_ret)); +} + +void StorePolicyOp::Execute() { + // get protobuf contents to sign + if (!policy_->has_policy_data()) + Fail(OPERATION_FAILED); + else if (!policy_->has_policy_data_signature()) + service_->StartSigningAttempt(policy_->policy_data(), this); + else + RequestStorePolicy(); +} + +void StorePolicyOp::Fail(SignedSettings::ReturnCode code) { + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&StorePolicyOp::PerformCallback, this, code, false)); +} + +void StorePolicyOp::OnKeyOpComplete(const OwnerManager::KeyOpCode return_code, + const std::vector<uint8>& payload) { + // Ensure we're on the UI thread, due to the need to send DBus traffic. + if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&StorePolicyOp::OnKeyOpComplete, this, return_code, + payload)); + return; + } + VLOG(2) << "StorePolicyOp::OnKeyOpComplete return_code = " << return_code; + // Now, sure we're on the UI thread. + if (return_code == OwnerManager::SUCCESS) { + policy_->set_policy_data_signature(std::string(payload.begin(), + payload.end())); + RequestStorePolicy(); + return; + } + Fail(SignedSettings::MapKeyOpCode(return_code)); +} + +StorePolicyOp::~StorePolicyOp() {} + +void StorePolicyOp::RequestStorePolicy() { + std::string serialized; + if (policy_->SerializeToString(&serialized)) { + DBusThreadManager::Get()->GetSessionManagerClient()->StoreDevicePolicy( + serialized, + base::Bind(&StorePolicyOp::OnBoolComplete, this)); + } else { + Fail(OPERATION_FAILED); + } +} + +void StorePolicyOp::OnBoolComplete(bool success) { + if (success) + Succeed(true); + else + Fail(NOT_FOUND); +} + +void StorePolicyOp::PerformCallback(SignedSettings::ReturnCode code, + bool value) { + d_->OnSettingsOpCompleted(code, value); +} + +RetrievePolicyOp::RetrievePolicyOp( + SignedSettings::Delegate<const em::PolicyFetchResponse&>* d) + : d_(d) { +} + +void RetrievePolicyOp::Succeed(const em::PolicyFetchResponse& value) { + em::PolicyData poldata; + if (SignedSettings::PolicyIsSane(value, &poldata)) { + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&RetrievePolicyOp::PerformCallback, this, SUCCESS, value)); + } else { + Fail(NOT_FOUND); + } +} + +void RetrievePolicyOp::Execute() { + DBusThreadManager::Get()->GetSessionManagerClient()->RetrieveDevicePolicy( + base::Bind(&RetrievePolicyOp::OnStringComplete, this)); +} + +void RetrievePolicyOp::Fail(SignedSettings::ReturnCode code) { + VLOG(2) << "RetrievePolicyOp::Execute() failed with " << code; + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&RetrievePolicyOp::PerformCallback, this, code, + em::PolicyFetchResponse())); +} + +void RetrievePolicyOp::OnKeyOpComplete( + const OwnerManager::KeyOpCode return_code, + const std::vector<uint8>& payload) { + if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&RetrievePolicyOp::OnKeyOpComplete, this, return_code, + payload)); + return; + } + // Now, sure we're on the UI thread. + if (return_code == OwnerManager::SUCCESS) + Succeed(policy_); + else + Fail(SignedSettings::MapKeyOpCode(return_code)); +} + +RetrievePolicyOp::~RetrievePolicyOp() {} + +void RetrievePolicyOp::OnStringComplete(const std::string& serialized_proto) { + ProcessPolicy(serialized_proto); +} + +void RetrievePolicyOp::ProcessPolicy(const std::string& serialized_proto) { + if (serialized_proto.empty() || !policy_.ParseFromString(serialized_proto) || + (!policy_.has_policy_data() && !policy_.has_policy_data_signature())) { + Fail(NOT_FOUND); + return; + } + if (!policy_.has_policy_data()) { + Fail(OPERATION_FAILED); + return; + } + if (!policy_.has_policy_data_signature()) { + Fail(BAD_SIGNATURE); + return; + } + std::vector<uint8> sig; + const char* sig_ptr = policy_.policy_data_signature().c_str(); + sig.assign(sig_ptr, sig_ptr + policy_.policy_data_signature().length()); + service_->StartVerifyAttempt(policy_.policy_data(), sig, this); +} + +void RetrievePolicyOp::PerformCallback(SignedSettings::ReturnCode code, + const em::PolicyFetchResponse& value) { + d_->OnSettingsOpCompleted(code, value); +} + +} // namespace chromeos diff --git a/chrome/browser/chromeos/settings/signed_settings.h b/chrome/browser/chromeos/settings/signed_settings.h new file mode 100644 index 0000000..17822e5 --- /dev/null +++ b/chrome/browser/chromeos/settings/signed_settings.h @@ -0,0 +1,95 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_CHROMEOS_SETTINGS_SIGNED_SETTINGS_H_ +#define CHROME_BROWSER_CHROMEOS_SETTINGS_SIGNED_SETTINGS_H_ + +#include <vector> + +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "chrome/browser/chromeos/settings/owner_manager.h" + +// There are two operations that can be performed on the Chrome OS owner-signed +// settings store: Storing and Retrieving the policy blob. +// +// The pattern of use here is that the caller instantiates some +// subclass of SignedSettings by calling one of the create +// methods. Then, call Execute() on this object from the UI +// thread. It'll go off and do work (on the FILE thread and over DBus), +// and then call the appropriate method of the Delegate you passed in +// -- again, on the UI thread. + +namespace enterprise_management { +class PolicyData; +class PolicyFetchResponse; +} // namespace enterprise_management + +namespace chromeos { +class OwnershipService; + +extern const char kDevicePolicyType[]; + +class SignedSettings : public base::RefCountedThreadSafe<SignedSettings>, + public OwnerManager::Delegate { + public: + enum ReturnCode { + SUCCESS, + NOT_FOUND, // Email address or property name not found. + KEY_UNAVAILABLE, // Owner key not yet configured. + OPERATION_FAILED, // IPC to signed settings daemon failed. + BAD_SIGNATURE // Signature verification failed. + }; + + template <class T> + class Delegate { + public: + // This method will be called on the UI thread. + virtual void OnSettingsOpCompleted(ReturnCode code, T value) {} + }; + + SignedSettings(); + + // These are both "policy" operations, and only one instance of + // one type can be in flight at a time. + static SignedSettings* CreateStorePolicyOp( + enterprise_management::PolicyFetchResponse* policy, + SignedSettings::Delegate<bool>* d); + + static SignedSettings* CreateRetrievePolicyOp( + SignedSettings::Delegate< + const enterprise_management::PolicyFetchResponse&>* d); + + static ReturnCode MapKeyOpCode(OwnerManager::KeyOpCode code); + + virtual void Execute() = 0; + + virtual void Fail(ReturnCode code) = 0; + + // Implementation of OwnerManager::Delegate + virtual void OnKeyOpComplete(const OwnerManager::KeyOpCode return_code, + const std::vector<uint8>& payload) = 0; + + protected: + virtual ~SignedSettings(); + + static bool PolicyIsSane( + const enterprise_management::PolicyFetchResponse& value, + enterprise_management::PolicyData* poldata); + + void set_service(OwnershipService* service) { service_ = service; } + + OwnershipService* service_; + + private: + friend class base::RefCountedThreadSafe<SignedSettings>; + friend class SignedSettingsTest; + friend class SignedSettingsHelperTest; + + DISALLOW_COPY_AND_ASSIGN(SignedSettings); +}; + +} // namespace chromeos + +#endif // CHROME_BROWSER_CHROMEOS_SETTINGS_SIGNED_SETTINGS_H_ diff --git a/chrome/browser/chromeos/settings/signed_settings_cache.cc b/chrome/browser/chromeos/settings/signed_settings_cache.cc new file mode 100644 index 0000000..811f428 --- /dev/null +++ b/chrome/browser/chromeos/settings/signed_settings_cache.cc @@ -0,0 +1,133 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/chromeos/settings/signed_settings_cache.h" + +#include <string> + +#include "base/base64.h" +#include "base/bind.h" +#include "chrome/browser/chromeos/settings/cros_settings.h" +#include "chrome/browser/chromeos/settings/ownership_service.h" +#include "chrome/browser/chromeos/settings/signed_settings_helper.h" +#include "chrome/browser/policy/proto/device_management_backend.pb.h" +#include "chrome/browser/prefs/pref_service.h" +#include "chrome/common/pref_names.h" + +using content::BrowserThread; + +namespace em = enterprise_management; + +namespace chromeos { + +namespace { + +void OnStorePolicyCompleted(SignedSettings::ReturnCode code) { + if (code != SignedSettings::SUCCESS) + LOG(ERROR) << "Couldn't save temp store to the policy blob. code: " << code; + else + CrosSettings::Get()->ReloadProviders(); +} + +void FinishFinalize(PrefService* local_state, + SignedSettings::ReturnCode code, + const em::PolicyFetchResponse& policy) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + if (code != SignedSettings::SUCCESS) { + LOG(ERROR) << "Can't finalize temp store error code:" << code; + return; + } + + if (local_state) { + std::string encoded = + local_state->GetString(prefs::kSignedSettingsCache); + std::string policy_string; + if (!base::Base64Decode(encoded, &policy_string)) { + LOG(ERROR) << "Can't decode policy from base64 on finalizing."; + return; + } + + em::PolicyData merging_policy_data; + if (!merging_policy_data.ParseFromString(policy_string)) { + LOG(ERROR) << "Can't decode policy from string on finalizing."; + return; + } + + em::PolicyFetchResponse policy_envelope = policy; + DCHECK(policy_envelope.has_policy_data()); + em::PolicyData base_policy_data; + base_policy_data.ParseFromString(policy_envelope.policy_data()); + // Merge only the policy value as we should never ever rewrite the other + // fields of the PolicyData protobuf. + base_policy_data.set_policy_value(merging_policy_data.policy_value()); + policy_envelope.set_policy_data(base_policy_data.SerializeAsString()); + DCHECK(base_policy_data.has_username()); + policy_envelope.clear_policy_data_signature(); + SignedSettingsHelper::Get()->StartStorePolicyOp( + policy_envelope, base::Bind(&OnStorePolicyCompleted)); + } +} + +// Reload the initial policy blob, and if successful apply the settings from +// temp storage, and write them back the blob in FinishFinalize. +void ReloadSignedSettingsAndFinalize( + PrefService* local_state, + OwnershipService::Status status, + bool current_user_is_owner) { + if (current_user_is_owner) { + SignedSettingsHelper::Get()->StartRetrievePolicyOp( + base::Bind(FinishFinalize, local_state)); + } +} + +} // namespace + +namespace signed_settings_cache { + +void RegisterPrefs(PrefService* local_state) { + local_state->RegisterStringPref(prefs::kSignedSettingsCache, + "invalid", + PrefService::UNSYNCABLE_PREF); +} + +bool Store(const em::PolicyData& policy, PrefService* local_state) { + if (local_state) { + std::string policy_string = policy.SerializeAsString(); + std::string encoded; + if (!base::Base64Encode(policy_string, &encoded)) { + LOG(ERROR) << "Can't encode policy in base64."; + return false; + } + local_state->SetString(prefs::kSignedSettingsCache, encoded); + return true; + } + return false; +} + +bool Retrieve(em::PolicyData *policy, PrefService* local_state) { + if (local_state) { + std::string encoded = + local_state->GetString(prefs::kSignedSettingsCache); + std::string policy_string; + if (!base::Base64Decode(encoded, &policy_string)) { + // This is normal and happens on first boot. + VLOG(1) << "Can't decode policy from base64."; + return false; + } + return policy->ParseFromString(policy_string); + } + return false; +} + +void Finalize(PrefService* local_state) { + // First we have to make sure the owner is really logged in because the key + // notification is generated on every cloud policy key rotation too. + OwnershipService::GetSharedInstance()->GetStatusAsync( + base::Bind(&ReloadSignedSettingsAndFinalize, local_state)); +} + +} // namespace signed_settings_cache + +} // namespace chromeos diff --git a/chrome/browser/chromeos/settings/signed_settings_cache.h b/chrome/browser/chromeos/settings/signed_settings_cache.h new file mode 100644 index 0000000..ab9da59 --- /dev/null +++ b/chrome/browser/chromeos/settings/signed_settings_cache.h @@ -0,0 +1,39 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_CHROMEOS_SETTINGS_SIGNED_SETTINGS_CACHE_H_ +#define CHROME_BROWSER_CHROMEOS_SETTINGS_SIGNED_SETTINGS_CACHE_H_ + +namespace enterprise_management { +class PolicyData; +} + +class PrefService; + +namespace chromeos { + +// There is need (metrics at OOBE stage) to store settings +// (that normally would go into SignedSettings storage) +// before owner has been assigned (hence no key is available). +// This set of functions serves as a transient storage in that case. +namespace signed_settings_cache { +// Registers required pref section. +void RegisterPrefs(PrefService* local_state); + +// Stores a new policy blob inside the cache stored in |local_state|. +bool Store(const enterprise_management::PolicyData &policy, + PrefService* local_state); + +// Retrieves the policy blob from the cache stored in |local_state|. +bool Retrieve(enterprise_management::PolicyData *policy, + PrefService* local_state); + +// Call this after owner has been assigned to persist settings +// into SignedSettings storage. +void Finalize(PrefService* local_state); +} // namespace signed_settings_cache + +} // namespace chromeos + +#endif // CHROME_BROWSER_CHROMEOS_SETTINGS_SIGNED_SETTINGS_CACHE_H_ diff --git a/chrome/browser/chromeos/settings/signed_settings_cache_unittest.cc b/chrome/browser/chromeos/settings/signed_settings_cache_unittest.cc new file mode 100644 index 0000000..de8bbc5 --- /dev/null +++ b/chrome/browser/chromeos/settings/signed_settings_cache_unittest.cc @@ -0,0 +1,57 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/chromeos/settings/signed_settings_cache.h" + +#include "chrome/browser/policy/proto/chrome_device_policy.pb.h" +#include "chrome/browser/policy/proto/device_management_backend.pb.h" +#include "chrome/common/pref_names.h" +#include "chrome/test/base/testing_pref_service.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace em = enterprise_management; + +namespace chromeos { + +class SignedSettingsCacheTest : public testing::Test { + protected: + virtual void SetUp() { + // prepare some data. + policy_.set_policy_type("google/chromeos/device"); + em::ChromeDeviceSettingsProto pol; + pol.mutable_allow_new_users()->set_allow_new_users(false); + policy_.set_policy_value(pol.SerializeAsString()); + + signed_settings_cache::RegisterPrefs(&local_state_); + } + + TestingPrefService local_state_; + em::PolicyData policy_; +}; + +TEST_F(SignedSettingsCacheTest, Basic) { + EXPECT_TRUE(signed_settings_cache::Store(policy_, &local_state_)); + + em::PolicyData policy_out; + EXPECT_TRUE(signed_settings_cache::Retrieve(&policy_out, &local_state_)); + + EXPECT_TRUE(policy_out.has_policy_type()); + EXPECT_TRUE(policy_out.has_policy_value()); + + em::ChromeDeviceSettingsProto pol; + pol.ParseFromString(policy_out.policy_value()); + EXPECT_TRUE(pol.has_allow_new_users()); + EXPECT_FALSE(pol.allow_new_users().allow_new_users()); +} + +TEST_F(SignedSettingsCacheTest, CorruptData) { + EXPECT_TRUE(signed_settings_cache::Store(policy_, &local_state_)); + + local_state_.SetString(prefs::kSignedSettingsCache, "blaaa"); + + em::PolicyData policy_out; + EXPECT_FALSE(signed_settings_cache::Retrieve(&policy_out, &local_state_)); +} + +} // namespace chromeos diff --git a/chrome/browser/chromeos/settings/signed_settings_helper.cc b/chrome/browser/chromeos/settings/signed_settings_helper.cc new file mode 100644 index 0000000..56486fa --- /dev/null +++ b/chrome/browser/chromeos/settings/signed_settings_helper.cc @@ -0,0 +1,251 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/chromeos/settings/signed_settings_helper.h" + +#include <vector> + +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "chrome/browser/policy/proto/device_management_backend.pb.h" +#include "content/public/browser/browser_thread.h" + +using content::BrowserThread; + +namespace em = enterprise_management; + +namespace chromeos { + +namespace { + +class OpContext { + public: + class Delegate { + public: + virtual void OnOpCreated(OpContext* context) = 0; + virtual void OnOpStarted(OpContext* context) = 0; + virtual void OnOpCompleted(OpContext* context) = 0; + }; + + virtual ~OpContext() {} + + // Creates and execute op. + void Execute() { + CreateOp(); + CHECK(op_.get()); + if (delegate_) + delegate_->OnOpCreated(this); + + // Note that the context could be released when op_->Execute() returns. + // So keep a local copy of delegate and executing flag to use after + // the call. + Delegate* delegate = delegate_; + executing_ = true; + op_->Execute(); + if (delegate) + delegate->OnOpStarted(this); + } + + // Cancels the callback and cancels the op if it is not executing. + void Cancel() { + if (!executing_) + OnOpCompleted(); + } + + // Accessors. + SignedSettings* op() const { + return op_.get(); + } + + void set_delegate(Delegate* delegate) { + delegate_ = delegate; + } + + protected: + explicit OpContext(Delegate* delegate) + : executing_(false), + delegate_(delegate) { + } + + // Creates the op to execute. + virtual void CreateOp() = 0; + + // Callback on op completion. + virtual void OnOpCompleted() { + if (delegate_) + delegate_->OnOpCompleted(this); + + delete this; + } + + bool executing_; + Delegate* delegate_; + + scoped_refptr<SignedSettings> op_; +}; + +class StorePolicyOpContext + : public SignedSettings::Delegate<bool>, + public OpContext { + public: + StorePolicyOpContext(const em::PolicyFetchResponse& policy, + SignedSettingsHelper::StorePolicyCallback callback, + OpContext::Delegate* delegate) + : OpContext(delegate), + callback_(callback), + policy_(policy) { + } + + // chromeos::SignedSettings::Delegate implementation + virtual void OnSettingsOpCompleted(SignedSettings::ReturnCode code, + bool unused) OVERRIDE { + VLOG(2) << "OnSettingsOpCompleted, code = " << code; + callback_.Run(code); + OnOpCompleted(); + } + + protected: + // OpContext implementation + virtual void CreateOp() OVERRIDE { + op_ = SignedSettings::CreateStorePolicyOp(&policy_, this); + } + + private: + SignedSettingsHelper::StorePolicyCallback callback_; + em::PolicyFetchResponse policy_; + + DISALLOW_COPY_AND_ASSIGN(StorePolicyOpContext); +}; + +class RetrievePolicyOpContext + : public SignedSettings::Delegate<const em::PolicyFetchResponse&>, + public OpContext { + public: + RetrievePolicyOpContext(SignedSettingsHelper::RetrievePolicyCallback callback, + OpContext::Delegate* delegate) + : OpContext(delegate), + callback_(callback){ + } + + // chromeos::SignedSettings::Delegate implementation + virtual void OnSettingsOpCompleted( + SignedSettings::ReturnCode code, + const em::PolicyFetchResponse& policy) OVERRIDE { + callback_.Run(code, policy); + OnOpCompleted(); + } + + protected: + // OpContext implementation + virtual void CreateOp() OVERRIDE { + op_ = SignedSettings::CreateRetrievePolicyOp(this); + } + + private: + SignedSettingsHelper::RetrievePolicyCallback callback_; + + DISALLOW_COPY_AND_ASSIGN(RetrievePolicyOpContext); +}; + +} // namespace + + +class SignedSettingsHelperImpl : public SignedSettingsHelper, + public OpContext::Delegate { + public: + // SignedSettingsHelper implementation + virtual void StartStorePolicyOp(const em::PolicyFetchResponse& policy, + StorePolicyCallback callback) OVERRIDE; + virtual void StartRetrievePolicyOp(RetrievePolicyCallback callback) OVERRIDE; + + // OpContext::Delegate implementation + virtual void OnOpCreated(OpContext* context); + virtual void OnOpStarted(OpContext* context); + virtual void OnOpCompleted(OpContext* context); + + private: + SignedSettingsHelperImpl(); + virtual ~SignedSettingsHelperImpl(); + + void AddOpContext(OpContext* context); + void ClearAll(); + + std::vector<OpContext*> pending_contexts_; + + friend struct base::DefaultLazyInstanceTraits<SignedSettingsHelperImpl>; + DISALLOW_COPY_AND_ASSIGN(SignedSettingsHelperImpl); +}; + +static base::LazyInstance<SignedSettingsHelperImpl> + g_signed_settings_helper_impl = LAZY_INSTANCE_INITIALIZER; + +SignedSettingsHelperImpl::SignedSettingsHelperImpl() { +} + +SignedSettingsHelperImpl::~SignedSettingsHelperImpl() { + if (!pending_contexts_.empty()) { + LOG(WARNING) << "SignedSettingsHelperImpl shutdown with pending ops, " + << "changes will be lost."; + ClearAll(); + } +} + +void SignedSettingsHelperImpl::StartStorePolicyOp( + const em::PolicyFetchResponse& policy, + StorePolicyCallback callback) { + AddOpContext(new StorePolicyOpContext(policy, callback, this)); +} + +void SignedSettingsHelperImpl::StartRetrievePolicyOp( + RetrievePolicyCallback callback) { + AddOpContext(new RetrievePolicyOpContext(callback, this)); +} + +void SignedSettingsHelperImpl::AddOpContext(OpContext* context) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + CHECK(context); + + pending_contexts_.push_back(context); + if (pending_contexts_.size() == 1) + context->Execute(); +} + +void SignedSettingsHelperImpl::ClearAll() { + for (size_t i = 0; i < pending_contexts_.size(); ++i) { + pending_contexts_[i]->set_delegate(NULL); + pending_contexts_[i]->Cancel(); + } + pending_contexts_.clear(); +} + +void SignedSettingsHelperImpl::OnOpCreated(OpContext* context) { + if (test_delegate_) + test_delegate_->OnOpCreated(context->op()); +} + +void SignedSettingsHelperImpl::OnOpStarted(OpContext* context) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + if (test_delegate_) + test_delegate_->OnOpStarted(context->op()); +} + +void SignedSettingsHelperImpl::OnOpCompleted(OpContext* context) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + DCHECK(pending_contexts_.front() == context); + + pending_contexts_.erase(pending_contexts_.begin()); + if (!pending_contexts_.empty()) + pending_contexts_.front()->Execute(); + + if (test_delegate_) + test_delegate_->OnOpCompleted(context->op()); +} + +SignedSettingsHelper* SignedSettingsHelper::Get() { + return g_signed_settings_helper_impl.Pointer(); +} + +} // namespace chromeos diff --git a/chrome/browser/chromeos/settings/signed_settings_helper.h b/chrome/browser/chromeos/settings/signed_settings_helper.h new file mode 100644 index 0000000..466f45a --- /dev/null +++ b/chrome/browser/chromeos/settings/signed_settings_helper.h @@ -0,0 +1,58 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_CHROMEOS_SETTINGS_SIGNED_SETTINGS_HELPER_H_ +#define CHROME_BROWSER_CHROMEOS_SETTINGS_SIGNED_SETTINGS_HELPER_H_ + +#include "chrome/browser/chromeos/settings/signed_settings.h" + +namespace enterprise_management { +class PolicyFetchResponse; +} // namespace enterprise_management + +namespace chromeos { + +// Helper to serialize signed settings ops, provide unified callback interface, +// and handle callbacks destruction before ops completion. +class SignedSettingsHelper { + public: + typedef base::Callback<void(SignedSettings::ReturnCode)> StorePolicyCallback; + typedef + base::Callback<void(SignedSettings::ReturnCode, + const enterprise_management::PolicyFetchResponse&)> + RetrievePolicyCallback; + + // Class factory + static SignedSettingsHelper* Get(); + + // Functions to start signed settings ops. + virtual void StartStorePolicyOp( + const enterprise_management::PolicyFetchResponse& policy, + StorePolicyCallback callback) = 0; + virtual void StartRetrievePolicyOp( + RetrievePolicyCallback callback) = 0; + + class TestDelegate { + public: + virtual void OnOpCreated(SignedSettings* op) = 0; + virtual void OnOpStarted(SignedSettings* op) = 0; + virtual void OnOpCompleted(SignedSettings* op) = 0; + }; + +#if defined(UNIT_TEST) + void set_test_delegate(TestDelegate* test_delegate) { + test_delegate_ = test_delegate; + } +#endif // defined(UNIT_TEST) + + protected: + SignedSettingsHelper() : test_delegate_(NULL) { + } + + TestDelegate* test_delegate_; +}; + +} // namespace chromeos + +#endif // CHROME_BROWSER_CHROMEOS_SETTINGS_SIGNED_SETTINGS_HELPER_H_ diff --git a/chrome/browser/chromeos/settings/signed_settings_helper_unittest.cc b/chrome/browser/chromeos/settings/signed_settings_helper_unittest.cc new file mode 100644 index 0000000..5a01f0d --- /dev/null +++ b/chrome/browser/chromeos/settings/signed_settings_helper_unittest.cc @@ -0,0 +1,209 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/chromeos/settings/signed_settings_helper.h" + +#include "base/bind.h" +#include "base/message_loop.h" +#include "chrome/browser/chromeos/cros/cros_library.h" +#include "chrome/browser/chromeos/settings/cros_settings_names.h" +#include "chrome/browser/chromeos/settings/mock_owner_key_utils.h" +#include "chrome/browser/chromeos/settings/mock_ownership_service.h" +#include "chrome/browser/chromeos/settings/owner_manager.h" +#include "chrome/browser/chromeos/settings/signed_settings.h" +#include "chrome/browser/policy/proto/chrome_device_policy.pb.h" +#include "chrome/browser/policy/proto/device_management_backend.pb.h" +#include "chromeos/dbus/dbus_thread_manager.h" +#include "chromeos/dbus/mock_dbus_thread_manager.h" +#include "chromeos/dbus/mock_session_manager_client.h" +#include "content/public/test/test_browser_thread.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::_; +using ::testing::A; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::WithArg; + +namespace em = enterprise_management; +namespace chromeos { + +ACTION_P(Retrieve, policy_blob) { arg0.Run(policy_blob); } +ACTION_P(Store, success) { arg1.Run(success); } + +class SignedSettingsHelperTest : public testing::Test, + public SignedSettingsHelper::TestDelegate { + public: + SignedSettingsHelperTest() + : message_loop_(MessageLoop::TYPE_UI), + ui_thread_(content::BrowserThread::UI, &message_loop_), + file_thread_(content::BrowserThread::FILE, &message_loop_), + pending_ops_(0), + mock_dbus_thread_manager_(new MockDBusThreadManager) { + } + + virtual void SetUp() { + SignedSettingsHelper::Get()->set_test_delegate(this); + DBusThreadManager::InitializeForTesting(mock_dbus_thread_manager_); + + fake_policy_data_ = BuildPolicyData(); + std::string data_serialized = fake_policy_data_.SerializeAsString(); + std::string serialized_policy_; + fake_policy_ = BuildProto(data_serialized, + std::string("false"), + &serialized_policy_); + + MockSessionManagerClient* client = + mock_dbus_thread_manager_->mock_session_manager_client(); + // Make sure the mocked out class calls back to notify success on store and + // retrieve ops. + EXPECT_CALL(*client, StoreDevicePolicy(_, _)) + .WillRepeatedly(Store(true)); + EXPECT_CALL(*client, RetrieveDevicePolicy(_)) + .WillRepeatedly(Retrieve(serialized_policy_)); + + EXPECT_CALL(m_, StartSigningAttempt(_, A<OwnerManager::Delegate*>())) + .WillRepeatedly(WithArg<1>( + Invoke(&SignedSettingsHelperTest::OnKeyOpComplete))); + EXPECT_CALL(m_, StartVerifyAttempt(_, _, A<OwnerManager::Delegate*>())) + .WillRepeatedly(WithArg<2>( + Invoke(&SignedSettingsHelperTest::OnKeyOpComplete))); + } + + virtual void TearDown() { + DBusThreadManager::Shutdown(); + SignedSettingsHelper::Get()->set_test_delegate(NULL); + } + + virtual void OnOpCreated(SignedSettings* op) { + // Use MockOwnershipService for all SignedSettings op. + op->set_service(&m_); + } + + virtual void OnOpStarted(SignedSettings* op) { + } + + virtual void OnOpCompleted(SignedSettings* op) { + --pending_ops_; + } + + static void OnKeyOpComplete(OwnerManager::Delegate* op) { + op->OnKeyOpComplete(OwnerManager::SUCCESS, std::vector<uint8>()); + } + + em::PolicyData BuildPolicyData() { + em::PolicyData to_return; + em::ChromeDeviceSettingsProto pol; + to_return.set_policy_type(chromeos::kDevicePolicyType); + to_return.set_policy_value(pol.SerializeAsString()); + return to_return; + } + + em::PolicyFetchResponse BuildProto(const std::string& data, + const std::string& sig, + std::string* out_serialized) { + em::PolicyFetchResponse fake_policy; + if (!data.empty()) + fake_policy.set_policy_data(data); + if (!sig.empty()) + fake_policy.set_policy_data_signature(sig); + EXPECT_TRUE(fake_policy.SerializeToString(out_serialized)); + return fake_policy; + } + + em::PolicyData fake_policy_data_; + em::PolicyFetchResponse fake_policy_; + std::string serialized_policy_; + MockOwnershipService m_; + + MessageLoop message_loop_; + content::TestBrowserThread ui_thread_; + content::TestBrowserThread file_thread_; + + int pending_ops_; + + MockDBusThreadManager* mock_dbus_thread_manager_; + + ScopedStubCrosEnabler stub_cros_enabler_; +}; + +class SignedSettingsCallbacks { + public: + virtual ~SignedSettingsCallbacks() {} + // Callback of StorePolicyOp. + virtual void OnStorePolicyCompleted(SignedSettings::ReturnCode code) = 0; + // Callback of RetrievePolicyOp. + virtual void OnRetrievePolicyCompleted( + SignedSettings::ReturnCode code, + const em::PolicyFetchResponse& policy) = 0; +}; + +class MockSignedSettingsCallbacks + : public SignedSettingsCallbacks, + public base::SupportsWeakPtr<MockSignedSettingsCallbacks> { +public: + virtual ~MockSignedSettingsCallbacks() {} + + MOCK_METHOD1(OnStorePolicyCompleted, void(SignedSettings::ReturnCode)); + MOCK_METHOD2(OnRetrievePolicyCompleted, void(SignedSettings::ReturnCode, + const em::PolicyFetchResponse&)); +}; + +TEST_F(SignedSettingsHelperTest, SerializedOps) { + MockSignedSettingsCallbacks cb; + + InSequence s; + EXPECT_CALL(cb, OnStorePolicyCompleted(SignedSettings::SUCCESS)) + .Times(1); + EXPECT_CALL(cb, OnRetrievePolicyCompleted(SignedSettings::SUCCESS, _)) + .Times(1); + + + pending_ops_ = 2; + SignedSettingsHelper::Get()->StartStorePolicyOp( + fake_policy_, + base::Bind(&MockSignedSettingsCallbacks::OnStorePolicyCompleted, + base::Unretained(&cb))); + SignedSettingsHelper::Get()->StartRetrievePolicyOp( + base::Bind(&MockSignedSettingsCallbacks::OnRetrievePolicyCompleted, + base::Unretained(&cb))); + + message_loop_.RunAllPending(); + ASSERT_EQ(0, pending_ops_); +} + +TEST_F(SignedSettingsHelperTest, CanceledOps) { + MockSignedSettingsCallbacks cb; + + InSequence s; + EXPECT_CALL(cb, OnStorePolicyCompleted(SignedSettings::SUCCESS)) + .Times(1); + EXPECT_CALL(cb, OnRetrievePolicyCompleted(SignedSettings::SUCCESS, _)) + .Times(1); + + pending_ops_ = 3; + + { + // This op will be deleted and never will be executed (expect only one call + // to OnRetrievePolicyCompleted above). However the OpComplete callback will + // be still called, therefore we expect three pending ops. + MockSignedSettingsCallbacks cb_to_be_deleted; + SignedSettingsHelper::Get()->StartRetrievePolicyOp( + base::Bind(&MockSignedSettingsCallbacks::OnRetrievePolicyCompleted, + cb_to_be_deleted.AsWeakPtr())); + } + SignedSettingsHelper::Get()->StartStorePolicyOp( + fake_policy_, + base::Bind(&MockSignedSettingsCallbacks::OnStorePolicyCompleted, + base::Unretained(&cb))); + SignedSettingsHelper::Get()->StartRetrievePolicyOp( + base::Bind(&MockSignedSettingsCallbacks::OnRetrievePolicyCompleted, + base::Unretained(&cb))); + + message_loop_.RunAllPending(); + ASSERT_EQ(0, pending_ops_); +} + +} // namespace chromeos diff --git a/chrome/browser/chromeos/settings/signed_settings_migration_helper.cc b/chrome/browser/chromeos/settings/signed_settings_migration_helper.cc new file mode 100644 index 0000000..566f2b3 --- /dev/null +++ b/chrome/browser/chromeos/settings/signed_settings_migration_helper.cc @@ -0,0 +1,66 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/chromeos/settings/signed_settings_migration_helper.h" + +#include "base/bind.h" +#include "base/values.h" +#include "chrome/browser/chromeos/settings/cros_settings.h" +#include "chrome/common/chrome_notification_types.h" +#include "content/public/browser/notification_service.h" + +namespace chromeos { + +SignedSettingsMigrationHelper::SignedSettingsMigrationHelper() + : ALLOW_THIS_IN_INITIALIZER_LIST(ptr_factory_(this)) { + registrar_.Add(this, chrome::NOTIFICATION_OWNERSHIP_CHECKED, + content::NotificationService::AllSources()); +} + +SignedSettingsMigrationHelper::~SignedSettingsMigrationHelper() { + registrar_.RemoveAll(); + migration_values_.Clear(); +} + +void SignedSettingsMigrationHelper::AddMigrationValue(const std::string& path, + base::Value* value) { + migration_values_.SetValue(path, value); +} + +void SignedSettingsMigrationHelper::MigrateValues(void) { + ptr_factory_.InvalidateWeakPtrs(); + OwnershipService::GetSharedInstance()->GetStatusAsync( + base::Bind(&SignedSettingsMigrationHelper::DoMigrateValues, + ptr_factory_.GetWeakPtr())); +} + +// NotificationObserver overrides: +void SignedSettingsMigrationHelper::Observe( + int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) { + if (type == chrome::NOTIFICATION_OWNERSHIP_CHECKED) + MigrateValues(); +} + +void SignedSettingsMigrationHelper::DoMigrateValues( + OwnershipService::Status status, + bool current_user_is_owner) { + // We can call StartStorePropertyOp in two cases - either if the owner is + // currently logged in and the policy can be updated immediately or if there + // is no owner yet in which case the value will be temporarily stored in the + // SignedSettingsCache until the device is owned. If none of these + // cases is met then we will wait for user change notification and retry. + if (current_user_is_owner || status != OwnershipService::OWNERSHIP_TAKEN) { + std::map<std::string, base::Value*>::const_iterator i; + for (i = migration_values_.begin(); i != migration_values_.end(); ++i) { + // Queue all values for storing. + CrosSettings::Get()->Set(i->first, *i->second); + } + migration_values_.Clear(); + } +} + +} // namespace chromeos + diff --git a/chrome/browser/chromeos/settings/signed_settings_migration_helper.h b/chrome/browser/chromeos/settings/signed_settings_migration_helper.h new file mode 100644 index 0000000..13d5bfb --- /dev/null +++ b/chrome/browser/chromeos/settings/signed_settings_migration_helper.h @@ -0,0 +1,59 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_CHROMEOS_SETTINGS_SIGNED_SETTINGS_MIGRATION_HELPER_H_ +#define CHROME_BROWSER_CHROMEOS_SETTINGS_SIGNED_SETTINGS_MIGRATION_HELPER_H_ + +#include "base/memory/weak_ptr.h" +#include "chrome/browser/chromeos/settings/ownership_service.h" +#include "chrome/browser/chromeos/settings/signed_settings_helper.h" +#include "chrome/browser/prefs/pref_value_map.h" +#include "content/public/browser/notification_observer.h" +#include "content/public/browser/notification_registrar.h" + +namespace base { +class Value; +} + +namespace chromeos { + +// This class provides the means to migrate settings to the signed settings +// store. It does one of three things - store the settings in the policy blob +// immediately if the current user is the owner. Uses the +// SignedSettingsCache if there is no owner yet, or waits for an +// OWNERSHIP_CHECKED notification to delay the storing until the owner has +// logged in. +class SignedSettingsMigrationHelper : public content::NotificationObserver { + public: + SignedSettingsMigrationHelper(); + virtual ~SignedSettingsMigrationHelper(); + + // Adds a value to be migrated. The class takes ownership of the |value|. + void AddMigrationValue(const std::string& path, base::Value* value); + + // Initiates values migration. If the device is already owned this will + // happen immediately if not it will wait for ownership login and finish the + // migration then. + void MigrateValues(void); + + // NotificationObserver overrides: + virtual void Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) OVERRIDE; + + private: + // Does the actual migration when ownership has been confirmed. + void DoMigrateValues(OwnershipService::Status status, + bool current_user_is_owner); + + content::NotificationRegistrar registrar_; + base::WeakPtrFactory<SignedSettingsMigrationHelper> ptr_factory_; + PrefValueMap migration_values_; + + DISALLOW_COPY_AND_ASSIGN(SignedSettingsMigrationHelper); +}; + +} // namespace chromeos + +#endif // CHROME_BROWSER_CHROMEOS_SETTINGS_SIGNED_SETTINGS_MIGRATION_HELPER_H_ diff --git a/chrome/browser/chromeos/settings/signed_settings_unittest.cc b/chrome/browser/chromeos/settings/signed_settings_unittest.cc new file mode 100644 index 0000000..0765f91 --- /dev/null +++ b/chrome/browser/chromeos/settings/signed_settings_unittest.cc @@ -0,0 +1,398 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/chromeos/settings/signed_settings.h" + +#include "base/logging.h" +#include "base/message_loop.h" +#include "base/values.h" +#include "chrome/browser/chromeos/cros/cros_library.h" +#include "chrome/browser/chromeos/settings/cros_settings_names.h" +#include "chrome/browser/chromeos/settings/mock_owner_key_utils.h" +#include "chrome/browser/chromeos/settings/mock_ownership_service.h" +#include "chrome/browser/chromeos/settings/owner_manager_unittest.h" +#include "chrome/browser/policy/proto/chrome_device_policy.pb.h" +#include "chrome/browser/policy/proto/device_management_backend.pb.h" +#include "chromeos/dbus/mock_dbus_thread_manager.h" +#include "chromeos/dbus/mock_session_manager_client.h" +#include "content/public/test/test_browser_thread.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::A; +using ::testing::AnyNumber; +using ::testing::Return; +using ::testing::ReturnRef; +using ::testing::SaveArg; +using ::testing::StrEq; +using ::testing::WithArg; +using ::testing::_; +using content::BrowserThread; +using google::protobuf::RepeatedPtrField; + +namespace em = enterprise_management; +namespace chromeos { + +namespace { +template <class T> +class DummyDelegate : public SignedSettings::Delegate<T> { + public: + explicit DummyDelegate(T to_expect) + : expect_success_(false), + expected_failure_(SignedSettings::SUCCESS), + expected_(to_expect), + run_(false) {} + virtual ~DummyDelegate() { EXPECT_TRUE(run_); } + virtual void OnSettingsOpCompleted(SignedSettings::ReturnCode code, + T value) { + run_ = true; + if (expect_success_) + compare_expected(value); + EXPECT_EQ(expected_failure_, code); + } + virtual void expect_success() { + expect_success_ = true; + expected_failure_ = SignedSettings::SUCCESS; + } + virtual void expect_failure(SignedSettings::ReturnCode code) { + expect_success_ = false; + expected_failure_ = code; + } + + protected: + bool expect_success_; + SignedSettings::ReturnCode expected_failure_; + T expected_; + bool run_; + virtual void compare_expected(T to_compare) = 0; +}; + +template <class T> +class NormalDelegate : public DummyDelegate<T> { + public: + explicit NormalDelegate(T to_expect) : DummyDelegate<T>(to_expect) {} + virtual ~NormalDelegate() {} + protected: + virtual void compare_expected(T to_compare) { + // without this-> this won't build. + EXPECT_EQ(this->expected_, to_compare); + } +}; + +class ProtoDelegate : public DummyDelegate<const em::PolicyFetchResponse&> { + public: + explicit ProtoDelegate(const em::PolicyFetchResponse& e) + : DummyDelegate<const em::PolicyFetchResponse&>(e) { + } + virtual ~ProtoDelegate() {} + protected: + virtual void compare_expected(const em::PolicyFetchResponse& to_compare) { + std::string ex_string, comp_string; + EXPECT_TRUE(expected_.SerializeToString(&ex_string)); + EXPECT_TRUE(to_compare.SerializeToString(&comp_string)); + EXPECT_EQ(ex_string, comp_string); + } +}; + +} // anonymous namespace + +class SignedSettingsTest : public testing::Test { + public: + SignedSettingsTest() + : fake_prop_(kAccountsPrefAllowGuest), + fake_signature_("false"), + fake_value_(false), + fake_value_signature_( + fake_signature_.c_str(), + fake_signature_.c_str() + fake_signature_.length()), + message_loop_(MessageLoop::TYPE_UI), + ui_thread_(BrowserThread::UI, &message_loop_), + file_thread_(BrowserThread::FILE), + mock_(new MockKeyUtils), + injector_(mock_) /* injector_ takes ownership of mock_ */, + mock_dbus_thread_manager_(new MockDBusThreadManager) { + } + + virtual ~SignedSettingsTest() {} + + virtual void SetUp() { + file_thread_.Start(); + DBusThreadManager::InitializeForTesting(mock_dbus_thread_manager_); + } + + virtual void TearDown() { + OwnerKeyUtils::set_factory(NULL); + DBusThreadManager::Shutdown(); + } + + void mock_service(SignedSettings* s, MockOwnershipService* m) { + s->set_service(m); + } + + void FailingStorePolicyOp(const OwnerManager::KeyOpCode return_code) { + NormalDelegate<bool> d(false); + d.expect_failure(SignedSettings::MapKeyOpCode(return_code)); + + em::PolicyFetchResponse fake_policy; + fake_policy.set_policy_data(fake_prop_); + std::string serialized; + ASSERT_TRUE(fake_policy.SerializeToString(&serialized)); + + scoped_refptr<SignedSettings> s( + SignedSettings::CreateStorePolicyOp(&fake_policy, &d)); + + mock_service(s.get(), &m_); + EXPECT_CALL(m_, StartSigningAttempt(StrEq(fake_prop_), _)) + .Times(1); + + s->Execute(); + s->OnKeyOpComplete(return_code, std::vector<uint8>()); + message_loop_.RunAllPending(); + } + + em::PolicyData BuildPolicyData(std::vector<std::string> whitelist) { + em::PolicyData to_return; + em::ChromeDeviceSettingsProto pol; + em::GuestModeEnabledProto* allow = pol.mutable_guest_mode_enabled(); + allow->set_guest_mode_enabled(false); + pol.mutable_device_proxy_settings()->set_proxy_mode("direct"); + + if (!whitelist.empty()) { + em::UserWhitelistProto* whitelist_proto = pol.mutable_user_whitelist(); + for (std::vector<std::string>::const_iterator it = whitelist.begin(); + it != whitelist.end(); + ++it) { + whitelist_proto->add_user_whitelist(*it); + } + } + + to_return.set_policy_type(chromeos::kDevicePolicyType); + to_return.set_policy_value(pol.SerializeAsString()); + return to_return; + } + + em::PolicyFetchResponse BuildProto(const std::string& data, + const std::string& sig, + std::string* out_serialized) { + em::PolicyFetchResponse fake_policy; + if (!data.empty()) + fake_policy.set_policy_data(data); + if (!sig.empty()) + fake_policy.set_policy_data_signature(sig); + EXPECT_TRUE(fake_policy.SerializeToString(out_serialized)); + return fake_policy; + } + + const std::string fake_prop_; + const std::string fake_signature_; + const base::FundamentalValue fake_value_; + const std::vector<uint8> fake_value_signature_; + MockOwnershipService m_; + + MessageLoop message_loop_; + content::TestBrowserThread ui_thread_; + content::TestBrowserThread file_thread_; + + MockKeyUtils* mock_; + MockInjector injector_; + MockDBusThreadManager* mock_dbus_thread_manager_; + + ScopedStubCrosEnabler stub_cros_enabler_; +}; + +ACTION_P(Retrieve, policy_blob) { arg0.Run(policy_blob); } +ACTION_P(Store, success) { arg1.Run(success); } + +TEST_F(SignedSettingsTest, SignAndStorePolicy) { + NormalDelegate<bool> d(true); + d.expect_success(); + + em::PolicyData in_pol = BuildPolicyData(std::vector<std::string>()); + std::string data_serialized = in_pol.SerializeAsString(); + std::string serialized; + em::PolicyFetchResponse fake_policy = BuildProto(data_serialized, + std::string(), + &serialized); + scoped_refptr<SignedSettings> s( + SignedSettings::CreateStorePolicyOp(&fake_policy, &d)); + + mock_service(s.get(), &m_); + EXPECT_CALL(m_, StartSigningAttempt(StrEq(data_serialized), _)) + .Times(1); + em::PolicyData out_pol; + + // Ask for signature over unsigned policy. + s->Execute(); + message_loop_.RunAllPending(); + + // Fake out a successful signing. + std::string signed_serialized; + em::PolicyFetchResponse signed_policy = BuildProto(data_serialized, + fake_signature_, + &signed_serialized); + MockSessionManagerClient* client = + mock_dbus_thread_manager_->mock_session_manager_client(); + EXPECT_CALL(*client, StoreDevicePolicy(signed_serialized, _)) + .WillOnce(Store(true)) + .RetiresOnSaturation(); + s->OnKeyOpComplete(OwnerManager::SUCCESS, fake_value_signature_); + message_loop_.RunAllPending(); +} + +TEST_F(SignedSettingsTest, StoreSignedPolicy) { + NormalDelegate<bool> d(true); + d.expect_success(); + + em::PolicyData in_pol = BuildPolicyData(std::vector<std::string>()); + std::string serialized = in_pol.SerializeAsString(); + std::string signed_serialized; + em::PolicyFetchResponse signed_policy = BuildProto(serialized, + fake_signature_, + &signed_serialized); + scoped_refptr<SignedSettings> s( + SignedSettings::CreateStorePolicyOp(&signed_policy, &d)); + MockSessionManagerClient* client = + mock_dbus_thread_manager_->mock_session_manager_client(); + EXPECT_CALL(*client, StoreDevicePolicy(signed_serialized, _)) + .WillOnce(Store(true)) + .RetiresOnSaturation(); + + mock_service(s.get(), &m_); + em::PolicyData out_pol; + + s->Execute(); + message_loop_.RunAllPending(); +} + +TEST_F(SignedSettingsTest, StorePolicyNoKey) { + FailingStorePolicyOp(OwnerManager::KEY_UNAVAILABLE); +} + +TEST_F(SignedSettingsTest, StorePolicyFailed) { + FailingStorePolicyOp(OwnerManager::OPERATION_FAILED); +} + +TEST_F(SignedSettingsTest, StorePolicyNoPolicyData) { + NormalDelegate<bool> d(false); + d.expect_failure(SignedSettings::OPERATION_FAILED); + + std::string serialized; + em::PolicyFetchResponse fake_policy = BuildProto(std::string(), + std::string(), + &serialized); + scoped_refptr<SignedSettings> s( + SignedSettings::CreateStorePolicyOp(&fake_policy, &d)); + + s->Execute(); + message_loop_.RunAllPending(); +} + +TEST_F(SignedSettingsTest, RetrievePolicy) { + em::PolicyData in_pol = BuildPolicyData(std::vector<std::string>()); + std::string serialized = in_pol.SerializeAsString(); + std::string signed_serialized; + em::PolicyFetchResponse signed_policy = BuildProto(serialized, + fake_signature_, + &signed_serialized); + ProtoDelegate d(signed_policy); + d.expect_success(); + scoped_refptr<SignedSettings> s(SignedSettings::CreateRetrievePolicyOp(&d)); + + MockSessionManagerClient* client = + mock_dbus_thread_manager_->mock_session_manager_client(); + EXPECT_CALL(*client, RetrieveDevicePolicy(_)) + .WillOnce(Retrieve(signed_serialized)) + .RetiresOnSaturation(); + + mock_service(s.get(), &m_); + EXPECT_CALL(m_, StartVerifyAttempt(serialized, fake_value_signature_, _)) + .Times(1); + em::PolicyData out_pol; + + s->Execute(); + message_loop_.RunAllPending(); + + s->OnKeyOpComplete(OwnerManager::SUCCESS, std::vector<uint8>()); + message_loop_.RunAllPending(); +} + +TEST_F(SignedSettingsTest, RetrieveNullPolicy) { + em::PolicyFetchResponse policy; + ProtoDelegate d(policy); + d.expect_failure(SignedSettings::NOT_FOUND); + scoped_refptr<SignedSettings> s(SignedSettings::CreateRetrievePolicyOp(&d)); + + MockSessionManagerClient* client = + mock_dbus_thread_manager_->mock_session_manager_client(); + EXPECT_CALL(*client, RetrieveDevicePolicy(_)) + .WillOnce(Retrieve("")) + .RetiresOnSaturation(); + + s->Execute(); + message_loop_.RunAllPending(); +} + +TEST_F(SignedSettingsTest, RetrieveEmptyPolicy) { + std::string serialized; + em::PolicyFetchResponse policy = BuildProto("", "", &serialized); + ProtoDelegate d(policy); + d.expect_failure(SignedSettings::NOT_FOUND); + scoped_refptr<SignedSettings> s(SignedSettings::CreateRetrievePolicyOp(&d)); + + MockSessionManagerClient* client = + mock_dbus_thread_manager_->mock_session_manager_client(); + EXPECT_CALL(*client, RetrieveDevicePolicy(_)) + .WillOnce(Retrieve("")) + .RetiresOnSaturation(); + + s->Execute(); + message_loop_.RunAllPending(); +} + +TEST_F(SignedSettingsTest, RetrieveUnsignedPolicy) { + std::string serialized; + em::PolicyFetchResponse policy = BuildProto(fake_prop_, + std::string(), + &serialized); + ProtoDelegate d(policy); + d.expect_failure(SignedSettings::BAD_SIGNATURE); + scoped_refptr<SignedSettings> s(SignedSettings::CreateRetrievePolicyOp(&d)); + + MockSessionManagerClient* client = + mock_dbus_thread_manager_->mock_session_manager_client(); + EXPECT_CALL(*client, RetrieveDevicePolicy(_)) + .WillOnce(Retrieve(serialized)) + .RetiresOnSaturation(); + + s->Execute(); + message_loop_.RunAllPending(); +} + +TEST_F(SignedSettingsTest, RetrieveMalsignedPolicy) { + std::string signed_serialized; + em::PolicyFetchResponse signed_policy = BuildProto(fake_prop_, + fake_signature_, + &signed_serialized); + ProtoDelegate d(signed_policy); + d.expect_failure(SignedSettings::BAD_SIGNATURE); + scoped_refptr<SignedSettings> s(SignedSettings::CreateRetrievePolicyOp(&d)); + + MockSessionManagerClient* client = + mock_dbus_thread_manager_->mock_session_manager_client(); + EXPECT_CALL(*client, RetrieveDevicePolicy(_)) + .WillOnce(Retrieve(signed_serialized)) + .RetiresOnSaturation(); + + mock_service(s.get(), &m_); + EXPECT_CALL(m_, StartVerifyAttempt(fake_prop_, fake_value_signature_, _)) + .Times(1); + + s->Execute(); + message_loop_.RunAllPending(); + + s->OnKeyOpComplete(OwnerManager::OPERATION_FAILED, std::vector<uint8>()); + message_loop_.RunAllPending(); +} + +} // namespace chromeos diff --git a/chrome/browser/chromeos/settings/stub_cros_settings_provider.cc b/chrome/browser/chromeos/settings/stub_cros_settings_provider.cc new file mode 100644 index 0000000..facadc3 --- /dev/null +++ b/chrome/browser/chromeos/settings/stub_cros_settings_provider.cc @@ -0,0 +1,92 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/chromeos/settings/stub_cros_settings_provider.h" + +#include "base/logging.h" +#include "base/values.h" +#include "chrome/browser/chromeos/login/user_manager.h" +#include "chrome/browser/chromeos/settings/cros_settings.h" +#include "chrome/browser/chromeos/settings/cros_settings_names.h" + +namespace chromeos { + +namespace { + +const char* kHandledSettings[] = { + kAccountsPrefAllowGuest, + kAccountsPrefAllowNewUser, + kAccountsPrefShowUserNamesOnSignIn, + kAccountsPrefUsers, + kAccountsPrefEphemeralUsersEnabled, + kDeviceOwner, + kPolicyMissingMitigationMode, + kReleaseChannel, + kReportDeviceVersionInfo, + kReportDeviceActivityTimes, + kReportDeviceBootMode, + kReportDeviceLocation, + kSettingProxyEverywhere, + kSignedDataRoamingEnabled, + kStatsReportingPref, + // Kiosk mode settings. + kIdleLogoutTimeout, + kIdleLogoutWarningDuration, + kScreenSaverExtensionId, + kScreenSaverTimeout +}; + +} // namespace + +StubCrosSettingsProvider::StubCrosSettingsProvider( + const NotifyObserversCallback& notify_cb) + : CrosSettingsProvider(notify_cb) { + SetDefaults(); +} + +StubCrosSettingsProvider::StubCrosSettingsProvider() + : CrosSettingsProvider(CrosSettingsProvider::NotifyObserversCallback()) { + SetDefaults(); +} + +StubCrosSettingsProvider::~StubCrosSettingsProvider() { +} + +const base::Value* StubCrosSettingsProvider::Get( + const std::string& path) const { + DCHECK(HandlesSetting(path)); + const base::Value* value; + if (values_.GetValue(path, &value)) + return value; + return NULL; +} + +CrosSettingsProvider::TrustedStatus + StubCrosSettingsProvider::PrepareTrustedValues(const base::Closure& cb) { + // We don't have a trusted store so all values are available immediately. + return TRUSTED; +} + +bool StubCrosSettingsProvider::HandlesSetting(const std::string& path) const { + const char** end = kHandledSettings + arraysize(kHandledSettings); + return std::find(kHandledSettings, end, path) != end; +} + +void StubCrosSettingsProvider::Reload() { +} + +void StubCrosSettingsProvider::DoSet(const std::string& path, + const base::Value& value) { + values_.SetValue(path, value.DeepCopy()); + NotifyObservers(path); +} + +void StubCrosSettingsProvider::SetDefaults() { + values_.SetBoolean(kAccountsPrefAllowGuest, true); + values_.SetBoolean(kAccountsPrefAllowNewUser, true); + values_.SetBoolean(kAccountsPrefShowUserNamesOnSignIn, true); + // |kDeviceOwner| will be set to the logged-in user by |UserManager|. +} + +} // namespace chromeos diff --git a/chrome/browser/chromeos/settings/stub_cros_settings_provider.h b/chrome/browser/chromeos/settings/stub_cros_settings_provider.h new file mode 100644 index 0000000..13a10cc --- /dev/null +++ b/chrome/browser/chromeos/settings/stub_cros_settings_provider.h @@ -0,0 +1,49 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_CHROMEOS_SETTINGS_STUB_CROS_SETTINGS_PROVIDER_H_ +#define CHROME_BROWSER_CHROMEOS_SETTINGS_STUB_CROS_SETTINGS_PROVIDER_H_ + +#include <string> + +#include "chrome/browser/chromeos/settings/cros_settings_provider.h" +#include "chrome/browser/prefs/pref_value_map.h" + +namespace chromeos { + +class CrosSettings; + +// CrosSettingsProvider implementation that stores settings in memory unsigned. +class StubCrosSettingsProvider : public CrosSettingsProvider { + public: + explicit StubCrosSettingsProvider(const NotifyObserversCallback& notify_cb); + StubCrosSettingsProvider(); + virtual ~StubCrosSettingsProvider(); + + // CrosSettingsProvider implementation. + virtual const base::Value* Get(const std::string& path) const OVERRIDE; + virtual TrustedStatus PrepareTrustedValues( + const base::Closure& callback) OVERRIDE; + virtual bool HandlesSetting(const std::string& path) const OVERRIDE; + virtual void Reload() OVERRIDE; + + private: + // CrosSettingsProvider implementation: + virtual void DoSet(const std::string& path, + const base::Value& value) OVERRIDE; + + // Initializes settings to their defaults. + void SetDefaults(); + + // In-memory settings storage. + PrefValueMap values_; + + CrosSettings* cros_settings_; + + DISALLOW_COPY_AND_ASSIGN(StubCrosSettingsProvider); +}; + +} // namespace chromeos + +#endif // CHROME_BROWSER_CHROMEOS_SETTINGS_STUB_CROS_SETTINGS_PROVIDER_H_ diff --git a/chrome/browser/chromeos/settings/stub_cros_settings_provider_unittest.cc b/chrome/browser/chromeos/settings/stub_cros_settings_provider_unittest.cc new file mode 100644 index 0000000..cd7b00b --- /dev/null +++ b/chrome/browser/chromeos/settings/stub_cros_settings_provider_unittest.cc @@ -0,0 +1,98 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/chromeos/settings/stub_cros_settings_provider.h" + +#include <string> + +#include "base/bind.h" +#include "base/memory/scoped_ptr.h" +#include "base/values.h" +#include "chrome/browser/chromeos/settings/cros_settings_names.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace chromeos { + +namespace { + +void Fail() { + // Should never be called. + FAIL(); +} + +} // namespace + +class StubCrosSettingsProviderTest : public testing::Test { + protected: + StubCrosSettingsProviderTest() + : provider_(new StubCrosSettingsProvider( + base::Bind(&StubCrosSettingsProviderTest::FireObservers, + base::Unretained(this)))) { + } + + virtual ~StubCrosSettingsProviderTest() { + } + + virtual void SetUp() OVERRIDE { + // Reset the observer notification count. + observer_count_.clear(); + } + + void AssertPref(const std::string& prefName, const Value* value) { + const Value* pref = provider_->Get(prefName); + ASSERT_TRUE(pref); + ASSERT_TRUE(pref->Equals(value)); + } + + void ExpectObservers(const std::string& prefName, int count) { + EXPECT_EQ(observer_count_[prefName], count); + } + + void FireObservers(const std::string& path) { + observer_count_[path]++; + } + + scoped_ptr<StubCrosSettingsProvider> provider_; + std::map<std::string, int> observer_count_; +}; + +TEST_F(StubCrosSettingsProviderTest, HandlesSettings) { + // HandlesSettings should return false for unknown settings. + ASSERT_TRUE(provider_->HandlesSetting(kDeviceOwner)); + ASSERT_FALSE(provider_->HandlesSetting("no.such.setting")); +} + +TEST_F(StubCrosSettingsProviderTest, Defaults) { + // Verify default values. + const base::FundamentalValue kTrueValue(true); + AssertPref(kAccountsPrefAllowGuest, &kTrueValue); + AssertPref(kAccountsPrefAllowNewUser, &kTrueValue); + AssertPref(kAccountsPrefShowUserNamesOnSignIn, &kTrueValue); +} + +TEST_F(StubCrosSettingsProviderTest, Set) { + // Setting value and reading it afterwards returns the same value. + base::StringValue owner_value("me@owner"); + provider_->Set(kDeviceOwner, owner_value); + AssertPref(kDeviceOwner, &owner_value); + ExpectObservers(kDeviceOwner, 1); +} + +TEST_F(StubCrosSettingsProviderTest, SetMissing) { + // Setting is missing initially but is added by |Set|. + base::StringValue pref_value("testing"); + ASSERT_FALSE(provider_->Get(kReleaseChannel)); + provider_->Set(kReleaseChannel, pref_value); + AssertPref(kReleaseChannel, &pref_value); + ExpectObservers(kReleaseChannel, 1); +} + +TEST_F(StubCrosSettingsProviderTest, PrepareTrustedValues) { + // Should return immediately without invoking the callback. + CrosSettingsProvider::TrustedStatus trusted = + provider_->PrepareTrustedValues(base::Bind(&Fail)); + EXPECT_EQ(CrosSettingsProvider::TRUSTED, trusted); +} + +} // namespace chromeos diff --git a/chrome/browser/chromeos/settings/system_settings_provider.cc b/chrome/browser/chromeos/settings/system_settings_provider.cc new file mode 100644 index 0000000..5148c74 --- /dev/null +++ b/chrome/browser/chromeos/settings/system_settings_provider.cc @@ -0,0 +1,75 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/chromeos/settings/system_settings_provider.h" + +#include "base/string16.h" +#include "base/time.h" +#include "base/values.h" +#include "chrome/browser/chromeos/login/user_manager.h" +#include "chrome/browser/chromeos/settings/cros_settings.h" +#include "chrome/browser/chromeos/settings/cros_settings_names.h" +#include "grit/generated_resources.h" +#include "ui/base/l10n/l10n_util.h" + +namespace chromeos { + +SystemSettingsProvider::SystemSettingsProvider( + const NotifyObserversCallback& notify_cb) + : CrosSettingsProvider(notify_cb) { + system::TimezoneSettings *timezone_settings = + system::TimezoneSettings::GetInstance(); + timezone_settings->AddObserver(this); + timezone_value_.reset(base::Value::CreateStringValue( + timezone_settings->GetCurrentTimezoneID())); +} + +SystemSettingsProvider::~SystemSettingsProvider() { + system::TimezoneSettings::GetInstance()->RemoveObserver(this); +} + +void SystemSettingsProvider::DoSet(const std::string& path, + const base::Value& in_value) { + // Non-guest users can change the time zone. + if (UserManager::Get()->IsLoggedInAsGuest()) + return; + + if (path == kSystemTimezone) { + string16 timezone_id; + if (!in_value.GetAsString(&timezone_id)) + return; + // This will call TimezoneChanged. + system::TimezoneSettings::GetInstance()->SetTimezoneFromID(timezone_id); + } +} + +const base::Value* SystemSettingsProvider::Get(const std::string& path) const { + if (path == kSystemTimezone) + return timezone_value_.get(); + return NULL; +} + +// The timezone is always trusted. +CrosSettingsProvider::TrustedStatus + SystemSettingsProvider::PrepareTrustedValues(const base::Closure& cb) { + return TRUSTED; +} + +bool SystemSettingsProvider::HandlesSetting(const std::string& path) const { + return path == kSystemTimezone; +} + +void SystemSettingsProvider::Reload() { + // TODO(pastarmovj): We can actually cache the timezone here to make returning + // it faster. +} + +void SystemSettingsProvider::TimezoneChanged(const icu::TimeZone& timezone) { + // Fires system setting change notification. + timezone_value_.reset(base::Value::CreateStringValue( + system::TimezoneSettings::GetTimezoneID(timezone))); + NotifyObservers(kSystemTimezone); +} + +} // namespace chromeos diff --git a/chrome/browser/chromeos/settings/system_settings_provider.h b/chrome/browser/chromeos/settings/system_settings_provider.h new file mode 100644 index 0000000..49ef2aa --- /dev/null +++ b/chrome/browser/chromeos/settings/system_settings_provider.h @@ -0,0 +1,50 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_CHROMEOS_SETTINGS_SYSTEM_SETTINGS_PROVIDER_H_ +#define CHROME_BROWSER_CHROMEOS_SETTINGS_SYSTEM_SETTINGS_PROVIDER_H_ + +#include <string> + +#include "base/callback.h" +#include "base/memory/scoped_ptr.h" +#include "chrome/browser/chromeos/settings/cros_settings_provider.h" +#include "chrome/browser/chromeos/system/timezone_settings.h" +#include "third_party/icu/public/i18n/unicode/timezone.h" + +namespace base { +class Value; +} + +namespace chromeos { + +class SystemSettingsProvider : public CrosSettingsProvider, + public system::TimezoneSettings::Observer { + public: + explicit SystemSettingsProvider(const NotifyObserversCallback& notify_cb); + virtual ~SystemSettingsProvider(); + + // CrosSettingsProvider implementation. + virtual const base::Value* Get(const std::string& path) const OVERRIDE; + virtual TrustedStatus PrepareTrustedValues( + const base::Closure& callback) OVERRIDE; + virtual bool HandlesSetting(const std::string& path) const OVERRIDE; + virtual void Reload() OVERRIDE; + + // TimezoneSettings::Observer implementation. + virtual void TimezoneChanged(const icu::TimeZone& timezone) OVERRIDE; + + private: + // CrosSettingsProvider implementation. + virtual void DoSet(const std::string& path, + const base::Value& in_value) OVERRIDE; + + scoped_ptr<base::Value> timezone_value_; + + DISALLOW_COPY_AND_ASSIGN(SystemSettingsProvider); +}; + +} // namespace chromeos + +#endif // CHROME_BROWSER_CHROMEOS_SETTINGS_SYSTEM_SETTINGS_PROVIDER_H_ |