blob: ae39aad3392fd21e2c9311680e1ecbf24653eac9 [file] [log] [blame]
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import json
import logging
import os
from autotest_lib.client.bin import test
from autotest_lib.client.common_lib import error
from autotest_lib.client.cros import cros_ui_test
class security_BundledExtensions(cros_ui_test.UITest):
version = 1
def load_baseline(self):
bfile = open(os.path.join(self.bindir, 'baseline'))
with open(os.path.join(self.bindir, 'baseline')) as bfile:
baseline = []
for line in bfile:
if not line.startswith('#'):
baseline.append(line)
baseline = json.loads(''.join(baseline))
self._ignored_extension_names = baseline['ignored_extension_names']
self._ignored_crx_files = baseline['ignored_crx_files']
self._bundled_crx_directory = baseline['bundled_crx_directory']
self._bundled_crx_baseline = baseline['bundled_crx_baseline']
self._component_extension_baseline = baseline[
'component_extension_baseline']
self._official_components = baseline['official_components']
def get_extensions_info(self):
"""Wraps the pyauto method GetExtensionsInfo().
Filters out extensions that are on the to-be-ignored list.
Returns:
A list of dicts, each representing an extension. For more
information, see the pyauto documentation.
"""
complete_info = self.pyauto.GetExtensionsInfo()
logging.debug("GetExtensionsInfo:\n%s" %
self.pyauto.pformat(complete_info))
filtered_info = []
for rec in complete_info:
if not rec['name'] in self._ignored_extension_names:
filtered_info.append(rec)
return filtered_info
def assert_perms_match(self, expected_set, actual_set, perm_type,
full_expected_info, full_actual_info):
"""Asserts that the set of permissions for an extension is expected.
Args:
expected_set: A set of permissions that are expected to be present.
actual_set: A set of permissions that are actually present.
perm_type: A string describing the type of permission involved.
full_expected_info: A dictionary fully describing the expected
information associated with the given extension.
full_actual_info: A dictionary fully describing the actual
information associated with the given extension.
"""
def _diff_set_msg(expected_set, actual_set):
strings = []
for missing_item in expected_set.difference(actual_set):
strings.append('Missing item: "%s"' % missing_item)
for extra_item in actual_set.difference(expected_set):
strings.append('Unexpected (extra) item: "%s"' % extra_item)
return '\n'.join(strings)
self.pyauto.assertEqual(
expected_set, actual_set,
msg=('%s do not match for "%s".\n'
'%s\n'
'Expected extension info:\n%s'
'\nActual extension info:\n%s' %
(perm_type, full_expected_info['name'],
_diff_set_msg(expected_set, actual_set),
self.pyauto.pformat(full_expected_info),
self.pyauto.pformat(full_actual_info))))
def assert_names_match(self, expected_set, actual_set, ext_type,
full_expected_info, full_actual_info):
"""Asserts that a set of extensions is expected.
Args:
expected_set: A set of extension names that are expected to be
present.
actual_set: A set of extension names that are actually present.
ext_type: A string describing the type of extensions involved.
full_expected_info: A list of dictionaries describing the expected
information for all extensions.
full_actual_info: A list of dictionaries describing the actual
information for all extensions.
"""
def _diff_set_msg(expected_set, actual_set):
strings = []
for missing_item in expected_set.difference(actual_set):
strings.append('Missing item: "%s"' % missing_item)
located_ext_info = [info for info in full_expected_info if
info['name'] == missing_item][0]
strings.append(self.pyauto.pformat(located_ext_info))
for extra_item in actual_set.difference(expected_set):
strings.append('Unexpected (extra) item: "%s"' % extra_item)
located_ext_info = [info for info in full_actual_info if
info['name'] == extra_item][0]
strings.append(self.pyauto.pformat(located_ext_info))
return '\n'.join(strings)
self.pyauto.assertEqual(
expected_set, actual_set,
msg='%s names do not match the baseline.\n'
'%s\n' % (ext_type, _diff_set_msg(expected_set, actual_set)))
def attempt_install(self, crx_file):
"""Try to install a crx, and log an error if it fails.
Args:
crx_file: A string containing the path to a .crx file.
"""
# This helps limit the degree to which future bugs like
# crbug.com/131480 interfere with testing. The test will still
# fail, but at least the test will complete (and notice any
# problems in any *other* extensions).
import pyauto_errors
logging.debug('Installing %s' % crx_file)
try:
self.pyauto.InstallExtension(crx_file, from_webstore=True)
except pyauto_errors.JSONInterfaceError:
logging.error('Installation failed for %s' % crx_file)
def verify_extension_perms(self, baseline):
"""Ensures extension permissions in the baseline match actual info.
This function will fail the current test if either (1) an
extension named in the baseline is not currently installed in
Chrome; or (2) the api permissions or effective host
permissions of an extension in the baseline do not match the
actual permissions associated with the extension in Chrome.
Args:
baseline: A dictionary of expected extension information, containing
extension names and api/effective host permission info.
"""
full_ext_actual_info = self.get_extensions_info()
for ext_expected_info in baseline:
located_ext_info = [info for info in full_ext_actual_info if
info['name'] == ext_expected_info['name']]
self.pyauto.assertTrue(
located_ext_info,
msg=('Cannot locate extension info for "%s".\n'
'Expected extension info:\n%s' %
(ext_expected_info['name'],
self.pyauto.pformat(ext_expected_info))))
ext_actual_info = located_ext_info[0]
self.assert_perms_match(
set(ext_expected_info['effective_host_permissions']),
set(ext_actual_info['effective_host_permissions']),
'Effective host permissions', ext_expected_info,
ext_actual_info)
self.assert_perms_match(
set(ext_expected_info['api_permissions']),
set(ext_actual_info['api_permissions']),
'API permissions', ext_expected_info, ext_actual_info)
def test_component_extension_permissions(self):
"""Ensures component extension permissions are as expected."""
expected_names = [ext['name'] for ext in
self._component_extension_baseline]
ext_actual_info = self.get_extensions_info()
actual_names = [ext['name'] for ext in ext_actual_info if
ext['is_component']]
self.assert_names_match(
set(expected_names), set(actual_names), 'Component extension',
self._component_extension_baseline, ext_actual_info)
self.verify_extension_perms(self._component_extension_baseline)
def test_bundled_crx_permissions(self):
"""Ensures bundled CRX permissions are as expected."""
# Verify that each bundled CRX on the device is expected, then
# install it.
for file_name in os.listdir(self._bundled_crx_directory):
if file_name in self._ignored_crx_files:
logging.debug('Ignoring %s' % file_name)
continue
if file_name.endswith('.crx'):
self.pyauto.assertTrue(
file_name in [x['crx_file'] for x in
self._bundled_crx_baseline],
msg='Unexpected CRX file: ' + file_name)
crx_file = os.path.join(self._bundled_crx_directory, file_name)
self.attempt_install(crx_file)
# Verify that the permissions information in the baseline matches the
# permissions associated with the installed bundled CRX extensions.
self.verify_extension_perms(self._bundled_crx_baseline)
def test_no_unexpected_extensions(self):
"""Ensures there are no unexpected bundled or component extensions."""
# Install all bundled extensions on the device.
for file_name in os.listdir(self._bundled_crx_directory):
if file_name in self._ignored_crx_files:
logging.debug('Ignoring %s' % file_name)
continue
if file_name.endswith('.crx'):
crx_file = os.path.join(self._bundled_crx_directory, file_name)
self.attempt_install(crx_file)
logging.debug('Done installing extensions')
# Ensure that the set of installed extension names precisely
# matches the baseline.
expected_names = [ext['name'] for ext in
self._component_extension_baseline]
expected_names.extend([ext['name'] for ext in
self._bundled_crx_baseline])
ext_actual_info = self.get_extensions_info()
installed_names = [ext['name'] for ext in ext_actual_info]
self.assert_names_match(
set(expected_names), set(installed_names), 'Installed extension',
self._component_extension_baseline + self._bundled_crx_baseline,
ext_actual_info)
def run_once(self, mode=None):
self.load_baseline()
if self.pyauto.GetBrowserInfo()['properties']['is_official']:
self._component_extension_baseline.extend(self._official_components)
# In pyauto this was implemented as 3 different tests, each of
# which can raise without stopping execution of the other two,
# and each of which was run in its own clean session.
# The autotest analogue is to label these in the control file as
# 3 distinct runs of the test.
if mode == 'ComponentExtensionPermissions':
self.test_component_extension_permissions()
elif mode == 'BundledCrxPermissions':
self.test_bundled_crx_permissions()
elif mode == 'NoUnexpectedExtensions':
self.test_no_unexpected_extensions()
else:
error.TestFail('Unimplemented: %s' % mode)