Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(64)

Side by Side Diff: chrome/browser/policy/app_pack_updater.cc

Issue 12189011: Split up chrome/browser/policy subdirectory (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Rebase. Created 7 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
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/policy/app_pack_updater.h"
6
7 #include "base/bind.h"
8 #include "base/bind_helpers.h"
9 #include "base/file_util.h"
10 #include "base/location.h"
11 #include "base/stl_util.h"
12 #include "base/string_util.h"
13 #include "base/values.h"
14 #include "base/version.h"
15 #include "chrome/browser/browser_process.h"
16 #include "chrome/browser/chromeos/settings/cros_settings.h"
17 #include "chrome/browser/chromeos/settings/cros_settings_names.h"
18 #include "chrome/browser/extensions/crx_installer.h"
19 #include "chrome/browser/extensions/external_loader.h"
20 #include "chrome/browser/extensions/external_provider_impl.h"
21 #include "chrome/browser/extensions/updater/extension_downloader.h"
22 #include "chrome/browser/policy/enterprise_install_attributes.h"
23 #include "chrome/common/chrome_notification_types.h"
24 #include "chrome/common/extensions/extension.h"
25 #include "chrome/common/extensions/extension_constants.h"
26 #include "content/public/browser/browser_thread.h"
27 #include "content/public/browser/notification_details.h"
28 #include "content/public/browser/notification_service.h"
29 #include "content/public/browser/notification_source.h"
30
31 using content::BrowserThread;
32 using file_util::FileEnumerator;
33
34 namespace policy {
35
36 namespace {
37
38 // Directory where the AppPack extensions are cached.
39 const char kAppPackCacheDir[] = "/var/cache/app_pack";
40
41 // File name extension for CRX files (not case sensitive).
42 const char kCRXFileExtension[] = ".crx";
43
44 } // namespace
45
46 const char AppPackUpdater::kExtensionId[] = "extension-id";
47 const char AppPackUpdater::kUpdateUrl[] = "update-url";
48
49 // A custom extensions::ExternalLoader that the AppPackUpdater creates and uses
50 // to publish AppPack updates to the extensions system.
51 class AppPackExternalLoader
52 : public extensions::ExternalLoader,
53 public base::SupportsWeakPtr<AppPackExternalLoader> {
54 public:
55 AppPackExternalLoader() {}
56
57 // Used by the AppPackUpdater to update the current list of extensions.
58 // The format of |prefs| is detailed in the extensions::ExternalLoader/
59 // Provider headers.
60 void SetCurrentAppPackExtensions(scoped_ptr<base::DictionaryValue> prefs) {
61 app_pack_prefs_.Swap(prefs.get());
62 StartLoading();
63 }
64
65 // Implementation of extensions::ExternalLoader:
66 virtual void StartLoading() OVERRIDE {
67 prefs_.reset(app_pack_prefs_.DeepCopy());
68 VLOG(1) << "AppPack extension loader publishing "
69 << app_pack_prefs_.size() << " crx files.";
70 LoadFinished();
71 }
72
73 protected:
74 virtual ~AppPackExternalLoader() {}
75
76 private:
77 base::DictionaryValue app_pack_prefs_;
78
79 DISALLOW_COPY_AND_ASSIGN(AppPackExternalLoader);
80 };
81
82 AppPackUpdater::AppPackUpdater(net::URLRequestContextGetter* request_context,
83 EnterpriseInstallAttributes* install_attributes)
84 : ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)),
85 initialized_(false),
86 created_extension_loader_(false),
87 request_context_(request_context),
88 install_attributes_(install_attributes) {
89 chromeos::CrosSettings::Get()->AddSettingsObserver(chromeos::kAppPack, this);
90
91 if (install_attributes_->GetMode() == DEVICE_MODE_KIOSK) {
92 // Already in Kiosk mode, start loading.
93 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
94 base::Bind(&AppPackUpdater::Init,
95 weak_ptr_factory_.GetWeakPtr()));
96 } else {
97 // Linger until the device switches to DEVICE_MODE_KIOSK and the app pack
98 // device setting appears.
99 }
100 }
101
102 AppPackUpdater::~AppPackUpdater() {
103 chromeos::CrosSettings::Get()->RemoveSettingsObserver(
104 chromeos::kAppPack, this);
105 }
106
107 extensions::ExternalLoader* AppPackUpdater::CreateExternalLoader() {
108 if (created_extension_loader_) {
109 NOTREACHED();
110 return NULL;
111 }
112 created_extension_loader_ = true;
113 AppPackExternalLoader* loader = new AppPackExternalLoader();
114 extension_loader_ = loader->AsWeakPtr();
115
116 // The cache may have been already checked. In that case, load the current
117 // extensions into the loader immediately.
118 UpdateExtensionLoader();
119
120 return loader;
121 }
122
123 void AppPackUpdater::SetScreenSaverUpdateCallback(
124 const AppPackUpdater::ScreenSaverUpdateCallback& callback) {
125 screen_saver_update_callback_ = callback;
126 if (!screen_saver_update_callback_.is_null() && !screen_saver_path_.empty()) {
127 BrowserThread::PostTask(
128 BrowserThread::UI, FROM_HERE,
129 base::Bind(screen_saver_update_callback_, screen_saver_path_));
130 }
131 }
132
133 void AppPackUpdater::Init() {
134 if (initialized_)
135 return;
136
137 initialized_ = true;
138 worker_pool_token_ = BrowserThread::GetBlockingPool()->GetSequenceToken();
139 notification_registrar_.Add(
140 this,
141 chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR,
142 content::NotificationService::AllBrowserContextsAndSources());
143 LoadPolicy();
144 }
145
146 void AppPackUpdater::Observe(int type,
147 const content::NotificationSource& source,
148 const content::NotificationDetails& details) {
149 switch (type) {
150 case chrome::NOTIFICATION_SYSTEM_SETTING_CHANGED:
151 DCHECK_EQ(chromeos::kAppPack,
152 *content::Details<const std::string>(details).ptr());
153 if (install_attributes_->GetMode() == DEVICE_MODE_KIOSK) {
154 if (!initialized_)
155 Init();
156 else
157 LoadPolicy();
158 }
159 break;
160
161 case chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR: {
162 extensions::CrxInstaller* installer =
163 content::Source<extensions::CrxInstaller>(source).ptr();
164 OnDamagedFileDetected(installer->source_file());
165 break;
166 }
167
168 default:
169 NOTREACHED();
170 }
171 }
172
173 void AppPackUpdater::LoadPolicy() {
174 chromeos::CrosSettings* settings = chromeos::CrosSettings::Get();
175 if (chromeos::CrosSettingsProvider::TRUSTED != settings->PrepareTrustedValues(
176 base::Bind(&AppPackUpdater::LoadPolicy,
177 weak_ptr_factory_.GetWeakPtr()))) {
178 return;
179 }
180
181 app_pack_extensions_.clear();
182 const base::Value* value = settings->GetPref(chromeos::kAppPack);
183 const base::ListValue* list = NULL;
184 if (value && value->GetAsList(&list)) {
185 for (base::ListValue::const_iterator it = list->begin();
186 it != list->end(); ++it) {
187 base::DictionaryValue* dict = NULL;
188 if (!(*it)->GetAsDictionary(&dict)) {
189 LOG(WARNING) << "AppPack entry is not a dictionary, ignoring.";
190 continue;
191 }
192 std::string id;
193 std::string update_url;
194 if (dict->GetString(kExtensionId, &id) &&
195 dict->GetString(kUpdateUrl, &update_url)) {
196 app_pack_extensions_[id] = update_url;
197 } else {
198 LOG(WARNING) << "Failed to read required fields for an AppPack entry, "
199 << "ignoring.";
200 }
201 }
202 }
203
204 VLOG(1) << "Refreshed AppPack policy, got " << app_pack_extensions_.size()
205 << " entries.";
206
207 value = settings->GetPref(chromeos::kScreenSaverExtensionId);
208 if (!value || !value->GetAsString(&screen_saver_id_)) {
209 screen_saver_id_.clear();
210 SetScreenSaverPath(FilePath());
211 }
212
213 CheckCacheNow();
214 }
215
216 void AppPackUpdater::CheckCacheNow() {
217 std::set<std::string>* valid_ids = new std::set<std::string>();
218 for (PolicyEntryMap::iterator it = app_pack_extensions_.begin();
219 it != app_pack_extensions_.end(); ++it) {
220 valid_ids->insert(it->first);
221 }
222 PostBlockingTask(FROM_HERE,
223 base::Bind(&AppPackUpdater::BlockingCheckCache,
224 weak_ptr_factory_.GetWeakPtr(),
225 base::Owned(valid_ids)));
226 }
227
228 // static
229 void AppPackUpdater::BlockingCheckCache(
230 base::WeakPtr<AppPackUpdater> app_pack_updater,
231 const std::set<std::string>* valid_ids) {
232 CacheEntryMap* entries = new CacheEntryMap();
233 BlockingCheckCacheInternal(valid_ids, entries);
234 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
235 base::Bind(&AppPackUpdater::OnCacheUpdated,
236 app_pack_updater,
237 base::Owned(entries)));
238 }
239
240 // static
241 void AppPackUpdater::BlockingCheckCacheInternal(
242 const std::set<std::string>* valid_ids,
243 CacheEntryMap* entries) {
244 // Start by verifying that the cache dir exists.
245 FilePath dir(kAppPackCacheDir);
246 if (!file_util::DirectoryExists(dir)) {
247 // Create it now.
248 if (!file_util::CreateDirectory(dir))
249 LOG(ERROR) << "Failed to create AppPack directory at " << dir.value();
250 // Nothing else to do.
251 return;
252 }
253
254 // Enumerate all the files in the cache |dir|, including directories
255 // and symlinks. Each unrecognized file will be erased.
256 int types = FileEnumerator::FILES | FileEnumerator::DIRECTORIES |
257 FileEnumerator::SHOW_SYM_LINKS;
258 FileEnumerator enumerator(dir, false /* recursive */, types);
259
260 for (FilePath path = enumerator.Next();
261 !path.empty(); path = enumerator.Next()) {
262 FileEnumerator::FindInfo info;
263 enumerator.GetFindInfo(&info);
264 std::string basename = path.BaseName().value();
265
266 if (FileEnumerator::IsDirectory(info) ||
267 file_util::IsLink(FileEnumerator::GetFilename(info))) {
268 LOG(ERROR) << "Erasing bad file in AppPack directory: " << basename;
269 file_util::Delete(path, true /* recursive */);
270 continue;
271 }
272
273 // crx files in the cache are named <extension-id>-<version>.crx.
274 std::string id;
275 std::string version;
276
277 if (EndsWith(basename, kCRXFileExtension, false /* case-sensitive */)) {
278 size_t n = basename.find('-');
279 if (n != std::string::npos && n + 1 < basename.size() - 4) {
280 id = basename.substr(0, n);
281 // Size of |version| = total size - "<id>" - "-" - ".crx"
282 version = basename.substr(n + 1, basename.size() - 5 - id.size());
283 }
284 }
285
286 if (!extensions::Extension::IdIsValid(id)) {
287 LOG(ERROR) << "Bad AppPack extension id in cache: " << id;
288 id.clear();
289 } else if (!ContainsKey(*valid_ids, id)) {
290 LOG(WARNING) << basename << " is in the cache but is not configured by "
291 << "the AppPack policy, and will be erased.";
292 id.clear();
293 }
294
295 if (!Version(version).IsValid()) {
296 LOG(ERROR) << "Bad AppPack extension version in cache: " << version;
297 version.clear();
298 }
299
300 if (id.empty() || version.empty()) {
301 LOG(ERROR) << "Invalid file in AppPack cache, erasing: " << basename;
302 file_util::Delete(path, true /* recursive */);
303 continue;
304 }
305
306 // Enforce a lower-case id.
307 id = StringToLowerASCII(id);
308
309 // File seems good so far. Make sure there isn't another entry with the
310 // same id but a different version.
311
312 if (ContainsKey(*entries, id)) {
313 LOG(ERROR) << "Found two AppPack files for the same extension, will "
314 "erase the oldest version";
315 CacheEntry& entry = (*entries)[id];
316 Version vEntry(entry.cached_version);
317 Version vCurrent(version);
318 DCHECK(vEntry.IsValid());
319 DCHECK(vCurrent.IsValid());
320 if (vEntry.CompareTo(vCurrent) < 0) {
321 file_util::Delete(FilePath(entry.path), true /* recursive */);
322 entry.path = path.value();
323 } else {
324 file_util::Delete(path, true /* recursive */);
325 }
326 continue;
327 }
328
329 // This is the only file for this |id| so far, add it.
330
331 CacheEntry& entry = (*entries)[id];
332 entry.path = path.value();
333 entry.cached_version = version;
334 }
335 }
336
337 void AppPackUpdater::OnCacheUpdated(CacheEntryMap* cache_entries) {
338 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
339 cached_extensions_.swap(*cache_entries);
340
341 CacheEntryMap::iterator it = cached_extensions_.find(screen_saver_id_);
342 if (it != cached_extensions_.end())
343 SetScreenSaverPath(FilePath(it->second.path));
344 else
345 SetScreenSaverPath(FilePath());
346
347 VLOG(1) << "Updated AppPack cache, there are " << cached_extensions_.size()
348 << " extensions cached and "
349 << (screen_saver_path_.empty() ? "no" : "the") << " screensaver";
350 UpdateExtensionLoader();
351 DownloadMissingExtensions();
352 }
353
354 void AppPackUpdater::UpdateExtensionLoader() {
355 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
356 if (!extension_loader_) {
357 VLOG(1) << "No AppPack loader created yet, not pushing extensions.";
358 return;
359 }
360
361 // Build a DictionaryValue with the format that
362 // extensions::ExternalProviderImpl expects, containing info about the locally
363 // cached extensions.
364
365 scoped_ptr<base::DictionaryValue> prefs(new base::DictionaryValue());
366 for (CacheEntryMap::iterator it = cached_extensions_.begin();
367 it != cached_extensions_.end(); ++it) {
368 const std::string& id = it->first;
369 // The screensaver isn't installed into the Profile.
370 if (id == screen_saver_id_)
371 continue;
372
373 base::DictionaryValue* dict = new base::DictionaryValue();
374 dict->SetString(extensions::ExternalProviderImpl::kExternalCrx,
375 it->second.path);
376 dict->SetString(extensions::ExternalProviderImpl::kExternalVersion,
377 it->second.cached_version);
378
379 // Include this optional flag if the extension's update url is the Webstore.
380 PolicyEntryMap::iterator policy_entry = app_pack_extensions_.find(id);
381 if (policy_entry != app_pack_extensions_.end() &&
382 extension_urls::IsWebstoreUpdateUrl(
383 GURL(policy_entry->second))) {
384 dict->SetBoolean(extensions::ExternalProviderImpl::kIsFromWebstore, true);
385 }
386
387 prefs->Set(it->first, dict);
388
389 VLOG(1) << "Updating AppPack extension loader, added " << it->second.path;
390 }
391
392 extension_loader_->SetCurrentAppPackExtensions(prefs.Pass());
393 }
394
395 void AppPackUpdater::DownloadMissingExtensions() {
396 // Check for updates for all extensions configured by the policy. Some of
397 // them may already be in the cache; only those with updated version will be
398 // downloaded, in that case.
399 if (!downloader_.get()) {
400 downloader_.reset(new extensions::ExtensionDownloader(this,
401 request_context_));
402 }
403 for (PolicyEntryMap::iterator it = app_pack_extensions_.begin();
404 it != app_pack_extensions_.end(); ++it) {
405 downloader_->AddPendingExtension(it->first, GURL(it->second), 0);
406 }
407 VLOG(1) << "Downloading AppPack update manifest now";
408 downloader_->StartAllPending();
409 }
410
411 void AppPackUpdater::OnExtensionDownloadFailed(
412 const std::string& id,
413 extensions::ExtensionDownloaderDelegate::Error error,
414 const extensions::ExtensionDownloaderDelegate::PingResult& ping_result,
415 const std::set<int>& request_ids) {
416 if (error == NO_UPDATE_AVAILABLE) {
417 if (!ContainsKey(cached_extensions_, id))
418 LOG(ERROR) << "AppPack extension " << id << " not found on update server";
419 } else {
420 LOG(ERROR) << "AppPack failed to download extension " << id
421 << ", error " << error;
422 }
423 }
424
425 void AppPackUpdater::OnExtensionDownloadFinished(
426 const std::string& id,
427 const FilePath& path,
428 const GURL& download_url,
429 const std::string& version,
430 const extensions::ExtensionDownloaderDelegate::PingResult& ping_result,
431 const std::set<int>& request_ids) {
432 // The explicit copy ctors are to make sure that Bind() binds a copy and not
433 // a reference to the arguments.
434 PostBlockingTask(FROM_HERE,
435 base::Bind(&AppPackUpdater::BlockingInstallCacheEntry,
436 weak_ptr_factory_.GetWeakPtr(),
437 std::string(id),
438 FilePath(path),
439 std::string(version)));
440 }
441
442 void AppPackUpdater::OnBlacklistDownloadFinished(
443 const std::string& data,
444 const std::string& package_hash,
445 const std::string& version,
446 const extensions::ExtensionDownloaderDelegate::PingResult& ping_result,
447 const std::set<int>& request_ids) {
448 NOTREACHED();
449 }
450
451 bool AppPackUpdater::IsExtensionPending(const std::string& id) {
452 // Pending means that there is no installed version yet.
453 return ContainsKey(app_pack_extensions_, id) &&
454 !ContainsKey(cached_extensions_, id);
455 }
456
457 bool AppPackUpdater::GetExtensionExistingVersion(const std::string& id,
458 std::string* version) {
459 if (!ContainsKey(app_pack_extensions_, id) ||
460 !ContainsKey(cached_extensions_, id)) {
461 return false;
462 }
463
464 *version = cached_extensions_[id].cached_version;
465 return true;
466 }
467
468 // static
469 void AppPackUpdater::BlockingInstallCacheEntry(
470 base::WeakPtr<AppPackUpdater> app_pack_updater,
471 const std::string& id,
472 const FilePath& path,
473 const std::string& version) {
474 Version version_validator(version);
475 if (!version_validator.IsValid()) {
476 LOG(ERROR) << "AppPack downloaded extension " << id << " but got bad "
477 << "version: " << version;
478 file_util::Delete(path, true /* recursive */);
479 return;
480 }
481
482 std::string basename = id + "-" + version + kCRXFileExtension;
483 FilePath cache_dir(kAppPackCacheDir);
484 FilePath cached_crx_path = cache_dir.Append(basename);
485
486 if (file_util::PathExists(cached_crx_path)) {
487 LOG(WARNING) << "AppPack downloaded a crx whose filename will overwrite "
488 << "an existing cached crx.";
489 file_util::Delete(cached_crx_path, true /* recursive */);
490 }
491
492 if (!file_util::DirectoryExists(cache_dir)) {
493 LOG(ERROR) << "AppPack cache directory does not exist, creating now: "
494 << cache_dir.value();
495 if (!file_util::CreateDirectory(cache_dir)) {
496 LOG(ERROR) << "Failed to create the AppPack cache dir!";
497 file_util::Delete(path, true /* recursive */);
498 return;
499 }
500 }
501
502 if (!file_util::Move(path, cached_crx_path)) {
503 LOG(ERROR) << "Failed to move AppPack crx from " << path.value()
504 << " to " << cached_crx_path.value();
505 file_util::Delete(path, true /* recursive */);
506 return;
507 }
508
509 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
510 base::Bind(&AppPackUpdater::OnCacheEntryInstalled,
511 app_pack_updater,
512 std::string(id),
513 cached_crx_path.value(),
514 std::string(version)));
515 }
516
517 void AppPackUpdater::OnCacheEntryInstalled(const std::string& id,
518 const std::string& path,
519 const std::string& version) {
520 VLOG(1) << "AppPack installed a new extension in the cache: " << path;
521 // Add to the list of cached extensions.
522 CacheEntry& entry = cached_extensions_[id];
523 entry.path = path;
524 entry.cached_version = version;
525
526 if (id == screen_saver_id_) {
527 VLOG(1) << "AppPack got the screen saver extension at " << path;
528 SetScreenSaverPath(FilePath(path));
529 } else {
530 UpdateExtensionLoader();
531 }
532 }
533
534 void AppPackUpdater::OnDamagedFileDetected(const FilePath& path) {
535 // Search for |path| in |cached_extensions_|, and delete it if found.
536 for (CacheEntryMap::iterator it = cached_extensions_.begin();
537 it != cached_extensions_.end(); ++it) {
538 if (it->second.path == path.value()) {
539 LOG(ERROR) << "AppPack extension at " << path.value() << " failed to "
540 << "install, deleting it.";
541 cached_extensions_.erase(it);
542 UpdateExtensionLoader();
543
544 // The file will be downloaded again on the next restart.
545 BrowserThread::PostTask(
546 BrowserThread::FILE, FROM_HERE,
547 base::Bind(base::IgnoreResult(file_util::Delete), path, true));
548
549 // Don't try to DownloadMissingExtensions() from here,
550 // since it can cause a fail/retry loop.
551 break;
552 }
553 }
554 }
555
556 void AppPackUpdater::PostBlockingTask(const tracked_objects::Location& location,
557 const base::Closure& task) {
558 BrowserThread::GetBlockingPool()->PostSequencedWorkerTaskWithShutdownBehavior(
559 worker_pool_token_, location, task,
560 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN);
561 }
562
563 void AppPackUpdater::SetScreenSaverPath(const FilePath& path) {
564 // Don't invoke the callback if the path isn't changing.
565 if (path != screen_saver_path_) {
566 screen_saver_path_ = path;
567 if (!screen_saver_update_callback_.is_null())
568 screen_saver_update_callback_.Run(screen_saver_path_);
569 }
570 }
571
572 } // namespace policy
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698