1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/extensions/api/storage/managed_value_store_cache.h"
6
7#include "base/bind.h"
8#include "base/bind_helpers.h"
9#include "base/callback.h"
10#include "base/files/file_util.h"
11#include "base/logging.h"
12#include "base/memory/weak_ptr.h"
13#include "base/scoped_observer.h"
14#include "chrome/browser/extensions/api/storage/policy_value_store.h"
15#include "chrome/browser/policy/profile_policy_connector.h"
16#include "chrome/browser/policy/profile_policy_connector_factory.h"
17#include "chrome/browser/policy/schema_registry_service.h"
18#include "chrome/browser/policy/schema_registry_service_factory.h"
19#include "chrome/browser/profiles/profile.h"
20#include "chrome/common/extensions/api/storage/storage_schema_manifest_handler.h"
21#include "components/policy/core/common/policy_namespace.h"
22#include "components/policy/core/common/schema.h"
23#include "components/policy/core/common/schema_map.h"
24#include "components/policy/core/common/schema_registry.h"
25#include "content/public/browser/browser_thread.h"
26#include "extensions/browser/api/storage/settings_storage_factory.h"
27#include "extensions/browser/extension_prefs.h"
28#include "extensions/browser/extension_registry.h"
29#include "extensions/browser/extension_registry_observer.h"
30#include "extensions/browser/extension_system.h"
31#include "extensions/browser/value_store/value_store_change.h"
32#include "extensions/common/api/storage.h"
33#include "extensions/common/constants.h"
34#include "extensions/common/extension.h"
35#include "extensions/common/extension_set.h"
36#include "extensions/common/manifest.h"
37#include "extensions/common/manifest_constants.h"
38#include "extensions/common/one_shot_event.h"
39
40using content::BrowserContext;
41using content::BrowserThread;
42
43namespace extensions {
44class ExtensionRegistry;
45
46namespace storage = core_api::storage;
47
48namespace {
49
50const char kLoadSchemasBackgroundTaskTokenName[] =
51    "load_managed_storage_schemas_token";
52
53// The Legacy Browser Support was the first user of the policy-for-extensions
54// API, and relied on behavior that will be phased out. If this extension is
55// present then its policies will be loaded in a special way.
56// TODO(joaodasilva): remove this for M35. http://crbug.com/325349
57const char kLegacyBrowserSupportExtensionId[] =
58    "heildphpnddilhkemkielfhnkaagiabh";
59
60}  // namespace
61
62// This helper observes initialization of all the installed extensions and
63// subsequent loads and unloads, and keeps the SchemaRegistry of the Profile
64// in sync with the current list of extensions. This allows the PolicyService
65// to fetch cloud policy for those extensions, and allows its providers to
66// selectively load only extension policy that has users.
67class ManagedValueStoreCache::ExtensionTracker
68    : public ExtensionRegistryObserver {
69 public:
70  explicit ExtensionTracker(Profile* profile);
71  virtual ~ExtensionTracker() {}
72
73 private:
74  // ExtensionRegistryObserver implementation.
75  virtual void OnExtensionWillBeInstalled(
76      content::BrowserContext* browser_context,
77      const Extension* extension,
78      bool is_update,
79      bool from_ephemeral,
80      const std::string& old_name) OVERRIDE;
81  virtual void OnExtensionUninstalled(
82      content::BrowserContext* browser_context,
83      const Extension* extension,
84      extensions::UninstallReason reason) OVERRIDE;
85
86  // Handler for the signal from ExtensionSystem::ready().
87  void OnExtensionsReady();
88
89  // Starts a schema load for all extensions that use managed storage.
90  void LoadSchemas(scoped_ptr<ExtensionSet> added);
91
92  bool UsesManagedStorage(const Extension* extension) const;
93
94  // Loads the schemas of the |extensions| and passes a ComponentMap to
95  // Register().
96  static void LoadSchemasOnBlockingPool(scoped_ptr<ExtensionSet> extensions,
97                                        base::WeakPtr<ExtensionTracker> self);
98  void Register(const policy::ComponentMap* components);
99
100  Profile* profile_;
101  ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
102      extension_registry_observer_;
103  policy::SchemaRegistry* schema_registry_;
104  base::WeakPtrFactory<ExtensionTracker> weak_factory_;
105
106  DISALLOW_COPY_AND_ASSIGN(ExtensionTracker);
107};
108
109ManagedValueStoreCache::ExtensionTracker::ExtensionTracker(Profile* profile)
110    : profile_(profile),
111      extension_registry_observer_(this),
112      schema_registry_(policy::SchemaRegistryServiceFactory::GetForContext(
113                           profile)->registry()),
114      weak_factory_(this) {
115  extension_registry_observer_.Add(ExtensionRegistry::Get(profile_));
116  // Load schemas when the extension system is ready. It might be ready now.
117  ExtensionSystem::Get(profile_)->ready().Post(
118      FROM_HERE,
119      base::Bind(&ExtensionTracker::OnExtensionsReady,
120                 weak_factory_.GetWeakPtr()));
121}
122
123void ManagedValueStoreCache::ExtensionTracker::OnExtensionWillBeInstalled(
124    content::BrowserContext* browser_context,
125    const Extension* extension,
126    bool is_update,
127    bool from_ephemeral,
128    const std::string& old_name) {
129  // Some extensions are installed on the first run before the ExtensionSystem
130  // becomes ready. Wait until all of them are ready before registering the
131  // schemas of managed extensions, so that the policy loaders are reloaded at
132  // most once.
133  if (!ExtensionSystem::Get(profile_)->ready().is_signaled())
134    return;
135  scoped_ptr<ExtensionSet> added(new ExtensionSet);
136  added->Insert(extension);
137  LoadSchemas(added.Pass());
138}
139
140void ManagedValueStoreCache::ExtensionTracker::OnExtensionUninstalled(
141    content::BrowserContext* browser_context,
142    const Extension* extension,
143    extensions::UninstallReason reason) {
144  if (!ExtensionSystem::Get(profile_)->ready().is_signaled())
145    return;
146  if (extension && UsesManagedStorage(extension)) {
147    schema_registry_->UnregisterComponent(policy::PolicyNamespace(
148        policy::POLICY_DOMAIN_EXTENSIONS, extension->id()));
149  }
150}
151
152void ManagedValueStoreCache::ExtensionTracker::OnExtensionsReady() {
153  // Load schemas for all installed extensions.
154  LoadSchemas(
155      ExtensionRegistry::Get(profile_)->GenerateInstalledExtensionsSet());
156}
157
158void ManagedValueStoreCache::ExtensionTracker::LoadSchemas(
159    scoped_ptr<ExtensionSet> added) {
160  // Filter out extensions that don't use managed storage.
161  ExtensionSet::const_iterator it = added->begin();
162  while (it != added->end()) {
163    std::string to_remove;
164    if (!UsesManagedStorage(it->get()))
165      to_remove = (*it)->id();
166    ++it;
167    if (!to_remove.empty())
168      added->Remove(to_remove);
169  }
170
171  // Load the schema files in a background thread.
172  BrowserThread::PostBlockingPoolSequencedTask(
173      kLoadSchemasBackgroundTaskTokenName, FROM_HERE,
174      base::Bind(&ExtensionTracker::LoadSchemasOnBlockingPool,
175                 base::Passed(&added),
176                 weak_factory_.GetWeakPtr()));
177}
178
179bool ManagedValueStoreCache::ExtensionTracker::UsesManagedStorage(
180    const Extension* extension) const {
181  if (extension->manifest()->HasPath(manifest_keys::kStorageManagedSchema))
182    return true;
183
184  // TODO(joaodasilva): remove this by M35.
185  return extension->id() == kLegacyBrowserSupportExtensionId;
186}
187
188// static
189void ManagedValueStoreCache::ExtensionTracker::LoadSchemasOnBlockingPool(
190    scoped_ptr<ExtensionSet> extensions,
191    base::WeakPtr<ExtensionTracker> self) {
192  DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
193  scoped_ptr<policy::ComponentMap> components(new policy::ComponentMap);
194
195  for (ExtensionSet::const_iterator it = extensions->begin();
196       it != extensions->end(); ++it) {
197    std::string schema_file;
198    if (!(*it)->manifest()->GetString(
199            manifest_keys::kStorageManagedSchema, &schema_file)) {
200      // TODO(joaodasilva): Remove this. http://crbug.com/325349
201      (*components)[(*it)->id()] = policy::Schema();
202      continue;
203    }
204    // The extension should have been validated, so assume the schema exists
205    // and is valid.
206    std::string error;
207    policy::Schema schema =
208        StorageSchemaManifestHandler::GetSchema(it->get(), &error);
209    // If the schema is invalid then proceed with an empty schema. The extension
210    // will be listed in chrome://policy but won't be able to load any policies.
211    if (!schema.valid())
212      schema = policy::Schema();
213    (*components)[(*it)->id()] = schema;
214  }
215
216  BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
217                          base::Bind(&ExtensionTracker::Register, self,
218                                     base::Owned(components.release())));
219}
220
221void ManagedValueStoreCache::ExtensionTracker::Register(
222    const policy::ComponentMap* components) {
223  DCHECK_CURRENTLY_ON(BrowserThread::UI);
224  schema_registry_->RegisterComponents(policy::POLICY_DOMAIN_EXTENSIONS,
225                                       *components);
226
227  // The first SetReady() call is performed after the ExtensionSystem is ready,
228  // even if there are no managed extensions. It will trigger a loading of the
229  // initial policy for any managed extensions, and eventually the PolicyService
230  // will become ready for POLICY_DOMAIN_EXTENSIONS, and
231  // OnPolicyServiceInitialized() will be invoked.
232  // Subsequent calls to SetReady() are ignored.
233  schema_registry_->SetReady(policy::POLICY_DOMAIN_EXTENSIONS);
234}
235
236ManagedValueStoreCache::ManagedValueStoreCache(
237    BrowserContext* context,
238    const scoped_refptr<SettingsStorageFactory>& factory,
239    const scoped_refptr<SettingsObserverList>& observers)
240    : profile_(Profile::FromBrowserContext(context)),
241      policy_service_(policy::ProfilePolicyConnectorFactory::GetForProfile(
242                          profile_)->policy_service()),
243      storage_factory_(factory),
244      observers_(observers),
245      base_path_(profile_->GetPath().AppendASCII(
246          extensions::kManagedSettingsDirectoryName)) {
247  DCHECK_CURRENTLY_ON(BrowserThread::UI);
248
249  policy_service_->AddObserver(policy::POLICY_DOMAIN_EXTENSIONS, this);
250
251  extension_tracker_.reset(new ExtensionTracker(profile_));
252
253  if (policy_service_->IsInitializationComplete(
254          policy::POLICY_DOMAIN_EXTENSIONS)) {
255    OnPolicyServiceInitialized(policy::POLICY_DOMAIN_EXTENSIONS);
256  }
257}
258
259ManagedValueStoreCache::~ManagedValueStoreCache() {
260  DCHECK_CURRENTLY_ON(BrowserThread::FILE);
261  // Delete the PolicyValueStores on FILE.
262  store_map_.clear();
263}
264
265void ManagedValueStoreCache::ShutdownOnUI() {
266  DCHECK_CURRENTLY_ON(BrowserThread::UI);
267  policy_service_->RemoveObserver(policy::POLICY_DOMAIN_EXTENSIONS, this);
268  extension_tracker_.reset();
269}
270
271void ManagedValueStoreCache::RunWithValueStoreForExtension(
272    const StorageCallback& callback,
273    scoped_refptr<const Extension> extension) {
274  DCHECK_CURRENTLY_ON(BrowserThread::FILE);
275  callback.Run(GetStoreFor(extension->id()));
276}
277
278void ManagedValueStoreCache::DeleteStorageSoon(
279    const std::string& extension_id) {
280  DCHECK_CURRENTLY_ON(BrowserThread::FILE);
281  // It's possible that the store exists, but hasn't been loaded yet
282  // (because the extension is unloaded, for example). Open the database to
283  // clear it if it exists.
284  if (!HasStore(extension_id))
285    return;
286  GetStoreFor(extension_id)->DeleteStorage();
287  store_map_.erase(extension_id);
288}
289
290void ManagedValueStoreCache::OnPolicyServiceInitialized(
291    policy::PolicyDomain domain) {
292  DCHECK_CURRENTLY_ON(BrowserThread::UI);
293
294  if (domain != policy::POLICY_DOMAIN_EXTENSIONS)
295    return;
296
297  // The PolicyService now has all the initial policies ready. Send policy
298  // for all the managed extensions to their backing stores now.
299  policy::SchemaRegistry* registry =
300      policy::SchemaRegistryServiceFactory::GetForContext(profile_)->registry();
301  const policy::ComponentMap* map = registry->schema_map()->GetComponents(
302      policy::POLICY_DOMAIN_EXTENSIONS);
303  if (!map)
304    return;
305
306  const policy::PolicyMap empty_map;
307  for (policy::ComponentMap::const_iterator it = map->begin();
308       it != map->end(); ++it) {
309    const policy::PolicyNamespace ns(policy::POLICY_DOMAIN_EXTENSIONS,
310                                     it->first);
311    // If there is no policy for |ns| then this will clear the previous store,
312    // if there is one.
313    OnPolicyUpdated(ns, empty_map, policy_service_->GetPolicies(ns));
314  }
315}
316
317void ManagedValueStoreCache::OnPolicyUpdated(const policy::PolicyNamespace& ns,
318                                             const policy::PolicyMap& previous,
319                                             const policy::PolicyMap& current) {
320  DCHECK_CURRENTLY_ON(BrowserThread::UI);
321
322  if (!policy_service_->IsInitializationComplete(
323           policy::POLICY_DOMAIN_EXTENSIONS)) {
324    // OnPolicyUpdated is called whenever a policy changes, but it doesn't
325    // mean that all the policy providers are ready; wait until we get the
326    // final policy values before passing them to the store.
327    return;
328  }
329
330  BrowserThread::PostTask(
331      BrowserThread::FILE, FROM_HERE,
332      base::Bind(&ManagedValueStoreCache::UpdatePolicyOnFILE,
333                 base::Unretained(this),
334                 ns.component_id,
335                 base::Passed(current.DeepCopy())));
336}
337
338void ManagedValueStoreCache::UpdatePolicyOnFILE(
339    const std::string& extension_id,
340    scoped_ptr<policy::PolicyMap> current_policy) {
341  DCHECK_CURRENTLY_ON(BrowserThread::FILE);
342
343  if (!HasStore(extension_id) && current_policy->empty()) {
344    // Don't create the store now if there are no policies configured for this
345    // extension. If the extension uses the storage.managed API then the store
346    // will be created at RunWithValueStoreForExtension().
347    return;
348  }
349
350  GetStoreFor(extension_id)->SetCurrentPolicy(*current_policy);
351}
352
353PolicyValueStore* ManagedValueStoreCache::GetStoreFor(
354    const std::string& extension_id) {
355  DCHECK_CURRENTLY_ON(BrowserThread::FILE);
356
357  PolicyValueStoreMap::iterator it = store_map_.find(extension_id);
358  if (it != store_map_.end())
359    return it->second.get();
360
361  // Create the store now, and serve the cached policy until the PolicyService
362  // sends updated values.
363  PolicyValueStore* store = new PolicyValueStore(
364      extension_id,
365      observers_,
366      make_scoped_ptr(storage_factory_->Create(base_path_, extension_id)));
367  store_map_[extension_id] = make_linked_ptr(store);
368
369  return store;
370}
371
372bool ManagedValueStoreCache::HasStore(const std::string& extension_id) const {
373  // TODO(joaodasilva): move this check to a ValueStore method.
374  return base::DirectoryExists(base_path_.AppendASCII(extension_id));
375}
376
377}  // namespace extensions
378