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}); | |
104 } | |
105 extra_args_.push_back({switches::kPolicySwitchesEnd, ""}); | |
106 } | |
107 | |
108 void AppendSwitchesToCommandLine(base::CommandLine* command_line) { | |
109 for (const auto& flag : login_args_) | |
110 command_line->AppendSwitchASCII(flag.name, flag.value); | |
111 for (const auto& flag : extra_args_) | |
112 command_line->AppendSwitchASCII(flag.name, flag.value); | |
113 } | |
114 | |
115 void StartSession(const cryptohome::Identification& cryptohome_id) override { | |
116 FakeSessionManagerClient::StartSession(cryptohome_id); | |
117 | |
118 std::string user_id_hash = | |
119 CryptohomeClient::GetStubSanitizedUsername(cryptohome_id); | |
120 login_args_ = {{"login-user", cryptohome_id.id()}, | |
121 {"login-profile", user_id_hash}}; | |
122 } | |
123 | |
124 void StopSession() override { | |
125 FakeSessionManagerClient::StopSession(); | |
126 | |
127 login_args_ = {{"login-manager", ""}}; | |
128 } | |
129 | |
130 bool SupportsRestartToApplyUserFlags() const override { return true; } | |
131 | |
132 void SetFlagsForUser(const cryptohome::Identification& identification, | |
133 const std::vector<std::string>& flags) override { | |
134 extra_args_.clear(); | |
135 FakeSessionManagerClient::SetFlagsForUser(identification, flags); | |
136 | |
137 std::vector<std::string> argv = {"" /* Empty program */}; | |
138 argv.insert(argv.end(), flags.begin(), flags.end()); | |
139 | |
140 // Parse flag name-value pairs using command line initialization. | |
141 base::CommandLine cmd_line(base::CommandLine::NO_PROGRAM); | |
142 cmd_line.InitFromArgv(argv); | |
143 | |
144 for (const auto& flag : cmd_line.GetSwitches()) | |
145 extra_args_.push_back({flag.first, flag.second}); | |
146 } | |
147 | |
148 private: | |
149 // Keeps information about a switch - its name and value. | |
150 struct Switch { | |
151 std::string name; | |
152 std::string value; | |
153 }; | |
154 | |
155 bool ExtractFlagsFromFile(const base::FilePath& backing_file) { | |
156 JSONFileValueDeserializer deserializer(backing_file); | |
157 | |
158 int error_code = 0; | |
159 std::unique_ptr<base::Value> value = | |
160 deserializer.Deserialize(&error_code, nullptr); | |
161 if (error_code != JSONFileValueDeserializer::JSON_NO_ERROR) | |
162 return false; | |
163 | |
164 std::unique_ptr<base::DictionaryValue> value_dict = | |
165 base::DictionaryValue::From(std::move(value)); | |
166 DCHECK(value_dict); | |
167 | |
168 CHECK(InitArgListFromCachedValue(*value_dict, kLoginArgsKey, &login_args_)); | |
169 CHECK(InitArgListFromCachedValue(*value_dict, kExtraArgsKey, &extra_args_)); | |
170 return true; | |
171 } | |
172 | |
173 bool PersistFlagsToFile(const base::FilePath& backing_file) { | |
174 base::DictionaryValue cached_state; | |
175 cached_state.Set(kLoginArgsKey, GetArgListValue(login_args_)); | |
176 cached_state.Set(kExtraArgsKey, GetArgListValue(extra_args_)); | |
177 | |
178 JSONFileValueSerializer serializer(backing_file); | |
179 return serializer.Serialize(cached_state); | |
180 } | |
181 | |
182 std::unique_ptr<base::ListValue> GetArgListValue( | |
183 const std::vector<Switch>& args) { | |
184 std::unique_ptr<base::ListValue> result(new base::ListValue()); | |
185 for (const auto& arg : args) { | |
186 result->Append(extensions::DictionaryBuilder() | |
187 .Set(kArgNameKey, arg.name) | |
188 .Set(kArgValueKey, arg.value) | |
189 .Build()); | |
190 } | |
191 return result; | |
192 } | |
193 | |
194 bool InitArgListFromCachedValue(const base::DictionaryValue& cache_value, | |
195 const std::string& list_key, | |
196 std::vector<Switch>* arg_list_out) { | |
197 arg_list_out->clear(); | |
198 const base::ListValue* arg_list_value; | |
199 if (!cache_value.GetList(list_key, &arg_list_value)) | |
200 return false; | |
201 for (size_t i = 0; i < arg_list_value->GetSize(); ++i) { | |
202 const base::DictionaryValue* arg_value; | |
203 if (!arg_list_value->GetDictionary(i, &arg_value)) | |
204 return false; | |
205 Switch arg; | |
206 if (!arg_value->GetStringASCII(kArgNameKey, &arg.name) || | |
207 !arg_value->GetStringASCII(kArgValueKey, &arg.value)) { | |
208 return false; | |
209 } | |
210 arg_list_out->push_back(arg); | |
211 } | |
212 return true; | |
213 } | |
214 | |
215 std::vector<Switch> login_args_; | |
216 std::vector<Switch> extra_args_; | |
217 | |
218 base::FilePath backing_file_; | |
219 | |
220 DISALLOW_COPY_AND_ASSIGN(PersistentSessionManagerClient); | |
221 }; | |
222 | |
223 // Used to listen for app termination notification. | |
224 class TerminationObserver : public content::NotificationObserver { | |
225 public: | |
226 TerminationObserver() { | |
227 registrar_.Add(this, chrome::NOTIFICATION_APP_TERMINATING, | |
228 content::NotificationService::AllSources()); | |
229 } | |
230 ~TerminationObserver() override = default; | |
231 | |
232 // Whether app has been terminated - i.e. whether app termination notification | |
233 // has been observed. | |
234 bool terminated() const { return notification_seen_; } | |
235 | |
236 private: | |
237 void Observe(int type, | |
238 const content::NotificationSource& source, | |
239 const content::NotificationDetails& details) override { | |
240 ASSERT_EQ(chrome::NOTIFICATION_APP_TERMINATING, type); | |
241 notification_seen_ = true; | |
242 } | |
243 | |
244 bool notification_seen_ = false; | |
245 content::NotificationRegistrar registrar_; | |
246 | |
247 DISALLOW_COPY_AND_ASSIGN(TerminationObserver); | |
248 }; | |
249 | |
250 } // namespace | |
251 | |
252 class AutoLaunchedKioskTest : public ExtensionApiTest { | |
253 public: | |
254 AutoLaunchedKioskTest() | |
255 : install_attributes_( | |
256 chromeos::ScopedStubInstallAttributes::CreateEnterprise( | |
257 "domain.com", | |
258 "device_id")), | |
259 owner_key_util_(new ownership::MockOwnerKeyUtil()), | |
260 fake_session_manager_(new PersistentSessionManagerClient()), | |
261 fake_cws_(new FakeCWS) { | |
262 set_chromeos_user_ = false; | |
263 } | |
264 | |
265 ~AutoLaunchedKioskTest() override = default; | |
266 | |
267 void SetUp() override { | |
268 ASSERT_TRUE(embedded_test_server()->InitializeAndListen()); | |
269 AppLaunchController::SkipSplashWaitForTesting(); | |
270 | |
271 ExtensionApiTest::SetUp(); | |
272 } | |
273 | |
274 void SetUpCommandLine(base::CommandLine* command_line) override { | |
275 fake_cws_->Init(embedded_test_server()); | |
276 fake_cws_->SetUpdateCrx(kTestKioskApp, std::string(kTestKioskApp) + ".crx", | |
277 "1.0.0"); | |
278 ExtensionApiTest::SetUpCommandLine(command_line); | |
279 } | |
280 | |
281 bool SetUpUserDataDirectory() override { | |
282 InitDevicePolicy(); | |
283 | |
284 base::FilePath user_data_path; | |
285 CHECK(PathService::Get(chrome::DIR_USER_DATA, &user_data_path)); | |
286 CacheDevicePolicyToLocalState(user_data_path); | |
287 | |
288 // Restore session_manager state and ensure session manager flags are | |
289 // applied. | |
290 fake_session_manager_->Initialize( | |
291 user_data_path.Append(kSessionManagerStateCache)); | |
292 fake_session_manager_->AppendSwitchesToCommandLine( | |
293 base::CommandLine::ForCurrentProcess()); | |
294 | |
295 return true; | |
296 } | |
297 | |
298 void SetUpInProcessBrowserTestFixture() override { | |
299 host_resolver()->AddRule("*", "127.0.0.1"); | |
300 | |
301 OwnerSettingsServiceChromeOSFactory::GetInstance() | |
302 ->SetOwnerKeyUtilForTesting(owner_key_util_); | |
303 owner_key_util_->SetPublicKeyFromPrivateKey( | |
304 *device_policy_.GetSigningKey()); | |
305 | |
306 fake_session_manager_->set_device_policy(device_policy_.GetBlob()); | |
307 DBusThreadManager::GetSetterForTesting()->SetSessionManagerClient( | |
308 std::move(fake_session_manager_)); | |
309 | |
310 ExtensionApiTest::SetUpInProcessBrowserTestFixture(); | |
311 } | |
312 | |
313 void SetUpOnMainThread() override { | |
314 extensions::browsertest_util::CreateAndInitializeLocalCache(); | |
315 | |
316 embedded_test_server()->StartAcceptingConnections(); | |
317 | |
318 ExtensionApiTest::SetUpOnMainThread(); | |
319 } | |
320 | |
321 void RunTestOnMainThreadLoop() override { | |
322 termination_observer_.reset(new TerminationObserver()); | |
323 | |
324 ExtensionApiTest::RunTestOnMainThreadLoop(); | |
325 } | |
326 | |
327 void TearDownOnMainThread() override { | |
328 termination_observer_.reset(); | |
329 | |
330 ExtensionApiTest::TearDownOnMainThread(); | |
331 } | |
332 | |
333 void InitDevicePolicy() { | |
334 // Create device policy, and cache it to local state. | |
335 em::DeviceLocalAccountsProto* const device_local_accounts = | |
336 device_policy_.payload().mutable_device_local_accounts(); | |
337 | |
338 em::DeviceLocalAccountInfoProto* const account = | |
339 device_local_accounts->add_account(); | |
340 account->set_account_id(kTestAccountId); | |
341 account->set_type(em::DeviceLocalAccountInfoProto::ACCOUNT_TYPE_KIOSK_APP); | |
342 account->mutable_kiosk_app()->set_app_id(kTestKioskApp); | |
343 | |
344 device_local_accounts->set_auto_login_id(kTestAccountId); | |
345 | |
346 device_policy_.Build(); | |
347 } | |
348 | |
349 void CacheDevicePolicyToLocalState(const base::FilePath& user_data_path) { | |
350 em::PolicyData policy_data; | |
351 DCHECK(device_policy_.payload().SerializeToString( | |
tbarzic
2017/01/26 18:19:42
this DCHECK made Release builder sad :/
| |
352 policy_data.mutable_policy_value())); | |
353 const std::string policy_data_str = policy_data.SerializeAsString(); | |
354 std::string policy_data_encoded; | |
355 base::Base64Encode(policy_data_str, &policy_data_encoded); | |
356 | |
357 std::unique_ptr<base::DictionaryValue> local_state = | |
358 extensions::DictionaryBuilder() | |
359 .Set(prefs::kDeviceSettingsCache, policy_data_encoded) | |
360 .Set("PublicAccounts", | |
361 extensions::ListBuilder().Append(GetTestAppUserId()).Build()) | |
362 .Build(); | |
363 | |
364 JSONFileValueSerializer serializer( | |
365 user_data_path.Append(chrome::kLocalStateFilename)); | |
366 CHECK(serializer.Serialize(*local_state)); | |
367 } | |
368 | |
369 const std::string GetTestAppUserId() const { | |
370 return policy::GenerateDeviceLocalAccountUserId( | |
371 kTestAccountId, policy::DeviceLocalAccount::TYPE_KIOSK_APP); | |
372 } | |
373 | |
374 bool CloseAppWindow(const std::string& app_id) { | |
375 Profile* const app_profile = ProfileManager::GetPrimaryUserProfile(); | |
376 if (!app_profile) { | |
377 ADD_FAILURE() << "No primary (app) profile."; | |
378 return false; | |
379 } | |
380 | |
381 extensions::AppWindowRegistry* const app_window_registry = | |
382 extensions::AppWindowRegistry::Get(app_profile); | |
383 extensions::AppWindow* const window = | |
384 apps::AppWindowWaiter(app_window_registry, app_id).Wait(); | |
385 if (!window) { | |
386 ADD_FAILURE() << "No app window found for " << app_id << "."; | |
387 return false; | |
388 } | |
389 | |
390 window->GetBaseWindow()->Close(); | |
391 | |
392 // Wait until the app terminates if it is still running. | |
393 if (!app_window_registry->GetAppWindowsForApp(app_id).empty()) | |
394 base::RunLoop().Run(); | |
395 return true; | |
396 } | |
397 | |
398 bool IsKioskAppAutoLaunched(const std::string& app_id) { | |
399 KioskAppManager::App app; | |
400 if (!KioskAppManager::Get()->GetApp(app_id, &app)) { | |
401 ADD_FAILURE() << "App " << app_id << " not found."; | |
402 return false; | |
403 } | |
404 return app.was_auto_launched_with_zero_delay; | |
405 } | |
406 | |
407 void ExpectCommandLineHasDefaultPolicySwitches( | |
408 const base::CommandLine& cmd_line) { | |
409 for (size_t i = 0u; i < arraysize(kDefaultPolicySwitches); ++i) { | |
410 EXPECT_TRUE(cmd_line.HasSwitch(kDefaultPolicySwitches[i].name)) | |
411 << "Missing flag " << kDefaultPolicySwitches[i].name; | |
412 EXPECT_EQ(kDefaultPolicySwitches[i].value, | |
413 cmd_line.GetSwitchValueASCII(kDefaultPolicySwitches[i].name)) | |
414 << "Invalid value for switch " << kDefaultPolicySwitches[i].name; | |
415 } | |
416 } | |
417 | |
418 protected: | |
419 std::unique_ptr<TerminationObserver> termination_observer_; | |
420 | |
421 private: | |
422 chromeos::ScopedStubInstallAttributes install_attributes_; | |
423 policy::DevicePolicyBuilder device_policy_; | |
424 scoped_refptr<ownership::MockOwnerKeyUtil> owner_key_util_; | |
425 std::unique_ptr<PersistentSessionManagerClient> fake_session_manager_; | |
426 std::unique_ptr<FakeCWS> fake_cws_; | |
427 | |
428 DISALLOW_COPY_AND_ASSIGN(AutoLaunchedKioskTest); | |
429 }; | |
430 | |
431 IN_PROC_BROWSER_TEST_F(AutoLaunchedKioskTest, PRE_CrashRestore) { | |
432 // Verify that Chrome hasn't already exited, e.g. in order to apply user | |
433 // session flags. | |
434 ASSERT_FALSE(termination_observer_->terminated()); | |
435 | |
436 // Set up default network connections, so tests think the device is online. | |
437 DBusThreadManager::Get() | |
438 ->GetShillManagerClient() | |
439 ->GetTestInterface() | |
440 ->SetupDefaultEnvironment(); | |
441 | |
442 // Check that policy flags have not been lost. | |
443 ExpectCommandLineHasDefaultPolicySwitches( | |
444 *base::CommandLine::ForCurrentProcess()); | |
445 | |
446 ExtensionTestMessageListener listener("appWindowLoaded", false); | |
447 EXPECT_TRUE(listener.WaitUntilSatisfied()); | |
448 | |
449 EXPECT_TRUE(IsKioskAppAutoLaunched(kTestKioskApp)); | |
450 | |
451 ASSERT_TRUE(CloseAppWindow(kTestKioskApp)); | |
452 } | |
453 | |
454 IN_PROC_BROWSER_TEST_F(AutoLaunchedKioskTest, CrashRestore) { | |
455 // Verify that Chrome hasn't already exited, e.g. in order to apply user | |
456 // session flags. | |
457 ASSERT_FALSE(termination_observer_->terminated()); | |
458 | |
459 ExpectCommandLineHasDefaultPolicySwitches( | |
460 *base::CommandLine::ForCurrentProcess()); | |
461 | |
462 ExtensionTestMessageListener listener("appWindowLoaded", false); | |
463 EXPECT_TRUE(listener.WaitUntilSatisfied()); | |
464 | |
465 EXPECT_TRUE(IsKioskAppAutoLaunched(kTestKioskApp)); | |
466 | |
467 ASSERT_TRUE(CloseAppWindow(kTestKioskApp)); | |
468 } | |
469 | |
470 } // namespace chromeos | |
OLD | NEW |