Upstream version 11.39.250.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / chromeos / app_mode / kiosk_external_updater.cc
1 // Copyright 2014 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/app_mode/kiosk_external_updater.h"
6
7 #include "base/bind.h"
8 #include "base/files/file_enumerator.h"
9 #include "base/files/file_util.h"
10 #include "base/json/json_file_value_serializer.h"
11 #include "base/location.h"
12 #include "base/logging.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "base/version.h"
15 #include "chrome/browser/chromeos/app_mode/kiosk_app_manager.h"
16 #include "chrome/browser/chromeos/ui/kiosk_external_update_notification.h"
17 #include "chrome/browser/extensions/sandboxed_unpacker.h"
18 #include "chrome/common/chrome_version_info.h"
19 #include "content/public/browser/browser_thread.h"
20 #include "extensions/common/extension.h"
21 #include "grit/chromium_strings.h"
22 #include "grit/generated_resources.h"
23 #include "ui/base/l10n/l10n_util.h"
24 #include "ui/base/resource/resource_bundle.h"
25
26 namespace chromeos {
27
28 namespace {
29
30 const char kExternalUpdateManifest[] = "external_update.json";
31 const char kExternalCrx[] = "external_crx";
32 const char kExternalVersion[] = "external_version";
33
34 void ParseExternalUpdateManifest(
35     const base::FilePath& external_update_dir,
36     base::DictionaryValue* parsed_manifest,
37     KioskExternalUpdater::ExternalUpdateErrorCode* error_code) {
38   base::FilePath manifest =
39       external_update_dir.AppendASCII(kExternalUpdateManifest);
40   if (!base::PathExists(manifest)) {
41     *error_code = KioskExternalUpdater::ERROR_NO_MANIFEST;
42     return;
43   }
44
45   JSONFileValueSerializer serializer(manifest);
46   std::string error_msg;
47   base::Value* extensions = serializer.Deserialize(NULL, &error_msg);
48   if (!extensions) {
49     *error_code = KioskExternalUpdater::ERROR_INVALID_MANIFEST;
50     return;
51   }
52
53   base::DictionaryValue* dict_value = NULL;
54   if (!extensions->GetAsDictionary(&dict_value)) {
55     *error_code = KioskExternalUpdater::ERROR_INVALID_MANIFEST;
56     return;
57   }
58
59   parsed_manifest->Swap(dict_value);
60   *error_code = KioskExternalUpdater::ERROR_NONE;
61 }
62
63 // Copies |external_crx_file| to |temp_crx_file|, and removes |temp_dir|
64 // created for unpacking |external_crx_file|.
65 void CopyExternalCrxAndDeleteTempDir(const base::FilePath& external_crx_file,
66                                      const base::FilePath& temp_crx_file,
67                                      const base::FilePath& temp_dir,
68                                      bool* success) {
69   base::DeleteFile(temp_dir, true);
70   *success = base::CopyFile(external_crx_file, temp_crx_file);
71 }
72
73 // Returns true if |version_1| < |version_2|, and
74 // if |update_for_same_version| is true and |version_1| = |version_2|.
75 bool ShouldUpdateForHigherVersion(const std::string& version_1,
76                                   const std::string& version_2,
77                                   bool update_for_same_version) {
78   const base::Version v1(version_1);
79   const base::Version v2(version_2);
80   if (!v1.IsValid() || !v2.IsValid())
81     return false;
82   int compare_result = v1.CompareTo(v2);
83   if (compare_result < 0)
84     return true;
85   else if (update_for_same_version && compare_result == 0)
86     return true;
87   else
88     return false;
89 }
90
91 }  // namespace
92
93 KioskExternalUpdater::ExternalUpdate::ExternalUpdate() {
94 }
95
96 KioskExternalUpdater::KioskExternalUpdater(
97     const scoped_refptr<base::SequencedTaskRunner>& backend_task_runner,
98     const base::FilePath& crx_cache_dir,
99     const base::FilePath& crx_unpack_dir)
100     : backend_task_runner_(backend_task_runner),
101       crx_cache_dir_(crx_cache_dir),
102       crx_unpack_dir_(crx_unpack_dir),
103       weak_factory_(this) {
104   // Subscribe to DiskMountManager.
105   DCHECK(disks::DiskMountManager::GetInstance());
106   disks::DiskMountManager::GetInstance()->AddObserver(this);
107 }
108
109 KioskExternalUpdater::~KioskExternalUpdater() {
110   if (disks::DiskMountManager::GetInstance())
111     disks::DiskMountManager::GetInstance()->RemoveObserver(this);
112 }
113
114 void KioskExternalUpdater::OnDiskEvent(
115     disks::DiskMountManager::DiskEvent event,
116     const disks::DiskMountManager::Disk* disk) {
117 }
118
119 void KioskExternalUpdater::OnDeviceEvent(
120     disks::DiskMountManager::DeviceEvent event,
121     const std::string& device_path) {
122 }
123
124 void KioskExternalUpdater::OnMountEvent(
125     disks::DiskMountManager::MountEvent event,
126     MountError error_code,
127     const disks::DiskMountManager::MountPointInfo& mount_info) {
128   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
129   if (mount_info.mount_type != MOUNT_TYPE_DEVICE ||
130       error_code != MOUNT_ERROR_NONE) {
131     return;
132   }
133
134   if (event == disks::DiskMountManager::MOUNTING) {
135     // If multiple disks have been mounted, skip the rest of them if kiosk
136     // update has already been found.
137     if (!external_update_path_.empty()) {
138       LOG(WARNING) << "External update path already found, skip "
139                    << mount_info.mount_path;
140       return;
141     }
142
143     base::DictionaryValue* parsed_manifest = new base::DictionaryValue();
144     ExternalUpdateErrorCode* parsing_error = new ExternalUpdateErrorCode;
145     backend_task_runner_->PostTaskAndReply(
146         FROM_HERE,
147         base::Bind(&ParseExternalUpdateManifest,
148                    base::FilePath(mount_info.mount_path),
149                    parsed_manifest,
150                    parsing_error),
151         base::Bind(&KioskExternalUpdater::ProcessParsedManifest,
152                    weak_factory_.GetWeakPtr(),
153                    base::Owned(parsing_error),
154                    base::FilePath(mount_info.mount_path),
155                    base::Owned(parsed_manifest)));
156   } else {  // unmounting a removable device.
157     if (external_update_path_.value().empty()) {
158       // Clear any previously displayed message.
159       DismissKioskUpdateNotification();
160     } else if (external_update_path_.value() == mount_info.mount_path) {
161       DismissKioskUpdateNotification();
162       if (IsExternalUpdatePending()) {
163         LOG(ERROR) << "External kiosk update is not completed when the usb "
164                       "stick is unmoutned.";
165       }
166       external_updates_.clear();
167       external_update_path_.clear();
168     }
169   }
170 }
171
172 void KioskExternalUpdater::OnFormatEvent(
173     disks::DiskMountManager::FormatEvent event,
174     FormatError error_code,
175     const std::string& device_path) {
176 }
177
178 void KioskExternalUpdater::OnExtenalUpdateUnpackSuccess(
179     const std::string& app_id,
180     const std::string& version,
181     const std::string& min_browser_version,
182     const base::FilePath& temp_dir) {
183   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
184
185   // User might pull out the usb stick before updating is completed.
186   if (CheckExternalUpdateInterrupted())
187     return;
188
189   if (!ShouldDoExternalUpdate(app_id, version, min_browser_version)) {
190     external_updates_[app_id].update_status = FAILED;
191     MaybeValidateNextExternalUpdate();
192     return;
193   }
194
195   // User might pull out the usb stick before updating is completed.
196   if (CheckExternalUpdateInterrupted())
197     return;
198
199   base::FilePath external_crx_path = external_updates_[app_id].external_crx;
200   base::FilePath temp_crx_path =
201       crx_unpack_dir_.Append(external_crx_path.BaseName());
202   bool* success = new bool;
203   backend_task_runner_->PostTaskAndReply(
204       FROM_HERE,
205       base::Bind(&CopyExternalCrxAndDeleteTempDir,
206                  external_crx_path,
207                  temp_crx_path,
208                  temp_dir,
209                  success),
210       base::Bind(&KioskExternalUpdater::PutValidatedExtension,
211                  weak_factory_.GetWeakPtr(),
212                  base::Owned(success),
213                  app_id,
214                  temp_crx_path,
215                  version));
216 }
217
218 void KioskExternalUpdater::OnExternalUpdateUnpackFailure(
219     const std::string& app_id) {
220   // User might pull out the usb stick before updating is completed.
221   if (CheckExternalUpdateInterrupted())
222     return;
223
224   external_updates_[app_id].update_status = FAILED;
225   external_updates_[app_id].error =
226       ui::ResourceBundle::GetSharedInstance().GetLocalizedString(
227           IDS_KIOSK_EXTERNAL_UPDATE_BAD_CRX);
228   MaybeValidateNextExternalUpdate();
229 }
230
231 void KioskExternalUpdater::ProcessParsedManifest(
232     ExternalUpdateErrorCode* parsing_error,
233     const base::FilePath& external_update_dir,
234     base::DictionaryValue* parsed_manifest) {
235   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
236
237   if (*parsing_error == ERROR_NO_MANIFEST) {
238     KioskAppManager::Get()->OnKioskAppExternalUpdateComplete(false);
239     return;
240   } else if (*parsing_error == ERROR_INVALID_MANIFEST) {
241     NotifyKioskUpdateProgress(
242         ui::ResourceBundle::GetSharedInstance().GetLocalizedString(
243             IDS_KIOSK_EXTERNAL_UPDATE_INVALID_MANIFEST));
244     KioskAppManager::Get()->OnKioskAppExternalUpdateComplete(false);
245     return;
246   }
247
248   NotifyKioskUpdateProgress(
249       ui::ResourceBundle::GetSharedInstance().GetLocalizedString(
250           IDS_KIOSK_EXTERNAL_UPDATE_IN_PROGRESS));
251
252   external_update_path_ = external_update_dir;
253   for (base::DictionaryValue::Iterator it(*parsed_manifest); !it.IsAtEnd();
254        it.Advance()) {
255     std::string app_id = it.key();
256     std::string cached_version_str;
257     base::FilePath cached_crx;
258     if (!KioskAppManager::Get()->GetCachedCrx(
259             app_id, &cached_crx, &cached_version_str)) {
260       LOG(WARNING) << "Can't find app in existing cache " << app_id;
261       continue;
262     }
263
264     const base::DictionaryValue* extension = NULL;
265     if (!it.value().GetAsDictionary(&extension)) {
266       LOG(ERROR) << "Found bad entry in manifest type " << it.value().GetType();
267       continue;
268     }
269
270     std::string external_crx_str;
271     if (!extension->GetString(kExternalCrx, &external_crx_str)) {
272       LOG(ERROR) << "Can't find external crx in manifest " << app_id;
273       continue;
274     }
275
276     std::string external_version_str;
277     if (extension->GetString(kExternalVersion, &external_version_str)) {
278       if (!ShouldUpdateForHigherVersion(
279               cached_version_str, external_version_str, false)) {
280         LOG(WARNING) << "External app " << app_id
281                      << " is at the same or lower version comparing to "
282                      << " the existing one.";
283         continue;
284       }
285     }
286
287     ExternalUpdate update;
288     KioskAppManager::App app;
289     if (KioskAppManager::Get()->GetApp(app_id, &app)) {
290       update.app_name = app.name;
291     } else {
292       NOTREACHED();
293     }
294     update.external_crx = external_update_path_.AppendASCII(external_crx_str);
295     update.update_status = PENDING;
296     external_updates_[app_id] = update;
297   }
298
299   if (external_updates_.empty()) {
300     NotifyKioskUpdateProgress(
301         ui::ResourceBundle::GetSharedInstance().GetLocalizedString(
302             IDS_KIOSK_EXTERNAL_UPDATE_NO_UPDATES));
303     KioskAppManager::Get()->OnKioskAppExternalUpdateComplete(false);
304     return;
305   }
306
307   ValidateExternalUpdates();
308 }
309
310 bool KioskExternalUpdater::CheckExternalUpdateInterrupted() {
311   if (external_updates_.empty()) {
312     // This could happen if user pulls out the usb stick before the updating
313     // operation is completed.
314     LOG(ERROR) << "external_updates_ has been cleared before external "
315                << "updating completes.";
316     return true;
317   }
318
319   return false;
320 }
321
322 void KioskExternalUpdater::ValidateExternalUpdates() {
323   for (ExternalUpdateMap::iterator it = external_updates_.begin();
324        it != external_updates_.end();
325        ++it) {
326     if (it->second.update_status == PENDING) {
327       scoped_refptr<KioskExternalUpdateValidator> crx_validator =
328           new KioskExternalUpdateValidator(backend_task_runner_,
329                                            it->first,
330                                            it->second.external_crx,
331                                            crx_unpack_dir_,
332                                            weak_factory_.GetWeakPtr());
333       crx_validator->Start();
334       break;
335     }
336   }
337 }
338
339 bool KioskExternalUpdater::IsExternalUpdatePending() {
340   for (ExternalUpdateMap::iterator it = external_updates_.begin();
341        it != external_updates_.end();
342        ++it) {
343     if (it->second.update_status == PENDING) {
344       return true;
345     }
346   }
347   return false;
348 }
349
350 bool KioskExternalUpdater::IsAllExternalUpdatesSucceeded() {
351   for (ExternalUpdateMap::iterator it = external_updates_.begin();
352        it != external_updates_.end();
353        ++it) {
354     if (it->second.update_status != SUCCESS) {
355       return false;
356     }
357   }
358   return true;
359 }
360
361 bool KioskExternalUpdater::ShouldDoExternalUpdate(
362     const std::string& app_id,
363     const std::string& version,
364     const std::string& min_browser_version) {
365   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
366
367   std::string existing_version_str;
368   base::FilePath existing_path;
369   bool cached = KioskAppManager::Get()->GetCachedCrx(
370       app_id, &existing_path, &existing_version_str);
371   DCHECK(cached);
372
373   // Compare app version.
374   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
375   if (!ShouldUpdateForHigherVersion(existing_version_str, version, false)) {
376     external_updates_[app_id].error = rb.GetLocalizedString(
377         IDS_KIOSK_EXTERNAL_UPDATE_SAME_OR_LOWER_APP_VERSION);
378     return false;
379   }
380
381   // Check minimum browser version.
382   if (!min_browser_version.empty()) {
383     chrome::VersionInfo current_version_info;
384     if (!ShouldUpdateForHigherVersion(
385             min_browser_version, current_version_info.Version(), true)) {
386       external_updates_[app_id].error = l10n_util::GetStringFUTF16(
387           IDS_KIOSK_EXTERNAL_UPDATE_REQUIRE_HIGHER_BROWSER_VERSION,
388           base::UTF8ToUTF16(min_browser_version));
389       return false;
390     }
391   }
392
393   return true;
394 }
395
396 void KioskExternalUpdater::PutValidatedExtension(bool* crx_copied,
397                                                  const std::string& app_id,
398                                                  const base::FilePath& crx_file,
399                                                  const std::string& version) {
400   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
401   if (CheckExternalUpdateInterrupted())
402     return;
403
404   if (!*crx_copied) {
405     LOG(ERROR) << "Cannot copy external crx file to " << crx_file.value();
406     external_updates_[app_id].update_status = FAILED;
407     external_updates_[app_id].error = l10n_util::GetStringFUTF16(
408         IDS_KIOSK_EXTERNAL_UPDATE_FAILED_COPY_CRX_TO_TEMP,
409         base::UTF8ToUTF16(crx_file.value()));
410     MaybeValidateNextExternalUpdate();
411     return;
412   }
413
414   chromeos::KioskAppManager::Get()->PutValidatedExternalExtension(
415       app_id,
416       crx_file,
417       version,
418       base::Bind(&KioskExternalUpdater::OnPutValidatedExtension,
419                  weak_factory_.GetWeakPtr()));
420 }
421
422 void KioskExternalUpdater::OnPutValidatedExtension(const std::string& app_id,
423                                                    bool success) {
424   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
425   if (CheckExternalUpdateInterrupted())
426     return;
427
428   if (!success) {
429     external_updates_[app_id].update_status = FAILED;
430     external_updates_[app_id].error = l10n_util::GetStringFUTF16(
431         IDS_KIOSK_EXTERNAL_UPDATE_CANNOT_INSTALL_IN_LOCAL_CACHE,
432         base::UTF8ToUTF16(external_updates_[app_id].external_crx.value()));
433   } else {
434     external_updates_[app_id].update_status = SUCCESS;
435   }
436
437   // Validate the next pending external update.
438   MaybeValidateNextExternalUpdate();
439 }
440
441 void KioskExternalUpdater::MaybeValidateNextExternalUpdate() {
442   if (IsExternalUpdatePending())
443     ValidateExternalUpdates();
444   else
445     MayBeNotifyKioskAppUpdate();
446 }
447
448 void KioskExternalUpdater::MayBeNotifyKioskAppUpdate() {
449   if (IsExternalUpdatePending())
450     return;
451
452   NotifyKioskUpdateProgress(GetUpdateReportMessage());
453   NotifyKioskAppUpdateAvailable();
454   KioskAppManager::Get()->OnKioskAppExternalUpdateComplete(
455       IsAllExternalUpdatesSucceeded());
456 }
457
458 void KioskExternalUpdater::NotifyKioskAppUpdateAvailable() {
459   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
460   for (ExternalUpdateMap::iterator it = external_updates_.begin();
461        it != external_updates_.end();
462        ++it) {
463     if (it->second.update_status == SUCCESS) {
464       KioskAppManager::Get()->OnKioskAppCacheUpdated(it->first);
465     }
466   }
467 }
468
469 void KioskExternalUpdater::NotifyKioskUpdateProgress(
470     const base::string16& message) {
471   if (!notification_)
472     notification_.reset(new KioskExternalUpdateNotification(message));
473   else
474     notification_->ShowMessage(message);
475 }
476
477 void KioskExternalUpdater::DismissKioskUpdateNotification() {
478   if (notification_.get()) {
479     notification_.reset();
480   }
481 }
482
483 base::string16 KioskExternalUpdater::GetUpdateReportMessage() {
484   DCHECK(!IsExternalUpdatePending());
485   int updated = 0;
486   int failed = 0;
487   base::string16 updated_apps;
488   base::string16 failed_apps;
489   for (ExternalUpdateMap::iterator it = external_updates_.begin();
490        it != external_updates_.end();
491        ++it) {
492     base::string16 app_name = base::UTF8ToUTF16(it->second.app_name);
493     if (it->second.update_status == SUCCESS) {
494       ++updated;
495       if (updated_apps.empty())
496         updated_apps = app_name;
497       else
498         updated_apps = updated_apps + base::ASCIIToUTF16(", ") + app_name;
499     } else {  // FAILED
500       ++failed;
501       if (failed_apps.empty()) {
502         failed_apps = app_name + base::ASCIIToUTF16(": ") + it->second.error;
503       } else {
504         failed_apps = failed_apps + base::ASCIIToUTF16("\n") + app_name +
505                       base::ASCIIToUTF16(": ") + it->second.error;
506       }
507     }
508   }
509
510   base::string16 message;
511   message = ui::ResourceBundle::GetSharedInstance().GetLocalizedString(
512       IDS_KIOSK_EXTERNAL_UPDATE_COMPLETE);
513   base::string16 success_app_msg;
514   if (updated) {
515     success_app_msg = l10n_util::GetStringFUTF16(
516         IDS_KIOSK_EXTERNAL_UPDATE_SUCCESSFUL_UPDATED_APPS, updated_apps);
517     message = message + base::ASCIIToUTF16("\n") + success_app_msg;
518   }
519
520   base::string16 failed_app_msg;
521   if (failed) {
522     failed_app_msg = ui::ResourceBundle::GetSharedInstance().GetLocalizedString(
523                          IDS_KIOSK_EXTERNAL_UPDATE_FAILED_UPDATED_APPS) +
524                      base::ASCIIToUTF16("\n") + failed_apps;
525     message = message + base::ASCIIToUTF16("\n") + failed_app_msg;
526   }
527   return message;
528 }
529
530 }  // namespace chromeos