diff options
author | bartfab@chromium.org <bartfab@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-10-21 20:10:21 +0000 |
---|---|---|
committer | bartfab@chromium.org <bartfab@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-10-21 20:10:21 +0000 |
commit | 535380a3d4c99727bd0cd197ad9d9525c2f3c983 (patch) | |
tree | 3362bb78a3afde95fc28d59230b7eef2cd9b4ee5 /chrome/browser/chromeos | |
parent | c124bdbd7e9d0420046984828cc121a209d44da5 (diff) | |
download | chromium_src-535380a3d4c99727bd0cd197ad9d9525c2f3c983.zip chromium_src-535380a3d4c99727bd0cd197ad9d9525c2f3c983.tar.gz chromium_src-535380a3d4c99727bd0cd197ad9d9525c2f3c983.tar.bz2 |
Cache force-installed apps/extensions in device-local accounts
This CL adds the DeviceLocalAccountExternalPolicyLoader, a replacement for
the ExternalPolicyLoader that caches force-installed apps/extensions,
allowing them to be installed offline. The caches for individual accounts
are managed by the DeviceLocalAccountPolicyService which makes sure to
remove obsolete cache directories.
BUG=287802
TEST=Full coverage with new browser and unit tests
Review URL: https://codereview.chromium.org/27548004
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@229896 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/chromeos')
11 files changed, 1607 insertions, 366 deletions
diff --git a/chrome/browser/chromeos/extensions/device_local_account_external_policy_loader.cc b/chrome/browser/chromeos/extensions/device_local_account_external_policy_loader.cc new file mode 100644 index 0000000..affd4d2 --- /dev/null +++ b/chrome/browser/chromeos/extensions/device_local_account_external_policy_loader.cc @@ -0,0 +1,114 @@ +// Copyright 2013 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/extensions/device_local_account_external_policy_loader.h" + +#include "base/callback.h" +#include "base/logging.h" +#include "base/prefs/pref_value_map.h" +#include "base/values.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/policy/configuration_policy_handler.h" +#include "chrome/browser/policy/policy_map.h" +#include "net/url_request/url_request_context_getter.h" + +namespace chromeos { + +namespace { + +// A dummy pref name under which policy::ExtensionInstallForcelistPolicyHandler +// can store the parsed list of force-installed extensions. +const char kExtensionInstallForcelist[] = "forcelist"; + +} // namespace + +DeviceLocalAccountExternalPolicyLoader:: + DeviceLocalAccountExternalPolicyLoader(policy::CloudPolicyStore* store, + const base::FilePath& cache_dir) + : store_(store), + cache_dir_(cache_dir) { +} + +bool DeviceLocalAccountExternalPolicyLoader::IsCacheRunning() const { + return external_cache_; +} + +void DeviceLocalAccountExternalPolicyLoader::StartCache( + const scoped_refptr<base::SequencedTaskRunner>& cache_task_runner) { + DCHECK(!external_cache_); + store_->AddObserver(this); + external_cache_.reset(new ExternalCache( + cache_dir_, + g_browser_process->system_request_context(), + cache_task_runner, + this, + true /* always_check_updates */, + false /* wait_for_cache_initialization */)); + + if (store_->is_initialized()) + UpdateExtensionListFromStore(); +} + +void DeviceLocalAccountExternalPolicyLoader::StopCache( + const base::Closure& callback) { + if (external_cache_) { + external_cache_->Shutdown(callback); + external_cache_.reset(); + store_->RemoveObserver(this); + } + + base::DictionaryValue empty_prefs; + OnExtensionListsUpdated(&empty_prefs); +} + +void DeviceLocalAccountExternalPolicyLoader::StartLoading() { + if (prefs_) + LoadFinished(); +} + +void DeviceLocalAccountExternalPolicyLoader::OnStoreLoaded( + policy::CloudPolicyStore* store) { + DCHECK(external_cache_); + DCHECK_EQ(store_, store); + UpdateExtensionListFromStore(); +} + +void DeviceLocalAccountExternalPolicyLoader::OnStoreError( + policy::CloudPolicyStore* store) { + DCHECK(external_cache_); + DCHECK_EQ(store_, store); +} + +void DeviceLocalAccountExternalPolicyLoader::OnExtensionListsUpdated( + const base::DictionaryValue* prefs) { + DCHECK(external_cache_ || prefs->empty()); + prefs_.reset(prefs->DeepCopy()); + LoadFinished(); +} + +DeviceLocalAccountExternalPolicyLoader:: + ~DeviceLocalAccountExternalPolicyLoader() { + DCHECK(!external_cache_); +} + +void DeviceLocalAccountExternalPolicyLoader::UpdateExtensionListFromStore() { + scoped_ptr<base::DictionaryValue> prefs(new base::DictionaryValue); + const policy::PolicyMap& policy_map = store_->policy_map(); + policy::ExtensionInstallForcelistPolicyHandler + policy_handler(kExtensionInstallForcelist); + if (policy_handler.CheckPolicySettings(policy_map, NULL)) { + PrefValueMap pref_value_map; + policy_handler.ApplyPolicySettings(policy_map, &pref_value_map); + const base::Value* value = NULL; + const base::DictionaryValue* dict = NULL; + if (pref_value_map.GetValue(kExtensionInstallForcelist, &value) && + value->GetAsDictionary(&dict)) { + prefs.reset(dict->DeepCopy()); + } + } + + external_cache_->UpdateExtensionsList(prefs.Pass()); +} + +} // namespace chromeos diff --git a/chrome/browser/chromeos/extensions/device_local_account_external_policy_loader.h b/chrome/browser/chromeos/extensions/device_local_account_external_policy_loader.h new file mode 100644 index 0000000..c9abf5d --- /dev/null +++ b/chrome/browser/chromeos/extensions/device_local_account_external_policy_loader.h @@ -0,0 +1,78 @@ +// Copyright 2013 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_EXTENSIONS_DEVICE_LOCAL_ACCOUNT_EXTERNAL_POLICY_LOADER_H_ +#define CHROME_BROWSER_CHROMEOS_EXTENSIONS_DEVICE_LOCAL_ACCOUNT_EXTERNAL_POLICY_LOADER_H_ + +#include "base/basictypes.h" +#include "base/callback_forward.h" +#include "base/compiler_specific.h" +#include "base/files/file_path.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/sequenced_task_runner.h" +#include "chrome/browser/chromeos/extensions/external_cache.h" +#include "chrome/browser/extensions/external_loader.h" +#include "chrome/browser/policy/cloud/cloud_policy_store.h" + +namespace chromeos { + +// A specialization of the ExternalLoader that serves external extensions from +// the enterprise policy force-install list. This class is used for device-local +// accounts in place of the ExternalPolicyLoader. The difference is that while +// the ExternalPolicyLoader requires extensions to be downloaded on-the-fly, +// this class caches them, allowing for offline installation. +class DeviceLocalAccountExternalPolicyLoader + : public extensions::ExternalLoader, + public policy::CloudPolicyStore::Observer, + public ExternalCache::Delegate { + public: + // The list of force-installed extensions will be read from |store| and the + // extensions will be cached in the |cache_dir_|. + DeviceLocalAccountExternalPolicyLoader(policy::CloudPolicyStore* store, + const base::FilePath& cache_dir); + + // While running, the cache requires exclusive write access to the + // |cache_dir_|. + bool IsCacheRunning() const; + + // Start the cache. This method must only be invoked when there are no pending + // write operations to |cache_dir_| on any thread and none will be initiated + // while the cache is running. + void StartCache( + const scoped_refptr<base::SequencedTaskRunner>& cache_task_runner); + + // Stop the cache. The |callback| will be invoked when the cache has shut down + // completely and write access to the |cache_dir_| is permitted again. + void StopCache(const base::Closure& callback); + + // extensions::ExternalLoader: + virtual void StartLoading() OVERRIDE; + + // policy::CloudPolicyStore::Observer: + virtual void OnStoreLoaded(policy::CloudPolicyStore* store) OVERRIDE; + virtual void OnStoreError(policy::CloudPolicyStore* store) OVERRIDE; + + // ExternalCache::Delegate: + virtual void OnExtensionListsUpdated( + const base::DictionaryValue* prefs) OVERRIDE; + + private: + // If the cache was started, it must be stopped before |this| is destroyed. + virtual ~DeviceLocalAccountExternalPolicyLoader(); + + // Pass the current list of force-installed extensions from the |store_| to + // the |external_cache_|. + void UpdateExtensionListFromStore(); + + policy::CloudPolicyStore* store_; + const base::FilePath cache_dir_; + scoped_ptr<ExternalCache> external_cache_; + + DISALLOW_COPY_AND_ASSIGN(DeviceLocalAccountExternalPolicyLoader); +}; + +} // namespace chromeos + +#endif // CHROME_BROWSER_CHROMEOS_EXTENSIONS_DEVICE_LOCAL_ACCOUNT_EXTERNAL_POLICY_LOADER_H_ diff --git a/chrome/browser/chromeos/extensions/device_local_account_external_policy_loader_unittest.cc b/chrome/browser/chromeos/extensions/device_local_account_external_policy_loader_unittest.cc new file mode 100644 index 0000000..27a70cd --- /dev/null +++ b/chrome/browser/chromeos/extensions/device_local_account_external_policy_loader_unittest.cc @@ -0,0 +1,309 @@ +// Copyright 2013 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/extensions/device_local_account_external_policy_loader.h" + +#include <string> + +#include "base/callback.h" +#include "base/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/message_loop/message_loop.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/path_service.h" +#include "base/run_loop.h" +#include "base/strings/stringprintf.h" +#include "base/values.h" +#include "base/version.h" +#include "chrome/browser/chrome_notification_types.h" +#include "chrome/browser/extensions/external_provider_impl.h" +#include "chrome/browser/extensions/external_provider_interface.h" +#include "chrome/browser/extensions/updater/extension_downloader.h" +#include "chrome/browser/policy/cloud/mock_cloud_policy_store.h" +#include "chrome/browser/policy/policy_map.h" +#include "chrome/browser/policy/policy_types.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/common/extensions/extension.h" +#include "chrome/common/extensions/extension_constants.h" +#include "chrome/test/base/testing_browser_process.h" +#include "content/public/browser/notification_service.h" +#include "content/public/browser/notification_source.h" +#include "content/public/browser/render_process_host.h" +#include "content/public/test/test_browser_thread_bundle.h" +#include "content/public/test/test_utils.h" +#include "extensions/common/manifest.h" +#include "net/url_request/test_url_fetcher_factory.h" +#include "net/url_request/url_fetcher_delegate.h" +#include "net/url_request/url_request_context_getter.h" +#include "net/url_request/url_request_test_util.h" +#include "policy/policy_constants.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" + +using ::testing::InvokeWithoutArgs; +using ::testing::Mock; +using ::testing::_; + +namespace chromeos { + +namespace { + +const char kCacheDir[] = "cache"; +const char kExtensionId[] = "ldnnhddmnhbkjipkidpdiheffobcpfmf"; +const char kExtensionUpdateManifest[] = + "extensions/good_v1_update_manifest.xml"; +const char kExtensionCRXSourceDir[] = "extensions"; +const char kExtensionCRXFile[] = "good.crx"; +const char kExtensionCRXVersion[] = "1.0.0.0"; + +class MockExternalPolicyProviderVisitor + : public extensions::ExternalProviderInterface::VisitorInterface { + public: + MockExternalPolicyProviderVisitor(); + virtual ~MockExternalPolicyProviderVisitor(); + + MOCK_METHOD6(OnExternalExtensionFileFound, + bool(const std::string&, + const base::Version*, + const base::FilePath&, + extensions::Manifest::Location, + int, + bool)); + MOCK_METHOD5(OnExternalExtensionUpdateUrlFound, + bool(const std::string&, + const GURL&, + extensions::Manifest::Location, + int, + bool)); + MOCK_METHOD1(OnExternalProviderReady, + void(const extensions::ExternalProviderInterface* provider)); + + private: + DISALLOW_COPY_AND_ASSIGN(MockExternalPolicyProviderVisitor); +}; + +MockExternalPolicyProviderVisitor::MockExternalPolicyProviderVisitor() { +} + +MockExternalPolicyProviderVisitor::~MockExternalPolicyProviderVisitor() { +} + +} // namespace + +class DeviceLocalAccountExternalPolicyLoaderTest : public testing::Test { + protected: + DeviceLocalAccountExternalPolicyLoaderTest(); + virtual ~DeviceLocalAccountExternalPolicyLoaderTest(); + + virtual void SetUp() OVERRIDE; + virtual void TearDown() OVERRIDE; + + void VerifyAndResetVisitorCallExpectations(); + void SetForceInstallListPolicy(); + + content::TestBrowserThreadBundle thread_bundle_; + base::ScopedTempDir temp_dir_; + base::FilePath cache_dir_; + policy::MockCloudPolicyStore store_; + scoped_refptr<net::URLRequestContextGetter> request_context_getter_; + base::FilePath test_dir_; + + scoped_refptr<DeviceLocalAccountExternalPolicyLoader> loader_; + MockExternalPolicyProviderVisitor visitor_; + scoped_ptr<extensions::ExternalProviderImpl> provider_; +}; + +DeviceLocalAccountExternalPolicyLoaderTest:: + DeviceLocalAccountExternalPolicyLoaderTest() + : thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP) { +} + +DeviceLocalAccountExternalPolicyLoaderTest:: + ~DeviceLocalAccountExternalPolicyLoaderTest() { +} + +void DeviceLocalAccountExternalPolicyLoaderTest::SetUp() { + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + cache_dir_ = temp_dir_.path().Append(kCacheDir); + ASSERT_TRUE(file_util::CreateDirectoryAndGetError(cache_dir_, NULL)); + request_context_getter_ = + new net::TestURLRequestContextGetter(base::MessageLoopProxy::current()); + TestingBrowserProcess::GetGlobal()->SetSystemRequestContext( + request_context_getter_.get()); + ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &test_dir_)); + + loader_ = new DeviceLocalAccountExternalPolicyLoader(&store_, cache_dir_); + provider_.reset(new extensions::ExternalProviderImpl( + &visitor_, + loader_, + NULL, + extensions::Manifest::EXTERNAL_POLICY, + extensions::Manifest::EXTERNAL_POLICY_DOWNLOAD, + extensions::Extension::NO_FLAGS)); + + content::RenderProcessHost::SetRunRendererInProcess(true); + + VerifyAndResetVisitorCallExpectations(); +} + +void DeviceLocalAccountExternalPolicyLoaderTest::TearDown() { + content::RenderProcessHost::SetRunRendererInProcess(false); + TestingBrowserProcess::GetGlobal()->SetSystemRequestContext(NULL); +} + +void DeviceLocalAccountExternalPolicyLoaderTest:: + VerifyAndResetVisitorCallExpectations() { + Mock::VerifyAndClearExpectations(&visitor_); + EXPECT_CALL(visitor_, OnExternalExtensionFileFound(_, _, _, _, _, _)) + .Times(0); + EXPECT_CALL(visitor_, OnExternalExtensionUpdateUrlFound(_, _, _, _, _)) + .Times(0); + EXPECT_CALL(visitor_, OnExternalProviderReady(_)) + .Times(0); +} + +void DeviceLocalAccountExternalPolicyLoaderTest::SetForceInstallListPolicy() { + scoped_ptr<base::ListValue> forcelist(new base::ListValue); + forcelist->AppendString("invalid"); + forcelist->AppendString(base::StringPrintf( + "%s;%s", + kExtensionId, + extension_urls::GetWebstoreUpdateUrl().spec().c_str())); + store_.policy_map_.Set(policy::key::kExtensionInstallForcelist, + policy::POLICY_LEVEL_MANDATORY, + policy::POLICY_SCOPE_USER, + forcelist.release(), + NULL); + store_.NotifyStoreLoaded(); +} + +// Verifies that when the cache is not explicitly started, the loader does not +// serve any extensions, even if the force-install list policy is set or a load +// is manually requested. +TEST_F(DeviceLocalAccountExternalPolicyLoaderTest, CacheNotStarted) { + // Set the force-install list policy. + SetForceInstallListPolicy(); + + // Manually request a load. + loader_->StartLoading(); + + EXPECT_FALSE(loader_->IsCacheRunning()); + EXPECT_TRUE(base::MessageLoop::current()->IsIdleForTesting()); +} + +// Verifies that the cache can be started and stopped correctly. +TEST_F(DeviceLocalAccountExternalPolicyLoaderTest, ForceInstallListEmpty) { + // Set an empty force-install list policy. + store_.NotifyStoreLoaded(); + + // Start the cache. Verify that the loader announces an empty extension list. + EXPECT_CALL(visitor_, OnExternalProviderReady(provider_.get())) + .Times(1); + loader_->StartCache(base::MessageLoopProxy::current()); + VerifyAndResetVisitorCallExpectations(); + + // Stop the cache. Verify that the loader announces an empty extension list. + EXPECT_CALL(visitor_, OnExternalProviderReady(provider_.get())) + .Times(1); + base::RunLoop run_loop; + loader_->StopCache(run_loop.QuitClosure()); + VerifyAndResetVisitorCallExpectations(); + + // Spin the loop until the cache shutdown callback is invoked. Verify that at + // that point, no further file I/O tasks are pending. + run_loop.Run(); + EXPECT_TRUE(base::MessageLoop::current()->IsIdleForTesting()); +} + +// Verifies that when a force-install list policy referencing an extension is +// set and the cache is started, the loader downloads, caches and serves the +// extension. +TEST_F(DeviceLocalAccountExternalPolicyLoaderTest, ForceInstallListSet) { + // Set a force-install list policy that contains an invalid entry (which + // should be ignored) and a valid reference to an extension. + SetForceInstallListPolicy(); + + // Start the cache. + loader_->StartCache(base::MessageLoopProxy::current()); + + // Spin the loop, allowing the loader to process the force-install list. + // Verify that the loader announces an empty extension list. + net::TestURLFetcherFactory factory; + EXPECT_CALL(visitor_, OnExternalProviderReady(provider_.get())) + .Times(1); + base::MessageLoop::current()->RunUntilIdle(); + + // Verify that a downloader has started and is attempting to download an + // update manifest. + net::TestURLFetcher* fetcher = factory.GetFetcherByID( + extensions::ExtensionDownloader::kManifestFetcherId); + ASSERT_TRUE(fetcher); + ASSERT_TRUE(fetcher->delegate()); + + // Return a manifest to the downloader. + std::string manifest; + EXPECT_TRUE(base::ReadFileToString(test_dir_.Append(kExtensionUpdateManifest), + &manifest)); + fetcher->set_response_code(200); + fetcher->SetResponseString(manifest); + fetcher->delegate()->OnURLFetchComplete(fetcher); + + // Wait for the manifest to be parsed. + content::WindowedNotificationObserver( + chrome::NOTIFICATION_EXTENSION_UPDATE_FOUND, + content::NotificationService::AllSources()).Wait(); + + // Verify that the downloader is attempting to download a CRX file. + fetcher = factory.GetFetcherByID( + extensions::ExtensionDownloader::kExtensionFetcherId); + ASSERT_TRUE(fetcher); + ASSERT_TRUE(fetcher->delegate()); + + // Create a temporary CRX file and return its path to the downloader. + EXPECT_TRUE(base::CopyFile( + test_dir_.Append(kExtensionCRXSourceDir).Append(kExtensionCRXFile), + temp_dir_.path().Append(kExtensionCRXFile))); + fetcher->set_response_code(200); + fetcher->SetResponseFilePath(temp_dir_.path().Append(kExtensionCRXFile)); + fetcher->delegate()->OnURLFetchComplete(fetcher); + + // Spin the loop. Verify that the loader announces the presence of a new CRX + // file, served from the cache directory. + const base::FilePath cached_crx_path = cache_dir_.Append(base::StringPrintf( + "%s-%s.crx", kExtensionId, kExtensionCRXVersion)); + base::RunLoop cache_run_loop; + EXPECT_CALL(visitor_, OnExternalExtensionFileFound( + kExtensionId, + _, + cached_crx_path, + extensions::Manifest::EXTERNAL_POLICY, + _, + _)); + EXPECT_CALL(visitor_, OnExternalProviderReady(provider_.get())) + .Times(1) + .WillOnce(InvokeWithoutArgs(&cache_run_loop, &base::RunLoop::Quit)); + cache_run_loop.Run(); + VerifyAndResetVisitorCallExpectations(); + + // Verify that the CRX file actually exists in the cache directory and its + // contents matches the file returned to the downloader. + EXPECT_TRUE(base::ContentsEqual( + test_dir_.Append(kExtensionCRXSourceDir).Append(kExtensionCRXFile), + cached_crx_path)); + + // Stop the cache. Verify that the loader announces an empty extension list. + EXPECT_CALL(visitor_, OnExternalProviderReady(provider_.get())) + .Times(1); + base::RunLoop shutdown_run_loop; + loader_->StopCache(shutdown_run_loop.QuitClosure()); + VerifyAndResetVisitorCallExpectations(); + + // Spin the loop until the cache shutdown callback is invoked. Verify that at + // that point, no further file I/O tasks are pending. + shutdown_run_loop.Run(); + EXPECT_TRUE(base::MessageLoop::current()->IsIdleForTesting()); +} + +} // namespace chromeos diff --git a/chrome/browser/chromeos/extensions/external_pref_cache_loader.h b/chrome/browser/chromeos/extensions/external_pref_cache_loader.h index 16a08ff..e448182 100644 --- a/chrome/browser/chromeos/extensions/external_pref_cache_loader.h +++ b/chrome/browser/chromeos/extensions/external_pref_cache_loader.h @@ -9,9 +9,8 @@ namespace chromeos { -// A specialization of the ExternalPrefCacheLoader that caches crx files for -// external extensions with update URL in common place for all users on the -// machine. +// A specialization of the ExternalPrefLoader that caches crx files for external +// extensions with update URL in a common place for all users on the machine. class ExternalPrefCacheLoader : public extensions::ExternalPrefLoader { public: // All instances of ExternalPrefCacheLoader use the same cache so diff --git a/chrome/browser/chromeos/policy/device_local_account.h b/chrome/browser/chromeos/policy/device_local_account.h index ff83461..d10b228 100644 --- a/chrome/browser/chromeos/policy/device_local_account.h +++ b/chrome/browser/chromeos/policy/device_local_account.h @@ -32,6 +32,22 @@ struct DeviceLocalAccount { ~DeviceLocalAccount(); Type type; + // A device-local account has two identifiers: + // * The |account_id| is chosen by the entity that defines the device-local + // account. The only constraints are that the |account_id| be unique and, + // for legacy reasons, it contain an @ symbol. + // * The |user_id| is a synthesized identifier that is guaranteed to be + // unique, contain an @ symbol, not collide with the |user_id| of any other + // user on the device (such as regular users or supervised users) and be + // identifiable as belonging to a device-local account by. + // The |account_id| is primarily used by policy code: If device policy defines + // a device-local account with a certain |account_id|, the user policy for + // that account has to be fetched by referencing the same |account_id|. + // The |user_id| is passed to the chromeos::UserManager where it becomes part + // of the global user list on the device. The |account_id| would not be safe + // to use here as it is a free-form identifier that could conflict with + // another |user_id| on the device and cannot be easily identified as + // belonging to a device-local account. std::string account_id; std::string user_id; std::string kiosk_app_id; diff --git a/chrome/browser/chromeos/policy/device_local_account_browsertest.cc b/chrome/browser/chromeos/policy/device_local_account_browsertest.cc index 12e5622..9934608 100644 --- a/chrome/browser/chromeos/policy/device_local_account_browsertest.cc +++ b/chrome/browser/chromeos/policy/device_local_account_browsertest.cc @@ -12,14 +12,17 @@ #include "base/command_line.h" #include "base/file_util.h" #include "base/files/file_path.h" +#include "base/files/scoped_temp_dir.h" #include "base/json/json_reader.h" #include "base/memory/scoped_ptr.h" #include "base/message_loop/message_loop.h" #include "base/path_service.h" #include "base/run_loop.h" +#include "base/strings/string_number_conversions.h" #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" +#include "base/test/scoped_path_override.h" #include "base/values.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/chrome_notification_types.h" @@ -57,6 +60,7 @@ #include "chrome/common/chrome_paths.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/extensions/extension.h" +#include "chromeos/chromeos_paths.h" #include "chromeos/chromeos_switches.h" #include "chromeos/dbus/cryptohome_client.h" #include "chromeos/dbus/dbus_method_call_status.h" @@ -113,9 +117,9 @@ const char kUpdateManifestFooter[] = "</gupdate>\n"; const char kHostedAppID[] = "kbmnembihfiondgfjekmnmcbddelicoi"; const char kHostedAppCRXPath[] = "extensions/hosted_app.crx"; -const char kHostedAppVersion[] = "0.1"; +const char kHostedAppVersion[] = "1.0.0.0"; const char kGoodExtensionID[] = "ldnnhddmnhbkjipkidpdiheffobcpfmf"; -const char kGoodExtensionPath[] = "extensions/good.crx"; +const char kGoodExtensionCRXPath[] = "extensions/good.crx"; const char kGoodExtensionVersion[] = "1.0"; // Helper that serves extension update manifests to Chrome. @@ -247,6 +251,11 @@ class DeviceLocalAccountTest : public DevicePolicyCrosBrowserTest { PolicyBuilder::kFakeDeviceId); ASSERT_TRUE(test_server_.Start()); + ASSERT_TRUE(extension_cache_root_dir_.CreateUniqueTempDir()); + extension_cache_root_dir_override_.reset(new base::ScopedPathOverride( + chromeos::DIR_DEVICE_LOCAL_ACCOUNT_CACHE, + extension_cache_root_dir_.path())); + DevicePolicyCrosBrowserTest::SetUp(); } @@ -336,9 +345,24 @@ class DeviceLocalAccountTest : public DevicePolicyCrosBrowserTest { EXPECT_EQ(chromeos::User::USER_TYPE_PUBLIC_ACCOUNT, user->GetType()); } + base::FilePath GetCacheDirectoryForAccountID(const std::string& account_id) { + return extension_cache_root_dir_.path() + .Append(base::HexEncode(account_id.c_str(), account_id.size())); + } + + base::FilePath GetCacheCRXFile(const std::string& account_id, + const std::string& id, + const std::string& version) { + return GetCacheDirectoryForAccountID(account_id) + .Append(base::StringPrintf("%s-%s.crx", id.c_str(), version.c_str())); + } + const std::string user_id_1_; const std::string user_id_2_; + base::ScopedTempDir extension_cache_root_dir_; + scoped_ptr<base::ScopedPathOverride> extension_cache_root_dir_override_; + UserPolicyBuilder device_local_account_policy_; LocalPolicyTestServer test_server_; }; @@ -554,7 +578,7 @@ IN_PROC_BROWSER_TEST_F(DeviceLocalAccountTest, FullscreenDisallowed) { EXPECT_FALSE(browser_window->IsFullscreen()); } -IN_PROC_BROWSER_TEST_F(DeviceLocalAccountTest, ExtensionWhitelist) { +IN_PROC_BROWSER_TEST_F(DeviceLocalAccountTest, ExtensionsUncached) { // Make it possible to force-install a hosted app and an extension. ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); TestingUpdateManifestProvider testing_update_manifest_provider( @@ -566,7 +590,7 @@ IN_PROC_BROWSER_TEST_F(DeviceLocalAccountTest, ExtensionWhitelist) { testing_update_manifest_provider.AddUpdate( kGoodExtensionID, kGoodExtensionVersion, - embedded_test_server()->GetURL(std::string("/") + kGoodExtensionPath)); + embedded_test_server()->GetURL(std::string("/") + kGoodExtensionCRXPath)); embedded_test_server()->RegisterRequestHandler( base::Bind(&TestingUpdateManifestProvider::HandleRequest, base::Unretained(&testing_update_manifest_provider))); @@ -625,6 +649,105 @@ IN_PROC_BROWSER_TEST_F(DeviceLocalAccountTest, ExtensionWhitelist) { controller->LoginAsPublicAccount(user_id_1_); // Wait for the hosted app installation to succeed and the extension + // installation to fail (because hosted apps are whitelisted for use in + // device-local accounts and extensions are not). + hosted_app_observer.Wait(); + extension_observer.Wait(); + + // Verify that the hosted app was installed. + Profile* profile = ProfileManager::GetDefaultProfile(); + ASSERT_TRUE(profile); + ExtensionService* extension_service = + extensions::ExtensionSystem::Get(profile)->extension_service(); + EXPECT_TRUE(extension_service->GetExtensionById(kHostedAppID, true)); + + // Verify that the extension was not installed. + EXPECT_FALSE(extension_service->GetExtensionById(kGoodExtensionID, true)); + + // Verify that the app was copied to the account's extension cache. + base::FilePath test_dir; + ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &test_dir)); + EXPECT_TRUE(ContentsEqual( + GetCacheCRXFile(kAccountId1, kHostedAppID, kHostedAppVersion), + test_dir.Append(kHostedAppCRXPath))); + + // Verify that the extension was not copied to the account's extension cache. + EXPECT_FALSE(PathExists(GetCacheCRXFile( + kAccountId1, kGoodExtensionID, kGoodExtensionVersion))); +} + +IN_PROC_BROWSER_TEST_F(DeviceLocalAccountTest, ExtensionsCached) { + ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); + + // Pre-populate the device local account's extension cache with a hosted app + // and an extension. + EXPECT_TRUE(file_util::CreateDirectory( + GetCacheDirectoryForAccountID(kAccountId1))); + base::FilePath test_dir; + ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &test_dir)); + const base::FilePath cached_hosted_app = + GetCacheCRXFile(kAccountId1, kHostedAppID, kHostedAppVersion); + EXPECT_TRUE(CopyFile(test_dir.Append(kHostedAppCRXPath), + cached_hosted_app)); + const base::FilePath cached_extension = + GetCacheCRXFile(kAccountId1, kGoodExtensionID, kGoodExtensionVersion); + EXPECT_TRUE(CopyFile(test_dir.Append(kGoodExtensionCRXPath), + cached_extension)); + + // Specify policy to force-install the hosted app. + em::StringList* forcelist = device_local_account_policy_.payload() + .mutable_extensioninstallforcelist()->mutable_value(); + forcelist->add_entries(base::StringPrintf( + "%s;%s", + kHostedAppID, + embedded_test_server()->GetURL(kRelativeUpdateURL).spec().c_str())); + forcelist->add_entries(base::StringPrintf( + "%s;%s", + kGoodExtensionID, + embedded_test_server()->GetURL(kRelativeUpdateURL).spec().c_str())); + + UploadAndInstallDeviceLocalAccountPolicy(); + AddPublicSessionToDevicePolicy(kAccountId1); + + // This observes the display name becoming available as this indicates + // device-local account policy is fully loaded, which is a prerequisite for + // successful login. + content::WindowedNotificationObserver( + chrome::NOTIFICATION_USER_LIST_CHANGED, + base::Bind(&DisplayNameMatches, user_id_1_, kDisplayName)).Wait(); + + // Wait for the login UI to be ready. + chromeos::LoginDisplayHostImpl* host = + reinterpret_cast<chromeos::LoginDisplayHostImpl*>( + chromeos::LoginDisplayHostImpl::default_host()); + ASSERT_TRUE(host); + chromeos::OobeUI* oobe_ui = host->GetOobeUI(); + ASSERT_TRUE(oobe_ui); + base::RunLoop run_loop; + const bool oobe_ui_ready = oobe_ui->IsJSReady(run_loop.QuitClosure()); + if (!oobe_ui_ready) + run_loop.Run(); + + // Ensure that the browser stays alive, even though no windows are opened + // during session start. + chrome::StartKeepAlive(); + + // Start listening for app/extension installation results. + content::WindowedNotificationObserver hosted_app_observer( + chrome::NOTIFICATION_EXTENSION_INSTALLED, + base::Bind(DoesInstallSuccessReferToId, kHostedAppID)); + content::WindowedNotificationObserver extension_observer( + chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR, + base::Bind(DoesInstallFailureReferToId, kGoodExtensionID)); + + // Start login into the device-local account. + host->StartSignInScreen(); + chromeos::ExistingUserController* controller = + chromeos::ExistingUserController::current_controller(); + ASSERT_TRUE(controller); + controller->LoginAsPublicAccount(user_id_1_); + + // Wait for the hosted app installation to succeed and the extension // installation to fail. hosted_app_observer.Wait(); extension_observer.Wait(); @@ -638,6 +761,12 @@ IN_PROC_BROWSER_TEST_F(DeviceLocalAccountTest, ExtensionWhitelist) { // Verify that the extension was not installed. EXPECT_FALSE(extension_service->GetExtensionById(kGoodExtensionID, true)); + + // Verify that the app is still in the account's extension cache. + EXPECT_TRUE(PathExists(cached_hosted_app)); + + // Verify that the extension was removed from the account's extension cache. + EXPECT_FALSE(PathExists(cached_extension)); } class TermsOfServiceTest : public DeviceLocalAccountTest, diff --git a/chrome/browser/chromeos/policy/device_local_account_policy_provider.cc b/chrome/browser/chromeos/policy/device_local_account_policy_provider.cc index 6b0535e..4381ce4 100644 --- a/chrome/browser/chromeos/policy/device_local_account_policy_provider.cc +++ b/chrome/browser/chromeos/policy/device_local_account_policy_provider.cc @@ -5,6 +5,7 @@ #include "chrome/browser/chromeos/policy/device_local_account_policy_provider.h" #include "base/bind.h" +#include "chrome/browser/policy/cloud/cloud_policy_core.h" #include "chrome/browser/policy/cloud/cloud_policy_service.h" #include "chrome/browser/policy/policy_bundle.h" #include "chrome/browser/policy/policy_service.h" diff --git a/chrome/browser/chromeos/policy/device_local_account_policy_service.cc b/chrome/browser/chromeos/policy/device_local_account_policy_service.cc index 825d39c..3579bec 100644 --- a/chrome/browser/chromeos/policy/device_local_account_policy_service.cc +++ b/chrome/browser/chromeos/policy/device_local_account_policy_service.cc @@ -7,18 +7,24 @@ #include <vector> #include "base/bind.h" +#include "base/file_util.h" +#include "base/files/file_enumerator.h" +#include "base/files/file_path.h" #include "base/logging.h" #include "base/message_loop/message_loop.h" #include "base/message_loop/message_loop_proxy.h" +#include "base/path_service.h" +#include "base/sequenced_task_runner.h" +#include "base/strings/string_number_conversions.h" #include "chrome/browser/chromeos/policy/device_local_account.h" #include "chrome/browser/chromeos/policy/device_local_account_policy_store.h" -#include "chrome/browser/chromeos/settings/cros_settings.h" #include "chrome/browser/chromeos/settings/device_settings_service.h" #include "chrome/browser/policy/cloud/cloud_policy_client.h" #include "chrome/browser/policy/cloud/cloud_policy_constants.h" #include "chrome/browser/policy/cloud/cloud_policy_refresh_scheduler.h" #include "chrome/browser/policy/cloud/device_management_service.h" #include "chrome/browser/policy/proto/cloud/device_management_backend.pb.h" +#include "chromeos/chromeos_paths.h" #include "chromeos/dbus/session_manager_client.h" #include "chromeos/settings/cros_settings_names.h" #include "chromeos/settings/cros_settings_provider.h" @@ -53,23 +59,86 @@ scoped_ptr<CloudPolicyClient> CreateClient( return client.Pass(); } +// Get the subdirectory of the cache directory in which force-installed +// extensions are cached for |account_id|. +std::string GetCacheSubdirectoryForAccountID(const std::string& account_id) { + return base::HexEncode(account_id.c_str(), account_id.size()); +} + +// Cleans up the cache directory by removing subdirectories that are not found +// in |subdirectories_to_keep|. Only caches whose cache directory is found in +// |subdirectories_to_keep| may be running while the clean-up is in progress. +void DeleteOrphanedExtensionCaches( + const std::set<std::string>& subdirectories_to_keep) { + base::FilePath cache_root_dir; + CHECK(PathService::Get(chromeos::DIR_DEVICE_LOCAL_ACCOUNT_CACHE, + &cache_root_dir)); + base::FileEnumerator enumerator(cache_root_dir, + false, + base::FileEnumerator::DIRECTORIES); + for (base::FilePath path = enumerator.Next(); !path.empty(); + path = enumerator.Next()) { + const std::string subdirectory(path.BaseName().MaybeAsASCII()); + if (subdirectories_to_keep.find(subdirectory) == + subdirectories_to_keep.end()) { + base::DeleteFile(path, true); + } + } +} + +// Removes the subdirectory belonging to |account_id_to_delete| from the cache +// directory. No cache belonging to |account_id_to_delete| may be running while +// the removal is in progress. +void DeleteObsoleteExtensionCache(const std::string& account_id_to_delete) { + base::FilePath cache_root_dir; + CHECK(PathService::Get(chromeos::DIR_DEVICE_LOCAL_ACCOUNT_CACHE, + &cache_root_dir)); + const base::FilePath path = cache_root_dir + .Append(GetCacheSubdirectoryForAccountID(account_id_to_delete)); + if (base::DirectoryExists(path)) + base::DeleteFile(path, true); +} + } // namespace DeviceLocalAccountPolicyBroker::DeviceLocalAccountPolicyBroker( - const std::string& user_id, + const DeviceLocalAccount& account, scoped_ptr<DeviceLocalAccountPolicyStore> store, const scoped_refptr<base::SequencedTaskRunner>& task_runner) - : user_id_(user_id), + : account_id_(account.account_id), + user_id_(account.user_id), store_(store.Pass()), core_(PolicyNamespaceKey(dm_protocol::kChromePublicAccountPolicyType, store_->account_id()), store_.get(), - task_runner) {} + task_runner) { + base::FilePath cache_root_dir; + CHECK(PathService::Get(chromeos::DIR_DEVICE_LOCAL_ACCOUNT_CACHE, + &cache_root_dir)); + extension_loader_ = new chromeos::DeviceLocalAccountExternalPolicyLoader( + store_.get(), + cache_root_dir.Append( + GetCacheSubdirectoryForAccountID(account.account_id))); +} -DeviceLocalAccountPolicyBroker::~DeviceLocalAccountPolicyBroker() {} +DeviceLocalAccountPolicyBroker::~DeviceLocalAccountPolicyBroker() { +} + +void DeviceLocalAccountPolicyBroker::Initialize() { + store_->Load(); +} + +void DeviceLocalAccountPolicyBroker::ConnectIfPossible( + chromeos::DeviceSettingsService* device_settings_service, + DeviceManagementService* device_management_service) { + if (core_.client()) + return; + + scoped_ptr<CloudPolicyClient> client(CreateClient(device_settings_service, + device_management_service)); + if (!client) + return; -void DeviceLocalAccountPolicyBroker::Connect( - scoped_ptr<CloudPolicyClient> client) { core_.Connect(client.Pass()); core_.StartRefreshScheduler(); UpdateRefreshDelay(); @@ -98,66 +167,26 @@ std::string DeviceLocalAccountPolicyBroker::GetDisplayName() const { return display_name; } -DeviceLocalAccountPolicyService::PolicyBrokerWrapper::PolicyBrokerWrapper() - : parent(NULL), broker(NULL) {} - -DeviceLocalAccountPolicyService::PolicyBrokerWrapper::~PolicyBrokerWrapper() {} - -DeviceLocalAccountPolicyBroker* - DeviceLocalAccountPolicyService::PolicyBrokerWrapper::GetBroker() { - if (!broker) { - scoped_ptr<DeviceLocalAccountPolicyStore> store( - new DeviceLocalAccountPolicyStore(account_id, - parent->session_manager_client_, - parent->device_settings_service_, - parent->background_task_runner_)); - broker = new DeviceLocalAccountPolicyBroker( - user_id, store.Pass(), base::MessageLoopProxy::current()); - broker->core()->store()->AddObserver(parent); - broker->core()->store()->Load(); - } - return broker; -} - -void DeviceLocalAccountPolicyService::PolicyBrokerWrapper::ConnectIfPossible() { - if (broker && broker->core()->client()) - return; - scoped_ptr<CloudPolicyClient> client(CreateClient( - parent->device_settings_service_, - parent->device_management_service_)); - if (client) - GetBroker()->Connect(client.Pass()); -} - -void DeviceLocalAccountPolicyService::PolicyBrokerWrapper::Disconnect() { - if (broker) - broker->Disconnect(); -} - -void DeviceLocalAccountPolicyService::PolicyBrokerWrapper::DeleteBroker() { - if (!broker) - return; - broker->core()->store()->RemoveObserver(parent); - delete broker; - broker = NULL; -} - DeviceLocalAccountPolicyService::DeviceLocalAccountPolicyService( chromeos::SessionManagerClient* session_manager_client, chromeos::DeviceSettingsService* device_settings_service, chromeos::CrosSettings* cros_settings, - scoped_refptr<base::SequencedTaskRunner> background_task_runner) + scoped_refptr<base::SequencedTaskRunner> store_background_task_runner, + scoped_refptr<base::SequencedTaskRunner> extension_cache_task_runner) : session_manager_client_(session_manager_client), device_settings_service_(device_settings_service), cros_settings_(cros_settings), device_management_service_(NULL), - background_task_runner_(background_task_runner), - cros_settings_callback_factory_(this) { - local_accounts_subscription_ = cros_settings_->AddSettingsObserver( - chromeos::kAccountsPrefDeviceLocalAccounts, - base::Bind(&DeviceLocalAccountPolicyService:: - UpdateAccountListIfNonePending, - base::Unretained(this))); + waiting_for_cros_settings_(false), + orphan_cache_deletion_state_(NOT_STARTED), + store_background_task_runner_(store_background_task_runner), + extension_cache_task_runner_(extension_cache_task_runner), + local_accounts_subscription_(cros_settings_->AddSettingsObserver( + chromeos::kAccountsPrefDeviceLocalAccounts, + base::Bind(&DeviceLocalAccountPolicyService:: + UpdateAccountListIfNonePending, + base::Unretained(this)))), + weak_factory_(this) { UpdateAccountList(); } @@ -173,7 +202,8 @@ void DeviceLocalAccountPolicyService::Connect( // Connect the brokers. for (PolicyBrokerMap::iterator it(policy_brokers_.begin()); it != policy_brokers_.end(); ++it) { - it->second.ConnectIfPossible(); + it->second->ConnectIfPossible(device_settings_service_, + device_management_service_); } } @@ -184,7 +214,7 @@ void DeviceLocalAccountPolicyService::Disconnect() { // Disconnect the brokers. for (PolicyBrokerMap::iterator it(policy_brokers_.begin()); it != policy_brokers_.end(); ++it) { - it->second.Disconnect(); + it->second->Disconnect(); } } @@ -195,7 +225,7 @@ DeviceLocalAccountPolicyBroker* if (entry == policy_brokers_.end()) return NULL; - return entry->second.GetBroker(); + return entry->second; } bool DeviceLocalAccountPolicyService::IsPolicyAvailableForUser( @@ -229,52 +259,214 @@ void DeviceLocalAccountPolicyService::OnStoreError(CloudPolicyStore* store) { FOR_EACH_OBSERVER(Observer, observers_, OnPolicyUpdated(broker->user_id())); } +bool DeviceLocalAccountPolicyService::IsExtensionCacheDirectoryBusy( + const std::string& account_id) { + return busy_extension_cache_directories_.find(account_id) != + busy_extension_cache_directories_.end(); +} + +void DeviceLocalAccountPolicyService::StartExtensionCachesIfPossible() { + for (PolicyBrokerMap::iterator it = policy_brokers_.begin(); + it != policy_brokers_.end(); ++it) { + if (!it->second->extension_loader()->IsCacheRunning() && + !IsExtensionCacheDirectoryBusy(it->second->account_id())) { + it->second->extension_loader()->StartCache(extension_cache_task_runner_); + } + } +} + +bool DeviceLocalAccountPolicyService::StartExtensionCacheForAccountIfPresent( + const std::string& account_id) { + for (PolicyBrokerMap::iterator it = policy_brokers_.begin(); + it != policy_brokers_.end(); ++it) { + if (it->second->account_id() == account_id) { + DCHECK(!it->second->extension_loader()->IsCacheRunning()); + it->second->extension_loader()->StartCache(extension_cache_task_runner_); + return true; + } + } + return false; +} + +void DeviceLocalAccountPolicyService::OnOrphanedExtensionCachesDeleted() { + DCHECK_EQ(IN_PROGRESS, orphan_cache_deletion_state_); + + orphan_cache_deletion_state_ = DONE; + StartExtensionCachesIfPossible(); +} + +void DeviceLocalAccountPolicyService::OnObsoleteExtensionCacheShutdown( + const std::string& account_id) { + DCHECK_NE(NOT_STARTED, orphan_cache_deletion_state_); + DCHECK(IsExtensionCacheDirectoryBusy(account_id)); + + // The account with |account_id| was deleted and the broker for it has shut + // down completely. + + if (StartExtensionCacheForAccountIfPresent(account_id)) { + // If another account with the same ID was created in the meantime, its + // extension cache is started, reusing the cache directory. The directory no + // longer needs to be marked as busy in this case. + busy_extension_cache_directories_.erase(account_id); + return; + } + + // If no account with |account_id| exists anymore, the cache directory should + // be removed. The directory must stay marked as busy while the removal is in + // progress. + extension_cache_task_runner_->PostTaskAndReply( + FROM_HERE, + base::Bind(&DeleteObsoleteExtensionCache, account_id), + base::Bind(&DeviceLocalAccountPolicyService:: + OnObsoleteExtensionCacheDeleted, + weak_factory_.GetWeakPtr(), + account_id)); +} + +void DeviceLocalAccountPolicyService::OnObsoleteExtensionCacheDeleted( + const std::string& account_id) { + DCHECK_EQ(DONE, orphan_cache_deletion_state_); + DCHECK(IsExtensionCacheDirectoryBusy(account_id)); + + // The cache directory for |account_id| has been deleted. The directory no + // longer needs to be marked as busy. + busy_extension_cache_directories_.erase(account_id); + + // If another account with the same ID was created in the meantime, start its + // extension cache, creating a new cache directory. + StartExtensionCacheForAccountIfPresent(account_id); +} + void DeviceLocalAccountPolicyService::UpdateAccountListIfNonePending() { // Avoid unnecessary calls to UpdateAccountList(): If an earlier call is still // pending (because the |cros_settings_| are not trusted yet), the updated // account list will be processed by that call when it eventually runs. - if (!cros_settings_callback_factory_.HasWeakPtrs()) + if (!waiting_for_cros_settings_) UpdateAccountList(); } void DeviceLocalAccountPolicyService::UpdateAccountList() { - if (chromeos::CrosSettingsProvider::TRUSTED != - cros_settings_->PrepareTrustedValues( - base::Bind(&DeviceLocalAccountPolicyService::UpdateAccountList, - cros_settings_callback_factory_.GetWeakPtr()))) { - return; + chromeos::CrosSettingsProvider::TrustedStatus status = + cros_settings_->PrepareTrustedValues( + base::Bind(&DeviceLocalAccountPolicyService::UpdateAccountList, + weak_factory_.GetWeakPtr())); + switch (status) { + case chromeos::CrosSettingsProvider::TRUSTED: + waiting_for_cros_settings_ = false; + break; + case chromeos::CrosSettingsProvider::TEMPORARILY_UNTRUSTED: + waiting_for_cros_settings_ = true; + return; + case chromeos::CrosSettingsProvider::PERMANENTLY_UNTRUSTED: + waiting_for_cros_settings_ = false; + return; } // Update |policy_brokers_|, keeping existing entries. PolicyBrokerMap old_policy_brokers; policy_brokers_.swap(old_policy_brokers); + std::set<std::string> subdirectories_to_keep; const std::vector<DeviceLocalAccount> device_local_accounts = GetDeviceLocalAccounts(cros_settings_); for (std::vector<DeviceLocalAccount>::const_iterator it = device_local_accounts.begin(); it != device_local_accounts.end(); ++it) { - PolicyBrokerWrapper& wrapper = policy_brokers_[it->user_id]; - wrapper.user_id = it->user_id; - wrapper.account_id = it->account_id; - wrapper.parent = this; - - // Reuse the existing broker if present. - PolicyBrokerWrapper& existing_wrapper = old_policy_brokers[it->user_id]; - wrapper.broker = existing_wrapper.broker; - existing_wrapper.broker = NULL; + PolicyBrokerMap::iterator broker_it = old_policy_brokers.find(it->user_id); + + scoped_ptr<DeviceLocalAccountPolicyBroker> broker; + bool broker_initialized = false; + if (broker_it != old_policy_brokers.end()) { + // Reuse the existing broker if present. + broker.reset(broker_it->second); + old_policy_brokers.erase(broker_it); + broker_initialized = true; + } else { + scoped_ptr<DeviceLocalAccountPolicyStore> store( + new DeviceLocalAccountPolicyStore(it->account_id, + session_manager_client_, + device_settings_service_, + store_background_task_runner_)); + store->AddObserver(this); + broker.reset(new DeviceLocalAccountPolicyBroker( + *it, + store.Pass(), + base::MessageLoopProxy::current())); + } // Fire up the cloud connection for fetching policy for the account from // the cloud if this is an enterprise-managed device. - wrapper.ConnectIfPossible(); + broker->ConnectIfPossible(device_settings_service_, + device_management_service_); + + policy_brokers_[it->user_id] = broker.release(); + if (!broker_initialized) { + // The broker must be initialized after it has been added to + // |policy_brokers_|. + policy_brokers_[it->user_id]->Initialize(); + } + + if (orphan_cache_deletion_state_ == NOT_STARTED) { + subdirectories_to_keep.insert( + GetCacheSubdirectoryForAccountID(it->account_id)); + } + } + + std::set<std::string> obsolete_account_ids; + for (PolicyBrokerMap::const_iterator it = old_policy_brokers.begin(); + it != old_policy_brokers.end(); ++it) { + obsolete_account_ids.insert(it->second->account_id()); + } + + if (orphan_cache_deletion_state_ == NOT_STARTED) { + DCHECK(old_policy_brokers.empty()); + DCHECK(busy_extension_cache_directories_.empty()); + + // If this method is running for the first time, no extension caches have + // been started yet. Take this opportunity to do a clean-up by removing + // orphaned cache directories not found in |subdirectories_to_keep| from the + // cache directory. + orphan_cache_deletion_state_ = IN_PROGRESS; + extension_cache_task_runner_->PostTaskAndReply( + FROM_HERE, + base::Bind(&DeleteOrphanedExtensionCaches, subdirectories_to_keep), + base::Bind(&DeviceLocalAccountPolicyService:: + OnOrphanedExtensionCachesDeleted, + weak_factory_.GetWeakPtr())); + + // Start the extension caches for all brokers. These belong to accounts in + // |account_ids| and are not affected by the clean-up. + StartExtensionCachesIfPossible(); + } else { + // If this method has run before, obsolete brokers may exist. Shut down + // their extension caches and delete the brokers. + DeleteBrokers(&old_policy_brokers); + + if (orphan_cache_deletion_state_ == DONE) { + // If the initial clean-up of orphaned cache directories has been + // complete, start any extension caches that are not running yet but can + // be started now because their cache directories are not busy. + StartExtensionCachesIfPossible(); + } } - DeleteBrokers(&old_policy_brokers); FOR_EACH_OBSERVER(Observer, observers_, OnDeviceLocalAccountsChanged()); } void DeviceLocalAccountPolicyService::DeleteBrokers(PolicyBrokerMap* map) { - for (PolicyBrokerMap::iterator it = map->begin(); it != map->end(); ++it) - it->second.DeleteBroker(); + for (PolicyBrokerMap::iterator it = map->begin(); it != map->end(); ++it) { + it->second->core()->store()->RemoveObserver(this); + scoped_refptr<chromeos::DeviceLocalAccountExternalPolicyLoader> + extension_loader = it->second->extension_loader(); + if (extension_loader->IsCacheRunning()) { + DCHECK(!IsExtensionCacheDirectoryBusy(it->second->account_id())); + busy_extension_cache_directories_.insert(it->second->account_id()); + extension_loader->StopCache(base::Bind( + &DeviceLocalAccountPolicyService::OnObsoleteExtensionCacheShutdown, + weak_factory_.GetWeakPtr(), + it->second->account_id())); + } + delete it->second; + } map->clear(); } @@ -283,8 +475,8 @@ DeviceLocalAccountPolicyBroker* CloudPolicyStore* store) { for (PolicyBrokerMap::iterator it(policy_brokers_.begin()); it != policy_brokers_.end(); ++it) { - if (it->second.broker && it->second.broker->core()->store() == store) - return it->second.broker; + if (it->second->core()->store() == store) + return it->second; } return NULL; } diff --git a/chrome/browser/chromeos/policy/device_local_account_policy_service.h b/chrome/browser/chromeos/policy/device_local_account_policy_service.h index 73b367e..268f0a7 100644 --- a/chrome/browser/chromeos/policy/device_local_account_policy_service.h +++ b/chrome/browser/chromeos/policy/device_local_account_policy_service.h @@ -6,6 +6,7 @@ #define CHROME_BROWSER_CHROMEOS_POLICY_DEVICE_LOCAL_ACCOUNT_POLICY_SERVICE_H_ #include <map> +#include <set> #include <string> #include "base/basictypes.h" @@ -14,6 +15,7 @@ #include "base/memory/scoped_ptr.h" #include "base/memory/weak_ptr.h" #include "base/observer_list.h" +#include "chrome/browser/chromeos/extensions/device_local_account_external_policy_loader.h" #include "chrome/browser/chromeos/settings/cros_settings.h" #include "chrome/browser/policy/cloud/cloud_policy_core.h" #include "chrome/browser/policy/cloud/cloud_policy_store.h" @@ -23,14 +25,13 @@ class SequencedTaskRunner; } namespace chromeos { -class CrosSettings; class DeviceSettingsService; class SessionManagerClient; } namespace policy { -class CloudPolicyClient; +struct DeviceLocalAccount; class DeviceLocalAccountPolicyStore; class DeviceManagementService; @@ -39,19 +40,31 @@ class DeviceManagementService; class DeviceLocalAccountPolicyBroker { public: // |task_runner| is the runner for policy refresh tasks. - explicit DeviceLocalAccountPolicyBroker( - const std::string& user_id, + DeviceLocalAccountPolicyBroker( + const DeviceLocalAccount& account, scoped_ptr<DeviceLocalAccountPolicyStore> store, const scoped_refptr<base::SequencedTaskRunner>& task_runner); ~DeviceLocalAccountPolicyBroker(); + // Initialize the broker, loading its |store_|. + void Initialize(); + + // For the difference between |account_id| and |user_id|, see the + // documentation of DeviceLocalAccount. + const std::string& account_id() const { return account_id_; } const std::string& user_id() const { return user_id_; } + scoped_refptr<chromeos::DeviceLocalAccountExternalPolicyLoader> + extension_loader() const { return extension_loader_; } + CloudPolicyCore* core() { return &core_; } const CloudPolicyCore* core() const { return &core_; } - // Establish a cloud connection for the service. - void Connect(scoped_ptr<CloudPolicyClient> client); + // Fire up the cloud connection for fetching policy for the account from the + // cloud if this is an enterprise-managed device. + void ConnectIfPossible( + chromeos::DeviceSettingsService* device_settings_service, + DeviceManagementService* device_management_service); // Destroy the cloud connection, stopping policy refreshes. void Disconnect(); @@ -64,8 +77,11 @@ class DeviceLocalAccountPolicyBroker { std::string GetDisplayName() const; private: + const std::string account_id_; const std::string user_id_; - scoped_ptr<DeviceLocalAccountPolicyStore> store_; + const scoped_ptr<DeviceLocalAccountPolicyStore> store_; + scoped_refptr<chromeos::DeviceLocalAccountExternalPolicyLoader> + extension_loader_; CloudPolicyCore core_; DISALLOW_COPY_AND_ASSIGN(DeviceLocalAccountPolicyBroker); @@ -93,7 +109,8 @@ class DeviceLocalAccountPolicyService : public CloudPolicyStore::Observer { chromeos::SessionManagerClient* session_manager_client, chromeos::DeviceSettingsService* device_settings_service, chromeos::CrosSettings* cros_settings, - scoped_refptr<base::SequencedTaskRunner> background_task_runner); + scoped_refptr<base::SequencedTaskRunner> store_background_task_runner, + scoped_refptr<base::SequencedTaskRunner> extension_cache_task_runner); virtual ~DeviceLocalAccountPolicyService(); // Initializes the cloud policy service connection. @@ -118,31 +135,32 @@ class DeviceLocalAccountPolicyService : public CloudPolicyStore::Observer { virtual void OnStoreError(CloudPolicyStore* store) OVERRIDE; private: - struct PolicyBrokerWrapper { - PolicyBrokerWrapper(); - ~PolicyBrokerWrapper(); + typedef std::map<std::string, DeviceLocalAccountPolicyBroker*> + PolicyBrokerMap; - // Return the |broker|, creating it first if necessary. - DeviceLocalAccountPolicyBroker* GetBroker(); + // Returns |true| if the directory in which force-installed extensions are + // cached for |account_id| is busy, either because a broker that was using + // this directory has not shut down completely yet or because the directory is + // being deleted. + bool IsExtensionCacheDirectoryBusy(const std::string& account_id); - // Fire up the cloud connection for fetching policy for the account from the - // cloud if this is an enterprise-managed device. - void ConnectIfPossible(); + // Starts any extension caches that are not running yet but can be started now + // because their cache directories are no longer busy. + void StartExtensionCachesIfPossible(); - // Destroy the cloud connection. - void Disconnect(); + // Checks whether a broker exists for |account_id|. If so, starts the broker's + // extension cache and returns |true|. Otherwise, returns |false|. + bool StartExtensionCacheForAccountIfPresent(const std::string& account_id); - // Delete the broker. - void DeleteBroker(); + // Called back when any extension caches belonging to device-local accounts + // that no longer exist have been removed at start-up. + void OnOrphanedExtensionCachesDeleted(); - std::string user_id; - std::string account_id; - DeviceLocalAccountPolicyService* parent; - DeviceLocalAccountPolicyBroker* broker; - scoped_refptr<base::SequencedTaskRunner> background_task_runner; - }; + // Called back when the extension cache for |account_id| has been shut down. + void OnObsoleteExtensionCacheShutdown(const std::string& account_id); - typedef std::map<std::string, PolicyBrokerWrapper> PolicyBrokerMap; + // Called back when the extension cache for |account_id| has been removed. + void OnObsoleteExtensionCacheDeleted(const std::string& account_id); // Re-queries the list of defined device-local accounts from device settings // and updates |policy_brokers_| to match that list. @@ -166,16 +184,33 @@ class DeviceLocalAccountPolicyService : public CloudPolicyStore::Observer { // The device-local account policy brokers, keyed by user ID. PolicyBrokerMap policy_brokers_; + // Whether a call to UpdateAccountList() is pending because |cros_settings_| + // are not trusted yet. + bool waiting_for_cros_settings_; + + // Orphaned extension caches are removed at startup. This tracks the status of + // that process. + enum OrphanCacheDeletionState { + NOT_STARTED, + IN_PROGRESS, + DONE, + }; + OrphanCacheDeletionState orphan_cache_deletion_state_; + + // Account IDs whose extension cache directories are busy, either because a + // broker for the account has not shut down completely yet or because the + // directory is being deleted. + std::set<std::string> busy_extension_cache_directories_; + + const scoped_refptr<base::SequencedTaskRunner> store_background_task_runner_; + const scoped_refptr<base::SequencedTaskRunner> extension_cache_task_runner_; + ObserverList<Observer, true> observers_; - scoped_ptr<chromeos::CrosSettings::ObserverSubscription> + const scoped_ptr<chromeos::CrosSettings::ObserverSubscription> local_accounts_subscription_; - scoped_refptr<base::SequencedTaskRunner> background_task_runner_; - - // Weak pointer factory for cros_settings_->PrepareTrustedValues() callbacks. - base::WeakPtrFactory<DeviceLocalAccountPolicyService> - cros_settings_callback_factory_; + base::WeakPtrFactory<DeviceLocalAccountPolicyService> weak_factory_; DISALLOW_COPY_AND_ASSIGN(DeviceLocalAccountPolicyService); }; diff --git a/chrome/browser/chromeos/policy/device_local_account_policy_service_unittest.cc b/chrome/browser/chromeos/policy/device_local_account_policy_service_unittest.cc index 8191a7a..0e640ba 100644 --- a/chrome/browser/chromeos/policy/device_local_account_policy_service_unittest.cc +++ b/chrome/browser/chromeos/policy/device_local_account_policy_service_unittest.cc @@ -7,6 +7,17 @@ #include "base/bind.h" #include "base/bind_helpers.h" #include "base/callback.h" +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/files/scoped_temp_dir.h" +#include "base/message_loop/message_loop.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/path_service.h" +#include "base/run_loop.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/stringprintf.h" +#include "base/test/scoped_path_override.h" +#include "base/test/test_simple_task_runner.h" #include "chrome/browser/chromeos/policy/device_local_account.h" #include "chrome/browser/chromeos/policy/device_local_account_policy_provider.h" #include "chrome/browser/chromeos/settings/cros_settings.h" @@ -20,8 +31,11 @@ #include "chrome/browser/policy/external_data_fetcher.h" #include "chrome/browser/policy/mock_configuration_policy_provider.h" #include "chrome/browser/policy/proto/chromeos/chrome_device_policy.pb.h" +#include "chrome/common/chrome_paths.h" +#include "chromeos/chromeos_paths.h" #include "chromeos/dbus/power_policy_controller.h" #include "policy/policy_constants.h" +#include "policy/proto/cloud_policy.pb.h" #include "testing/gtest/include/gtest/gtest.h" using testing::AnyNumber; @@ -34,6 +48,19 @@ namespace em = enterprise_management; namespace policy { +namespace { + +const char kAccount1[] = "account1@localhost"; +const char kAccount2[] = "account2@localhost"; +const char kAccount3[] = "account3@localhost"; + +const char kExtensionID[] = "kbmnembihfiondgfjekmnmcbddelicoi"; +const char kExtensionVersion[] = "1.0.0.0"; +const char kExtensionCRXPath[] = "extensions/hosted_app.crx"; +const char kUpdateURL[] = "https://clients2.google.com/service/update2/crx"; + +} // namespace + class MockDeviceLocalAccountPolicyServiceObserver : public DeviceLocalAccountPolicyService::Observer { public: @@ -41,274 +68,335 @@ class MockDeviceLocalAccountPolicyServiceObserver MOCK_METHOD0(OnDeviceLocalAccountsChanged, void(void)); }; -class DeviceLocalAccountPolicyServiceTest +class DeviceLocalAccountPolicyServiceTestBase : public chromeos::DeviceSettingsTestBase { public: - DeviceLocalAccountPolicyServiceTest() - : public_session_user_id_(GenerateDeviceLocalAccountUserId( - PolicyBuilder::kFakeUsername, - DeviceLocalAccount::TYPE_PUBLIC_SESSION)), - cros_settings_(&device_settings_service_), - service_(&device_settings_test_helper_, - &device_settings_service_, - &cros_settings_, - loop_.message_loop_proxy()) {} - - virtual void SetUp() OVERRIDE { - DeviceSettingsTestBase::SetUp(); - - // Values implicitly enforced for public accounts. - expected_policy_map_.Set(key::kLidCloseAction, - POLICY_LEVEL_MANDATORY, - POLICY_SCOPE_USER, - base::Value::CreateIntegerValue( - chromeos::PowerPolicyController:: - ACTION_STOP_SESSION), - NULL); - expected_policy_map_.Set(key::kShelfAutoHideBehavior, - POLICY_LEVEL_MANDATORY, - POLICY_SCOPE_USER, - Value::CreateStringValue("Never"), - NULL); - expected_policy_map_.Set(key::kShowLogoutButtonInTray, - POLICY_LEVEL_MANDATORY, - POLICY_SCOPE_USER, - Value::CreateBooleanValue(true), - NULL); - expected_policy_map_.Set(key::kFullscreenAllowed, - POLICY_LEVEL_MANDATORY, - POLICY_SCOPE_USER, - Value::CreateBooleanValue(false), - NULL); - - // Explicitly set value. - expected_policy_map_.Set(key::kDisableSpdy, - POLICY_LEVEL_MANDATORY, - POLICY_SCOPE_USER, - Value::CreateBooleanValue(true), - NULL); - - device_local_account_policy_.payload().mutable_disablespdy()->set_value( - true); - device_local_account_policy_.policy_data().set_policy_type( - dm_protocol::kChromePublicAccountPolicyType); - device_local_account_policy_.policy_data().set_settings_entity_id( - PolicyBuilder::kFakeUsername); - device_local_account_policy_.Build(); - - em::DeviceLocalAccountInfoProto* account = - device_policy_.payload().mutable_device_local_accounts()->add_account(); - account->set_account_id(PolicyBuilder::kFakeUsername); - account->set_type( - em::DeviceLocalAccountInfoProto::ACCOUNT_TYPE_PUBLIC_SESSION); - device_policy_.Build(); - - service_.AddObserver(&service_observer_); - } - - virtual void TearDown() OVERRIDE { - service_.RemoveObserver(&service_observer_); - - DeviceSettingsTestBase::TearDown(); - } - - void InstallDevicePolicy() { - EXPECT_CALL(service_observer_, OnDeviceLocalAccountsChanged()); - device_settings_test_helper_.set_policy_blob(device_policy_.GetBlob()); - ReloadDeviceSettings(); - Mock::VerifyAndClearExpectations(&service_observer_); - } + DeviceLocalAccountPolicyServiceTestBase(); - MOCK_METHOD1(OnRefreshDone, void(bool)); + virtual void SetUp() OVERRIDE; + + void CreatePolicyService(); + + void InstallDeviceLocalAccountPolicy(const std::string& account_id); + void AddDeviceLocalAccountToPolicy(const std::string& account_id); + virtual void InstallDevicePolicy(); - const std::string public_session_user_id_; + const std::string account_1_user_id_; + const std::string account_2_user_id_; PolicyMap expected_policy_map_; UserPolicyBuilder device_local_account_policy_; chromeos::CrosSettings cros_settings_; - MockDeviceLocalAccountPolicyServiceObserver service_observer_; + scoped_refptr<base::TestSimpleTaskRunner> extension_cache_task_runner_; MockDeviceManagementService mock_device_management_service_; - DeviceLocalAccountPolicyService service_; + scoped_ptr<DeviceLocalAccountPolicyService> service_; + + private: + DISALLOW_COPY_AND_ASSIGN(DeviceLocalAccountPolicyServiceTestBase); +}; + +class DeviceLocalAccountPolicyServiceTest + : public DeviceLocalAccountPolicyServiceTestBase { + public: + MOCK_METHOD1(OnRefreshDone, void(bool)); + + protected: + DeviceLocalAccountPolicyServiceTest(); + + virtual void SetUp() OVERRIDE; + virtual void TearDown() OVERRIDE; + + void InstallDevicePolicy() OVERRIDE; + + MockDeviceLocalAccountPolicyServiceObserver service_observer_; private: DISALLOW_COPY_AND_ASSIGN(DeviceLocalAccountPolicyServiceTest); }; +DeviceLocalAccountPolicyServiceTestBase:: + DeviceLocalAccountPolicyServiceTestBase() + : account_1_user_id_(GenerateDeviceLocalAccountUserId( + kAccount1, + DeviceLocalAccount::TYPE_PUBLIC_SESSION)), + account_2_user_id_(GenerateDeviceLocalAccountUserId( + kAccount2, + DeviceLocalAccount::TYPE_PUBLIC_SESSION)), + cros_settings_(&device_settings_service_), + extension_cache_task_runner_(new base::TestSimpleTaskRunner) { +} + +void DeviceLocalAccountPolicyServiceTestBase::SetUp() { + chromeos::DeviceSettingsTestBase::SetUp(); + + // Values implicitly enforced for public accounts. + expected_policy_map_.Set(key::kLidCloseAction, + POLICY_LEVEL_MANDATORY, + POLICY_SCOPE_USER, + base::Value::CreateIntegerValue( + chromeos::PowerPolicyController:: + ACTION_STOP_SESSION), + NULL); + expected_policy_map_.Set(key::kShelfAutoHideBehavior, + POLICY_LEVEL_MANDATORY, + POLICY_SCOPE_USER, + Value::CreateStringValue("Never"), + NULL); + expected_policy_map_.Set(key::kShowLogoutButtonInTray, + POLICY_LEVEL_MANDATORY, + POLICY_SCOPE_USER, + Value::CreateBooleanValue(true), + NULL); + expected_policy_map_.Set(key::kFullscreenAllowed, + POLICY_LEVEL_MANDATORY, + POLICY_SCOPE_USER, + Value::CreateBooleanValue(false), + NULL); + + // Explicitly set value. + expected_policy_map_.Set(key::kDisableSpdy, + POLICY_LEVEL_MANDATORY, + POLICY_SCOPE_USER, + Value::CreateBooleanValue(true), + NULL); + + device_local_account_policy_.payload().mutable_disablespdy()->set_value( + true); + device_local_account_policy_.policy_data().set_policy_type( + dm_protocol::kChromePublicAccountPolicyType); +} + +void DeviceLocalAccountPolicyServiceTestBase::CreatePolicyService() { + service_.reset(new DeviceLocalAccountPolicyService( + &device_settings_test_helper_, + &device_settings_service_, + &cros_settings_, + loop_.message_loop_proxy(), + extension_cache_task_runner_)); +} + +void DeviceLocalAccountPolicyServiceTestBase:: + InstallDeviceLocalAccountPolicy(const std::string& account_id) { + device_local_account_policy_.policy_data().set_settings_entity_id(account_id); + device_local_account_policy_.policy_data().set_username(account_id); + device_local_account_policy_.Build(); + device_settings_test_helper_.set_device_local_account_policy_blob( + account_id, device_local_account_policy_.GetBlob()); +} + +void DeviceLocalAccountPolicyServiceTestBase::AddDeviceLocalAccountToPolicy( + const std::string& account_id) { + em::DeviceLocalAccountInfoProto* account = + device_policy_.payload().mutable_device_local_accounts()->add_account(); + account->set_account_id(account_id); + account->set_type( + em::DeviceLocalAccountInfoProto::ACCOUNT_TYPE_PUBLIC_SESSION); +} + +void DeviceLocalAccountPolicyServiceTestBase::InstallDevicePolicy() { + device_policy_.Build(); + device_settings_test_helper_.set_policy_blob(device_policy_.GetBlob()); + ReloadDeviceSettings(); +} + +DeviceLocalAccountPolicyServiceTest::DeviceLocalAccountPolicyServiceTest() { + CreatePolicyService(); +} + +void DeviceLocalAccountPolicyServiceTest::SetUp() { + DeviceLocalAccountPolicyServiceTestBase::SetUp(); + service_->AddObserver(&service_observer_); +} + +void DeviceLocalAccountPolicyServiceTest::TearDown() { + service_->RemoveObserver(&service_observer_); + DeviceLocalAccountPolicyServiceTestBase::TearDown(); +} + +void DeviceLocalAccountPolicyServiceTest::InstallDevicePolicy() { + EXPECT_CALL(service_observer_, OnDeviceLocalAccountsChanged()); + DeviceLocalAccountPolicyServiceTestBase::InstallDevicePolicy(); + Mock::VerifyAndClearExpectations(&service_observer_); +} + TEST_F(DeviceLocalAccountPolicyServiceTest, NoAccounts) { - EXPECT_FALSE(service_.GetBrokerForUser(public_session_user_id_)); + EXPECT_FALSE(service_->GetBrokerForUser(account_1_user_id_)); } TEST_F(DeviceLocalAccountPolicyServiceTest, GetBroker) { + InstallDeviceLocalAccountPolicy(kAccount1); + AddDeviceLocalAccountToPolicy(kAccount1); + EXPECT_CALL(service_observer_, OnPolicyUpdated(account_1_user_id_)); InstallDevicePolicy(); DeviceLocalAccountPolicyBroker* broker = - service_.GetBrokerForUser(public_session_user_id_); + service_->GetBrokerForUser(account_1_user_id_); ASSERT_TRUE(broker); - EXPECT_EQ(public_session_user_id_, broker->user_id()); + EXPECT_EQ(account_1_user_id_, broker->user_id()); ASSERT_TRUE(broker->core()->store()); EXPECT_EQ(CloudPolicyStore::STATUS_OK, broker->core()->store()->status()); EXPECT_FALSE(broker->core()->client()); - EXPECT_TRUE(broker->core()->store()->policy_map().empty()); + EXPECT_FALSE(broker->core()->store()->policy_map().empty()); } TEST_F(DeviceLocalAccountPolicyServiceTest, LoadNoPolicy) { + AddDeviceLocalAccountToPolicy(kAccount1); + EXPECT_CALL(service_observer_, OnPolicyUpdated(account_1_user_id_)); InstallDevicePolicy(); - EXPECT_CALL(service_observer_, OnPolicyUpdated(public_session_user_id_)); DeviceLocalAccountPolicyBroker* broker = - service_.GetBrokerForUser(public_session_user_id_); + service_->GetBrokerForUser(account_1_user_id_); ASSERT_TRUE(broker); - FlushDeviceSettings(); - Mock::VerifyAndClearExpectations(&service_observer_); - + EXPECT_EQ(account_1_user_id_, broker->user_id()); ASSERT_TRUE(broker->core()->store()); EXPECT_EQ(CloudPolicyStore::STATUS_LOAD_ERROR, broker->core()->store()->status()); EXPECT_TRUE(broker->core()->store()->policy_map().empty()); - EXPECT_FALSE(service_.IsPolicyAvailableForUser(public_session_user_id_)); + EXPECT_FALSE(service_->IsPolicyAvailableForUser(account_1_user_id_)); } TEST_F(DeviceLocalAccountPolicyServiceTest, LoadValidationFailure) { device_local_account_policy_.policy_data().set_policy_type( dm_protocol::kChromeUserPolicyType); - device_local_account_policy_.Build(); - device_settings_test_helper_.set_device_local_account_policy_blob( - PolicyBuilder::kFakeUsername, device_local_account_policy_.GetBlob()); + InstallDeviceLocalAccountPolicy(kAccount1); + AddDeviceLocalAccountToPolicy(kAccount1); + EXPECT_CALL(service_observer_, OnPolicyUpdated(account_1_user_id_)); InstallDevicePolicy(); - EXPECT_CALL(service_observer_, OnPolicyUpdated(public_session_user_id_)); DeviceLocalAccountPolicyBroker* broker = - service_.GetBrokerForUser(public_session_user_id_); + service_->GetBrokerForUser(account_1_user_id_); ASSERT_TRUE(broker); - FlushDeviceSettings(); - Mock::VerifyAndClearExpectations(&service_observer_); - + EXPECT_EQ(account_1_user_id_, broker->user_id()); ASSERT_TRUE(broker->core()->store()); EXPECT_EQ(CloudPolicyStore::STATUS_VALIDATION_ERROR, broker->core()->store()->status()); EXPECT_TRUE(broker->core()->store()->policy_map().empty()); - EXPECT_FALSE(service_.IsPolicyAvailableForUser(public_session_user_id_)); + EXPECT_FALSE(service_->IsPolicyAvailableForUser(account_1_user_id_)); } TEST_F(DeviceLocalAccountPolicyServiceTest, LoadPolicy) { - device_settings_test_helper_.set_device_local_account_policy_blob( - PolicyBuilder::kFakeUsername, device_local_account_policy_.GetBlob()); + InstallDeviceLocalAccountPolicy(kAccount1); + AddDeviceLocalAccountToPolicy(kAccount1); + EXPECT_CALL(service_observer_, OnPolicyUpdated(account_1_user_id_)); InstallDevicePolicy(); - EXPECT_CALL(service_observer_, OnPolicyUpdated(public_session_user_id_)); DeviceLocalAccountPolicyBroker* broker = - service_.GetBrokerForUser(public_session_user_id_); + service_->GetBrokerForUser(account_1_user_id_); ASSERT_TRUE(broker); - FlushDeviceSettings(); - Mock::VerifyAndClearExpectations(&service_observer_); - + EXPECT_EQ(account_1_user_id_, broker->user_id()); ASSERT_TRUE(broker->core()->store()); - EXPECT_EQ(CloudPolicyStore::STATUS_OK, - broker->core()->store()->status()); + EXPECT_EQ(CloudPolicyStore::STATUS_OK, broker->core()->store()->status()); ASSERT_TRUE(broker->core()->store()->policy()); EXPECT_EQ(device_local_account_policy_.policy_data().SerializeAsString(), broker->core()->store()->policy()->SerializeAsString()); EXPECT_TRUE(expected_policy_map_.Equals( broker->core()->store()->policy_map())); - EXPECT_TRUE(service_.IsPolicyAvailableForUser(public_session_user_id_)); + EXPECT_TRUE(service_->IsPolicyAvailableForUser(account_1_user_id_)); } TEST_F(DeviceLocalAccountPolicyServiceTest, StoreValidationFailure) { - device_local_account_policy_.policy_data().set_policy_type( - dm_protocol::kChromeUserPolicyType); - device_local_account_policy_.Build(); + AddDeviceLocalAccountToPolicy(kAccount1); + EXPECT_CALL(service_observer_, OnPolicyUpdated(account_1_user_id_)); InstallDevicePolicy(); + Mock::VerifyAndClearExpectations(&service_observer_); - EXPECT_CALL(service_observer_, OnPolicyUpdated(public_session_user_id_)); DeviceLocalAccountPolicyBroker* broker = - service_.GetBrokerForUser(public_session_user_id_); + service_->GetBrokerForUser(account_1_user_id_); ASSERT_TRUE(broker); + EXPECT_EQ(account_1_user_id_, broker->user_id()); ASSERT_TRUE(broker->core()->store()); + + device_local_account_policy_.policy_data().set_policy_type( + dm_protocol::kChromeUserPolicyType); + device_local_account_policy_.Build(); broker->core()->store()->Store(device_local_account_policy_.policy()); + EXPECT_CALL(service_observer_, OnPolicyUpdated(account_1_user_id_)); FlushDeviceSettings(); - Mock::VerifyAndClearExpectations(&service_observer_); - ASSERT_TRUE(broker->core()->store()); EXPECT_EQ(CloudPolicyStore::STATUS_VALIDATION_ERROR, broker->core()->store()->status()); EXPECT_EQ(CloudPolicyValidatorBase::VALIDATION_WRONG_POLICY_TYPE, broker->core()->store()->validation_status()); - EXPECT_FALSE(service_.IsPolicyAvailableForUser(public_session_user_id_)); + EXPECT_FALSE(service_->IsPolicyAvailableForUser(account_1_user_id_)); } TEST_F(DeviceLocalAccountPolicyServiceTest, StorePolicy) { + AddDeviceLocalAccountToPolicy(kAccount1); + EXPECT_CALL(service_observer_, OnPolicyUpdated(account_1_user_id_)); InstallDevicePolicy(); + Mock::VerifyAndClearExpectations(&service_observer_); - EXPECT_CALL(service_observer_, OnPolicyUpdated(public_session_user_id_)); DeviceLocalAccountPolicyBroker* broker = - service_.GetBrokerForUser(public_session_user_id_); + service_->GetBrokerForUser(account_1_user_id_); ASSERT_TRUE(broker); + EXPECT_EQ(account_1_user_id_, broker->user_id()); ASSERT_TRUE(broker->core()->store()); + + device_local_account_policy_.policy_data().set_settings_entity_id(kAccount1); + device_local_account_policy_.policy_data().set_username(kAccount1); + device_local_account_policy_.Build(); broker->core()->store()->Store(device_local_account_policy_.policy()); + EXPECT_CALL(service_observer_, OnPolicyUpdated(account_1_user_id_)); FlushDeviceSettings(); - Mock::VerifyAndClearExpectations(&service_observer_); - EXPECT_EQ(device_local_account_policy_.GetBlob(), - device_settings_test_helper_.device_local_account_policy_blob( - PolicyBuilder::kFakeUsername)); - EXPECT_TRUE(service_.IsPolicyAvailableForUser(public_session_user_id_)); + EXPECT_EQ(CloudPolicyStore::STATUS_OK, broker->core()->store()->status()); + ASSERT_TRUE(broker->core()->store()->policy()); + EXPECT_EQ(device_local_account_policy_.policy_data().SerializeAsString(), + broker->core()->store()->policy()->SerializeAsString()); + EXPECT_TRUE(expected_policy_map_.Equals( + broker->core()->store()->policy_map())); + EXPECT_TRUE(service_->IsPolicyAvailableForUser(account_1_user_id_)); } TEST_F(DeviceLocalAccountPolicyServiceTest, DevicePolicyChange) { - device_settings_test_helper_.set_device_local_account_policy_blob( - PolicyBuilder::kFakeUsername, device_local_account_policy_.GetBlob()); + InstallDeviceLocalAccountPolicy(kAccount1); + AddDeviceLocalAccountToPolicy(kAccount1); + EXPECT_CALL(service_observer_, OnPolicyUpdated(account_1_user_id_)); InstallDevicePolicy(); - EXPECT_CALL(service_observer_, OnDeviceLocalAccountsChanged()); device_policy_.payload().mutable_device_local_accounts()->clear_account(); - device_policy_.Build(); - device_settings_test_helper_.set_policy_blob(device_policy_.GetBlob()); - device_settings_service_.PropertyChangeComplete(true); - FlushDeviceSettings(); - EXPECT_FALSE(service_.GetBrokerForUser(public_session_user_id_)); - Mock::VerifyAndClearExpectations(&service_observer_); + InstallDevicePolicy(); + + EXPECT_FALSE(service_->GetBrokerForUser(account_1_user_id_)); } TEST_F(DeviceLocalAccountPolicyServiceTest, DuplicateAccounts) { + InstallDeviceLocalAccountPolicy(kAccount1); + AddDeviceLocalAccountToPolicy(kAccount1); + EXPECT_CALL(service_observer_, OnPolicyUpdated(account_1_user_id_)); InstallDevicePolicy(); - DeviceLocalAccountPolicyBroker* broker = - service_.GetBrokerForUser(public_session_user_id_); - ASSERT_TRUE(broker); + Mock::VerifyAndClearExpectations(&service_observer_); // Add a second entry with a duplicate account name to device policy. - em::DeviceLocalAccountInfoProto* account = - device_policy_.payload().mutable_device_local_accounts()->add_account(); - account->set_account_id(PolicyBuilder::kFakeUsername); - account->set_type( - em::DeviceLocalAccountInfoProto::ACCOUNT_TYPE_PUBLIC_SESSION); - device_policy_.Build(); - device_settings_test_helper_.set_device_local_account_policy_blob( - PolicyBuilder::kFakeUsername, device_local_account_policy_.GetBlob()); - device_settings_test_helper_.set_policy_blob(device_policy_.GetBlob()); - - EXPECT_CALL(service_observer_, OnDeviceLocalAccountsChanged()); - EXPECT_CALL(service_observer_, OnPolicyUpdated(public_session_user_id_)); - device_settings_service_.PropertyChangeComplete(true); - FlushDeviceSettings(); - Mock::VerifyAndClearExpectations(&service_observer_); + AddDeviceLocalAccountToPolicy(kAccount1); + InstallDevicePolicy(); // Make sure the broker is accessible and policy got loaded. - broker = service_.GetBrokerForUser(public_session_user_id_); + DeviceLocalAccountPolicyBroker* broker = + service_->GetBrokerForUser(account_1_user_id_); ASSERT_TRUE(broker); - EXPECT_EQ(public_session_user_id_, broker->user_id()); - EXPECT_TRUE(broker->core()->store()->policy()); + EXPECT_EQ(account_1_user_id_, broker->user_id()); + ASSERT_TRUE(broker->core()->store()); + EXPECT_EQ(CloudPolicyStore::STATUS_OK, broker->core()->store()->status()); + ASSERT_TRUE(broker->core()->store()->policy()); + EXPECT_EQ(device_local_account_policy_.policy_data().SerializeAsString(), + broker->core()->store()->policy()->SerializeAsString()); + EXPECT_TRUE(expected_policy_map_.Equals( + broker->core()->store()->policy_map())); + EXPECT_TRUE(service_->IsPolicyAvailableForUser(account_1_user_id_)); } TEST_F(DeviceLocalAccountPolicyServiceTest, FetchPolicy) { - device_settings_test_helper_.set_device_local_account_policy_blob( - PolicyBuilder::kFakeUsername, device_local_account_policy_.GetBlob()); + InstallDeviceLocalAccountPolicy(kAccount1); + AddDeviceLocalAccountToPolicy(kAccount1); + EXPECT_CALL(service_observer_, OnPolicyUpdated(account_1_user_id_)); InstallDevicePolicy(); DeviceLocalAccountPolicyBroker* broker = - service_.GetBrokerForUser(public_session_user_id_); + service_->GetBrokerForUser(account_1_user_id_); ASSERT_TRUE(broker); - service_.Connect(&mock_device_management_service_); + service_->Connect(&mock_device_management_service_); EXPECT_TRUE(broker->core()->client()); em::DeviceManagementRequest request; @@ -326,7 +414,7 @@ TEST_F(DeviceLocalAccountPolicyServiceTest, FetchPolicy) { device_policy_.policy_data().device_id(), _)) .WillOnce(SaveArg<6>(&request)); - EXPECT_CALL(service_observer_, OnPolicyUpdated(public_session_user_id_)); + EXPECT_CALL(service_observer_, OnPolicyUpdated(account_1_user_id_)); broker->core()->client()->FetchPolicy(); FlushDeviceSettings(); Mock::VerifyAndClearExpectations(&service_observer_); @@ -336,7 +424,7 @@ TEST_F(DeviceLocalAccountPolicyServiceTest, FetchPolicy) { EXPECT_EQ(dm_protocol::kChromePublicAccountPolicyType, request.policy_request().request(0).policy_type()); EXPECT_FALSE(request.policy_request().request(0).has_machine_id()); - EXPECT_EQ(PolicyBuilder::kFakeUsername, + EXPECT_EQ(kAccount1, request.policy_request().request(0).settings_entity_id()); ASSERT_TRUE(broker->core()->store()); @@ -347,26 +435,27 @@ TEST_F(DeviceLocalAccountPolicyServiceTest, FetchPolicy) { broker->core()->store()->policy()->SerializeAsString()); EXPECT_TRUE(expected_policy_map_.Equals( broker->core()->store()->policy_map())); - EXPECT_TRUE(service_.IsPolicyAvailableForUser(public_session_user_id_)); + EXPECT_TRUE(service_->IsPolicyAvailableForUser(account_1_user_id_)); - EXPECT_CALL(service_observer_, OnPolicyUpdated(public_session_user_id_)) + EXPECT_CALL(service_observer_, OnPolicyUpdated(account_1_user_id_)) .Times(0); - service_.Disconnect(); + service_->Disconnect(); EXPECT_FALSE(broker->core()->client()); Mock::VerifyAndClearExpectations(&service_observer_); - EXPECT_TRUE(service_.IsPolicyAvailableForUser(public_session_user_id_)); + EXPECT_TRUE(service_->IsPolicyAvailableForUser(account_1_user_id_)); } TEST_F(DeviceLocalAccountPolicyServiceTest, RefreshPolicy) { - device_settings_test_helper_.set_device_local_account_policy_blob( - PolicyBuilder::kFakeUsername, device_local_account_policy_.GetBlob()); + InstallDeviceLocalAccountPolicy(kAccount1); + AddDeviceLocalAccountToPolicy(kAccount1); + EXPECT_CALL(service_observer_, OnPolicyUpdated(account_1_user_id_)); InstallDevicePolicy(); DeviceLocalAccountPolicyBroker* broker = - service_.GetBrokerForUser(public_session_user_id_); + service_->GetBrokerForUser(account_1_user_id_); ASSERT_TRUE(broker); - service_.Connect(&mock_device_management_service_); + service_->Connect(&mock_device_management_service_); ASSERT_TRUE(broker->core()->service()); em::DeviceManagementResponse response; @@ -376,7 +465,7 @@ TEST_F(DeviceLocalAccountPolicyServiceTest, RefreshPolicy) { .WillOnce(mock_device_management_service_.SucceedJob(response)); EXPECT_CALL(mock_device_management_service_, StartJob(_, _, _, _, _, _, _)); EXPECT_CALL(*this, OnRefreshDone(true)).Times(1); - EXPECT_CALL(service_observer_, OnPolicyUpdated(public_session_user_id_)); + EXPECT_CALL(service_observer_, OnPolicyUpdated(account_1_user_id_)); broker->core()->service()->RefreshPolicy( base::Bind(&DeviceLocalAccountPolicyServiceTest::OnRefreshDone, base::Unretained(this))); @@ -390,90 +479,368 @@ TEST_F(DeviceLocalAccountPolicyServiceTest, RefreshPolicy) { broker->core()->store()->status()); EXPECT_TRUE(expected_policy_map_.Equals( broker->core()->store()->policy_map())); - EXPECT_TRUE(service_.IsPolicyAvailableForUser(public_session_user_id_)); + EXPECT_TRUE(service_->IsPolicyAvailableForUser(account_1_user_id_)); +} + +class DeviceLocalAccountPolicyExtensionCacheTest + : public DeviceLocalAccountPolicyServiceTestBase { + protected: + DeviceLocalAccountPolicyExtensionCacheTest(); + + virtual void SetUp() OVERRIDE; + + base::FilePath GetCacheDirectoryForAccountID(const std::string& account_id); + + base::ScopedTempDir cache_root_dir_; + scoped_ptr<base::ScopedPathOverride> cache_root_dir_override_; + + base::FilePath cache_dir_1_; + base::FilePath cache_dir_2_; + base::FilePath cache_dir_3_; + + private: + DISALLOW_COPY_AND_ASSIGN(DeviceLocalAccountPolicyExtensionCacheTest); +}; + +DeviceLocalAccountPolicyExtensionCacheTest:: + DeviceLocalAccountPolicyExtensionCacheTest() { +} + +void DeviceLocalAccountPolicyExtensionCacheTest::SetUp() { + DeviceLocalAccountPolicyServiceTestBase::SetUp(); + ASSERT_TRUE(cache_root_dir_.CreateUniqueTempDir()); + cache_root_dir_override_.reset(new base::ScopedPathOverride( + chromeos::DIR_DEVICE_LOCAL_ACCOUNT_CACHE, + cache_root_dir_.path())); + + cache_dir_1_ = GetCacheDirectoryForAccountID(kAccount1); + cache_dir_2_ = GetCacheDirectoryForAccountID(kAccount2); + cache_dir_3_ = GetCacheDirectoryForAccountID(kAccount3); + + em::StringList* forcelist = device_local_account_policy_.payload() + .mutable_extensioninstallforcelist()->mutable_value(); + forcelist->add_entries(base::StringPrintf("%s;%s", kExtensionID, kUpdateURL)); +} + +base::FilePath DeviceLocalAccountPolicyExtensionCacheTest:: + GetCacheDirectoryForAccountID(const std::string& account_id) { + return cache_root_dir_.path().Append(base::HexEncode(account_id.c_str(), + account_id.size())); +} + +// Verifies that during startup, orphaned cache directories are deleted, +// cache directories belonging to an existing account are preserved and missing +// cache directories are created. Also verifies that when startup is complete, +// the caches for all existing accounts are running. +TEST_F(DeviceLocalAccountPolicyExtensionCacheTest, Startup) { + base::FilePath test_data_dir; + ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir)); + const base::FilePath source_crx_file = + test_data_dir.Append(kExtensionCRXPath); + const std::string target_crx_file_name = + base::StringPrintf("%s-%s.crx", kExtensionID, kExtensionVersion); + + // Create and pre-populate a cache directory for account 1. + EXPECT_TRUE(file_util::CreateDirectory(cache_dir_1_)); + EXPECT_TRUE(CopyFile(source_crx_file, + cache_dir_1_.Append(target_crx_file_name))); + + // Create and pre-populate a cache directory for account 3. + EXPECT_TRUE(file_util::CreateDirectory(cache_dir_3_)); + EXPECT_TRUE(CopyFile(source_crx_file, + cache_dir_3_.Append(target_crx_file_name))); + + // Add accounts 1 and 2 to device policy. + InstallDeviceLocalAccountPolicy(kAccount1); + InstallDeviceLocalAccountPolicy(kAccount2); + AddDeviceLocalAccountToPolicy(kAccount1); + AddDeviceLocalAccountToPolicy(kAccount2); + InstallDevicePolicy(); + + // Create the DeviceLocalAccountPolicyService, allowing it to finish the + // deletion of orphaned cache directories. + CreatePolicyService(); + FlushDeviceSettings(); + extension_cache_task_runner_->RunUntilIdle(); + + // Verify that the cache directory for account 1 and its contents still exist. + EXPECT_TRUE(base::DirectoryExists(cache_dir_1_)); + EXPECT_TRUE(ContentsEqual(source_crx_file, + cache_dir_1_.Append(target_crx_file_name))); + + // Verify that a cache directory for account 2 was created. + EXPECT_TRUE(base::DirectoryExists(cache_dir_2_)); + + // Verify that the cache directory for account 3 was deleted. + EXPECT_FALSE(base::DirectoryExists(cache_dir_3_)); + + // Verify that the cache for account 1 has been started. + DeviceLocalAccountPolicyBroker* broker = + service_->GetBrokerForUser(account_1_user_id_); + ASSERT_TRUE(broker); + EXPECT_TRUE(broker->extension_loader()->IsCacheRunning()); + + // Verify that the cache for account 2 has been started. + broker = service_->GetBrokerForUser(account_2_user_id_); + ASSERT_TRUE(broker); + EXPECT_TRUE(broker->extension_loader()->IsCacheRunning()); +} + +// Verifies that while the deletion of orphaned cache directories is in +// progress, the caches for accounts which existed before the deletion started +// are running but caches for newly added accounts are not started. +TEST_F(DeviceLocalAccountPolicyExtensionCacheTest, RaceAgainstOrphanDeletion) { + // Add account 1 to device policy. + InstallDeviceLocalAccountPolicy(kAccount1); + AddDeviceLocalAccountToPolicy(kAccount1); + InstallDevicePolicy(); + + // Create the DeviceLocalAccountPolicyService, triggering the deletion of + // orphaned cache directories. + CreatePolicyService(); + FlushDeviceSettings(); + + // Verify that the cache for account 1 has been started as it is unaffected by + // the orphan deletion. + DeviceLocalAccountPolicyBroker* broker = + service_->GetBrokerForUser(account_1_user_id_); + ASSERT_TRUE(broker); + EXPECT_TRUE(broker->extension_loader()->IsCacheRunning()); + + // Add account 2 to device policy. + InstallDeviceLocalAccountPolicy(kAccount2); + AddDeviceLocalAccountToPolicy(kAccount2); + InstallDevicePolicy(); + + // Verify that the cache for account 2 has not been started yet as the orphan + // deletion is still in progress. + broker = service_->GetBrokerForUser(account_2_user_id_); + ASSERT_TRUE(broker); + EXPECT_FALSE(broker->extension_loader()->IsCacheRunning()); + + // Allow the orphan deletion to finish. + extension_cache_task_runner_->RunUntilIdle(); + base::RunLoop().RunUntilIdle(); + + // Verify that the cache for account 2 has been started. + EXPECT_TRUE(broker->extension_loader()->IsCacheRunning()); +} + +// Verifies that while the shutdown of a cache is in progress, no new cache is +// started if an account with the same ID is re-added. +TEST_F(DeviceLocalAccountPolicyExtensionCacheTest, RaceAgainstCacheShutdown) { + // Add account 1 to device policy. + InstallDeviceLocalAccountPolicy(kAccount1); + AddDeviceLocalAccountToPolicy(kAccount1); + InstallDevicePolicy(); + + // Create the DeviceLocalAccountPolicyService, allowing it to finish the + // deletion of orphaned cache directories. + CreatePolicyService(); + FlushDeviceSettings(); + extension_cache_task_runner_->RunUntilIdle(); + + // Remove account 1 from device policy, triggering a shutdown of its cache. + device_policy_.payload().mutable_device_local_accounts()->clear_account(); + InstallDevicePolicy(); + + // Re-add account 1 to device policy. + AddDeviceLocalAccountToPolicy(kAccount1); + InstallDevicePolicy(); + + // Verify that the cache for account 1 has not been started yet as the + // shutdown of a previous cache for this account ID is still in progress. + DeviceLocalAccountPolicyBroker* broker = + service_->GetBrokerForUser(account_1_user_id_); + ASSERT_TRUE(broker); + EXPECT_FALSE(broker->extension_loader()->IsCacheRunning()); + + // Allow the cache shutdown to finish. + extension_cache_task_runner_->RunUntilIdle(); + base::RunLoop().RunUntilIdle(); + + // Verify that the cache directory for account 1 still exists. + EXPECT_TRUE(base::DirectoryExists(cache_dir_1_)); + + // Verify that the cache for account 1 has been started, reusing the existing + // cache directory. + EXPECT_TRUE(broker->extension_loader()->IsCacheRunning()); +} + +// Verifies that while the deletion of an obsolete cache directory is in +// progress, no new cache is started if an account with the same ID is re-added. +TEST_F(DeviceLocalAccountPolicyExtensionCacheTest, + RaceAgainstObsoleteDeletion) { + // Add account 1 to device policy. + InstallDeviceLocalAccountPolicy(kAccount1); + AddDeviceLocalAccountToPolicy(kAccount1); + InstallDevicePolicy(); + + // Create the DeviceLocalAccountPolicyService, allowing it to finish the + // deletion of orphaned cache directories. + CreatePolicyService(); + FlushDeviceSettings(); + extension_cache_task_runner_->RunUntilIdle(); + + // Remove account 1 from device policy, allowing the shutdown of its cache to + // finish and the deletion of its now obsolete cache directory to begin. + device_policy_.payload().mutable_device_local_accounts()->clear_account(); + InstallDevicePolicy(); + extension_cache_task_runner_->RunUntilIdle(); + base::RunLoop().RunUntilIdle(); + + // Re-add account 1 to device policy. + AddDeviceLocalAccountToPolicy(kAccount1); + InstallDevicePolicy(); + + // Verify that the cache for account 1 has not been started yet as the + // deletion of the cache directory for this account ID is still in progress. + DeviceLocalAccountPolicyBroker* broker = + service_->GetBrokerForUser(account_1_user_id_); + ASSERT_TRUE(broker); + EXPECT_FALSE(broker->extension_loader()->IsCacheRunning()); + + // Allow the deletion to finish. + extension_cache_task_runner_->RunUntilIdle(); + base::RunLoop().RunUntilIdle(); + + // Verify that the cache directory for account 1 was deleted. + EXPECT_FALSE(base::DirectoryExists(cache_dir_1_)); + + // Verify that the cache for account 1 has been started. + EXPECT_TRUE(broker->extension_loader()->IsCacheRunning()); +} + +// Verifies that when an account is added and no deletion of cache directories +// affecting this account is in progress, its cache is started immediately. +TEST_F(DeviceLocalAccountPolicyExtensionCacheTest, AddAccount) { + // Create the DeviceLocalAccountPolicyService, allowing it to finish the + // deletion of orphaned cache directories. + InstallDevicePolicy(); + CreatePolicyService(); + FlushDeviceSettings(); + extension_cache_task_runner_->RunUntilIdle(); + + // Add account 1 to device policy. + InstallDeviceLocalAccountPolicy(kAccount1); + AddDeviceLocalAccountToPolicy(kAccount1); + InstallDevicePolicy(); + + // Verify that the cache for account 1 has been started. + DeviceLocalAccountPolicyBroker* broker = + service_->GetBrokerForUser(account_1_user_id_); + ASSERT_TRUE(broker); + EXPECT_TRUE(broker->extension_loader()->IsCacheRunning()); +} + +// Verifies that when an account is removed, its cache directory is deleted. +TEST_F(DeviceLocalAccountPolicyExtensionCacheTest, RemoveAccount) { + // Add account 1 to device policy. + InstallDeviceLocalAccountPolicy(kAccount1); + AddDeviceLocalAccountToPolicy(kAccount1); + InstallDevicePolicy(); + + // Create the DeviceLocalAccountPolicyService, allowing it to finish the + // deletion of orphaned cache directories. + CreatePolicyService(); + FlushDeviceSettings(); + extension_cache_task_runner_->RunUntilIdle(); + + // Verify that a cache directory has been created for account 1. + EXPECT_TRUE(base::DirectoryExists(cache_dir_1_)); + + // Remove account 1 from device policy, allowing the deletion of its now + // obsolete cache directory to finish. + device_policy_.payload().mutable_device_local_accounts()->clear_account(); + InstallDevicePolicy(); + extension_cache_task_runner_->RunUntilIdle(); + base::RunLoop().RunUntilIdle(); + extension_cache_task_runner_->RunUntilIdle(); + + // Verify that the cache directory for account 1 was deleted. + EXPECT_FALSE(base::DirectoryExists(cache_dir_1_)); } class DeviceLocalAccountPolicyProviderTest - : public DeviceLocalAccountPolicyServiceTest { + : public DeviceLocalAccountPolicyServiceTestBase { protected: - DeviceLocalAccountPolicyProviderTest() - : provider_( - GenerateDeviceLocalAccountUserId( - PolicyBuilder::kFakeUsername, - DeviceLocalAccount::TYPE_PUBLIC_SESSION), - &service_) {} - - virtual void SetUp() OVERRIDE { - DeviceLocalAccountPolicyServiceTest::SetUp(); - provider_.Init(); - provider_.AddObserver(&provider_observer_); - - EXPECT_CALL(service_observer_, OnPolicyUpdated(_)).Times(AnyNumber()); - EXPECT_CALL(service_observer_, OnDeviceLocalAccountsChanged()) - .Times(AnyNumber()); - } - - virtual void TearDown() OVERRIDE { - provider_.RemoveObserver(&provider_observer_); - provider_.Shutdown(); - DeviceLocalAccountPolicyServiceTest::TearDown(); - } - - DeviceLocalAccountPolicyProvider provider_; + DeviceLocalAccountPolicyProviderTest(); + + virtual void SetUp() OVERRIDE; + virtual void TearDown() OVERRIDE; + + scoped_ptr<DeviceLocalAccountPolicyProvider> provider_; MockConfigurationPolicyObserver provider_observer_; private: DISALLOW_COPY_AND_ASSIGN(DeviceLocalAccountPolicyProviderTest); }; +DeviceLocalAccountPolicyProviderTest::DeviceLocalAccountPolicyProviderTest() { + CreatePolicyService(); + provider_.reset(new DeviceLocalAccountPolicyProvider( + GenerateDeviceLocalAccountUserId(kAccount1, + DeviceLocalAccount::TYPE_PUBLIC_SESSION), + service_.get())); +} + +void DeviceLocalAccountPolicyProviderTest::SetUp() { + DeviceLocalAccountPolicyServiceTestBase::SetUp(); + provider_->Init(); + provider_->AddObserver(&provider_observer_); +} + +void DeviceLocalAccountPolicyProviderTest::TearDown() { + provider_->RemoveObserver(&provider_observer_); + provider_->Shutdown(); + DeviceLocalAccountPolicyServiceTestBase::TearDown(); +} + TEST_F(DeviceLocalAccountPolicyProviderTest, Initialization) { - EXPECT_FALSE(provider_.IsInitializationComplete(POLICY_DOMAIN_CHROME)); + EXPECT_FALSE(provider_->IsInitializationComplete(POLICY_DOMAIN_CHROME)); // Policy change should complete initialization. - EXPECT_CALL(provider_observer_, OnUpdatePolicy(&provider_)).Times(AtLeast(1)); - device_settings_test_helper_.set_device_local_account_policy_blob( - PolicyBuilder::kFakeUsername, device_local_account_policy_.GetBlob()); - device_settings_test_helper_.set_policy_blob(device_policy_.GetBlob()); - ReloadDeviceSettings(); + InstallDeviceLocalAccountPolicy(kAccount1); + AddDeviceLocalAccountToPolicy(kAccount1); + EXPECT_CALL(provider_observer_, OnUpdatePolicy(provider_.get())) + .Times(AtLeast(1)); + InstallDevicePolicy(); Mock::VerifyAndClearExpectations(&provider_observer_); - EXPECT_TRUE(provider_.IsInitializationComplete(POLICY_DOMAIN_CHROME)); + EXPECT_TRUE(provider_->IsInitializationComplete(POLICY_DOMAIN_CHROME)); // The account disappearing should *not* flip the initialization flag back. - EXPECT_CALL(provider_observer_, OnUpdatePolicy(&provider_)) + EXPECT_CALL(provider_observer_, OnUpdatePolicy(provider_.get())) .Times(AnyNumber()); device_policy_.payload().mutable_device_local_accounts()->clear_account(); - device_policy_.Build(); - device_settings_test_helper_.set_policy_blob(device_policy_.GetBlob()); - ReloadDeviceSettings(); + InstallDevicePolicy(); Mock::VerifyAndClearExpectations(&provider_observer_); - EXPECT_TRUE(provider_.IsInitializationComplete(POLICY_DOMAIN_CHROME)); + EXPECT_TRUE(provider_->IsInitializationComplete(POLICY_DOMAIN_CHROME)); } TEST_F(DeviceLocalAccountPolicyProviderTest, Policy) { // Policy should load successfully. - EXPECT_CALL(provider_observer_, OnUpdatePolicy(&provider_)).Times(AtLeast(1)); - device_settings_test_helper_.set_policy_blob(device_policy_.GetBlob()); - device_settings_test_helper_.set_device_local_account_policy_blob( - PolicyBuilder::kFakeUsername, device_local_account_policy_.GetBlob()); - ReloadDeviceSettings(); + EXPECT_CALL(provider_observer_, OnUpdatePolicy(provider_.get())) + .Times(AtLeast(1)); + InstallDeviceLocalAccountPolicy(kAccount1); + AddDeviceLocalAccountToPolicy(kAccount1); + InstallDevicePolicy(); Mock::VerifyAndClearExpectations(&provider_observer_); PolicyBundle expected_policy_bundle; expected_policy_bundle.Get(PolicyNamespace( POLICY_DOMAIN_CHROME, std::string())).CopyFrom(expected_policy_map_); - EXPECT_TRUE(expected_policy_bundle.Equals(provider_.policies())); + EXPECT_TRUE(expected_policy_bundle.Equals(provider_->policies())); // Policy change should be reported. - EXPECT_CALL(provider_observer_, OnUpdatePolicy(&provider_)).Times(AtLeast(1)); + EXPECT_CALL(provider_observer_, OnUpdatePolicy(provider_.get())) + .Times(AtLeast(1)); device_local_account_policy_.payload().mutable_disablespdy()->set_value( false); - device_local_account_policy_.Build(); - device_settings_test_helper_.set_device_local_account_policy_blob( - PolicyBuilder::kFakeUsername, device_local_account_policy_.GetBlob()); + InstallDeviceLocalAccountPolicy(kAccount1); DeviceLocalAccountPolicyBroker* broker = - service_.GetBrokerForUser(public_session_user_id_); + service_->GetBrokerForUser(account_1_user_id_); ASSERT_TRUE(broker); broker->core()->store()->Load(); FlushDeviceSettings(); @@ -486,57 +853,55 @@ TEST_F(DeviceLocalAccountPolicyProviderTest, Policy) { POLICY_SCOPE_USER, Value::CreateBooleanValue(false), NULL); - EXPECT_TRUE(expected_policy_bundle.Equals(provider_.policies())); + EXPECT_TRUE(expected_policy_bundle.Equals(provider_->policies())); // Any values set for the |ShelfAutoHideBehavior|, |ShowLogoutButtonInTray| // and |ExtensionAllowedTypes| policies should be overridden. - EXPECT_CALL(provider_observer_, OnUpdatePolicy(&provider_)).Times(AtLeast(1)); + EXPECT_CALL(provider_observer_, OnUpdatePolicy(provider_.get())) + .Times(AtLeast(1)); device_local_account_policy_.payload().mutable_shelfautohidebehavior()-> set_value("Always"); device_local_account_policy_.payload().mutable_showlogoutbuttonintray()-> set_value(false); - device_local_account_policy_.Build(); - device_settings_test_helper_.set_device_local_account_policy_blob( - PolicyBuilder::kFakeUsername, device_local_account_policy_.GetBlob()); + InstallDeviceLocalAccountPolicy(kAccount1); broker->core()->store()->Load(); FlushDeviceSettings(); Mock::VerifyAndClearExpectations(&provider_observer_); - EXPECT_TRUE(expected_policy_bundle.Equals(provider_.policies())); + EXPECT_TRUE(expected_policy_bundle.Equals(provider_->policies())); // Account disappears, policy should stay in effect. - EXPECT_CALL(provider_observer_, OnUpdatePolicy(&provider_)) + EXPECT_CALL(provider_observer_, OnUpdatePolicy(provider_.get())) .Times(AnyNumber()); device_policy_.payload().mutable_device_local_accounts()->clear_account(); - device_policy_.Build(); - device_settings_test_helper_.set_policy_blob(device_policy_.GetBlob()); - ReloadDeviceSettings(); + InstallDevicePolicy(); Mock::VerifyAndClearExpectations(&provider_observer_); - EXPECT_TRUE(expected_policy_bundle.Equals(provider_.policies())); + EXPECT_TRUE(expected_policy_bundle.Equals(provider_->policies())); } TEST_F(DeviceLocalAccountPolicyProviderTest, RefreshPolicies) { // If there's no device policy, the refresh completes immediately. - EXPECT_FALSE(service_.GetBrokerForUser(public_session_user_id_)); - EXPECT_CALL(provider_observer_, OnUpdatePolicy(&provider_)).Times(AtLeast(1)); - provider_.RefreshPolicies(); + EXPECT_FALSE(service_->GetBrokerForUser(account_1_user_id_)); + EXPECT_CALL(provider_observer_, OnUpdatePolicy(provider_.get())) + .Times(AtLeast(1)); + provider_->RefreshPolicies(); Mock::VerifyAndClearExpectations(&provider_observer_); // Make device settings appear. - EXPECT_CALL(provider_observer_, OnUpdatePolicy(&provider_)) + EXPECT_CALL(provider_observer_, OnUpdatePolicy(provider_.get())) .Times(AnyNumber()); - device_settings_test_helper_.set_policy_blob(device_policy_.GetBlob()); - ReloadDeviceSettings(); - Mock::VerifyAndClearExpectations(&provider_observer_); - EXPECT_TRUE(service_.GetBrokerForUser(public_session_user_id_)); + AddDeviceLocalAccountToPolicy(kAccount1); + InstallDevicePolicy(); + EXPECT_TRUE(service_->GetBrokerForUser(account_1_user_id_)); // If there's no cloud connection, refreshes are still immediate. DeviceLocalAccountPolicyBroker* broker = - service_.GetBrokerForUser(public_session_user_id_); + service_->GetBrokerForUser(account_1_user_id_); ASSERT_TRUE(broker); EXPECT_FALSE(broker->core()->client()); - EXPECT_CALL(provider_observer_, OnUpdatePolicy(&provider_)).Times(AtLeast(1)); - provider_.RefreshPolicies(); + EXPECT_CALL(provider_observer_, OnUpdatePolicy(provider_.get())) + .Times(AtLeast(1)); + provider_->RefreshPolicies(); Mock::VerifyAndClearExpectations(&provider_observer_); // Bring up the cloud connection. The refresh scheduler may fire refreshes at @@ -546,7 +911,7 @@ TEST_F(DeviceLocalAccountPolicyProviderTest, RefreshPolicies) { mock_device_management_service_.FailJob(DM_STATUS_REQUEST_FAILED)); EXPECT_CALL(mock_device_management_service_, StartJob(_, _, _, _, _, _, _)) .Times(AnyNumber()); - service_.Connect(&mock_device_management_service_); + service_->Connect(&mock_device_management_service_); FlushDeviceSettings(); Mock::VerifyAndClearExpectations(&mock_device_management_service_); @@ -556,16 +921,18 @@ TEST_F(DeviceLocalAccountPolicyProviderTest, RefreshPolicies) { EXPECT_CALL(mock_device_management_service_, CreateJob(_)) .WillOnce(mock_device_management_service_.CreateAsyncJob(&request_job)); EXPECT_CALL(mock_device_management_service_, StartJob(_, _, _, _, _, _, _)); - provider_.RefreshPolicies(); + provider_->RefreshPolicies(); ReloadDeviceSettings(); Mock::VerifyAndClearExpectations(&provider_observer_); Mock::VerifyAndClearExpectations(&mock_device_management_service_); - EXPECT_TRUE(provider_.IsInitializationComplete(POLICY_DOMAIN_CHROME)); + EXPECT_TRUE(provider_->IsInitializationComplete(POLICY_DOMAIN_CHROME)); // When the response comes in, it should propagate and fire the notification. - EXPECT_CALL(provider_observer_, OnUpdatePolicy(&provider_)).Times(AtLeast(1)); + EXPECT_CALL(provider_observer_, OnUpdatePolicy(provider_.get())) + .Times(AtLeast(1)); ASSERT_TRUE(request_job); em::DeviceManagementResponse response; + device_local_account_policy_.Build(); response.mutable_policy_response()->add_response()->CopyFrom( device_local_account_policy_.policy()); request_job->SendResponse(DM_STATUS_SUCCESS, response); diff --git a/chrome/browser/chromeos/settings/device_settings_test_helper.cc b/chrome/browser/chromeos/settings/device_settings_test_helper.cc index 04a77ea..2050364 100644 --- a/chrome/browser/chromeos/settings/device_settings_test_helper.cc +++ b/chrome/browser/chromeos/settings/device_settings_test_helper.cc @@ -63,6 +63,7 @@ void DeviceSettingsTestHelper::FlushRetrieve() { for (device_local_account_state = device_local_account_policy_.begin(); device_local_account_state != device_local_account_policy_.end(); ++device_local_account_state) { + std::vector<RetrievePolicyCallback> callbacks; callbacks.swap(device_local_account_state->second.retrieve_callbacks_); for (std::vector<RetrievePolicyCallback>::iterator cb(callbacks.begin()); cb != callbacks.end(); ++cb) { |