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