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