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.
5 #include "chrome/browser/chromeos/app_mode/kiosk_app_manager.h"
7 #include "base/command_line.h"
8 #include "base/file_util.h"
9 #include "base/files/scoped_temp_dir.h"
10 #include "base/message_loop/message_loop.h"
11 #include "base/path_service.h"
12 #include "base/prefs/scoped_user_pref_update.h"
13 #include "base/strings/stringprintf.h"
14 #include "base/values.h"
15 #include "chrome/browser/browser_process.h"
16 #include "chrome/browser/chromeos/app_mode/kiosk_app_manager_observer.h"
17 #include "chrome/browser/chromeos/policy/device_local_account.h"
18 #include "chrome/browser/chromeos/settings/cros_settings.h"
19 #include "chrome/browser/policy/browser_policy_connector.h"
20 #include "chrome/browser/ui/browser.h"
21 #include "chrome/common/chrome_paths.h"
22 #include "chrome/common/chrome_switches.h"
23 #include "chrome/test/base/in_process_browser_test.h"
24 #include "chromeos/settings/cros_settings_names.h"
25 #include "content/public/test/test_utils.h"
26 #include "extensions/common/extension.h"
27 #include "net/base/host_port_pair.h"
28 #include "net/dns/mock_host_resolver.h"
29 #include "net/test/embedded_test_server/embedded_test_server.h"
35 const char kWebstoreDomain
[] = "cws.com";
37 // Helper KioskAppManager::GetConsumerKioskModeStatusCallback implementation.
38 void ConsumerKioskModeStatusCheck(
39 KioskAppManager::ConsumerKioskModeStatus
* out_status
,
40 const base::Closure
& runner_quit_task
,
41 KioskAppManager::ConsumerKioskModeStatus in_status
) {
42 LOG(INFO
) << "ConsumerKioskModeStatus = " << in_status
;
43 *out_status
= in_status
;
44 runner_quit_task
.Run();
47 // Helper KioskAppManager::EnableKioskModeCallback implementation.
48 void ConsumerKioskModeLockCheck(
50 const base::Closure
& runner_quit_task
,
52 LOG(INFO
) << "kioks locked = " << in_locked
;
53 *out_locked
= in_locked
;
54 runner_quit_task
.Run();
57 // Helper EnterpriseInstallAttributes::LockResultCallback implementation.
58 void OnEnterpriseDeviceLock(
59 policy::EnterpriseInstallAttributes::LockResult
* out_locked
,
60 const base::Closure
& runner_quit_task
,
61 policy::EnterpriseInstallAttributes::LockResult in_locked
) {
62 LOG(INFO
) << "Enterprise lock = " << in_locked
;
63 *out_locked
= in_locked
;
64 runner_quit_task
.Run();
67 scoped_refptr
<extensions::Extension
> MakeApp(const std::string
& name
,
68 const std::string
& version
,
69 const std::string
& url
,
70 const std::string
& id
) {
72 base::DictionaryValue value
;
73 value
.SetString("name", name
);
74 value
.SetString("version", version
);
75 value
.SetString("app.launch.web_url", url
);
76 scoped_refptr
<extensions::Extension
> app
=
77 extensions::Extension::Create(
79 extensions::Manifest::INTERNAL
,
81 extensions::Extension::WAS_INSTALLED_BY_DEFAULT
,
88 class TestKioskAppManagerObserver
: public KioskAppManagerObserver
{
90 explicit TestKioskAppManagerObserver(KioskAppManager
* manager
)
92 data_changed_count_(0),
93 load_failure_count_(0) {
94 manager_
->AddObserver(this);
96 virtual ~TestKioskAppManagerObserver() {
97 manager_
->RemoveObserver(this);
101 data_changed_count_
= 0;
102 load_failure_count_
= 0;
105 int data_changed_count() const { return data_changed_count_
; }
106 int load_failure_count() const { return load_failure_count_
; }
109 // KioskAppManagerObserver overrides:
110 virtual void OnKioskAppDataChanged(const std::string
& app_id
) OVERRIDE
{
111 ++data_changed_count_
;
113 virtual void OnKioskAppDataLoadFailure(const std::string
& app_id
) OVERRIDE
{
114 ++load_failure_count_
;
117 KioskAppManager
* manager_
;
118 int data_changed_count_
;
119 int load_failure_count_
;
121 DISALLOW_COPY_AND_ASSIGN(TestKioskAppManagerObserver
);
124 class AppDataLoadWaiter
: public KioskAppManagerObserver
{
126 explicit AppDataLoadWaiter(KioskAppManager
* manager
)
131 virtual ~AppDataLoadWaiter() {
135 manager_
->AddObserver(this);
136 runner_
= new content::MessageLoopRunner
;
138 manager_
->RemoveObserver(this);
141 bool loaded() const { return loaded_
; }
144 // KioskAppManagerObserver overrides:
145 virtual void OnKioskAppDataChanged(const std::string
& app_id
) OVERRIDE
{
150 virtual void OnKioskAppDataLoadFailure(const std::string
& app_id
) OVERRIDE
{
155 scoped_refptr
<content::MessageLoopRunner
> runner_
;
156 KioskAppManager
* manager_
;
159 DISALLOW_COPY_AND_ASSIGN(AppDataLoadWaiter
);
164 class KioskAppManagerTest
: public InProcessBrowserTest
{
166 KioskAppManagerTest() {}
167 virtual ~KioskAppManagerTest() {}
169 // InProcessBrowserTest overrides:
170 virtual void SetUp() OVERRIDE
{
171 base::FilePath test_data_dir
;
172 PathService::Get(chrome::DIR_TEST_DATA
, &test_data_dir
);
173 base::FilePath webstore_dir
=
174 test_data_dir
.Append(FILE_PATH_LITERAL("chromeos/app_mode/"));
175 embedded_test_server()->ServeFilesFromDirectory(webstore_dir
);
176 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
177 // Stop IO thread here because no threads are allowed while
178 // spawning sandbox host process. See crbug.com/322732.
179 embedded_test_server()->StopThread();
181 ASSERT_TRUE(temp_dir_
.CreateUniqueTempDir());
183 InProcessBrowserTest::SetUp();
186 virtual void SetUpCommandLine(CommandLine
* command_line
) OVERRIDE
{
187 InProcessBrowserTest::SetUpCommandLine(command_line
);
189 // Get fake webstore gallery URL. At the end, it should look something like
190 // http://cws.com:<test_server_port>/webstore.
191 const GURL
& server_url
= embedded_test_server()->base_url();
192 std::string
google_host(kWebstoreDomain
);
193 GURL::Replacements replace_google_host
;
194 replace_google_host
.SetHostStr(google_host
);
195 GURL google_url
= server_url
.ReplaceComponents(replace_google_host
);
196 GURL fake_store_url
= google_url
.Resolve("/webstore");
197 command_line
->AppendSwitchASCII(switches::kAppsGalleryURL
,
198 fake_store_url
.spec());
201 virtual void SetUpOnMainThread() OVERRIDE
{
202 InProcessBrowserTest::SetUpOnMainThread();
204 // Restart the thread as the sandbox host process has already been spawned.
205 embedded_test_server()->RestartThreadAndListen();
208 virtual void SetUpInProcessBrowserTestFixture() OVERRIDE
{
209 InProcessBrowserTest::SetUpInProcessBrowserTestFixture();
211 host_resolver()->AddRule(kWebstoreDomain
, "127.0.0.1");
214 std::string
GetAppIds() const {
215 KioskAppManager::Apps apps
;
216 manager()->GetApps(&apps
);
219 for (size_t i
= 0; i
< apps
.size(); ++i
) {
222 str
+= apps
[i
].app_id
;
228 // Locks device for enterprise.
229 policy::EnterpriseInstallAttributes::LockResult
LockDeviceForEnterprise() {
230 scoped_ptr
<policy::EnterpriseInstallAttributes::LockResult
> lock_result(
231 new policy::EnterpriseInstallAttributes::LockResult(
232 policy::EnterpriseInstallAttributes::LOCK_NOT_READY
));
233 scoped_refptr
<content::MessageLoopRunner
> runner
=
234 new content::MessageLoopRunner
;
235 g_browser_process
->browser_policy_connector()->GetInstallAttributes()->
238 policy::DEVICE_MODE_ENTERPRISE
,
240 base::Bind(&OnEnterpriseDeviceLock
,
242 runner
->QuitClosure()));
244 return *lock_result
.get();
247 void SetExistingApp(const std::string
& app_id
,
248 const std::string
& app_name
,
249 const std::string
& icon_file_name
) {
250 base::FilePath test_dir
;
251 ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA
, &test_dir
));
252 base::FilePath data_dir
= test_dir
.AppendASCII("chromeos/app_mode/");
254 // Copy the icon file to temp dir for using because ClearAppData test
256 base::FilePath icon_path
= temp_dir_
.path().AppendASCII(icon_file_name
);
257 base::CopyFile(data_dir
.AppendASCII(icon_file_name
), icon_path
);
259 scoped_ptr
<base::DictionaryValue
> apps_dict(new base::DictionaryValue
);
260 apps_dict
->SetString(app_id
+ ".name", app_name
);
261 apps_dict
->SetString(app_id
+ ".icon", icon_path
.MaybeAsASCII());
263 PrefService
* local_state
= g_browser_process
->local_state();
264 DictionaryPrefUpdate
dict_update(local_state
,
265 KioskAppManager::kKioskDictionaryName
);
266 dict_update
->Set(KioskAppManager::kKeyApps
, apps_dict
.release());
268 // Make the app appear in device settings.
269 base::ListValue device_local_accounts
;
270 scoped_ptr
<base::DictionaryValue
> entry(new base::DictionaryValue
);
271 entry
->SetStringWithoutPathExpansion(
272 kAccountsPrefDeviceLocalAccountsKeyId
,
274 entry
->SetIntegerWithoutPathExpansion(
275 kAccountsPrefDeviceLocalAccountsKeyType
,
276 policy::DeviceLocalAccount::TYPE_KIOSK_APP
);
277 entry
->SetStringWithoutPathExpansion(
278 kAccountsPrefDeviceLocalAccountsKeyKioskAppId
,
280 device_local_accounts
.Append(entry
.release());
281 CrosSettings::Get()->Set(kAccountsPrefDeviceLocalAccounts
,
282 device_local_accounts
);
285 KioskAppManager
* manager() const { return KioskAppManager::Get(); }
288 base::ScopedTempDir temp_dir_
;
290 DISALLOW_COPY_AND_ASSIGN(KioskAppManagerTest
);
293 IN_PROC_BROWSER_TEST_F(KioskAppManagerTest
, Basic
) {
294 // Add a couple of apps. Use "fake_app_x" that do not have data on the test
295 // server to avoid pending data loads that could be lingering on tear down and
296 // cause DCHECK failure in utility_process_host_impl.cc.
297 manager()->AddApp("fake_app_1");
298 manager()->AddApp("fake_app_2");
299 EXPECT_EQ("fake_app_1,fake_app_2", GetAppIds());
301 // Set an auto launch app.
302 manager()->SetAutoLaunchApp("fake_app_1");
303 EXPECT_EQ("fake_app_1", manager()->GetAutoLaunchApp());
305 // Clear the auto launch app.
306 manager()->SetAutoLaunchApp("");
307 EXPECT_EQ("", manager()->GetAutoLaunchApp());
308 EXPECT_FALSE(manager()->IsAutoLaunchEnabled());
310 // Set another auto launch app.
311 manager()->SetAutoLaunchApp("fake_app_2");
312 EXPECT_EQ("fake_app_2", manager()->GetAutoLaunchApp());
314 // Check auto launch permissions.
315 EXPECT_FALSE(manager()->IsAutoLaunchEnabled());
316 manager()->SetEnableAutoLaunch(true);
317 EXPECT_TRUE(manager()->IsAutoLaunchEnabled());
319 // Remove the auto launch app.
320 manager()->RemoveApp("fake_app_2");
321 EXPECT_EQ("fake_app_1", GetAppIds());
322 EXPECT_EQ("", manager()->GetAutoLaunchApp());
324 // Add the just removed auto launch app again and it should no longer be
325 // the auto launch app.
326 manager()->AddApp("fake_app_2");
327 EXPECT_EQ("", manager()->GetAutoLaunchApp());
328 manager()->RemoveApp("fake_app_2");
329 EXPECT_EQ("fake_app_1", GetAppIds());
331 // Set a none exist app as auto launch.
332 manager()->SetAutoLaunchApp("none_exist_app");
333 EXPECT_EQ("", manager()->GetAutoLaunchApp());
334 EXPECT_FALSE(manager()->IsAutoLaunchEnabled());
336 // Add an existing app again.
337 manager()->AddApp("fake_app_1");
338 EXPECT_EQ("fake_app_1", GetAppIds());
341 IN_PROC_BROWSER_TEST_F(KioskAppManagerTest
, LoadCached
) {
342 SetExistingApp("app_1", "Cached App1 Name", "red16x16.png");
344 AppDataLoadWaiter
waiter(manager());
346 EXPECT_TRUE(waiter
.loaded());
348 KioskAppManager::Apps apps
;
349 manager()->GetApps(&apps
);
350 EXPECT_EQ(1u, apps
.size());
351 EXPECT_EQ("app_1", apps
[0].app_id
);
352 EXPECT_EQ("Cached App1 Name", apps
[0].name
);
353 EXPECT_EQ(gfx::Size(16, 16), apps
[0].icon
.size());
356 IN_PROC_BROWSER_TEST_F(KioskAppManagerTest
, ClearAppData
) {
357 SetExistingApp("app_1", "Cached App1 Name", "red16x16.png");
359 PrefService
* local_state
= g_browser_process
->local_state();
360 const base::DictionaryValue
* dict
=
361 local_state
->GetDictionary(KioskAppManager::kKioskDictionaryName
);
362 const base::DictionaryValue
* apps_dict
;
363 EXPECT_TRUE(dict
->GetDictionary(KioskAppManager::kKeyApps
, &apps_dict
));
364 EXPECT_TRUE(apps_dict
->HasKey("app_1"));
366 manager()->ClearAppData("app_1");
368 EXPECT_FALSE(apps_dict
->HasKey("app_1"));
371 IN_PROC_BROWSER_TEST_F(KioskAppManagerTest
, UpdateAppDataFromProfile
) {
372 SetExistingApp("app_1", "Cached App1 Name", "red16x16.png");
374 AppDataLoadWaiter
waiter(manager());
376 EXPECT_TRUE(waiter
.loaded());
378 KioskAppManager::Apps apps
;
379 manager()->GetApps(&apps
);
380 EXPECT_EQ(1u, apps
.size());
381 EXPECT_EQ("app_1", apps
[0].app_id
);
382 EXPECT_EQ("Cached App1 Name", apps
[0].name
);
384 scoped_refptr
<extensions::Extension
> updated_app
=
385 MakeApp("Updated App1 Name", "2.0", "http://localhost/", "app_1");
386 manager()->UpdateAppDataFromProfile(
387 "app_1", browser()->profile(), updated_app
.get());
390 EXPECT_TRUE(waiter
.loaded());
392 manager()->GetApps(&apps
);
393 EXPECT_EQ(1u, apps
.size());
394 EXPECT_EQ("app_1", apps
[0].app_id
);
395 EXPECT_EQ("Updated App1 Name", apps
[0].name
);
398 IN_PROC_BROWSER_TEST_F(KioskAppManagerTest
, BadApp
) {
399 manager()->AddApp("unknown_app");
401 TestKioskAppManagerObserver
observer(manager());
403 AppDataLoadWaiter
waiter(manager());
405 EXPECT_FALSE(waiter
.loaded());
407 EXPECT_EQ("", GetAppIds());
408 EXPECT_EQ(1, observer
.load_failure_count());
411 IN_PROC_BROWSER_TEST_F(KioskAppManagerTest
, GoodApp
) {
412 // Webstore data json is in
413 // chrome/test/data/chromeos/app_mode/webstore/inlineinstall/detail/app_1
414 manager()->AddApp("app_1");
416 AppDataLoadWaiter
waiter(manager());
418 EXPECT_TRUE(waiter
.loaded());
420 // Check data is correct.
421 KioskAppManager::Apps apps
;
422 manager()->GetApps(&apps
);
423 ASSERT_EQ(1u, apps
.size());
424 EXPECT_EQ("app_1", apps
[0].app_id
);
425 EXPECT_EQ("Name of App 1", apps
[0].name
);
426 EXPECT_EQ(gfx::Size(16, 16), apps
[0].icon
.size());
428 // Check data is cached in local state.
429 PrefService
* local_state
= g_browser_process
->local_state();
430 const base::DictionaryValue
* dict
=
431 local_state
->GetDictionary(KioskAppManager::kKioskDictionaryName
);
434 EXPECT_TRUE(dict
->GetString("apps.app_1.name", &name
));
435 EXPECT_EQ(apps
[0].name
, name
);
437 std::string icon_path_string
;
438 EXPECT_TRUE(dict
->GetString("apps.app_1.icon", &icon_path_string
));
440 base::FilePath expected_icon_path
;
441 ASSERT_TRUE(PathService::Get(chrome::DIR_USER_DATA
, &expected_icon_path
));
442 expected_icon_path
= expected_icon_path
.
443 AppendASCII(KioskAppManager::kIconCacheDir
).
444 AppendASCII(apps
[0].app_id
).AddExtension(".png");
445 EXPECT_EQ(expected_icon_path
.value(), icon_path_string
);
448 IN_PROC_BROWSER_TEST_F(KioskAppManagerTest
, EnableConsumerKiosk
) {
449 scoped_ptr
<KioskAppManager::ConsumerKioskModeStatus
> status(
450 new KioskAppManager::ConsumerKioskModeStatus(
451 KioskAppManager::CONSUMER_KIOSK_MODE_DISABLED
));
452 scoped_ptr
<bool> locked(new bool(false));
454 scoped_refptr
<content::MessageLoopRunner
> runner
=
455 new content::MessageLoopRunner
;
456 manager()->GetConsumerKioskModeStatus(
457 base::Bind(&ConsumerKioskModeStatusCheck
,
459 runner
->QuitClosure()));
461 EXPECT_EQ(*status
.get(), KioskAppManager::CONSUMER_KIOSK_MODE_CONFIGURABLE
);
463 scoped_refptr
<content::MessageLoopRunner
> runner2
=
464 new content::MessageLoopRunner
;
465 manager()->EnableConsumerModeKiosk(
466 base::Bind(&ConsumerKioskModeLockCheck
,
468 runner2
->QuitClosure()));
470 EXPECT_TRUE(*locked
.get());
472 scoped_refptr
<content::MessageLoopRunner
> runner3
=
473 new content::MessageLoopRunner
;
474 manager()->GetConsumerKioskModeStatus(
475 base::Bind(&ConsumerKioskModeStatusCheck
,
477 runner3
->QuitClosure()));
479 EXPECT_EQ(*status
.get(), KioskAppManager::CONSUMER_KIOSK_MODE_ENABLED
);
482 IN_PROC_BROWSER_TEST_F(KioskAppManagerTest
,
483 PreventEnableConsumerKioskForEnterprise
) {
484 // First, lock the device as enterprise.
485 EXPECT_EQ(LockDeviceForEnterprise(),
486 policy::EnterpriseInstallAttributes::LOCK_SUCCESS
);
488 scoped_ptr
<KioskAppManager::ConsumerKioskModeStatus
> status(
489 new KioskAppManager::ConsumerKioskModeStatus(
490 KioskAppManager::CONSUMER_KIOSK_MODE_DISABLED
));
491 scoped_ptr
<bool> locked(new bool(true));
493 scoped_refptr
<content::MessageLoopRunner
> runner
=
494 new content::MessageLoopRunner
;
495 manager()->GetConsumerKioskModeStatus(
496 base::Bind(&ConsumerKioskModeStatusCheck
,
498 runner
->QuitClosure()));
500 EXPECT_EQ(*status
.get(), KioskAppManager::CONSUMER_KIOSK_MODE_DISABLED
);
502 scoped_refptr
<content::MessageLoopRunner
> runner2
=
503 new content::MessageLoopRunner
;
504 manager()->EnableConsumerModeKiosk(
505 base::Bind(&ConsumerKioskModeLockCheck
,
507 runner2
->QuitClosure()));
509 EXPECT_FALSE(*locked
.get());
511 scoped_refptr
<content::MessageLoopRunner
> runner3
=
512 new content::MessageLoopRunner
;
513 manager()->GetConsumerKioskModeStatus(
514 base::Bind(&ConsumerKioskModeStatusCheck
,
516 runner3
->QuitClosure()));
518 EXPECT_EQ(*status
.get(), KioskAppManager::CONSUMER_KIOSK_MODE_DISABLED
);
521 } // namespace chromeos