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/chromeos/app_mode/kiosk_app_manager.h"
6
7#include <map>
8#include <set>
9
10#include "base/bind.h"
11#include "base/chromeos/chromeos_version.h"
12#include "base/logging.h"
13#include "base/path_service.h"
14#include "base/prefs/pref_registry_simple.h"
15#include "base/prefs/pref_service.h"
16#include "base/stl_util.h"
17#include "chrome/browser/browser_process.h"
18#include "chrome/browser/chrome_notification_types.h"
19#include "chrome/browser/chromeos/app_mode/kiosk_app_data.h"
20#include "chrome/browser/chromeos/app_mode/kiosk_app_manager_observer.h"
21#include "chrome/browser/chromeos/login/user_manager.h"
22#include "chrome/browser/chromeos/policy/device_local_account.h"
23#include "chrome/browser/chromeos/settings/cros_settings.h"
24#include "chrome/browser/chromeos/settings/cros_settings_names.h"
25#include "chrome/browser/chromeos/settings/owner_key_util.h"
26#include "chrome/browser/policy/browser_policy_connector.h"
27#include "chrome/browser/prefs/scoped_user_pref_update.h"
28#include "chrome/common/chrome_paths.h"
29#include "chromeos/cryptohome/async_method_caller.h"
30#include "chromeos/cryptohome/cryptohome_library.h"
31#include "content/public/browser/browser_thread.h"
32
33namespace chromeos {
34
35namespace {
36
37// Domain that is used for kiosk-app account IDs.
38const char kKioskAppAccountDomain[] = "kiosk-apps";
39
40std::string GenerateKioskAppAccountId(const std::string& app_id) {
41  return app_id + '@' + kKioskAppAccountDomain;
42}
43
44void OnRemoveAppCryptohomeComplete(const std::string& app,
45                                   bool success,
46                                   cryptohome::MountError return_code) {
47  if (!success) {
48    LOG(ERROR) << "Remove cryptohome for " << app
49        << " failed, return code: " << return_code;
50  }
51}
52
53// Check for presence of machine owner public key file.
54void CheckOwnerFilePresence(bool *present) {
55  scoped_refptr<OwnerKeyUtil> util = OwnerKeyUtil::Create();
56  *present = util->IsPublicKeyPresent();
57}
58
59}  // namespace
60
61// static
62const char KioskAppManager::kKioskDictionaryName[] = "kiosk";
63const char KioskAppManager::kKeyApps[] = "apps";
64const char KioskAppManager::kKeyAutoLoginState[] = "auto_login_state";
65const char KioskAppManager::kIconCacheDir[] = "kiosk";
66
67// static
68static base::LazyInstance<KioskAppManager> instance = LAZY_INSTANCE_INITIALIZER;
69KioskAppManager* KioskAppManager::Get() {
70  return instance.Pointer();
71}
72
73// static
74void KioskAppManager::Shutdown() {
75  if (instance == NULL)
76    return;
77
78  instance.Pointer()->CleanUp();
79}
80
81// static
82void KioskAppManager::RegisterPrefs(PrefRegistrySimple* registry) {
83  registry->RegisterDictionaryPref(kKioskDictionaryName);
84}
85
86KioskAppManager::App::App(const KioskAppData& data)
87    : app_id(data.app_id()),
88      user_id(data.user_id()),
89      name(data.name()),
90      icon(data.icon()),
91      is_loading(data.IsLoading()) {
92}
93
94KioskAppManager::App::App() : is_loading(false) {}
95KioskAppManager::App::~App() {}
96
97std::string KioskAppManager::GetAutoLaunchApp() const {
98  return auto_launch_app_id_;
99}
100
101void KioskAppManager::SetAutoLaunchApp(const std::string& app_id) {
102  SetAutoLoginState(AUTOLOGIN_REQUESTED);
103  // Clean first, so the proper change notifications are triggered even
104  // if we are only changing AutoLoginState here.
105  if (!auto_launch_app_id_.empty()) {
106    CrosSettings::Get()->SetString(kAccountsPrefDeviceLocalAccountAutoLoginId,
107                                   std::string());
108  }
109
110  CrosSettings::Get()->SetString(
111      kAccountsPrefDeviceLocalAccountAutoLoginId,
112      app_id.empty() ? std::string() : GenerateKioskAppAccountId(app_id));
113  CrosSettings::Get()->SetInteger(
114      kAccountsPrefDeviceLocalAccountAutoLoginDelay, 0);
115}
116
117void KioskAppManager::EnableConsumerModeKiosk(
118    const KioskAppManager::EnableKioskModeCallback& callback) {
119  g_browser_process->browser_policy_connector()->GetInstallAttributes()->
120      LockDevice(std::string(),  // user
121                 policy::DEVICE_MODE_CONSUMER_KIOSK,
122                 std::string(),  // device_id
123                 base::Bind(&KioskAppManager::OnLockDevice,
124                            base::Unretained(this),
125                            callback));
126}
127
128void KioskAppManager::GetConsumerKioskModeStatus(
129    const KioskAppManager::GetConsumerKioskModeStatusCallback& callback) {
130  g_browser_process->browser_policy_connector()->GetInstallAttributes()->
131      ReadImmutableAttributes(
132          base::Bind(&KioskAppManager::OnReadImmutableAttributes,
133                     base::Unretained(this),
134                     callback));
135}
136
137void KioskAppManager::OnLockDevice(
138    const KioskAppManager::EnableKioskModeCallback& callback,
139    policy::EnterpriseInstallAttributes::LockResult result) {
140  if (callback.is_null())
141    return;
142
143  callback.Run(result == policy::EnterpriseInstallAttributes::LOCK_SUCCESS);
144}
145
146void KioskAppManager::OnOwnerFileChecked(
147    const KioskAppManager::GetConsumerKioskModeStatusCallback& callback,
148    bool* owner_present) {
149  ownership_established_ = *owner_present;
150
151  if (callback.is_null())
152    return;
153
154  // If we have owner already established on the machine, don't let
155  // consumer kiosk to be enabled.
156  if (ownership_established_)
157    callback.Run(CONSUMER_KIOSK_MODE_DISABLED);
158  else
159    callback.Run(CONSUMER_KIOSK_MODE_CONFIGURABLE);
160}
161
162void KioskAppManager::OnReadImmutableAttributes(
163    const KioskAppManager::GetConsumerKioskModeStatusCallback& callback) {
164  if (callback.is_null())
165    return;
166
167  ConsumerKioskModeStatus status = CONSUMER_KIOSK_MODE_DISABLED;
168  policy::EnterpriseInstallAttributes* attributes =
169      g_browser_process->browser_policy_connector()->GetInstallAttributes();
170  switch (attributes->GetMode()) {
171    case policy::DEVICE_MODE_NOT_SET: {
172      if (!base::chromeos::IsRunningOnChromeOS()) {
173        status = CONSUMER_KIOSK_MODE_CONFIGURABLE;
174      } else if (!ownership_established_) {
175        bool* owner_present = new bool(false);
176        content::BrowserThread::PostBlockingPoolTaskAndReply(
177            FROM_HERE,
178            base::Bind(&CheckOwnerFilePresence,
179                       owner_present),
180            base::Bind(&KioskAppManager::OnOwnerFileChecked,
181                       base::Unretained(this),
182                       callback,
183                       base::Owned(owner_present)));
184        return;
185      }
186      break;
187    }
188    case policy::DEVICE_MODE_CONSUMER_KIOSK:
189      status = CONSUMER_KIOSK_MODE_ENABLED;
190      break;
191    default:
192      break;
193  }
194
195  callback.Run(status);
196}
197
198void KioskAppManager::SetEnableAutoLaunch(bool value) {
199  SetAutoLoginState(value ? AUTOLOGIN_APPROVED : AUTOLOGIN_REJECTED);
200}
201
202bool KioskAppManager::IsAutoLaunchRequested() const {
203  if (GetAutoLaunchApp().empty())
204    return false;
205
206  // Apps that were installed by the policy don't require machine owner
207  // consent through UI.
208  if (g_browser_process->browser_policy_connector()->IsEnterpriseManaged())
209    return false;
210
211  return GetAutoLoginState() == AUTOLOGIN_REQUESTED;
212}
213
214bool KioskAppManager::IsAutoLaunchEnabled() const {
215  if (GetAutoLaunchApp().empty())
216    return false;
217
218  // Apps that were installed by the policy don't require machine owner
219  // consent through UI.
220  if (g_browser_process->browser_policy_connector()->IsEnterpriseManaged())
221    return true;
222
223  return GetAutoLoginState() == AUTOLOGIN_APPROVED;
224}
225
226void KioskAppManager::AddApp(const std::string& app_id) {
227  std::vector<policy::DeviceLocalAccount> device_local_accounts =
228      policy::GetDeviceLocalAccounts(CrosSettings::Get());
229
230  // Don't insert the app if it's already in the list.
231  for (std::vector<policy::DeviceLocalAccount>::const_iterator
232           it = device_local_accounts.begin();
233       it != device_local_accounts.end(); ++it) {
234    if (it->type == policy::DeviceLocalAccount::TYPE_KIOSK_APP &&
235        it->kiosk_app_id == app_id) {
236      return;
237    }
238  }
239
240  // Add the new account.
241  device_local_accounts.push_back(policy::DeviceLocalAccount(
242      policy::DeviceLocalAccount::TYPE_KIOSK_APP,
243      GenerateKioskAppAccountId(app_id),
244      app_id,
245      std::string()));
246
247  policy::SetDeviceLocalAccounts(CrosSettings::Get(), device_local_accounts);
248}
249
250void KioskAppManager::RemoveApp(const std::string& app_id) {
251  // Resets auto launch app if it is the removed app.
252  if (auto_launch_app_id_ == app_id)
253    SetAutoLaunchApp(std::string());
254
255  std::vector<policy::DeviceLocalAccount> device_local_accounts =
256      policy::GetDeviceLocalAccounts(CrosSettings::Get());
257  if (device_local_accounts.empty())
258    return;
259
260  // Remove entries that match |app_id|.
261  for (std::vector<policy::DeviceLocalAccount>::iterator
262           it = device_local_accounts.begin();
263       it != device_local_accounts.end(); ++it) {
264    if (it->type == policy::DeviceLocalAccount::TYPE_KIOSK_APP &&
265        it->kiosk_app_id == app_id) {
266      device_local_accounts.erase(it);
267      break;
268    }
269  }
270
271  policy::SetDeviceLocalAccounts(CrosSettings::Get(), device_local_accounts);
272}
273
274void KioskAppManager::GetApps(Apps* apps) const {
275  apps->reserve(apps_.size());
276  for (size_t i = 0; i < apps_.size(); ++i)
277    apps->push_back(App(*apps_[i]));
278}
279
280bool KioskAppManager::GetApp(const std::string& app_id, App* app) const {
281  const KioskAppData* data = GetAppData(app_id);
282  if (!data)
283    return false;
284
285  *app = App(*data);
286  return true;
287}
288
289const base::RefCountedString* KioskAppManager::GetAppRawIcon(
290    const std::string& app_id) const {
291  const KioskAppData* data = GetAppData(app_id);
292  if (!data)
293    return NULL;
294
295  return data->raw_icon();
296}
297
298bool KioskAppManager::GetDisableBailoutShortcut() const {
299  bool enable;
300  if (CrosSettings::Get()->GetBoolean(
301          kAccountsPrefDeviceLocalAccountAutoLoginBailoutEnabled, &enable)) {
302    return !enable;
303  }
304
305  return false;
306}
307
308void KioskAppManager::AddObserver(KioskAppManagerObserver* observer) {
309  observers_.AddObserver(observer);
310}
311
312void KioskAppManager::RemoveObserver(KioskAppManagerObserver* observer) {
313  observers_.RemoveObserver(observer);
314}
315
316KioskAppManager::KioskAppManager() : ownership_established_(false) {
317  UpdateAppData();
318  CrosSettings::Get()->AddSettingsObserver(
319      kAccountsPrefDeviceLocalAccounts, this);
320  CrosSettings::Get()->AddSettingsObserver(
321      kAccountsPrefDeviceLocalAccountAutoLoginId, this);
322}
323
324KioskAppManager::~KioskAppManager() {}
325
326void KioskAppManager::CleanUp() {
327  CrosSettings::Get()->RemoveSettingsObserver(
328      kAccountsPrefDeviceLocalAccounts, this);
329  CrosSettings::Get()->RemoveSettingsObserver(
330      kAccountsPrefDeviceLocalAccountAutoLoginId, this);
331  apps_.clear();
332}
333
334const KioskAppData* KioskAppManager::GetAppData(
335    const std::string& app_id) const {
336  for (size_t i = 0; i < apps_.size(); ++i) {
337    const KioskAppData* data = apps_[i];
338    if (data->app_id() == app_id)
339      return data;
340  }
341
342  return NULL;
343}
344
345void KioskAppManager::UpdateAppData() {
346  // Gets app id to data mapping for existing apps.
347  std::map<std::string, KioskAppData*> old_apps;
348  for (size_t i = 0; i < apps_.size(); ++i)
349    old_apps[apps_[i]->app_id()] = apps_[i];
350  apps_.weak_clear();  // |old_apps| takes ownership
351
352  auto_launch_app_id_.clear();
353  std::string auto_login_account_id;
354  CrosSettings::Get()->GetString(kAccountsPrefDeviceLocalAccountAutoLoginId,
355                                 &auto_login_account_id);
356
357  // Re-populates |apps_| and reuses existing KioskAppData when possible.
358  const std::vector<policy::DeviceLocalAccount> device_local_accounts =
359      policy::GetDeviceLocalAccounts(CrosSettings::Get());
360  for (std::vector<policy::DeviceLocalAccount>::const_iterator
361           it = device_local_accounts.begin();
362       it != device_local_accounts.end(); ++it) {
363    if (it->type != policy::DeviceLocalAccount::TYPE_KIOSK_APP)
364      continue;
365
366    if (it->account_id == auto_login_account_id)
367      auto_launch_app_id_ = it->kiosk_app_id;
368
369    // TODO(mnissler): Support non-CWS update URLs.
370
371    std::map<std::string, KioskAppData*>::iterator old_it =
372        old_apps.find(it->kiosk_app_id);
373    if (old_it != old_apps.end()) {
374      apps_.push_back(old_it->second);
375      old_apps.erase(old_it);
376    } else {
377      KioskAppData* new_app =
378          new KioskAppData(this, it->kiosk_app_id, it->user_id);
379      apps_.push_back(new_app);  // Takes ownership of |new_app|.
380      new_app->Load();
381    }
382  }
383
384  // Clears cache and deletes the remaining old data.
385  for (std::map<std::string, KioskAppData*>::iterator it = old_apps.begin();
386       it != old_apps.end(); ++it) {
387    it->second->ClearCache();
388    cryptohome::AsyncMethodCaller::GetInstance()->AsyncRemove(
389        it->second->user_id(),
390        base::Bind(&OnRemoveAppCryptohomeComplete, it->first));
391  }
392  STLDeleteValues(&old_apps);
393
394  FOR_EACH_OBSERVER(KioskAppManagerObserver, observers_,
395                    OnKioskAppsSettingsChanged());
396}
397
398void KioskAppManager::Observe(int type,
399                              const content::NotificationSource& source,
400                              const content::NotificationDetails& details) {
401  DCHECK_EQ(chrome::NOTIFICATION_SYSTEM_SETTING_CHANGED, type);
402  UpdateAppData();
403}
404
405void KioskAppManager::GetKioskAppIconCacheDir(base::FilePath* cache_dir) {
406  base::FilePath user_data_dir;
407  CHECK(PathService::Get(chrome::DIR_USER_DATA, &user_data_dir));
408  *cache_dir = user_data_dir.AppendASCII(kIconCacheDir);
409}
410
411void KioskAppManager::OnKioskAppDataChanged(const std::string& app_id) {
412  FOR_EACH_OBSERVER(KioskAppManagerObserver,
413                    observers_,
414                    OnKioskAppDataChanged(app_id));
415}
416
417void KioskAppManager::OnKioskAppDataLoadFailure(const std::string& app_id) {
418  FOR_EACH_OBSERVER(KioskAppManagerObserver,
419                    observers_,
420                    OnKioskAppDataLoadFailure(app_id));
421  RemoveApp(app_id);
422}
423
424KioskAppManager::AutoLoginState KioskAppManager::GetAutoLoginState() const {
425  PrefService* prefs = g_browser_process->local_state();
426  const base::DictionaryValue* dict =
427      prefs->GetDictionary(KioskAppManager::kKioskDictionaryName);
428  int value;
429  if (!dict->GetInteger(kKeyAutoLoginState, &value))
430    return AUTOLOGIN_NONE;
431
432  return static_cast<AutoLoginState>(value);
433}
434
435void KioskAppManager::SetAutoLoginState(AutoLoginState state) {
436  PrefService* prefs = g_browser_process->local_state();
437  DictionaryPrefUpdate dict_update(prefs,
438                                   KioskAppManager::kKioskDictionaryName);
439  dict_update->SetInteger(kKeyAutoLoginState, state);
440  prefs->CommitPendingWrite();
441}
442
443}  // namespace chromeos
444