blob: 47ad4127e6895d593fc1c9aac8cc2c1e7215a8f2 [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 "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback.h"
#include "base/file_util.h"
#include "base/logging.h"
#include "base/memory/weak_ptr.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/extensions/api/storage/policy_value_store.h"
#include "chrome/browser/extensions/api/storage/settings_storage_factory.h"
#include "chrome/browser/extensions/extension_prefs.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_system.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/browser/value_store/value_store_change.h"
#include "chrome/common/extensions/api/storage.h"
#include "chrome/common/extensions/api/storage/storage_schema_manifest_handler.h"
#include "chrome/common/extensions/extension_set.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 "content/public/browser/notification_details.h"
#include "content/public/browser/notification_observer.h"
#include "content/public/browser/notification_registrar.h"
#include "content/public/browser/notification_source.h"
#include "extensions/common/constants.h"
#include "extensions/common/extension.h"
#include "extensions/common/manifest.h"
#include "extensions/common/manifest_constants.h"
#include "extensions/common/one_shot_event.h"
#include "extensions/common/permissions/api_permission.h"
using content::BrowserThread;
namespace extensions {
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";
} // 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 content::NotificationObserver {
public:
explicit ExtensionTracker(Profile* profile);
virtual ~ExtensionTracker() {}
// NotificationObserver implementation:
virtual void Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) OVERRIDE;
private:
bool UsesManagedStorage(const Extension* extension) const;
// Loads the schemas of the |extensions| and passes a ComponentMap to
// Register().
static void LoadSchemas(scoped_ptr<ExtensionSet> extensions,
base::WeakPtr<ExtensionTracker> self);
void Register(const policy::ComponentMap* components);
Profile* profile_;
content::NotificationRegistrar registrar_;
policy::SchemaRegistry* schema_registry_;
base::WeakPtrFactory<ExtensionTracker> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(ExtensionTracker);
};
ManagedValueStoreCache::ExtensionTracker::ExtensionTracker(Profile* profile)
: profile_(profile),
schema_registry_(
policy::SchemaRegistryServiceFactory::GetForContext(profile)),
weak_factory_(this) {
registrar_.Add(this,
chrome::NOTIFICATION_EXTENSIONS_READY,
content::Source<Profile>(profile_));
registrar_.Add(this,
chrome::NOTIFICATION_EXTENSION_INSTALLED,
content::Source<Profile>(profile_));
registrar_.Add(this,
chrome::NOTIFICATION_EXTENSION_UNINSTALLED,
content::Source<Profile>(profile_));
}
void ManagedValueStoreCache::ExtensionTracker::Observe(
int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
// Some extensions are installed on the first run before the ExtensionService
// 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;
const Extension* removed = NULL;
switch (type) {
case chrome::NOTIFICATION_EXTENSIONS_READY: {
ExtensionService* service =
ExtensionSystem::Get(profile_)->extension_service();
added = service->GenerateInstalledExtensionsSet();
break;
}
case chrome::NOTIFICATION_EXTENSION_INSTALLED:
added.reset(new ExtensionSet);
added->Insert(
content::Details<InstalledExtensionInfo>(details)->extension);
break;
case chrome::NOTIFICATION_EXTENSION_UNINSTALLED:
removed = content::Details<const Extension>(details).ptr();
break;
default:
NOTREACHED();
return;
}
if (removed) {
if (UsesManagedStorage(removed)) {
schema_registry_->UnregisterComponent(policy::PolicyNamespace(
policy::POLICY_DOMAIN_EXTENSIONS, removed->id()));
}
return;
}
if (!added) {
NOTREACHED();
return;
}
ExtensionSet::const_iterator it = added->begin();
while (it != added->end()) {
std::string to_remove;
if (!UsesManagedStorage(*it))
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::LoadSchemas,
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::LoadSchemas(
scoped_ptr<ExtensionSet> extensions,
base::WeakPtr<ExtensionTracker> self) {
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/240704
if ((*it)->HasAPIPermission(APIPermission::kStorage)) {
(*components)[(*it)->id()] = policy::Schema();
} else {
NOTREACHED();
}
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);
CHECK(schema.valid()) << error;
(*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) {
schema_registry_->RegisterComponents(policy::POLICY_DOMAIN_EXTENSIONS,
*components);
// The first SetReady() call is performed after receiving
// NOTIFICATION_EXTENSIONS_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(
Profile* profile,
const scoped_refptr<SettingsStorageFactory>& factory,
const scoped_refptr<SettingsObserverList>& observers)
: profile_(profile),
policy_service_(policy::ProfilePolicyConnectorFactory::GetForProfile(
profile)->policy_service()),
storage_factory_(factory),
observers_(observers),
base_path_(profile->GetPath().AppendASCII(
extensions::kManagedSettingsDirectoryName)) {
DCHECK(BrowserThread::CurrentlyOn(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(BrowserThread::CurrentlyOn(BrowserThread::FILE));
// Delete the PolicyValueStores on FILE.
store_map_.clear();
}
void ManagedValueStoreCache::ShutdownOnUI() {
DCHECK(BrowserThread::CurrentlyOn(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(BrowserThread::CurrentlyOn(BrowserThread::FILE));
callback.Run(GetStoreFor(extension->id()));
}
void ManagedValueStoreCache::DeleteStorageSoon(
const std::string& extension_id) {
DCHECK(BrowserThread::CurrentlyOn(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(BrowserThread::CurrentlyOn(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_);
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(BrowserThread::CurrentlyOn(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(BrowserThread::CurrentlyOn(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(BrowserThread::CurrentlyOn(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_,
make_scoped_ptr(storage_factory_->Create(base_path_, extension_id)));
store_map_[extension_id] = make_linked_ptr(store);
return store;
}
bool ManagedValueStoreCache::HasStore(const std::string& extension_id) const {
// TODO(joaodasilva): move this check to a ValueStore method.
return base::DirectoryExists(base_path_.AppendASCII(extension_id));
}
} // namespace extensions