// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/extensions/api/storage/managed_value_store_cache.h" #include <utility> #include "base/bind.h" #include "base/bind_helpers.h" #include "base/callback.h" #include "base/files/file_util.h" #include "base/logging.h" #include "base/macros.h" #include "base/memory/weak_ptr.h" #include "base/scoped_observer.h" #include "chrome/browser/extensions/api/storage/policy_value_store.h" #include "chrome/browser/policy/profile_policy_connector.h" #include "chrome/browser/policy/profile_policy_connector_factory.h" #include "chrome/browser/policy/schema_registry_service.h" #include "chrome/browser/policy/schema_registry_service_factory.h" #include "chrome/browser/profiles/profile.h" #include "chrome/common/extensions/api/storage/storage_schema_manifest_handler.h" #include "components/policy/core/common/policy_namespace.h" #include "components/policy/core/common/schema.h" #include "components/policy/core/common/schema_map.h" #include "components/policy/core/common/schema_registry.h" #include "content/public/browser/browser_thread.h" #include "extensions/browser/extension_prefs.h" #include "extensions/browser/extension_registry.h" #include "extensions/browser/extension_registry_observer.h" #include "extensions/browser/extension_system.h" #include "extensions/browser/value_store/value_store_change.h" #include "extensions/browser/value_store/value_store_factory.h" #include "extensions/common/api/storage.h" #include "extensions/common/constants.h" #include "extensions/common/extension.h" #include "extensions/common/extension_set.h" #include "extensions/common/manifest.h" #include "extensions/common/manifest_constants.h" #include "extensions/common/one_shot_event.h" using content::BrowserContext; using content::BrowserThread; namespace extensions { class ExtensionRegistry; namespace storage = api::storage; namespace { const char kLoadSchemasBackgroundTaskTokenName[] = "load_managed_storage_schemas_token"; // The Legacy Browser Support was the first user of the policy-for-extensions // API, and relied on behavior that will be phased out. If this extension is // present then its policies will be loaded in a special way. // TODO(joaodasilva): remove this for M35. http://crbug.com/325349 const char kLegacyBrowserSupportExtensionId[] = "heildphpnddilhkemkielfhnkaagiabh"; // Only extension settings are stored in the managed namespace - not apps. const ValueStoreFactory::ModelType kManagedModelType = ValueStoreFactory::ModelType::EXTENSION; } // namespace // This helper observes initialization of all the installed extensions and // subsequent loads and unloads, and keeps the SchemaRegistry of the Profile // in sync with the current list of extensions. This allows the PolicyService // to fetch cloud policy for those extensions, and allows its providers to // selectively load only extension policy that has users. class ManagedValueStoreCache::ExtensionTracker : public ExtensionRegistryObserver { public: explicit ExtensionTracker(Profile* profile); ~ExtensionTracker() override {} private: // ExtensionRegistryObserver implementation. void OnExtensionWillBeInstalled(content::BrowserContext* browser_context, const Extension* extension, bool is_update, const std::string& old_name) override; void OnExtensionUninstalled(content::BrowserContext* browser_context, const Extension* extension, extensions::UninstallReason reason) override; // Handler for the signal from ExtensionSystem::ready(). void OnExtensionsReady(); // Starts a schema load for all extensions that use managed storage. void LoadSchemas(scoped_ptr<ExtensionSet> added); bool UsesManagedStorage(const Extension* extension) const; // Loads the schemas of the |extensions| and passes a ComponentMap to // Register(). static void LoadSchemasOnBlockingPool(scoped_ptr<ExtensionSet> extensions, base::WeakPtr<ExtensionTracker> self); void Register(const policy::ComponentMap* components); Profile* profile_; ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver> extension_registry_observer_; policy::SchemaRegistry* schema_registry_; base::WeakPtrFactory<ExtensionTracker> weak_factory_; DISALLOW_COPY_AND_ASSIGN(ExtensionTracker); }; ManagedValueStoreCache::ExtensionTracker::ExtensionTracker(Profile* profile) : profile_(profile), extension_registry_observer_(this), schema_registry_(policy::SchemaRegistryServiceFactory::GetForContext( profile)->registry()), weak_factory_(this) { extension_registry_observer_.Add(ExtensionRegistry::Get(profile_)); // Load schemas when the extension system is ready. It might be ready now. ExtensionSystem::Get(profile_)->ready().Post( FROM_HERE, base::Bind(&ExtensionTracker::OnExtensionsReady, weak_factory_.GetWeakPtr())); } void ManagedValueStoreCache::ExtensionTracker::OnExtensionWillBeInstalled( content::BrowserContext* browser_context, const Extension* extension, bool is_update, const std::string& old_name) { // Some extensions are installed on the first run before the ExtensionSystem // becomes ready. Wait until all of them are ready before registering the // schemas of managed extensions, so that the policy loaders are reloaded at // most once. if (!ExtensionSystem::Get(profile_)->ready().is_signaled()) return; scoped_ptr<ExtensionSet> added(new ExtensionSet); added->Insert(extension); LoadSchemas(std::move(added)); } void ManagedValueStoreCache::ExtensionTracker::OnExtensionUninstalled( content::BrowserContext* browser_context, const Extension* extension, extensions::UninstallReason reason) { if (!ExtensionSystem::Get(profile_)->ready().is_signaled()) return; if (extension && UsesManagedStorage(extension)) { schema_registry_->UnregisterComponent(policy::PolicyNamespace( policy::POLICY_DOMAIN_EXTENSIONS, extension->id())); } } void ManagedValueStoreCache::ExtensionTracker::OnExtensionsReady() { // Load schemas for all installed extensions. LoadSchemas( ExtensionRegistry::Get(profile_)->GenerateInstalledExtensionsSet()); } void ManagedValueStoreCache::ExtensionTracker::LoadSchemas( scoped_ptr<ExtensionSet> added) { // Filter out extensions that don't use managed storage. ExtensionSet::const_iterator it = added->begin(); while (it != added->end()) { std::string to_remove; if (!UsesManagedStorage(it->get())) to_remove = (*it)->id(); ++it; if (!to_remove.empty()) added->Remove(to_remove); } // Load the schema files in a background thread. BrowserThread::PostBlockingPoolSequencedTask( kLoadSchemasBackgroundTaskTokenName, FROM_HERE, base::Bind(&ExtensionTracker::LoadSchemasOnBlockingPool, base::Passed(&added), weak_factory_.GetWeakPtr())); } bool ManagedValueStoreCache::ExtensionTracker::UsesManagedStorage( const Extension* extension) const { if (extension->manifest()->HasPath(manifest_keys::kStorageManagedSchema)) return true; // TODO(joaodasilva): remove this by M35. return extension->id() == kLegacyBrowserSupportExtensionId; } // static void ManagedValueStoreCache::ExtensionTracker::LoadSchemasOnBlockingPool( scoped_ptr<ExtensionSet> extensions, base::WeakPtr<ExtensionTracker> self) { DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); scoped_ptr<policy::ComponentMap> components(new policy::ComponentMap); for (ExtensionSet::const_iterator it = extensions->begin(); it != extensions->end(); ++it) { std::string schema_file; if (!(*it)->manifest()->GetString( manifest_keys::kStorageManagedSchema, &schema_file)) { // TODO(joaodasilva): Remove this. http://crbug.com/325349 (*components)[(*it)->id()] = policy::Schema(); continue; } // The extension should have been validated, so assume the schema exists // and is valid. std::string error; policy::Schema schema = StorageSchemaManifestHandler::GetSchema(it->get(), &error); // If the schema is invalid then proceed with an empty schema. The extension // will be listed in chrome://policy but won't be able to load any policies. if (!schema.valid()) schema = policy::Schema(); (*components)[(*it)->id()] = schema; } BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(&ExtensionTracker::Register, self, base::Owned(components.release()))); } void ManagedValueStoreCache::ExtensionTracker::Register( const policy::ComponentMap* components) { DCHECK_CURRENTLY_ON(BrowserThread::UI); schema_registry_->RegisterComponents(policy::POLICY_DOMAIN_EXTENSIONS, *components); // The first SetReady() call is performed after the ExtensionSystem is ready, // even if there are no managed extensions. It will trigger a loading of the // initial policy for any managed extensions, and eventually the PolicyService // will become ready for POLICY_DOMAIN_EXTENSIONS, and // OnPolicyServiceInitialized() will be invoked. // Subsequent calls to SetReady() are ignored. schema_registry_->SetReady(policy::POLICY_DOMAIN_EXTENSIONS); } ManagedValueStoreCache::ManagedValueStoreCache( BrowserContext* context, const scoped_refptr<ValueStoreFactory>& factory, const scoped_refptr<SettingsObserverList>& observers) : profile_(Profile::FromBrowserContext(context)), policy_service_( policy::ProfilePolicyConnectorFactory::GetForBrowserContext(context) ->policy_service()), storage_factory_(factory), observers_(observers) { DCHECK_CURRENTLY_ON(BrowserThread::UI); policy_service_->AddObserver(policy::POLICY_DOMAIN_EXTENSIONS, this); extension_tracker_.reset(new ExtensionTracker(profile_)); if (policy_service_->IsInitializationComplete( policy::POLICY_DOMAIN_EXTENSIONS)) { OnPolicyServiceInitialized(policy::POLICY_DOMAIN_EXTENSIONS); } } ManagedValueStoreCache::~ManagedValueStoreCache() { DCHECK_CURRENTLY_ON(BrowserThread::FILE); // Delete the PolicyValueStores on FILE. store_map_.clear(); } void ManagedValueStoreCache::ShutdownOnUI() { DCHECK_CURRENTLY_ON(BrowserThread::UI); policy_service_->RemoveObserver(policy::POLICY_DOMAIN_EXTENSIONS, this); extension_tracker_.reset(); } void ManagedValueStoreCache::RunWithValueStoreForExtension( const StorageCallback& callback, scoped_refptr<const Extension> extension) { DCHECK_CURRENTLY_ON(BrowserThread::FILE); callback.Run(GetStoreFor(extension->id())); } void ManagedValueStoreCache::DeleteStorageSoon( const std::string& extension_id) { DCHECK_CURRENTLY_ON(BrowserThread::FILE); // It's possible that the store exists, but hasn't been loaded yet // (because the extension is unloaded, for example). Open the database to // clear it if it exists. if (!HasStore(extension_id)) return; GetStoreFor(extension_id)->DeleteStorage(); store_map_.erase(extension_id); } void ManagedValueStoreCache::OnPolicyServiceInitialized( policy::PolicyDomain domain) { DCHECK_CURRENTLY_ON(BrowserThread::UI); if (domain != policy::POLICY_DOMAIN_EXTENSIONS) return; // The PolicyService now has all the initial policies ready. Send policy // for all the managed extensions to their backing stores now. policy::SchemaRegistry* registry = policy::SchemaRegistryServiceFactory::GetForContext(profile_)->registry(); const policy::ComponentMap* map = registry->schema_map()->GetComponents( policy::POLICY_DOMAIN_EXTENSIONS); if (!map) return; const policy::PolicyMap empty_map; for (policy::ComponentMap::const_iterator it = map->begin(); it != map->end(); ++it) { const policy::PolicyNamespace ns(policy::POLICY_DOMAIN_EXTENSIONS, it->first); // If there is no policy for |ns| then this will clear the previous store, // if there is one. OnPolicyUpdated(ns, empty_map, policy_service_->GetPolicies(ns)); } } void ManagedValueStoreCache::OnPolicyUpdated(const policy::PolicyNamespace& ns, const policy::PolicyMap& previous, const policy::PolicyMap& current) { DCHECK_CURRENTLY_ON(BrowserThread::UI); if (!policy_service_->IsInitializationComplete( policy::POLICY_DOMAIN_EXTENSIONS)) { // OnPolicyUpdated is called whenever a policy changes, but it doesn't // mean that all the policy providers are ready; wait until we get the // final policy values before passing them to the store. return; } BrowserThread::PostTask( BrowserThread::FILE, FROM_HERE, base::Bind(&ManagedValueStoreCache::UpdatePolicyOnFILE, base::Unretained(this), ns.component_id, base::Passed(current.DeepCopy()))); } void ManagedValueStoreCache::UpdatePolicyOnFILE( const std::string& extension_id, scoped_ptr<policy::PolicyMap> current_policy) { DCHECK_CURRENTLY_ON(BrowserThread::FILE); if (!HasStore(extension_id) && current_policy->empty()) { // Don't create the store now if there are no policies configured for this // extension. If the extension uses the storage.managed API then the store // will be created at RunWithValueStoreForExtension(). return; } GetStoreFor(extension_id)->SetCurrentPolicy(*current_policy); } PolicyValueStore* ManagedValueStoreCache::GetStoreFor( const std::string& extension_id) { DCHECK_CURRENTLY_ON(BrowserThread::FILE); PolicyValueStoreMap::iterator it = store_map_.find(extension_id); if (it != store_map_.end()) return it->second.get(); // Create the store now, and serve the cached policy until the PolicyService // sends updated values. PolicyValueStore* store = new PolicyValueStore( extension_id, observers_, storage_factory_->CreateSettingsStore(settings_namespace::MANAGED, kManagedModelType, extension_id)); store_map_[extension_id] = make_linked_ptr(store); return store; } bool ManagedValueStoreCache::HasStore(const std::string& extension_id) const { // Note: Currently only manage extensions (not apps). return storage_factory_->HasSettings(settings_namespace::MANAGED, kManagedModelType, extension_id); } } // namespace extensions