Rewrite security_BundledExtensions
Reports all discrepancies now, not just the first one found.
Tracks extension by ID rather than name, won't fail on name changes now.
Much faster, consolidates 3 login/logouts into 1.
Handles board-specific extensions now.
BUG=chromium-os:36788
TEST=run_remote_tests ... on link, butterfly, zgb ToT
Reviewed-on: https://gerrit.chromium.org/gerrit/42602
Reviewed-by: Kees Cook <keescook@chromium.org>
Commit-Queue: Jim Hebert <jimhebert@chromium.org>
Tested-by: Jim Hebert <jimhebert@chromium.org>
(cherry picked from commit deb5557f3e6172a7594e316c06bba843927601ee)
Change-Id: I142a7f88d4ff2feb2d02d5e8ed2daf855dd3ba37
Reviewed-on: https://gerrit.chromium.org/gerrit/42961
Commit-Queue: Jim Hebert <jimhebert@chromium.org>
Reviewed-by: Jim Hebert <jimhebert@chromium.org>
Tested-by: Jim Hebert <jimhebert@chromium.org>
diff --git a/client/site_tests/security_BundledExtensions/baseline b/client/site_tests/security_BundledExtensions/baseline
index 297a4f3..8781ee0 100644
--- a/client/site_tests/security_BundledExtensions/baseline
+++ b/client/site_tests/security_BundledExtensions/baseline
@@ -2,16 +2,16 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
{
- "ignored_extension_names": [
- "Bookmark Manager",
- "Chrome Web Store",
- "GaiaTestAuthExtension"
+# Chrome Web Store ("ahfgeienlihckogmohjhadlkjgocpleb") and
+# Bookmark Manager ("eemcgdkfndhakfknompkggombfjjjeno") are
+# already tracked and reviewed on the Chrome side. Ignore them
+# in our test, otherwise it's just a baseline maintenance treadmill.
+ "ignored_extension_ids": [
+ "ahfgeienlihckogmohjhadlkjgocpleb",
+ "eemcgdkfndhakfknompkggombfjjjeno"
],
- "ignored_crx_files": [
- ],
- "bundled_crx_directory": "/opt/google/chrome/extensions",
"bundled_crx_baseline": [
- { "crx_file": "apdfllckaahabafndbhieahigkjlhalf.crx",
+ { "id": "apdfllckaahabafndbhieahigkjlhalf",
"name": "Google Drive",
"effective_host_permissions": [],
"api_permissions": ["background",
@@ -20,37 +20,37 @@
"notifications",
"unlimitedStorage"]
},
- { "crx_file": "blpcfgokakmgnkcojhhkbfbldkacnbeo.crx",
+ { "id": "blpcfgokakmgnkcojhhkbfbldkacnbeo",
"name": "YouTube",
"effective_host_permissions": [],
"api_permissions": ["appNotifications"]
},
- { "crx_file": "ejjicmeblgpmajnghnpcppodonldlgfn.crx",
+ { "id": "ejjicmeblgpmajnghnpcppodonldlgfn",
"name": "Google Calendar",
"effective_host_permissions": [],
"api_permissions": ["notifications",
"unlimitedStorage"]
},
- { "crx_file": "fobcpibfeplaikcclojfdhfdmbbeofai.crx",
+ { "id": "fobcpibfeplaikcclojfdhfdmbbeofai",
"name": "Games",
"effective_host_permissions": [],
"api_permissions": []
},
- { "crx_file": "gbchcmhmhahfdphkhkmpfmihenigjmpp.crx",
+ { "id": "gbchcmhmhahfdphkhkmpfmihenigjmpp",
"name": "Chrome Remote Desktop",
"effective_host_permissions": ["https://accounts.google.com/*",
"https://relay.google.com/*",
"https://*.talkgadget.google.com/*",
-"https://chromoting-oauth.talkgadget.google.com/talkgadget/oauth/chrome-remote-desktop/rel/*",
+"https://*.talkgadget.google.com/talkgadget/oauth/chrome-remote-desktop/rel/*",
"https://www.googleapis.com/*"],
"api_permissions": ["clipboardRead", "clipboardWrite"]
},
- { "crx_file": "icppfcnhkcmnfdhfhphakoifcfokfdhg.crx",
+ { "id": "icppfcnhkcmnfdhfhphakoifcfokfdhg",
"name": "Google Play Music",
"effective_host_permissions": [],
"api_permissions": []
},
- { "crx_file": "kjebfhglflhjjjiceimfkgicifkhjlnm.crx",
+ { "id": "kjebfhglflhjjjiceimfkgicifkhjlnm",
"name": "Scratchpad",
"effective_host_permissions": ["https://docs.google.com/*",
"https://www.google.com/*"],
@@ -59,78 +59,79 @@
"notifications",
"tabs"]
},
- { "crx_file": "pjkljhegncpnkpknbcohdijeoejaedia.crx",
+ { "id": "pjkljhegncpnkpknbcohdijeoejaedia",
"name": "Gmail",
"effective_host_permissions": [],
"api_permissions": ["notifications"]
},
- { "crx_file": "lneaknkopdijkpnocmklfnjbeapigfbh.crx",
+ { "id": "lneaknkopdijkpnocmklfnjbeapigfbh",
"name": "Google Maps",
"effective_host_permissions": [],
"api_permissions": []
},
- { "crx_file": "dlppkpafhbajpcmmoheippocdidnckmm.crx",
+ { "id": "dlppkpafhbajpcmmoheippocdidnckmm",
"name": "Google+",
"effective_host_permissions": [],
"api_permissions": []
},
- { "crx_file": "gbkeegbaiigmenfmjfclcdgdpimamgkj.crx",
+ { "id": "gbkeegbaiigmenfmjfclcdgdpimamgkj",
"name": "Chrome Office Viewer",
"effective_host_permissions": [],
"api_permissions": ["fileBrowserHandler",
"fileBrowserHandlerInternal",
+ "metricsPrivate",
"unlimitedStorage"
]
},
- { "crx_file": "coobgpohoikkiipiblmjeljniedjpjpf.crx",
+ { "id": "coobgpohoikkiipiblmjeljniedjpjpf",
"name": "Google Search",
"effective_host_permissions": [],
"api_permissions": []
},
- { "crx_file": "kbpgddbgniojgndnhlkjbkpknjhppkbk.crx",
+ { "id": "kbpgddbgniojgndnhlkjbkpknjhppkbk",
"name": "Google+ Hangouts",
"effective_host_permissions": [],
"api_permissions": ["background", "tabs"]
},
- { "crx_file": "hkhhlkdconhgemhegnplaldnmnmkaemd.crx",
+ { "id": "hkhhlkdconhgemhegnplaldnmnmkaemd",
"name": "Get Started",
"effective_host_permissions": [],
"api_permissions": ["unlimitedStorage"]
},
- { "crx_file": "mmimngoggfoobjdlefbcabngfnmieonb.crx",
+ { "id": "mmimngoggfoobjdlefbcabngfnmieonb",
"name": "Google Play Books",
"effective_host_permissions": [],
"api_permissions": ["clipboardWrite",
"unlimitedStorage"]
},
- { "crx_file": "aohghmighlieiainnegkcijnfilokake.crx",
+ { "id": "aohghmighlieiainnegkcijnfilokake",
"name": "Google Docs",
"effective_host_permissions": [],
"api_permissions": []
},
- { "crx_file": "joodangkbfjnajiiifokapkpmhfnpleo.crx",
+ { "id": "joodangkbfjnajiiifokapkpmhfnpleo",
"name": "Calculator",
"effective_host_permissions": [],
"api_permissions": ["app.currentWindowInternal",
"app.runtime",
"app.window"]
},
- { "crx_file": "felcaaldnbdncclmgdcncolpebgiejap.crx",
+ { "id": "felcaaldnbdncclmgdcncolpebgiejap",
"name": "Google Sheets",
"effective_host_permissions": [],
"api_permissions": []
},
- { "crx_file": "aapocclcgogkmnckokdopfmhonfmgoek.crx",
+ { "id": "aapocclcgogkmnckokdopfmhonfmgoek",
"name": "Google Slides",
"effective_host_permissions": [],
"api_permissions": []
},
- { "crx_file": "fppdphmgcddhjeddoeghpjefkdlccljb.crx",
+ { "id": "fppdphmgcddhjeddoeghpjefkdlccljb",
"name": "Google Play Movies",
"effective_host_permissions": [],
"api_permissions": []
},
- { "crx_file": "hfhhnacclhffhdffklopdkcgdhifgngh.crx",
+ { "id": "hfhhnacclhffhdffklopdkcgdhifgngh",
"name": "Camera",
"effective_host_permissions": [],
"api_permissions": ["app.currentWindowInternal",
@@ -145,7 +146,8 @@
}
],
"component_extension_baseline": [
- { "name": "Files",
+ { "id": "hhaomjibdihmijegdhdafkllkbggdgoj",
+ "name": "File Manager",
"effective_host_permissions": ["chrome://extension-icon/*",
"chrome://resources/*",
"chrome://theme/*",
@@ -161,7 +163,8 @@
"clipboardRead",
"unlimitedStorage"]
},
- { "name": "Wallpaper Picker",
+ { "id": "obklkkbkpaoaejdabbfldmcfplpdgolj",
+ "name": "Wallpaper Picker",
"effective_host_permissions": ["https://storage.googleapis.com/*",
"https://commondatastorage.googleapis.com/*"],
"api_permissions": ["app.currentWindowInternal",
@@ -170,28 +173,45 @@
"storage",
"wallpaperPrivate"]
},
- { "name": "Mobile Activation",
+ { "id": "iadeocfgjdjdmpenejdbfeaocpbikmab",
+ "name": "Mobile Activation",
"effective_host_permissions": [],
"api_permissions": []
},
- { "name": "crosh_builtin",
+ { "id": "nkoccljplnhpfnfiajclkommnmllphnl",
+ "name": "crosh_builtin",
"effective_host_permissions": [],
"api_permissions": ["terminalPrivate",
"unlimitedStorage"]
},
- { "name": "Chrome OS ECHO Extension",
+ { "id": "kddnkjkcjddckihglkfcickdhbmaodcn",
+ "name": "Chrome OS ECHO Extension",
"effective_host_permissions": ["https://*/*"],
"api_permissions": ["cookies",
"echoPrivate",
"experimental"]
},
- { "name": "Chrome",
+ { "id": "mgndgikekgjfcpckkfioiadnlibdjbkf",
+ "name": "Chrome",
+ "effective_host_permissions": [],
+ "api_permissions": []
+ },
+ { "id": "mlbmkoenclnokonejhlfakkeabdlmpek",
+ "name": "TimeScapes",
+ "boards": ["link"],
+ "effective_host_permissions": [],
+ "api_permissions": []
+ },
+ { "id": "plamffmopddapimaopfdbibfkjpbojbf",
+ "name": "Registration",
+ "boards": ["butterfly"],
"effective_host_permissions": [],
"api_permissions": []
}
],
"official_components": [
{ "name": "Help",
+ "id": "honijodknafkokifofgiaalefdiedpko",
"effective_host_permissions": [
"https://gweb-gettingstartedguide.appspot.com/*",
"http://support.google.com/chromeos/*",
diff --git a/client/site_tests/security_BundledExtensions/control b/client/site_tests/security_BundledExtensions/control
index 656a19c..0d1dcf5 100644
--- a/client/site_tests/security_BundledExtensions/control
+++ b/client/site_tests/security_BundledExtensions/control
@@ -22,12 +22,4 @@
list and fails (with various helpful details) if there is not an exact match.
"""
-job.run_test('security_BundledExtensions', creds='$default',
- tag='ComponentExtensionPermissions',
- mode='ComponentExtensionPermissions')
-job.run_test('security_BundledExtensions', creds='$default',
- tag='BundledCrxPermissions',
- mode='BundledCrxPermissions')
-job.run_test('security_BundledExtensions', creds='$default',
- tag='NoUnexpectedExtensions',
- mode='NoUnexpectedExtensions')
+job.run_test('security_BundledExtensions', creds='$default')
diff --git a/client/site_tests/security_BundledExtensions/security_BundledExtensions.py b/client/site_tests/security_BundledExtensions/security_BundledExtensions.py
index ae39aad..a8978fc 100644
--- a/client/site_tests/security_BundledExtensions/security_BundledExtensions.py
+++ b/client/site_tests/security_BundledExtensions/security_BundledExtensions.py
@@ -7,12 +7,11 @@
import os
from autotest_lib.client.bin import test
-from autotest_lib.client.common_lib import error
+from autotest_lib.client.common_lib import error, site_utils
from autotest_lib.client.cros import cros_ui_test
class security_BundledExtensions(cros_ui_test.UITest):
- version = 1
-
+ version = 2
def load_baseline(self):
bfile = open(os.path.join(self.bindir, 'baseline'))
@@ -22,9 +21,7 @@
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._ignored_extension_ids = baseline['ignored_extension_ids']
self._bundled_crx_baseline = baseline['bundled_crx_baseline']
self._component_extension_baseline = baseline[
'component_extension_baseline']
@@ -45,78 +42,11 @@
self.pyauto.pformat(complete_info))
filtered_info = []
for rec in complete_info:
- if not rec['name'] in self._ignored_extension_names:
+ if not rec['id'] in self._ignored_extension_ids:
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.
@@ -135,99 +65,101 @@
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)
+ def install_all(self, crx_dirs):
+ for crx_dir in crx_dirs:
+ if not os.path.exists(crx_dir):
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)
+ for file_name in os.listdir(crx_dir):
+ if not file_name.endswith('.crx'):
+ continue
+ crx_id = self.crx_id_from_filename(file_name)
+ if crx_id in self._ignored_extension_ids:
+ logging.debug('Ignoring %s' % file_name)
+ continue
+ self.attempt_install(os.path.join(crx_dir, file_name))
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 crx_id_from_filename(self, filename):
+ return filename.split('.crx')[0]
+
+
+ def install_and_compare(self):
+ test_fail = False
+ # Install all bundled extensions on the device.
+ main_crx_dir = '/opt/google/chrome/extensions'
+ board_specific_crx_dir = '/usr/share/google-chrome/extensions'
+ self.install_all([main_crx_dir, board_specific_crx_dir])
+
+ # * Find the set of expected IDs.
+ # * Find the set of observed IDs.
+ # * Do set comparison to find the unexpected, and the expected/missing.
+ combined_baseline = (self._bundled_crx_baseline +
+ self._component_extension_baseline)
+ # Filter out any baseline entries that don't apply to this board.
+ # If there is no 'boards' limiter on a given record, the record applies.
+ # If there IS a 'boards' limiter, check that it applies.
+ board = site_utils.get_current_board()
+ combined_baseline = [x for x in combined_baseline
+ if ((not 'boards' in x) or
+ ('boards' in x and board in x['boards']))]
+
+ observed_extensions = self.get_extensions_info()
+ observed_ids = set([x['id'] for x in observed_extensions])
+ expected_ids = set([x['id'] for x in combined_baseline])
+
+ missing_ids = expected_ids - observed_ids
+ missing_names = ['%s (%s)' % (x['name'], x['id'])
+ for x in combined_baseline if x['id'] in missing_ids]
+
+ unexpected_ids = observed_ids - expected_ids
+ unexpected_names = ['%s (%s)' % (x['name'], x['id'])
+ for x in observed_extensions if
+ x['id'] in unexpected_ids]
+
+ good_ids = expected_ids.intersection(observed_ids)
+
+ if missing_names:
+ logging.error('Missing: %s' % '; '.join(missing_names))
+ test_fail = True
+ if unexpected_names:
+ logging.error('Unexpected: %s' % '; '.join(unexpected_names))
+ test_fail = True
+
+ # For those IDs in both the expected-and-observed, ie, "good":
+ # Compare sets of expected-vs-actual API permissions, report diffs.
+ # Do same for host permissions.
+ for good_id in good_ids:
+ baseline = [x for x in combined_baseline if x['id'] == good_id][0]
+ actual = [x for x in observed_extensions if x['id'] == good_id][0]
+ # Check the API permissions.
+ baseline_apis = set(baseline['api_permissions'])
+ actual_apis = set(actual['api_permissions'])
+ missing_apis = baseline_apis - actual_apis
+ unexpected_apis = actual_apis - baseline_apis
+ if missing_apis or unexpected_apis:
+ test_fail = True
+ self._report_attribute_diffs(missing_apis, unexpected_apis,
+ actual)
+ # Check the host permissions.
+ baseline_hosts = set(baseline['effective_host_permissions'])
+ actual_hosts = set(actual['effective_host_permissions'])
+ missing_hosts = baseline_hosts - actual_hosts
+ unexpected_hosts = actual_hosts - baseline_hosts
+ if missing_hosts or unexpected_hosts:
+ test_fail = True
+ self._report_attribute_diffs(missing_hosts, unexpected_hosts,
+ actual)
+ if test_fail:
+ raise error.TestFail('Bundled extensions mismatch, see error log.')
+
+
+ def _report_attribute_diffs(self, missing, unexpected, rec):
+ logging.error('Problem with %s (%s):' % (rec['name'], rec['id']))
+ if missing:
+ logging.error('It no longer uses: %s' % '; '.join(missing))
+ if unexpected:
+ logging.error('It unexpectedly uses: %s' % '; '.join(unexpected))
def run_once(self, mode=None):
@@ -235,16 +167,4 @@
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)
+ self.install_and_compare()