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/memory/ref_counted.h"
9#include "base/sequenced_task_runner.h"
10#include "base/threading/sequenced_worker_pool.h"
11#include "base/values.h"
12#include "chrome/browser/chromeos/policy/enterprise_install_attributes.h"
13#include "chrome/browser/chromeos/settings/cros_settings.h"
14#include "chrome/browser/extensions/external_loader.h"
15#include "chrome/browser/extensions/external_provider_impl.h"
16#include "chromeos/settings/cros_settings_names.h"
17#include "content/public/browser/browser_thread.h"
18
19using content::BrowserThread;
20
21namespace policy {
22
23namespace {
24
25// Directory where the AppPack extensions are cached.
26const char kAppPackCacheDir[] = "/var/cache/app_pack";
27
28}  // namespace
29
30// A custom extensions::ExternalLoader that the AppPackUpdater creates and uses
31// to publish AppPack updates to the extensions system.
32class AppPackExternalLoader
33    : public extensions::ExternalLoader,
34      public base::SupportsWeakPtr<AppPackExternalLoader> {
35 public:
36  AppPackExternalLoader() {}
37
38  // Used by the AppPackUpdater to update the current list of extensions.
39  // The format of |prefs| is detailed in the extensions::ExternalLoader/
40  // Provider headers.
41  void SetCurrentAppPackExtensions(scoped_ptr<base::DictionaryValue> prefs) {
42    app_pack_prefs_.Swap(prefs.get());
43    StartLoading();
44  }
45
46  // Implementation of extensions::ExternalLoader:
47  virtual void StartLoading() OVERRIDE {
48    prefs_.reset(app_pack_prefs_.DeepCopy());
49    VLOG(1) << "AppPack extension loader publishing "
50            << app_pack_prefs_.size() << " crx files.";
51    LoadFinished();
52  }
53
54 protected:
55  virtual ~AppPackExternalLoader() {}
56
57 private:
58  base::DictionaryValue app_pack_prefs_;
59
60  DISALLOW_COPY_AND_ASSIGN(AppPackExternalLoader);
61};
62
63AppPackUpdater::AppPackUpdater(net::URLRequestContextGetter* request_context,
64                               EnterpriseInstallAttributes* install_attributes)
65    : created_extension_loader_(false),
66      install_attributes_(install_attributes),
67      external_cache_(base::FilePath(kAppPackCacheDir),
68                      request_context,
69                      content::BrowserThread::GetBlockingPool()->
70                          GetSequencedTaskRunnerWithShutdownBehavior(
71                              content::BrowserThread::GetBlockingPool()->
72                                  GetSequenceToken(),
73                              base::SequencedWorkerPool::SKIP_ON_SHUTDOWN),
74                      this,
75                      false,
76                      false),
77      weak_ptr_factory_(this) {
78  app_pack_subscription_ = chromeos::CrosSettings::Get()->AddSettingsObserver(
79      chromeos::kAppPack,
80      base::Bind(&AppPackUpdater::AppPackChanged, base::Unretained(this)));
81
82  if (install_attributes_->GetMode() == DEVICE_MODE_RETAIL_KIOSK) {
83    // Already in Kiosk mode, start loading.
84    BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
85                            base::Bind(&AppPackUpdater::LoadPolicy,
86                                       weak_ptr_factory_.GetWeakPtr()));
87  } else {
88    // Linger until the device switches to DEVICE_MODE_RETAIL_KIOSK and the
89    // app pack device setting appears.
90  }
91}
92
93AppPackUpdater::~AppPackUpdater() {
94}
95
96extensions::ExternalLoader* AppPackUpdater::CreateExternalLoader() {
97  if (created_extension_loader_) {
98    NOTREACHED();
99    return NULL;
100  }
101  created_extension_loader_ = true;
102  AppPackExternalLoader* loader = new AppPackExternalLoader();
103  extension_loader_ = loader->AsWeakPtr();
104
105  // The cache may have been already checked. In that case, load the current
106  // extensions into the loader immediately.
107  UpdateExtensionLoader();
108
109  return loader;
110}
111
112void AppPackUpdater::SetScreenSaverUpdateCallback(
113    const AppPackUpdater::ScreenSaverUpdateCallback& callback) {
114  screen_saver_update_callback_ = callback;
115  if (!screen_saver_update_callback_.is_null() && !screen_saver_path_.empty()) {
116    BrowserThread::PostTask(
117        BrowserThread::UI, FROM_HERE,
118        base::Bind(screen_saver_update_callback_, screen_saver_path_));
119  }
120}
121
122void AppPackUpdater::AppPackChanged() {
123  if (install_attributes_->GetMode() == DEVICE_MODE_RETAIL_KIOSK)
124    LoadPolicy();
125}
126
127void AppPackUpdater::LoadPolicy() {
128  chromeos::CrosSettings* settings = chromeos::CrosSettings::Get();
129  if (chromeos::CrosSettingsProvider::TRUSTED != settings->PrepareTrustedValues(
130          base::Bind(&AppPackUpdater::LoadPolicy,
131                     weak_ptr_factory_.GetWeakPtr()))) {
132    return;
133  }
134
135  scoped_ptr<base::DictionaryValue> prefs(new base::DictionaryValue());
136  const base::Value* value = settings->GetPref(chromeos::kAppPack);
137  const base::ListValue* list = NULL;
138  if (value && value->GetAsList(&list)) {
139    for (base::ListValue::const_iterator it = list->begin();
140         it != list->end(); ++it) {
141      base::DictionaryValue* dict = NULL;
142      if (!(*it)->GetAsDictionary(&dict)) {
143        LOG(WARNING) << "AppPack entry is not a dictionary, ignoring.";
144        continue;
145      }
146      std::string id;
147      std::string update_url;
148      if (dict->GetString(chromeos::kAppPackKeyExtensionId, &id) &&
149          dict->GetString(chromeos::kAppPackKeyUpdateUrl, &update_url)) {
150        base::DictionaryValue* entry = new base::DictionaryValue();
151        entry->SetString(extensions::ExternalProviderImpl::kExternalUpdateUrl,
152                         update_url);
153        prefs->Set(id, entry);
154      } else {
155        LOG(WARNING) << "Failed to read required fields for an AppPack entry, "
156                     << "ignoring.";
157      }
158    }
159  }
160
161  VLOG(1) << "Refreshed AppPack policy, got " << prefs->size()
162          << " entries.";
163
164  value = settings->GetPref(chromeos::kScreenSaverExtensionId);
165  if (!value || !value->GetAsString(&screen_saver_id_)) {
166    screen_saver_id_.clear();
167    SetScreenSaverPath(base::FilePath());
168  }
169
170  external_cache_.UpdateExtensionsList(prefs.Pass());
171}
172
173void AppPackUpdater::OnExtensionListsUpdated(
174    const base::DictionaryValue* prefs) {
175  std::string crx_path;
176  const base::DictionaryValue* screen_saver = NULL;
177  if (prefs->GetDictionary(screen_saver_id_, &screen_saver)) {
178    screen_saver->GetString(extensions::ExternalProviderImpl::kExternalCrx,
179                            &crx_path);
180  }
181  if (!crx_path.empty())
182    SetScreenSaverPath(base::FilePath(crx_path));
183  else
184    SetScreenSaverPath(base::FilePath());
185
186  UpdateExtensionLoader();
187}
188
189void AppPackUpdater::UpdateExtensionLoader() {
190  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
191  if (!extension_loader_) {
192    VLOG(1) << "No AppPack loader created yet, not pushing extensions.";
193    return;
194  }
195
196  scoped_ptr<base::DictionaryValue> prefs(
197      external_cache_.cached_extensions()->DeepCopy());
198
199  // The screensaver isn't installed into the Profile.
200  prefs->Remove(screen_saver_id_, NULL);
201
202  extension_loader_->SetCurrentAppPackExtensions(prefs.Pass());
203}
204
205void AppPackUpdater::OnDamagedFileDetected(const base::FilePath& path) {
206  external_cache_.OnDamagedFileDetected(path);
207}
208
209void AppPackUpdater::SetScreenSaverPath(const base::FilePath& path) {
210  // Don't invoke the callback if the path isn't changing.
211  if (path != screen_saver_path_) {
212    screen_saver_path_ = path;
213    if (!screen_saver_update_callback_.is_null())
214      screen_saver_update_callback_.Run(screen_saver_path_);
215  }
216}
217
218}  // namespace policy
219