1# Copyright 2015 The Chromium OS 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 5import copy 6import json 7import logging 8import os 9import time 10 11from autotest_lib.client.common_lib.cros import arc 12from autotest_lib.client.common_lib.cros.arc import is_android_container_alive 13 14from autotest_lib.client.bin import test 15from autotest_lib.client.common_lib import error 16from autotest_lib.client.common_lib.cros import chrome 17from autotest_lib.client.common_lib.cros import enrollment 18from autotest_lib.client.common_lib.cros import policy 19from autotest_lib.client.cros import cryptohome 20from autotest_lib.client.cros import httpd 21from autotest_lib.client.cros.input_playback import keyboard 22from autotest_lib.client.cros.enterprise import enterprise_fake_dmserver 23from py_utils import TimeoutException 24 25from telemetry.core import exceptions 26 27CROSQA_FLAGS = [ 28 '--gaia-url=https://gaiastaging.corp.google.com', 29 '--lso-url=https://gaiastaging.corp.google.com', 30 '--google-apis-url=https://www-googleapis-test.sandbox.google.com', 31 '--oauth2-client-id=236834563817.apps.googleusercontent.com', 32 '--oauth2-client-secret=RsKv5AwFKSzNgE0yjnurkPVI', 33 ('--cloud-print-url=' 34 'https://cloudprint-nightly-ps.sandbox.google.com/cloudprint'), 35 '--ignore-urlfetcher-cert-requests'] 36CROSALPHA_FLAGS = [ 37 ('--cloud-print-url=' 38 'https://cloudprint-nightly-ps.sandbox.google.com/cloudprint'), 39 '--ignore-urlfetcher-cert-requests'] 40TESTDMS_FLAGS = [ 41 '--ignore-urlfetcher-cert-requests', 42 '--disable-policy-key-verification'] 43FLAGS_DICT = { 44 'prod': [], 45 'crosman-qa': CROSQA_FLAGS, 46 'crosman-alpha': CROSALPHA_FLAGS, 47 'dm-test': TESTDMS_FLAGS, 48 'dm-fake': TESTDMS_FLAGS 49} 50DMS_URL_DICT = { 51 'prod': 'http://m.google.com/devicemanagement/data/api', 52 'crosman-qa': 53 'https://crosman-qa.sandbox.google.com/devicemanagement/data/api', 54 'crosman-alpha': 55 'https://crosman-alpha.sandbox.google.com/devicemanagement/data/api', 56 'dm-test': 'http://chromium-dm-test.appspot.com/d/%s', 57 'dm-fake': 'http://127.0.0.1:%d/' 58} 59DMSERVER = '--device-management-url=%s' 60# Username and password for the fake dm server can be anything, since 61# they are not used to authenticate against GAIA. 62USERNAME = 'fake-user@managedchrome.com' 63PASSWORD = 'fakepassword' 64GAIA_ID = 'fake-gaia-id' 65 66# Convert from chrome://policy name to what fake dms expects. 67DEVICE_POLICY_DICT = { 68 'DeviceAutoUpdateDisabled': 'update_disabled', 69 'DeviceEphemeralUsersEnabled': 'ephemeral_users_enabled', 70 'DeviceOpenNetworkConfiguration': 'open_network_configuration', 71 'DeviceRollbackToTargetVersion': 'rollback_to_target_version', 72 'DeviceTargetVersionPrefix': 'target_version_prefix', 73 'SystemTimezone': 'timezone', 74 'ReportUploadFrequency': 'device_status_frequency', 75 'DeviceLocalAccounts': 'account', 76 'DeviceLocalAccountAutoLoginId': 'auto_login_id' 77} 78 79# Default settings for managed user policies 80DEFAULT_POLICY = { 81 'AllowDinosaurEasterEgg': False, 82 'ArcBackupRestoreServiceEnabled': 0, 83 'ArcEnabled': False, 84 'ArcGoogleLocationServicesEnabled': 0, 85 'CaptivePortalAuthenticationIgnoresProxy': False, 86 'CastReceiverEnabled': False, 87 'ChromeOsMultiProfileUserBehavior': 'primary-only', 88 'EasyUnlockAllowed': False, 89 'InstantTetheringAllowed': False, 90 'NTLMShareAuthenticationEnabled': False, 91 'NTPContentSuggestionsEnabled': False, 92 'NetBiosShareDiscoveryEnabled': False, 93 'QuickUnlockModeWhitelist': [], 94 'SmartLockSigninAllowed': False, 95 'SmsMessagesAllowed': False 96} 97 98 99class EnterprisePolicyTest(arc.ArcTest, test.test): 100 """Base class for Enterprise Policy Tests.""" 101 102 WEB_PORT = 8080 103 WEB_HOST = 'http://localhost:%d' % WEB_PORT 104 CHROME_POLICY_PAGE = 'chrome://policy' 105 CHROME_VERSION_PAGE = 'chrome://version' 106 107 108 def initialize(self, **kwargs): 109 """ 110 Initialize test parameters. 111 112 Consume the check_client_result parameter if this test was started 113 from a server test. 114 115 """ 116 self._initialize_enterprise_policy_test(**kwargs) 117 118 119 def _initialize_enterprise_policy_test( 120 self, case='', env='dm-fake', dms_name=None, 121 username=USERNAME, password=PASSWORD, gaia_id=GAIA_ID, 122 set_auto_logout=None, **kwargs): 123 """ 124 Initialize test parameters and fake DM Server. 125 126 This function exists so that ARC++ tests (which inherit from the 127 ArcTest class) can also initialize a policy setup. 128 129 @param case: String name of the test case to run. 130 @param env: String environment of DMS and Gaia servers. 131 @param username: String user name login credential. 132 @param password: String password login credential. 133 @param gaia_id: String gaia_id login credential. 134 @param dms_name: String name of test DM Server. 135 @param kwargs: Not used. 136 137 """ 138 self.case = case 139 self.env = env 140 self.username = username 141 self.password = password 142 self.gaia_id = gaia_id 143 self.set_auto_logout = set_auto_logout 144 self.dms_name = dms_name 145 self.dms_is_fake = (env == 'dm-fake') 146 self.arc_enabled = False 147 self.version = None 148 self._enforce_variable_restrictions() 149 150 # Install protobufs and add import path. 151 policy.install_protobufs(self.autodir, self.job) 152 153 # Initialize later variables to prevent error after an early failure. 154 self._web_server = None 155 self.cr = None 156 157 # Start AutoTest DM Server if using local fake server. 158 if self.dms_is_fake: 159 self.fake_dm_server = enterprise_fake_dmserver.FakeDMServer() 160 self.fake_dm_server.start(self.tmpdir, self.debugdir) 161 162 # Get enterprise directory of shared resources. 163 client_dir = os.path.dirname(os.path.dirname(self.bindir)) 164 self.enterprise_dir = os.path.join(client_dir, 'cros/enterprise') 165 166 if self.set_auto_logout is not None: 167 self._auto_logout = self.set_auto_logout 168 169 # Log the test context parameters. 170 logging.info('Test Context Parameters:') 171 logging.info(' Case: %r', self.case) 172 logging.info(' Environment: %r', self.env) 173 logging.info(' Username: %r', self.username) 174 logging.info(' Password: %r', self.password) 175 logging.info(' Test DMS Name: %r', self.dms_name) 176 177 178 def check_page_readiness(self, tab, command): 179 """ 180 Checks to see if page has fully loaded. 181 182 @param tab: chrome tab loading the page. 183 @param command: JS command to be checked in the tab. 184 185 @returns True if loaded and False if not. 186 187 """ 188 try: 189 tab.EvaluateJavaScript(command) 190 return True 191 except exceptions.EvaluateException: 192 return False 193 194 195 def cleanup(self): 196 """Close out anything used by this test.""" 197 # Clean up AutoTest DM Server if using local fake server. 198 if self.dms_is_fake: 199 self.fake_dm_server.stop() 200 201 # Stop web server if it was started. 202 if self._web_server: 203 self._web_server.stop() 204 205 # Close Chrome instance if opened. 206 if self.cr and self._auto_logout: 207 self.cr.close() 208 209 # Cleanup the ARC test if needed. 210 if self.arc_enabled: 211 super(EnterprisePolicyTest, self).cleanup() 212 213 214 def start_webserver(self): 215 """Set up HTTP Server to serve pages from enterprise directory.""" 216 self._web_server = httpd.HTTPListener( 217 self.WEB_PORT, docroot=self.enterprise_dir) 218 self._web_server.run() 219 220 221 def _enforce_variable_restrictions(self): 222 """Validate class-level test context parameters. 223 224 @raises error.TestError if context parameter has an invalid value, 225 or a combination of parameters have incompatible values. 226 """ 227 # Verify |env| is a valid environment. 228 if self.env not in FLAGS_DICT: 229 raise error.TestError('Environment is invalid: %s' % self.env) 230 231 # Verify test |dms_name| is given iff |env| is 'dm-test'. 232 if self.env == 'dm-test' and not self.dms_name: 233 raise error.TestError('dms_name must be given when using ' 234 'env=dm-test.') 235 if self.env != 'dm-test' and self.dms_name: 236 raise error.TestError('dms_name must not be given when not using ' 237 'env=dm-test.') 238 239 240 def setup_case(self, 241 user_policies={}, 242 suggested_user_policies={}, 243 device_policies={}, 244 extension_policies={}, 245 skip_policy_value_verification=False, 246 kiosk_mode=False, 247 enroll=False, 248 auto_login=True, 249 auto_logout=True, 250 init_network_controller=False, 251 arc_mode=False, 252 setup_arc=True, 253 use_clouddpc_test=None, 254 disable_default_apps=True, 255 extension_paths=[], 256 extra_chrome_flags=[]): 257 """Set up DMS, log in, and verify policy values. 258 259 If the AutoTest fake DM Server is used, make a JSON policy blob 260 and upload it to the fake DM server. 261 262 Launch Chrome and sign in to Chrome OS. Examine the user's 263 cryptohome vault, to confirm user is signed in successfully. 264 265 @param user_policies: dict of mandatory user policies in 266 name -> value format. 267 @param suggested_user_policies: optional dict of suggested policies 268 in name -> value format. 269 @param device_policies: dict of device policies in 270 name -> value format. 271 @param extension_policies: dict of extension policies. 272 @param skip_policy_value_verification: True if setup_case should not 273 verify that the correct policy value shows on policy page. 274 @param enroll: True for enrollment instead of login. 275 @param auto_login: Sign in to chromeos. 276 @param auto_logout: Sign out of chromeos when test is complete. 277 @param init_network_controller: whether to init network controller. 278 @param arc_mode: whether to enable arc_mode on chrome.chrome(). 279 @param setup_arc: whether to run setup_arc in arc.Arctest. 280 @param use_clouddpc_test: whether to run the cloud dpc test. 281 @param extension_paths: list of extensions to install. 282 @param extra_chrome_flags: list of flags to add to Chrome. 283 284 @raises error.TestError if cryptohome vault is not mounted for user. 285 @raises error.TestFail if |policy_name| and |policy_value| are not 286 shown on the Policies page. 287 """ 288 289 # Need a real account, for now. Note: Even though the account is 'real' 290 # you can still use a fake DM server. 291 if arc_mode and self.username == USERNAME: 292 self.username = 'tester50@managedchrome.com' 293 self.password = 'Test0000' 294 295 self._auto_logout = auto_logout 296 self._kiosk_mode = kiosk_mode 297 298 if self.dms_is_fake: 299 self.fake_dm_server.setup_policy(self._make_json_blob( 300 user_policies, suggested_user_policies, device_policies, 301 extension_policies)) 302 303 self._create_chrome(enroll=enroll, 304 auto_login=auto_login, 305 init_network_controller=init_network_controller, 306 extension_paths=extension_paths, 307 arc_mode=arc_mode, 308 disable_default_apps=disable_default_apps, 309 extra_chrome_flags=extra_chrome_flags) 310 311 # Skip policy check upon request or if we enroll but don't log in. 312 skip_policy_value_verification = ( 313 skip_policy_value_verification or not auto_login) 314 315 if not skip_policy_value_verification: 316 self.verify_policy_stats(user_policies, suggested_user_policies, 317 device_policies) 318 self.verify_extension_stats(extension_policies) 319 320 if arc_mode: 321 self.start_arc(use_clouddpc_test, setup_arc) 322 323 def start_arc(self, use_clouddpc_test, setup_arc): 324 ''' 325 Starts ARC when creating the chrome object. Specifically will create 326 the ADB shell container for testing use. 327 328 We are NOT going to use the arc class initialize, it overwrites the 329 creation of chrome.chrome() in a way which cannot support the DM sever. 330 331 Instead we check for the android container, and run arc_setup if 332 needed. Note: To use the cloud dpc test, you MUST also run setup_arc 333 334 @param setup_arc: whether to run setup_arc in arc.Arctest. 335 @param use_clouddpc_test: bool, run_clouddpc_test() or not. 336 ''' 337 _APP_FILENAME = 'autotest-deps-cloudpctest-0.4.apk' 338 _DEP_PACKAGE = 'CloudDPCTest-apks' 339 _PKG_NAME = 'com.google.android.apps.work.clouddpc.e2etests' 340 341 # By default on boot the container is alive, and will not close until 342 # a user with ARC disabled logs in. This wait accounts for that. 343 time.sleep(3) 344 345 if use_clouddpc_test and not setup_arc: 346 raise error.TestFail('For cloud DPC setup_arc cannot be disabled') 347 348 if is_android_container_alive(): 349 logging.info('Android Container is alive!') 350 else: 351 logging.error('Android Container is not alive!') 352 353 # Install the clouddpc test. 354 if use_clouddpc_test: 355 self.arc_setup(dep_packages=_DEP_PACKAGE, 356 apks=[_APP_FILENAME], 357 full_pkg_names=[_PKG_NAME]) 358 self.run_clouddpc_test() 359 else: 360 if setup_arc: 361 self.arc_setup() 362 363 self.arc_enabled = True 364 365 def run_clouddpc_test(self): 366 """ 367 Run clouddpc end-to-end test and fail this test if it fails. 368 369 Assumes start_arc() was run with use_clouddpc_test. 370 371 Determines the policy values to pass to the test from those set in 372 Chrome OS. 373 374 @raises error.TestFail if the test does not pass. 375 376 """ 377 policy_blob = self._get_clouddpc_policies() 378 policy_blob_str = json.dumps(policy_blob, separators=(',',':')) 379 cmd = ('am instrument -w -e policy "%s" ' 380 'com.google.android.apps.work.clouddpc.e2etests/' 381 '.ArcInstrumentationTestRunner') % policy_blob_str 382 383 # Run the command as a shell script so that its length is not capped. 384 temp_shell_script_path = '/sdcard/tmp.sh' 385 arc.write_android_file(temp_shell_script_path, cmd) 386 387 logging.info('Running clouddpc test with policy: %s', policy_blob_str) 388 results = arc.adb_shell('sh ' + temp_shell_script_path).strip() 389 arc.remove_android_file(temp_shell_script_path) 390 if results.find('FAILURES!!!') >= 0: 391 logging.info('CloudDPC E2E Results:\n%s', results) 392 err_msg = results.splitlines()[-1] 393 raise error.TestFail('CloudDPC E2E failure: %s' % err_msg) 394 395 logging.debug(results) 396 logging.info('CloudDPC E2E test passed!') 397 398 def _get_clouddpc_policies(self): 399 """ 400 Return the relevant CloudDPC policies and their values for e2e testing. 401 402 The CloudDPC end-to-end test takes in a string of the policies which 403 are set. These policies don't have the same names in Chrome OS, or 404 they don't map 1:1 with Chrome OS's policies. 405 406 Figuring out the values from chrome://policy (rather than creating a 407 dict of the policies under test) will prevent the test from failing on 408 accounts which happen to have unrelated policies set. 409 410 Constructs a json object of the CloudDPC policy names and values. 411 Finds the values which map 1:1 to Chrome OS and handles the exceptions. 412 413 @returns a dict where the keys are CloudDPC policy names and the values 414 are as shown on chrome://policy. 415 416 """ 417 418 # CloudDPC policy -> value 419 policy_map = {} 420 421 # The policies which are a 1:1 rename between Chrome OS and CloudDPC. 422 chromeos_to_clouddpc = { 423 'AudioCaptureAllowed': 'unmuteMicrophoneDisabled', 424 'DefaultGeolocationSetting': 'shareLocationDisabled', 425 'DeviceBlockDevmode': 'debuggingFeaturesDisabled', 426 'DisableScreenshots': 'screenCaptureDisabled', 427 'ExternalStorageDisabled': 'usbFileTransferDisabled', 428 'VideoCaptureAllowed': 'cameraDisabled', 429 } 430 431 # Only check caCerts if the value is allowed to be passed to Android. 432 caCerts_passed = self._get_policy_value_from_new_tab( 433 'ArcCertificatesSyncMode') 434 if caCerts_passed: 435 chromeos_to_clouddpc['OpenNetworkConfiguration'] = 'caCerts' 436 437 # The policies which take some special handling to convert. 438 exception_policies = ['URLBlacklist', 'URLWhitelist', 'ArcPolicy'] 439 440 values = self._get_policy_values_from_new_tab( 441 chromeos_to_clouddpc.keys() + exception_policies) 442 443 # Map the 1:1 policies: Android policy name to ChromeOS policy value. 444 for chromeos_policy in chromeos_to_clouddpc: 445 clouddpc_policy = chromeos_to_clouddpc[chromeos_policy] 446 value = values[chromeos_policy] 447 if value is not None: 448 policy_map[clouddpc_policy] = value 449 450 # ArcPolicy value contains some stand-alone CloudDPC policies. 451 arc_policy_value = values['ArcPolicy'] 452 453 if arc_policy_value: 454 for key in ['applications', 'accountTypesWithManagementDisabled']: 455 if key in arc_policy_value: 456 policy_map[key] = arc_policy_value[key] 457 458 return policy_map 459 460 def _make_json_blob(self, user_policies={}, suggested_user_policies={}, 461 device_policies={}, extension_policies={}): 462 """Create JSON policy blob from mandatory and suggested policies. 463 464 For the status of a policy to be shown as "Not set" on the 465 chrome://policy page, the policy dictionary must contain no NVP for 466 for that policy. Remove policy NVPs if value is None. 467 468 @param user_policies: mandatory user policies -> values. 469 @param suggested user_policies: suggested user policies -> values. 470 @param device_policies: mandatory device policies -> values. 471 @param extension_policies: extension policies. 472 473 @returns: JSON policy blob to send to the fake DM server. 474 """ 475 476 user_p = copy.deepcopy(user_policies) 477 s_user_p = copy.deepcopy(suggested_user_policies) 478 device_p = copy.deepcopy(device_policies) 479 extension_p = copy.deepcopy(extension_policies) 480 481 # Replace all device policies with their FakeDMS-friendly names. 482 fixed_device_p = {} 483 for policy in device_p: 484 if policy not in DEVICE_POLICY_DICT: 485 raise error.TestError('Cannot convert %s!' % policy) 486 fixed_device_p[DEVICE_POLICY_DICT[policy]] = device_p[policy] 487 488 # Remove "Not set" policies and json-ify dicts because the 489 # FakeDMServer expects "policy": "{value}" not "policy": {value} 490 # and "policy": "[{value}]" not "policy": [{value}]. 491 for policies_dict in [user_p, s_user_p, fixed_device_p]: 492 policies_to_pop = [] 493 for policy in policies_dict: 494 value = policies_dict[policy] 495 if value is None: 496 policies_to_pop.append(policy) 497 elif isinstance(value, dict): 498 policies_dict[policy] = encode_json_string(value) 499 elif isinstance(value, list) and not ( 500 policies_dict in [fixed_device_p]): 501 if value and isinstance(value[0], dict): 502 policies_dict[policy] = encode_json_string(value) 503 for policy in policies_to_pop: 504 policies_dict.pop(policy) 505 506 management_dict = { 507 'managed_users': ['*'], 508 'policy_user': self.username, 509 'current_key_index': 0, 510 'invalidation_source': 16, 511 'invalidation_name': 'test_policy' 512 } 513 514 if user_p or s_user_p: 515 user_modes_dict = {} 516 if user_p: 517 user_modes_dict['mandatory'] = user_p 518 if suggested_user_policies: 519 user_modes_dict['recommended'] = s_user_p 520 management_dict['google/chromeos/user'] = user_modes_dict 521 522 if fixed_device_p: 523 management_dict['google/chromeos/device'] = fixed_device_p 524 525 if extension_p: 526 management_dict['google/chrome/extension'] = extension_p 527 logging.info('Created policy blob: %s', management_dict) 528 return encode_json_string(management_dict) 529 530 531 def _get_extension_policy_table(self, policy_tab, ext_id): 532 """ 533 Find the policy table that matches the given extension ID. 534 535 The user and device policies are in table[0]. Extension policies are 536 in their own tables. 537 538 @param policy_tab: Tab displaying the policy page. 539 @param ext_id: Extension ID. 540 541 @returns: Index of the table in the DOM. 542 @raises error.TestError: if the table for the extension ID does 543 not exist. 544 545 """ 546 table_index = policy_tab.EvaluateJavaScript(""" 547 var table_id = -1; 548 var section = document.getElementsByClassName('policy-table'); 549 for (var i = 0; i < section.length; i++) { 550 var temp_name = section[i] 551 .getElementsByClassName('id')[0].innerText; 552 if (temp_name === "%s") 553 { var table_id = i; 554 break ; 555 } 556 }; 557 table_id; 558 """ % ext_id) 559 if table_index == -1: 560 raise error.TestError( 561 'Policy table for extension %s does not exist. ' 562 'Make sure the extension is installed.' % ext_id) 563 564 return table_index 565 566 567 def reload_policies(self): 568 """Force a policy fetch.""" 569 policy_tab = self.navigate_to_url(self.CHROME_POLICY_PAGE) 570 reload_button = "document.querySelector('button#reload-policies')" 571 policy_tab.ExecuteJavaScript("%s.click()" % reload_button) 572 policy_tab.WaitForJavaScriptCondition("!%s.disabled" % reload_button, 573 timeout=1) 574 policy_tab.Close() 575 576 577 def verify_extension_stats(self, extension_policies, sensitive_fields=[]): 578 """ 579 Verify the extension policies match what is on chrome://policy. 580 581 @param extension_policies: the dictionary of extension IDs mapping 582 to download_url and secure_hash. 583 @param sensitive_fields: list of fields that should have their value 584 censored. 585 @raises error.TestError: if the shown values do not match what we are 586 expecting. 587 588 """ 589 policy_tab = self.navigate_to_url(self.CHROME_POLICY_PAGE) 590 for id in extension_policies.keys(): 591 table = self._get_extension_policy_table(policy_tab, id) 592 download_url = extension_policies[id]['download_url'] 593 policy_file = os.path.join(self.enterprise_dir, 594 download_url.split('/')[-1]) 595 596 with open(policy_file) as f: 597 policies = json.load(f) 598 599 for policy_name, settings in policies.items(): 600 expected_value = settings['Value'] 601 value_shown = self._get_policy_stats_shown( 602 policy_tab, policy_name, table)['value'] 603 604 if policy_name in sensitive_fields: 605 expected_value = '********' 606 607 self._compare_values(policy_name, expected_value, value_shown) 608 609 policy_tab.Close() 610 611 def _get_policy_stats_shown(self, policy_tab, policy_name, 612 table_index=0): 613 """Get the info shown for |policy_name| from the |policy_tab| page. 614 615 Return a dict of stats for the policy given by |policy_name|, from 616 from the chrome://policy page given by |policy_tab|. 617 618 CAVEAT: the policy page does not display proper JSON. For example, lists 619 are generally shown without the [ ] and cannot be distinguished from 620 strings. This function decodes what it can and returns the string it 621 found when in doubt. 622 623 @param policy_tab: Tab displaying the Policies page. 624 @param policy_name: The name of the policy. 625 @param table_index: Index of table in DOM to check. 626 627 @returns: A dict of stats, including JSON decode 'value' (see caveat). 628 Also included are 'name', 'status', 'level', 'scope', 629 and 'source'. 630 """ 631 stats = {'name': policy_name} 632 row_values = policy_tab.EvaluateJavaScript(''' 633 var rowValues = {}; 634 var section = document.getElementsByClassName('policy-table')[%s]; 635 table = section.getElementsByClassName('main')[0]; 636 var pol_rows = table.getElementsByClassName('policy-data'); 637 for (i = 0; i < pol_rows.length; i++) { 638 if (window.getComputedStyle(pol_rows[i]).display === "none") 639 { break ;} 640 var pol_name = pol_rows[i] 641 .getElementsByClassName('policy row')[0] 642 .getElementsByClassName('name')[0].innerText; 643 if (pol_name === '%s'){ 644 var pol_data = pol_rows[i] 645 .getElementsByClassName('policy row')[0]; 646 var value_data = pol_rows[i] 647 .getElementsByClassName('value row')[0]; 648 rowValues["value"] = value_data 649 .getElementsByClassName('value')[0].innerText; 650 var column_titles = ["name", "source", 651 "scope", "level", "messages"]; 652 column_titles.forEach(function(entry) { 653 var entry_div = pol_data.getElementsByClassName(entry)[0]; 654 rowValues[entry] = entry_div.innerText}); 655 }; 656 }; 657 rowValues; 658 ''' % (table_index, policy_name)) 659 660 entries = ["name", "value", "source", "scope", "level", "messages"] 661 662 # New Policy Parser returns empty, rather than 'Not Set.'. This is 663 # a fix to make it compatible with the rest of the parsing code rather 664 # than a larger re-write. 665 if not row_values: 666 for entry in entries: 667 row_values[entry] = '' 668 row_values['messages'] = 'Not set.' 669 670 logging.debug('Policy %s row: %s', policy_name, row_values) 671 key_diff = set(entries) - set(row_values.keys()) 672 if key_diff: 673 raise error.TestError( 674 'Could not get policy info for %s. ' 675 'Missing columns: %s.' % (policy_name, key_diff)) 676 677 for v in entries: 678 stats[v] = row_values[v].encode('ascii', 'ignore') 679 680 if stats['messages'] == 'Not set.': 681 for v in entries: 682 stats[v] = None 683 else: 684 stats['value'] = decode_json_string(stats['value']) 685 686 return stats 687 688 def _get_policy_value_from_new_tab(self, policy_name): 689 """Get the policy value for |policy_name| from the Policies page. 690 691 Information comes from the policy page. A single new tab is opened 692 and then closed to check this info, so device must be logged in. 693 694 @param policy_name: string of policy name. 695 696 @returns: decoded value of the policy as shown on chrome://policy. 697 """ 698 values = self._get_policy_stats_from_new_tab([policy_name]) 699 return values[policy_name]['value'] 700 701 def _get_chrome_version_from_browser(self): 702 """Get the Chrome Version from the ://version page. 703 704 @returns: Version as shown on ://version page. 705 """ 706 tab = self.navigate_to_url(self.CHROME_VERSION_PAGE) 707 table_name = 'inner' 708 version_box = 'version' 709 version_row = 0 710 return tab.EvaluateJavaScript( 711 "document.getElementById('{}').rows[{}]\ 712 .getElementsByClassName('{}')[0].innerText" 713 .format(table_name, version_row, version_box)) 714 715 def _get_policy_values_from_new_tab(self, policy_names): 716 """Get the policy values of the given policies. 717 718 Information comes from the policy page. A single new tab is opened 719 and then closed to check this info, so device must be logged in. 720 721 @param policy_names: list of strings of policy names. 722 723 @returns: dict of policy name mapped to decoded values of the policy as 724 shown on chrome://policy. 725 """ 726 values = {} 727 tab = self.navigate_to_url(self.CHROME_POLICY_PAGE) 728 for policy_name in policy_names: 729 values[policy_name] = ( 730 self._get_policy_stats_shown(tab, policy_name)['value']) 731 tab.Close() 732 733 return values 734 735 736 def _get_policy_stats_from_new_tab(self, policy_names): 737 """Get policy info about the given policy names. 738 739 Information comes from the policy page. A single new tab is opened 740 and then closed to check this info, so device must be logged in. 741 742 @param policy_name: list of policy names (strings). 743 744 @returns: dict of policy names mapped to dicts containing policy info. 745 Values are decoded JSON. 746 """ 747 stats = {} 748 tab = self.navigate_to_url(self.CHROME_POLICY_PAGE) 749 for policy_name in policy_names: 750 stats[policy_name] = self._get_policy_stats_shown(tab, policy_name) 751 tab.Close() 752 753 return stats 754 755 756 def _compare_values(self, policy_name, input_value, value_shown): 757 """Pass if an expected value and the chrome://policy version match. 758 759 Handles some of the inconsistencies in the chrome://policy JSON format. 760 761 @param policy_name: The policy name to be verified. 762 @param input_value: The setting given for the policy. 763 @param value_shown: The setting of the policy from the DUT. 764 765 @raises: error.TestError if policy values do not match. 766 767 """ 768 expected_value = copy.deepcopy(input_value) 769 770 # If we expect a list and don't have a list, modify the value_shown. 771 if isinstance(expected_value, list): 772 if isinstance(value_shown, str): 773 if '{' in value_shown: # List of dicts. 774 value_shown = decode_json_string('[%s]' % value_shown) 775 elif ',' in value_shown: # List of strs. 776 value_shown = value_shown.split(',') 777 else: # List with one str. 778 value_shown = [value_shown] 779 elif not isinstance(value_shown, list): # List with one element. 780 value_shown = [value_shown] 781 782 # Special case for user and device network configurations. 783 # Passphrases are hidden on the policy page, so the passphrase 784 # field needs to be converted to asterisks to be compared. 785 SANITIZED_PASSWORD = '*' * 8 786 if policy_name.endswith('OpenNetworkConfiguration'): 787 for network in expected_value.get('NetworkConfigurations', []): 788 wifi = network.get('WiFi', {}) 789 if 'Passphrase' in wifi: 790 wifi['Passphrase'] = SANITIZED_PASSWORD 791 if 'EAP' in wifi and 'Password' in wifi['EAP']: 792 wifi['EAP']['Password'] = SANITIZED_PASSWORD 793 for cert in expected_value.get('Certificates', []): 794 if 'PKCS12' in cert: 795 cert['PKCS12'] = SANITIZED_PASSWORD 796 797 # Some managed policies have a default value when they are not set. 798 # Replace these unset policies with their default value. 799 elif policy_name in DEFAULT_POLICY and expected_value is None: 800 expected_value = DEFAULT_POLICY[policy_name] 801 802 if expected_value != value_shown: 803 raise error.TestError('chrome://policy shows the incorrect value ' 804 'for %s! Expected %s, got %s.' % ( 805 policy_name, expected_value, 806 value_shown)) 807 808 809 def verify_policy_value(self, policy_name, expected_value): 810 """ 811 Verify that the a single policy correctly shows in chrome://policy. 812 813 @param policy_name: the policy we are checking. 814 @param expected_value: the expected value for policy_name. 815 816 @raises error.TestError if value does not match expected. 817 818 """ 819 value_shown = self._get_policy_value_from_new_tab(policy_name) 820 self._compare_values(policy_name, expected_value, value_shown) 821 822 823 def verify_policy_stats(self, user_policies={}, suggested_user_policies={}, 824 device_policies={}): 825 """Verify that the correct policy values show in chrome://policy. 826 827 @param policy_dict: the policies we are checking. 828 829 @raises error.TestError if value does not match expected. 830 """ 831 def _compare_stat(stat, desired, name, stats): 832 """ Raise error if a stat doesn't match.""" 833 err_str = 'Incorrect '+stat+' for '+name+': expected %s, got %s!' 834 shown = stats[name][stat] 835 # If policy is not set, there are no stats to match. 836 if stats[name]['messages'] is None: 837 if not shown == None: 838 raise error.TestError(err_str % (None, shown)) 839 else: 840 return 841 if not desired == shown: 842 raise error.TestError(err_str % (desired, shown)) 843 844 keys = (user_policies.keys() + suggested_user_policies.keys() + 845 device_policies.keys()) 846 847 # If no policies were modified from default, return. 848 if len(keys) == 0: 849 return 850 851 stats = self._get_policy_stats_from_new_tab(keys) 852 853 for policy in user_policies: 854 self._compare_values(policy, user_policies[policy], 855 stats[policy]['value']) 856 _compare_stat('level', 'Mandatory', policy, stats) 857 _compare_stat('scope', 'Current user', policy, stats) 858 for policy in suggested_user_policies: 859 self._compare_values(policy, suggested_user_policies[policy], 860 stats[policy]['value']) 861 _compare_stat('level', 'Recommended', policy, stats) 862 _compare_stat('scope', 'Current user', policy, stats) 863 for policy in device_policies: 864 self._compare_values(policy, device_policies[policy], 865 stats[policy]['value']) 866 _compare_stat('level', 'Mandatory', policy, stats) 867 _compare_stat('scope', 'Device', policy, stats) 868 869 870 def _initialize_chrome_extra_flags(self): 871 """ 872 Initialize flags used to create Chrome instance. 873 874 @returns: list of extra Chrome flags. 875 876 """ 877 # Construct DM Server URL flags if not using production server. 878 env_flag_list = [] 879 if self.env != 'prod': 880 if self.dms_is_fake: 881 # Use URL provided by the fake AutoTest DM server. 882 dmserver_str = (DMSERVER % self.fake_dm_server.server_url) 883 else: 884 # Use URL defined in the DMS URL dictionary. 885 dmserver_str = (DMSERVER % (DMS_URL_DICT[self.env])) 886 if self.env == 'dm-test': 887 dmserver_str = (dmserver_str % self.dms_name) 888 889 # Merge with other flags needed by non-prod enviornment. 890 env_flag_list = ([dmserver_str] + FLAGS_DICT[self.env]) 891 892 return env_flag_list 893 894 895 def _create_chrome(self, 896 enroll=False, 897 auto_login=True, 898 arc_mode=False, 899 init_network_controller=False, 900 disable_default_apps=True, 901 extension_paths=[], 902 extra_chrome_flags=[]): 903 """ 904 Create a Chrome object. Enroll and/or sign in. 905 906 Function results in self.cr set as the Chrome object. 907 908 @param enroll: enroll the device. 909 @param auto_login: sign in to chromeos. 910 @param arc_mode: enable arc mode. 911 @param extension_paths: list of extensions to install. 912 @param init_network_controller: whether to init network controller. 913 @param extra_chrome_flags: list of flags to add. 914 """ 915 extra_flags = self._initialize_chrome_extra_flags() + extra_chrome_flags 916 917 logging.info('Chrome Browser Arguments:') 918 logging.info(' extra_browser_args: %s', extra_flags) 919 logging.info(' username: %s', self.username) 920 logging.info(' password: %s', self.password) 921 logging.info(' gaia_login: %s', not self.dms_is_fake) 922 923 if enroll: 924 self.cr = chrome.Chrome( 925 auto_login=False, 926 extra_browser_args=extra_flags, 927 extension_paths=extension_paths, 928 expect_policy_fetch=True) 929 if self.dms_is_fake: 930 if self._kiosk_mode: 931 # This try is needed for kiosk; without it the test fails 932 # in telemtry code in _WaitForEnterpriseWebview. Webview 933 # never loads since it's kiosk. 934 # TODO(rzakarian): Try to modify telemetry code to not 935 # wait for Webview when in kiosk mode. 936 # http://crbug.com/934876. 937 try: 938 enrollment.EnterpriseFakeEnrollment( 939 self.cr.browser, self.username, self.password, 940 self.gaia_id, auto_login=auto_login) 941 except TimeoutException: 942 pass 943 else: 944 enrollment.EnterpriseFakeEnrollment( 945 self.cr.browser, self.username, self.password, 946 self.gaia_id, auto_login=auto_login) 947 else: 948 enrollment.EnterpriseEnrollment( 949 self.cr.browser, self.username, self.password, 950 auto_login=auto_login) 951 952 elif auto_login: 953 if arc_mode: 954 self.cr = chrome.Chrome(extension_paths=extension_paths, 955 username=self.username, 956 password=self.password, 957 arc_mode=arc_mode, 958 disable_gaia_services=False, 959 disable_arc_opt_in=False, 960 enterprise_arc_test=True, 961 extra_browser_args=extra_flags) 962 963 else: 964 self.cr = chrome.Chrome( 965 extra_browser_args=extra_flags, 966 username=self.username, 967 password=self.password, 968 gaia_login=not self.dms_is_fake, 969 disable_gaia_services=self.dms_is_fake, 970 autotest_ext=True, 971 init_network_controller=init_network_controller, 972 expect_policy_fetch=True, 973 extension_paths=extension_paths, 974 disable_default_apps=disable_default_apps) 975 else: 976 self.cr = chrome.Chrome( 977 auto_login=False, 978 extra_browser_args=extra_flags, 979 disable_gaia_services=self.dms_is_fake, 980 autotest_ext=True, 981 expect_policy_fetch=True) 982 983 # Used by arc.py to determine the state of the chrome obj 984 self.initialized = True 985 if auto_login: 986 if not cryptohome.is_vault_mounted(user=self.username, 987 allow_fail=True): 988 raise error.TestError('Expected to find a mounted vault for %s.' 989 % self.username) 990 991 992 def navigate_to_url(self, url, tab=None): 993 """Navigate tab to the specified |url|. Create new tab if none given. 994 995 @param url: URL of web page to load. 996 @param tab: browser tab to load (if any). 997 @returns: browser tab loaded with web page. 998 @raises: telemetry TimeoutException if document ready state times out. 999 """ 1000 logging.info('Navigating to URL: %r', url) 1001 if not tab: 1002 tab = self.cr.browser.tabs.New() 1003 tab.Activate() 1004 tab.Navigate(url, timeout=8) 1005 tab.WaitForDocumentReadyStateToBeComplete() 1006 return tab 1007 1008 1009 def get_elements_from_page(self, tab, cmd): 1010 """Get collection of page elements that match the |cmd| filter. 1011 1012 @param tab: tab containing the page to be scraped. 1013 @param cmd: JavaScript command to evaluate on the page. 1014 @returns object containing elements on page that match the cmd. 1015 @raises: TestFail if matching elements are not found on the page. 1016 """ 1017 try: 1018 elements = tab.EvaluateJavaScript(cmd) 1019 except Exception as err: 1020 raise error.TestFail('Unable to find matching elements on ' 1021 'the test page: %s\n %r' %(tab.url, err)) 1022 return elements 1023 1024 def log_out_via_keyboard(self): 1025 """ 1026 Logs out of the device using the keyboard shortcut 1027 1028 """ 1029 _keyboard = keyboard.Keyboard() 1030 _keyboard.press_key('ctrl+shift+q') 1031 _keyboard.press_key('ctrl+shift+q') 1032 _keyboard.close() 1033 1034def encode_json_string(object_value): 1035 """Convert given value to JSON format string. 1036 1037 @param object_value: object to be converted. 1038 1039 @returns: string in JSON format. 1040 """ 1041 return json.dumps(object_value) 1042 1043 1044def decode_json_string(json_string): 1045 """Convert given JSON format string to an object. 1046 1047 If no object is found, return json_string instead. This is to allow 1048 us to "decode" items off the policy page that aren't real JSON. 1049 1050 @param json_string: the JSON string to be decoded. 1051 1052 @returns: Python object represented by json_string or json_string. 1053 """ 1054 def _decode_list(json_list): 1055 result = [] 1056 for value in json_list: 1057 if isinstance(value, unicode): 1058 value = value.encode('ascii') 1059 if isinstance(value, list): 1060 value = _decode_list(value) 1061 if isinstance(value, dict): 1062 value = _decode_dict(value) 1063 result.append(value) 1064 return result 1065 1066 def _decode_dict(json_dict): 1067 result = {} 1068 for key, value in json_dict.iteritems(): 1069 if isinstance(key, unicode): 1070 key = key.encode('ascii') 1071 if isinstance(value, unicode): 1072 value = value.encode('ascii') 1073 elif isinstance(value, list): 1074 value = _decode_list(value) 1075 result[key] = value 1076 return result 1077 1078 try: 1079 # Decode JSON turning all unicode strings into ascii. 1080 # object_hook will get called on all dicts, so also handle lists. 1081 result = json.loads(json_string, encoding='ascii', 1082 object_hook=_decode_dict) 1083 if isinstance(result, list): 1084 result = _decode_list(result) 1085 return result 1086 except ValueError as e: 1087 # Input not valid, e.g. '1, 2, "c"' instead of '[1, 2, "c"]'. 1088 logging.warning('Could not unload: %s (%s)', json_string, e) 1089 return json_string 1090