blob: 8b4a8467a7e3ef4bba37cb64cd090d0dc6dc71d3 [file] [log] [blame]
// 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/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/api/storage/backend_task_runner.h"
#include "extensions/browser/extension_file_task_runner.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"
#if defined(OS_CHROMEOS)
#include "chrome/browser/chromeos/profiles/profile_helper.h"
#endif
using content::BrowserContext;
using content::BrowserThread;
namespace extensions {
class ExtensionRegistry;
namespace storage = api::storage;
namespace {
// 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:
ExtensionTracker(Profile* profile, policy::PolicyDomain policy_domain);
~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(std::unique_ptr<ExtensionSet> added);
bool UsesManagedStorage(const Extension* extension) const;
// Loads the schemas of the |extensions| and passes a ComponentMap to
// Register().
static void LoadSchemasOnFileTaskRunner(
std::unique_ptr<ExtensionSet> extensions,
base::WeakPtr<ExtensionTracker> self);
void Register(const policy::ComponentMap* components);
Profile* profile_;
policy::PolicyDomain policy_domain_;
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,
policy::PolicyDomain policy_domain)
: profile_(profile),
policy_domain_(policy_domain),
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;
std::unique_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_domain_, extension->id()));
}
}
void ManagedValueStoreCache::ExtensionTracker::OnExtensionsReady() {
// Load schemas for all installed extensions.
LoadSchemas(
ExtensionRegistry::Get(profile_)->GenerateInstalledExtensionsSet());
}
void ManagedValueStoreCache::ExtensionTracker::LoadSchemas(
std::unique_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);
}
GetExtensionFileTaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(&ExtensionTracker::LoadSchemasOnFileTaskRunner,
base::Passed(&added), weak_factory_.GetWeakPtr()));
}
bool ManagedValueStoreCache::ExtensionTracker::UsesManagedStorage(
const Extension* extension) const {
return extension->manifest()->HasPath(manifest_keys::kStorageManagedSchema);
}
// static
void ManagedValueStoreCache::ExtensionTracker::LoadSchemasOnFileTaskRunner(
std::unique_ptr<ExtensionSet> extensions,
base::WeakPtr<ExtensionTracker> self) {
base::ThreadRestrictions::AssertIOAllowed();
std::unique_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::BindOnce(&ExtensionTracker::Register, self,
base::Owned(components.release())));
}
void ManagedValueStoreCache::ExtensionTracker::Register(
const policy::ComponentMap* components) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
schema_registry_->RegisterComponents(policy_domain_, *components);
// The first SetExtensionsDomainsReady() 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 for extensions,
// and OnPolicyServiceInitialized() will be invoked.
// Subsequent calls to SetExtensionsDomainsReady() are ignored.
//
// Note that there is only ever one |ManagedValueStoreCache| instance for each
// profile, regardless of its type, therefore all extensions policy domains
// are marked as ready here.
schema_registry_->SetExtensionsDomainsReady();
}
ManagedValueStoreCache::ManagedValueStoreCache(
BrowserContext* context,
const scoped_refptr<ValueStoreFactory>& factory,
const scoped_refptr<SettingsObserverList>& observers)
: profile_(Profile::FromBrowserContext(context)),
policy_domain_(GetPolicyDomain(profile_)),
policy_service_(
policy::ProfilePolicyConnectorFactory::GetForBrowserContext(context)
->policy_service()),
storage_factory_(factory),
observers_(observers) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
policy_service_->AddObserver(policy_domain_, this);
extension_tracker_.reset(new ExtensionTracker(profile_, policy_domain_));
if (policy_service_->IsInitializationComplete(policy_domain_))
OnPolicyServiceInitialized(policy_domain_);
}
ManagedValueStoreCache::~ManagedValueStoreCache() {
DCHECK(IsOnBackendSequence());
// Delete the PolicyValueStores on FILE.
store_map_.clear();
}
void ManagedValueStoreCache::ShutdownOnUI() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
policy_service_->RemoveObserver(policy_domain_, this);
extension_tracker_.reset();
}
void ManagedValueStoreCache::RunWithValueStoreForExtension(
const StorageCallback& callback,
scoped_refptr<const Extension> extension) {
DCHECK(IsOnBackendSequence());
callback.Run(GetStoreFor(extension->id()));
}
void ManagedValueStoreCache::DeleteStorageSoon(
const std::string& extension_id) {
DCHECK(IsOnBackendSequence());
// 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_domain_)
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_domain_);
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_domain_, 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_domain_)) {
// 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;
}
GetBackendTaskRunner()->PostTask(
FROM_HERE, base::BindOnce(&ManagedValueStoreCache::UpdatePolicyOnBackend,
base::Unretained(this), ns.component_id,
base::Passed(current.DeepCopy())));
}
// static
policy::PolicyDomain ManagedValueStoreCache::GetPolicyDomain(Profile* profile) {
#if defined(OS_CHROMEOS)
return chromeos::ProfileHelper::IsSigninProfile(profile)
? policy::POLICY_DOMAIN_SIGNIN_EXTENSIONS
: policy::POLICY_DOMAIN_EXTENSIONS;
#else
return policy::POLICY_DOMAIN_EXTENSIONS;
#endif
}
void ManagedValueStoreCache::UpdatePolicyOnBackend(
const std::string& extension_id,
std::unique_ptr<policy::PolicyMap> current_policy) {
DCHECK(IsOnBackendSequence());
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(IsOnBackendSequence());
auto 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.
std::unique_ptr<PolicyValueStore> store(new PolicyValueStore(
extension_id, observers_,
storage_factory_->CreateSettingsStore(settings_namespace::MANAGED,
kManagedModelType, extension_id)));
PolicyValueStore* raw_store = store.get();
store_map_[extension_id] = std::move(store);
return raw_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