1// Copyright 2013 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/ui/webui/extensions/chromeos/kiosk_apps_handler.h"
6
7#include <algorithm>
8#include <set>
9#include <string>
10#include <vector>
11
12#include "base/bind.h"
13#include "base/command_line.h"
14#include "base/memory/scoped_ptr.h"
15#include "base/strings/string_util.h"
16#include "base/sys_info.h"
17#include "base/values.h"
18#include "chrome/browser/chromeos/settings/cros_settings.h"
19#include "chrome/grit/chromium_strings.h"
20#include "chrome/grit/generated_resources.h"
21#include "chromeos/chromeos_switches.h"
22#include "chromeos/settings/cros_settings_names.h"
23#include "components/crx_file/id_util.h"
24#include "components/user_manager/user_manager.h"
25#include "content/public/browser/web_ui.h"
26#include "content/public/browser/web_ui_data_source.h"
27#include "extensions/common/extension_urls.h"
28#include "ui/base/l10n/l10n_util.h"
29#include "ui/base/webui/web_ui_util.h"
30#include "url/gurl.h"
31
32namespace chromeos {
33
34namespace {
35
36// Populates app info dictionary with |app_data|.
37void PopulateAppDict(const KioskAppManager::App& app_data,
38                     base::DictionaryValue* app_dict) {
39  std::string icon_url("chrome://theme/IDR_APP_DEFAULT_ICON");
40
41  // TODO(xiyuan): Replace data url with a URLDataSource.
42  if (!app_data.icon.isNull())
43    icon_url = webui::GetBitmapDataUrl(*app_data.icon.bitmap());
44
45  // The items which are to be written into app_dict are also described in
46  // chrome/browser/resources/extensions/chromeos/kiosk_app_list.js in @typedef
47  // for AppDict. Please update it whenever you add or remove any keys here.
48  app_dict->SetString("id", app_data.app_id);
49  app_dict->SetString("name", app_data.name);
50  app_dict->SetString("iconURL", icon_url);
51  app_dict->SetBoolean(
52      "autoLaunch",
53      KioskAppManager::Get()->GetAutoLaunchApp() == app_data.app_id &&
54      (KioskAppManager::Get()->IsAutoLaunchEnabled() ||
55          KioskAppManager::Get()->IsAutoLaunchRequested()));
56  app_dict->SetBoolean("isLoading", app_data.is_loading);
57}
58
59// Sanitize app id input value and extracts app id out of it.
60// Returns false if an app id could not be derived out of the input.
61bool ExtractsAppIdFromInput(const std::string& input,
62                            std::string* app_id) {
63  if (crx_file::id_util::IdIsValid(input)) {
64    *app_id = input;
65    return true;
66  }
67
68  GURL webstore_url = GURL(input);
69  if (!webstore_url.is_valid())
70    return false;
71
72  GURL webstore_base_url =
73      GURL(extension_urls::GetWebstoreItemDetailURLPrefix());
74
75  if (webstore_url.scheme() != webstore_base_url.scheme() ||
76      webstore_url.host() != webstore_base_url.host() ||
77      !StartsWithASCII(
78          webstore_url.path(), webstore_base_url.path(), true)) {
79    return false;
80  }
81
82  const std::string path = webstore_url.path();
83  const size_t last_slash = path.rfind('/');
84  if (last_slash == std::string::npos)
85    return false;
86
87  const std::string candidate_id = path.substr(last_slash + 1);
88  if (!crx_file::id_util::IdIsValid(candidate_id))
89    return false;
90
91  *app_id = candidate_id;
92  return true;
93}
94
95}  // namespace
96
97KioskAppsHandler::KioskAppsHandler()
98    : kiosk_app_manager_(KioskAppManager::Get()),
99      initialized_(false),
100      is_kiosk_enabled_(false),
101      is_auto_launch_enabled_(false),
102      weak_ptr_factory_(this) {
103  kiosk_app_manager_->AddObserver(this);
104}
105
106KioskAppsHandler::~KioskAppsHandler() {
107  kiosk_app_manager_->RemoveObserver(this);
108}
109
110void KioskAppsHandler::RegisterMessages() {
111  web_ui()->RegisterMessageCallback("initializeKioskAppSettings",
112      base::Bind(&KioskAppsHandler::HandleInitializeKioskAppSettings,
113                 base::Unretained(this)));
114  web_ui()->RegisterMessageCallback("getKioskAppSettings",
115      base::Bind(&KioskAppsHandler::HandleGetKioskAppSettings,
116                 base::Unretained(this)));
117  web_ui()->RegisterMessageCallback("addKioskApp",
118      base::Bind(&KioskAppsHandler::HandleAddKioskApp,
119                 base::Unretained(this)));
120  web_ui()->RegisterMessageCallback("removeKioskApp",
121      base::Bind(&KioskAppsHandler::HandleRemoveKioskApp,
122                 base::Unretained(this)));
123  web_ui()->RegisterMessageCallback("enableKioskAutoLaunch",
124      base::Bind(&KioskAppsHandler::HandleEnableKioskAutoLaunch,
125                 base::Unretained(this)));
126  web_ui()->RegisterMessageCallback("disableKioskAutoLaunch",
127      base::Bind(&KioskAppsHandler::HandleDisableKioskAutoLaunch,
128                 base::Unretained(this)));
129  web_ui()->RegisterMessageCallback("setDisableBailoutShortcut",
130      base::Bind(&KioskAppsHandler::HandleSetDisableBailoutShortcut,
131                 base::Unretained(this)));
132}
133
134void KioskAppsHandler::GetLocalizedValues(content::WebUIDataSource* source) {
135  source->AddString(
136      "addKioskAppButton",
137      l10n_util::GetStringUTF16(IDS_EXTENSIONS_ADD_KIOSK_APP_BUTTON));
138  source->AddString(
139      "kioskOverlayTitle",
140      l10n_util::GetStringUTF16(IDS_OPTIONS_KIOSK_OVERLAY_TITLE));
141  source->AddString(
142      "addKioskApp",
143      l10n_util::GetStringUTF16(IDS_OPTIONS_KIOSK_ADD_APP));
144  source->AddString(
145      "kioskAppIdEditHint",
146      l10n_util::GetStringUTF16(IDS_OPTIONS_KIOSK_ADD_APP_HINT));
147  source->AddString(
148      "enableAutoLaunchButton",
149      l10n_util::GetStringUTF16(IDS_OPTIONS_KIOSK_ENABLE_AUTO_LAUNCH));
150  source->AddString(
151      "disableAutoLaunchButton",
152      l10n_util::GetStringUTF16(IDS_OPTIONS_KIOSK_DISABLE_AUTO_LAUNCH));
153  source->AddString(
154      "autoLaunch",
155      l10n_util::GetStringUTF16(IDS_OPTIONS_KIOSK_AUTO_LAUNCH));
156  source->AddString(
157      "invalidApp",
158      l10n_util::GetStringUTF16(IDS_OPTIONS_KIOSK_INVALID_APP));
159  source->AddString(
160      "kioskDiableBailoutShortcutLabel",
161      l10n_util::GetStringUTF16(
162          IDS_OPTIONS_KIOSK_DISABLE_BAILOUT_SHORTCUT_LABEL));
163  source->AddString(
164      "kioskDisableBailoutShortcutWarningBold",
165      l10n_util::GetStringUTF16(
166          IDS_OPTIONS_KIOSK_DISABLE_BAILOUT_SHORTCUT_WARNING_BOLD));
167  const base::string16 product_os_name =
168      l10n_util::GetStringUTF16(IDS_SHORT_PRODUCT_OS_NAME);
169  source->AddString(
170      "kioskDisableBailoutShortcutWarning",
171      l10n_util::GetStringFUTF16(
172          IDS_OPTIONS_KIOSK_DISABLE_BAILOUT_SHORTCUT_WARNING_FORMAT,
173          product_os_name));
174  source->AddString(
175      "kioskDisableBailoutShortcutConfirm",
176      l10n_util::GetStringUTF16(IDS_CONFIRM_MESSAGEBOX_YES_BUTTON_LABEL));
177  source->AddString(
178      "kioskDisableBailoutShortcutCancel",
179      l10n_util::GetStringUTF16(IDS_CONFIRM_MESSAGEBOX_NO_BUTTON_LABEL));
180  source->AddString("done", l10n_util::GetStringUTF16(IDS_DONE));
181  source->AddString("add", l10n_util::GetStringUTF16(IDS_ADD));
182}
183
184void KioskAppsHandler::OnKioskAppDataChanged(const std::string& app_id) {
185  UpdateApp(app_id);
186}
187
188void KioskAppsHandler::OnKioskAppDataLoadFailure(const std::string& app_id) {
189  ShowError(app_id);
190}
191
192void KioskAppsHandler::OnKioskExtensionLoadedInCache(
193    const std::string& app_id) {
194  UpdateApp(app_id);
195}
196
197void KioskAppsHandler::OnKioskExtensionDownloadFailed(
198    const std::string& app_id) {
199  ShowError(app_id);
200}
201
202void KioskAppsHandler::OnGetConsumerKioskAutoLaunchStatus(
203    chromeos::KioskAppManager::ConsumerKioskAutoLaunchStatus status) {
204  initialized_ = true;
205  is_kiosk_enabled_ = user_manager::UserManager::Get()->IsCurrentUserOwner() ||
206                      !base::SysInfo::IsRunningOnChromeOS();
207
208  is_auto_launch_enabled_ =
209      status == KioskAppManager::CONSUMER_KIOSK_AUTO_LAUNCH_ENABLED ||
210      !base::SysInfo::IsRunningOnChromeOS();
211
212  if (is_kiosk_enabled_) {
213    base::DictionaryValue kiosk_params;
214    kiosk_params.SetBoolean("kioskEnabled", is_kiosk_enabled_);
215    kiosk_params.SetBoolean("autoLaunchEnabled", is_auto_launch_enabled_);
216    web_ui()->CallJavascriptFunction("extensions.KioskAppsOverlay.enableKiosk",
217                                     kiosk_params);
218  }
219}
220
221
222void KioskAppsHandler::OnKioskAppsSettingsChanged() {
223  SendKioskAppSettings();
224}
225
226void KioskAppsHandler::SendKioskAppSettings() {
227  if (!initialized_ || !is_kiosk_enabled_)
228    return;
229
230  bool enable_bailout_shortcut;
231  if (!CrosSettings::Get()->GetBoolean(
232          kAccountsPrefDeviceLocalAccountAutoLoginBailoutEnabled,
233          &enable_bailout_shortcut)) {
234    enable_bailout_shortcut = true;
235  }
236
237  base::DictionaryValue settings;
238  settings.SetBoolean("disableBailout", !enable_bailout_shortcut);
239  settings.SetBoolean("hasAutoLaunchApp",
240                      !kiosk_app_manager_->GetAutoLaunchApp().empty());
241
242  KioskAppManager::Apps apps;
243  kiosk_app_manager_->GetApps(&apps);
244
245  scoped_ptr<base::ListValue> apps_list(new base::ListValue);
246  for (size_t i = 0; i < apps.size(); ++i) {
247    const KioskAppManager::App& app_data = apps[i];
248
249    scoped_ptr<base::DictionaryValue> app_info(new base::DictionaryValue);
250    PopulateAppDict(app_data, app_info.get());
251    apps_list->Append(app_info.release());
252  }
253  settings.SetWithoutPathExpansion("apps", apps_list.release());
254
255  web_ui()->CallJavascriptFunction("extensions.KioskAppsOverlay.setSettings",
256                                   settings);
257}
258
259void KioskAppsHandler::HandleInitializeKioskAppSettings(
260    const base::ListValue* args) {
261  KioskAppManager::Get()->GetConsumerKioskAutoLaunchStatus(
262      base::Bind(&KioskAppsHandler::OnGetConsumerKioskAutoLaunchStatus,
263                 weak_ptr_factory_.GetWeakPtr()));
264}
265
266void KioskAppsHandler::HandleGetKioskAppSettings(const base::ListValue* args) {
267  SendKioskAppSettings();
268}
269
270
271void KioskAppsHandler::HandleAddKioskApp(const base::ListValue* args) {
272  if (!initialized_ || !is_kiosk_enabled_)
273    return;
274
275  std::string input;
276  CHECK(args->GetString(0, &input));
277
278  std::string app_id;
279  if (!ExtractsAppIdFromInput(input, &app_id)) {
280    OnKioskAppDataLoadFailure(input);
281    return;
282  }
283
284  kiosk_app_manager_->AddApp(app_id);
285}
286
287void KioskAppsHandler::HandleRemoveKioskApp(const base::ListValue* args) {
288  if (!initialized_ || !is_kiosk_enabled_)
289    return;
290
291  std::string app_id;
292  CHECK(args->GetString(0, &app_id));
293
294  kiosk_app_manager_->RemoveApp(app_id);
295}
296
297void KioskAppsHandler::HandleEnableKioskAutoLaunch(
298    const base::ListValue* args) {
299  if (!initialized_ || !is_kiosk_enabled_ || !is_auto_launch_enabled_)
300    return;
301
302  std::string app_id;
303  CHECK(args->GetString(0, &app_id));
304
305  kiosk_app_manager_->SetAutoLaunchApp(app_id);
306}
307
308void KioskAppsHandler::HandleDisableKioskAutoLaunch(
309    const base::ListValue* args) {
310  if (!initialized_ || !is_kiosk_enabled_ || !is_auto_launch_enabled_)
311    return;
312
313  std::string app_id;
314  CHECK(args->GetString(0, &app_id));
315
316  std::string startup_app_id = kiosk_app_manager_->GetAutoLaunchApp();
317  if (startup_app_id != app_id)
318    return;
319
320  kiosk_app_manager_->SetAutoLaunchApp("");
321}
322
323void KioskAppsHandler::HandleSetDisableBailoutShortcut(
324    const base::ListValue* args) {
325  if (!initialized_ || !is_kiosk_enabled_)
326    return;
327
328  bool disable_bailout_shortcut;
329  CHECK(args->GetBoolean(0, &disable_bailout_shortcut));
330
331  CrosSettings::Get()->SetBoolean(
332      kAccountsPrefDeviceLocalAccountAutoLoginBailoutEnabled,
333      !disable_bailout_shortcut);
334}
335
336void KioskAppsHandler::UpdateApp(const std::string& app_id) {
337  KioskAppManager::App app_data;
338  if (!kiosk_app_manager_->GetApp(app_id, &app_data))
339    return;
340
341  base::DictionaryValue app_dict;
342  PopulateAppDict(app_data, &app_dict);
343
344  web_ui()->CallJavascriptFunction("extensions.KioskAppsOverlay.updateApp",
345                                   app_dict);
346}
347
348void KioskAppsHandler::ShowError(const std::string& app_id) {
349  base::StringValue app_id_value(app_id);
350  web_ui()->CallJavascriptFunction("extensions.KioskAppsOverlay.showError",
351                                   app_id_value);
352
353  kiosk_app_manager_->RemoveApp(app_id);
354}
355
356}  // namespace chromeos
357