app_pack_updater.cc revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
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/chromeos/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/policy/enterprise_install_attributes.h"
17#include "chrome/browser/chromeos/settings/cros_settings.h"
18#include "chrome/browser/chromeos/settings/cros_settings_names.h"
19#include "chrome/browser/extensions/crx_installer.h"
20#include "chrome/browser/extensions/external_loader.h"
21#include "chrome/browser/extensions/external_provider_impl.h"
22#include "chrome/browser/extensions/updater/extension_downloader.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
31using content::BrowserThread;
32using file_util::FileEnumerator;
33
34namespace policy {
35
36namespace {
37
38// Directory where the AppPack extensions are cached.
39const char kAppPackCacheDir[] = "/var/cache/app_pack";
40
41// File name extension for CRX files (not case sensitive).
42const char kCRXFileExtension[] = ".crx";
43
44}  // namespace
45
46const char AppPackUpdater::kExtensionId[] = "extension-id";
47const 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.
51class 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
82AppPackUpdater::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
102AppPackUpdater::~AppPackUpdater() {
103  chromeos::CrosSettings::Get()->RemoveSettingsObserver(
104      chromeos::kAppPack, this);
105}
106
107extensions::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
123void 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
133void 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
146void 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
173void 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(base::FilePath());
211  }
212
213  CheckCacheNow();
214}
215
216void 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
229void 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
241void AppPackUpdater::BlockingCheckCacheInternal(
242    const std::set<std::string>* valid_ids,
243    CacheEntryMap* entries) {
244  // Start by verifying that the cache dir exists.
245  base::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 (base::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(base::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
337void 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(base::FilePath(it->second.path));
344  else
345    SetScreenSaverPath(base::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
354void 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
395void 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
411void 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
425void AppPackUpdater::OnExtensionDownloadFinished(
426    const std::string& id,
427    const base::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                              base::FilePath(path),
439                              std::string(version)));
440}
441
442void 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
451bool 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
457bool 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
469void AppPackUpdater::BlockingInstallCacheEntry(
470    base::WeakPtr<AppPackUpdater> app_pack_updater,
471    const std::string& id,
472    const base::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  base::FilePath cache_dir(kAppPackCacheDir);
484  base::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
517void 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(base::FilePath(path));
529  } else {
530    UpdateExtensionLoader();
531  }
532}
533
534void AppPackUpdater::OnDamagedFileDetected(const base::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
556void 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
563void AppPackUpdater::SetScreenSaverPath(const base::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
573