OLD | NEW |
---|---|
(Empty) | |
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 <memory> | |
6 #include <string> | |
7 #include <vector> | |
8 | |
9 #include "apps/test/app_window_waiter.h" | |
10 #include "base/base64.h" | |
11 #include "base/command_line.h" | |
12 #include "base/files/file_path.h" | |
13 #include "base/json/json_file_value_serializer.h" | |
14 #include "base/logging.h" | |
15 #include "base/path_service.h" | |
16 #include "base/run_loop.h" | |
17 #include "base/values.h" | |
18 #include "chrome/browser/chrome_notification_types.h" | |
19 #include "chrome/browser/chromeos/app_mode/fake_cws.h" | |
20 #include "chrome/browser/chromeos/app_mode/kiosk_app_manager.h" | |
21 #include "chrome/browser/chromeos/login/app_launch_controller.h" | |
22 #include "chrome/browser/chromeos/ownership/owner_settings_service_chromeos_fact ory.h" | |
23 #include "chrome/browser/chromeos/policy/device_local_account.h" | |
24 #include "chrome/browser/chromeos/policy/device_policy_builder.h" | |
25 #include "chrome/browser/chromeos/settings/stub_install_attributes.h" | |
26 #include "chrome/browser/extensions/browsertest_util.h" | |
27 #include "chrome/browser/extensions/extension_apitest.h" | |
28 #include "chrome/browser/profiles/profile_manager.h" | |
29 #include "chrome/common/chrome_constants.h" | |
30 #include "chrome/common/chrome_paths.h" | |
31 #include "chrome/common/pref_names.h" | |
32 #include "chromeos/dbus/cryptohome_client.h" | |
33 #include "chromeos/dbus/dbus_thread_manager.h" | |
34 #include "chromeos/dbus/fake_session_manager_client.h" | |
35 #include "chromeos/dbus/shill_manager_client.h" | |
36 #include "components/ownership/mock_owner_key_util.h" | |
37 #include "content/public/browser/notification_observer.h" | |
38 #include "content/public/browser/notification_registrar.h" | |
39 #include "content/public/browser/notification_service.h" | |
40 #include "extensions/browser/app_window/app_window.h" | |
41 #include "extensions/browser/app_window/app_window_registry.h" | |
42 #include "extensions/browser/app_window/native_app_window.h" | |
43 #include "extensions/common/value_builder.h" | |
44 #include "extensions/test/extension_test_message_listener.h" | |
45 #include "net/dns/mock_host_resolver.h" | |
46 #include "third_party/cros_system_api/switches/chrome_switches.h" | |
47 | |
48 namespace em = enterprise_management; | |
49 | |
50 namespace chromeos { | |
51 | |
52 namespace { | |
53 | |
54 // This is a simple test app that creates an app window and immediately closes | |
55 // it again. Webstore data json is in | |
56 // chrome/test/data/chromeos/app_mode/webstore/inlineinstall/ | |
57 // detail/ggbflgnkafappblpkiflbgpmkfdpnhhe | |
58 const char kTestKioskApp[] = "ggbflgnkafappblpkiflbgpmkfdpnhhe"; | |
59 | |
60 const char kTestAccountId[] = "enterprise-kiosk-app@localhost"; | |
61 | |
62 const char kSessionManagerStateCache[] = "test_session_manager_state.json"; | |
63 | |
64 // Keys for values in dictionary used to preserve session manager state. | |
65 const char kLoginArgsKey[] = "login_args"; | |
66 const char kExtraArgsKey[] = "extra_args"; | |
67 const char kArgNameKey[] = "name"; | |
68 const char kArgValueKey[] = "value"; | |
69 | |
70 // Default set policy switches. | |
71 const struct { | |
72 const char* name; | |
73 const char* value; | |
74 } kDefaultPolicySwitches[] = {{"test_switch_1", ""}, | |
75 {"test_switch_2", "test_switch_2_value"}}; | |
76 | |
77 // Fake session manager implementation that persists its state in local file. | |
78 // It can be used to preserve session state in PRE_ browser tests. | |
79 // Primarily used for testing user/login switches. | |
80 class PersistentSessionManagerClient : public FakeSessionManagerClient { | |
81 public: | |
82 PersistentSessionManagerClient() {} | |
83 | |
84 ~PersistentSessionManagerClient() override { | |
85 PersistFlagsToFile(backing_file_); | |
86 } | |
87 | |
88 // Initializes session state (primarily session flags)- if |backing_file| | |
89 // exists, the session state is restored from the file value. Otherwise it's | |
90 // set to the default session state. | |
91 void Initialize(const base::FilePath& backing_file) { | |
92 backing_file_ = backing_file; | |
93 | |
94 if (ExtractFlagsFromFile(backing_file_)) | |
95 return; | |
96 | |
97 // Failed to extract ached flags - set the default values. | |
98 login_args_ = {{"login-manager", ""}}; | |
99 | |
100 extra_args_ = {{switches::kPolicySwitchesBegin, ""}}; | |
101 for (size_t i = 0; i < arraysize(kDefaultPolicySwitches); ++i) | |
102 extra_args_.push_back( | |
103 {kDefaultPolicySwitches[i].name, kDefaultPolicySwitches[i].value}); | |
xiyuan
2017/01/25 21:06:43
nit: wrap in {} since the body > 1 line
tbarzic
2017/01/25 21:33:43
Done.
| |
104 extra_args_.push_back({switches::kPolicySwitchesEnd, ""}); | |
105 } | |
106 | |
107 void AppendSwitchesToCommandLine(base::CommandLine* command_line) { | |
108 for (const auto& flag : login_args_) | |
109 command_line->AppendSwitchASCII(flag.name, flag.value); | |
110 for (const auto& flag : extra_args_) | |
111 command_line->AppendSwitchASCII(flag.name, flag.value); | |
112 } | |
113 | |
114 void StartSession(const cryptohome::Identification& cryptohome_id) override { | |
115 FakeSessionManagerClient::StartSession(cryptohome_id); | |
116 | |
117 std::string user_id_hash = | |
118 CryptohomeClient::GetStubSanitizedUsername(cryptohome_id); | |
119 login_args_ = {{"login-user", cryptohome_id.id()}, | |
120 {"login-profile", user_id_hash}}; | |
121 } | |
122 | |
123 void StopSession() override { | |
124 FakeSessionManagerClient::StopSession(); | |
125 | |
126 login_args_ = {{"login-manager", ""}}; | |
127 } | |
128 | |
129 bool SupportsRestartToApplyUserFlags() const override { return true; } | |
130 | |
131 void SetFlagsForUser(const cryptohome::Identification& identification, | |
132 const std::vector<std::string>& flags) override { | |
133 extra_args_.clear(); | |
134 FakeSessionManagerClient::SetFlagsForUser(identification, flags); | |
135 | |
136 std::vector<std::string> argv = {"" /* Empty program */}; | |
137 argv.insert(argv.end(), flags.begin(), flags.end()); | |
138 | |
139 // Parse flag name-value pairs using command line initialization. | |
140 base::CommandLine cmd_line(base::CommandLine::NO_PROGRAM); | |
141 cmd_line.InitFromArgv(argv); | |
142 | |
143 for (const auto& flag : cmd_line.GetSwitches()) | |
144 extra_args_.push_back({flag.first, flag.second}); | |
145 } | |
146 | |
147 private: | |
148 // Keeps information about a switch - its name and value. | |
149 struct Switch { | |
150 std::string name; | |
151 std::string value; | |
152 }; | |
153 | |
154 bool ExtractFlagsFromFile(const base::FilePath& backing_file) { | |
155 JSONFileValueDeserializer deserializer(backing_file); | |
156 | |
157 int error_code = 0; | |
158 std::unique_ptr<base::Value> value = | |
159 deserializer.Deserialize(&error_code, nullptr); | |
160 if (error_code != JSONFileValueDeserializer::JSON_NO_ERROR) | |
161 return false; | |
162 | |
163 std::unique_ptr<base::DictionaryValue> value_dict = | |
164 base::DictionaryValue::From(std::move(value)); | |
165 DCHECK(value_dict); | |
166 | |
167 CHECK(InitArgListFromCachedValue(*value_dict, kLoginArgsKey, &login_args_)); | |
168 CHECK(InitArgListFromCachedValue(*value_dict, kExtraArgsKey, &extra_args_)); | |
169 return true; | |
170 } | |
171 | |
172 bool PersistFlagsToFile(const base::FilePath& backing_file) { | |
173 base::DictionaryValue cached_state; | |
174 cached_state.Set(kLoginArgsKey, GetArgListValue(login_args_)); | |
175 cached_state.Set(kExtraArgsKey, GetArgListValue(extra_args_)); | |
176 | |
177 JSONFileValueSerializer serializer(backing_file); | |
178 return serializer.Serialize(cached_state); | |
179 } | |
180 | |
181 std::unique_ptr<base::ListValue> GetArgListValue( | |
182 const std::vector<Switch>& args) { | |
183 std::unique_ptr<base::ListValue> result(new base::ListValue()); | |
184 for (const auto& arg : args) { | |
185 result->Append(extensions::DictionaryBuilder() | |
186 .Set(kArgNameKey, arg.name) | |
187 .Set(kArgValueKey, arg.value) | |
188 .Build()); | |
189 } | |
190 return result; | |
191 } | |
192 | |
193 bool InitArgListFromCachedValue(const base::DictionaryValue& cache_value, | |
194 const std::string& list_key, | |
195 std::vector<Switch>* arg_list_out) { | |
196 arg_list_out->clear(); | |
197 const base::ListValue* arg_list_value; | |
198 if (!cache_value.GetList(list_key, &arg_list_value)) | |
199 return false; | |
200 for (size_t i = 0; i < arg_list_value->GetSize(); ++i) { | |
201 const base::DictionaryValue* arg_value; | |
202 if (!arg_list_value->GetDictionary(i, &arg_value)) | |
203 return false; | |
204 Switch arg; | |
205 if (!arg_value->GetStringASCII(kArgNameKey, &arg.name) || | |
206 !arg_value->GetStringASCII(kArgValueKey, &arg.value)) { | |
207 return false; | |
208 } | |
209 arg_list_out->push_back(arg); | |
210 } | |
211 return true; | |
212 } | |
213 | |
214 std::vector<Switch> login_args_; | |
215 std::vector<Switch> extra_args_; | |
216 | |
217 base::FilePath backing_file_; | |
218 | |
219 DISALLOW_COPY_AND_ASSIGN(PersistentSessionManagerClient); | |
220 }; | |
221 | |
222 // Used to listen for app termination notification. | |
223 class TerminationObserver : public content::NotificationObserver { | |
224 public: | |
225 TerminationObserver() { | |
226 registrar_.Add(this, chrome::NOTIFICATION_APP_TERMINATING, | |
227 content::NotificationService::AllSources()); | |
228 } | |
229 ~TerminationObserver() override = default; | |
230 | |
231 // Whether app has been terminated - i.e. whether app termination notification | |
232 // has been observed. | |
233 bool terminated() const { return notification_seen_; } | |
234 | |
235 private: | |
236 void Observe(int type, | |
237 const content::NotificationSource& source, | |
238 const content::NotificationDetails& details) override { | |
239 ASSERT_EQ(chrome::NOTIFICATION_APP_TERMINATING, type); | |
240 notification_seen_ = true; | |
241 } | |
242 | |
243 bool notification_seen_ = false; | |
244 content::NotificationRegistrar registrar_; | |
245 | |
246 DISALLOW_COPY_AND_ASSIGN(TerminationObserver); | |
247 }; | |
248 | |
249 } // namespace | |
250 | |
251 class AutoLaunchedKioskTest : public ExtensionApiTest { | |
252 public: | |
253 AutoLaunchedKioskTest() | |
254 : install_attributes_( | |
255 chromeos::ScopedStubInstallAttributes::CreateEnterprise( | |
256 "domain.com", | |
257 "device_id")), | |
258 owner_key_util_(new ownership::MockOwnerKeyUtil()), | |
259 fake_session_manager_(new PersistentSessionManagerClient()), | |
260 fake_cws_(new FakeCWS) { | |
261 set_chromeos_user_ = false; | |
262 } | |
263 | |
264 ~AutoLaunchedKioskTest() override = default; | |
265 | |
266 void SetUp() override { | |
267 ASSERT_TRUE(embedded_test_server()->InitializeAndListen()); | |
268 AppLaunchController::SkipSplashWaitForTesting(); | |
269 | |
270 ExtensionApiTest::SetUp(); | |
271 } | |
272 | |
273 void SetUpCommandLine(base::CommandLine* command_line) override { | |
274 fake_cws_->Init(embedded_test_server()); | |
275 fake_cws_->SetUpdateCrx(kTestKioskApp, std::string(kTestKioskApp) + ".crx", | |
276 "1.0.0"); | |
277 ExtensionApiTest::SetUpCommandLine(command_line); | |
278 } | |
279 | |
280 bool SetUpUserDataDirectory() override { | |
281 InitDevicePolicy(); | |
282 | |
283 base::FilePath user_data_path; | |
284 CHECK(PathService::Get(chrome::DIR_USER_DATA, &user_data_path)); | |
285 CacheDevicePolicyToLocalState(user_data_path); | |
286 | |
287 // Restore session_manager state and ensure session manager flags are | |
288 // applied. | |
289 fake_session_manager_->Initialize( | |
290 user_data_path.Append(kSessionManagerStateCache)); | |
291 fake_session_manager_->AppendSwitchesToCommandLine( | |
292 base::CommandLine::ForCurrentProcess()); | |
293 | |
294 return true; | |
295 } | |
296 | |
297 void SetUpInProcessBrowserTestFixture() override { | |
298 host_resolver()->AddRule("*", "127.0.0.1"); | |
299 | |
300 OwnerSettingsServiceChromeOSFactory::GetInstance() | |
301 ->SetOwnerKeyUtilForTesting(owner_key_util_); | |
302 owner_key_util_->SetPublicKeyFromPrivateKey( | |
303 *device_policy_.GetSigningKey()); | |
304 | |
305 fake_session_manager_->set_device_policy(device_policy_.GetBlob()); | |
306 DBusThreadManager::GetSetterForTesting()->SetSessionManagerClient( | |
307 std::move(fake_session_manager_)); | |
308 | |
309 ExtensionApiTest::SetUpInProcessBrowserTestFixture(); | |
310 } | |
311 | |
312 void SetUpOnMainThread() override { | |
313 extensions::browsertest_util::CreateAndInitializeLocalCache(); | |
314 | |
315 embedded_test_server()->StartAcceptingConnections(); | |
316 | |
317 ExtensionApiTest::SetUpOnMainThread(); | |
318 } | |
319 | |
320 void RunTestOnMainThreadLoop() override { | |
321 termination_observer_.reset(new TerminationObserver()); | |
322 | |
323 ExtensionApiTest::RunTestOnMainThreadLoop(); | |
324 } | |
325 | |
326 void TearDownOnMainThread() override { | |
327 termination_observer_.reset(); | |
328 | |
329 ExtensionApiTest::TearDownOnMainThread(); | |
330 } | |
331 | |
332 void InitDevicePolicy() { | |
333 // Create device policy, and cache it to local state. | |
334 em::DeviceLocalAccountsProto* const device_local_accounts = | |
335 device_policy_.payload().mutable_device_local_accounts(); | |
336 | |
337 em::DeviceLocalAccountInfoProto* const account = | |
338 device_local_accounts->add_account(); | |
339 account->set_account_id(kTestAccountId); | |
340 account->set_type(em::DeviceLocalAccountInfoProto::ACCOUNT_TYPE_KIOSK_APP); | |
341 account->mutable_kiosk_app()->set_app_id(kTestKioskApp); | |
342 | |
343 device_local_accounts->set_auto_login_id(kTestAccountId); | |
344 | |
345 device_policy_.Build(); | |
346 } | |
347 | |
348 void CacheDevicePolicyToLocalState(const base::FilePath& user_data_path) { | |
349 em::PolicyData policy_data; | |
350 DCHECK(device_policy_.payload().SerializeToString( | |
351 policy_data.mutable_policy_value())); | |
352 const std::string policy_data_str = policy_data.SerializeAsString(); | |
353 std::string policy_data_encoded; | |
354 base::Base64Encode(policy_data_str, &policy_data_encoded); | |
355 | |
356 std::unique_ptr<base::DictionaryValue> local_state = | |
357 extensions::DictionaryBuilder() | |
358 .Set(prefs::kDeviceSettingsCache, policy_data_encoded) | |
359 .Set("PublicAccounts", | |
360 extensions::ListBuilder().Append(GetTestAppUserId()).Build()) | |
361 .Build(); | |
362 | |
363 JSONFileValueSerializer serializer( | |
364 user_data_path.Append(chrome::kLocalStateFilename)); | |
365 CHECK(serializer.Serialize(*local_state)); | |
366 } | |
367 | |
368 const std::string GetTestAppUserId() const { | |
369 return policy::GenerateDeviceLocalAccountUserId( | |
370 kTestAccountId, policy::DeviceLocalAccount::TYPE_KIOSK_APP); | |
371 } | |
372 | |
373 bool CloseAppWindow(const std::string& app_id) { | |
374 Profile* const app_profile = ProfileManager::GetPrimaryUserProfile(); | |
375 if (!app_profile) { | |
376 ADD_FAILURE() << "No primary (app) profile."; | |
377 return false; | |
378 } | |
379 | |
380 extensions::AppWindowRegistry* const app_window_registry = | |
381 extensions::AppWindowRegistry::Get(app_profile); | |
382 extensions::AppWindow* const window = | |
383 apps::AppWindowWaiter(app_window_registry, app_id).Wait(); | |
384 if (!window) { | |
385 ADD_FAILURE() << "No app window found for " << app_id << "."; | |
386 return false; | |
387 } | |
388 | |
389 window->GetBaseWindow()->Close(); | |
390 | |
391 // Wait until the app terminates if it is still running. | |
392 if (!app_window_registry->GetAppWindowsForApp(app_id).empty()) | |
393 base::RunLoop().Run(); | |
394 return true; | |
395 } | |
396 | |
397 bool IsKioskAppAutoLaunched(const std::string& app_id) { | |
398 KioskAppManager::App app; | |
399 if (!KioskAppManager::Get()->GetApp(app_id, &app)) { | |
400 ADD_FAILURE() << "App " << app_id << " not found."; | |
401 return false; | |
402 } | |
403 return app.was_auto_launched_with_zero_delay; | |
404 } | |
405 | |
406 void ExpectCommandLineHasDefaultPolicySwitches( | |
407 const base::CommandLine& cmd_line) { | |
408 for (size_t i = 0u; i < arraysize(kDefaultPolicySwitches); ++i) { | |
409 EXPECT_TRUE(cmd_line.HasSwitch(kDefaultPolicySwitches[i].name)) | |
410 << "Missing flag " << kDefaultPolicySwitches[i].name; | |
411 EXPECT_EQ(kDefaultPolicySwitches[i].value, | |
412 cmd_line.GetSwitchValueASCII(kDefaultPolicySwitches[i].name)) | |
413 << "Invalid value for switch " << kDefaultPolicySwitches[i].name; | |
414 } | |
415 } | |
416 | |
417 protected: | |
418 std::unique_ptr<TerminationObserver> termination_observer_; | |
419 | |
420 private: | |
421 chromeos::ScopedStubInstallAttributes install_attributes_; | |
422 policy::DevicePolicyBuilder device_policy_; | |
423 scoped_refptr<ownership::MockOwnerKeyUtil> owner_key_util_; | |
424 std::unique_ptr<PersistentSessionManagerClient> fake_session_manager_; | |
425 std::unique_ptr<FakeCWS> fake_cws_; | |
426 | |
427 DISALLOW_COPY_AND_ASSIGN(AutoLaunchedKioskTest); | |
428 }; | |
429 | |
430 IN_PROC_BROWSER_TEST_F(AutoLaunchedKioskTest, PRE_CrashRestore) { | |
431 // Verify that Chrome hasn't already exited, e.g. in order to apply user | |
432 // session flags. | |
433 ASSERT_FALSE(termination_observer_->terminated()); | |
434 | |
435 // Set up default network connections, so tests think the device is online. | |
436 DBusThreadManager::Get() | |
437 ->GetShillManagerClient() | |
438 ->GetTestInterface() | |
439 ->SetupDefaultEnvironment(); | |
440 | |
441 // Check that policy flags have not been lost. | |
442 ExpectCommandLineHasDefaultPolicySwitches( | |
443 *base::CommandLine::ForCurrentProcess()); | |
444 | |
445 ExtensionTestMessageListener listener("appWindowLoaded", false); | |
446 EXPECT_TRUE(listener.WaitUntilSatisfied()); | |
447 | |
448 EXPECT_TRUE(IsKioskAppAutoLaunched(kTestKioskApp)); | |
449 | |
450 ASSERT_TRUE(CloseAppWindow(kTestKioskApp)); | |
451 } | |
452 | |
453 IN_PROC_BROWSER_TEST_F(AutoLaunchedKioskTest, CrashRestore) { | |
454 // Verify that Chrome hasn't already exited, e.g. in order to apply user | |
455 // session flags. | |
456 ASSERT_FALSE(termination_observer_->terminated()); | |
457 | |
458 ExpectCommandLineHasDefaultPolicySwitches( | |
459 *base::CommandLine::ForCurrentProcess()); | |
460 | |
461 ExtensionTestMessageListener listener("appWindowLoaded", false); | |
462 EXPECT_TRUE(listener.WaitUntilSatisfied()); | |
463 | |
464 EXPECT_TRUE(IsKioskAppAutoLaunched(kTestKioskApp)); | |
465 | |
466 ASSERT_TRUE(CloseAppWindow(kTestKioskApp)); | |
467 } | |
468 | |
469 } // namespace chromeos | |
OLD | NEW |