click-reviewers-tools-0.44~16.04.1/0000775000000000000000000000000012750117317013517 5ustar click-reviewers-tools-0.44~16.04.1/Makefile0000664000000000000000000000133512660650204015156 0ustar all: # Use setup.py to install exit 1 install: all test: ./run-tests coverage: python3 -m coverage run ./run-tests coverage-report: python3 -m coverage report --show-missing --omit="*skeleton*,*/dist-packages/*" syntax-check: clean ./run-pyflakes ./run-pep8 python -mjson.tool ./data/*.json >/dev/null check-names: # make sure check-names.list is up to date cp -f check-names.list check-names.list.orig ./collect-check-names diff -Naur check-names.list.orig check-names.list || exit 1 rm -f check-names.list.orig check: test syntax-check check-names clean: rm -rf ./clickreviews/__pycache__ ./clickreviews/tests/__pycache__ rm -rf ./.coverage .PHONY: check-names.list check-names.list: ./collect-check-names click-reviewers-tools-0.44~16.04.1/collect-check-names-from-tests0000775000000000000000000000177312565666155021374 0ustar #!/usr/bin/python3 '''collect-check-names-from-tests: print list of check names discovered from tests''' # # Copyright (C) 2015 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # NOTE: changes to this file may also need to be made to 'run-tests' import logging import unittest test_directory = 'clickreviews/tests/' logging.basicConfig(level=logging.DEBUG) suite = unittest.TestLoader().discover(test_directory) unittest.TextTestRunner(verbosity=0).run(suite) click-reviewers-tools-0.44~16.04.1/clickreviews/0000775000000000000000000000000012750117074016211 5ustar click-reviewers-tools-0.44~16.04.1/clickreviews/__init__.py0000664000000000000000000000000012666350672020321 0ustar click-reviewers-tools-0.44~16.04.1/clickreviews/cr_lint.py0000664000000000000000000014415712736736344020244 0ustar '''cr_lint.py: lint checks''' # # Copyright (C) 2013-2015 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from __future__ import print_function from apt import apt_pkg from debian.deb822 import Deb822 import glob import os import re import stat import yaml from clickreviews.frameworks import Frameworks from clickreviews.cr_common import ( ClickReview, ) from clickreviews.common import ( open_file_read, cmd, error, ) from clickreviews.common import ( find_external_symlinks, ) CONTROL_FILE_NAMES = ["control", "manifest", "preinst"] MINIMUM_CLICK_FRAMEWORK_VERSION = "0.4" class ClickReviewLint(ClickReview): '''This class represents click lint reviews''' def __init__(self, fn, overrides=None): '''Set up the class.''' ClickReview.__init__(self, fn, "lint", overrides=overrides) if not self.is_click and not self.is_snap1: return if self.is_click and "md5sums" not in CONTROL_FILE_NAMES: CONTROL_FILE_NAMES.append("md5sums") elif self.is_snap1: CONTROL_FILE_NAMES.append("hashes.yaml") self.control_files = dict() self._list_control_files() # Valid values for Architecture in DEBIAN/control. Note: # all - no compiled code # multi - fat packages (click manifest declares which ones as a list) # armhf - compiled, single arch (unity8-based devices) # i386 - compiled, single arch (unity8-based emulator and desktop) # amd64 - compiled, single arch (unity8-based desktop) # arm64 - compiled, single arch (not available yet) # ppc64el - compiled, single arch (not available yet) # powerpc - compiled, single arch (not available yet) self.valid_compiled_architectures = ['armhf', 'i386', 'amd64', 'arm64', ] self.valid_control_architectures = ['all', 'multi', ] + self.valid_compiled_architectures self.vcs_files = ['.bzr*', # '.excludes', # autogenerated by SDK '.git*', '.idea', '.svn*', '.hg', '.project', 'CVS*', 'RCS*' ] if self.manifest is not None and 'maintainer' in self.manifest: maintainer = self.manifest['maintainer'] self.email = maintainer.partition('<')[2].rstrip('>') self.is_core_app = \ (self.click_pkgname.startswith('com.ubuntu.') and not self.click_pkgname.startswith('com.ubuntu.developer.') and (self.email == 'ubuntu-touch-coreapps@lists.launchpad.net' or self.email == 'ubuntu-devel-discuss@lists.ubuntu.com')) # "core scope" is not necessarily a word we use right now, but # we want to special case scopes which are written through our # vetted development process. self.is_core_scope = (self.click_pkgname.startswith('com.ubuntu.scopes.') and self.email == 'ubuntu-devel-discuss@lists.ubuntu.com') # "core snappy" is not necessarily a word we use right now, but # we want to special case scopes which are written through our # vetted development process. self.is_core_snappy = \ (self.click_pkgname.startswith('com.ubuntu.snappy.') and self.email == 'ubuntu-devel-discuss@lists.ubuntu.com') else: self.email = None self.is_core_app = False self.is_core_scope = False self.is_core_snappy = False self._list_all_compiled_binaries() self.known_hooks = ['accounts', 'account-application', 'account-provider', 'account-qml-plugin', 'account-service', 'apparmor', 'apparmor-profile', 'bin-path', # obsoleted, ignored 'content-hub', 'desktop', 'pay-ui', 'puritine', 'push-helper', 'scope', 'snappy-systemd', # obsoleted, ignored 'urls'] self.redflagged_hooks = ['pay-ui', 'puritine', 'apparmor-profile', ] # Valid values for 'type' in packaging yaml # - app # - framework # - kernel # - oem (deprecated, 15.04 only) # - gadget # - os self.snappy_valid_types = ['app', 'framework', 'kernel', 'oem', 'gadget', 'os', ] self.snappy_redflagged_types = ['framework', 'kernel', 'oem', 'gadget', 'os', ] def _list_control_files(self): '''List all control files with their full path.''' for i in CONTROL_FILE_NAMES: self.control_files[i] = os.path.join(self.unpack_dir, "DEBIAN/%s" % i) def check_control_files(self): '''Check DEBIAN/* files''' if not self.is_click and not self.is_snap1: return for f in self.control_files: t = 'info' n = self._get_check_name( 'DEBIAN_has_files', extra=os.path.basename(f)) s = "OK" if not os.path.isfile(self.control_files[os.path.basename(f)]): if self.is_snap1 and os.path.basename(f) == 'md5sums': s = "OK (skip md5sums with snap)" else: t = 'error' s = "'%s' not found in DEBIAN/" % os.path.basename(f) self._add_result(t, n, s) found = [] for f in sorted(glob.glob("%s/DEBIAN/*" % self.unpack_dir)): if os.path.basename(f) not in self.control_files: found.append(os.path.basename(f)) t = 'info' n = self._get_check_name('DEBIAN_extra_files') s = 'OK' if len(found) > 0: t = 'warn' s = 'found extra files in DEBIAN/: %s' % ", ".join(found) self._add_result(t, n, s) def check_control(self): '''Check control()''' if not self.is_click and not self.is_snap1: return fh = self._extract_control_file() tmp = list(Deb822.iter_paragraphs(fh)) t = 'info' n = self._get_check_name('control_structure') s = 'OK' if len(tmp) != 1: self._add_result('error', n, 'control malformed: too many paragraphs') return self._add_result(t, n, s) control = tmp[0] fields = ['Package', 'Version', 'Click-Version', 'Architecture', 'Maintainer', 'Installed-Size', 'Description'] error = False for f in sorted(fields): t = 'info' n = self._get_check_name('control_has_field', extra=f) s = 'OK' if f not in control: if f == 'Maintainer' and self.is_snap1: s = 'OK (maintainer not required for snappy)' else: t = 'error' s = "'%s' missing" % f error = True self._add_result(t, n, s) if error is True: return t = 'info' n = self._get_check_name('control_extra_fields') s = 'OK' found = [] for k in sorted(control.keys()): if k not in fields: found.append(k) if len(found) > 0: self._add_result('error', n, "found extra fields: '%s'" % (", ".join(found))) t = 'info' n = self._get_check_name('control_package_match') s = "OK" if self.manifest['name'] != self.click_pkgname: t = 'error' s = "Package=%s does not match manifest name=%s" % \ (self.manifest['name'], self.click_pkgname) self._add_result(t, n, s) t = 'info' n = self._get_check_name('control_version_match') s = "OK" if self.manifest['version'] != self.click_version: t = 'error' s = "Version=%s does not match manifest version=%s" % \ (self.manifest['version'], self.click_version) self._add_result(t, n, s) t = 'info' n = self._get_check_name('control_architecture_match') s = 'OK' if 'architecture' in self.manifest: if control['Architecture'] == "multi": if not isinstance(self.manifest['architecture'], (list, tuple)): t = 'error' s = 'If arch=multi, manifest architecture needs to ' + \ 'list all the individual architectures.' elif list(filter(lambda a: a not in self.valid_compiled_architectures, self.manifest['architecture'])): t = 'error' s = 'If arch=multi, manifest architecture needs to ' + \ 'comprise of only compiled architectures.' elif control['Architecture'] != self.manifest['architecture'] and \ not self.is_snap1: # snappy doesn't use this field; ignore t = 'error' s = "Architecture=%s " % control['Architecture'] + \ "does not match manifest architecture=%s" % \ self.manifest['architecture'] else: # Lack of architecture in manifest is not an error t = 'info' s = 'OK: architecture not specified in manifest' self._add_result(t, n, s) t = 'info' n = self._get_check_name('control_maintainer_match') s = 'OK' if 'maintainer' in self.manifest: if 'Maintainer' in control and \ control['Maintainer'] != self.manifest['maintainer']: t = 'error' s = "Maintainer=%s does not match manifest maintainer=%s" % \ (control['Maintainer'], self.manifest['maintainer']) else: s = 'Skipped: maintainer not in manifest' self._add_result(t, n, s) # TODO: click currently sets the Description to be the manifest title. # Is this intended behavior? t = 'info' n = self._get_check_name('control_description_match') s = 'OK' if 'title' in self.manifest: if control['Description'].strip() != \ self.manifest['title'].strip(): t = 'error' s = "Description=%s does not match manifest title=%s" % \ (control['Description'], self.manifest['title']) else: t = 'warn' s = 'Skipped: title not in manifest' self._add_result(t, n, s) t = 'info' n = self._get_check_name('control_click_version_up_to_date') s = 'OK' l = None if apt_pkg.version_compare( control['Click-Version'], MINIMUM_CLICK_FRAMEWORK_VERSION) < 0: t = 'error' s = "Click-Version is too old, has '%s', needs '%s' or newer" % ( control['Click-Version'], MINIMUM_CLICK_FRAMEWORK_VERSION) l = 'http://askubuntu.com/questions/417366/what-does-lint-control-click-version-up-to-date-mean/417367' self._add_result(t, n, s, l) t = 'info' n = self._get_check_name('control_installed_size') s = 'OK' try: int(control['Installed-Size']) except TypeError: t = 'error' s = "invalid Installed-Size '%s'" % (control['Installed-Size']) self._add_result(t, n, s) def check_md5sums(self): '''Check md5sums()''' if not self.is_click: return t = 'info' n = self._get_check_name('md5sums') # The puritine click will have bad checksums due to bad symlinks if 'hooks' in self.manifest: for app in self.manifest['hooks']: if "puritine" in self.manifest['hooks'][app]: s = "SKIPPED: puritine" self._add_result(t, n, s) return curdir = os.getcwd() fh = open_file_read(self.control_files["md5sums"]) badsums = [] os.chdir(self.unpack_dir) for line in fh.readlines(): split_line = line.strip().split() fn = " ".join(split_line[1:]) (rc, out) = cmd(['md5sum', fn]) if line != out: badsums.append(fn) fh.close() os.chdir(curdir) s = 'OK' if len(badsums) > 0: t = 'error' s = 'found bad checksums: %s' % ", ".join(badsums) self._add_result(t, n, s) def check_preinst(self): '''Check preinst()''' if not self.is_click and not self.is_snap1: return expected = '''#! /bin/sh echo "Click packages may not be installed directly using dpkg." echo "Use 'click install' instead." exit 1 ''' fh = open_file_read(self.control_files["preinst"]) contents = "" for line in fh.readlines(): contents += line fh.close() t = 'info' n = self._get_check_name('preinst') s = "OK" if contents != expected: t = 'error' s = "unexpected preinst contents" self._add_result(t, n, s) def check_hooks(self): '''Check click manifest hooks''' if not self.is_click and not self.is_snap1: return # oem snaps don't have a hooks entry if self.is_snap_oem: return # Some checks are already handled in # cr_common.py:_verify_manifest_structure() t = 'info' n = self._get_check_name('hooks_multiple_apps') s = 'OK' count = 0 for app in self.manifest['hooks']: if "desktop" in self.manifest['hooks'][app]: count += 1 if count > 1: # 'info' for now but this might be removed in a future version # (see https://launchpad.net/bugs/1496402) t = 'info' s = 'more than one desktop app specified in hooks' self._add_result(t, n, s) # Verify keys are well-formatted for app in self.manifest['hooks']: t = 'info' n = self._get_check_name('hooks_valid', app=app) s = "OK" if not re.search(r'^[A-Za-z0-9+-.:~-]+$', app): t = 'error' s = "malformed application name: '%s'" % app self._add_result(t, n, s) # Verify we have the required hooks required = ['apparmor'] for f in required: for app in self.manifest['hooks']: t = 'info' n = self._get_check_name('hooks', app=app, extra=f) s = "OK" if f in list(filter(lambda a: a.startswith('account-'), self.known_hooks)): s = "OK (run check-online-accounts for more checks)" elif f == "apparmor": s = "OK (run check-security for more checks)" elif f == "content-hub": s = "OK (run check-content-hub for more checks)" elif f == "desktop": s = "OK (run check-desktop for more checks)" elif f == "scope": s = "OK (run check-scope for more checks)" elif f == "urls": s = "OK (run check-url-dispatcher for more checks)" if f not in self.manifest['hooks'][app]: # TODO: when have apparmor policy for account-provider and # account-qml-plugin, remove this conditional if f == 'apparmor' and \ 'apparmor-profile' in self.manifest['hooks'][app]: # Don't require apparmor if have apparmor-profile pass elif f == 'apparmor' and \ 'puritine' in self.manifest['hooks'][app]: s = "OK (puritine does not use apparmor)" elif f != 'apparmor' or \ len(self.manifest['hooks'][app]) != 2 or \ 'account-provider' not in \ self.manifest['hooks'][app] or \ 'account-qml-plugin' not in \ self.manifest['hooks'][app]: t = 'error' s = "'%s' hook not found for '%s'" % (f, app) self._add_result(t, n, s) mutually_exclusive = ['scope', 'desktop'] for app in self.manifest['hooks']: t = 'info' n = self._get_check_name('exclusive_hooks', app=app) s = "OK" found = [] for i in mutually_exclusive: if i in self.manifest['hooks'][app]: found.append(i) if len(found) > 1: t = 'error' s = "'%s' hooks should not be used together" % ", ".join(found) self._add_result(t, n, s) for app in self.manifest['hooks']: if "apparmor" in self.manifest['hooks'][app]: t = 'info' n = self._get_check_name('sdk_security_extension', app=app) s = "OK" fn = self.manifest['hooks'][app]['apparmor'] if not fn.endswith(".apparmor"): t = 'info' s = '%s does not end with .apparmor (ok if not using sdk)' % fn self._add_result(t, n, s) def check_hooks_unknown(self): '''Check if have any unknown hooks''' if not self.is_click and not self.is_snap1: return # oem snaps don't have a hooks entry if self.is_snap_oem: return t = 'info' n = self._get_check_name('unknown_hooks') s = 'OK' # Verify keys are well-formatted for app in self.manifest['hooks']: for hook in self.manifest['hooks'][app]: t = 'info' n = self._get_check_name('hooks_known', app=app, extra=hook) s = "OK" if hook not in self.known_hooks: t = 'warn' s = "unknown hook '%s' in %s" % (hook, app) self._add_result(t, n, s) def check_hooks_redflagged(self): '''Check if have any redflagged hooks''' if not self.is_click and not self.is_snap1: return t = 'info' n = self._get_check_name('redflagged_hooks') s = 'OK' for app in self.manifest['hooks']: found = [] t = 'info' n = self._get_check_name('hooks_redflag', app=app) s = "OK" manual_review = False for hook in self.manifest['hooks'][app]: if hook in self.redflagged_hooks: # This check is handled elsewhere if self.is_snap1 and hook == "apparmor-profile": continue found.append(hook) if len(found) > 0: t = 'error' s = "(NEEDS REVIEW) '%s' not allowed" % ", ".join(found) manual_review = True self._add_result(t, n, s, manual_review=manual_review) def check_external_symlinks(self): '''Check if symlinks in the click package go out to the system.''' if not self.is_click and not self.is_snap1: return if self.is_snap1 and 'type' in self.pkg_yaml and \ self.pkg_yaml['type'] not in ['app', 'framework']: return t = 'info' n = self._get_check_name('external_symlinks') s = 'OK' # The puritine click is expected to have external symlinks if 'hooks' in self.manifest: for app in self.manifest['hooks']: if "puritine" in self.manifest['hooks'][app]: s = "SKIPPED: puritine" self._add_result(t, n, s) return links = find_external_symlinks(self.unpack_dir, self.pkg_files) if len(links) > 0: t = 'error' s = 'package contains external symlinks: %s' % ', '.join(links) self._add_result(t, n, s) def check_pkgname(self): '''Check click package name valid''' if not self.is_click: return p = self.manifest['name'] # http://www.debian.org/doc/debian-policy/ch-controlfields.html t = 'info' n = self._get_check_name('pkgname_valid') s = "OK" if not self._verify_pkgname(p): t = 'error' s = "'%s' not properly formatted" % p self._add_result(t, n, s) def check_version(self): '''Check click package version is valid''' if not self.is_click and not self.is_snap1: return # deb-version(5) t = 'info' n = self._get_check_name('version_valid') s = "OK" # From debian_support.py re_valid_version = re.compile(r'^((\d+):)?' # epoch '([A-Za-z0-9.+:~-]+?)' # upstream '(-([A-Za-z0-9+.~]+))?$') # debian if not re_valid_version.match(self.click_version): t = 'error' s = "'%s' not properly formatted" % self.click_version self._add_result(t, n, s) def check_architecture(self): '''Check click package architecture in DEBIAN/control is valid''' if not self.is_click and not self.is_snap1: return t = 'info' n = self._get_check_name('control_architecture_valid') s = 'OK' if self.pkg_arch[0] not in self.valid_control_architectures: t = 'error' s = "not a valid architecture: %s" % self.pkg_arch[0] self._add_result(t, n, s) def check_architecture_all(self): '''Check if actually architecture all''' if not self.is_click and not self.is_snap1: return t = 'info' n = self._get_check_name('control_architecture_valid_contents') s = 'OK' if self.pkg_arch[0] != "all": self._add_result(t, n, s) return # look for compiled code x_binaries = [] for i in self.pkg_bin_files: x_binaries.append(os.path.relpath(i, self.unpack_dir)) if len(x_binaries) > 0: t = 'error' s = "found binaries for architecture 'all': %s" % \ ", ".join(x_binaries) self._add_result(t, n, s) def check_architecture_specified_needed(self): '''Check if the specified architecture is actually needed''' if not self.is_click and not self.is_snap1: return for arch in self.pkg_arch: t = 'info' n = self._get_check_name('architecture_specified_needed') s = 'OK' if arch == "all": s = "SKIPPED: architecture is 'all'" self._add_result(t, n, s) return if len(self.pkg_bin_files) == 0: t = 'warn' s = "Could not find compiled binaries for architecture '%s'" % \ arch self._add_result(t, n, s) def check_maintainer(self): '''Check manifest maintainer()''' if not self.is_click and not self.is_snap1: return t = 'info' n = self._get_check_name('maintainer_present') s = 'OK' if 'maintainer' not in self.manifest: if self.is_snap1: s = 'Skipped optional maintainer field not specified in ' + \ 'manifest' else: t = 'error' s = 'required maintainer field not specified in manifest' self._add_result(t, n, s) return self._add_result(t, n, s) # Don't perform maintainer checks for snaps. They aren't used by # anything. if self.is_snap1: return # Simple regex as used by python3-debian. If we wanted to be more # thorough we could use email_re from django.core.validators t = 'info' n = self._get_check_name('maintainer_format') s = 'OK' if self.manifest['maintainer'] == "": self._add_result('error', n, 'invalid maintainer (empty), (should be ' 'like "Joe Bloggs ")', 'http://askubuntu.com/questions/417351/what-does-lint-maintainer-format-mean/417352') return elif not self._verify_maintainer(self.manifest['maintainer']): self._add_result('error', n, 'invalid format for maintainer: %s (should be ' 'like "Joe Bloggs ")' % self.manifest['maintainer'], 'http://askubuntu.com/questions/417351/what-does-lint-maintainer-format-mean/417352') return self._add_result(t, n, s) def check_title(self): '''Check manifest title()''' if not self.is_click and not self.is_snap1: return t = 'info' n = self._get_check_name('title_present') s = 'OK' if 'title' not in self.manifest: s = 'required title field not specified in manifest' self._add_result('error', n, s) return self._add_result(t, n, s) t = 'info' n = self._get_check_name('title') s = 'OK' pkgname_base = self.click_pkgname.split('.')[-1] if len(self.manifest['title']) < len(pkgname_base): t = 'info' s = "'%s' may be too short" % self.manifest['title'] self._add_result(t, n, s) def check_description(self): '''Check manifest description()''' if not self.is_click and not self.is_snap1: return t = 'info' n = self._get_check_name('description_present') s = 'OK' if 'description' not in self.manifest: s = 'required description field not specified in manifest' self._add_result('error', n, s) return self._add_result(t, n, s) t = 'info' n = self._get_check_name('description') s = 'OK' pkgname_base = self.click_pkgname.split('.')[-1] if len(self.manifest['description']) < len(pkgname_base): t = 'warn' if self.is_snap1 and (self.manifest['description'] == '\n' or self.manifest['description'] == ''): s = "manifest description is empty. Is meta/readme.md " + \ "formatted correctly?" else: s = "'%s' is too short" % self.manifest['description'] self._add_result(t, n, s) def check_framework(self): '''Check manifest framework()''' if not self.is_click and not self.is_snap1: return # Can check with: # click-check-lint '{"framework": {"ubuntu-sdk-15.04": { # "state": "obsolete", "policy_vendor": "ubuntu-core", # "policy_version": "16.04"}}}' n = self._get_check_name('framework') l = "http://askubuntu.com/questions/460512/what-framework-should-i-use-in-my-manifest-file" framework_overrides = self.overrides.get('framework', {}) frameworks = Frameworks(overrides=framework_overrides) if not self.is_snap1 and ',' in self.manifest['framework']: # click doesn't support multiple frameworks yet t = 'error' s = 'ERROR: multiple frameworks found in click manifest' self._add_result(t, n, s) return for framework in self.manifest['framework'].split(','): framework = framework.strip() if framework in frameworks.OBSOLETE_FRAMEWORKS: t = 'error' s = "'%s' is obsolete. Please use a newer framework" % \ framework self._add_result(t, n, s, l) return elif framework in frameworks.DEPRECATED_FRAMEWORKS: t = 'warn' s = "'%s' is deprecated. Please use a newer framework" % \ framework self._add_result(t, n, s, l) return elif framework in frameworks.AVAILABLE_FRAMEWORKS: t = 'info' s = 'OK' self._add_result(t, n, s) return else: # None of the above checks triggered, this is an unknown framework t = 'error' s = "'%s' is not a supported framework" % \ framework self._add_result(t, n, s, l) def check_click_local_extensions(self): '''Report any click local extensions''' if not self.is_click and not self.is_snap1: return t = 'info' n = self._get_check_name('click_local_extensions') s = 'OK' found = [] for k in sorted(self.manifest): if k.startswith('x-'): found.append(k) # Some local extensions are ok for core apps, so just skip them if self.is_core_app: for i in ['x-source', 'x-test']: if i in found: found.remove(i) if len(found) > 0: t = 'warn' plural = "" if len(found) > 1: plural = "s" s = 'found unofficial extension%s: %s' % (plural, ', '.join(found)) self._add_result(t, n, s) def check_vcs(self): '''Check for VCS files in the package''' if not self.is_click and not self.is_snap1: return t = 'info' n = self._get_check_name('vcs_files') s = 'OK' found = [] for d in self.vcs_files: entries = glob.glob("%s/%s" % (self.unpack_dir, d)) if len(entries) > 0: for i in entries: found.append(os.path.relpath(i, self.unpack_dir)) if len(found) > 0: t = 'warn' s = 'found VCS files in package: %s' % ", ".join(found) self._add_result(t, n, s) def check_click_in_package(self): '''Check for *.click files in the toplevel click package''' if not self.is_click: return t = 'info' n = self._get_check_name('click_files') s = 'OK' found = [] entries = glob.glob("%s/*.click" % self.unpack_dir) if len(entries) > 0: for i in entries: found.append(os.path.relpath(i, self.unpack_dir)) if len(found) > 0: t = 'warn' s = 'found click packages in toplevel dir: %s' % ", ".join(found) self._add_result(t, n, s) def check_dot_click(self): '''Check for .click directory in the toplevel click package''' if not self.is_click and not self.is_snap1: return t = 'info' n = self._get_check_name('dot_click') s = 'OK' path = os.path.join(self.unpack_dir, '.click') if os.path.exists(path): t = 'error' s = 'found .click in toplevel dir' self._add_result(t, n, s) def check_contents_for_hardcoded_paths(self): '''Check for known hardcoded paths.''' if not self.is_click and not self.is_snap1: return PATH_BLACKLIST = ["/opt/click.ubuntu.com/"] t = 'info' n = self._get_check_name('hardcoded_paths') s = 'OK' for dirpath, dirnames, filenames in os.walk(self.unpack_dir): for filename in filenames: full_fn = os.path.join(dirpath, filename) (rc, out) = cmd(['file', '-b', full_fn]) if 'text' not in out: continue try: lines = open_file_read(full_fn).readlines() for bad_path in PATH_BLACKLIST: if list(filter(lambda line: bad_path in line, lines)): t = 'error' s = "Hardcoded path '%s' found in '%s'." % ( bad_path, full_fn) except FileNotFoundError: pass except UnicodeDecodeError: pass self._add_result(t, n, s) def _verify_architecture(self, my_dict, test_str): t = 'info' n = self._get_check_name('%s_architecture_valid' % test_str) s = 'OK' if 'architecture' not in my_dict and 'architectures' not in my_dict: s = 'OK (architecture not specified)' self._add_result(t, n, s) return key = 'architecture' if self.is_snap1 and 'architectures' in my_dict: # new yaml allows for 'architecture' and 'architectures' key = 'architectures' archs_list = list(self.valid_control_architectures) archs_list.remove("multi") if self.is_snap1 and key == 'architectures' and \ isinstance(my_dict[key], str): # new yaml uses 'architectures' that must be a list t = 'error' s = "not a valid architecture: %s (not a list)" % my_dict[key] elif isinstance(my_dict[key], str) and my_dict[key] not in archs_list: t = 'error' s = "not a valid architecture: %s" % my_dict[key] elif isinstance(my_dict[key], list): if not self.is_snap1: archs_list.remove("all") bad_archs = [] for a in my_dict[key]: if a not in archs_list: bad_archs.append(a) if len(bad_archs) > 0: t = 'error' s = "not valid multi architecture: %s" % \ ",".join(bad_archs) self._add_result(t, n, s) def check_manifest_architecture(self): '''Check package architecture in manifest is valid''' if not self.is_click and not self.is_snap1: return self._verify_architecture(self.manifest, "manifest") def _verify_icon(self, my_dict, test_str): t = 'info' n = self._get_check_name('%s_icon_present' % test_str) s = 'OK' if 'icon' not in my_dict: s = 'Skipped, optional icon not present' self._add_result(t, n, s) return self._add_result(t, n, s) t = 'info' n = self._get_check_name('%s_icon_empty' % test_str) s = 'OK' if len(my_dict['icon']) == 0: t = 'error' s = "icon entry is empty" return self._add_result(t, n, s) t = 'info' n = self._get_check_name('%s_icon_absolute_path' % test_str) s = 'OK' if my_dict['icon'].startswith('/'): t = 'error' s = "icon entry '%s' should not specify absolute path" % \ my_dict['icon'] self._add_result(t, n, s) def check_icon(self): '''Check icon()''' if not self.is_click and not self.is_snap1: return self._verify_icon(self.manifest, "manifest") def check_snappy_name(self): '''Check package name''' if self.is_click or not self.is_snap1: return t = 'info' n = self._get_check_name('snappy_name_valid') s = 'OK' if 'name' not in self.pkg_yaml: t = 'error' s = "could not find 'name' in yaml" elif not self._verify_pkgname(self.pkg_yaml['name']): t = 'error' s = "malformed 'name': '%s'" % self.pkg_yaml['name'] self._add_result(t, n, s) def check_snappy_version(self): '''Check package version''' if self.is_click or not self.is_snap1: return t = 'info' n = self._get_check_name('snappy_version_valid') s = 'OK' if 'version' not in self.pkg_yaml: t = 'error' s = "could not find 'version' in yaml" elif not self._verify_pkgversion(self.pkg_yaml['version']): t = 'error' s = "malformed 'version': '%s'" % self.pkg_yaml['version'] self._add_result(t, n, s) def check_snappy_type(self): '''Check type''' if self.is_click or not self.is_snap1: return t = 'info' n = self._get_check_name('snappy_type_valid') s = 'OK' if 'type' not in self.pkg_yaml: s = 'OK (skip missing)' elif self.pkg_yaml['type'] not in self.snappy_valid_types: t = 'error' s = "unknown 'type': '%s'" % self.pkg_yaml['type'] self._add_result(t, n, s) def check_snappy_type_redflagged(self): '''Check if snappy type is redflagged''' if self.is_click or not self.is_snap1: return t = 'info' n = self._get_check_name('snappy_type_redflag') s = "OK" l = None manual_review = False if 'type' not in self.pkg_yaml: s = 'OK (skip missing)' elif self.pkg_yaml['type'] in self.snappy_redflagged_types: t = 'error' s = "(NEEDS REVIEW) type '%s' not allowed" % self.pkg_yaml['type'] manual_review = True if self.pkg_yaml['type'] == "framework": l = "https://developer.ubuntu.com/en/snappy/guides/frameworks/" self._add_result(t, n, s, link=l, manual_review=manual_review) def check_snappy_icon(self): '''Check icon()''' if self.is_click or not self.is_snap1: return self._verify_icon(self.pkg_yaml, "package_yaml") def check_snappy_architecture(self): '''Check package architecture in package.yaml is valid''' if self.is_click or not self.is_snap1: return self._verify_architecture(self.pkg_yaml, "package yaml") t = 'info' n = self._get_check_name('snappy_architecture_deprecated') s = 'OK' l = None if 'architecture' in self.pkg_yaml: t = 'warn' s = "Found deprecated 'architecture' field in " + \ "meta/package.yaml. Use 'architectures' instead" l = 'https://developer.ubuntu.com/en/snappy/guides/package-metadata/' self._add_result(t, n, s, link=l) def check_snappy_unknown_entries(self): '''Check for any unknown fields''' if self.is_click or not self.is_snap1: return t = 'info' n = self._get_check_name('snappy_unknown') s = 'OK' unknown = [] for f in self.pkg_yaml: if f not in self.snappy_required + self.snappy_optional: unknown.append(f) if len(unknown) > 0: t = 'warn' s = "unknown entries in package.yaml: '%s'" % \ (",".join(sorted(unknown))) obsoleted = ['maintainer', 'ports'] tmp = list(set(unknown) & set(obsoleted)) if len(tmp) > 0: t = 'error' s += " (%s obsoleted)" % ",".join(tmp) self._add_result(t, n, s) def _extract_readme_md(self): '''Extract meta/readme.md''' contents = None readme = os.path.join(self.unpack_dir, "meta/readme.md") if os.path.exists(readme): fh = open_file_read(readme) contents = fh.read() return contents def check_snappy_readme_md(self): '''Check snappy readme.md''' if self.is_click or not self.is_snap1: return contents = self._extract_readme_md() t = 'info' n = self._get_check_name('snappy_readme.md') s = 'OK' if contents is None: t = 'error' s = 'meta/readme.md does not exist' self._add_result(t, n, s) return self._add_result(t, n, s) t = 'info' n = self._get_check_name('snappy_readme.md_length') s = 'OK' pkgname_base = self.pkg_yaml['name'].split('.')[0] if len(contents) < len(pkgname_base): t = 'warn' s = "meta/readme.md is too short" self._add_result(t, n, s) def check_snappy_config(self): '''Check snappy config''' if self.is_click or not self.is_snap1: return fn = os.path.join(self.unpack_dir, 'meta/hooks/config') if fn not in self.pkg_files: return t = 'info' n = self._get_check_name('snappy_config_hook_executable') s = 'OK' if not self._check_innerpath_executable(fn): t = 'error' s = 'meta/hooks/config is not executable' self._add_result(t, n, s) def check_snappy_services_and_binaries(self): '''Services and binaries should not overlap''' if self.is_click or not self.is_snap1: return for exe_t in ['binaries', 'services']: if exe_t not in self.pkg_yaml: break if exe_t == 'binaries': other_exe_t = 'services' else: other_exe_t = 'binaries' if other_exe_t not in self.pkg_yaml: break for a in self.pkg_yaml[exe_t]: if 'name' not in a: continue # handle 'exec' in binaries app = os.path.basename(a['name']) t = 'info' n = self._get_check_name( 'snappy_in_%s' % other_exe_t, app=app) s = 'OK' for other_a in self.pkg_yaml[other_exe_t]: other_app = os.path.basename(other_a['name']) if app == other_app: t = 'error' s = "'%s' in both 'services' and 'binaries'" % app break self._add_result(t, n, s) def check_snappy_hashes(self): '''Check snappy hashes.yaml''' if self.is_click or not self.is_snap1: return def _check_allowed_perms(mode, allowed): '''Check that mode only uses allowed perms''' for p in mode[1:]: if p not in allowed: return False return True try: hashes_yaml = yaml.safe_load(self._extract_hashes_yaml()) except Exception: error("Could not load hashes.yaml. Is it properly formatted?") if 'archive-sha512' not in hashes_yaml: t = 'error' n = self._get_check_name('hashes_archive-sha512_present') s = "'archive-sha512' not found in hashes.yaml" self._add_result(t, n, s) return # verify the ar file t = 'info' n = self._get_check_name('hashes_archive-sha512_valid') s = 'OK' fn = self._path_join(self.raw_unpack_dir, 'data.tar.gz') sum = self._get_sha512sum(fn) if hashes_yaml['archive-sha512'] != sum: t = 'error' s = "hash mismatch: '%s' != '%s'" % (hashes_yaml['archive-sha512'], sum) self._add_result(t, n, s) return self._add_result(t, n, s) if 'files' not in hashes_yaml: t = 'error' n = self._get_check_name('hashes_files_present') s = "'files' not found in hashes.yaml" self._add_result(t, n, s) return # verify the individual files errors = [] badsums = [] hash_files = set([]) # used to check with extra files for entry in hashes_yaml['files']: if 'name' not in entry: errors.append("'name' not found for entry '%s'" % entry) continue else: hash_files.add(entry['name']) if 'mode' not in entry: errors.append("'mode' not found for entry '%s'" % entry['name']) continue elif len(entry['mode']) != 10: errors.append("malformed mode '%s' for entry '%s'" % (entry['mode'], entry['name'])) continue elif entry['mode'].startswith('d'): if not _check_allowed_perms(entry['mode'], ['r', 'w', 'x', '-']): errors.append("unusual mode '%s' for entry '%s'" % (entry['mode'], entry['name'])) # world write shouldn't be allowed if entry['mode'][-2] != '-': errors.append("'%s' is world-writable" % entry['name']) continue elif entry['mode'].startswith('l'): if entry['mode'] != "lrwxrwxrwx": errors.append("unusual mode '%s' for entry '%s'" % (entry['mode'], entry['name'])) continue elif not entry['mode'].startswith('f'): # files and symlinks are ok, everything else is not errors.append("illegal file mode '%s': '%s' for '%s'" % (entry['mode'][0], entry['mode'], entry['name'])) continue elif 'size' not in entry: errors.append("'size' not found for entry: %s" % entry['name']) continue elif 'sha512' not in entry: errors.append("'sha512' not found for entry: %s" % entry['name']) continue fn = self._path_join(self.unpack_dir, entry['name']) # quick verify the size, if it is wrong, we don't have to do the # sha512sum statinfo = self._extract_statinfo(fn) # file is missing if statinfo is None: errors.append("'%s' does not exist" % entry['name']) continue if entry['size'] != statinfo.st_size: errors.append("size %d != %d for '%s'" % (entry['size'], statinfo.st_size, entry['name'])) continue # check for weird perms if not _check_allowed_perms(entry['mode'], ['r', 'w', 'x', '-']): errors.append("unusual mode '%s' for entry '%s'" % (entry['mode'], entry['name'])) continue # world write shouldn't be allowed if entry['mode'][-2] != '-': errors.append("mode '%s' for '%s' is world-writable" % (entry['mode'], entry['name'])) continue # verify perms match what is in hashes.yaml filemode = stat.filemode(statinfo.st_mode)[1:] if entry['mode'][1:] != filemode: errors.append("mode '%s' != '%s' for '%s'" % (entry['mode'][1:], filemode, entry['name'])) continue # ok, now all the cheap tests are done so we can check if we have a # valid sha512sum sum = self._get_sha512sum(fn) if entry['sha512'] != sum: badsums.append("'%s' != '%s' for '%s'" % (entry['sha512'], sum, entry['name'])) t = 'info' n = self._get_check_name('sha512sums') s = 'OK' if len(badsums) > 0: t = 'error' s = 'found bad checksums: %s' % ", ".join(badsums) self._add_result(t, n, s) t = 'info' n = self._get_check_name('file_mode') s = 'OK' if len(errors) > 0: t = 'error' s = 'found errors in hashes.yaml: %s' % ", ".join(errors) self._add_result(t, n, s) # Now check for extra files t = 'info' n = self._get_check_name('hashes_extra_files') s = 'OK' self._add_result(t, n, s) extra = [] click_compat_files = set(["DEBIAN/hashes.yaml", "DEBIAN/control", "DEBIAN/preinst", "DEBIAN/manifest" ]) for f in self.pkg_files: fn = os.path.relpath(f, self.unpack_dir) if fn not in hash_files and fn not in click_compat_files: extra.append(fn) if len(extra) > 0: t = 'error' s = 'found extra files not listed in hashes.yaml: %s' % \ ", ".join(extra) self._add_result(t, n, s) def check_snappy_frameworks(self): '''TODO''' return False click-reviewers-tools-0.44~16.04.1/clickreviews/cr_common.py0000664000000000000000000004305512666351141020547 0ustar '''cr_common.py: common classes and functions''' # # Copyright (C) 2013-2016 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from __future__ import print_function from debian.deb822 import Deb822 import glob import json import os import pprint import re import yaml from clickreviews.common import( Review, ReviewException, error, open_file_read, ) # # Utility classes # class ClickReviewException(ReviewException): '''This class represents ClickReview exceptions''' class ClickReview(Review): '''This class represents click reviews''' # Convenience to break out common types of clicks (eg, app, scope, # click service) app_allowed_peer_hooks = ["account-application", "account-service", "accounts", "account-provider", "account-qml-plugin", "apparmor", "content-hub", "desktop", "push-helper", "urls", ] scope_allowed_peer_hooks = ["account-application", "account-service", "accounts", "apparmor", "scope", ] # FIXME: when apparmor-policy is implemented, use this service_allowed_peer_hooks = ["apparmor", "bin-path", # obsoleted, ignored "snappy-systemd", # obsoleted, ignored ] snappy_required = ["name", "version"] # optional snappy fields here (may be required by appstore) snappy_optional = ["architecture", "architectures", "binaries", "caps", "config", "dtbs", "firmware", "frameworks", "icon", "immutable-config", "initrd", "kernel", "modules", "oem", "gadget", "services", "source", "type", "vendor", # deprecated, unused ] snappy_exe_security = ["caps", "security-template", "security-override", "security-policy"] magic_binary_file_descriptions = [ 'application/x-executable; charset=binary', 'application/x-sharedlib; charset=binary', 'application/x-object; charset=binary', 'application/octet-stream; charset=binary' ] def __init__(self, fn, review_type, peer_hooks=None, overrides=None, peer_hooks_link=None): Review.__init__(self, fn, review_type, overrides=overrides) # The cr_* scripts only support 15.04 snaps (v1). Use sr_* scripts for # 16.04 (v2) or higher if not self.is_click and not self.is_snap1: return if not self.pkg_filename.endswith(".click") and \ not self.pkg_filename.endswith(".snap"): if self.pkg_filename.endswith(".deb"): error("filename does not end with '.click', but '.deb' " "instead. See http://askubuntu.com/a/485544/94326 for " "how click packages are different.") error("filename does not end with '.click'") self.manifest = None self.click_pkgname = None self.click_version = None self.pkg_arch = [] self.is_snap_oem = False self.peer_hooks = peer_hooks self.peer_hooks_link = peer_hooks_link if self.is_snap1: pkg_yaml = self._extract_package_yaml() if pkg_yaml: try: self.pkg_yaml = yaml.safe_load(pkg_yaml) except Exception: error("Could not load package.yaml. Is it properly formatted?") self._verify_package_yaml_structure() else: error("Could not load package.yaml.") # default to 'app' if 'type' not in self.pkg_yaml: self.pkg_yaml['type'] = 'app' if 'architectures' in self.pkg_yaml: self.pkg_arch = self.pkg_yaml['architectures'] elif 'architecture' in self.pkg_yaml: if isinstance(self.pkg_yaml['architecture'], str): self.pkg_arch = [self.pkg_yaml['architecture']] elif isinstance(self.pkg_yaml['architecture'], list): self.pkg_arch = self.pkg_yaml['architecture'] else: error("Could not load package.yaml: invalid 'architecture'") else: self.pkg_arch = ['all'] if 'type' in self.pkg_yaml and self.pkg_yaml['type'] == 'oem': self.is_snap_oem = True if self.is_click or self.is_snap1: # Get some basic information from the control file control_file = self._extract_control_file() tmp = list(Deb822.iter_paragraphs(control_file)) if len(tmp) != 1: error("malformed control file: too many paragraphs") control = tmp[0] self.click_pkgname = control['Package'] self.click_version = control['Version'] if self.is_click: if control['Architecture'] not in self.pkg_arch: self.pkg_arch.append(control['Architecture']) self.pkgfmt["version"] = str(control['Click-Version']) # Parse and store the manifest manifest_json = self._extract_manifest_file() try: self.manifest = json.load(manifest_json) except Exception: error("Could not load manifest file. Is it properly formatted?") self._verify_manifest_structure() self.valid_frameworks = self._extract_click_frameworks() def _extract_click_frameworks(self): '''Extract installed click frameworks''' # TODO: update to use libclick API when available valid_frameworks = [] frameworks = sorted( glob.glob("/usr/share/click/frameworks/*.framework")) if len(frameworks) == 0: valid_frameworks.append('ubuntu-sdk-13.10') else: for f in frameworks: valid_frameworks.append(os.path.basename( os.path.splitext(f)[0])) return valid_frameworks def _extract_manifest_file(self): '''Extract and read the manifest file''' m = os.path.join(self.unpack_dir, "DEBIAN/manifest") if not os.path.isfile(m): error("Could not find manifest file") return open_file_read(m) def _extract_package_yaml(self): '''Extract and read the snappy 15.04 package.yaml''' y = os.path.join(self.unpack_dir, "meta/package.yaml") if not os.path.isfile(y): return None # snappy packaging is still optional return open_file_read(y) def _extract_hashes_yaml(self): '''Extract and read the snappy hashes.yaml''' y = os.path.join(self.unpack_dir, "DEBIAN/hashes.yaml") return open_file_read(y) def _extract_control_file(self): '''Extract ''' fh = open_file_read(os.path.join(self.unpack_dir, "DEBIAN/control")) return fh.readlines() def _verify_manifest_structure(self): '''Verify manifest has the expected structure''' # lp:click doc/file-format.rst mp = pprint.pformat(self.manifest) if not isinstance(self.manifest, dict): error("manifest malformed:\n%s" % self.manifest) required = ["name", "version", "framework"] # click required for f in required: if f not in self.manifest: error("could not find required '%s' in manifest:\n%s" % (f, mp)) elif not isinstance(self.manifest[f], str): error("manifest malformed: '%s' is not str:\n%s" % (f, mp)) # optional click fields here (may be required by appstore) # http://click.readthedocs.org/en/latest/file-format.html optional = ["title", "description", "maintainer", "architecture", "installed-size", "icon"] # https://developer.ubuntu.com/snappy/guides/packaging-format-apps/ snappy_optional = ["ports", "source", "type"] for f in optional: if f in self.manifest: if f != "architecture" and \ not isinstance(self.manifest[f], str): error("manifest malformed: '%s' is not str:\n%s" % (f, mp)) elif f == "architecture" and not \ (isinstance(self.manifest[f], str) or isinstance(self.manifest[f], list)): error("manifest malformed: '%s' is not str or list:\n%s" % (f, mp)) # FIXME: this is kinda gross but the best we can do while we are trying # to support clicks and native snaps if 'type' in self.manifest and self.manifest['type'] == 'oem': if 'hooks' in self.manifest: error("'hooks' present in manifest with type 'oem'") # mock up something for other tests self.manifest['hooks'] = {'oem': {'reviewtools': True}} # Not required by click, but required by appstore. 'hooks' is assumed # to be present in other checks if 'hooks' not in self.manifest: error("could not find required 'hooks' in manifest:\n%s" % mp) if not isinstance(self.manifest['hooks'], dict): error("manifest malformed: 'hooks' is not dict:\n%s" % mp) # 'hooks' is assumed to be present and non-empty in other checks if len(self.manifest['hooks']) < 1: error("manifest malformed: 'hooks' is empty:\n%s" % mp) for app in self.manifest['hooks']: if not isinstance(self.manifest['hooks'][app], dict): error("manifest malformed: hooks/%s is not dict:\n%s" % (app, mp)) # let cr_lint.py handle required hooks if len(self.manifest['hooks'][app]) < 1: error("manifest malformed: hooks/%s is empty:\n%s" % (app, mp)) for k in sorted(self.manifest): if k not in required + optional + snappy_optional + ['hooks']: # click supports local extensions via 'x-...', ignore those # here but report in lint if k.startswith('x-'): continue error("manifest malformed: unsupported field '%s':\n%s" % (k, mp)) def _verify_package_yaml_structure(self): '''Verify package.yaml has the expected structure''' # https://developer.ubuntu.com/en/snappy/guides/packaging-format-apps/ # lp:click doc/file-format.rst yp = yaml.dump(self.pkg_yaml, default_flow_style=False, indent=4) if not isinstance(self.pkg_yaml, dict): error("package yaml malformed:\n%s" % self.pkg_yaml) for f in self.snappy_required: if f not in self.pkg_yaml: error("could not find required '%s' in package.yaml:\n%s" % (f, yp)) elif f in ['name', 'version']: # make sure this is a string for other tests since # yaml.safe_load may make it an int, float or str self.pkg_yaml[f] = str(self.pkg_yaml[f]) for f in self.snappy_optional: if f in self.pkg_yaml: if f in ["architecture", "frameworks"] and not \ (isinstance(self.pkg_yaml[f], str) or isinstance(self.pkg_yaml[f], list)): error("yaml malformed: '%s' is not str or list:\n%s" % (f, yp)) elif f in ["binaries", "services"] and not \ isinstance(self.pkg_yaml[f], list): error("yaml malformed: '%s' is not list:\n%s" % (f, yp)) elif f in ["icon", "source", "type", "vendor"] and not \ isinstance(self.pkg_yaml[f], str): error("yaml malformed: '%s' is not str:\n%s" % (f, yp)) def _verify_peer_hooks(self, my_hook): '''Compare manifest for required and allowed hooks''' d = dict() if self.peer_hooks is None: return d for app in self.manifest["hooks"]: if my_hook not in self.manifest["hooks"][app]: continue for h in self.peer_hooks[my_hook]['required']: if h == my_hook: continue if h not in self.manifest["hooks"][app]: # Treat these as equivalent for satisfying peer hooks if h == 'apparmor' and \ 'apparmor-profile' in self.manifest["hooks"][app]: continue if 'missing' not in d: d['missing'] = dict() if app not in d['missing']: d['missing'][app] = [] d['missing'][app].append(h) for h in self.manifest["hooks"][app]: if h == my_hook: continue if h not in self.peer_hooks[my_hook]['allowed']: # 'apparmor-profile' is allowed when 'apparmor' is, but # they may not be used together if h == 'apparmor-profile': if 'apparmor' in self.peer_hooks[my_hook]['allowed'] \ and 'apparmor' not in self.manifest["hooks"][app]: continue if 'disallowed' not in d: d['disallowed'] = dict() if app not in d['disallowed']: d['disallowed'][app] = [] d['disallowed'][app].append(h) return d def _verify_pkgname(self, n): '''Verify package name''' if self.is_snap1: # snaps can't have '.' in the name pat = re.compile(r'^[a-z0-9][a-z0-9+-]+$') else: pat = re.compile(r'^[a-z0-9][a-z0-9+.-]+$') if pat.search(n): return True return False def _verify_maintainer(self, m): '''Verify maintainer email''' # Simple regex as used by python3-debian. If we wanted to be more # thorough we could use email_re from django.core.validators if re.search(r"^(.*)\s+<(.*@.*)>$", m): return True return False def _create_dict(self, lst, topkey='name'): '''Converts list of dicts into dict[topkey][]. Useful for conversions from yaml list to json dict''' d = dict() for entry in lst: if topkey not in entry: error("required field '%s' not present: %s" % (topkey, entry)) name = entry[topkey] d[name] = dict() for key in entry: if key == topkey: continue d[name][key] = entry[key] return d def check_peer_hooks(self, hooks_sublist=[]): '''Check if peer hooks are valid''' # Nothing to verify if not hasattr(self, 'peer_hooks') or self.peer_hooks is None: return for hook in self.peer_hooks: if len(hooks_sublist) > 0 and hook not in hooks_sublist: continue d = self._verify_peer_hooks(hook) t = 'info' n = self._get_check_name("peer_hooks_required", extra=hook) s = "OK" if 'missing' in d and len(d['missing'].keys()) > 0: t = 'error' for app in d['missing']: s = "Missing required hooks for '%s': %s" % ( app, ", ".join(d['missing'][app])) self._add_result(t, n, s, manual_review=True, link=self.peer_hooks_link) else: self._add_result(t, n, s) t = 'info' n = self._get_check_name("peer_hooks_disallowed", extra=hook) s = "OK" if 'disallowed' in d and len(d['disallowed'].keys()) > 0: t = 'error' for app in d['disallowed']: s = "Disallowed with %s (%s): %s" % ( hook, app, ", ".join(d['disallowed'][app])) self._add_result(t, n, s, manual_review=True, link=self.peer_hooks_link) else: self._add_result(t, n, s) click-reviewers-tools-0.44~16.04.1/clickreviews/data/0000775000000000000000000000000012713206312017113 5ustar click-reviewers-tools-0.44~16.04.1/clickreviews/data/icon.png0000664000000000000000000000546612660650204020570 0ustar ‰PNG  IHDRk¬XT„PLTEêêêëëëìììîîîïïïóóóôôôööö÷÷÷ÕÕÕÖÖÖ×××ØØØÙÙÙÚÚÚÛÛÛÜÜÜÝÝÝÞÞÞßßßàààáááâââãããäääåååæææçççèèèéééêêêëëëìììíííîîîïïïðððñññòòòóóóôôôõõõööö÷÷÷Ë è tRNSÍÍÍÍÍÙÙÙÙ¼‡™ XIDATxäÐQAÄP°!¼YÿñpíO›HÈ'_9/ñù2 |ÀèáFÈÑÂ= |ÀèáFÈyA@/9O÷s1`ô€òÑ:z@ô€ŽP>`ô€òÑ:z@ô€ŽP>`ô€òÑ:z@ô€ŽP> z@ù€<2`o”yœß[ªlô€åªlô€ê= zÀFX> zÀF¨°Ñ–ˆ°|À?5u·Ó0 ƒaø€#;?±—Žfé’´”û¿F„@*C*c›#ÍÏ%¼úì¿ÿ`Éiààœ1€Æ8O‡cÊU€ëGPNÑØaëþ»¨‰-\åbnªìL ¼zø/¤ÔÔ¸øƒmôp¤¬=À6óáv¨ŠlÈî…|V`ûƒÙÁChêཟ·eòø0.KG/=ÌŒ"bS`=±C6) Ð"¡”P”X} (ÅŒªÌ‘¾XCUO€‰é[@96+ °Ž´q(è¨"À2ÐOñüüZ¤ %ùúì*Ó/%}0g'{rúFÇ/I.%@ ‹ H4¸ßÿ ÓŒ þU-<ÔßsÌÏ¥b™¤ûì=÷…ïA k?9@ÇO°ðA¯ÚÑö¦`áâèø™ìòín,NŸ‰Úxæÿò­0ì·èˆ€¸þ°=˜ vËœÎH÷‰Á}×n…õåt¥Ï`%÷\nwÆÓM˜Ÿ`ʹïâ4vOþN~Z€Š#n…£Ý3ç?¬>+@Ã#؃ž^‘Ð 0üV˜Úƒ¾]D0)~@íAHUYi³Ò…LÁ @NÀJ|NÍ}Ô­0Q¦³;–k‹ïþ”÷#–­=—žžìâ°>Bâ{PxWx¼TewFd{ˆŸ0ÿ5#Þ“ˆÁ!:xé¯DãþÖ°R¼‡$Öa8dþOzˆÄ~@€J¼+¨9Ân¯?T?À Þ"‚¦€—ìû˜plŒ ïÈÇuUd¹ÃX+Õ®S0¤"b€€¨óüP_{°Ç ø-Ix¢€ú­8€p¸Âä± @;ÿV ‘з®N@6F P Z·¹ar€ÔQú’³õˆ:Z3À(h­÷È”ÔncÝ);Z÷"”؈jA2ó·u²?ÿâ±)eÂà…¥²l,² u¼IïÿýÊÈÖ¡5‡aìi(Y¼ äÃÎH€)'X1¸£ž¥‹ i/Ùè¥u(E¬NPÚ³­ uÒ¸„$V€VŠyo*„à€ÉC0&@h#ÈCÀ<ª?o…ˆl>Eœý¼3Hñ…&éÝN¸$N€^àätvý«  ´@è£Ðgί•Fç€ÓQ¨°˜6¿ç€ËlØÈ¹‡†4ð%—:41ô7ΞFlq 4ä·aO!@ô à¤ð¤aïx–ÊDPTK|;f€«‚‹”Dù+ÐÊ=Îp‰¶Z†’ÓEÿº`’¨böÔò e8=m¹Ïu€A¢4YK0\:y$CÙÛôò¨lº®)å—fÞh剌áÚi«b¨þöÜQÍcþò0J¾tÛ…)Ï$ U„,ööµôån³òò5ˆ¥ögçÚ2T}{€Rzüo_ÓZ ôKø ÓþãêöþT+7o{Ãü‡‘—FÀL[ø‹Ûär«9;!ú8-A·ÂrÚR £n äÖãì1A‘ÏL"àé¦dq{¹Užëÿ6ôV˜V«â%WJeŸ ™Ì÷xH½Ãñ¸úù •RvôS3g?#‹ » ×?Ã<¯ùwì#PÅo ÒPÃKÿÒ½i®ˆyTè´Ï­áö7<3<·:鮈ú(¬4Ê>·ŒÆôWD}"6àüÜÒ¨áj€˜¯Ã¹Æ=·bZî@|1ÿÿ "ßû R逩ž4jº)ñIÌtyû“X¡C®iШÇí‰ªÉ¢Š€úéi5j¹ ÞgñB‡œê_ô¾¸ ÊFü HþŸ:j¯X®ª$ª_<5µ‰µÞ_½ þs=€‘¨jñ<qˆ—jaF0Hœ[<1øVwÔy‰à!q;2…醺cD°ä¥æÅã½>Ua;í¡q]”µÄµ‹Ï}¯R£Ì¯§¯Ó8%À ÉðYõÞ[`½Ðà[¢˜%¡YÎ À.§!oQ,¥Ä©iÙq9ý Ô®Òäʼè¿ÔØÝvƒ(Àñ˽G‡aÆ÷ÀÝæ´§]ò‘ù=Aç_ŒO㜶–awØa²F¬ Θ¶ôí (îŒÀPX)@ĽAènn@{8®Z'8§ÍM—0|ϸ—€•ªX:Á‘Ûb=€Ç<ذ¯RÀÑX-@R‚e6/ƒH¼Þ* _´õX`ÍÌBܳ}áœséü+H xîï vvòú2™ð|Õ^køK{1p—\5@:‹úgvü7÷qX7€%äò³¹;ëç߯´‚"©b€ò# Nó÷t%ó»ˆ_‚)?õL¢Hg:!î‰u›_CA#W¤(uâ¾\ÄÝפø/!µÄN”á¯@³¯KÜÏ ú’)?6á Ë~©1߃à ä§¥sW¢‡/ãB‡<8Ú@‡FzÚ?|ÞÜ•€ozJt€}ŸéÆÐH€¬;Þ ~)wRp i'‘@õ”?°!›æ¿„É †v2pÔL€ûŽqÆaä€ú²ëPFŸ¹PK²ên –î€@mHsŠ´å˜ÐH€¼œÊ†-{°Ø\€zþ |àx¸B#µ O=x ù  2µ {æ ,£óßù%ì©DmÈŽÙ€eT o8Á2Qc˜ÜKO £WÀüÿ› }Ï€rüüõð›p¨8…9žØ+ð ©õy˜+ð #RûrÒ_Œ‰¨‘ ׿cª…>%@ŽÃë7 Eúœ9™o@ˆ À˜Ï¯<6ѧÈÉöü,9n2cqðé3 -žæÑ;àû¬ÿY¬|ŠËë[½3­i”RÌøm õGöJ>@ϸ2š°þAÑÞýÏ_VFãhý?¼£r‘VFóÖ=Š^K–~úöÍ€q•< 3üXoÁgï¬QJI©”2Öù9&fö Pa–¦üÛ.WEQmceƒñ_s‡ƒ„5Œ8qÚ°8ÀˆX`´€8Àâ#@`q€ñÀìÃ|¯Oˆ`à0`€:€qâ˜8Œ `€8@À8€qâxiŒø>?åëýý`À8€ÐðÚ@À8€q€"ü3c"¾¦P]kIEND®B`‚click-reviewers-tools-0.44~16.04.1/clickreviews/frameworks.py0000664000000000000000000000345712666350672020765 0ustar # # Copyright (C) 2014 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import os import clickreviews.remote USER_DATA_FILE = os.path.join(clickreviews.remote.DATA_DIR, 'frameworks.json') FRAMEWORKS_DATA_URL = \ "https://myapps.developer.ubuntu.com/dev/api/click-framework/" def get_frameworks_file(fn): if fn is None: fn = USER_DATA_FILE clickreviews.remote.get_remote_file(fn, FRAMEWORKS_DATA_URL) class Frameworks(object): DEPRECATED_FRAMEWORKS = [] OBSOLETE_FRAMEWORKS = [] AVAILABLE_FRAMEWORKS = [] def __init__(self, overrides=None): self.FRAMEWORKS = clickreviews.remote.read_cr_file(USER_DATA_FILE, FRAMEWORKS_DATA_URL) if overrides is not None: self.FRAMEWORKS.update(overrides) for name, data in self.FRAMEWORKS.items(): if type(data) is dict: state = data.get('state') else: state = data if state == 'deprecated': self.DEPRECATED_FRAMEWORKS.append(name) elif state == 'obsolete': self.OBSOLETE_FRAMEWORKS.append(name) elif state == 'available': self.AVAILABLE_FRAMEWORKS.append(name) click-reviewers-tools-0.44~16.04.1/clickreviews/remote.py0000664000000000000000000000614012666350672020070 0ustar # # Copyright (C) 2014 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import json import os import re from socket import timeout import sys import time from urllib import request, parse from urllib.error import HTTPError, URLError DATA_DIR = os.path.join(os.path.expanduser('~/.cache/click-reviewers-tools/')) UPDATE_INTERVAL = 60 * 60 * 24 * 7 def _update_is_necessary(fn): return (not os.path.exists(fn)) or \ (time.time() - os.path.getmtime(fn) >= UPDATE_INTERVAL) def _update_is_possible(url): update = True try: request.urlopen(url) except (HTTPError, URLError): update = False except timeout: update = False return update def abort(msg=None): if msg: print(msg, file=sys.stderr) print('Aborted.', file=sys.stderr) sys.exit(1) # # Public # def get_remote_data(url): try: f = request.urlopen(url) except (HTTPError, URLError) as error: abort('Data not retrieved because %s.' % error) except timeout: abort('Socket timed out.') if not f: abort() return f.read() def get_remote_file_url(url): html = get_remote_data(url) # XXX: This is a hack and will be gone, as soon as myapps has an API for this. link = re.findall(b'download file', html) if not link: abort() download_link = '{}://{}/{}'.format( parse.urlparse(url).scheme, parse.urlparse(url).netloc, link[0].decode("utf-8")) return download_link def get_remote_file(fn, url, data_dir=DATA_DIR): data = get_remote_data(url) if os.path.exists(fn): os.remove(fn) if not os.path.exists(os.path.dirname(fn)): os.makedirs(os.path.dirname(fn)) with open(fn, 'bw') as local_file: local_file.write(data) def read_cr_file(fn, url, local_copy_fn=None): '''read click reviews file from remote or local copy: - fn: where to store the cached file - url: url to fetch - local_copy_fn: force use of local copy ''' j = {} if local_copy_fn and os.path.exists(local_copy_fn): try: j = json.loads(open(local_copy_fn, 'r').read()) except ValueError: raise ValueError("Could not parse '%s'" % local_copy_fn) else: if _update_is_necessary(fn) and _update_is_possible(url): get_remote_file(fn, url) if os.path.exists(fn): try: j = json.loads(open(fn, 'r').read()) except ValueError: raise ValueError("Could not parse '%s'" % fn) return j click-reviewers-tools-0.44~16.04.1/clickreviews/modules.py0000664000000000000000000000461212725613310020232 0ustar import clickreviews import imp import inspect import os import pkgutil IRRELEVANT_MODULES = ['cr_common', 'cr_tests', 'cr_skeleton', 'sr_common', 'sr_tests', 'sr_skeleton', 'common'] def narrow_down_modules(modules): ''' Get a list of file names or module names and filter out the ones we know are irrelevant. ''' relevant_modules = [] for module in modules: module_name = os.path.basename(module).replace('.py', '') if module_name not in IRRELEVANT_MODULES and \ (module_name.startswith('cr_') or module_name.startswith('sr_')): relevant_modules += [module] return relevant_modules def get_modules(): ''' Here we have a look at all the modules in the clickreviews package and filter out a few which are not relevant. Basically we look at all the ones which are derived from [cs]r_common, where we can later on instantiate a *Review* object and run the necessary checks. ''' all_modules = [name for _, name, _ in pkgutil.iter_modules(clickreviews.__path__)] return narrow_down_modules(all_modules) def find_main_class(module_name): ''' This function will find the Click*Review class in the specified module. ''' module = imp.load_source(module_name, '%s/%s.py' % (clickreviews.__path__[0], module_name)) classes = inspect.getmembers(module, inspect.isclass) def find_test_class(a): return (a[0].startswith('Click') or a[0].startswith('Snap')) and \ not a[0].endswith('Exception') and \ a[1].__module__ == module_name test_class = list(filter(find_test_class, classes)) if not test_class: return None init_object = getattr(module, test_class[0][0]) return init_object def init_main_class(module_name, click_file, overrides=None): ''' This function will instantiate the main Click*Review class of a given module and instantiate it with the location of the .click file we want to inspect. ''' init_object = find_main_class(module_name) if not init_object: return None try: ob = init_object(click_file, overrides) except TypeError as e: print('Could not init %s: %s' % (init_object, str(e))) raise return ob click-reviewers-tools-0.44~16.04.1/clickreviews/sr_common.py0000664000000000000000000001623412733311361020561 0ustar '''sr_common.py: common classes and functions''' # # Copyright (C) 2013-2016 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from __future__ import print_function import os import re import yaml from clickreviews.common import( Review, ReviewException, error, open_file_read, ) import clickreviews.apparmor_policy as apparmor_policy # # Utility classes # class SnapReviewException(ReviewException): '''This class represents SnapReview exceptions''' class SnapReview(Review): '''This class represents snap reviews''' snappy_required = ["name", "version", ] # optional snappy fields here (may be required by appstore) snappy_optional = ['apps', 'architectures', 'confinement', 'description', 'environment', 'epoch', 'license-agreement', 'license-version', 'summary', 'type', 'plugs', 'slots', ] apps_required = ['command'] apps_optional = ['daemon', 'environment', 'stop-command', 'stop-timeout', 'restart-condition', 'post-stop-command', 'plugs', 'slots', 'ports', 'socket', 'listen-stream', 'socket-user', 'socket-group', ] # https://docs.google.com/document/d/1Q5_T00yTq0wobm_nHzCV-KV8R4jdk-PXcrtm80ETTLU/edit# # 'plugs': # 'interface': name # 'attrib-name': # 'slots': # 'interface': name # 'attrib-name': # self.interfaces lists interfaces and the valid attribute names for the # interface with the valid python type for the attribute (eg, [], '', {}, # etc). # Interfaces with no attributes should specify an empty # dictionary. # # Interfaces from apparmor-easyprof-ubuntu.json are read in __init__() so # they don't have to be added to self.interfaces. interfaces = dict() # Since apparmor-easyprof-ubuntu.json doesn't allow specifying attributes, # merge this into self.interfaces after reading # apparmor-easyprof-ubuntu.json interfaces_attribs = {'bool-file': {'path/slots': ""}, 'content': {'read/slots': "", 'write/slots': "", 'target/plugs': "", }, 'serial-port': {'path/slots': ""}, } def __init__(self, fn, review_type, overrides=None): Review.__init__(self, fn, review_type, overrides=overrides) if not self.is_snap2: return snap_yaml = self._extract_snap_yaml() try: self.snap_yaml = yaml.safe_load(snap_yaml) except Exception: # pragma: nocover error("Could not load snap.yaml. Is it properly formatted?") # If local_copy is None, then this will check the server to see if # we are up to date. However, if we are working within the development # tree, use it unconditionally. local_copy = None branch_fn = os.path.join(os.path.dirname(__file__), '../data/apparmor-easyprof-ubuntu.json') if os.path.exists(branch_fn): local_copy = branch_fn p = apparmor_policy.ApparmorPolicy(local_copy) self.aa_policy = p.policy # TODO: may need updating for ubuntu-personal, etc self.policy_vendor = "ubuntu-core" self.policy_version = str(self._pkgfmt_version()) if self.policy_vendor in self.aa_policy and \ self.policy_version in self.aa_policy[self.policy_vendor] and \ 'policy_groups' in self.aa_policy[self.policy_vendor][self.policy_version]: for t in ['common', 'reserved']: if t not in self.aa_policy[self.policy_vendor][self.policy_version]['policy_groups']: continue for p in self.aa_policy[self.policy_vendor][self.policy_version]['policy_groups'][t]: if p in self.interfaces_attribs: self.interfaces[p] = self.interfaces_attribs[p] else: self.interfaces[p] = {} # default to 'app' if 'type' not in self.snap_yaml: self.snap_yaml['type'] = 'app' if 'architectures' in self.snap_yaml: self.pkg_arch = self.snap_yaml['architectures'] else: self.pkg_arch = ['all'] self.is_snap_gadget = False if 'type' in self.snap_yaml and self.snap_yaml['type'] == 'gadget': self.is_snap_gadget = True # snapd understands: # plugs: # foo: null # but yaml.safe_load() treats 'null' as 'None', but we need a {}, so # we need to account for that. for k in ['plugs', 'slots']: if k not in self.snap_yaml: continue for iface in self.snap_yaml[k]: if self.snap_yaml[k][iface] is None: self.snap_yaml[k][iface] = {} # Since coverage is looked at via the testsuite and the testsuite mocks # this out, don't cover this def _extract_snap_yaml(self): # pragma: nocover '''Extract and read the snappy 16.04 snap.yaml''' y = os.path.join(self.unpack_dir, "meta/snap.yaml") if not os.path.isfile(y): error("Could not find snap.yaml.") return open_file_read(y) # Since coverage is looked at via the testsuite and the testsuite mocks # this out, don't cover this def _get_unpack_dir(self): # pragma: nocover '''Get unpack directory''' return self.unpack_dir def _verify_pkgname(self, n): '''Verify package name''' pat = re.compile(r'^[a-z](?:-?[a-z0-9])*$') if pat.search(n): return True return False def _verify_appname(self, n): '''Verify app name''' pat = re.compile(r'^[a-zA-Z0-9](?:-?[a-zA-Z0-9])*$') if pat.search(n): return True return False def _devmode_override(self): '''Determine override result type based on confinement property''' if 'confinement' in self.snap_yaml and \ self.snap_yaml['confinement'] == "devmode": return 'info' return None click-reviewers-tools-0.44~16.04.1/clickreviews/sr_security.py0000664000000000000000000002732412735265152021152 0ustar '''sr_security.py: snap security checks''' # # Copyright (C) 2013-2016 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from __future__ import print_function from clickreviews.sr_common import ( SnapReview, ) from clickreviews.common import ( cmd, create_tempdir, ReviewException, AA_PROFILE_NAME_MAXLEN, AA_PROFILE_NAME_ADVLEN, MKSQUASHFS_OPTS, ) import os class SnapReviewSecurity(SnapReview): '''This class represents snap security reviews''' def __init__(self, fn, overrides=None): SnapReview.__init__(self, fn, "security-snap-v2", overrides=overrides) if not self.is_snap2: return self.sec_skipped_types = ['oem', 'os', 'kernel'] # these don't need security items self.sec_safe_slots = ['content', 'mpris'] def check_security_policy_vendor(self): '''Check policy-vendor''' if not self.is_snap2: return t = 'info' n = self._get_check_name('policy-vendor') s = 'OK' if self.policy_vendor not in self.aa_policy: t = 'error' s = "unknown policy-vendor '%s'" % self.policy_vendor self._add_result(t, n, s) def check_security_policy_version(self): '''Check policy-version''' if not self.is_snap2 or self.policy_vendor not in self.aa_policy: return t = 'info' n = self._get_check_name('policy-version') s = 'OK' if self.policy_version not in self.aa_policy[self.policy_vendor]: t = 'error' s = "unknown policy-version '%s'" % self.policy_version self._add_result(t, n, s) def _verify_iface(self, name, iface, interface): sec_type = self._get_policy_group_type(self.policy_vendor, self.policy_version, interface) if sec_type is None: return # not in aa_policy o = self._devmode_override() if name.endswith('slot') and interface not in self.sec_safe_slots: t = 'warn' n = self._get_check_name('is_slot', app=iface, extra=interface) s = "(NEEDS REVIEW) slots requires approval" m = False if o is None: m = True self._add_result(t, n, s, manual_review=m, override_result_type=o) t = 'info' n = self._get_check_name('%s_safe' % name, app=iface, extra=interface) s = "OK" m = False l = None if interface == "debug": t = 'error' s = "'%s' not for production use" % interface l = 'http://askubuntu.com/a/562123/94326' if o is None: m = True elif sec_type == "reserved": t = 'error' s = "%s interface '%s' for vetted applications only" % (sec_type, interface) if o is None: m = True elif sec_type != "common": t = 'error' s = "unknown type '%s' for interface '%s'" % (sec_type, interface) o = None self._add_result(t, n, s, l, manual_review=m, override_result_type=o) def check_security_plugs(self): '''Check security plugs''' if not self.is_snap2 or 'plugs' not in self.snap_yaml: return for plug in self.snap_yaml['plugs']: # If the 'interface' name is the same as the 'plug' name, then # 'interface' is optional since the interface name and the plug # name are the same interface = plug if 'interface' in self.snap_yaml['plugs'][plug]: interface = self.snap_yaml['plugs'][plug]['interface'] self._verify_iface('plug', plug, interface) def check_security_apps_plugs(self): '''Check security app plugs''' if not self.is_snap2 or 'apps' not in self.snap_yaml: return for app in self.snap_yaml['apps']: if 'plugs' not in self.snap_yaml['apps'][app]: continue # The interface referenced in the app's 'plugs' field can either be # a known interface (when the interface name reference and the # interface is the same) or can reference a name in the snap's # toplevel 'plugs' mapping for plug_ref in self.snap_yaml['apps'][app]['plugs']: if not isinstance(plug_ref, str): continue # checked elsewhere elif plug_ref not in self.interfaces: continue # check_security_plugs() verifies these self._verify_iface('app_plug', app, plug_ref) def check_security_slots(self): '''Check security slots''' if not self.is_snap2 or 'slots' not in self.snap_yaml: return for slot in self.snap_yaml['slots']: # If the 'interface' name is the same as the 'slot' name, then # 'interface' is optional since the interface name and the slot # name are the same interface = slot if 'interface' in self.snap_yaml['slots'][slot]: interface = self.snap_yaml['slots'][slot]['interface'] self._verify_iface('slot', slot, interface) def check_security_apps_slots(self): '''Check security app slots''' if not self.is_snap2 or 'apps' not in self.snap_yaml: return for app in self.snap_yaml['apps']: if 'slots' not in self.snap_yaml['apps'][app]: continue # The interface referenced in the app's 'slots' field can either be # a known interface (when the interface name reference and the # interface is the same) or can reference a name in the snap's # toplevel 'slots' mapping for slot_ref in self.snap_yaml['apps'][app]['slots']: if not isinstance(slot_ref, str): continue # checked elsewhere elif slot_ref not in self.interfaces: continue # check_security_slots() verifies these self._verify_iface('app_slot', app, slot_ref) def check_apparmor_profile_name_length(self): '''Check AppArmor profile name length''' if not self.is_snap2 or 'apps' not in self.snap_yaml: return maxlen = AA_PROFILE_NAME_MAXLEN advlen = AA_PROFILE_NAME_ADVLEN for app in self.snap_yaml['apps']: t = 'info' n = self._get_check_name('profile_name_length', app=app) s = "OK" profile = "snap.%s.%s" % (self.snap_yaml['name'], app) if len(profile) > maxlen: t = 'error' s = ("'%s' too long (exceeds %d characters). Please shorten " "'%s' and/or '%s'" % (profile, maxlen, self.snap_yaml['name'], app)) elif len(profile) > advlen: t = 'warn' s = ("'%s' is long (exceeds %d characters) and thus could be " "problematic in certain environments. Please consider " "shortening '%s' and/or '%s'" % (profile, advlen, self.snap_yaml['name'], app)) self._add_result(t, n, s) def check_squashfs_resquash(self): '''Check resquash of squashfs''' if not self.is_snap2: return fn = os.path.abspath(self.pkg_filename) # Verify squashfs supports the -fstime option, if not, warn (which # blocks in store) (rc, out) = cmd(['unsquashfs', '-fstime', fn]) if rc != 0: t = 'warn' n = self._get_check_name('squashfs_supports_fstime') s = 'could not determine fstime of squashfs' self._add_result(t, n, s) return fstime = out.strip() # For now, skip the checks on if have symlinks due to LP: #1555305 (rc, out) = cmd(['unsquashfs', '-lls', fn]) if rc != 0: t = 'error' n = self._get_check_name('squashfs_lls') s = 'could not list contents of squashfs' self._add_result(t, n, s) return elif 'lrwxrwxrwx' in out: t = 'info' n = self._get_check_name('squashfs_resquash_1555305') s = 'cannot reproduce squashfs' l = 'https://launchpad.net/bugs/1555305' self._add_result(t, n, s, link=l) return # end LP: #1555305 workaround tmpdir = create_tempdir() # this is autocleaned tmp_unpack = os.path.join(tmpdir, 'squashfs-root') tmp_repack = os.path.join(tmpdir, 'repack.snap') curdir = os.getcwd() os.chdir(tmpdir) # ensure we don't alter the permissions from the unsquashfs old_umask = os.umask(000) try: (rc, out) = cmd(['unsquashfs', '-d', tmp_unpack, fn]) if rc != 0: raise ReviewException("could not unsquash '%s': %s" % (os.path.basename(fn), out)) (rc, out) = cmd(['mksquashfs', tmp_unpack, tmp_repack, '-fstime', fstime] + MKSQUASHFS_OPTS) if rc != 0: raise ReviewException("could not mksquashfs '%s': %s" % (os.path.relpath(tmp_unpack, tmpdir), out)) except ReviewException as e: t = 'error' n = self._get_check_name('squashfs_resquash') self._add_result(t, n, str(e)) return finally: os.umask(old_umask) os.chdir(curdir) # Now calculate the hashes t = 'info' n = self._get_check_name('squashfs_repack_checksum') s = "OK" (rc, out) = cmd(['sha512sum', fn]) if rc != 0: t = 'error' s = "could not determine checksum of '%s'" % os.path.basename(fn) self._add_result(t, n, s) return orig_sum = out.split()[0] (rc, out) = cmd(['sha512sum', tmp_repack]) if rc != 0: t = 'error' s = "could not determine checksum of '%s'" % \ os.path.relpath(tmp_repack, tmpdir) self._add_result(t, n, s) return repack_sum = out.split()[0] if orig_sum != repack_sum: if 'type' in self.snap_yaml and self.snap_yaml['type'] == 'os': t = 'info' s = 'checksums do not match (expected for os snap)' else: # FIXME: turn this into an error once the squashfs-tools bugs # are fixed # t = 'error' t = 'info' s = "checksums do not match. Please ensure the snap is " + \ "created with either 'snapcraft snap ' or " + \ "'mksquashfs %s'" % " ".join(MKSQUASHFS_OPTS) self._add_result(t, n, s) click-reviewers-tools-0.44~16.04.1/clickreviews/cr_tests.py0000664000000000000000000012330012666351223020412 0ustar '''cr_tests.py: common setup and tests for test modules''' # # Copyright (C) 2013-2015 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import io import json import os import tempfile from xdg.DesktopEntry import DesktopEntry import yaml from unittest.mock import patch from unittest import TestCase from clickreviews.cr_lint import MINIMUM_CLICK_FRAMEWORK_VERSION import clickreviews.common as common # These should be set in the test cases TEST_CONTROL = "" TEST_MANIFEST = "" TEST_PKG_YAML = "" TEST_HASHES_YAML = "" TEST_README_MD = "" TEST_SECURITY = dict() TEST_SECURITY_PROFILES = dict() TEST_DESKTOP = dict() TEST_WEBAPP_MANIFESTS = dict() TEST_URLS = dict() TEST_SCOPES = dict() TEST_CONTENT_HUB = dict() TEST_ACCOUNTS_MANIFEST = dict() TEST_ACCOUNTS_APPLICATION = dict() TEST_ACCOUNTS_PROVIDER = dict() TEST_ACCOUNTS_QML_PLUGIN = dict() TEST_ACCOUNTS_SERVICE = dict() TEST_PUSH_HELPER = dict() TEST_BIN_PATH = dict() TEST_FRAMEWORK = dict() TEST_FRAMEWORK_POLICY = dict() TEST_FRAMEWORK_POLICY_UNKNOWN = [] TEST_PKGFMT_TYPE = "click" TEST_PKGFMT_VERSION = "0.4" # # Mock override functions # def _mock_func(self): '''Fake test function''' return def _extract_control_file(self): '''Pretend we read the control file''' return io.StringIO(TEST_CONTROL) def _extract_manifest_file(self): '''Pretend we read the manifest file''' return io.StringIO(TEST_MANIFEST) def _extract_package_yaml(self): '''Pretend we read the package.yaml file''' return io.StringIO(TEST_PKG_YAML) def _extract_hashes_yaml(self): '''Pretend we read the hashes.yaml file''' return io.StringIO(TEST_HASHES_YAML) def _path_join(self, d, fn): '''Pretend we have a tempdir''' return os.path.join("/fake", fn) def _get_sha512sum(self, fn): '''Pretend we found performed a sha512''' (rc, out) = common.cmd(['sha512sum', os.path.realpath(__file__)]) if rc != 0: return None return out.split()[0] def _extract_statinfo(self, fn): '''Pretend we found performed an os.stat()''' return os.stat(os.path.realpath(__file__)) def _extract_readme_md(self): '''Pretend we read the meta/readme.md file''' return TEST_README_MD def _check_innerpath_executable(self, fn): '''Pretend we a file''' if '.nonexec' in fn: return False return True def _extract_click_frameworks(self): '''Pretend we enumerated the click frameworks''' return ["ubuntu-sdk-13.10", "ubuntu-sdk-14.04-dev1", "ubuntu-sdk-14.04-html-dev1", "ubuntu-sdk-14.04-papi-dev1", "ubuntu-sdk-14.04-qml-dev1", "ubuntu-sdk-14.04", "ubuntu-sdk-14.04-html", "ubuntu-sdk-14.04-papi", "ubuntu-sdk-14.04-qml", "ubuntu-sdk-14.10-dev1", "ubuntu-sdk-14.10-html-dev1", "ubuntu-sdk-14.10-papi-dev1", "ubuntu-sdk-14.10-qml-dev1", "ubuntu-sdk-14.10-dev2", "ubuntu-sdk-14.10-html-dev2", "ubuntu-sdk-14.10-papi-dev2", "ubuntu-sdk-14.10-qml-dev2", ] def _extract_security_manifest(self, app): '''Pretend we read the security manifest file''' return io.StringIO(TEST_SECURITY[app]) def _get_security_manifest(self, app): '''Pretend we read the security manifest file''' return ("%s.apparmor" % app, json.loads(TEST_SECURITY[app])) def _extract_security_profile(self, app): '''Pretend we read the security profile''' return io.StringIO(TEST_SECURITY_PROFILES[app]) def _get_security_profile(self, app): '''Pretend we read the security profile''' return ("%s.profile" % app, TEST_SECURITY_PROFILES[app]) def _get_security_supported_policy_versions(self): '''Pretend we read the contens of /usr/share/apparmor/easyprof''' return [1.0, 1.1, 1.2, 1.3] def _extract_desktop_entry(self, app): '''Pretend we read the desktop file''' return ("%s.desktop" % app, TEST_DESKTOP[app]) def _get_desktop_entry(self, app): '''Pretend we read the desktop file''' return TEST_DESKTOP[app] def _extract_webapp_manifests(self): '''Pretend we read the webapp manifest files''' return TEST_WEBAPP_MANIFESTS def _extract_url_dispatcher(self, app): '''Pretend we read the url dispatcher file''' return ("%s.url-dispatcher" % app, TEST_URLS[app]) def _extract_scopes(self, app): '''Pretend we found and read the files in the scope directories''' return TEST_SCOPES[app] def _extract_content_hub(self, app): '''Pretend we read the content-hub file''' return ("%s.content.json" % app, TEST_CONTENT_HUB[app]) def _extract_account(self, app, account_type): '''Pretend we read the accounts file''' f = app val = None if account_type == "accounts": f += ".accounts" val = TEST_ACCOUNTS_MANIFEST[app] elif account_type == "account-application": f += ".application" val = TEST_ACCOUNTS_APPLICATION[app] elif account_type == "account-provider": f += ".provider" val = TEST_ACCOUNTS_PROVIDER[app] elif account_type == "account-qml-plugin": f += ".qml-plugin" val = TEST_ACCOUNTS_QML_PLUGIN[app] elif account_type == "account-service": f += ".service" val = TEST_ACCOUNTS_SERVICE[app] else: # should never get here raise ValueError("Unknown account_type '%s'" % account_type) return (f, val) def _extract_push_helper(self, app): '''Pretend we read the push-helper file''' return ("%s.push-helper.json" % app, TEST_PUSH_HELPER[app]) def _extract_bin_path(self, app): '''Pretend we found the bin-path file''' return ("%s" % TEST_BIN_PATH[app]) def _check_bin_path_executable(self, app): '''Pretend we found the bin-path file''' if TEST_BIN_PATH[app].endswith('.nonexec'): return False return True def _extract_framework(self, app): '''Pretend we found the framework file''' return ("%s.framework" % app, TEST_FRAMEWORK[app]) def _extract_framework_policy(self): '''Pretend we found the framework policy files''' return (TEST_FRAMEWORK_POLICY, TEST_FRAMEWORK_POLICY_UNKNOWN) def _has_framework_in_metadir(self): '''Pretend we found the framework file''' return True def _pkgfmt_type(self): '''Pretend we found the pkgfmt type''' return TEST_PKGFMT_TYPE def _pkgfmt_version(self): '''Pretend we found the pkgfmt version''' return TEST_PKGFMT_VERSION def _detect_package(self, fn): '''Pretend we detected the package''' ver = 1 if TEST_PKGFMT_TYPE == "snap" and TEST_PKGFMT_VERSION != "15.04": ver = 2 return (TEST_PKGFMT_TYPE, ver) def create_patches(): # http://docs.python.org/3.4/library/unittest.mock-examples.html # Mock patching. Don't use decorators but instead patch in setUp() of the # child. patches = [] patches.append(patch('clickreviews.common.Review._check_package_exists', _mock_func)) patches.append(patch( 'clickreviews.cr_common.ClickReview._extract_control_file', _extract_control_file)) patches.append(patch( 'clickreviews.cr_common.ClickReview._extract_manifest_file', _extract_manifest_file)) patches.append(patch( 'clickreviews.cr_common.ClickReview._extract_package_yaml', _extract_package_yaml)) patches.append(patch( 'clickreviews.cr_common.ClickReview._extract_hashes_yaml', _extract_hashes_yaml)) patches.append(patch('clickreviews.common.Review._path_join', _path_join)) patches.append(patch( 'clickreviews.common.Review._get_sha512sum', _get_sha512sum)) patches.append(patch( 'clickreviews.common.Review._extract_statinfo', _extract_statinfo)) patches.append(patch( 'clickreviews.cr_common.ClickReview._extract_click_frameworks', _extract_click_frameworks)) patches.append(patch('clickreviews.common.unpack_pkg', _mock_func)) patches.append(patch('clickreviews.common.raw_unpack_pkg', _mock_func)) patches.append(patch('clickreviews.common.detect_package', _detect_package)) patches.append(patch('clickreviews.common.Review._list_all_files', _mock_func)) patches.append(patch( 'clickreviews.common.Review._list_all_compiled_binaries', _mock_func)) # lint overrides patches.append(patch( 'clickreviews.cr_lint.ClickReviewLint._list_control_files', _mock_func)) patches.append(patch( 'clickreviews.cr_lint.ClickReviewLint._extract_readme_md', _extract_readme_md)) patches.append(patch( 'clickreviews.common.Review._check_innerpath_executable', _check_innerpath_executable)) # security overrides patches.append(patch( 'clickreviews.cr_security.ClickReviewSecurity._extract_security_manifest', _extract_security_manifest)) patches.append(patch( 'clickreviews.cr_security.ClickReviewSecurity._get_security_manifest', _get_security_manifest)) patches.append(patch( 'clickreviews.cr_security.ClickReviewSecurity._extract_security_profile', _extract_security_profile)) patches.append(patch( 'clickreviews.cr_security.ClickReviewSecurity._get_security_profile', _get_security_profile)) # desktop overrides patches.append(patch( 'clickreviews.cr_desktop.ClickReviewDesktop._extract_desktop_entry', _extract_desktop_entry)) patches.append(patch( 'clickreviews.cr_desktop.ClickReviewDesktop._get_desktop_entry', _get_desktop_entry)) patches.append(patch( 'clickreviews.cr_desktop.ClickReviewDesktop._extract_webapp_manifests', _extract_webapp_manifests)) # url-dispatcher overrides patches.append(patch( 'clickreviews.cr_url_dispatcher.ClickReviewUrlDispatcher._extract_url_dispatcher', _extract_url_dispatcher)) # scope overrides patches.append(patch( 'clickreviews.cr_scope.ClickReviewScope._extract_scopes', _extract_scopes)) # content-hub overrides patches.append(patch( 'clickreviews.cr_content_hub.ClickReviewContentHub._extract_content_hub', _extract_content_hub)) # online accounts overrides patches.append(patch( 'clickreviews.cr_online_accounts.ClickReviewAccounts._extract_account', _extract_account)) # push-helper overrides patches.append(patch( 'clickreviews.cr_push_helper.ClickReviewPushHelper._extract_push_helper', _extract_push_helper)) # bin-path overrides patches.append(patch( 'clickreviews.cr_bin_path.ClickReviewBinPath._extract_bin_path', _extract_bin_path)) patches.append(patch( 'clickreviews.cr_bin_path.ClickReviewBinPath._check_bin_path_executable', _check_bin_path_executable)) # framework overrides patches.append(patch( 'clickreviews.cr_framework.ClickReviewFramework._extract_framework', _extract_framework)) patches.append(patch( 'clickreviews.cr_framework.ClickReviewFramework._extract_framework_policy', _extract_framework_policy)) patches.append(patch( 'clickreviews.cr_framework.ClickReviewFramework._has_framework_in_metadir', _has_framework_in_metadir)) # pkgfmt patches.append(patch("clickreviews.common.Review._pkgfmt_type", _pkgfmt_type)) patches.append(patch("clickreviews.common.Review._pkgfmt_version", _pkgfmt_version)) return patches class TestClickReview(TestCase): """Tests for the lint review tool.""" def __init__(self, *args): if not hasattr(self, 'desktop_tmpdir'): self.desktop_tmpdir = \ tempfile.mkdtemp(prefix="clickreview-test-desktop-") TestCase.__init__(self, *args) self._reset_test_data() def _reset_test_data(self): # dictionary representing DEBIAN/control self.test_control = dict() self.set_test_control('Package', "com.ubuntu.developer.someuser.testapp") self.set_test_control('Version', "1.0") self.set_test_control('Click-Version', MINIMUM_CLICK_FRAMEWORK_VERSION) self.set_test_control('Architecture', "all") self.set_test_control('Maintainer', "Some User ") self.set_test_control('Installed-Size', "111") self.set_test_control('Description', "My Test App") # dictionary representing DEBIAN/manifest self.test_manifest = dict() self.set_test_manifest("description", "Some longish description of My Test App") self.set_test_manifest("framework", "ubuntu-sdk-13.10") self.set_test_manifest("maintainer", self.test_control['Maintainer']) self.set_test_manifest("name", self.test_control['Package']) self.set_test_manifest("title", self.test_control['Description']) self.set_test_manifest("version", self.test_control['Version']) self.test_manifest["hooks"] = dict() self.default_appname = "test-app" self.test_manifest["hooks"][self.default_appname] = dict() self.test_manifest["hooks"][self.default_appname]["apparmor"] = \ "%s.apparmor" % self.default_appname self.test_manifest["hooks"][self.default_appname]["desktop"] = \ "%s.desktop" % self.default_appname self.test_manifest["hooks"][self.default_appname]["urls"] = \ "%s.url-dispatcher" % self.default_appname self._update_test_manifest() self.test_pkg_yaml = dict() self.set_test_pkg_yaml("name", self.test_control['Package']) self.set_test_pkg_yaml("version", self.test_control['Version']) self.set_test_pkg_yaml("architectures", [self.test_control['Architecture']]) self._update_test_pkg_yaml() self.test_hashes_yaml = dict() self._update_test_hashes_yaml() self.test_readme_md = self.test_control['Description'] self._update_test_readme_md() self.set_test_pkgfmt("click", "0.4") # hooks self.test_security_manifests = dict() self.test_security_profiles = dict() self.test_desktop_files = dict() self.test_url_dispatcher = dict() self.test_scopes = dict() self.test_content_hub = dict() self.test_accounts_manifest = dict() self.test_accounts_application = dict() self.test_accounts_provider = dict() self.test_accounts_qml_plugin = dict() self.test_accounts_service = dict() self.test_push_helper = dict() self.test_bin_path = dict() self.test_framework = dict() self.test_framework_policy = dict() self.test_framework_policy_unknown = [] for app in self.test_manifest["hooks"].keys(): # setup security manifest for each app self.set_test_security_manifest(app, 'policy_groups', ['networking']) self.set_test_security_manifest(app, 'policy_version', 1.0) # setup desktop file for each app self.set_test_desktop(app, 'Name', self.default_appname, no_update=True) self.set_test_desktop(app, 'Comment', '%s test comment' % app, no_update=True) self.set_test_desktop(app, 'Exec', 'qmlscene %s.qml' % app, no_update=True) self.set_test_desktop(app, 'Icon', '%s.png' % app, no_update=True) self.set_test_desktop(app, 'Terminal', 'false', no_update=True) self.set_test_desktop(app, 'Type', 'Application', no_update=True) self.set_test_desktop(app, 'X-Ubuntu-Touch', 'true', no_update=True) self.set_test_url_dispatcher(app, None, None) # Ensure we have no scope entries since they conflict with desktop. # Scope tests will have to add them as part of their tests. self.set_test_scope(app, None) # Reset to no content-hub entries in manifest self.set_test_content_hub(app, None, None) # Reset to no accounts entries in manifest self.set_test_account(app, "accounts", None) self.set_test_account(app, "account-application", None) self.set_test_account(app, "account-provider", None) self.set_test_account(app, "account-qml-plugin", None) self.set_test_account(app, "account-service", None) # Reset to no push-helper entries in manifest self.set_test_push_helper(app, None, None) # Reset to no bin-path entries in manifest self.set_test_bin_path(app, None) # Reset to no framework entries in manifest self.set_test_framework(app, None, None) # Reset to no framework entries in manifest self.set_test_framework_policy(None) self.set_test_framework_policy_unknown([]) # Reset to no security profiles self.set_test_security_profile(app, None) self._update_test_security_manifests() self._update_test_security_profiles() self._update_test_desktop_files() self._update_test_url_dispatcher() self._update_test_scopes() self._update_test_content_hub() self._update_test_accounts_manifest() self._update_test_accounts_application() self._update_test_accounts_provider() self._update_test_accounts_qml_plugin() self._update_test_accounts_service() self._update_test_push_helper() self._update_test_bin_path() self._update_test_framework() self._update_test_framework_policy() self._update_test_framework_policy_unknown() # webapps manifests (leave empty for now) self.test_webapp_manifests = dict() self._update_test_webapp_manifests() # mockup a click package name based on the above self._update_test_name() def _update_test_control(self): global TEST_CONTROL TEST_CONTROL = "" for k in self.test_control.keys(): TEST_CONTROL += "%s: %s\n" % (k, self.test_control[k]) def _update_test_manifest(self): global TEST_MANIFEST TEST_MANIFEST = json.dumps(self.test_manifest) def _update_test_pkg_yaml(self): global TEST_PKG_YAML TEST_PKG_YAML = yaml.dump(self.test_pkg_yaml, default_flow_style=False, indent=4) def _update_test_hashes_yaml(self): global TEST_HASHES_YAML TEST_HASHES_YAML = yaml.dump(self.test_hashes_yaml, default_flow_style=False, indent=4) def _update_test_readme_md(self): global TEST_README_MD TEST_README_MD = self.test_readme_md def _update_test_security_manifests(self): global TEST_SECURITY TEST_SECURITY = dict() for app in self.test_security_manifests.keys(): TEST_SECURITY[app] = json.dumps(self.test_security_manifests[app]) def _update_test_security_profiles(self): global TEST_SECURITY_PROFILES TEST_SECURITY_PROFILES = dict() for app in self.test_security_profiles.keys(): TEST_SECURITY_PROFILES[app] = self.test_security_profiles[app] self.test_manifest["hooks"][app]["apparmor-profile"] = \ "%s.profile" % app self._update_test_manifest() def _update_test_desktop_files(self): global TEST_DESKTOP TEST_DESKTOP = dict() for app in self.test_desktop_files.keys(): contents = '''[Desktop Entry]''' for k in self.test_desktop_files[app].keys(): contents += '\n%s=%s' % (k, self.test_desktop_files[app][k]) contents += "\n" fn = os.path.join(self.desktop_tmpdir, "%s.desktop" % app) with open(fn, "w") as f: f.write(contents) f.close() TEST_DESKTOP[app] = DesktopEntry(fn) def _update_test_webapp_manifests(self): global TEST_WEBAPP_MANIFESTS TEST_WEBAPP_MANIFESTS = dict() for i in self.test_webapp_manifests.keys(): TEST_WEBAPP_MANIFESTS[i] = self.test_webapp_manifests[i] def _update_test_url_dispatcher(self): global TEST_URLS TEST_URLS = dict() for app in self.test_url_dispatcher.keys(): TEST_URLS[app] = self.test_url_dispatcher[app] def _update_test_scopes(self): global TEST_SCOPES TEST_SCOPES = dict() for app in self.test_scopes.keys(): TEST_SCOPES[app] = self.test_scopes[app] self.test_manifest["hooks"][app]["scope"] = \ TEST_SCOPES[app]["dir_rel"] self._update_test_manifest() def _update_test_content_hub(self): global TEST_CONTENT_HUB TEST_CONTENT_HUB = dict() for app in self.test_content_hub.keys(): TEST_CONTENT_HUB[app] = self.test_content_hub[app] self.test_manifest["hooks"][app]["content-hub"] = \ "%s.content.json" % app self._update_test_manifest() def _update_test_accounts_manifest(self): global TEST_ACCOUNTS_MANIFEST TEST_ACCOUNTS_MANIFEST = dict() for app in self.test_accounts_manifest.keys(): TEST_ACCOUNTS_MANIFEST[app] = self.test_accounts_manifest[app] self.test_manifest["hooks"][app]["accounts"] = \ "%s.accounts" % app self._update_test_manifest() def _update_test_accounts_application(self): global TEST_ACCOUNTS_APPLICATION TEST_ACCOUNTS_APPLICATION = dict() for app in self.test_accounts_application.keys(): TEST_ACCOUNTS_APPLICATION[app] = self.test_accounts_application[app] self.test_manifest["hooks"][app]["account-application"] = \ "%s.application" % app self._update_test_manifest() def _update_test_accounts_provider(self): global TEST_ACCOUNTS_PROVIDER TEST_ACCOUNTS_PROVIDER = dict() for app in self.test_accounts_provider.keys(): TEST_ACCOUNTS_PROVIDER[app] = self.test_accounts_provider[app] self.test_manifest["hooks"][app]["account-provider"] = \ "%s.provider" % app self._update_test_manifest() def _update_test_accounts_qml_plugin(self): global TEST_ACCOUNTS_QML_PLUGIN TEST_ACCOUNTS_QML_PLUGIN = dict() for app in self.test_accounts_qml_plugin.keys(): TEST_ACCOUNTS_QML_PLUGIN[app] = self.test_accounts_qml_plugin[app] self.test_manifest["hooks"][app]["account-qml-plugin"] = \ "%s.qml_plugin" % app self._update_test_manifest() def _update_test_accounts_service(self): global TEST_ACCOUNTS_SERVICE TEST_ACCOUNTS_SERVICE = dict() for app in self.test_accounts_service.keys(): TEST_ACCOUNTS_SERVICE[app] = self.test_accounts_service[app] self.test_manifest["hooks"][app]["account-service"] = \ "%s.service" % app self._update_test_manifest() def _update_test_push_helper(self): global TEST_PUSH_HELPER TEST_PUSH_HELPER = dict() for app in self.test_push_helper.keys(): TEST_PUSH_HELPER[app] = self.test_push_helper[app] self.test_manifest["hooks"][app]["push-helper"] = \ "%s.push-helper.json" % app self._update_test_manifest() def _update_test_bin_path(self): global TEST_BIN_PATH TEST_BIN_PATH = dict() for app in self.test_bin_path.keys(): TEST_BIN_PATH[app] = self.test_bin_path[app] self.test_manifest["hooks"][app]["bin-path"] = \ "%s" % TEST_BIN_PATH[app] self._update_test_manifest() def _update_test_framework(self): global TEST_FRAMEWORK TEST_FRAMEWORK = dict() for app in self.test_framework.keys(): TEST_FRAMEWORK[app] = self.test_framework[app] if app not in self.test_manifest["hooks"]: self.test_manifest["hooks"][app] = dict() self.test_manifest["hooks"][app]["framework"] = \ "%s.framework" % TEST_FRAMEWORK[app] self._update_test_manifest() def _update_test_framework_policy(self): global TEST_FRAMEWORK_POLICY TEST_FRAMEWORK_POLICY = self.test_framework_policy def _update_test_framework_policy_unknown(self): global TEST_FRAMEWORK_POLICY_UNKNOWN TEST_FRAMEWORK_POLICY_UNKNOWN = self.test_framework_policy_unknown def _update_test_name(self): self.test_name = "%s_%s_%s.click" % (self.test_control['Package'], self.test_control['Version'], self.test_control['Architecture']) def check_results(self, report, expected_counts={'info': 1, 'warn': 0, 'error': 0}, expected=None): common.check_results(self, report, expected_counts, expected) def check_manual_review(self, report, check_name, result_type='error', manual_review=True): result = report[result_type][check_name] self.assertEqual(result['manual_review'], manual_review) def set_test_control(self, key, value): '''Set key in DEBIAN/control to value. If value is None, remove key''' if value is None: if key in self.test_control: self.test_control.pop(key, None) else: self.test_control[key] = value self._update_test_control() def set_test_manifest(self, key, value): '''Set key in DEBIAN/manifest to value. If value is None, remove key''' if value is None: if key in self.test_manifest: self.test_manifest.pop(key, None) else: self.test_manifest[key] = value self._update_test_manifest() def set_test_pkg_yaml(self, key, value): '''Set key in meta/package.yaml to value. If value is None, remove key''' if value is None: if key in self.test_pkg_yaml: self.test_pkg_yaml.pop(key, None) else: self.test_pkg_yaml[key] = value self._update_test_pkg_yaml() def set_test_hashes_yaml(self, yaml): '''Set hashes.yaml to yaml''' self.test_hashes_yaml = yaml self._update_test_hashes_yaml() def set_test_readme_md(self, contents): '''Set contents of meta/readme.md''' if contents is None: self.test_readme_md = None else: self.test_readme_md = contents self._update_test_readme_md() def set_test_security_manifest(self, app, key, value): '''Set key in security manifest and package.yaml to value. If value is None, remove key, if key is None, remove app. ''' # package.yaml - we don't know if it is a service or a binary with # these values, so just use 'binaries' if key is None: if 'binaries' in self.test_pkg_yaml: for b in self.test_pkg_yaml['binaries']: if 'name' in b and b['name'] == app: self.test_pkg_yaml['binaries'].remove(b) break elif value is None: if 'binaries' in self.test_pkg_yaml: found = False for b in self.test_pkg_yaml['binaries']: if 'name' in b and b['name'] == app: if key in b: b.remove(key) found = True break else: found = False k = key if key == 'template': k = 'security-template' elif key == 'policy_groups': k = 'caps' if 'binaries' in self.test_pkg_yaml: for b in self.test_pkg_yaml['binaries']: if 'name' in b and b['name'] == app: # Found the entry, so update key/value b[k] = value found = True break # Did not find the entry, so create one if not found: if 'binaries' not in self.test_pkg_yaml: self.test_pkg_yaml['binaries'] = [] self.test_pkg_yaml['binaries'].append({'name': app, k: value}) self._update_test_pkg_yaml() # click manifest if app not in self.test_security_manifests: self.test_security_manifests[app] = dict() if key is None: if app in self.test_security_manifests: del self.test_security_manifests[app] del self.test_manifest["hooks"][app] elif value is None: if key in self.test_security_manifests[app]: self.test_security_manifests[app].pop(key, None) else: self.test_security_manifests[app][key] = value self._update_test_security_manifests() def set_test_security_profile(self, app, policy): '''Set policy in security profile''' if policy is None: if app in self.test_security_profiles: self.test_security_profiles.pop(app) else: if app not in self.test_security_profiles: self.test_security_profiles[app] = dict() self.test_security_profiles[app] = policy self._update_test_security_profiles() def set_test_pkgfmt(self, t, v): global TEST_PKGFMT_TYPE global TEST_PKGFMT_VERSION TEST_PKGFMT_TYPE = t TEST_PKGFMT_VERSION = v def set_test_desktop(self, app, key, value, no_update=False): '''Set key in desktop file to value. If value is None, remove key''' if app not in self.test_desktop_files: self.test_desktop_files[app] = dict() if value is None: if key in self.test_desktop_files[app]: self.test_desktop_files[app].pop(key, None) else: self.test_desktop_files[app][key] = value if not no_update: self._update_test_desktop_files() def set_test_webapp_manifest(self, fn, key, value): '''Set key in webapp manifest to value. If value is None, remove key''' if key is None and value is None: self.test_webapp_manifests[fn] = None self._update_test_webapp_manifests() return if fn not in self.test_webapp_manifests: self.test_webapp_manifests[fn] = dict() if value is None: if key in self.test_webapp_manifests[fn]: self.test_webapp_manifests[fn].pop(key, None) else: self.test_webapp_manifests[fn][key] = value self._update_test_webapp_manifests() def set_test_url_dispatcher(self, app, key, value, append=False): '''Set url-dispatcher entries. If value is None, remove''' if app not in self.test_url_dispatcher: self.test_url_dispatcher[app] = [] if value is None: self.test_url_dispatcher[app] = [] else: if not append: self.test_url_dispatcher[app] = [] self.test_url_dispatcher[app].append({key: value}) self._update_test_url_dispatcher() def set_test_scope(self, app, scope): '''Set scope for app. If it is None, remove''' if scope is None: if app in self.test_scopes: self.test_scopes.pop(app) if 'scope' in self.test_manifest['hooks'][app]: self.test_manifest['hooks'][app].pop('scope', None) else: self.test_scopes[app] = scope self._update_test_scopes() def set_test_content_hub(self, app, key, value): '''Set content-hub entries. If value is None, remove key, if key is None, remove content-hub from manifest''' if key is None: if app in self.test_content_hub: self.test_content_hub.pop(app) elif value is None: if key in self.test_content_hub[app]: self.test_content_hub[app].pop(key) else: if app not in self.test_content_hub: self.test_content_hub[app] = dict() if key not in self.test_content_hub[app]: self.test_content_hub[app][key] = [] self.test_content_hub[app][key].append(value) self._update_test_content_hub() def set_test_account(self, app, account_type, value): '''Set accounts XML. If value is None, remove from manifest''' if account_type == "accounts": d = self.test_accounts_manifest elif account_type == "account-application": d = self.test_accounts_application elif account_type == "account-provider": d = self.test_accounts_provider elif account_type == "account-qml-plugin": d = self.test_accounts_qml_plugin elif account_type == "account-service": d = self.test_accounts_service else: # should never get here raise ValueError("Unknown account_type '%s'" % account_type) if value is None: if app in d: d[app] = None else: d[app] = value if account_type == "accounts": self._update_test_accounts_manifest() elif account_type == "account-application": self._update_test_accounts_application() elif account_type == "account-provider": self._update_test_accounts_provider() elif account_type == "account-qml-plugin": self._update_test_accounts_qml_plugin() elif account_type == "account-service": self._update_test_accounts_service() def set_test_push_helper(self, app, key, value): '''Set push-helper entries. If value is None, remove key, if key is None, remove push-helper from manifest''' if key is None: if app in self.test_push_helper: self.test_push_helper.pop(app) elif value is None: if key in self.test_push_helper[app]: self.test_push_helper[app].pop(key) else: if app not in self.test_push_helper: self.test_push_helper[app] = dict() self.test_push_helper[app][key] = value self._update_test_push_helper() def set_test_bin_path(self, app, value): '''Set bin-path entries. If value is None, remove bin-path from manifest and yaml. If app != value, set 'exec' in the yaml Note the click manifest and the package.yaml use different storage types. pkg_yaml['binaries'] is a list of dictionaries where manifest['hooks'] is a dictionary of dictionaries. This function sets the manifest entry and then a yaml entry with 'name' and 'exec' fields. manifest['hooks'][app]['bin-path'] = value pkg_yaml['binaries'][*]['name'] = app pkg_yaml['binaries'][*]['exec'] = value ''' # Update the package.yaml if value is None: if 'binaries' in self.test_pkg_yaml: for b in self.test_pkg_yaml['binaries']: if 'name' in b and b['name'] == app: self.test_pkg_yaml['binaries'].remove(b) break else: found = False if 'binaries' in self.test_pkg_yaml: for b in self.test_pkg_yaml['binaries']: if 'name' in b and b['name'] == app: found = True break if not found: if 'binaries' not in self.test_pkg_yaml: self.test_pkg_yaml['binaries'] = [] if value == app: self.test_pkg_yaml['binaries'].append({'name': app}) else: self.test_pkg_yaml['binaries'].append({'name': app, 'exec': value}) self._update_test_pkg_yaml() # Update the click manifest (we still support click format) if value is None: if app in self.test_bin_path: self.test_bin_path.pop(app) else: if app not in self.test_bin_path: self.test_bin_path[app] = dict() self.test_bin_path[app] = value # Now update TEST_BIN_PATH self._update_test_bin_path() def set_test_framework(self, app, key, value): '''Set framework entries. If value is None, remove key, if key is None, remove framework from manifest''' if key is None: if app in self.test_framework: self.test_framework.pop(app) elif value is None: if key in self.test_framework[app]: self.test_framework[app].pop(key) else: if app not in self.test_framework: self.test_framework[app] = dict() self.test_framework[app][key] = value self._update_test_framework() def set_test_framework_policy(self, policy_dict=None): '''Set framework policy''' if policy_dict is None: # Reset self.test_framework_policy = dict() for i in ['apparmor', 'seccomp']: self.test_framework_policy[i] = dict() for j in ['templates', 'policygroups']: self.test_framework_policy[i][j] = dict() for k in ['-common', '-reserved']: n = "%s%s" % (j.rstrip('s'), k) self.test_framework_policy[i][j][n] = \ '''# Description: %s # Usage: %s ''' % (n, k.lstrip('-')) else: self.test_framework_policy = policy_dict self._update_test_framework_policy() def set_test_framework_policy_unknown(self, unknown=[]): '''Set framework policy unknown''' self.test_framework_policy_unknown = unknown self._update_test_framework_policy_unknown() def set_test_systemd(self, app, key, value): '''Set systemd entries. If key is None, remove snappy-systemd from yaml. pkg_yaml['services'][*]['name'] = app pkg_yaml['services'][*][key] = value ''' # Update the package.yaml if key is None: if 'services' in self.test_pkg_yaml: for s in self.test_pkg_yaml['services']: if 'name' in s and s['name'] == app: self.test_pkg_yaml['services'].remove(s) break else: found = False if 'services' in self.test_pkg_yaml: for s in self.test_pkg_yaml['services']: if 'name' in s and s['name'] == app: # Found the entry, so update key/value s[key] = value found = True break # Did not find the entry, so create one if not found: if 'services' not in self.test_pkg_yaml: self.test_pkg_yaml['services'] = [] self.test_pkg_yaml['services'].append({'name': app, key: value}) self._update_test_pkg_yaml() def setUp(self): '''Make sure our patches are applied everywhere''' patches = create_patches() for p in patches: p.start() self.addCleanup(p.stop) def tearDown(self): '''Make sure we reset everything to known good values''' global TEST_CONTROL TEST_CONTROL = "" global TEST_MANIFEST TEST_MANIFEST = "" global TEST_PKG_YAML TEST_PKG_YAML = "" global TEST_HASHES_YAML TEST_HASHES_YAML = "" global TEST_README_MD TEST_README_MD = "" global TEST_SECURITY TEST_SECURITY = dict() global TEST_SECURITY_PROFILES TEST_SECURITY_PROFILES = dict() global TEST_DESKTOP TEST_DESKTOP = dict() global TEST_URLS TEST_URLS = dict() global TEST_SCOPES TEST_SCOPES = dict() global TEST_CONTENT_HUB TEST_CONTENT_HUB = dict() global TEST_ACCOUNTS_MANIFEST TEST_ACCOUNTS_MANIFEST = dict() global TEST_ACCOUNTS_APPLICATION TEST_ACCOUNTS_APPLICATION = dict() global TEST_ACCOUNTS_PROVIDER TEST_ACCOUNTS_PROVIDER = dict() global TEST_ACCOUNTS_QML_PLUGIN TEST_ACCOUNTS_QML_PLUGIN = dict() global TEST_ACCOUNTS_SERVICE TEST_ACCOUNTS_SERVICE = dict() global TEST_PUSH_HELPER TEST_PUSH_HELPER = dict() global TEST_BIN_PATH TEST_BIN_PATH = dict() global TEST_FRAMEWORK TEST_FRAMEWORK = dict() global TEST_FRAMEWORK_POLICY TEST_FRAMEWORK_POLICY = dict() global TEST_FRAMEWORK_POLICY_UNKNOWN TEST_FRAMEWORK_POLICY_UNKNOWN = [] global TEST_PKGFMT_TYPE TEST_PKGFMT_TYPE = "click" global TEST_PKGFMT_VERSION TEST_PKGFMT_VERSION = "0.4" self._reset_test_data() common.recursive_rm(self.desktop_tmpdir) click-reviewers-tools-0.44~16.04.1/clickreviews/cr_desktop.py0000664000000000000000000010407212666350672020735 0ustar '''cr_desktop.py: click desktop checks''' # # Copyright (C) 2013-2015 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from __future__ import print_function from clickreviews.cr_common import ClickReview, error, open_file_read import glob import json import os import re from urllib.parse import urlsplit from xdg.DesktopEntry import DesktopEntry from xdg.Exceptions import ParsingError as xdgParsingError class ClickReviewDesktop(ClickReview): '''This class represents click lint reviews''' def __init__(self, fn, overrides=None): peer_hooks = dict() my_hook = 'desktop' peer_hooks[my_hook] = dict() peer_hooks[my_hook]['allowed'] = ClickReview.app_allowed_peer_hooks peer_hooks[my_hook]['required'] = ["apparmor"] ClickReview.__init__(self, fn, "desktop", peer_hooks=peer_hooks, overrides=overrides) if not self.is_click and not self.is_snap1: return self.desktop_files = dict() # click-show-files and a couple tests self.desktop_entries = dict() self.desktop_hook_entries = 0 if self.manifest is None: return for app in self.manifest['hooks']: if 'desktop' not in self.manifest['hooks'][app]: # msg("Skipped missing desktop hook for '%s'" % app) continue if not isinstance(self.manifest['hooks'][app]['desktop'], str): error("manifest malformed: hooks/%s/desktop is not str" % app) self.desktop_hook_entries += 1 (de, full_fn) = self._extract_desktop_entry(app) self.desktop_entries[app] = de self.desktop_files[app] = full_fn self.required_keys = ['Name', 'Type', 'Icon', 'Exec', 'X-Ubuntu-Touch', ] self.expected_execs = ['qmlscene', 'webbrowser-app', 'webapp-container', 'ubuntu-html5-app-launcher', ] self.deprecated_execs = ['cordova-ubuntu-2.8', ] # TODO: the desktop hook will actually handle this correctly self.blacklisted_keys = ['Path'] def _extract_desktop_entry(self, app): '''Get DesktopEntry for desktop file and verify it''' d = self.manifest['hooks'][app]['desktop'] fn = os.path.join(self.unpack_dir, d) bn = os.path.basename(fn) if not os.path.exists(fn): error("Could not find '%s'" % bn) fh = open_file_read(fn) contents = "" for line in fh.readlines(): contents += line fh.close() try: de = DesktopEntry(fn) except xdgParsingError as e: error("desktop file unparseable: %s (%s):\n%s" % (bn, str(e), contents)) try: de.parse(fn) except Exception as e: error("desktop file unparseable: %s (%s):\n%s" % (bn, str(e), contents)) return de, fn def _get_desktop_entry(self, app): '''Get DesktopEntry from parsed values''' return self.desktop_entries[app] def _get_desktop_files(self): '''Get desktop_files (abstracted out for mock)''' return self.desktop_files def _get_desktop_filename(self, app): '''Get desktop file filenames''' return self.desktop_files[app] def check_desktop_file(self): '''Check desktop file''' if not self.is_click and not self.is_snap1: return t = 'info' n = self._get_check_name('files_usable') s = 'OK' if len(self._get_desktop_files().keys()) != self.desktop_hook_entries: t = 'error' s = 'Could not use all specified .desktop files' elif self.desktop_hook_entries == 0: s = 'Skipped: could not find any desktop files' self._add_result(t, n, s) def check_desktop_file_valid(self): '''Check desktop file validates''' if not self.is_click and not self.is_snap1: return for app in sorted(self.desktop_entries): de = self._get_desktop_entry(app) t = 'info' n = self._get_check_name('validates', app=app) s = 'OK' l = None try: de.validate() except Exception as e: t = 'error' s = 'did not validate: (%s)' % str(e) l = 'http://askubuntu.com/questions/417377/what-does-desktop-validates-mean/417378' self._add_result(t, n, s, l) def check_desktop_required_keys(self): '''Check for required keys''' if not self.is_click and not self.is_snap1: return for app in sorted(self.desktop_entries): de = self._get_desktop_entry(app) t = 'info' n = self._get_check_name('required_keys', app=app) s = "OK" missing = [] for f in self.required_keys: if not de.hasKey(f): missing.append(f) if len(missing) > 0: t = 'error' s = 'missing required keys: %s' % ",".join(missing) self._add_result(t, n, s) t = 'info' n = self._get_check_name('required_fields_not_empty', app=app) s = "OK" empty = [] for f in self.required_keys: if de.hasKey(f) and de.get(f) == "": empty.append(f) if len(empty) > 0: t = 'error' s = 'Empty required keys: %s' % ",".join(empty) self._add_result(t, n, s) def check_desktop_blacklisted_keys(self): '''Check for blacklisted keys''' if not self.is_click and not self.is_snap1: return for app in sorted(self.desktop_entries): de = self._get_desktop_entry(app) t = 'info' n = self._get_check_name('blacklisted_keys', app=app) s = "OK" found = [] for f in self.blacklisted_keys: if de.hasKey(f): found.append(f) if len(found) > 0: t = 'error' s = 'found blacklisted keys: %s' % ",".join(found) self._add_result(t, n, s) def check_desktop_exec(self): '''Check Exec entry''' if not self.is_click and not self.is_snap1: return for app in sorted(self.desktop_entries): de = self._get_desktop_entry(app) t = 'info' n = self._get_check_name('Exec', app=app) s = 'OK' l = None if not de.hasKey('Exec'): t = 'error' s = "missing key 'Exec'" elif de.getExec().startswith('/'): t = 'error' s = "absolute path '%s' for Exec given in .desktop file." % \ de.getExec() l = 'http://askubuntu.com/questions/417381/what-does-desktop-exec-mean/417382' elif de.getExec().split()[0] not in self.expected_execs: if self.pkg_arch[0] == "all": # interpreted file if de.getExec().split()[0] not in self.deprecated_execs: s = "found unexpected Exec with architecture '%s': %s" % \ (self.pkg_arch[0], de.getExec().split()[0]) else: s = "found deprecated Exec with architecture '%s': %s" % \ (self.pkg_arch[0], de.getExec().split()[0]) t = 'warn' else: # compiled # TODO: this can be a lot smarter s = "Non-standard Exec with architecture " + \ "'%s': %s (ok for compiled code)" % \ (self.pkg_arch[0], de.getExec().split()[0]) t = 'info' self._add_result(t, n, s, l) def check_desktop_exec_webapp_container(self): '''Check Exec=webapp-container entry''' if not self.is_click and not self.is_snap1: return if self.manifest is None: return fwk = self.manifest['framework'] for app in sorted(self.desktop_entries): de = self._get_desktop_entry(app) t = 'info' n = self._get_check_name('Exec_webapp_container', app=app) s = 'OK' if not de.hasKey('Exec'): t = 'error' s = "missing key 'Exec'" self._add_result(t, n, s) continue elif de.getExec().split()[0] == "ubuntu-html5-app-launcher" and \ fwk.startswith('ubuntu-sdk') and not \ (fwk.startswith('ubuntu-sdk-13') or fwk.startswith('ubuntu-sdk-14')): # ubuntu-html5-app-launcher only available in ubuntu-sdk-14.10 # and lower t = 'error' s = "ubuntu-html5-app-launcher is obsoleted in 15.04 " + \ "frameworks and higher. Please use 'webapp-container' " + \ "instead and ensure your security policy uses the " + \ "'ubuntu-webapp' template" self._add_result(t, n, s) continue elif de.getExec().split()[0] != "webapp-container": s = "SKIPPED (not webapp-container)" self._add_result(t, n, s) continue t = 'info' n = self._get_check_name('Exec_webapp_container_webapp', app=app) s = 'OK' if '--webapp' in de.getExec().split(): t = 'error' s = "should not use --webapp in '%s'" % \ (de.getExec()) self._add_result(t, n, s) t = 'info' n = self._get_check_name('Exec_webapp_container_13.10', app=app) s = 'OK' if self.manifest['framework'] == "ubuntu-sdk-13.10": t = 'info' s = "'webapp-container' not available in 13.10 release " \ "images (ok if targeting 14.04 images with %s " \ "framework" % self.manifest['framework'] self._add_result(t, n, s) def check_desktop_exec_webbrowser(self): '''Check Exec=webbrowser-app entry''' if not self.is_click and not self.is_snap1: return for app in sorted(self.desktop_entries): de = self._get_desktop_entry(app) t = 'info' n = self._get_check_name('Exec_webbrowser', app=app) s = 'OK' if not de.hasKey('Exec'): t = 'error' s = "missing key 'Exec'" self._add_result(t, n, s) continue elif de.getExec().split()[0] != "webbrowser-app": s = "SKIPPED (not webbrowser-app)" self._add_result(t, n, s) continue t = 'info' n = self._get_check_name('Exec_webbrowser_webapp', app=app) s = 'OK' if '--webapp' not in de.getExec().split(): t = 'error' s = "could not find --webapp in '%s'" % \ (de.getExec()) self._add_result(t, n, s) t = 'info' n = self._get_check_name('Exec_webbrowser_13.10', app=app) s = 'OK' if self.manifest['framework'] != "ubuntu-sdk-13.10": t = 'error' s = "may not use 'webbrowser-app' with framework '%s'" % \ self.manifest['framework'] self._add_result(t, n, s) def check_desktop_exec_webapp_args(self): '''Check Exec=web* args''' if not self.is_click and not self.is_snap1: return for app in sorted(self.desktop_entries): de = self._get_desktop_entry(app) t = 'info' n = self._get_check_name('Exec_webapp_args', app=app) s = 'OK' if not de.hasKey('Exec'): t = 'error' s = "missing key 'Exec'" self._add_result(t, n, s) continue elif de.getExec().split()[0] != "webbrowser-app" and \ de.getExec().split()[0] != "webapp-container": s = "SKIPPED (not webapp-container or webbrowser-app)" self._add_result(t, n, s) continue t = 'info' n = self._get_check_name('Exec_webapp_args_minimal_chrome', app=app) s = 'OK' if '--enable-back-forward' not in de.getExec().split(): s = "could not find --enable-back-forward in '%s'" % \ (de.getExec()) self._add_result(t, n, s) # verify the presence of either webappUrlPatterns or # webappModelSearchPath t = 'info' n = self._get_check_name('Exec_webapp_args_required', app=app) s = 'OK' found_url_patterns = False found_model_search_path = False found_named_webapp = False urls = [] for i in de.getExec().split(): if i == "webbrowser-app" or i == "webapp-container": continue if i.startswith('--webappUrlPatterns'): found_url_patterns = True if i.startswith('--webappModelSearchPath'): found_model_search_path = True if i.startswith('--webapp='): found_model_search_path = True # consider potential Exec field codes as 'non urls' if not i.startswith('--') and not i.startswith('%'): urls.append(i) is_launching_local_app = True if len(urls) == 0: is_launching_local_app = False for url in urls: parts = urlsplit(url) if parts.scheme in ['http', 'https']: is_launching_local_app = False break if is_launching_local_app and \ (found_url_patterns or found_model_search_path or found_named_webapp): t = 'error' s = "should not specify --webappUrlPatterns, " + \ "--webappModelSearchPath or --webapp= when " + \ "running local application" elif not is_launching_local_app: if not found_url_patterns and not found_model_search_path: t = 'error' s = "must specify one of --webappUrlPatterns or " + \ "--webappModelSearchPath" self._add_result(t, n, s) def _check_patterns(self, app, patterns, args): pattern_count = 1 for pattern in patterns: urlp_scheme_pat = pattern[:-1].split(':')[0] urlp_p = urlsplit(re.sub('\?', '', pattern[:-1])) target = args[-1] urlp_t = urlsplit(target) t = 'info' n = self._get_check_name( 'Exec_webbrowser_webapp_url_patterns_has_https', app=app, extra=pattern) s = 'OK' if not pattern.startswith('https?://'): t = 'warn' s = "'https?://' not found in '%s'" % pattern + \ " (may cause needless redirect)" self._add_result(t, n, s) t = 'info' n = self._get_check_name( 'Exec_webbrowser_webapp_url_patterns_uses_trailing_glob', app=app, extra=pattern) s = 'OK' if not pattern.endswith('*'): t = 'warn' s = "'%s' does not end with '*'" % pattern + \ " (may cause needless redirect) - %s" % urlp_p.path self._add_result(t, n, s) t = 'info' n = self._get_check_name( 'Exec_webbrowser_webapp_url_patterns_uses_unsafe_glob', app=app, extra=pattern) s = 'OK' if len(urlp_p.path) == 0 and pattern.endswith('*'): t = 'error' s = "'%s' contains trailing glob in netloc" % pattern self._add_result(t, n, s) t = 'info' n = self._get_check_name( 'Exec_webbrowser_webapp_url_patterns_uses_safe_glob', app=app, extra=pattern) s = 'OK' if '*' in pattern[:-1] and \ (pattern[:-1].count('*') != 1 or not pattern.startswith('https?://*')): t = 'warn' s = "'%s' contains nested '*'" % pattern + \ " (needs human review)" self._add_result(t, n, s) t = 'info' n = self._get_check_name('Exec_webbrowser_target_exists', app=app) s = 'OK' if urlp_t.scheme == "": t = 'error' s = 'Exec line does not end with parseable URL' self._add_result(t, n, s) continue self._add_result(t, n, s) t = 'info' n = self._get_check_name( 'Exec_webbrowser_target_scheme_matches_patterns', app=app, extra=pattern) s = 'OK' if not re.match(r'^%s$' % urlp_scheme_pat, urlp_t.scheme): t = 'error' s = "'%s' doesn't match '%s' " % (urlp_t.scheme, urlp_scheme_pat) + \ "(will likely cause needless redirect)" self._add_result(t, n, s) t = 'info' n = self._get_check_name( 'Exec_webbrowser_target_netloc_matches_patterns', app=app, extra=pattern) s = 'OK' # TODO: this is admittedly simple, but matches Canonical # webapps currently, so ok for now if urlp_p.netloc.startswith('*') and len(urlp_p.netloc) > 2 and \ urlp_t.netloc.endswith(urlp_p.netloc[1:]): s = "OK ('%s' matches '%s')" % (urlp_t.netloc, urlp_p.netloc) elif urlp_t.netloc != urlp_p.netloc: if pattern_count == 1: t = 'warn' s = "'%s' != primary pattern '%s'" % \ (urlp_t.netloc, urlp_p.netloc) + \ " (may cause needless redirect)" else: t = 'info' s = "target '%s' != non-primary pattern '%s'" % \ (urlp_t.netloc, urlp_p.netloc) self._add_result(t, n, s) pattern_count += 1 def check_desktop_exec_webbrowser_urlpatterns(self): '''Check Exec=webbrowser-app entry has valid --webappUrlPatterns''' if not self.is_click and not self.is_snap1: return for app in sorted(self.desktop_entries): de = self._get_desktop_entry(app) execline = de.getExec().split() if not de.hasKey('Exec'): continue elif execline[0] != "webbrowser-app": continue elif len(execline) < 2: continue args = execline[1:] t = 'info' n = self._get_check_name( 'Exec_webbrowser_webappUrlPatterns', app=app) s = 'OK' pats = "" count = 0 for a in args: if not a.startswith('--webappUrlPatterns='): continue pats = a.split('=', maxsplit=1)[1] count += 1 if count == 0: # one of --webappUrlPatterns or --webappModelSearchPath is a # required arg and generates an error so just make this info t = 'info' s = "SKIPPED (--webappUrlPatterns not specified)" self._add_result(t, n, s) continue elif count > 1: t = 'error' s = "found multiple '--webappUrlPatterns=' in '%s'" % \ " ".join(args) self._add_result(t, n, s) continue self._check_patterns(app, pats.split(','), args) def _extract_webapp_manifests(self): '''Extract webapp manifest file''' files = sorted(glob.glob("%s/unity-webapps-*/manifest.json" % self.unpack_dir)) manifests = dict() for fn in files: key = os.path.relpath(fn, self.unpack_dir) try: manifests[key] = json.load(open_file_read(fn)) except Exception: manifests[key] = None error("Could not parse '%s'" % fn, do_exit=False) return manifests def check_desktop_exec_webbrowser_modelsearchpath(self): '''Check Exec=webbrowser-app entry has valid --webappModelSearchPath''' if not self.is_click and not self.is_snap1: return for app in sorted(self.desktop_entries): de = self._get_desktop_entry(app) execline = de.getExec().split() if not de.hasKey('Exec'): continue elif execline[0] != "webbrowser-app": continue elif len(execline) < 2: continue args = execline[1:] t = 'info' n = self._get_check_name( 'Exec_webbrowser_webappModelSearchPath_present', app=app) s = 'OK' path = "" count = 0 for a in args: if not a.startswith('--webappModelSearchPath='): continue path = a.split('=', maxsplit=1)[1] count += 1 if count == 0: # one of --webappUrlPatterns or --webappModelSearchPath is a # required arg and generates an error so just make this info t = 'info' s = "SKIPPED (--webappModelSearchPath not specified)" self._add_result(t, n, s) continue elif count > 1: t = 'error' s = "found multiple '--webappModelSearchPath=' in '%s'" % \ " ".join(args) self._add_result(t, n, s) continue if not path: t = 'error' s = 'empty arg to --webappModelSearchPath' self._add_result(t, n, s) continue self._add_result(t, n, s) # if --webappModelSearchPath is specified, that means we should # look for webapp configuration in the manifest.json in # ubuntu-webapps-*/ manifests = self._extract_webapp_manifests() t = 'info' n = self._get_check_name( 'Exec_webbrowser_webapp_manifest', app=app) s = 'OK' if len(manifests) == 0: t = 'error' s = 'could not find unity-webaps-*/manifest.json' self._add_result(t, n, s) continue elif len(manifests) > 1: # for now error on this since having # multiple manifests is unknown t = 'error' fns = [] for f in manifests.keys(): fns.append(f) s = 'found multiple webapp manifest files: %s' % ",".join(fns) self._add_result(t, n, s) continue self._add_result(t, n, s) for k in manifests.keys(): m = manifests[k] t = 'info' n = self._get_check_name( 'Exec_webbrowser_webapp_manifest_wellformed', app=app, extra=k) s = 'OK' if m is None or m == 'null': # 'null' is for testsuite t = 'error' s = 'could not load webapp manifest file. Is it ' + \ 'properly formatted?' self._add_result(t, n, s) continue self._add_result(t, n, s) # 'includes' contains the patterns t = 'info' n = self._get_check_name( 'Exec_webbrowser_webapp_manifest_includes_present', app=app, extra=k) s = 'OK' if 'includes' not in m: t = 'error' s = "could not find 'includes' in webapp manifest" elif not isinstance(m['includes'], list): t = 'error' s = "'includes' in webapp manifest is not list" self._add_result(t, n, s) if t == 'error': continue self._check_patterns(app, m['includes'], args) def check_desktop_groups(self): '''Check Desktop Entry entry''' if not self.is_click and not self.is_snap1: return for app in sorted(self.desktop_entries): de = self._get_desktop_entry(app) t = 'info' n = self._get_check_name('groups', app=app) s = "OK" if len(de.groups()) != 1: t = 'error' s = 'too many desktop groups' elif "Desktop Entry" not in de.groups(): t = 'error' s = "'[Desktop Entry]' group not found" self._add_result(t, n, s) def check_desktop_type(self): '''Check Type entry''' if not self.is_click and not self.is_snap1: return for app in sorted(self.desktop_entries): de = self._get_desktop_entry(app) t = 'info' n = self._get_check_name('Type', app=app) s = "OK" if not de.hasKey('Type'): t = 'error' s = "missing key 'Type'" elif de.getType() != "Application": t = 'error' s = 'does not use Type=Application' self._add_result(t, n, s) def check_desktop_x_ubuntu_touch(self): '''Check X-Ubuntu-Touch entry''' if not self.is_click and not self.is_snap1: return for app in sorted(self.desktop_entries): de = self._get_desktop_entry(app) t = 'info' n = self._get_check_name('X-Ubuntu-Touch', app=app) s = "OK" if not de.hasKey('X-Ubuntu-Touch'): t = 'error' s = "missing key 'X-Ubuntu-Touch'" elif de.get("X-Ubuntu-Touch") != "true" and \ de.get("X-Ubuntu-Touch") != "True": t = 'error' s = 'does not use X-Ubuntu-Touch=true' self._add_result(t, n, s) def check_desktop_x_ubuntu_stagehint(self): '''Check X-Ubuntu-StageHint entry''' if not self.is_click and not self.is_snap1: return for app in sorted(self.desktop_entries): de = self._get_desktop_entry(app) t = 'info' n = self._get_check_name('X-Ubuntu-StageHint', app=app) s = "OK" if not de.hasKey('X-Ubuntu-StageHint'): t = 'info' s = "OK (not specified)" elif de.get("X-Ubuntu-StageHint") != "SideStage": t = 'error' s = "unsupported X-Ubuntu-StageHint=%s " % \ de.get("X-Ubuntu-StageHint") + \ "(should be for example, 'SideStage')" self._add_result(t, n, s) def check_desktop_x_ubuntu_gettext_domain(self): '''Check X-Ubuntu-Gettext-Domain entry''' if not self.is_click and not self.is_snap1: return for app in sorted(self.desktop_entries): de = self._get_desktop_entry(app) t = 'info' n = self._get_check_name('X-Ubuntu-Gettext-Domain', app=app) s = "OK" if not de.hasKey('X-Ubuntu-Gettext-Domain'): t = 'info' s = "OK (not specified)" elif de.get("X-Ubuntu-Gettext-Domain") == "": t = 'error' s = "X-Ubuntu-Gettext-Domain is empty" elif de.get("X-Ubuntu-Gettext-Domain") != self.click_pkgname: t = 'warn' s = "'%s' != '%s'" % (de.get("X-Ubuntu-Gettext-Domain"), self.click_pkgname) s += " (ok if app uses i18n.domain('%s')" % \ de.get("X-Ubuntu-Gettext-Domain") + \ " or uses organizationName" self._add_result(t, n, s) def check_desktop_terminal(self): '''Check Terminal entry''' if not self.is_click and not self.is_snap1: return for app in sorted(self.desktop_entries): de = self._get_desktop_entry(app) t = 'info' n = self._get_check_name('Terminal', app=app) s = "OK" if not de.hasKey('Terminal'): s = "OK (not specified)" elif de.getTerminal() is not False: t = 'error' s = 'does not use Terminal=false (%s)' % de.getTerminal() self._add_result(t, n, s) def check_desktop_version(self): '''Check Version entry''' if not self.is_click and not self.is_snap1: return for app in sorted(self.desktop_entries): de = self._get_desktop_entry(app) t = 'info' n = self._get_check_name('Version', app=app) s = "OK" l = None if not de.hasKey('Version'): s = "OK (not specified)" elif de.getVersionString() != "1.0": # http://standards.freedesktop.org/desktop-entry-spec/latest t = 'error' s = "'%s' does not match freedesktop.org version '1.0'" % \ de.getVersionString() l = 'http://askubuntu.com/questions/419907/what-does-version-mean-in-the-desktop-file/419908' self._add_result(t, n, s, l) def check_desktop_comment(self): '''Check Comment entry''' if not self.is_click and not self.is_snap1: return for app in sorted(self.desktop_entries): de = self._get_desktop_entry(app) t = 'info' n = self._get_check_name('Comment_boilerplate', app=app) s = "OK" l = None if de.hasKey('Comment') and \ de.getComment() == "My project description": t = 'warn' s = "Comment uses SDK boilerplate '%s'" % de.getComment() l = 'http://askubuntu.com/questions/417359/what-does-desktop-comment-boilerplate-mean/417360' self._add_result(t, n, s, l) def check_desktop_icon(self): '''Check Icon entry''' if not self.is_click and not self.is_snap1: return ICON_SUFFIXES = ['.svg', '.png', '.jpg', ] for app in sorted(self.desktop_entries): de = self._get_desktop_entry(app) t = 'info' n = self._get_check_name('Icon', app=app) s = 'OK' l = None if not de.hasKey('Icon'): t = 'error' s = "missing key 'Icon'" l = 'http://askubuntu.com/questions/417369/what-does-desktop-icon-mean/417370' elif de.getIcon().startswith('/'): t = 'error' s = "absolute path '%s' for icon given in .desktop file." % \ de.getIcon() l = 'http://askubuntu.com/questions/417369/what-does-desktop-icon-mean/417370' elif not os.path.exists(os.path.join(self.unpack_dir, de.getIcon())) and \ True not in filter(lambda a: os.path.exists(os.path.join( self.unpack_dir, de.getIcon() + a)), ICON_SUFFIXES): t = 'error' s = "'%s' specified as icon in .desktop file for app '%s', " \ "which is not available in the click package." % \ (de.getIcon(), app) l = 'http://askubuntu.com/questions/417369/what-does-desktop-icon-mean/417370' self._add_result(t, n, s, l) def check_desktop_duplicate_entries(self): '''Check desktop for duplicate entries''' if not self.is_click and not self.is_snap1: return for app in sorted(self.desktop_entries): found = [] dupes = [] t = 'info' n = self._get_check_name('duplicate_keys', app=app) s = 'OK' fn = self._get_desktop_filename(app) content = open_file_read(fn).readlines() for line in content: tmp = line.split('=') if len(tmp) < 2: continue if tmp[0] in found: dupes.append(tmp[0]) else: found.append(tmp[0]) if len(dupes) > 0: t = 'error' s = 'found duplicate keys: %s' % ",".join(dupes) self._add_result(t, n, s) click-reviewers-tools-0.44~16.04.1/clickreviews/cr_scope.py0000664000000000000000000001607612666350672020403 0ustar '''cr_scope.py: click scope''' # # Copyright (C) 2014-2015 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from __future__ import print_function from clickreviews.cr_common import ClickReview, error import codecs import configparser import os import re # Please refer to the config file documentation at: # http://bazaar.launchpad.net/~unity-team/unity-scopes-api/trunk/view/head:/CONFIGFILES KNOWN_SECTIONS = set(["ScopeConfig", "Appearance"]) class ClickReviewScope(ClickReview): '''This class represents click lint reviews''' def __init__(self, fn, overrides=None): peer_hooks = dict() my_hook = 'scope' peer_hooks[my_hook] = dict() peer_hooks[my_hook]['allowed'] = ClickReview.scope_allowed_peer_hooks peer_hooks[my_hook]['required'] = ['apparmor'] ClickReview.__init__(self, fn, "scope", peer_hooks=peer_hooks, overrides=overrides) if not self.is_click and not self.is_snap1: return self.scopes = dict() if self.manifest is None: return for app in self.manifest['hooks']: if 'scope' not in self.manifest['hooks'][app]: # msg("Skipped missing scope hook for '%s'" % app) continue if not isinstance(self.manifest['hooks'][app]['scope'], str): error("manifest malformed: hooks/%s/scope is not str" % app) self.scopes[app] = self._extract_scopes(app) def _extract_scopes(self, app): '''Get scopes''' d = dict() s = self.manifest['hooks'][app]['scope'] fn = os.path.join(self.unpack_dir, s) bn = os.path.basename(fn) if not os.path.exists(fn): error("Could not find '%s'" % bn) elif not os.path.isdir(fn): error("'%s' is not a directory" % bn) ini_fn = os.path.join(fn, "%s_%s.ini" % (self.manifest['name'], app)) ini_fn_bn = os.path.relpath(ini_fn, self.unpack_dir) if not os.path.exists(ini_fn): error("Could not find scope INI file '%s'" % ini_fn_bn) try: d["scope_config"] = configparser.ConfigParser() d["scope_config"].read_file(codecs.open(ini_fn, "r", "utf8")) except Exception as e: error("scope config unparseable: %s (%s)" % (ini_fn_bn, str(e))) d["dir"] = fn d["dir_rel"] = bn d["ini_file"] = ini_fn d["ini_file_rel"] = ini_fn_bn return d def check_scope_ini(self): '''Check scope .ini file''' if not self.is_click and not self.is_snap1: return for app in sorted(self.scopes.keys()): t = 'info' n = self._get_check_name('ini_scope_section', app=app) s = "OK" sections = set(self.scopes[app]["scope_config"].sections()) unknown_sections = sections.difference(KNOWN_SECTIONS) if unknown_sections: t = 'error' s = "'%s' has unknown sections: %s" % ( self.scopes[app]["ini_file_rel"], ", ".join(unknown_sections)) elif "ScopeConfig" not in sections: t = 'error' s = "Could not find 'ScopeConfig' in '%s'" % ( self.scopes[app]["ini_file_rel"]) self._add_result(t, n, s) continue self._add_result(t, n, s) # Make these all lower case for easier comparisons required = ['author', 'description', 'displayname'] optional = ['art', 'childscopes', 'hotkey', 'icon', 'idletimeout', 'invisible', 'keywords', 'locationdataneeded', 'resultsttltype', 'scoperunner', 'searchhint'] translated = ['description', 'displayname', 'searchhint'] internal = ['debugmode'] missing = [] t = 'info' n = self._get_check_name('ini_scope_required_fields', app=app) s = "OK" for r in required: if r not in self.scopes[app]["scope_config"]['ScopeConfig']: missing.append(r) if len(missing) == 1: t = 'error' s = "Missing required field in '%s': %s" % ( self.scopes[app]["ini_file_rel"], missing[0]) elif len(missing) > 1: t = 'error' s = "Missing required fields in '%s': %s" % ( self.scopes[app]["ini_file_rel"], ", ".join(missing)) self._add_result(t, n, s) t = 'info' n = self._get_check_name('ini_scope_unknown_fields', app=app) s = 'OK' unknown = [] for i in self.scopes[app]["scope_config"]['ScopeConfig'].keys(): f = i.lower() if f not in required and f not in optional and f not in internal and \ (f.split("[")[0] not in translated or not re.search('.*\[[a-z]{2,3}(_[a-z]{2,3})?\]$', f)): unknown.append(f) if len(unknown) == 1: t = 'warn' s = "Unknown field in '%s': %s" % ( self.scopes[app]["ini_file_rel"], unknown[0]) elif len(unknown) > 1: t = 'warn' s = "Unknown fields in '%s': %s" % ( self.scopes[app]["ini_file_rel"], ", ".join(unknown)) self._add_result(t, n, s) t = 'info' n = self._get_check_name('ini_scope_internal_fields', app=app) s = "OK" forbidden = [] for r in internal: if r in self.scopes[app]["scope_config"]['ScopeConfig']: forbidden.append(r) if len(forbidden) == 1: t = 'error' s = "Forbidden field in '%s': %s" % ( self.scopes[app]["ini_file_rel"], forbidden[0]) elif len(forbidden) > 1: t = 'error' s = "Forbidden fields in '%s': %s" % ( self.scopes[app]["ini_file_rel"], ", ".join(forbidden)) self._add_result(t, n, s) click-reviewers-tools-0.44~16.04.1/clickreviews/tests/0000775000000000000000000000000012750117074017353 5ustar click-reviewers-tools-0.44~16.04.1/clickreviews/tests/test_cr_common.py0000664000000000000000000001156112717665470022757 0ustar from clickreviews.cr_common import ClickReview from clickreviews import cr_tests class ClickReviewTestCase(cr_tests.TestClickReview): def setUp(self): super().setUp() self.review = ClickReview('app.click', 'review_type') def test_add_result_default_manual_review(self): self.review._add_result('info', 'some-check', 'OK') self.assertEqual(self.review.click_report, { 'info': { 'some-check': { 'text': 'OK', 'manual_review': False, } }, 'warn': {}, 'error': {}, }) def test_add_result_custom_manual_review(self): self.review._add_result('info', 'some-check', 'OK', manual_review=True) self.assertEqual(self.review.click_report, { 'info': { 'some-check': { 'text': 'OK', 'manual_review': True, } }, 'warn': {}, 'error': {}, }) def test_add_result_override_result_type_warn(self): self.review._add_result('warn', 'some-check', 'notok', override_result_type='info') self.assertEqual(self.review.click_report, { 'info': { 'some-check': { 'text': '[WARN] notok', 'manual_review': False, } }, 'warn': {}, 'error': {}, }) def test_add_result_override_result_type_error(self): self.review._add_result('error', 'some-check', 'notok', override_result_type='info') self.assertEqual(self.review.click_report, { 'info': { 'some-check': { 'text': '[ERROR] notok', 'manual_review': False, } }, 'warn': {}, 'error': {}, }) def test_add_result_override_result_type_info(self): self.review._add_result('info', 'some-check', 'ok', override_result_type='warn') self.assertEqual(self.review.click_report, { 'warn': { 'some-check': { 'text': '[INFO] ok', 'manual_review': False, } }, 'info': {}, 'error': {}, }) def test_verify_peer_hooks_empty(self): '''Check verify_peer_hooks() - empty''' peer_hooks = dict() my_hook = "foo" peer_hooks[my_hook] = dict() peer_hooks[my_hook]['allowed'] = [] peer_hooks[my_hook]['required'] = [] self.review.peer_hooks = peer_hooks d = self.review._verify_peer_hooks(my_hook) self.assertEqual(0, len(d.keys())) def test_verify_peer_hooks_missing(self): '''Check verify_peer_hooks() - missing required''' peer_hooks = dict() my_hook = "desktop" peer_hooks[my_hook] = dict() peer_hooks[my_hook]['allowed'] = ["apparmor", "urls"] peer_hooks[my_hook]['required'] = ["nonexistent"] self.review.peer_hooks = peer_hooks d = self.review._verify_peer_hooks(my_hook) self.assertEqual(1, len(d.keys())) self.assertTrue('missing' in d.keys()) self.assertTrue('nonexistent' in d['missing'][self.default_appname]) def test_verify_peer_hooks_disallowed(self): '''Check verify_peer_hooks() - disallowed''' peer_hooks = dict() my_hook = "desktop" peer_hooks[my_hook] = dict() peer_hooks[my_hook]['allowed'] = ["apparmor"] peer_hooks[my_hook]['required'] = [] self.review.peer_hooks = peer_hooks d = self.review._verify_peer_hooks(my_hook) self.assertEqual(1, len(d.keys())) self.assertTrue('disallowed' in d.keys()) self.assertTrue('urls' in d['disallowed'][self.default_appname]) def test_get_check_name(self): name = self.review._get_check_name('prefix') self.assertEqual(name, 'review_type:prefix') def test_get_check_name_with_app(self): name = self.review._get_check_name('prefix', app='app') self.assertEqual(name, 'review_type:prefix:app') def test_get_check_name_with_extra(self): name = self.review._get_check_name('prefix', extra='extra') self.assertEqual(name, 'review_type:prefix:extra') def test_get_check_name_with_app_and_extra(self): name = self.review._get_check_name('prefix', app='app', extra='extra') self.assertEqual(name, 'review_type:prefix:app:extra') def test_check_if_message_catalog_true(self): self.assertTrue(self.review._check_if_message_catalog('/a/b/foo.mo')) def test_check_if_message_catalog_false(self): self.assertFalse(self.review._check_if_message_catalog('/a/b/foo.txt')) click-reviewers-tools-0.44~16.04.1/clickreviews/tests/test_sr_lint.py0000664000000000000000000034511712744146674022464 0ustar '''test_sr_lint.py: tests for the sr_lint module''' # # Copyright (C) 2013-2016 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from unittest import TestCase import os import shutil import tempfile from clickreviews.common import cleanup_unpack from clickreviews.common import check_results as common_check_results from clickreviews.sr_lint import SnapReviewLint import clickreviews.sr_tests as sr_tests from clickreviews.tests import utils class TestSnapReviewLint(sr_tests.TestSnapReview): """Tests for the lint review tool.""" def setUp(self): '''Make sure we are snap v2''' super().setUp() self.set_test_pkgfmt("snap", "16.04") def _create_ports(self): ports = {'internal': {'int1': {"port": '8081/tcp', "negotiable": True}}, 'external': {'ext1': {"port": '80/tcp', "negotiable": False}, 'ext2': {"port": '88/udp'} } } return ports def _create_top_plugs(self): plugs = {'iface-content': {'interface': 'content', 'target': '/path/to/something'}, 'iface-network': {'interface': 'network'}, 'iface-network-bind': {'interface': 'network-bind'}, # intentionally omitted # 'iface-network-control': {'interface': 'network-control'}, } return plugs def _create_apps_plugs(self): plugs = {'app1': {'plugs': ['iface-network']}, 'app2': {'plugs': ['iface-network-bind']}, 'app3': {'plugs': ['network-control']}, 'app4': {'plugs': ['iface-content']}, } return plugs def _create_top_slots(self): slots = {'iface-bool-file': {'interface': 'bool-file', 'path': '/path/to/something'}, 'iface-content': {'interface': 'content', 'read': '/path/to/somewhere', 'write': '/path/to/somewhere/else'}, 'iface-serial-port': {'interface': 'serial-port', 'path': '/path/to/something'}, } return slots def _create_apps_slots(self): slots = {'app1': {'slots': ['iface-bool-file']}, 'app2': {'slots': ['iface-content']}, 'app3': {'slots': ['iface-serial-port']}, } return slots def test_all_checks_as_v2(self): '''Test snap v2 has checks''' self.set_test_pkgfmt("snap", "16.04") c = SnapReviewLint(self.test_name) c.do_checks() sum = 0 for i in c.click_report: sum += len(c.click_report[i]) self.assertTrue(sum != 0) def test_all_checks_as_v1(self): '''Test snap v1 has no checks''' self.set_test_pkgfmt("snap", "15.04") c = SnapReviewLint(self.test_name) c.do_checks() sum = 0 for i in c.click_report: sum += len(c.click_report[i]) self.assertTrue(sum == 0) def test_all_checks_as_click(self): '''Test click format has no checks''' self.set_test_pkgfmt("click", "0.4") c = SnapReviewLint(self.test_name) c.do_checks() sum = 0 for i in c.click_report: sum += len(c.click_report[i]) self.assertTrue(sum == 0) def test_check_name_toplevel(self): '''Test check_name - toplevel''' self.set_test_snap_yaml("name", "foo") c = SnapReviewLint(self.test_name) c.check_name() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_name_flat(self): '''Test check_name - obsoleted flat''' self.set_test_snap_yaml("name", "foo.bar") c = SnapReviewLint(self.test_name) c.check_name() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_name_reverse_domain(self): '''Test check_name - obsoleted reverse domain''' self.set_test_snap_yaml("name", "com.ubuntu.develeper.baz.foo") c = SnapReviewLint(self.test_name) c.check_name() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_name_bad(self): '''Test check_name - bad - ?''' self.set_test_snap_yaml("name", "foo?bar") c = SnapReviewLint(self.test_name) c.check_name() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_name_bad1(self): '''Test check_name - bad - /''' self.set_test_snap_yaml("name", "foo/bar") c = SnapReviewLint(self.test_name) c.check_name() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_name_bad2(self): '''Test check_name - empty''' self.set_test_snap_yaml("name", "") c = SnapReviewLint(self.test_name) c.check_name() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_name_bad3(self): '''Test check_name - list''' self.set_test_snap_yaml("name", []) c = SnapReviewLint(self.test_name) c.check_name() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_name_bad4(self): '''Test check_name - dict''' self.set_test_snap_yaml("name", {}) c = SnapReviewLint(self.test_name) c.check_name() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_name_bad5(self): '''Test check_name - bad - --''' self.set_test_snap_yaml("name", "foo--bar") c = SnapReviewLint(self.test_name) c.check_name() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_name_bad6(self): '''Test check_name - bad - endswith -''' self.set_test_snap_yaml("name", "foo-bar-") c = SnapReviewLint(self.test_name) c.check_name() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_name_bad7(self): '''Test check_name - bad - cap''' self.set_test_snap_yaml("name", "foo-Bar") c = SnapReviewLint(self.test_name) c.check_name() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_name_missing(self): '''Test check_name - missing''' self.set_test_snap_yaml("name", None) c = SnapReviewLint(self.test_name) c.check_name() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_version(self): '''Test check_version''' self.set_test_snap_yaml("version", 1) c = SnapReviewLint(self.test_name) c.check_version() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_version1(self): '''Test check_version - integer''' self.set_test_snap_yaml("version", 1) c = SnapReviewLint(self.test_name) c.check_version() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_version2(self): '''Test check_version - float''' self.set_test_snap_yaml("version", 1.0) c = SnapReviewLint(self.test_name) c.check_version() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_version3(self): '''Test check_version - MAJOR.MINOR.MICRO''' self.set_test_snap_yaml("version", "1.0.1") c = SnapReviewLint(self.test_name) c.check_version() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_version4(self): '''Test check_version - str''' self.set_test_snap_yaml("version", "1.0a") c = SnapReviewLint(self.test_name) c.check_version() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_version5(self): '''Test check_version - alpha''' self.set_test_snap_yaml("version", "a.b") c = SnapReviewLint(self.test_name) c.check_version() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_version_bad(self): '''Test check_version - bad''' self.set_test_snap_yaml("version", "foo?bar") c = SnapReviewLint(self.test_name) c.check_version() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_version_bad2(self): '''Test check_version - empty''' self.set_test_snap_yaml("version", "") c = SnapReviewLint(self.test_name) c.check_version() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_version_bad3(self): '''Test check_version - list''' self.set_test_snap_yaml("version", []) c = SnapReviewLint(self.test_name) c.check_version() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_version_bad4(self): '''Test check_version - dict''' self.set_test_snap_yaml("version", {}) c = SnapReviewLint(self.test_name) c.check_version() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_version_missing(self): '''Test check_version - missing''' self.set_test_snap_yaml("version", None) c = SnapReviewLint(self.test_name) c.check_version() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_type(self): '''Test check_type - unspecified''' self.set_test_snap_yaml("type", None) c = SnapReviewLint(self.test_name) c.check_type() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_type_app(self): '''Test check_type - app''' self.set_test_snap_yaml("type", "app") c = SnapReviewLint(self.test_name) c.check_type() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_type_framework(self): '''Test check_type - framework''' self.set_test_snap_yaml("type", "framework") c = SnapReviewLint(self.test_name) c.check_type() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_type_redflagged(self): '''Test check_type_redflagged - unspecified''' self.set_test_snap_yaml("type", None) c = SnapReviewLint(self.test_name) c.check_type_redflagged() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_type_redflagged_app(self): '''Test check_type_redflagged - app''' self.set_test_snap_yaml("type", "app") c = SnapReviewLint(self.test_name) c.check_type_redflagged() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_type_redflagged_gadget(self): '''Test check_type_redflagged - gadget''' self.set_test_snap_yaml("type", "gadget") c = SnapReviewLint(self.test_name) c.check_type_redflagged() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) name = c._get_check_name('snap_type_redflag') self.check_manual_review(r, name) def test_check_type_redflagged_kernel(self): '''Test check_type_redflagged - kernel''' self.set_test_snap_yaml("type", "kernel") c = SnapReviewLint(self.test_name) c.check_type_redflagged() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) name = c._get_check_name('snap_type_redflag') self.check_manual_review(r, name) def test_check_type_redflagged_os(self): '''Test check_type_redflagged - os''' self.set_test_snap_yaml("type", "os") c = SnapReviewLint(self.test_name) c.check_type_redflagged() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) name = c._get_check_name('snap_type_redflag') self.check_manual_review(r, name) def test_check_type_unknown(self): '''Test check_type - unknown''' self.set_test_snap_yaml("type", "nonexistent") c = SnapReviewLint(self.test_name) c.check_type() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_icon(self): '''Test check_icon()''' self.set_test_snap_yaml("icon", "someicon") self.set_test_snap_yaml("type", "gadget") self.set_test_unpack_dir = "/nonexistent" c = SnapReviewLint(self.test_name) c.pkg_files.append(os.path.join(c._get_unpack_dir(), 'someicon')) c.check_icon() r = c.click_report expected_counts = {'info': 4, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_icon_no_gadget(self): '''Test check_icon() - no gadget''' self.set_test_snap_yaml("icon", "someicon") self.set_test_unpack_dir = "/nonexistent" c = SnapReviewLint(self.test_name) c.pkg_files.append(os.path.join(c._get_unpack_dir(), 'someicon')) c.check_icon() r = c.click_report expected_counts = {'info': None, 'warn': 1, 'error': 0} self.check_results(r, expected_counts) def test_check_icon_unspecified(self): '''Test check_icon() - unspecified''' self.set_test_snap_yaml("icon", None) self.set_test_snap_yaml("type", "gadget") c = SnapReviewLint(self.test_name) c.check_icon() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_icon_empty(self): '''Test check_icon() - empty''' self.set_test_snap_yaml("icon", "") self.set_test_snap_yaml("type", "gadget") c = SnapReviewLint(self.test_name) c.check_icon() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_icon_absolute_path(self): '''Test check_icon() - absolute path''' self.set_test_snap_yaml("icon", "/foo/bar/someicon") self.set_test_snap_yaml("type", "gadget") c = SnapReviewLint(self.test_name) c.pkg_files.append('/foo/bar/someicon') c.check_icon() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_icon_missing(self): '''Test check_icon() - missing icon''' self.set_test_snap_yaml("icon", "someicon") self.set_test_snap_yaml("type", "gadget") self.set_test_unpack_dir = "/nonexistent" c = SnapReviewLint(self.test_name) # since the icon isn't in c.pkg_files, don't add it for this test # c.pkg_files.append(os.path.join(c._get_unpack_dir(), 'someicon')) c.check_icon() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_architectures_bad(self): '''Test check_architectures() - bad (dict)''' self.set_test_snap_yaml("architectures", {}) c = SnapReviewLint(self.test_name) c.check_architectures() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_architectures_missing(self): '''Test check_architectures() (missing)''' self.set_test_snap_yaml("architectures", None) c = SnapReviewLint(self.test_name) c.check_architectures() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_architectures_all(self): '''Test check_architectures() (all)''' self.set_test_snap_yaml("architectures", ["all"]) c = SnapReviewLint(self.test_name) c.check_architectures() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_architectures_single_armhf(self): '''Test check_architectures() (single arch, armhf)''' self.set_test_snap_yaml("architectures", ["armhf"]) c = SnapReviewLint(self.test_name) c.check_architectures() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_architectures_single_arm64(self): '''Test check_architectures() (single arch, arm64)''' self.set_test_snap_yaml("architectures", ["arm64"]) c = SnapReviewLint(self.test_name) c.check_architectures() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_architectures_single_i386(self): '''Test check_architectures() (single arch, i386)''' self.set_test_snap_yaml("architectures", ["i386"]) c = SnapReviewLint(self.test_name) c.check_architectures() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_architectures_single_amd64(self): '''Test check_architectures() (single arch, amd64)''' self.set_test_snap_yaml("architectures", ["amd64"]) c = SnapReviewLint(self.test_name) c.check_architectures() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_architectures_single_s390x(self): '''Test check_architectures() (single arch, s390x)''' self.set_test_snap_yaml("architectures", ["s390x"]) c = SnapReviewLint(self.test_name) c.check_architectures() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_architectures_single_ppc64el(self): '''Test check_architectures() (single arch, ppc64el)''' self.set_test_snap_yaml("architectures", ["ppc64el"]) c = SnapReviewLint(self.test_name) c.check_architectures() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_architectures_single_nonexistent(self): '''Test check_architectures() (single nonexistent arch)''' self.set_test_snap_yaml("architectures", ["nonexistent"]) c = SnapReviewLint(self.test_name) c.check_architectures() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_snappy_valid_arch_multi(self): '''Test check_architectures() (valid multi)''' self.set_test_snap_yaml("architectures", ["amd64", "armhf"]) c = SnapReviewLint(self.test_name) c.check_architectures() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_snappy_valid_arch_multi2(self): '''Test check_architectures() (valid multi2)''' self.set_test_snap_yaml("architectures", ["armhf", "arm64", "i386"]) c = SnapReviewLint(self.test_name) c.check_architectures() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_unknown_entries(self): '''Test check_unknown_entries - none''' c = SnapReviewLint(self.test_name) c.check_unknown_entries() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_unknown_entries2(self): '''Test check_unknown_entries - one''' self.set_test_snap_yaml("nonexistent", "bar") c = SnapReviewLint(self.test_name) c.check_unknown_entries() r = c.click_report expected_counts = {'info': 0, 'warn': 1, 'error': 0} self.check_results(r, expected_counts) def test_check_config(self): '''Test check_config()''' c = SnapReviewLint(self.test_name) self.set_test_unpack_dir("/nonexistent") c.pkg_files.append(os.path.join(c._get_unpack_dir(), 'meta/hooks/config')) c.check_config() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_config_nonexecutable(self): '''Test check_config() - not executable''' c = SnapReviewLint(self.test_name) self.set_test_unpack_dir("/nonexistent.nonexec") c.pkg_files.append(os.path.join(c._get_unpack_dir(), 'meta/hooks/config')) c.check_config() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_description(self): '''Test check_description''' self.set_test_snap_yaml("description", "This is a test description") c = SnapReviewLint(self.test_name) c.check_description() r = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_description_missing(self): '''Test check_description - not present''' self.set_test_snap_yaml("description", None) c = SnapReviewLint(self.test_name) c.check_description() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_description_bad(self): '''Test check_description - short''' self.set_test_snap_yaml("description", "a") c = SnapReviewLint(self.test_name) c.check_description() r = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_description_bad2(self): '''Test check_description - empty''' self.set_test_snap_yaml("description", "") c = SnapReviewLint(self.test_name) c.check_description() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_description_bad3(self): '''Test check_description - list''' self.set_test_snap_yaml("description", []) c = SnapReviewLint(self.test_name) c.check_description() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_license_agreement(self): '''Test check_license_agreement''' self.set_test_snap_yaml("license-agreement", "This is a test license_agreement") c = SnapReviewLint(self.test_name) c.check_license_agreement() r = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_license_agreement_missing(self): '''Test check_license_agreement - not present''' self.set_test_snap_yaml("license-agreement", None) c = SnapReviewLint(self.test_name) c.check_license_agreement() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_license_agreement_bad(self): '''Test check_license_agreement - empty''' self.set_test_snap_yaml("license-agreement", "") c = SnapReviewLint(self.test_name) c.check_license_agreement() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_license_agreement_bad2(self): '''Test check_license_agreement - list''' self.set_test_snap_yaml("license-agreement", []) c = SnapReviewLint(self.test_name) c.check_license_agreement() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_license_version(self): '''Test check_license_version''' self.set_test_snap_yaml("license-version", "This is a test license_version") c = SnapReviewLint(self.test_name) c.check_license_version() r = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_license_version_missing(self): '''Test check_license_version - not present''' self.set_test_snap_yaml("license-version", None) c = SnapReviewLint(self.test_name) c.check_license_version() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_license_version_bad(self): '''Test check_license_version - empty''' self.set_test_snap_yaml("license-version", "") c = SnapReviewLint(self.test_name) c.check_license_version() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_license_version_bad2(self): '''Test check_license_version - list''' self.set_test_snap_yaml("license-version", []) c = SnapReviewLint(self.test_name) c.check_license_version() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_summary(self): '''Test check_summary''' self.set_test_snap_yaml("summary", "This is a test summary") c = SnapReviewLint(self.test_name) c.check_summary() r = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_summary_missing(self): '''Test check_summary - not present''' self.set_test_snap_yaml("summary", None) c = SnapReviewLint(self.test_name) c.check_summary() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_summary_bad(self): '''Test check_summary - short''' self.set_test_snap_yaml("summary", "a") c = SnapReviewLint(self.test_name) c.check_summary() r = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_summary_bad2(self): '''Test check_summary - empty''' self.set_test_snap_yaml("summary", "") c = SnapReviewLint(self.test_name) c.check_summary() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_summary_bad3(self): '''Test check_summary - list''' self.set_test_snap_yaml("summary", []) c = SnapReviewLint(self.test_name) c.check_summary() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_one_command(self): '''Test check_apps() - one command''' self.set_test_snap_yaml("apps", {"foo": {"command": "bin/foo"}}) c = SnapReviewLint(self.test_name) c.check_apps() r = c.click_report expected_counts = {'info': 5, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_apps_one_command_capitalized(self): '''Test check_apps() - one command (capitalized)''' self.set_test_snap_yaml("apps", {"Fo0-Bar": {"command": "bin/foo"}}) c = SnapReviewLint(self.test_name) c.check_apps() r = c.click_report expected_counts = {'info': 5, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_apps_one_daemon(self): '''Test check_apps() - one daemon''' self.set_test_snap_yaml("apps", {"foo": {"command": "bin/foo", "daemon": "single"}, }) c = SnapReviewLint(self.test_name) c.check_apps() r = c.click_report expected_counts = {'info': 5, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_apps_two_commands(self): '''Test check_apps() - two commands''' self.set_test_snap_yaml("apps", {"foo": {"command": "bin/foo"}, "bar": {"command": "bin/bar"}, }) c = SnapReviewLint(self.test_name) c.check_apps() r = c.click_report expected_counts = {'info': 8, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_apps_command_plus_daemon(self): '''Test check_apps() - command and daemon''' self.set_test_snap_yaml("apps", {"foo": {"command": "bin/foo"}, "bar": {"command": "bin/bar", "daemon": "single"}, }) c = SnapReviewLint(self.test_name) c.check_apps() r = c.click_report expected_counts = {'info': 8, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_apps_two_daemons(self): '''Test check_apps() - command and daemon''' self.set_test_snap_yaml("apps", {"foo": {"command": "bin/foo", "daemon": "single"}, "bar": {"command": "bin/bar", "daemon": "single"}, }) c = SnapReviewLint(self.test_name) c.check_apps() r = c.click_report expected_counts = {'info': 8, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_apps_missing(self): '''Test check_apps() - missing''' self.set_test_snap_yaml("apps", None) c = SnapReviewLint(self.test_name) c.check_apps() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_apps_bad(self): '''Test check_apps() - bad''' self.set_test_snap_yaml("apps", []) c = SnapReviewLint(self.test_name) c.check_apps() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_bad2(self): '''Test check_apps() - empty''' self.set_test_snap_yaml("apps", {}) c = SnapReviewLint(self.test_name) c.check_apps() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_bad3(self): '''Test check_apps() - missing command''' self.set_test_snap_yaml("apps", {"foo": {"daemon": "single"}, }) c = SnapReviewLint(self.test_name) c.check_apps() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_bad4(self): '''Test check_apps() - unknown field''' self.set_test_snap_yaml("apps", {"foo": {"command": "bin/foo", "nonexistent": "abc"}, }) c = SnapReviewLint(self.test_name) c.check_apps() r = c.click_report expected_counts = {'info': None, 'warn': 1, 'error': 0} self.check_results(r, expected_counts) def test_check_apps_bad5(self): '''Test check_apps() - invalid field''' self.set_test_snap_yaml("apps", {"foo": []}) c = SnapReviewLint(self.test_name) c.check_apps() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_bad6(self): '''Test check_apps() - empty fields''' self.set_test_snap_yaml("apps", {"foo": {}}) c = SnapReviewLint(self.test_name) c.check_apps() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_bad7(self): '''Test check_apps() - unknown field (bus-name)''' self.set_test_snap_yaml("apps", {"foo": {"command": "bin/foo", "bus-name": "foo"}, }) c = SnapReviewLint(self.test_name) c.check_apps() r = c.click_report expected_counts = {'info': None, 'warn': 1, 'error': 0} self.check_results(r, expected_counts) def test_check_apps_bad8(self): '''Test check_apps() - bad name with .''' self.set_test_snap_yaml("apps", {"foo.bar": {"command": "bin/foo"}}) c = SnapReviewLint(self.test_name) c.check_apps() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_bad9(self): '''Test check_apps() - bad name with _''' self.set_test_snap_yaml("apps", {"foo_bar": {"command": "bin/foo"}}) c = SnapReviewLint(self.test_name) c.check_apps() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_bad10(self): '''Test check_apps() - bad name with /''' self.set_test_snap_yaml("apps", {"foo/bar": {"command": "bin/foo"}}) c = SnapReviewLint(self.test_name) c.check_apps() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_bad11(self): '''Test check_apps() - bad name ends with -''' self.set_test_snap_yaml("apps", {"foo-bar-": {"command": "bin/foo"}}) c = SnapReviewLint(self.test_name) c.check_apps() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_bad12(self): '''Test check_apps() - bad name with --''' self.set_test_snap_yaml("apps", {"foo--bar": {"command": "bin/foo"}}) c = SnapReviewLint(self.test_name) c.check_apps() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_command(self): '''Test check_apps_command()''' cmd = "bin/foo" self.set_test_snap_yaml("apps", {"foo": {"command": cmd}, }) c = SnapReviewLint(self.test_name) c.pkg_files.append(os.path.join('/fake', cmd)) c.check_apps_command() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_apps_command_dotslash(self): '''Test check_apps_command() - starts with ./''' cmd = "bin/foo" self.set_test_snap_yaml("apps", {"foo": {"command": "./" + cmd}, }) c = SnapReviewLint(self.test_name) c.pkg_files.append(os.path.join('/fake', cmd)) c.check_apps_command() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_apps_command_missing(self): '''Test check_apps_command() - missing''' self.set_test_snap_yaml("apps", {"foo": {}}) c = SnapReviewLint(self.test_name) c.check_apps_command() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_apps_command_empty(self): '''Test check_apps_command() - empty''' self.set_test_snap_yaml("apps", {"foo": {"command": ""}, }) c = SnapReviewLint(self.test_name) c.check_apps_command() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_command_invalid(self): '''Test check_apps_command() - list''' self.set_test_snap_yaml("apps", {"foo": {"command": []}, }) c = SnapReviewLint(self.test_name) c.check_apps_command() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_command_nonexistent(self): '''Test check_apps_command() - nonexistent''' cmd = "bin/foo" self.set_test_snap_yaml("apps", {"foo": {"command": cmd}, }) c = SnapReviewLint(self.test_name) c.check_apps_command() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_stop_command(self): '''Test check_apps_stop_command()''' cmd = "bin/foo" self.set_test_snap_yaml("apps", {"foo": {"stop-command": cmd}, }) c = SnapReviewLint(self.test_name) c.pkg_files.append(os.path.join('/fake', cmd)) c.check_apps_stop_command() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_apps_stop_command_missing(self): '''Test check_apps_stop_command() - missing''' self.set_test_snap_yaml("apps", {"foo": {}}) c = SnapReviewLint(self.test_name) c.check_apps_stop_command() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_apps_stop_command_empty(self): '''Test check_apps_stop_command() - empty''' self.set_test_snap_yaml("apps", {"foo": {"stop-command": ""}, }) c = SnapReviewLint(self.test_name) c.check_apps_stop_command() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_stop_command_invalid(self): '''Test check_apps_stop_command() - list''' self.set_test_snap_yaml("apps", {"foo": {"stop-command": []}, }) c = SnapReviewLint(self.test_name) c.check_apps_stop_command() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_stop_command_nonexistent(self): '''Test check_apps_stop_command() - nonexistent''' cmd = "bin/foo" self.set_test_snap_yaml("apps", {"foo": {"stop-command": cmd}, }) c = SnapReviewLint(self.test_name) c.check_apps_stop_command() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_post_stop_command(self): '''Test check_apps_post_stop_command()''' cmd = "bin/foo" self.set_test_snap_yaml("apps", {"foo": {"post-stop-command": cmd}, }) c = SnapReviewLint(self.test_name) c.pkg_files.append(os.path.join('/fake', cmd)) c.check_apps_post_stop_command() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_apps_post_stop_command_missing(self): '''Test check_apps_post_stop_command() - missing''' self.set_test_snap_yaml("apps", {"foo": {}}) c = SnapReviewLint(self.test_name) c.check_apps_post_stop_command() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_apps_post_stop_command_empty(self): '''Test check_apps_post_stop_command() - empty''' self.set_test_snap_yaml("apps", {"foo": {"post-stop-command": ""}, }) c = SnapReviewLint(self.test_name) c.check_apps_post_stop_command() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_post_stop_command_invalid(self): '''Test check_apps_post_stop_command() - list''' self.set_test_snap_yaml("apps", {"foo": {"post-stop-command": []}, }) c = SnapReviewLint(self.test_name) c.check_apps_post_stop_command() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_post_stop_command_nonexistent(self): '''Test check_apps_post_stop_command() - nonexistent''' cmd = "bin/foo" self.set_test_snap_yaml("apps", {"foo": {"post-stop-command": cmd}, }) c = SnapReviewLint(self.test_name) c.check_apps_post_stop_command() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_daemon_simple(self): '''Test check_apps_daemon() - simple''' entry = "simple" self.set_test_snap_yaml("apps", {"foo": {"daemon": entry}}) c = SnapReviewLint(self.test_name) c.check_apps_daemon() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_apps_daemon_forking(self): '''Test check_apps_daemon() - forking''' entry = "forking" self.set_test_snap_yaml("apps", {"foo": {"daemon": entry}}) c = SnapReviewLint(self.test_name) c.check_apps_daemon() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_apps_daemon_oneshot(self): '''Test check_apps_daemon() - oneshot''' entry = "oneshot" self.set_test_snap_yaml("apps", {"foo": {"daemon": entry}}) c = SnapReviewLint(self.test_name) c.check_apps_daemon() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_apps_daemon_dbus(self): '''Test check_apps_daemon() - dbus''' entry = "dbus" self.set_test_snap_yaml("apps", {"foo": {"daemon": entry}}) c = SnapReviewLint(self.test_name) c.check_apps_daemon() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_apps_daemon_missing(self): '''Test check_apps_daemon() - missing''' self.set_test_snap_yaml("apps", {"foo": {}}) c = SnapReviewLint(self.test_name) c.check_apps_daemon() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_apps_daemon_empty(self): '''Test check_apps_daemon() - empty''' self.set_test_snap_yaml("apps", {"foo": {"daemon": ""}, }) c = SnapReviewLint(self.test_name) c.check_apps_daemon() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_daemon_invalid(self): '''Test check_apps_daemon() - list''' self.set_test_snap_yaml("apps", {"foo": {"daemon": []}, }) c = SnapReviewLint(self.test_name) c.check_apps_daemon() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_daemon_nonexistent(self): '''Test check_apps_daemon() - nonexistent''' entry = "nonexistent" self.set_test_snap_yaml("apps", {"foo": {"daemon": entry}}) c = SnapReviewLint(self.test_name) c.check_apps_daemon() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_nondaemon(self): '''Test check_apps_nondaemon()''' entry = "simple" self.set_test_snap_yaml("apps", {"foo": {"daemon": entry, "stop-command": "bin/bar"}}) c = SnapReviewLint(self.test_name) c.check_apps_nondaemon() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_apps_nondaemon_command(self): '''Test check_apps_nondaemon() - command''' self.set_test_snap_yaml("apps", {"foo": {"command": "bin/bar"}}) c = SnapReviewLint(self.test_name) c.check_apps_nondaemon() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_apps_nondaemon_plugs(self): '''Test check_apps_nondaemon() - plugs''' self.set_test_snap_yaml("apps", {"foo": {"command": "bin/bar", "plugs": {}}}) c = SnapReviewLint(self.test_name) c.check_apps_nondaemon() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_apps_nondaemon_stop(self): '''Test check_apps_nondaemon() - stop''' self.set_test_snap_yaml("apps", {"foo": {"command": "bin/bar", "stop-command": "bin/bar"}}) c = SnapReviewLint(self.test_name) c.check_apps_nondaemon() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_nondaemon_stop_timeout(self): '''Test check_apps_nondaemon() - stop-timeout''' self.set_test_snap_yaml("apps", {"foo": {"command": "bin/bar", "stop-timeout": 60}}) c = SnapReviewLint(self.test_name) c.check_apps_nondaemon() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_nondaemon_restart_condition(self): '''Test check_apps_nondaemon() - restart-condition''' self.set_test_snap_yaml("apps", {"foo": {"command": "bin/bar", "restart-condition": "never"}}) c = SnapReviewLint(self.test_name) c.check_apps_nondaemon() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_nondaemon_post_stop_command(self): '''Test check_apps_nondaemon() - post-stop-command''' self.set_test_snap_yaml("apps", {"foo": {"command": "bin/bar", "post-stop-command": "bin/bar"}}) c = SnapReviewLint(self.test_name) c.check_apps_nondaemon() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_nondaemon_ports(self): '''Test check_apps_nondaemon() - ports''' ports = self._create_ports() self.set_test_snap_yaml("apps", {"foo": {"command": "bin/bar", "ports": ports}}) c = SnapReviewLint(self.test_name) c.check_apps_nondaemon() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_nondaemon_socket(self): '''Test check_apps_nondaemon() - socket''' self.set_test_snap_yaml("apps", {"foo": {"command": "bin/bar", "socket": True}}) c = SnapReviewLint(self.test_name) c.check_apps_nondaemon() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_nondaemon_listen_stream(self): '''Test check_apps_nondaemon() - listen-stream''' self.set_test_snap_yaml("apps", {"foo": {"command": "bin/bar", "listen-stream": "@bar"}}) c = SnapReviewLint(self.test_name) c.check_apps_nondaemon() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_nondaemon_socket_user(self): '''Test check_apps_nondaemon() - socket-user''' self.set_test_snap_yaml("apps", {"foo": {"command": "bin/bar", "socket-user": "docker"}}) c = SnapReviewLint(self.test_name) c.check_apps_nondaemon() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_nondaemon_socket_group(self): '''Test check_apps_nondaemon() - socket-group''' self.set_test_snap_yaml("apps", {"foo": {"command": "bin/bar", "socket-group": "docker"}}) c = SnapReviewLint(self.test_name) c.check_apps_nondaemon() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_restart_condition_always(self): '''Test check_apps_restart-condition() - always''' entry = "always" self.set_test_snap_yaml("apps", {"foo": {"restart-condition": entry}}) c = SnapReviewLint(self.test_name) c.check_apps_restart_condition() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_apps_restart_condition_never(self): '''Test check_apps_restart-condition() - never''' entry = "never" self.set_test_snap_yaml("apps", {"foo": {"restart-condition": entry}}) c = SnapReviewLint(self.test_name) c.check_apps_restart_condition() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_apps_restart_condition_on_abnormal(self): '''Test check_apps_restart-condition() - on-abnormal''' entry = "on-abnormal" self.set_test_snap_yaml("apps", {"foo": {"restart-condition": entry}}) c = SnapReviewLint(self.test_name) c.check_apps_restart_condition() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_apps_restart_condition_on_abort(self): '''Test check_apps_restart-condition() - on-abort''' entry = "on-abort" self.set_test_snap_yaml("apps", {"foo": {"restart-condition": entry}}) c = SnapReviewLint(self.test_name) c.check_apps_restart_condition() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_apps_restart_condition_on_failure(self): '''Test check_apps_restart-condition() - on-failure''' entry = "on-failure" self.set_test_snap_yaml("apps", {"foo": {"restart-condition": entry}}) c = SnapReviewLint(self.test_name) c.check_apps_restart_condition() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_apps_restart_condition_on_success(self): '''Test check_apps_restart-condition() - on-success''' entry = "on-success" self.set_test_snap_yaml("apps", {"foo": {"restart-condition": entry}}) c = SnapReviewLint(self.test_name) c.check_apps_restart_condition() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_apps_restart_condition_missing(self): '''Test check_apps_restart-condition() - missing''' self.set_test_snap_yaml("apps", {"foo": {}}) c = SnapReviewLint(self.test_name) c.check_apps_restart_condition() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_apps_restart_condition_empty(self): '''Test check_apps_restart-condition() - empty''' self.set_test_snap_yaml("apps", {"foo": {"restart-condition": ""}, }) c = SnapReviewLint(self.test_name) c.check_apps_restart_condition() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_restart_condition_invalid(self): '''Test check_apps_restart-condition() - list''' self.set_test_snap_yaml("apps", {"foo": {"restart-condition": []}, }) c = SnapReviewLint(self.test_name) c.check_apps_restart_condition() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_restart_condition_nonexistent(self): '''Test check_apps_restart-condition() - nonexistent''' entry = "nonexistent" self.set_test_snap_yaml("apps", {"foo": {"restart-condition": entry}}) c = SnapReviewLint(self.test_name) c.check_apps_restart_condition() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_ports(self): '''Test check_apps_ports()''' ports = self._create_ports() self.set_test_snap_yaml("apps", {"bar": {"ports": ports}}) c = SnapReviewLint(self.test_name) c.check_apps_ports() r = c.click_report expected_counts = {'info': 7, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_apps_ports_internal(self): '''Test check_apps_ports() - internal''' ports = self._create_ports() del ports['external'] self.set_test_snap_yaml("apps", {"bar": {"ports": ports}}) c = SnapReviewLint(self.test_name) c.check_apps_ports() r = c.click_report expected_counts = {'info': 3, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_apps_ports_external(self): '''Test check_apps_ports() - external''' ports = self._create_ports() del ports['internal'] self.set_test_snap_yaml("apps", {"bar": {"ports": ports}}) c = SnapReviewLint(self.test_name) c.check_apps_ports() r = c.click_report expected_counts = {'info': 5, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_apps_ports_empty(self): '''Test check_apps_ports() - empty''' self.set_test_snap_yaml("apps", {"bar": {"ports": {}}}) c = SnapReviewLint(self.test_name) c.check_apps_ports() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_ports_invalid(self): '''Test check_apps_ports() - invalid''' self.set_test_snap_yaml("apps", {"bar": {"ports": []}}) c = SnapReviewLint(self.test_name) c.check_apps_ports() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_ports_bad_key(self): '''Test check_apps_ports() - bad key''' ports = self._create_ports() ports['xternal'] = ports['external'] del ports['external'] self.set_test_snap_yaml("apps", {"bar": {"ports": ports}}) c = SnapReviewLint(self.test_name) c.check_apps_ports() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_ports_missing_internal(self): '''Test check_apps_ports() - missing internal''' ports = self._create_ports() del ports['internal']['int1'] self.set_test_snap_yaml("apps", {"bar": {"ports": ports}}) c = SnapReviewLint(self.test_name) c.check_apps_ports() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_ports_missing_external(self): '''Test check_apps_ports() - missing external''' ports = self._create_ports() del ports['external']['ext1'] del ports['external']['ext2'] self.set_test_snap_yaml("apps", {"bar": {"ports": ports}}) c = SnapReviewLint(self.test_name) c.check_apps_ports() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_ports_missing_external_subkey(self): '''Test check_apps_ports() - missing external subkey''' ports = self._create_ports() del ports['external']['ext2']['port'] self.set_test_snap_yaml("apps", {"bar": {"ports": ports}}) c = SnapReviewLint(self.test_name) c.check_apps_ports() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_ports_missing_internal_subkey(self): '''Test check_apps_ports() - missing internal subkey''' ports = self._create_ports() del ports['internal']['int1']['port'] del ports['internal']['int1']['negotiable'] self.set_test_snap_yaml("apps", {"bar": {"ports": ports}}) c = SnapReviewLint(self.test_name) c.check_apps_ports() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_ports_missing_internal_port_subkey(self): '''Test check_apps_ports() - missing internal port subkey''' ports = self._create_ports() del ports['internal']['int1']['port'] self.set_test_snap_yaml("apps", {"bar": {"ports": ports}}) c = SnapReviewLint(self.test_name) c.check_apps_ports() r = c.click_report expected_counts = {'info': 7, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_apps_ports_invalid_internal_subkey(self): '''Test check_apps_ports() - invalid internal subkey''' ports = self._create_ports() ports['internal']['int1']['prt'] = ports['internal']['int1']['port'] self.set_test_snap_yaml("apps", {"bar": {"ports": ports}}) c = SnapReviewLint(self.test_name) c.check_apps_ports() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_ports_invalid_internal_port(self): '''Test check_apps_ports() - invalid internal port''' ports = self._create_ports() ports['internal']['int1']['port'] = "bad/8080" self.set_test_snap_yaml("apps", {"bar": {"ports": ports}}) c = SnapReviewLint(self.test_name) c.check_apps_ports() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_ports_invalid_external_port(self): '''Test check_apps_ports() - invalid external port''' ports = self._create_ports() ports['external']['ext2']['port'] = [] self.set_test_snap_yaml("apps", {"bar": {"ports": ports}}) c = SnapReviewLint(self.test_name) c.check_apps_ports() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_ports_invalid_internal_low_port(self): '''Test check_apps_ports() - invalid internal low port''' ports = self._create_ports() ports['internal']['int1']['port'] = "0/tcp" self.set_test_snap_yaml("apps", {"bar": {"ports": ports}}) c = SnapReviewLint(self.test_name) c.check_apps_ports() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_ports_invalid_internal_high_port(self): '''Test check_apps_ports() - invalid internal high port''' ports = self._create_ports() ports['internal']['int1']['port'] = "65536/tcp" self.set_test_snap_yaml("apps", {"bar": {"ports": ports}}) c = SnapReviewLint(self.test_name) c.check_apps_ports() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_ports_invalid_internal_negotiable(self): '''Test check_apps_ports() - invalid internal negotiable''' ports = self._create_ports() ports['internal']['int1']['negotiable'] = -99999999 self.set_test_snap_yaml("apps", {"bar": {"ports": ports}}) c = SnapReviewLint(self.test_name) c.check_apps_ports() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_ports_invalid_internal_negotiable2(self): '''Test check_apps_ports() - invalid internal negotiable''' ports = self._create_ports() ports['internal']['int1']['negotiable'] = [] self.set_test_snap_yaml("apps", {"bar": {"ports": ports}}) c = SnapReviewLint(self.test_name) c.check_apps_ports() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_stop_timeout(self): '''Test check_apps_stop_timeout()''' self.set_test_snap_yaml("apps", {"bar": {"stop-timeout": 30}}) c = SnapReviewLint(self.test_name) c.check_apps_stop_timeout() r = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_apps_stop_timeout_nonexistent(self): '''Test check_apps_stop_timeout_nonexistent()''' self.set_test_snap_yaml("apps", {"bar": {}}) c = SnapReviewLint(self.test_name) c.check_apps_stop_timeout() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_apps_stop_timeout_granularity(self): '''Test check_apps_stop_timeout()''' self.set_test_snap_yaml("apps", {"bar": {"stop-timeout": '30s'}}) c = SnapReviewLint(self.test_name) c.check_apps_stop_timeout() r = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_apps_stop_timeout_empty(self): '''Test check_apps_stop_timeout() - empty''' self.set_test_snap_yaml("apps", {"bar": {"stop-timeout": ''}}) c = SnapReviewLint(self.test_name) c.check_apps_stop_timeout() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_stop_timeout_bad(self): '''Test check_apps_stop_timeout() - bad''' self.set_test_snap_yaml("apps", {"bar": {"stop-timeout": 'a'}}) c = SnapReviewLint(self.test_name) c.check_apps_stop_timeout() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_stop_timeout_bad2(self): '''Test check_apps_stop_timeout() - bad (list)''' self.set_test_snap_yaml("apps", {"bar": {"stop-timeout": []}}) c = SnapReviewLint(self.test_name) c.check_apps_stop_timeout() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_stop_timeout_bad_granularity(self): '''Test check_apps_stop_timeout() - bad with granularity''' self.set_test_snap_yaml("apps", {"bar": {"stop-timeout": '30a'}}) c = SnapReviewLint(self.test_name) c.check_apps_stop_timeout() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_stop_timeout_range_low(self): '''Test check_apps_stop_timeout() - out of range (low)''' self.set_test_snap_yaml("apps", {"bar": {"stop-timeout": -1}}) c = SnapReviewLint(self.test_name) c.check_apps_stop_timeout() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_stop_timeout_range_high(self): '''Test check_apps_stop_timeout() - out of range (high)''' self.set_test_snap_yaml("apps", {"bar": {"stop-timeout": 61}}) c = SnapReviewLint(self.test_name) c.check_apps_stop_timeout() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_socket(self): '''Test check_apps_socket()''' name = self.test_snap_yaml['name'] self.set_test_snap_yaml("apps", {"bar": {"socket": True, "listen-stream": "@%s" % name}}) c = SnapReviewLint(self.test_name) c.check_apps_socket() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_apps_listen_stream_path(self): '''Test check_apps_listen_stream() - path''' name = self.test_snap_yaml['name'] self.set_test_snap_yaml("apps", {"bar": {"listen-stream": "/tmp/%s" % name}}) c = SnapReviewLint(self.test_name) c.check_apps_listen_stream() r = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_apps_socket_no_listen_stream(self): '''Test check_apps_socket() - missing listen-stream''' self.set_test_snap_yaml("apps", {"bar": {"socket": True}}) c = SnapReviewLint(self.test_name) c.check_apps_socket() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_socket_bad(self): '''Test check_apps_socket() - bad''' self.set_test_snap_yaml("apps", {"bar": {"socket": ""}}) c = SnapReviewLint(self.test_name) c.check_apps_socket() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_socket_nonexistent(self): '''Test check_apps_socket() - nonexistent''' self.set_test_snap_yaml("apps", {"bar": {}}) c = SnapReviewLint(self.test_name) c.check_apps_socket() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_apps_listen_stream_abspkgname(self): '''Test check_apps_listen_stream() - @pkgname''' name = self.test_snap_yaml['name'] self.set_test_snap_yaml("apps", {"bar": {"listen-stream": "@%s" % name}}) c = SnapReviewLint(self.test_name) c.check_apps_listen_stream() r = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_apps_listen_stream_abspkgname2(self): '''Test check_apps_listen_stream() - @pkgname_''' name = self.test_snap_yaml['name'] self.set_test_snap_yaml("apps", {"bar": {"listen-stream": "@%s_something" % name}}) c = SnapReviewLint(self.test_name) c.check_apps_listen_stream() r = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_apps_listen_stream_nonexistent(self): '''Test check_apps_listen_stream() - nonexistent''' self.set_test_snap_yaml("apps", {"bar": {}}) c = SnapReviewLint(self.test_name) c.check_apps_listen_stream() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_apps_listen_stream_bad(self): '''Test check_apps_listen_stream() - bad (list)''' self.set_test_snap_yaml("apps", {"bar": {"listen-stream": []}}) c = SnapReviewLint(self.test_name) c.check_apps_listen_stream() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_listen_stream_bad_abstract(self): '''Test check_apps_listen_stream() - bad (wrong name)''' name = self.test_snap_yaml['name'] self.set_test_snap_yaml("apps", {"bar": {"listen-stream": "@%s/nomatch" % name}}) c = SnapReviewLint(self.test_name) c.check_apps_listen_stream() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_listen_stream_bad_relative(self): '''Test check_apps_listen_stream() - bad (not / or @)''' name = self.test_snap_yaml['name'] self.set_test_snap_yaml("apps", {"bar": {"listen-stream": name}}) c = SnapReviewLint(self.test_name) c.check_apps_listen_stream() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_listen_stream_bad_path(self): '''Test check_apps_listen_stream() - bad path''' name = self.test_snap_yaml['name'] self.set_test_snap_yaml("apps", {"bar": {"listen-stream": "/var/log/%s" % name}}) c = SnapReviewLint(self.test_name) c.check_apps_listen_stream() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_listen_stream_empty(self): '''Test check_apps_listen_stream() - empty''' self.set_test_snap_yaml("apps", {"bar": {"listen-stream": ""}}) c = SnapReviewLint(self.test_name) c.check_apps_listen_stream() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_socket_user(self): '''Test check_apps_socket_user()''' name = self.test_snap_yaml['name'] self.set_test_snap_yaml("apps", {"bar": {"socket-user": name, "listen-stream": "@%s" % name}}) c = SnapReviewLint(self.test_name) c.check_apps_socket_user() r = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_socket_user_no_listen_stream(self): '''Test check_apps_socket_user() - missing listen-stream''' name = self.test_snap_yaml['name'] self.set_test_snap_yaml("apps", {"bar": {"socket-user": name}}) c = SnapReviewLint(self.test_name) c.check_apps_socket_user() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_socket_user_bad(self): '''Test check_apps_socket_user() - bad user''' name = self.test_snap_yaml['name'] self.set_test_snap_yaml("apps", {"bar": {"socket-user": name + "-no", "listen-stream": "@%s" % name}}) c = SnapReviewLint(self.test_name) c.check_apps_socket_user() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 2} self.check_results(r, expected_counts) def test_check_apps_socket_user_bad2(self): '''Test check_apps_socket_user() - bad (list)''' name = self.test_snap_yaml['name'] self.set_test_snap_yaml("apps", {"bar": {"socket-user": [], "listen-stream": "@%s" % name}}) c = SnapReviewLint(self.test_name) c.check_apps_socket_user() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_socket_user_empty(self): '''Test check_apps_socket_user() - empty''' name = self.test_snap_yaml['name'] self.set_test_snap_yaml("apps", {"bar": {"socket-user": "", "listen-stream": "@%s" % name}}) c = SnapReviewLint(self.test_name) c.check_apps_socket_user() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_socket_user_nonexistent(self): '''Test check_apps_socket_user() - nonexistent''' self.set_test_snap_yaml("apps", {"bar": {}}) c = SnapReviewLint(self.test_name) c.check_apps_socket_user() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_apps_socket_group(self): '''Test check_apps_socket_group()''' name = self.test_snap_yaml['name'] self.set_test_snap_yaml("apps", {"bar": {"socket-group": name, "listen-stream": "@%s" % name}}) c = SnapReviewLint(self.test_name) c.check_apps_socket_group() r = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_socket_group_no_listen_stream(self): '''Test check_apps_socket_group() - missing listen-stream''' name = self.test_snap_yaml['name'] self.set_test_snap_yaml("apps", {"bar": {"socket-group": name}}) c = SnapReviewLint(self.test_name) c.check_apps_socket_group() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_socket_group_bad(self): '''Test check_apps_socket_group() - bad group''' name = self.test_snap_yaml['name'] self.set_test_snap_yaml("apps", {"bar": {"socket-group": name + "-no", "listen-stream": "@%s" % name}}) c = SnapReviewLint(self.test_name) c.check_apps_socket_group() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 2} self.check_results(r, expected_counts) def test_check_apps_socket_group_bad2(self): '''Test check_apps_socket_group() - bad (list)''' name = self.test_snap_yaml['name'] self.set_test_snap_yaml("apps", {"bar": {"socket-group": [], "listen-stream": "@%s" % name}}) c = SnapReviewLint(self.test_name) c.check_apps_socket_group() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_socket_group_empty(self): '''Test check_apps_socket_group() - empty''' name = self.test_snap_yaml['name'] self.set_test_snap_yaml("apps", {"bar": {"socket-group": "", "listen-stream": "@%s" % name}}) c = SnapReviewLint(self.test_name) c.check_apps_socket_group() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_socket_group_nonexistent(self): '''Test check_apps_socket_group() - nonexistent''' self.set_test_snap_yaml("apps", {"bar": {}}) c = SnapReviewLint(self.test_name) c.check_apps_socket_group() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_plugs(self): '''Test check_plugs()''' plugs = self._create_top_plugs() self.set_test_snap_yaml("plugs", plugs) c = SnapReviewLint(self.test_name) c.check_plugs() r = c.click_report expected_counts = {'info': 7, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_plugs_bad_interface(self): '''Test check_plugs() - bad interface (list)''' plugs = {'iface-bad': {'interface': []}} self.set_test_snap_yaml("plugs", plugs) c = SnapReviewLint(self.test_name) c.check_plugs() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_plugs_empty_interface(self): '''Test check_plugs() - empty interface''' plugs = {'iface-empty': {'interface': ""}} self.set_test_snap_yaml("plugs", plugs) c = SnapReviewLint(self.test_name) c.check_plugs() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_plugs_unspecified_interface(self): '''Test check_plugs() - unspecified interface''' plugs = {'content': {'target': '/path/to/some/where'}} self.set_test_snap_yaml("plugs", plugs) c = SnapReviewLint(self.test_name) c.check_plugs() r = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_plugs_unknown_interface(self): '''Test check_plugs() - interface (unknown)''' plugs = {'iface-unknown': {'interface': 'nonexistent'}} self.set_test_snap_yaml("plugs", plugs) c = SnapReviewLint(self.test_name) c.check_plugs() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_plugs_unknown_interface_old_security(self): '''Test check_plugs() - interface (old-security)''' plugs = {'iface-caps': {'interface': 'old-security', 'caps': ['network']}} self.set_test_snap_yaml("plugs", plugs) c = SnapReviewLint(self.test_name) c.check_plugs() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_plugs_unspecified_unknown_interface(self): '''Test check_plugs() - unspecified interface (unknown)''' plugs = {'nonexistent': {'caps': ['network']}} self.set_test_snap_yaml("plugs", plugs) c = SnapReviewLint(self.test_name) c.check_plugs() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_plugs_unknown_attrib(self): '''Test check_plugs() - unknown attrib''' plugs = {'test': {'interface': 'content', 'nonexistent': 'abc'}} self.set_test_snap_yaml("plugs", plugs) c = SnapReviewLint(self.test_name) c.check_plugs() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_plugs_bad_attrib_content(self): '''Test check_plugs() - bad attrib - content''' plugs = {'test': {'interface': 'content', 'target': ['invalid']}} self.set_test_snap_yaml("plugs", plugs) c = SnapReviewLint(self.test_name) c.check_plugs() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_plugs_wrong_attrib_content(self): '''Test check_plugs() - content (used slot attrib with plug)''' plugs = {'test': {'interface': 'content', 'read': '/path/to/something'}} self.set_test_snap_yaml("plugs", plugs) c = SnapReviewLint(self.test_name) c.check_plugs() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_plugs_abbreviated(self): '''Test check_plugs() - abbreviated''' self.set_test_snap_yaml("plugs", {'nm': 'network-manager'}) c = SnapReviewLint(self.test_name) c.check_plugs() r = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_apps_plugs(self): '''Test check_apps_plugs()''' plugs = self._create_top_plugs() apps_plugs = self._create_apps_plugs() self.set_test_snap_yaml("plugs", plugs) self.set_test_snap_yaml("apps", apps_plugs) c = SnapReviewLint(self.test_name) c.check_apps_plugs() r = c.click_report expected_counts = {'info': 8, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_apps_no_plugs(self): '''Test check_apps_plugs() - no plugs''' plugs = self._create_top_plugs() apps_plugs = {'bar': {'command': 'bin/bar'}} self.set_test_snap_yaml("plugs", plugs) self.set_test_snap_yaml("apps", apps_plugs) c = SnapReviewLint(self.test_name) c.check_apps_plugs() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_apps_plugs_bad(self): '''Test check_apps_plugs() - bad (dict)''' plugs = self._create_top_plugs() apps_plugs = {'bar': {'plugs': {}}} self.set_test_snap_yaml("plugs", plugs) self.set_test_snap_yaml("apps", apps_plugs) c = SnapReviewLint(self.test_name) c.check_apps_plugs() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_plugs_empty(self): '''Test check_apps_plugs() - empty''' plugs = self._create_top_plugs() apps_plugs = {'bar': {'plugs': []}} self.set_test_snap_yaml("plugs", plugs) self.set_test_snap_yaml("apps", apps_plugs) c = SnapReviewLint(self.test_name) c.check_apps_plugs() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_plugs_bad_entry(self): '''Test check_apps_plugs() - bad entry (dict)''' plugs = self._create_top_plugs() apps_plugs = {'bar': {'plugs': [{}]}} self.set_test_snap_yaml("plugs", plugs) self.set_test_snap_yaml("apps", apps_plugs) c = SnapReviewLint(self.test_name) c.check_apps_plugs() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_plugs_unknown_entry(self): '''Test check_apps_plugs() - unknown''' plugs = self._create_top_plugs() apps_plugs = {'bar': {'plugs': ['nonexistent']}} self.set_test_snap_yaml("plugs", plugs) self.set_test_snap_yaml("apps", apps_plugs) c = SnapReviewLint(self.test_name) c.check_apps_plugs() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_slots(self): '''Test check_slots()''' slots = self._create_top_slots() self.set_test_snap_yaml("slots", slots) c = SnapReviewLint(self.test_name) c.check_slots() r = c.click_report expected_counts = {'info': 10, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_slots_bad_interface(self): '''Test check_slots() - bad interface (list)''' slots = {'iface-bad': {'interface': []}} self.set_test_snap_yaml("slots", slots) c = SnapReviewLint(self.test_name) c.check_slots() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_slots_empty_interface(self): '''Test check_slots() - empty interface''' slots = {'iface-empty': {'interface': ""}} self.set_test_snap_yaml("slots", slots) c = SnapReviewLint(self.test_name) c.check_slots() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_slots_unspecified_interface(self): '''Test check_slots() - unspecified interface''' slots = {'bool-file': {'path': '/path/to/some/where'}} self.set_test_snap_yaml("slots", slots) c = SnapReviewLint(self.test_name) c.check_slots() r = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_slots_wrong_attrib_content(self): '''Test check_slots() - content (used plug attrib with slot)''' plugs = {'test': {'interface': 'content', 'target': '/path/to/something'}} self.set_test_snap_yaml("slots", plugs) c = SnapReviewLint(self.test_name) c.check_slots() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_slots_unknown_interface(self): '''Test check_slots() - interface (unknown)''' slots = {'iface-unknown': {'interface': 'nonexistent'}} self.set_test_snap_yaml("slots", slots) c = SnapReviewLint(self.test_name) c.check_slots() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_slots_unknown_interface_old_security(self): '''Test check_slots() - interface (old-security)''' slots = {'iface-caps': {'interface': 'old-security', 'caps': ['network']}} self.set_test_snap_yaml("slots", slots) c = SnapReviewLint(self.test_name) c.check_slots() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_slots_unspecified_unknown_interface(self): '''Test check_slots() - unspecified interface (unknown)''' slots = {'nonexistent': {'caps': ['network']}} self.set_test_snap_yaml("slots", slots) c = SnapReviewLint(self.test_name) c.check_slots() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_slots_unknown_attrib(self): '''Test check_slots() - unknown attrib''' slots = {'test': {'interface': 'bool-file', 'nonexistent': 'abc'}} self.set_test_snap_yaml("slots", slots) c = SnapReviewLint(self.test_name) c.check_slots() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_slots_bad_attrib_boolfile(self): '''Test check_slots() - bad attrib - bool-file''' slots = {'test': {'interface': 'bool-file', 'path': ['invalid']}} self.set_test_snap_yaml("slots", slots) c = SnapReviewLint(self.test_name) c.check_slots() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_slots_abbreviated(self): '''Test check_slots() - abbreviated''' self.set_test_snap_yaml("slots", {'nm': 'network-manager'}) c = SnapReviewLint(self.test_name) c.check_slots() r = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_apps_slots(self): '''Test check_apps_slots()''' slots = self._create_top_slots() self.set_test_snap_yaml("slots", slots) apps_slots = self._create_apps_slots() self.set_test_snap_yaml("apps", apps_slots) c = SnapReviewLint(self.test_name) c.check_apps_slots() r = c.click_report expected_counts = {'info': 6, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_apps_no_slots(self): '''Test check_apps_slots() - no slots''' slots = self._create_top_slots() apps_slots = {'bar': {'command': 'bin/bar'}} self.set_test_snap_yaml("slots", slots) self.set_test_snap_yaml("apps", apps_slots) c = SnapReviewLint(self.test_name) c.check_apps_slots() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_apps_slots_bad(self): '''Test check_apps_slots() - bad (dict)''' slots = self._create_top_slots() apps_slots = {'bar': {'slots': {}}} self.set_test_snap_yaml("slots", slots) self.set_test_snap_yaml("apps", apps_slots) c = SnapReviewLint(self.test_name) c.check_apps_slots() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_slots_empty(self): '''Test check_apps_slots() - empty''' slots = self._create_top_slots() apps_slots = {'bar': {'slots': []}} self.set_test_snap_yaml("slots", slots) self.set_test_snap_yaml("apps", apps_slots) c = SnapReviewLint(self.test_name) c.check_apps_slots() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_slots_bad_entry(self): '''Test check_apps_slots() - bad entry (dict)''' slots = self._create_top_slots() apps_slots = {'bar': {'slots': [{}]}} self.set_test_snap_yaml("slots", slots) self.set_test_snap_yaml("apps", apps_slots) c = SnapReviewLint(self.test_name) c.check_apps_slots() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_apps_slots_unknown_entry(self): '''Test check_apps_slots() - unknown''' slots = self._create_top_slots() apps_slots = {'bar': {'slots': ['nonexistent']}} self.set_test_snap_yaml("slots", slots) self.set_test_snap_yaml("apps", apps_slots) c = SnapReviewLint(self.test_name) c.check_apps_slots() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_epoch(self): '''Test check_epoch''' self.set_test_snap_yaml("epoch", 2) c = SnapReviewLint(self.test_name) c.check_epoch() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_epoch_negative(self): '''Test check_epoch - negative''' self.set_test_snap_yaml("epoch", -1) c = SnapReviewLint(self.test_name) c.check_epoch() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_epoch_decimal(self): '''Test check_epoch - decimal''' self.set_test_snap_yaml("epoch", 1.01) c = SnapReviewLint(self.test_name) c.check_epoch() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_epoch_missing(self): '''Test check_epoch - not present''' self.set_test_snap_yaml("epoch", None) c = SnapReviewLint(self.test_name) c.check_epoch() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_epoch_bad(self): '''Test check_epoch - string''' self.set_test_snap_yaml("epoch", "abc") c = SnapReviewLint(self.test_name) c.check_epoch() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_confinement_strict(self): '''Test check_confinement - strict''' self.set_test_snap_yaml("confinement", "strict") c = SnapReviewLint(self.test_name) c.check_confinement() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_confinement_devmode(self): '''Test check_confinement - devmode''' self.set_test_snap_yaml("confinement", "devmode") c = SnapReviewLint(self.test_name) c.check_confinement() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_confinement_os(self): '''Test check_confinement - os''' self.set_test_snap_yaml("confinement", "strict") self.set_test_snap_yaml("type", "os") c = SnapReviewLint(self.test_name) c.check_confinement() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_confinement_kernel(self): '''Test check_confinement - kernel''' self.set_test_snap_yaml("confinement", "strict") self.set_test_snap_yaml("type", "kernel") c = SnapReviewLint(self.test_name) c.check_confinement() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_confinement_missing(self): '''Test check_confinement - missing''' self.set_test_snap_yaml("confinement", None) c = SnapReviewLint(self.test_name) c.check_confinement() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_confinement_nonexistent(self): '''Test check_confinement - nonexistent''' self.set_test_snap_yaml("confinement", "nonexistent") c = SnapReviewLint(self.test_name) c.check_confinement() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_confinement_bad(self): '''Test check_confinement - bad (boolean)''' self.set_test_snap_yaml("confinement", True) c = SnapReviewLint(self.test_name) c.check_confinement() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_confinement_bad2(self): '''Test check_confinement - bad (yaml true)''' self.set_test_snap_yaml("confinement", 'true') c = SnapReviewLint(self.test_name) c.check_confinement() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_environment(self): '''Test check_environment''' env = {'ENV1': "value", 'ENV2': "value2", } self.set_test_snap_yaml("environment", env) c = SnapReviewLint(self.test_name) c.check_environment() r = c.click_report expected_counts = {'info': 5, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_apps_environment(self): '''Test check_environment''' env = {'ENV1': "value", 'ENV2': "value2", } apps = {'app1': {'environment': env}, 'app2': {'environment': env}, 'app3': {'environment': env}, 'app4': {'environment': env}, } self.set_test_snap_yaml("apps", apps) c = SnapReviewLint(self.test_name) c.check_apps_environment() r = c.click_report expected_counts = {'info': 20, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_environment_bad_equal(self): '''Test check_environment - bad - =''' env = {'ENV1=': "value", } self.set_test_snap_yaml("environment", env) c = SnapReviewLint(self.test_name) c.check_environment() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_environment_bad_null(self): '''Test check_environment - bad - \0''' env = {'E\0NV1': "value", } self.set_test_snap_yaml("environment", env) c = SnapReviewLint(self.test_name) c.check_environment() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_environment_not_portable(self): '''Test check_environment - not portable: starts with number''' env = {'1ENV': "value", } self.set_test_snap_yaml("environment", env) c = SnapReviewLint(self.test_name) c.check_environment() r = c.click_report expected_counts = {'info': 3, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_environment_not_portable2(self): '''Test check_environment - not portable - lower case''' env = {'EnV1': "value", } self.set_test_snap_yaml("environment", env) c = SnapReviewLint(self.test_name) c.check_environment() r = c.click_report expected_counts = {'info': 3, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_environment_unusual(self): '''Test check_environment - unusual''' env = {'En:V1': "value", } self.set_test_snap_yaml("environment", env) c = SnapReviewLint(self.test_name) c.check_environment() r = c.click_report expected_counts = {'info': None, 'warn': 1, 'error': 0} self.check_results(r, expected_counts) def test_check_environment_bad_env(self): '''Test check_environment - bad env - list''' env = [] self.set_test_snap_yaml("environment", env) c = SnapReviewLint(self.test_name) c.check_environment() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_environment_bad_value(self): '''Test check_environment - bad value - list''' env = {'ENV1': [], } self.set_test_snap_yaml("environment", env) c = SnapReviewLint(self.test_name) c.check_environment() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) class TestSnapReviewLintNoMock(TestCase): """Tests without mocks where they are not needed.""" def setUp(self): # XXX cleanup_unpack() is required because global variables # UNPACK_DIR, RAW_UNPACK_DIR are initialised to None at module # load time, but updated when a real (non-Mock) test runs, such as # here. While, at the same time, two of the existing tests using # mocks depend on both global vars being None. Ideally, those # global vars should be refactored away. self.addCleanup(cleanup_unpack) super().setUp() def mkdtemp(self): """Create a temp dir which is cleaned up after test.""" tmp_dir = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, tmp_dir) return tmp_dir def check_results(self, report, expected_counts={'info': 1, 'warn': 0, 'error': 0}, expected=None): common_check_results(self, report, expected_counts, expected) def test_check_external_symlinks(self): '''Test check_external_symlinks()''' package = utils.make_snap2(output_dir=self.mkdtemp()) c = SnapReviewLint(package) c.check_external_symlinks() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_external_symlinks_has_symlink(self): '''Test check_external_symlinks() - has symlink''' package = utils.make_snap2(output_dir=self.mkdtemp(), extra_files=['/some/where,outside'] ) c = SnapReviewLint(package) c.check_external_symlinks() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_external_symlinks_has_symlink_libc6(self): '''Test check_external_symlinks() - has symlink for libc6''' package = utils.make_snap2(output_dir=self.mkdtemp(), extra_files=['/usr/lib/x86_64-linux-gnu/libmvec.so,libmvec.so'] ) c = SnapReviewLint(package) c.check_external_symlinks() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_external_symlinks_has_symlink_libc6_ld_linux(self): '''Test check_external_symlinks() - ld-linux-x86-64.so.2''' package = utils.make_snap2(output_dir=self.mkdtemp(), extra_files=['/lib64/ld-linux-x86-64.so.2,ld-linux-x86-64.so.2'] ) c = SnapReviewLint(package) c.check_external_symlinks() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_external_symlinks_type_kernel(self): '''Test check_external_symlinks() - type kernel''' output_dir = self.mkdtemp() path = os.path.join(output_dir, 'snap.yaml') content = ''' name: test version: 0.1 summary: some thing description: some desc architectures: [ amd64 ] type: kernel ''' with open(path, 'w') as f: f.write(content) package = utils.make_snap2(output_dir=output_dir, extra_files=['%s:meta/snap.yaml' % path, '/some/where,outside'] ) c = SnapReviewLint(package) c.check_external_symlinks() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_external_symlinks_has_symlink_gadget(self): '''Test check_external_symlinks() - has symlink (gadget)''' output_dir = self.mkdtemp() path = os.path.join(output_dir, 'snap.yaml') content = ''' name: test version: 0.1 summary: some thing description: some desc type: gadget ''' with open(path, 'w') as f: f.write(content) package = utils.make_snap2(output_dir=output_dir, extra_files=['%s:meta/snap.yaml' % path, '/some/where,outside'] ) c = SnapReviewLint(package) c.check_external_symlinks() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_external_symlinks_os(self): '''Test check_external_symlinks() - os''' output_dir = self.mkdtemp() path = os.path.join(output_dir, 'snap.yaml') content = ''' name: test version: 0.1 summary: some thing description: some desc type: os ''' with open(path, 'w') as f: f.write(content) package = utils.make_snap2(output_dir=output_dir, extra_files=['%s:meta/snap.yaml' % path] ) c = SnapReviewLint(package) c.check_external_symlinks() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_architecture_all(self): '''Test check_architecture_all()''' package = utils.make_snap2(output_dir=self.mkdtemp()) c = SnapReviewLint(package) c.check_architecture_all() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_architecture_all_amd64(self): '''Test check_architecture_all() - amd64''' output_dir = self.mkdtemp() path = os.path.join(output_dir, 'snap.yaml') content = ''' name: test version: 0.1 summary: some thing description: some desc architectures: [ amd64 ] ''' with open(path, 'w') as f: f.write(content) package = utils.make_snap2(output_dir=output_dir, extra_files=['%s:meta/snap.yaml' % path] ) c = SnapReviewLint(package) c.check_architecture_all() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_architecture_all_has_binary(self): '''Test check_architecture_all() - has binary''' package = utils.make_snap2(output_dir=self.mkdtemp(), extra_files=['/bin/ls:ls'] ) c = SnapReviewLint(package) c.check_architecture_all() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_architecture_all_skips_pyc(self): '''Test check_architecture_all() - skips .pyc''' # copy /bin/ls to foo.pyc since ls is a binary package = utils.make_snap2(output_dir=self.mkdtemp(), extra_files=['/bin/ls:foo.pyc'] ) c = SnapReviewLint(package) c.check_architecture_all() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_architecture_specified_needed_has_binary(self): '''Test check_architecture_specified_needed() - has binary''' output_dir = self.mkdtemp() path = os.path.join(output_dir, 'snap.yaml') content = ''' name: test version: 0.1 summary: some thing description: some desc architectures: [ amd64 ] ''' with open(path, 'w') as f: f.write(content) package = utils.make_snap2(output_dir=output_dir, extra_files=['%s:meta/snap.yaml' % path, '/bin/ls:ls' ] ) c = SnapReviewLint(package) c.check_architecture_specified_needed() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_architecture_specified_needed(self): '''Test check_architecture_specified_needed()''' output_dir = self.mkdtemp() path = os.path.join(output_dir, 'snap.yaml') content = ''' name: test version: 0.1 summary: some thing description: some desc architectures: [ amd64 ] ''' with open(path, 'w') as f: f.write(content) package = utils.make_snap2(output_dir=output_dir, extra_files=['%s:meta/snap.yaml' % path] ) c = SnapReviewLint(package) c.check_architecture_specified_needed() r = c.click_report expected_counts = {'info': None, 'warn': 1, 'error': 0} self.check_results(r, expected_counts) def test_check_vcs(self): '''Test check_vcs()''' package = utils.make_snap2(output_dir=self.mkdtemp()) c = SnapReviewLint(package) c.check_vcs() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_vcs_bzrignore(self): '''Test check_vcs() - .bzrignore''' package = utils.make_snap2(output_dir=self.mkdtemp(), extra_files=['.bzrignore'] ) c = SnapReviewLint(package) c.check_vcs() r = c.click_report expected_counts = {'info': None, 'warn': 1, 'error': 0} self.check_results(r, expected_counts) def test_check_plugs_lp1579201(self): '''Test check_architecture_all() - amd64''' output_dir = self.mkdtemp() path = os.path.join(output_dir, 'snap.yaml') content = ''' name: test version: 0.1 summary: some thing description: some desc architectures: [ amd64 ] plugs: network: null ''' with open(path, 'w') as f: f.write(content) package = utils.make_snap2(output_dir=output_dir, extra_files=['%s:meta/snap.yaml' % path] ) c = SnapReviewLint(package) c.check_plugs() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) click-reviewers-tools-0.44~16.04.1/clickreviews/tests/__init__.py0000664000000000000000000000000012666350676021467 0ustar click-reviewers-tools-0.44~16.04.1/clickreviews/tests/test_cr_desktop.py0000664000000000000000000012307512666350676023146 0ustar '''test_cr_desktop.py: tests for the cr_desktop module''' # # Copyright (C) 2013 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from clickreviews.cr_desktop import ClickReviewDesktop import clickreviews.cr_tests as cr_tests class TestClickReviewDesktop(cr_tests.TestClickReview): """Tests for the desktop review tool.""" def test_check_desktop_file(self): '''Test check_desktop_file()''' c = ClickReviewDesktop(self.test_name) c.check_desktop_file() r = c.click_report expected = dict() expected['info'] = dict() expected['warn'] = dict() expected['error'] = dict() name = c._get_check_name('files_usable') expected['info'][name] = {"text": "OK"} self.check_results(r, expected=expected) def test_check_desktop_file_valid(self): '''Test check_desktop_file_valid()''' c = ClickReviewDesktop(self.test_name) c.check_desktop_file_valid() r = c.click_report expected = dict() expected['info'] = dict() expected['warn'] = dict() expected['error'] = dict() name = c._get_check_name('validates', app=self.default_appname) expected['info'][name] = {"text": "OK"} self.check_results(r, expected=expected) def test_check_desktop_file_valid_missing_exec(self): '''Test check_desktop_file_valid() - missing Exec''' c = ClickReviewDesktop(self.test_name) self.set_test_desktop(self.default_appname, "Exec", None) c.check_desktop_file_valid() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_desktop_exec_has_deprecated_exec(self): '''Test check_desktop_exec() - Exec has deprecated exec''' c = ClickReviewDesktop(self.test_name) self.set_test_desktop(self.default_appname, "Exec", "cordova-ubuntu-2.8 .") c.check_desktop_exec() r = c.click_report expected_counts = {'info': 0, 'warn': 1, 'error': 0} self.check_results(r, expected_counts) def test_check_desktop_file_has_valid_exec(self): '''Test check_desktop_exec() - Exec has valid exec''' c = ClickReviewDesktop(self.test_name) self.set_test_desktop(self.default_appname, "Exec", "qmlscene $@ myApp.qml") c.check_desktop_exec() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_desktop_file_valid_empty_name(self): '''Test check_desktop_file_valid() - empty Name''' c = ClickReviewDesktop(self.test_name) self.set_test_desktop(self.default_appname, "Name", "") c.check_desktop_file_valid() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_desktop_required_keys(self): '''Test check_desktop_required_keys()''' c = ClickReviewDesktop(self.test_name) c.check_desktop_required_keys() r = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_desktop_required_keys_missing(self): '''Test check_desktop_required_keys()''' c = ClickReviewDesktop(self.test_name) self.set_test_desktop(self.default_appname, "Name", None) c.check_desktop_required_keys() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_desktop_x_ubuntu_gettext_domain_missing(self): '''Test check_desktop_x_ubuntu_gettext_domain when missing''' c = ClickReviewDesktop(self.test_name) self.set_test_desktop(self.default_appname, "X-Ubuntu-Gettext-Domain", None) c.check_desktop_x_ubuntu_gettext_domain() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_desktop_x_ubuntu_gettext_domain_empty(self): '''Test check_desktop_x_ubuntu_gettext_domain when empty''' c = ClickReviewDesktop(self.test_name) self.set_test_desktop(self.default_appname, "X-Ubuntu-Gettext-Domain", "") c.check_desktop_x_ubuntu_gettext_domain() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_desktop_x_ubuntu_gettext_domain_valid(self): '''Test check_desktop_x_ubuntu_gettext_domain valid''' c = ClickReviewDesktop(self.test_name) self.set_test_desktop(self.default_appname, "X-Ubuntu-Gettext-Domain", self.test_control['Package']) c.check_desktop_x_ubuntu_gettext_domain() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_desktop_x_ubuntu_gettext_domain_mismatch(self): '''Test check_desktop_x_ubuntu_gettext_domain doesn't match''' c = ClickReviewDesktop(self.test_name) self.set_test_desktop(self.default_appname, "X-Ubuntu-Gettext-Domain", "com.example.mismatch") c.check_desktop_x_ubuntu_gettext_domain() r = c.click_report expected_counts = {'info': 0, 'warn': 1, 'error': 0} self.check_results(r, expected_counts) def test_check_desktop_exec_webapp_args_with_url_patterns(self): '''Test check_desktop_exec_webapp_args with --webappUrlPatterns''' for exe in ['webbrowser-app --webapp', 'webapp-container']: c = ClickReviewDesktop(self.test_name) ex = "%s --enable-back-forward --webapp " % exe + \ "--webappUrlPatterns=https?://mobile.twitter.com/* " + \ "http://mobile.twitter.com" self.set_test_desktop(self.default_appname, "Exec", ex) c.check_desktop_exec_webapp_args() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_desktop_exec_webapp_args_with_model_search_path(self): '''Test check_desktop_exec_webapp_args with --webappModelSearchPath''' c = ClickReviewDesktop(self.test_name) for exe in ['webbrowser-app --webapp', 'webapp-container']: ex = "%s --enable-back-forward " % exe + \ "--webappModelSearchPath=. " + \ "http://mobile.twitter.com" self.set_test_desktop(self.default_appname, "Exec", ex) c.check_desktop_exec_webapp_args() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_desktop_exec_webapp_args_without_required(self): '''Test check_desktop_exec_webapp_args without required''' for exe in ['webbrowser-app --webapp', 'webapp-container']: c = ClickReviewDesktop(self.test_name) ex = "%s --enable-back-forward " % exe + \ "http://mobile.twitter.com" self.set_test_desktop(self.default_appname, "Exec", ex) c.check_desktop_exec_webapp_args() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_desktop_exec_webapp_args_without_optional(self): '''Test check_desktop_exec_webapp_args without optional --enable-back-forward''' for exe in ['webbrowser-app --webapp', 'webapp-container']: c = ClickReviewDesktop(self.test_name) ex = "%s " % exe + \ "--webappUrlPatterns=https?://mobile.twitter.com/* " + \ "http://mobile.twitter.com" self.set_test_desktop(self.default_appname, "Exec", ex) c.check_desktop_exec_webapp_args() r = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_desktop_exec_webapp_args_with_both_required(self): '''Test check_desktop_exec_webbrowser with both required''' for exe in ['webbrowser-app --webapp', 'webapp-container']: c = ClickReviewDesktop(self.test_name) ex = "%s --enable-back-forward " % exe + \ "--webappUrlPatterns=https?://mobile.twitter.com/* " + \ "--webappModelSearchPath=. " + \ "http://mobile.twitter.com" self.set_test_desktop(self.default_appname, "Exec", ex) c.check_desktop_exec_webapp_args() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_desktop_exec_webbrowser_missing_exec(self): '''Test check_desktop_exec_webbrowser - missing exec''' c = ClickReviewDesktop(self.test_name) self.set_test_desktop(self.default_appname, "Exec", None) c.check_desktop_exec_webbrowser() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_desktop_exec_webbrowser_missing_webapp(self): '''Test check_desktop_exec_webbrowser - missing --webapp''' c = ClickReviewDesktop(self.test_name) ex = "webbrowser-app --enable-back-forward " + \ "--webappUrlPatterns=https?://mobile.twitter.com/* " + \ "http://mobile.twitter.com" c = ClickReviewDesktop(self.test_name) self.set_test_desktop(self.default_appname, "Exec", ex) c.check_desktop_exec_webbrowser() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_desktop_exec_webbrowser_non1310(self): '''Test check_desktop_exec_webbrowser without 13.10''' self.set_test_manifest("framework", "not-ubuntu-sdk-13.10") c = ClickReviewDesktop(self.test_name) ex = "webbrowser-app --webapp --enable-back-forward " + \ "--webappUrlPatterns=https?://mobile.twitter.com/* " + \ "http://mobile.twitter.com" c = ClickReviewDesktop(self.test_name) self.set_test_desktop(self.default_appname, "Exec", ex) c.check_desktop_exec_webbrowser() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_desktop_exec_webapp_container_has_webapp(self): '''Test check_desktop_exec_webapp_container - has --webapp''' c = ClickReviewDesktop(self.test_name) ex = "webapp-container --enable-back-forward --webapp " + \ "--webappUrlPatterns=https?://mobile.twitter.com/* " + \ "http://mobile.twitter.com" c = ClickReviewDesktop(self.test_name) self.set_test_desktop(self.default_appname, "Exec", ex) c.check_desktop_exec_webapp_container() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_desktop_exec_webapp_container_1310a(self): '''Test check_desktop_exec_webapp_container on 13.10 framework''' c = ClickReviewDesktop(self.test_name) ex = "webapp-container --enable-back-forward " + \ "--webappUrlPatterns=https?://mobile.twitter.com/* " + \ "http://mobile.twitter.com" c = ClickReviewDesktop(self.test_name) self.set_test_desktop(self.default_appname, "Exec", ex) c.check_desktop_exec_webapp_container() r = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_desktop_exec_webapp_container_1310b(self): '''Test check_desktop_exec_webapp_container on non-13.10 framework''' self.set_test_manifest("framework", "not-ubuntu-sdk-13.10") c = ClickReviewDesktop(self.test_name) ex = "webapp-container --enable-back-forward " + \ "--webappUrlPatterns=https?://mobile.twitter.com/* " + \ "http://mobile.twitter.com" c = ClickReviewDesktop(self.test_name) self.set_test_desktop(self.default_appname, "Exec", ex) c.check_desktop_exec_webapp_container() r = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_desktop_exec_webapp_container_html5_launcher_1410(self): '''Test check_desktop_exec_webapp_container - html5 launcher 14.10''' self.set_test_manifest("framework", "ubuntu-sdk-14.10") ex = "ubuntu-html5-app-launcher $@ --www=www" c = ClickReviewDesktop(self.test_name) self.set_test_desktop(self.default_appname, "Exec", ex) c.check_desktop_exec_webapp_container() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_desktop_exec_webapp_container_html5_launcher_1510(self): '''Test check_desktop_exec_webapp_container - html5 launcher 15.10''' self.set_test_manifest("framework", "ubuntu-sdk-15.10") ex = "ubuntu-html5-app-launcher $@ --www=www" c = ClickReviewDesktop(self.test_name) self.set_test_desktop(self.default_appname, "Exec", ex) c.check_desktop_exec_webapp_container() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_desktop_exec_webapp_container_missing_exec(self): '''Test check_desktop_exec_webapp_container - missing exec''' c = ClickReviewDesktop(self.test_name) self.set_test_desktop(self.default_appname, "Exec", None) c.check_desktop_exec_webapp_container() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_desktop_exec_webbrowser_urlpatterns_valid(self): '''Test check_desktop_exec_webbrowser_urlpatterns() valid''' c = ClickReviewDesktop(self.test_name) ex = "webbrowser-app --enable-back-forward --webapp " + \ "--webappUrlPatterns=https?://mobile.twitter.com/* " + \ "http://mobile.twitter.com" self.set_test_desktop(self.default_appname, "Exec", ex) c.check_desktop_exec_webbrowser_urlpatterns() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_desktop_exec_webbrowser_urlpatterns_missing_exec(self): '''Test check_desktop_exec_webbrowser_urlpatterns() - missing exec''' c = ClickReviewDesktop(self.test_name) self.set_test_desktop(self.default_appname, "Exec", None) c.check_desktop_exec_webbrowser_urlpatterns() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_desktop_exec_webbrowser_urlpatterns_missing_arg(self): '''Test check_desktop_exec_webbrowser_urlpatterns() missing arg''' c = ClickReviewDesktop(self.test_name) ex = "webbrowser-app --enable-back-forward --webapp " + \ "http://mobile.twitter.com" self.set_test_desktop(self.default_appname, "Exec", ex) c.check_desktop_exec_webbrowser_urlpatterns() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_desktop_exec_webbrowser_urlpatterns_multiple_args(self): '''Test check_desktop_exec_webbrowser_urlpatterns() multiple args''' c = ClickReviewDesktop(self.test_name) ex = "webbrowser-app --enable-back-forward --webapp " + \ "--webappUrlPatterns=https?://mobile.twitter.com/* " + \ "--webappUrlPatterns=https?://mobile.twitter.com/* " + \ "http://mobile.twitter.com" self.set_test_desktop(self.default_appname, "Exec", ex) c.check_desktop_exec_webbrowser_urlpatterns() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_desktop_exec_webbrowser_urlpatterns_no_https(self): '''Test check_desktop_exec_webbrowser_urlpatterns() missing https?''' c = ClickReviewDesktop(self.test_name) ex = "webbrowser-app --enable-back-forward --webapp " + \ "--webappUrlPatterns=http://mobile.twitter.com/* " + \ "http://mobile.twitter.com" self.set_test_desktop(self.default_appname, "Exec", ex) c.check_desktop_exec_webbrowser_urlpatterns() r = c.click_report expected_counts = {'info': None, 'warn': 1, 'error': 0} self.check_results(r, expected_counts) def test_check_desktop_exec_webbrowser_urlpatterns_no_trailing_glob(self): '''Test check_desktop_exec_webbrowser_urlpatterns() no trailing glob''' c = ClickReviewDesktop(self.test_name) ex = "webbrowser-app --enable-back-forward --webapp " + \ "--webappUrlPatterns=https?://mobile.twitter.com/ " + \ "http://mobile.twitter.com" self.set_test_desktop(self.default_appname, "Exec", ex) c.check_desktop_exec_webbrowser_urlpatterns() r = c.click_report expected_counts = {'info': None, 'warn': 1, 'error': 0} self.check_results(r, expected_counts) def test_check_desktop_exec_webbrowser_urlpatterns_trailing_glob1(self): '''Test check_desktop_exec_webbrowser_urlpatterns() - trailing glob1''' c = ClickReviewDesktop(self.test_name) ex = "webbrowser-app --enable-back-forward --webapp " + \ "--webappUrlPatterns=https?://mobile.twitter.com/* " + \ "http://mobile.twitter.com" self.set_test_desktop(self.default_appname, "Exec", ex) c.check_desktop_exec_webbrowser_urlpatterns() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_desktop_exec_webbrowser_urlpatterns_trailing_glob2(self): '''Test check_desktop_exec_webbrowser_urlpatterns() - trailing glob2''' c = ClickReviewDesktop(self.test_name) ex = "webbrowser-app --enable-back-forward --webapp " + \ "--webappUrlPatterns=https?://m.bbc.co.uk/sport* " + \ "http://m.bbc.co.uk/sport" self.set_test_desktop(self.default_appname, "Exec", ex) c.check_desktop_exec_webbrowser_urlpatterns() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_desktop_exec_webbrowser_urlpatterns_trailing_glob3(self): '''Test check_desktop_exec_webbrowser_urlpatterns() - trailing glob3''' c = ClickReviewDesktop(self.test_name) ex = "webbrowser-app --enable-back-forward --webapp " + \ "--webappUrlPatterns=https?://*.bbc.co.uk/sport* " + \ "http://*.bbc.co.uk/sport" self.set_test_desktop(self.default_appname, "Exec", ex) c.check_desktop_exec_webbrowser_urlpatterns() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_desktop_exec_webbrowser_urlpatterns_trailing_glob4(self): '''Test check_desktop_exec_webbrowser_urlpatterns() - trailing glob4''' c = ClickReviewDesktop(self.test_name) ex = "webbrowser-app --enable-back-forward --webapp " + \ "--webappUrlPatterns=https?://www.bbc.co.uk* " + \ "http://www.bbc.co.uk" self.set_test_desktop(self.default_appname, "Exec", ex) c.check_desktop_exec_webbrowser_urlpatterns() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_desktop_exec_webbrowser_urlpatterns_embedded_glob(self): '''Test check_desktop_exec_webbrowser_urlpatterns() embedded glob''' c = ClickReviewDesktop(self.test_name) ex = "webbrowser-app --enable-back-forward --webapp " + \ "--webappUrlPatterns=https?://mobile.twitter.com*/* " + \ "http://mobile.twitter.com" self.set_test_desktop(self.default_appname, "Exec", ex) c.check_desktop_exec_webbrowser_urlpatterns() r = c.click_report expected_counts = {'info': None, 'warn': 2, 'error': 0} self.check_results(r, expected_counts) def test_check_desktop_exec_webbrowser_urlpatterns_leading_glob(self): '''Test check_desktop_exec_webbrowser_urlpatterns() leading glob''' c = ClickReviewDesktop(self.test_name) ex = "webbrowser-app --enable-back-forward --webapp " + \ "--webappUrlPatterns=https?://*.twitter.com/* " + \ "http://mobile.twitter.com" self.set_test_desktop(self.default_appname, "Exec", ex) c.check_desktop_exec_webbrowser_urlpatterns() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_desktop_exec_webbrowser_urlpatterns_target_mismatch(self): '''Test check_desktop_exec_webbrowser_urlpatterns() target mismatch''' c = ClickReviewDesktop(self.test_name) ex = "webbrowser-app --enable-back-forward --webapp " + \ "--webappUrlPatterns=https?://mobile.twitter.com/* " + \ "http://mobile.twitter.net" self.set_test_desktop(self.default_appname, "Exec", ex) c.check_desktop_exec_webbrowser_urlpatterns() r = c.click_report expected_counts = {'info': None, 'warn': 1, 'error': 0} self.check_results(r, expected_counts) def test_check_desktop_exec_webbrowser_urlpatterns_target_mismatch2(self): '''Test check_desktop_exec_webbrowser_urlpatterns() target mismatch2''' c = ClickReviewDesktop(self.test_name) # this shouldn't error or warn, but should give info ex = "webbrowser-app --enable-back-forward --webapp " + \ "--webappUrlPatterns=https?://mobile.twitter.com/* " + \ "ftp://mobile.twitter.com" self.set_test_desktop(self.default_appname, "Exec", ex) c.check_desktop_exec_webbrowser_urlpatterns() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_desktop_exec_webbrowser_urlpatterns_target_mismatch3(self): '''Test check_desktop_exec_webbrowser_urlpatterns() target mismatch3''' c = ClickReviewDesktop(self.test_name) # this shouldn't error or warn, but should give info ex = "webbrowser-app --enable-back-forward --webapp " + \ "--webappUrlPatterns=https?://mobile.twitter.com/*," + \ "https?://nonmatch.twitter.com/* " + \ "http://mobile.twitter.com" self.set_test_desktop(self.default_appname, "Exec", ex) c.check_desktop_exec_webbrowser_urlpatterns() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_desktop_exec_webbrowser_urlpatterns_target_missing(self): '''Test check_desktop_exec_webbrowser_urlpatterns() target missing''' c = ClickReviewDesktop(self.test_name) ex = "webbrowser-app --enable-back-forward --webapp " + \ "--webappUrlPatterns=https?://mobile.twitter.com/*" self.set_test_desktop(self.default_appname, "Exec", ex) c.check_desktop_exec_webbrowser_urlpatterns() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_desktop_exec_webbrowser_modelsearchpath_valid(self): '''Test check_desktop_exec_webbrowser_modelsearchpath() valid''' c = ClickReviewDesktop(self.test_name) self.set_test_webapp_manifest("unity-webapps-foo/manifest.json", "name", "foo") self.set_test_webapp_manifest("unity-webapps-foo/manifest.json", "includes", ['https?://mobile.twitter.com/*']) ex = "webbrowser-app --enable-back-forward --webapp " + \ "--webappModelSearchPath=. http://mobile.twitter.com" self.set_test_desktop(self.default_appname, "Exec", ex) c.check_desktop_exec_webbrowser_modelsearchpath() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_desktop_exec_webbrowser_modelsearchpath_missing_exec(self): '''Test check_desktop_exec_webbrowser_modelsearchpath - missing exec''' c = ClickReviewDesktop(self.test_name) self.set_test_desktop(self.default_appname, "Exec", None) c.check_desktop_exec_webbrowser_modelsearchpath() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_desktop_exec_webbrowser_modelsearchpath_missing_arg(self): '''Test check_desktop_exec_webbrowser_modelsearchpath() missing arg''' c = ClickReviewDesktop(self.test_name) ex = "webbrowser-app --enable-back-forward --webapp " + \ "http://mobile.twitter.com" self.set_test_desktop(self.default_appname, "Exec", ex) c.check_desktop_exec_webbrowser_modelsearchpath() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_desktop_exec_webbrowser_modelsearchpath_multiple_args(self): '''Test check_desktop_exec_webbrowser_modelsearchpath multiple args''' c = ClickReviewDesktop(self.test_name) ex = "webbrowser-app --enable-back-forward --webapp " + \ "--webappModelSearchPath=. " + \ "--webappModelSearchPath=. " + \ "http://mobile.twitter.com" self.set_test_desktop(self.default_appname, "Exec", ex) c.check_desktop_exec_webbrowser_modelsearchpath() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_desktop_exec_webbrowser_modelsearchpath_empty(self): '''Test check_desktop_exec_webbrowser_modelsearchpath() empty''' c = ClickReviewDesktop(self.test_name) ex = "webbrowser-app --enable-back-forward --webapp " + \ "--webappModelSearchPath= http://mobile.twitter.com" self.set_test_desktop(self.default_appname, "Exec", ex) c.check_desktop_exec_webbrowser_modelsearchpath() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_desktop_exec_webbrowser_modelsearchpath_no_manifest(self): '''Test check_desktop_exec_webbrowser_modelsearchpath() no manifest''' c = ClickReviewDesktop(self.test_name) ex = "webbrowser-app --enable-back-forward --webapp " + \ "--webappModelSearchPath=. http://mobile.twitter.com" self.set_test_desktop(self.default_appname, "Exec", ex) c.check_desktop_exec_webbrowser_modelsearchpath() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_desktop_exec_webbrowser_modelsearchpath_bad_manifest(self): '''Test check_desktop_exec_webbrowser_modelsearchpath() bad manifest''' c = ClickReviewDesktop(self.test_name) self.set_test_webapp_manifest("unity-webapps-foo/manifest.json", None, None) ex = "webbrowser-app --enable-back-forward --webapp " + \ "--webappModelSearchPath=. http://mobile.twitter.com" self.set_test_desktop(self.default_appname, "Exec", ex) c.check_desktop_exec_webbrowser_modelsearchpath() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_desktop_exec_webbrowser_modelsearchpath_mult_manifest(self): '''Test check_desktop_exec_webbrowser_modelsearchpath mult manifest''' c = ClickReviewDesktop(self.test_name) self.set_test_webapp_manifest("unity-webapps-foo/manifest.json", "name", "foo") self.set_test_webapp_manifest("unity-webapps-bar/manifest.json", "name", "bar") ex = "webbrowser-app --enable-back-forward --webapp " + \ "--webappModelSearchPath=. http://mobile.twitter.com" self.set_test_desktop(self.default_appname, "Exec", ex) c.check_desktop_exec_webbrowser_modelsearchpath() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_desktop_exec_webbrowser_modelsearchpath_bad_includes(self): '''Test check_desktop_exec_webbrowser_modelsearchpath() bad includes''' c = ClickReviewDesktop(self.test_name) self.set_test_webapp_manifest("unity-webapps-foo/manifest.json", "name", "foo") self.set_test_webapp_manifest("unity-webapps-foo/manifest.json", "includes", "not list") ex = "webbrowser-app --enable-back-forward --webapp " + \ "--webappModelSearchPath=. http://mobile.twitter.com" self.set_test_desktop(self.default_appname, "Exec", ex) c.check_desktop_exec_webbrowser_modelsearchpath() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_desktop_exec_webbrowser_modelsearchpath_no_includes(self): '''Test check_desktop_exec_webbrowser_modelsearchpath() no includes''' c = ClickReviewDesktop(self.test_name) self.set_test_webapp_manifest("unity-webapps-foo/manifest.json", "name", "foo") ex = "webbrowser-app --enable-back-forward --webapp " + \ "--webappModelSearchPath=. http://mobile.twitter.com" self.set_test_desktop(self.default_appname, "Exec", ex) c.check_desktop_exec_webbrowser_modelsearchpath() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_desktop_exec_webbrowser_modelsearchpath_mismatch(self): '''Test check_desktop_exec_webbrowser_modelsearchpath() no includes''' c = ClickReviewDesktop(self.test_name) self.set_test_webapp_manifest("unity-webapps-foo/manifest.json", "name", "foo") self.set_test_webapp_manifest("unity-webapps-foo/manifest.json", "includes", ['https?://mobile.twitter.net/*']) ex = "webbrowser-app --enable-back-forward --webapp " + \ "--webappModelSearchPath=. http://mobile.twitter.com" self.set_test_desktop(self.default_appname, "Exec", ex) c.check_desktop_exec_webbrowser_modelsearchpath() r = c.click_report expected_counts = {'info': None, 'warn': 1, 'error': 0} self.check_results(r, expected_counts) def test_check_desktop_exec_webbrowser_local_app(self): '''Test test_check_desktop_exec_webbrowser_local_app() local app''' c = ClickReviewDesktop(self.test_name) ex = "webapp-container ./www/index.html" self.set_test_desktop(self.default_appname, "Exec", ex) c.check_desktop_exec_webapp_args() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_desktop_exec_webbrowser_no_homepage(self): '''Test check_desktop_exec_webbrowser_no_homepage() not local app''' c = ClickReviewDesktop(self.test_name) self.set_test_webapp_manifest("unity-webapps-foo/manifest.json", "name", "foo") self.set_test_webapp_manifest("unity-webapps-foo/manifest.json", "includes", ['https?://mobile.twitter.net/*']) ex = "webapp-container --webapp='Zm9v' " + \ "--enable-back-forward --webappModelSearchPath=." self.set_test_desktop(self.default_appname, "Exec", ex) c.check_desktop_exec_webapp_args() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_desktop_exec_webbrowser_field_code(self): '''Test check_desktop_exec_webbrowser_field_code() with field code''' c = ClickReviewDesktop(self.test_name) self.set_test_webapp_manifest("unity-webapps-foo/manifest.json", "name", "foo") self.set_test_webapp_manifest("unity-webapps-foo/manifest.json", "includes", ['https?://mobile.twitter.net/*']) ex = "webapp-container --webapp='Zm9v' " + \ "--enable-back-forward --webappModelSearchPath=. %u" self.set_test_desktop(self.default_appname, "Exec", ex) c.check_desktop_exec_webapp_args() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_desktop_exec_webbrowser_local_pattern(self): '''Test test_check_desktop_exec_webbrowser_local_pattern() invalid pattern''' c = ClickReviewDesktop(self.test_name) ex = "webapp-container " + \ "--webappUrlPatterns=https?://mobile.twitter.com/* " + \ "./www/index.html" self.set_test_desktop(self.default_appname, "Exec", ex) c.check_desktop_exec_webapp_args() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_desktop_exec_webbrowser_local_webapp(self): '''Test test_check_desktop_exec_webbrowser_local_webapp() invalid webapp cli''' c = ClickReviewDesktop(self.test_name) ex = "webapp-container " + \ "--webapp=DEADBEEF " + \ "./www/index.html" self.set_test_desktop(self.default_appname, "Exec", ex) c.check_desktop_exec_webapp_args() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_desktop_exec_webbrowser_local_model(self): '''Test test_check_desktop_exec_webbrowser_local_model() invalid model''' c = ClickReviewDesktop(self.test_name) ex = "webapp-container " + \ "--webappModelSearchPath=. " + \ "./www/index.html" self.set_test_desktop(self.default_appname, "Exec", ex) c.check_desktop_exec_webapp_args() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_peer_hooks(self): '''Test check_peer_hooks()''' c = ClickReviewDesktop(self.test_name) # create a new hooks database for our peer hooks tests tmp = dict() # add our hook tmp["desktop"] = "foo.desktop" # add any required peer hooks tmp["apparmor"] = "foo.apparmor" # update the manifest and test_manifest c.manifest["hooks"][self.default_appname] = tmp self._update_test_manifest() # do the test c.check_peer_hooks() r = c.click_report # We should end up with 2 info expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_peer_hooks_disallowed(self): '''Test check_peer_hooks() - disallowed''' c = ClickReviewDesktop(self.test_name) # create a new hooks database for our peer hooks tests tmp = dict() # add our hook tmp["desktop"] = "foo.desktop" # add any required peer hooks tmp["desktop"] = "foo.desktop" tmp["apparmor"] = "foo.apparmor" # add something not allowed tmp["nonexistent"] = "nonexistent-hook" c.manifest["hooks"][self.default_appname] = tmp self._update_test_manifest() # do the test c.check_peer_hooks() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_peer_hooks_required(self): '''Test check_peer_hooks() - required''' c = ClickReviewDesktop(self.test_name) # create a new hooks database for our peer hooks tests tmp = dict() # add our hook tmp["desktop"] = "foo.desktop" # skip adding required hooks c.manifest["hooks"][self.default_appname] = tmp self._update_test_manifest() # do the test c.check_peer_hooks() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_desktop_file_snappy_1504(self): '''Test check_desktop_file() - snappy 15.04''' self.set_test_pkgfmt("snap", "15.04") c = ClickReviewDesktop(self.test_name) c.check_desktop_file() r = c.click_report expected = dict() expected['info'] = dict() expected['warn'] = dict() expected['error'] = dict() name = c._get_check_name('files_usable') expected['info'][name] = {"text": "OK"} self.check_results(r, expected=expected) click-reviewers-tools-0.44~16.04.1/clickreviews/tests/test_modules.py0000664000000000000000000000206412666350676022453 0ustar from clickreviews import modules, cr_tests import clickreviews import glob class TestModules(cr_tests.TestClickReview): '''Tests for the modules module.''' def setUp(self): self.modules = modules.get_modules() super().setUp() def test_number_of_suitable_modules(self): path = clickreviews.__path__[0] module_files = glob.glob(path + '/*.py') relevant_module_files = modules.narrow_down_modules(module_files) self.assertEqual(len(relevant_module_files), len(self.modules)) def test_number_of_suitable_modules_greater0(self): self.assertTrue(len(self.modules) > 0) def test_number_of_available_review_classes(self): count = 0 for module_name in self.modules: review = modules.find_main_class(module_name) if review: count += 1 self.assertEqual(count, len(self.modules), 'Not all files in clickreviews/[cs]r_*.py contain ' 'classes named Click|Snap*Review.') click-reviewers-tools-0.44~16.04.1/clickreviews/tests/test_cr_scope.py0000664000000000000000000002454012666350676022603 0ustar '''test_cr_scope.py: tests for the cr_scope module''' # # Copyright (C) 2014 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from clickreviews.cr_scope import ClickReviewScope import clickreviews.cr_tests as cr_tests import configparser class TestClickReviewScope(cr_tests.TestClickReview): """Tests for the lint review tool.""" def _create_scope(self, config_dict=None): '''Create a scope to pass to tests''' scope = dict() scope["dir_rel"] = "scope-directory" scope["ini_file_rel"] = "%s/%s_%s.ini" % (scope["dir_rel"], self.default_appname, 'foo') scope["scope_config"] = configparser.ConfigParser() scope["scope_config"]['ScopeConfig'] = config_dict return scope def _stub_config(self): '''Stub configparser file''' config_dict = { 'ScopeRunner': "%s" % self.default_appname, 'ChildScopes': 'Child1', 'DisplayName': 'Foo', 'Description': 'Some description', 'Author': 'Foo Ltd.', 'Art': '', 'Icon': 'foo.svg', 'SearchHint': 'Search Foo', 'HotKey': 'h', 'IdleTimeout': '1234', 'Invisible': 'false', 'LocationDataNeeded': 'false', 'ResultsTtlType': 'small', } return config_dict def test_check_scope_ini(self): '''Test check_scope_ini()''' scope = self._create_scope(self._stub_config()) self.set_test_scope(self.default_appname, scope) c = ClickReviewScope(self.test_name) c.check_scope_ini() r = c.click_report expected_counts = {'info': 4, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_scope_ini_missing_required1(self): '''Test check_scope_ini() - missing Description''' config = self._stub_config() del config['Description'] scope = self._create_scope(config) self.set_test_scope(self.default_appname, scope) c = ClickReviewScope(self.test_name) c.check_scope_ini() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_scope_ini_missing_required2(self): '''Test check_scope_ini() - missing DisplayName''' config = self._stub_config() del config['DisplayName'] scope = self._create_scope(config) self.set_test_scope(self.default_appname, scope) c = ClickReviewScope(self.test_name) c.check_scope_ini() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_scope_ini_missing_required3(self): '''Test check_scope_ini() - missing Author''' config = self._stub_config() del config['Author'] scope = self._create_scope(config) self.set_test_scope(self.default_appname, scope) c = ClickReviewScope(self.test_name) c.check_scope_ini() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_scope_ini_missing_required4(self): '''Test check_scope_ini() - missing multiple''' config = self._stub_config() del config['Description'] del config['DisplayName'] del config['Author'] scope = self._create_scope(config) self.set_test_scope(self.default_appname, scope) c = ClickReviewScope(self.test_name) c.check_scope_ini() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_scope_ini_translated_field(self): '''Test check_scope_ini() - translated field - es''' config = self._stub_config() config['searchhint[es]'] = "foo" scope = self._create_scope(config) self.set_test_scope(self.default_appname, scope) c = ClickReviewScope(self.test_name) c.check_scope_ini() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_scope_ini_translated_field2(self): '''Test check_scope_ini() - translated field - pt_br''' config = self._stub_config() config['searchhint[pt_br]'] = "foo" scope = self._create_scope(config) self.set_test_scope(self.default_appname, scope) c = ClickReviewScope(self.test_name) c.check_scope_ini() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_scope_ini_translated_field3(self): '''Test check_scope_ini() - translated field - ast''' config = self._stub_config() config['searchhint[ast]'] = "foo" scope = self._create_scope(config) self.set_test_scope(self.default_appname, scope) c = ClickReviewScope(self.test_name) c.check_scope_ini() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_scope_ini_bad_translated_field(self): '''Test check_scope_ini() - bad translated field''' config = self._stub_config() config['searchhint[ba;r]'] = "foo" scope = self._create_scope(config) self.set_test_scope(self.default_appname, scope) c = ClickReviewScope(self.test_name) c.check_scope_ini() r = c.click_report expected_counts = {'info': None, 'warn': 1, 'error': 0} self.check_results(r, expected_counts) def test_check_scope_ini_bad_translated_field2(self): '''Test check_scope_ini() - translated field - de_DE''' config = self._stub_config() config['searchhint[de_DE]'] = "foo" scope = self._create_scope(config) self.set_test_scope(self.default_appname, scope) c = ClickReviewScope(self.test_name) c.check_scope_ini() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_scope_ini_nonexistent_field(self): '''Test check_scope_ini() - non-existent field''' config = self._stub_config() config['nonexistent'] = "foo" scope = self._create_scope(config) self.set_test_scope(self.default_appname, scope) c = ClickReviewScope(self.test_name) c.check_scope_ini() r = c.click_report expected_counts = {'info': None, 'warn': 1, 'error': 0} self.check_results(r, expected_counts) def test_check_scope_ini_forbidden_field(self): '''Test check_scope_ini() - forbidden field''' config = self._stub_config() config['debugmode'] = "true" scope = self._create_scope(config) self.set_test_scope(self.default_appname, scope) c = ClickReviewScope(self.test_name) c.check_scope_ini() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_peer_hooks(self): '''Test check_peer_hooks()''' c = ClickReviewScope(self.test_name) # create a new hooks database for our peer hooks tests tmp = dict() # add our hook tmp["scope"] = "scope.ini" # add any required peer hooks tmp["apparmor"] = "foo.apparmor" # update the manifest and test_manifest c.manifest["hooks"][self.default_appname] = tmp self._update_test_manifest() # do the test c.check_peer_hooks() r = c.click_report # We should end up with 2 info expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_peer_hooks_disallowed(self): '''Test check_peer_hooks() - disallowed''' c = ClickReviewScope(self.test_name) # create a new hooks database for our peer hooks tests tmp = dict() # add our hook tmp["scope"] = "scope.ini" # add any required peer hooks tmp["apparmor"] = "foo.apparmor" # add something not allowed tmp["desktop"] = "foo.desktop" # update the manifest and test_manifest c.manifest["hooks"][self.default_appname] = tmp self._update_test_manifest() # do the test c.check_peer_hooks() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_peer_hooks_required(self): '''Test check_peer_hooks() - required''' c = ClickReviewScope(self.test_name) # create a new hooks database for our peer hooks tests tmp = dict() # add our hook tmp["scope"] = "scope.ini" # skip adding required hooks # update the manifest and test_manifest c.manifest["hooks"][self.default_appname] = tmp self._update_test_manifest() # do the test c.check_peer_hooks() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_scope_ini_snappy_1504(self): '''Test check_scope_ini() - snappy 15.04''' self.set_test_pkgfmt("snap", "15.04") scope = self._create_scope(self._stub_config()) self.set_test_scope(self.default_appname, scope) c = ClickReviewScope(self.test_name) c.check_scope_ini() r = c.click_report expected_counts = {'info': 4, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) click-reviewers-tools-0.44~16.04.1/clickreviews/tests/test_cr_url_dispatcher.py0000664000000000000000000002713512666350676024505 0ustar '''test_cr_url dispatcher.py: tests for the cr_url_dispatcher module''' # # Copyright (C) 2014 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from clickreviews.cr_url_dispatcher import ClickReviewUrlDispatcher import clickreviews.cr_tests as cr_tests class TestClickReviewUrlDispatcher(cr_tests.TestClickReview): """Tests for the lint review tool.""" def test_check_required(self): '''Test check_required() - has protocol''' self.set_test_url_dispatcher(self.default_appname, key="protocol", value="some-protocol") c = ClickReviewUrlDispatcher(self.test_name) c.check_required() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_required_empty_value(self): '''Test check_required() - empty protocol''' self.set_test_url_dispatcher(self.default_appname, key="protocol", value="") c = ClickReviewUrlDispatcher(self.test_name) c.check_required() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_required_bad_value(self): '''Test check_required() - bad protocol''' self.set_test_url_dispatcher(self.default_appname, key="protocol", value=[]) c = ClickReviewUrlDispatcher(self.test_name) c.check_required() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_required_multiple(self): '''Test check_required() - multiple''' self.set_test_url_dispatcher(self.default_appname, key="protocol", value="some-protocol") self.set_test_url_dispatcher(self.default_appname, key="domain-suffix", value="example.com", append=True) c = ClickReviewUrlDispatcher(self.test_name) c.check_required() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_required_multiple_with_nonexiting(self): '''Test check_required() - multiple with nonexistent''' self.set_test_url_dispatcher(self.default_appname, key="protocol", value="some-protocol") self.set_test_url_dispatcher(self.default_appname, key="domain-suffix", value="example.com", append=True) self.set_test_url_dispatcher(self.default_appname, key="nonexistent", value="foo", append=True) c = ClickReviewUrlDispatcher(self.test_name) c.check_required() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_optional_none(self): '''Test check_optional() - protocol only''' self.set_test_url_dispatcher(self.default_appname, key="protocol", value="some-protocol") c = ClickReviewUrlDispatcher(self.test_name) c.check_optional() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_optional_domain_suffix_empty(self): '''Test check_optional() - with empty domain-suffix''' self.set_test_url_dispatcher(self.default_appname, key="protocol", value="some-protocol") self.set_test_url_dispatcher(self.default_appname, key="domain-suffix", value="", append=True) c = ClickReviewUrlDispatcher(self.test_name) c.check_optional() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_optional_domain_suffix_bad(self): '''Test check_optional() - with bad domain-suffix''' self.set_test_url_dispatcher(self.default_appname, key="protocol", value="some-protocol") self.set_test_url_dispatcher(self.default_appname, key="domain-suffix", value=[], append=True) c = ClickReviewUrlDispatcher(self.test_name) c.check_optional() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_optional_domain_suffix_nonexistent(self): '''Test check_optional() - with domain-suffix plus nonexistent''' self.set_test_url_dispatcher(self.default_appname, key="protocol", value="some-protocol") self.set_test_url_dispatcher(self.default_appname, key="domain-suffix", value="example.com", append=True) self.set_test_url_dispatcher(self.default_appname, key="nonexistent", value="foo", append=True) c = ClickReviewUrlDispatcher(self.test_name) c.check_optional() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_optional_domain_suffix_without_protocol(self): '''Test check_optional() - with domain-suffix, no protocol''' self.set_test_url_dispatcher(self.default_appname, key="domain-suffix", value="example.com") c = ClickReviewUrlDispatcher(self.test_name) c.check_optional() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_optional_domain_suffix_without_protocol2(self): '''Test check_optional() - with domain-suffix, nonexistent, no protocol''' self.set_test_url_dispatcher(self.default_appname, key="domain-suffix", value="example.com") self.set_test_url_dispatcher(self.default_appname, key="nonexistent", value="example.com", append=True) c = ClickReviewUrlDispatcher(self.test_name) c.check_optional() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_unknown(self): '''Test check_unknown()''' self.set_test_url_dispatcher(self.default_appname, key="nonexistent", value="foo") c = ClickReviewUrlDispatcher(self.test_name) c.check_unknown() r = c.click_report expected_counts = {'info': 0, 'warn': 1, 'error': 0} self.check_results(r, expected_counts) def test_check_unknown_multiple(self): '''Test check_unknown() - multiple with nonexistent''' self.set_test_url_dispatcher(self.default_appname, key="protocol", value="some-protocol") self.set_test_url_dispatcher(self.default_appname, key="domain-suffix", value="example.com", append=True) self.set_test_url_dispatcher(self.default_appname, key="nonexistent", value="foo", append=True) c = ClickReviewUrlDispatcher(self.test_name) c.check_unknown() r = c.click_report expected_counts = {'info': 0, 'warn': 1, 'error': 0} self.check_results(r, expected_counts) def test_check_peer_hooks(self): '''Test check_peer_hooks()''' self.set_test_url_dispatcher(self.default_appname, key="protocol", value="some-protocol") c = ClickReviewUrlDispatcher(self.test_name) # create a new hooks database for our peer hooks tests tmp = dict() # add our hook tmp["urls"] = \ self.test_manifest["hooks"][self.default_appname]["urls"] # update the manifest and test_manifest c.manifest["hooks"][self.default_appname] = tmp self._update_test_manifest() # do the test c.check_peer_hooks() r = c.click_report # We should end up with 2 info expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_peer_hooks_disallowed(self): '''Test check_peer_hooks() - disallowed''' self.set_test_url_dispatcher(self.default_appname, key="protocol", value="some-protocol") c = ClickReviewUrlDispatcher(self.test_name) # create a new hooks database for our peer hooks tests tmp = dict() # add our hook tmp["urls"] = \ self.test_manifest["hooks"][self.default_appname]["urls"] # add something not allowed tmp["nonexistent"] = "nonexistent-hook" c.manifest["hooks"][self.default_appname] = tmp self._update_test_manifest() # do the test c.check_peer_hooks() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_required_snappy_1504(self): '''Test check_required() - has protocol - snappy 15.04''' self.set_test_pkgfmt("snap", "15.04") self.set_test_url_dispatcher(self.default_appname, key="protocol", value="some-protocol") c = ClickReviewUrlDispatcher(self.test_name) c.check_required() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) click-reviewers-tools-0.44~16.04.1/clickreviews/tests/test_cr_push_helper.py0000664000000000000000000001773512666350676024020 0ustar '''test_cr_push_helper.py: tests for the cr_push_helper module''' # # Copyright (C) 2013 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from clickreviews.cr_push_helper import ClickReviewPushHelper import clickreviews.cr_tests as cr_tests class TestClickReviewPushHelper(cr_tests.TestClickReview): """Tests for the lint review tool.""" def test_check_unknown_keys_none(self): '''Test check_unknown() - no unknown''' self.set_test_push_helper(self.default_appname, "exec", "foo") c = ClickReviewPushHelper(self.test_name) c.check_unknown_keys() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_unknown_keys1(self): '''Test check_unknown() - one unknown''' self.set_test_push_helper(self.default_appname, "nonexistent", "foo") c = ClickReviewPushHelper(self.test_name) c.check_unknown_keys() r = c.click_report expected_counts = {'info': 0, 'warn': 1, 'error': 0} self.check_results(r, expected_counts) def test_check_unknown_keys2(self): '''Test check_unknown() - good with one unknown''' self.set_test_push_helper(self.default_appname, "exec", "foo") self.set_test_push_helper(self.default_appname, "nonexistent", "foo") c = ClickReviewPushHelper(self.test_name) c.check_unknown_keys() r = c.click_report expected_counts = {'info': 0, 'warn': 1, 'error': 0} self.check_results(r, expected_counts) def test_check_valid_exec(self): '''Test check_valid() - exec''' self.set_test_push_helper(self.default_appname, "exec", "foo") c = ClickReviewPushHelper(self.test_name) c.check_valid() r = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_valid_missing_exec(self): '''Test check_valid() - missing exec''' self.set_test_push_helper(self.default_appname, "app_id", "foo_foo") c = ClickReviewPushHelper(self.test_name) c.check_valid() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_valid_app_id(self): '''Test check_valid() - app_id''' self.set_test_push_helper(self.default_appname, "exec", "foo") self.set_test_push_helper(self.default_appname, "app_id", "foo_foo") c = ClickReviewPushHelper(self.test_name) c.check_valid() r = c.click_report expected_counts = {'info': 3, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_valid_bad_value(self): '''Test check_valid() - bad value''' self.set_test_push_helper(self.default_appname, "exec", []) c = ClickReviewPushHelper(self.test_name) c.check_valid() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_valid_empty_value(self): '''Test check_valid() - empty value''' self.set_test_push_helper(self.default_appname, "exec", "foo") self.set_test_push_helper(self.default_appname, "app_id", "") c = ClickReviewPushHelper(self.test_name) c.check_valid() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_valid_empty_value2(self): '''Test check_valid() - empty value''' self.set_test_push_helper(self.default_appname, "exec", "") self.set_test_push_helper(self.default_appname, "app_id", "foo_foo") c = ClickReviewPushHelper(self.test_name) c.check_valid() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_hooks(self): '''Test check_hooks()''' self.set_test_push_helper(self.default_appname, "exec", "foo") c = ClickReviewPushHelper(self.test_name) # remove hooks that are added by the framework c.manifest['hooks'][self.default_appname].pop('desktop') c.manifest['hooks'][self.default_appname].pop('urls') c.check_hooks() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_hooks_bad(self): '''Test check_hooks() - bad''' self.set_test_push_helper(self.default_appname, "exec", "foo") c = ClickReviewPushHelper(self.test_name) # The desktop and urls hooks are specified by default in the framework, # so just running this without other setup should generate an error c.check_hooks() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_peer_hooks(self): '''Test check_peer_hooks()''' c = ClickReviewPushHelper(self.test_name) # create a new hooks database for our peer hooks tests tmp = dict() # add our hook tmp["push-helper"] = "foo.push-helper" # add any required peer hooks tmp["apparmor"] = "foo.apparmor" # update the manifest and test_manifest c.manifest["hooks"][self.default_appname] = tmp self._update_test_manifest() # do the test c.check_peer_hooks() r = c.click_report # We should end up with 2 info expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_peer_hooks_disallowed(self): '''Test check_peer_hooks() - disallowed''' c = ClickReviewPushHelper(self.test_name) # create a new hooks database for our peer hooks tests tmp = dict() # add our hook tmp["push-helper"] = "foo.push-helper" # add any required peer hooks tmp["apparmor"] = "foo.apparmor" # add something not allowed tmp["nonexistent"] = "nonexistent-hook" c.manifest["hooks"][self.default_appname] = tmp self._update_test_manifest() # do the test c.check_peer_hooks() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_peer_hooks_required(self): '''Test check_peer_hooks() - required''' c = ClickReviewPushHelper(self.test_name) # create a new hooks database for our peer hooks tests tmp = dict() # add our hook tmp["push-helper"] = "foo.push-helper" # skip adding required hooks c.manifest["hooks"][self.default_appname] = tmp self._update_test_manifest() # do the test c.check_peer_hooks() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_valid_exec_snappy_1504(self): '''Test check_valid() - exec - snappy 15.04''' self.set_test_pkgfmt("snap", "15.04") self.set_test_push_helper(self.default_appname, "exec", "foo") c = ClickReviewPushHelper(self.test_name) c.check_valid() r = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) click-reviewers-tools-0.44~16.04.1/clickreviews/tests/test_cr_online_accounts.py0000664000000000000000000006262512666351247024656 0ustar '''test_cr_online_accounts.py: tests for the cr_online accounts module''' # # Copyright (C) 2013 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from clickreviews.cr_online_accounts import ClickReviewAccounts import clickreviews.cr_tests as cr_tests import json import lxml.etree as etree class TestClickReviewAccounts(cr_tests.TestClickReview): """Tests for the lint review tool.""" def _stub_application(self, root=None, id=None, do_subtree=True): '''Stub application xml''' if root is None: root = "application" if id == "" or id is None: xml = etree.Element(root) else: xml = etree.Element(root, id="%s" % id) if do_subtree: services = etree.SubElement(xml, "services") if id is None: elem1 = etree.SubElement(services, "service") else: elem1 = etree.SubElement(services, "service", id="element1") desc1 = etree.SubElement(elem1, "description") desc1.text = "elem1 description" if id is None: elem2 = etree.SubElement(services, "service") else: elem2 = etree.SubElement(services, "service", id="element2") desc2 = etree.SubElement(elem2, "description") desc2.text = "elem2 description" return xml def _stub_service(self, root=None, id=None, do_subtree=True): '''Stub service xml''' if root is None: root = "service" if id == "" or id is None: xml = etree.Element(root) else: xml = etree.Element(root, id="%s" % id) if do_subtree: service_name = etree.SubElement(xml, "name") service_name.text = "Foo" service_provider = etree.SubElement(xml, "provider") service_provider.text = "some-provider" return xml def _stub_provider(self, root=None, id=None, do_subtree=True): '''Stub provider xml''' if root is None: root = "provider" if id == "" or id is None: xml = etree.Element(root) else: xml = etree.Element(root, id="%s" % id) if do_subtree: service_name = etree.SubElement(xml, "name") service_name.text = "Foo" service_plugin = etree.SubElement(xml, "plugin") service_plugin.text = "generic-oauth" service_domains = etree.SubElement(xml, "domains") service_domains.text = ".*\.example\.com" # More can go here, see /usr/share/accounts/providers/* return xml def test_check_hooks_versions_new(self): '''Test check_hooks_versions() - new hook''' self.set_test_manifest("framework", "ubuntu-sdk-15.04.1") self.set_test_account(self.default_appname, "accounts", dict()) c = ClickReviewAccounts(self.test_name) c.check_hooks_versions() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_hooks_versions_deprecated_service(self): '''Test check_hooks_versions() - deprecated -service hook''' self.set_test_manifest("framework", "ubuntu-sdk-15.10") self.set_test_account(self.default_appname, "account-service", dict()) c = ClickReviewAccounts(self.test_name) c.check_hooks_versions() r = c.click_report expected_counts = {'info': 0, 'warn': 1, 'error': 0} self.check_results(r, expected_counts) def test_check_hooks_versions_disallowed_service(self): '''Test check_hooks_versions() - deprecated -service hook''' self.set_test_manifest("framework", "ubuntu-sdk-16.10") self.set_test_account(self.default_appname, "account-service", dict()) c = ClickReviewAccounts(self.test_name) c.check_hooks_versions() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_hooks_versions_deprecated_application(self): '''Test check_hooks_versions() - deprecated -application hook''' self.set_test_manifest("framework", "ubuntu-sdk-15.10") self.set_test_account(self.default_appname, "account-application", dict()) c = ClickReviewAccounts(self.test_name) c.check_hooks_versions() r = c.click_report expected_counts = {'info': 0, 'warn': 1, 'error': 0} self.check_results(r, expected_counts) def test_check_hooks_versions_old_framework(self): '''Test check_hooks_versions() - deprecated -application hook''' self.set_test_manifest("framework", "ubuntu-sdk-15.04") self.set_test_account(self.default_appname, "account-application", dict()) self.set_test_account(self.default_appname, "account-service", dict()) c = ClickReviewAccounts(self.test_name) c.check_hooks_versions() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_manifest(self): '''Test check_manifest()''' data = json.loads('''{ "translations": "my-app", "services": [ { "name": "Example", "provider": "myapp.com_example", "description": "publish my photos in example.com", "auth": { "oauth2/web_server": { "ClientId": "foo", "ClientSecret": "bar", "UseSSL": false, "Scopes": ["one scope","and another"] } } }, { "provider": "becool" } ], "plugin": { "name": "Example site", "icon": "example.png", "qml": "qml_files" } }''') self.set_test_account(self.default_appname, "accounts", data) c = ClickReviewAccounts(self.test_name) c.check_manifest() r = c.click_report expected_counts = {'info': 3, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_manifest_not_specified(self): '''Test check_manifest() - not specified''' c = ClickReviewAccounts(self.test_name) c.check_manifest() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_manifest_missing_services(self): '''Test check_manifest() - missing services''' data = json.loads('''{ "translations": "my-app" }''') self.set_test_account(self.default_appname, "accounts", data) c = ClickReviewAccounts(self.test_name) c.check_manifest() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_manifest_invalid_services(self): '''Test check_manifest() - invalid services''' data = json.loads('''{ "services": 12 }''') self.set_test_account(self.default_appname, "accounts", data) c = ClickReviewAccounts(self.test_name) c.check_manifest() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_manifest_empty_services(self): '''Test check_manifest() - empty services''' data = json.loads('''{ "services": [] }''') self.set_test_account(self.default_appname, "accounts", data) c = ClickReviewAccounts(self.test_name) c.check_manifest() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_manifest_empty_service(self): '''Test check_manifest() - empty services''' data = json.loads('''{ "services": [{}] }''') self.set_test_account(self.default_appname, "accounts", data) c = ClickReviewAccounts(self.test_name) c.check_manifest() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_manifest_no_provider(self): '''Test check_manifest() - no provider''' data = json.loads('''{ "services": [{ "name": "Example", "description": "Hello world" }] }''') self.set_test_account(self.default_appname, "accounts", data) c = ClickReviewAccounts(self.test_name) c.check_manifest() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_manifest_invalid_provider(self): '''Test check_manifest() - invalid provider''' data = json.loads('''{ "services": [{ "name": "Example", "provider": "no/slashes.please", "description": "Hello world" }] }''') self.set_test_account(self.default_appname, "accounts", data) c = ClickReviewAccounts(self.test_name) c.check_manifest() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_manifest_unknown_key(self): '''Test check_manifest() - unknown key''' data = json.loads('''{ "services": [{ "name": "Example", "provider": "example", "description": "Hello world", "intruder": "Who, me?" }] }''') self.set_test_account(self.default_appname, "accounts", data) c = ClickReviewAccounts(self.test_name) c.check_manifest() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_application(self): '''Test check_application()''' xml = self._stub_application() # print(etree.tostring(xml)) self.set_test_account(self.default_appname, "account-application", xml) c = ClickReviewAccounts(self.test_name) c.check_application() r = c.click_report expected_counts = {'info': 4, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_application_snappy_1504(self): '''Test check_application() - snappy 15.04''' self.set_test_pkgfmt("snap", "15.04") xml = self._stub_application() # print(etree.tostring(xml)) self.set_test_account(self.default_appname, "account-application", xml) c = ClickReviewAccounts(self.test_name) c.check_application() r = c.click_report expected_counts = {'info': 4, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_application_not_specified(self): '''Test check_application() - not specified''' c = ClickReviewAccounts(self.test_name) c.check_application() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_application_has_id(self): '''Test check_application() - has id''' xml = self._stub_application(id="%s_%s" % (self.test_manifest["name"], self.default_appname)) self.set_test_account(self.default_appname, "account-application", xml) c = ClickReviewAccounts(self.test_name) c.check_application() r = c.click_report expected_counts = {'info': None, 'warn': 1, 'error': 0} self.check_results(r, expected_counts) def test_check_application_wrong_root(self): '''Test check_application() - wrong root''' xml = self._stub_application(root="wrongroot") self.set_test_account(self.default_appname, "account-application", xml) c = ClickReviewAccounts(self.test_name) c.check_application() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_application_missing_services(self): '''Test check_application() - missing services''' xml = self._stub_application(do_subtree=False) sometag = etree.SubElement(xml, "sometag") elem1 = etree.SubElement(sometag, "something", id="element1") desc1 = etree.SubElement(elem1, "description") desc1.text = "elem1 description" self.set_test_account(self.default_appname, "account-application", xml) c = ClickReviewAccounts(self.test_name) c.check_application() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_application_missing_service(self): '''Test check_application() - missing service''' xml = self._stub_application(do_subtree=False) services = etree.SubElement(xml, "services") elem1 = etree.SubElement(services, "somesubtag", id="element1") desc1 = etree.SubElement(elem1, "description") desc1.text = "elem1 description" self.set_test_account(self.default_appname, "account-application", xml) c = ClickReviewAccounts(self.test_name) c.check_application() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_service(self): '''Test check_service()''' xml = self._stub_service() self.set_test_account(self.default_appname, "account-service", xml) xml = self._stub_application() self.set_test_account(self.default_appname, "account-application", xml) c = ClickReviewAccounts(self.test_name) c.check_service() r = c.click_report expected_counts = {'info': 4, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_service_not_specified(self): '''Test check_service() - not specified''' c = ClickReviewAccounts(self.test_name) c.check_service() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_service_has_id(self): '''Test check_service() - has id''' xml = self._stub_service(id="%s_%s" % (self.test_manifest["name"], self.default_appname)) self.set_test_account(self.default_appname, "account-service", xml) xml = self._stub_application() self.set_test_account(self.default_appname, "account-application", xml) c = ClickReviewAccounts(self.test_name) c.check_service() r = c.click_report expected_counts = {'info': None, 'warn': 1, 'error': 0} self.check_results(r, expected_counts) def test_check_service_wrong_root(self): '''Test check_service() - wrong root''' xml = self._stub_service(root="wrongroot") self.set_test_account(self.default_appname, "account-service", xml) xml = self._stub_application() self.set_test_account(self.default_appname, "account-application", xml) c = ClickReviewAccounts(self.test_name) c.check_service() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_service_missing_name(self): '''Test check_service() - missing name''' xml = self._stub_service(do_subtree=False) service_provider = etree.SubElement(xml, "provider") service_provider.text = "some-provider" self.set_test_account(self.default_appname, "account-service", xml) xml = self._stub_application() self.set_test_account(self.default_appname, "account-application", xml) c = ClickReviewAccounts(self.test_name) c.check_service() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_service_missing_provider(self): '''Test check_service() - missing provider''' xml = self._stub_service(do_subtree=False) service_name = etree.SubElement(xml, "name") service_name.text = "Foo" self.set_test_account(self.default_appname, "account-service", xml) xml = self._stub_application() self.set_test_account(self.default_appname, "account-application", xml) c = ClickReviewAccounts(self.test_name) c.check_service() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_provider(self): '''Test check_provider()''' xml = self._stub_provider() self.set_test_account(self.default_appname, "account-provider", xml) self.set_test_account(self.default_appname, "account-qml-plugin", True) c = ClickReviewAccounts(self.test_name) c.check_provider() r = c.click_report expected_counts = {'info': 3, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_provider_has_id(self): '''Test check_provider() - has id''' xml = self._stub_provider(id="%s_%s" % (self.test_manifest["name"], self.default_appname)) self.set_test_account(self.default_appname, "account-provider", xml) self.set_test_account(self.default_appname, "account-qml-plugin", True) c = ClickReviewAccounts(self.test_name) c.check_provider() r = c.click_report expected_counts = {'info': 2, 'warn': 1, 'error': 0} self.check_results(r, expected_counts) def test_check_qml_plugin(self): '''Test check_qml_plugin()''' self.set_test_account(self.default_appname, "account-qml-plugin", True) xml = self._stub_provider() self.set_test_account(self.default_appname, "account-provider", xml) c = ClickReviewAccounts(self.test_name) c.check_qml_plugin() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_peer_hooks_application(self): '''Test check_peer_hooks() - application''' c = ClickReviewAccounts(self.test_name) # create a new hooks database for our peer hooks tests tmp = dict() # add our hook tmp["account-application"] = "foo.application" # add any required peer hooks tmp["apparmor"] = "foo.apparmor" # update the manifest and test_manifest c.manifest["hooks"][self.default_appname] = tmp self._update_test_manifest() # do the test c.check_peer_hooks(["account-application"]) r = c.click_report # We should end up with 8 info expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_peer_hooks_application_disallowed(self): '''Test check_peer_hooks() - disallowed (application)''' c = ClickReviewAccounts(self.test_name) # create a new hooks database for our peer hooks tests tmp = dict() # add our hook tmp["account-application"] = "foo.application" # add any required peer hooks tmp["apparmor"] = "foo.apparmor" # add something not allowed tmp["nonexistent"] = "nonexistent-hook" c.manifest["hooks"][self.default_appname] = tmp self._update_test_manifest() # do the test c.check_peer_hooks(["account-application"]) r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_peer_hooks_application_required(self): '''Test check_peer_hooks() - required (application)''' c = ClickReviewAccounts(self.test_name) # create a new hooks database for our peer hooks tests tmp = dict() # add our hook tmp["account-application"] = "foo.application" # skip adding required hooks c.manifest["hooks"][self.default_appname] = tmp self._update_test_manifest() # do the test c.check_peer_hooks(["account-application"]) r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_peer_hooks_service(self): '''Test check_peer_hooks() - service''' c = ClickReviewAccounts(self.test_name) # create a new hooks database for our peer hooks tests tmp = dict() # add our hook tmp["account-service"] = "foo.service" # add any required peer hooks tmp["account-application"] = "foo.application" tmp["apparmor"] = "foo.apparmor" # update the manifest and test_manifest c.manifest["hooks"][self.default_appname] = tmp self._update_test_manifest() # do the test c.check_peer_hooks(["account-service"]) r = c.click_report # We should end up with 8 info expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_peer_hooks_service_disallowed(self): '''Test check_peer_hooks() - disallowed (service)''' c = ClickReviewAccounts(self.test_name) # create a new hooks database for our peer hooks tests tmp = dict() # add our hook tmp["account-service"] = "foo.service" # add any required peer hooks tmp["account-application"] = "foo.application" tmp["apparmor"] = "foo.apparmor" # add something not allowed tmp["nonexistent"] = "nonexistent-hook" c.manifest["hooks"][self.default_appname] = tmp self._update_test_manifest() # do the test c.check_peer_hooks(["account-service"]) r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_peer_hooks_service_required(self): '''Test check_peer_hooks() - required (service)''' c = ClickReviewAccounts(self.test_name) # create a new hooks database for our peer hooks tests tmp = dict() # add our hook tmp["account-service"] = "foo.service" # skip adding required hooks c.manifest["hooks"][self.default_appname] = tmp self._update_test_manifest() # do the test c.check_peer_hooks(["account-service"]) r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_peer_hooks_provider(self): '''Test check_peer_hooks() - provider''' c = ClickReviewAccounts(self.test_name) # create a new hooks database for our peer hooks tests tmp = dict() # add our hook tmp["account-provider"] = "foo.provider" # add any required peer hooks tmp["account-qml-plugin"] = "foo.qml_plugin" tmp["apparmor"] = "foo.apparmor" # update the manifest and test_manifest c.manifest["hooks"][self.default_appname] = tmp self._update_test_manifest() # do the test c.check_peer_hooks(["account-provider"]) r = c.click_report # We should end up with 8 info expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_peer_hooks_provider_disallowed(self): '''Test check_peer_hooks() - disallowed (provider)''' c = ClickReviewAccounts(self.test_name) # create a new hooks database for our peer hooks tests tmp = dict() # add our hook tmp["account-provider"] = "foo.provider" # add any required peer hooks tmp["account-qml-plugin"] = "foo.qml_plugin" tmp["apparmor"] = "foo.apparmor" # add something not allowed tmp["nonexistent"] = "nonexistent-hook" c.manifest["hooks"][self.default_appname] = tmp self._update_test_manifest() # do the test c.check_peer_hooks(["account-provider"]) r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_peer_hooks_provider_required(self): '''Test check_peer_hooks() - required (provider)''' c = ClickReviewAccounts(self.test_name) # create a new hooks database for our peer hooks tests tmp = dict() # add our hook tmp["account-provider"] = "foo.provider" # skip adding required hooks c.manifest["hooks"][self.default_appname] = tmp self._update_test_manifest() # do the test c.check_peer_hooks(["account-provider"]) r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) click-reviewers-tools-0.44~16.04.1/clickreviews/tests/test_bbb_example_sr_skeleton.py0000664000000000000000000000473712666350676025664 0ustar '''test_sr_skeleton.py: tests for the sr_skeleton module''' # # Copyright (C) 2014-2016 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from clickreviews.sr_skeleton import SnapReviewSkeleton import clickreviews.sr_tests as sr_tests class TestSnapReviewSkeleton(sr_tests.TestSnapReview): """Tests for the lint review tool.""" def test_check_foo(self): '''Test check_foo()''' c = SnapReviewSkeleton(self.test_name) c.check_foo() r = c.click_report # We should end up with 1 info expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_bar(self): '''Test check_bar()''' c = SnapReviewSkeleton(self.test_name) c.check_bar() r = c.click_report # We should end up with 1 error expected_counts = {'info': 0, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_baz(self): '''Test check_baz()''' c = SnapReviewSkeleton(self.test_name) c.check_baz() r = c.click_report # We should end up with 1 warning expected_counts = {'info': 0, 'warn': 1, 'error': 0} self.check_results(r, expected_counts) # Check specific entries expected = dict() expected['info'] = dict() expected['warn'] = dict() name = c._get_check_name('baz') expected['warn'][name] = {"text": "TODO", "link": "http://example.com"} expected['error'] = dict() self.check_results(r, expected=expected) def test_output(self): '''Test output''' # Update the control field and output the changes self._update_test_name() import pprint import yaml print(''' = test output = == Mock filename == %s == Mock meta/snap.yaml == ''' % (self.test_name)) pprint.pprint(yaml.load(sr_tests.TEST_SNAP_YAML)) click-reviewers-tools-0.44~16.04.1/clickreviews/tests/test_cr_content_hub.py0000664000000000000000000001552712666350676024007 0ustar '''test_cr_content_hub.py: tests for the cr_content-hub module''' # # Copyright (C) 2013 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from clickreviews.cr_content_hub import ClickReviewContentHub import clickreviews.cr_tests as cr_tests class TestClickReviewContentHub(cr_tests.TestClickReview): """Tests for the lint review tool.""" def test_check_unknown_keys_none(self): '''Test check_unknown() - no unknown''' self.set_test_content_hub(self.default_appname, "source", "pictures") c = ClickReviewContentHub(self.test_name) c.check_unknown_keys() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_unknown_keys1(self): '''Test check_unknown() - one unknown''' self.set_test_content_hub(self.default_appname, "nonexistent", "foo") c = ClickReviewContentHub(self.test_name) c.check_unknown_keys() r = c.click_report expected_counts = {'info': 0, 'warn': 1, 'error': 0} self.check_results(r, expected_counts) def test_check_unknown_keys2(self): '''Test check_unknown() - good with one unknown''' self.set_test_content_hub(self.default_appname, "source", "pictures") self.set_test_content_hub(self.default_appname, "nonexistent", "foo") c = ClickReviewContentHub(self.test_name) c.check_unknown_keys() r = c.click_report expected_counts = {'info': 0, 'warn': 1, 'error': 0} self.check_results(r, expected_counts) def test_check_valid_source(self): '''Test check_valid() - source''' self.set_test_content_hub(self.default_appname, "source", "pictures") c = ClickReviewContentHub(self.test_name) c.check_valid() r = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_valid_share(self): '''Test check_valid() - share''' self.set_test_content_hub(self.default_appname, "share", "pictures") c = ClickReviewContentHub(self.test_name) c.check_valid() r = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_valid_destination(self): '''Test check_valid() - destination''' self.set_test_content_hub(self.default_appname, "destination", "pictures") c = ClickReviewContentHub(self.test_name) c.check_valid() r = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_valid_all(self): '''Test check_valid() - all''' self.set_test_content_hub(self.default_appname, "destination", "pictures") self.set_test_content_hub(self.default_appname, "share", "pictures") self.set_test_content_hub(self.default_appname, "source", "pictures") c = ClickReviewContentHub(self.test_name) c.check_valid() r = c.click_report expected_counts = {'info': 6, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_valid_bad_value(self): '''Test check_valid() - bad value''' self.set_test_content_hub(self.default_appname, "destination", []) c = ClickReviewContentHub(self.test_name) c.check_valid() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_valid_empty_value(self): '''Test check_valid() - empty value''' self.set_test_content_hub(self.default_appname, "source", "") c = ClickReviewContentHub(self.test_name) c.check_valid() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_peer_hooks(self): '''Test check_peer_hooks()''' self.set_test_content_hub(self.default_appname, "destination", "pictures") self.set_test_content_hub(self.default_appname, "share", "pictures") self.set_test_content_hub(self.default_appname, "source", "pictures") # create a new hooks database for our peer hooks tests tmp = dict() # add our hook tmp["content-hub"] = \ self.test_manifest["hooks"][self.default_appname]["content-hub"] self.test_manifest["hooks"][self.default_appname] = tmp self._update_test_manifest() # do the test c = ClickReviewContentHub(self.test_name) c.check_peer_hooks() r = c.click_report # We should end up with 2 info expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_peer_hooks_disallowed(self): '''Test check_peer_hooks() - disallowed''' self.set_test_content_hub(self.default_appname, "destination", "pictures") self.set_test_content_hub(self.default_appname, "share", "pictures") self.set_test_content_hub(self.default_appname, "source", "pictures") c = ClickReviewContentHub(self.test_name) # create a new hooks database for our peer hooks tests tmp = dict() # add our hook tmp["content-hub"] = \ self.test_manifest["hooks"][self.default_appname]["content-hub"] # add something not allowed tmp["nonexistent"] = "nonexistent-hook" c.manifest["hooks"][self.default_appname] = tmp self._update_test_manifest() # do the test c.check_peer_hooks() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_valid_snappy_1504(self): '''Test check_valid() - snappy 15.04''' self.set_test_pkgfmt("snap", "15.04") self.set_test_content_hub(self.default_appname, "destination", "pictures") self.set_test_content_hub(self.default_appname, "share", "pictures") self.set_test_content_hub(self.default_appname, "source", "pictures") c = ClickReviewContentHub(self.test_name) c.check_valid() r = c.click_report expected_counts = {'info': 6, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) click-reviewers-tools-0.44~16.04.1/clickreviews/tests/test_cr_framework.py0000664000000000000000000002106512666350676023466 0ustar '''test_cr_framework.py: tests for the cr_framework module''' # # Copyright (C) 2014 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from clickreviews.cr_framework import ClickReviewFramework import clickreviews.cr_tests as cr_tests class TestClickReviewFramework(cr_tests.TestClickReview): """Tests for the framework review tool.""" def setUp(self): super().setUp() self.set_test_pkgfmt("snap", "15.04") def test_framework_hook_obsolete(self): '''Test check_framework_hook_obsolete()''' self.set_test_framework(self.default_appname, "", "") c = ClickReviewFramework(self.test_name) c.check_framework_hook_obsolete() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_snappy_framework_file_obsolete(self): '''Test check_snappy_framework_file_obsolete()''' self.set_test_pkg_yaml("type", "framework") c = ClickReviewFramework(self.test_name) c.check_snappy_framework_file_obsolete() r = c.click_report expected_counts = {'info': 0, 'warn': 1, 'error': 0} self.check_results(r, expected_counts) def test_snappy_framework_depends(self): '''Test check_snappy_framework_depends()''' self.set_test_pkg_yaml("type", "framework") c = ClickReviewFramework(self.test_name) c.check_snappy_framework_depends() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_snappy_framework_depends_bad(self): '''Test check_snappy_framework_depends() - bad''' self.set_test_pkg_yaml("type", "framework") self.set_test_pkg_yaml("frameworks", ['foo']) c = ClickReviewFramework(self.test_name) c.check_snappy_framework_depends() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_snappy_framework_policy(self): '''Test check_snappy_framework_policy()''' self.set_test_pkg_yaml("type", "framework") c = ClickReviewFramework(self.test_name) c.check_snappy_framework_policy() r = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_snappy_framework_policy_missing(self): '''Test check_snappy_framework_policy() - missing''' self.set_test_pkg_yaml("type", "framework") self.set_test_framework_policy({}) c = ClickReviewFramework(self.test_name) c.check_snappy_framework_policy() r = c.click_report expected_counts = {'info': None, 'warn': 1, 'error': 0} self.check_results(r, expected_counts) def test_snappy_framework_policy_unknown(self): '''Test check_snappy_framework_policy() - unknown''' self.set_test_pkg_yaml("type", "framework") self.set_test_framework_policy_unknown(['foo/bar/baz']) c = ClickReviewFramework(self.test_name) c.check_snappy_framework_policy() r = c.click_report expected_counts = {'info': None, 'warn': 1, 'error': 0} self.check_results(r, expected_counts) def test_snappy_framework_policy_metadata(self): '''Test check_snappy_framework_policy_metadata()''' self.set_test_pkg_yaml("type", "framework") c = ClickReviewFramework(self.test_name) c.check_snappy_framework_policy_metadata() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_snappy_framework_policy_metadata_template(self): '''Test check_snappy_framework_policy_metadata() - template missing data ''' self.set_test_pkg_yaml("type", "framework") tmp = self.test_framework_policy tmp['apparmor']['templates']['template-common'] = 'missing data' self.set_test_framework_policy(tmp) c = ClickReviewFramework(self.test_name) c.check_snappy_framework_policy_metadata() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_snappy_framework_policy_metadata_policygroup(self): '''Test check_snappy_framework_policy_metadata() - policygroup missing data ''' self.set_test_pkg_yaml("type", "framework") tmp = self.test_framework_policy tmp['seccomp']['policygroups']['policygroup-reserved'] = 'missing data' self.set_test_framework_policy(tmp) c = ClickReviewFramework(self.test_name) c.check_snappy_framework_policy_metadata() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_snappy_framework_policy_matching(self): '''Test check_snappy_framework_policy_matching()''' self.set_test_pkg_yaml("type", "framework") c = ClickReviewFramework(self.test_name) c.check_snappy_framework_policy_matching() r = c.click_report expected_counts = {'info': 8, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_snappy_framework_policy_matching_missing_aa_template(self): '''Test check_snappy_framework_policy_matching() - missing aa template ''' self.set_test_pkg_yaml("type", "framework") tmp = self.test_framework_policy del tmp['apparmor']['templates']['template-common'] self.set_test_framework_policy(tmp) c = ClickReviewFramework(self.test_name) c.check_snappy_framework_policy_matching() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_snappy_framework_policy_matching_missing_sc_policygroup(self): '''Test check_snappy_framework_policy_matching() - missing sc policy group ''' self.set_test_pkg_yaml("type", "framework") tmp = self.test_framework_policy del tmp['seccomp']['policygroups']['policygroup-reserved'] self.set_test_framework_policy(tmp) c = ClickReviewFramework(self.test_name) c.check_snappy_framework_policy_matching() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_snappy_framework_policy_filenames(self): '''Test check_snappy_framework_policy_filenames()''' self.set_test_pkg_yaml("type", "framework") c = ClickReviewFramework(self.test_name) c.check_snappy_framework_policy_filenames() r = c.click_report expected_counts = {'info': 8, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_snappy_framework_policy_filenames_bad(self): '''Test check_snappy_framework_policy_filenames() - bad name''' self.set_test_pkg_yaml("type", "framework") tmp = self.test_framework_policy tmp['seccomp']['policygroups']['policygroup-res_erved'] = "foo" self.set_test_framework_policy(tmp) c = ClickReviewFramework(self.test_name) c.check_snappy_framework_policy_filenames() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_snappy_framework_policy_filenames_bad2(self): '''Test check_snappy_framework_policy_filenames() - starts with package name ''' self.set_test_pkg_yaml("type", "framework") tmp = self.test_framework_policy n = self.test_name.split('_')[0] tmp['seccomp']['policygroups']['%s-group' % n] = "foo" self.set_test_framework_policy(tmp) c = ClickReviewFramework(self.test_name) c.check_snappy_framework_policy_filenames() r = c.click_report expected_counts = {'info': None, 'warn': 1, 'error': 0} self.check_results(r, expected_counts) click-reviewers-tools-0.44~16.04.1/clickreviews/tests/test_cr_lint.py0000664000000000000000000025154312736744774022451 0ustar '''test_cr_lint.py: tests for the cr_lint module''' # # Copyright (C) 2013-2015 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from unittest import TestCase from unittest.mock import patch from clickreviews.common import cleanup_unpack from clickreviews.cr_lint import ClickReviewLint from clickreviews.cr_lint import MINIMUM_CLICK_FRAMEWORK_VERSION from clickreviews.frameworks import FRAMEWORKS_DATA_URL, USER_DATA_FILE from clickreviews.tests import utils import clickreviews.cr_tests as cr_tests import os import shutil import stat import tempfile class TestClickReviewLint(cr_tests.TestClickReview): """Tests for the lint review tool.""" def _create_hashes_yaml(self): # find cr_tests.py since that is what _get_statinfo() is mocked to # look at. f = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../cr_tests.py") statinfo = os.stat(f) self.sha512 = cr_tests._get_sha512sum(self, f) hashes = {'archive-sha512': self.sha512, 'files': [{'name': 'bin', 'mode': 'drwxrwxr-x'}, {'name': 'bin/foo', 'size': statinfo.st_size, 'mode': 'f%s' % stat.filemode(statinfo.st_mode)[1:], 'sha512': self.sha512}, {'name': 'barlink', 'mode': 'lrwxrwxrwx'}, ] } self._test_pkg_files = [] for i in hashes['files']: self._test_pkg_files.append(i['name']) return hashes def patch_frameworks(self): def _mock_frameworks(self, overrides=None): self.FRAMEWORKS = { 'ubuntu-core-15.04': 'available', 'ubuntu-sdk-14.10-qml-dev2': 'available', 'ubuntu-sdk-13.10': 'deprecated', 'ubuntu-sdk-14.10-qml-dev1': 'obsolete', } self.AVAILABLE_FRAMEWORKS = ['ubuntu-core-15.04', 'ubuntu-sdk-14.10-qml-dev2'] self.OBSOLETE_FRAMEWORKS = ['ubuntu-sdk-14.10-qml-dev1'] self.DEPRECATED_FRAMEWORKS = ['ubuntu-sdk-13.10'] p = patch('clickreviews.frameworks.Frameworks.__init__', _mock_frameworks) p.start() self.addCleanup(p.stop) def test_check_architecture(self): '''Test check_architecture()''' c = ClickReviewLint(self.test_name) c.check_architecture() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_architecture_all(self): '''Test check_architecture_all() - no binaries''' self.set_test_control("Architecture", "all") self.set_test_manifest("architecture", "all") c = ClickReviewLint(self.test_name) c.pkg_bin_files = [] c.check_architecture_all() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_architecture_all2(self): '''Test check_architecture_all() - binaries''' self.set_test_control("Architecture", "all") self.set_test_manifest("architecture", "all") c = ClickReviewLint(self.test_name) c.pkg_bin_files = ["path/to/some/compiled/binary"] c.check_architecture_all() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_architecture_armhf(self): '''Test check_architecture() - armhf''' self.set_test_control("Architecture", "armhf") c = ClickReviewLint(self.test_name) c.check_architecture() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_architecture_i386(self): '''Test check_architecture() - i386''' self.set_test_control("Architecture", "i386") c = ClickReviewLint(self.test_name) c.check_architecture() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_architecture_arm64(self): '''Test check_architecture() - arm64''' self.set_test_control("Architecture", "arm64") c = ClickReviewLint(self.test_name) c.check_architecture() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_architecture_amd64(self): '''Test check_architecture() - amd64''' self.set_test_control("Architecture", "amd64") c = ClickReviewLint(self.test_name) c.check_architecture() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_architecture_nonexistent(self): '''Test check_architecture() - nonexistent''' self.set_test_control("Architecture", "nonexistent") self.set_test_pkgfmt("click", "0.4") c = ClickReviewLint(self.test_name) c.pkg_arch = ["nonexistent"] c.check_architecture() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_control_architecture(self): '''Test check_control() (architecture)''' c = ClickReviewLint(self.test_name) c.check_control() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_control_architecture_snappy_1504(self): '''Test check_control() (architecture) - snappy 15.04''' self.set_test_pkgfmt("snap", "15.04") c = ClickReviewLint(self.test_name) c.check_control() r = c.click_report expected_counts = {'info': 15, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_control_architecture_missing(self): '''Test check_control() (architecture missing)''' self.set_test_control("Architecture", None) try: ClickReviewLint(self.test_name) except KeyError: return raise Exception("Should have raised a KeyError") def test_check_control_matches_manifest_architecture(self): '''Test check_control() (architecture matches manifest)''' self.set_test_control("Architecture", "armhf") self.set_test_manifest("architecture", "armhf") c = ClickReviewLint(self.test_name) c.check_control() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_control_mismatches_manifest_architecture(self): '''Test check_control() (architecture mismatches manifest)''' self.set_test_control("Architecture", "armhf") self.set_test_manifest("architecture", "amd64") c = ClickReviewLint(self.test_name) c.is_snap1 = False c.check_control() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_control_mismatches_manifest_architecture_snappy(self): '''Test check_control() (architecture mismatches manifest (snappy))''' self.set_test_control("Architecture", ["all"]) self.set_test_manifest("architecture", "all") c = ClickReviewLint(self.test_name) c.is_snap1 = True c.check_control() r = c.click_report expected_counts = {'info': 15, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_control_manifest_architecture_missing(self): '''Test check_control() (manifest architecture)''' self.set_test_control("Architecture", "armhf") self.set_test_manifest("architecture", None) c = ClickReviewLint(self.test_name) c.check_control() r = c.click_report expected = dict() expected['info'] = dict() expected['warn'] = dict() expected['error'] = dict() name = c._get_check_name('control_architecture_match') expected['info'][name] = { "text": "OK: architecture not specified in manifest"} self.check_results(r, expected=expected) def test_check_architecture_specified_needed(self): '''Test check_architecture_specified_needed() - no binaries''' self.set_test_control("Architecture", "armhf") self.set_test_manifest("architecture", "armhf") c = ClickReviewLint(self.test_name) c.pkg_arch = ['armhf'] c.pkg_bin_files = [] c.check_architecture_specified_needed() r = c.click_report expected_counts = {'info': None, 'warn': 1, 'error': 0} self.check_results(r, expected_counts) def test_check_architecture_specified_needed2(self): '''Test check_architecture_specified_needed2() - binaries''' self.set_test_control("Architecture", "armhf") self.set_test_manifest("architecture", "armhf") c = ClickReviewLint(self.test_name) c.pkg_bin_files = ["path/to/some/compiled/binary"] c.check_architecture_specified_needed() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_manifest_missing_arch(self): '''Test check_manifest_architecture() (missing)''' self.set_test_manifest("architecture", None) c = ClickReviewLint(self.test_name) c.check_manifest_architecture() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_manifest_missing_arch_snappy_1504(self): '''Test check_manifest_architecture() - snappy 15.04''' self.set_test_pkgfmt("snap", "15.04") self.set_test_manifest("architecture", None) c = ClickReviewLint(self.test_name) c.check_manifest_architecture() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_manifest_arch_all(self): '''Test check_manifest_architecture() (all)''' self.set_test_manifest("architecture", "all") c = ClickReviewLint(self.test_name) c.check_manifest_architecture() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_manifest_arch_single_armhf(self): '''Test check_manifest_architecture() (single arch, armhf)''' self.set_test_manifest("architecture", "armhf") c = ClickReviewLint(self.test_name) c.check_manifest_architecture() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_manifest_arch_single_i386(self): '''Test check_manifest_architecture() (single arch, i386)''' self.set_test_manifest("architecture", "i386") c = ClickReviewLint(self.test_name) c.check_manifest_architecture() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_manifest_arch_single_amd64(self): '''Test check_manifest_architecture() (single arch, amd64)''' self.set_test_manifest("architecture", "amd64") c = ClickReviewLint(self.test_name) c.check_manifest_architecture() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_manifest_arch_single_nonexistent(self): '''Test check_manifest_architecture() (single nonexistent arch)''' self.set_test_manifest("architecture", "nonexistent") c = ClickReviewLint(self.test_name) c.check_manifest_architecture() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_manifest_arch_single_multi(self): '''Test check_manifest_architecture() (single arch: invalid multi)''' self.set_test_manifest("architecture", "multi") c = ClickReviewLint(self.test_name) c.check_manifest_architecture() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_manifest_valid_arch_multi(self): '''Test check_manifest_architecture() (valid multi)''' arch = "multi" self.set_test_manifest("architecture", ["armhf"]) self.set_test_control("Architecture", arch) test_name = "%s_%s_%s.click" % (self.test_control['Package'], self.test_control['Version'], arch) c = ClickReviewLint(test_name) c.check_manifest_architecture() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_manifest_valid_arch_multi2(self): '''Test check_manifest_architecture() (valid multi2)''' arch = "multi" self.set_test_manifest("architecture", ["armhf", "i386"]) self.set_test_control("Architecture", arch) test_name = "%s_%s_%s.click" % (self.test_control['Package'], self.test_control['Version'], arch) c = ClickReviewLint(test_name) c.check_manifest_architecture() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_manifest_invalid_arch_multi_nonexistent(self): '''Test check_manifest_architecture() (invalid multi)''' arch = "multi" self.set_test_manifest("architecture", ["armhf", "nonexistent"]) self.set_test_control("Architecture", arch) test_name = "%s_%s_%s.click" % (self.test_control['Package'], self.test_control['Version'], arch) c = ClickReviewLint(test_name) c.check_manifest_architecture() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_manifest_invalid_arch_multi_all(self): '''Test check_manifest_architecture() (invalid all)''' arch = "multi" self.set_test_manifest("architecture", ["armhf", "all"]) self.set_test_control("Architecture", arch) test_name = "%s_%s_%s.click" % (self.test_control['Package'], self.test_control['Version'], arch) c = ClickReviewLint(test_name) c.is_snap1 = False c.check_manifest_architecture() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_manifest_invalid_arch_multi_multi(self): '''Test check_manifest_architecture() (invalid multi)''' arch = "multi" self.set_test_manifest("architecture", ["multi", "armhf"]) self.set_test_control("Architecture", arch) test_name = "%s_%s_%s.click" % (self.test_control['Package'], self.test_control['Version'], arch) c = ClickReviewLint(test_name) c.check_manifest_architecture() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_control(self): """A very basic test to make sure check_control can be tested.""" c = ClickReviewLint(self.test_name) c.is_snap1 = False c.check_control() r = c.click_report expected_counts = {'info': 15, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_control_snap(self): """check_control with snap.""" c = ClickReviewLint(self.test_name) c.is_snap1 = True c.check_control() r = c.click_report expected_counts = {'info': 15, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_control_snap_missing_maintainer(self): """check_control with snap with missing maintainer.""" c = ClickReviewLint(self.test_name) c.is_snap1 = True self.set_test_control('Maintainer', None) c.check_control() r = c.click_report expected_counts = {'info': 15, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) # Lets check that the right info is triggering name = c._get_check_name('control_has_field:Maintainer') m = r['info'][name]['text'] self.assertIn('OK (maintainer not required for snappy)', m) # Make the current MINIMUM_CLICK_FRAMEWORK_VERSION newer @patch('clickreviews.cr_lint.MINIMUM_CLICK_FRAMEWORK_VERSION', MINIMUM_CLICK_FRAMEWORK_VERSION + '.1') def test_check_control_click_framework_version(self): """Test that enforcing click framework versions works.""" test_name = 'net.launchpad.click-webapps.test-app_3_all.click' c = ClickReviewLint(test_name) c.check_control() r = c.click_report # We should end up with an error as the click version is out of date expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) # Lets check that the right error is triggering name = c._get_check_name('control_click_version_up_to_date') m = r['error'][name]['text'] self.assertIn('Click-Version is too old', m) def test_check_maintainer(self): '''Test check_maintainer()''' c = ClickReviewLint(self.test_name) c.check_maintainer() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_maintainer_empty(self): '''Test check_maintainer() - empty''' self.set_test_manifest("maintainer", "") c = ClickReviewLint(self.test_name) c.is_snap1 = False c.check_maintainer() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_maintainer_empty_snap(self): '''Test check_maintainer() - empty (snap)''' self.set_test_manifest("maintainer", "") c = ClickReviewLint(self.test_name) c.is_snap1 = True c.check_maintainer() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_maintainer_missing(self): '''Test check_maintainer() - missing (click)''' self.set_test_manifest("maintainer", None) c = ClickReviewLint(self.test_name) c.is_snap1 = False c.check_maintainer() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_maintainer_missing_snap(self): '''Test check_maintainer() - missing (snap)''' self.set_test_manifest("maintainer", None) c = ClickReviewLint(self.test_name) c.is_snap1 = True c.check_maintainer() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_maintainer_badformat(self): '''Test check_maintainer() - badly formatted''' self.set_test_manifest("maintainer", "$%^@*") c = ClickReviewLint(self.test_name) c.is_snap1 = False c.check_maintainer() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_maintainer_badformat_snap(self): '''Test check_maintainer() - badly formatted (snap)''' self.set_test_manifest("maintainer", "$%^@*") c = ClickReviewLint(self.test_name) c.is_snap1 = True c.check_maintainer() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_maintainer_bad_email_missing_name(self): '''Test check_maintainer() - bad email (missing name)''' self.set_test_manifest("name", "com.ubuntu.developer.user.app") self.set_test_manifest("maintainer", "user@example.com") c = ClickReviewLint(self.test_name) c.is_snap1 = False c.check_maintainer() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_maintainer_bad_email_missing_name_snap(self): '''Test check_maintainer() - bad email (missing name, snap)''' self.set_test_manifest("name", "com.ubuntu.developer.user.app") self.set_test_manifest("maintainer", "user@example.com") c = ClickReviewLint(self.test_name) c.is_snap1 = True c.check_maintainer() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_maintainer_domain_appstore(self): '''Test check_maintainer() - appstore domain (com.ubuntu.developer)''' self.set_test_manifest("name", "com.ubuntu.developer.user.app") self.set_test_manifest("maintainer", "Foo User ") c = ClickReviewLint(self.test_name) c.check_maintainer() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_icon(self): '''Test check_icon()''' self.set_test_manifest("icon", "someicon") c = ClickReviewLint(self.test_name) c.check_icon() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_icon_snappy_1504(self): '''Test check_icon() - snappy 15.04''' self.set_test_pkgfmt("snap", "15.04") self.set_test_manifest("icon", "someicon") c = ClickReviewLint(self.test_name) c.check_icon() r = c.click_report expected_counts = {'info': 3, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_icon_unspecified(self): '''Test check_icon()''' self.set_test_manifest("icon", None) c = ClickReviewLint(self.test_name) c.check_icon() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_icon_empty(self): '''Test check_icon() - empty''' self.set_test_manifest("icon", "") c = ClickReviewLint(self.test_name) c.check_icon() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_icon_absolute_path(self): '''Test check_icon() - absolute path''' self.set_test_manifest("icon", "/foo/bar/someicon") c = ClickReviewLint(self.test_name) c.check_icon() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_click_local_extensions_missing(self): '''Testeck_click_local_extensions() - missing''' for k in self.test_manifest.keys(): if k.startswith("x-"): self.set_test_manifest(k, None) c = ClickReviewLint(self.test_name) c.check_click_local_extensions() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_click_local_extensions_empty(self): '''Testeck_click_local_extensions() - empty''' for k in self.test_manifest.keys(): if k.startswith("x-"): self.set_test_manifest(k, None) self.set_test_manifest("x-test", "") c = ClickReviewLint(self.test_name) c.check_click_local_extensions() r = c.click_report expected_counts = {'info': 0, 'warn': 1, 'error': 0} self.check_results(r, expected_counts) def test_check_click_local_extensions(self): '''Testeck_click_local_extensions()''' for k in self.test_manifest.keys(): if k.startswith("x-"): self.set_test_manifest(k, None) self.set_test_manifest("x-source", {"vcs-bzr": "lp:notes-app", "vcs-bzr-revno": "209"}) c = ClickReviewLint(self.test_name) c.check_click_local_extensions() r = c.click_report expected_counts = {'info': 0, 'warn': 1, 'error': 0} self.check_results(r, expected_counts) def test_check_click_local_extensions_coreapp(self): '''Testeck_click_local_extensions() - coreapp''' for k in self.test_manifest.keys(): if k.startswith("x-"): self.set_test_manifest(k, None) self.set_test_manifest("x-source", "foo") c = ClickReviewLint(self.test_name) c.is_core_app = True c.check_click_local_extensions() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_click_local_extensions_snappy_1504(self): '''Testeck_click_local_extensions() - snappy 15.04''' self.set_test_pkgfmt("snap", "15.04") for k in self.test_manifest.keys(): if k.startswith("x-"): self.set_test_manifest(k, None) self.set_test_manifest("x-source", {"vcs-bzr": "lp:notes-app", "vcs-bzr-revno": "209"}) c = ClickReviewLint(self.test_name) c.check_click_local_extensions() r = c.click_report expected_counts = {'info': 0, 'warn': 1, 'error': 0} self.check_results(r, expected_counts) def test_check_framework(self): '''Test check_framework()''' self.patch_frameworks() self.set_test_manifest("framework", "ubuntu-sdk-14.10-qml-dev2") c = ClickReviewLint(self.test_name) c.check_framework() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_framework_multiple_click(self): '''Test check_framework() - click''' self.patch_frameworks() self.set_test_manifest("framework", "ubuntu-sdk-14.10-qml-dev2,ubuntu-core-15.04") c = ClickReviewLint(self.test_name) c.is_snap1 = False c.check_framework() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_framework_multiple_snappy(self): '''Test check_framework() - snappy''' self.patch_frameworks() self.set_test_manifest("framework", "ubuntu-sdk-14.10-qml-dev2,ubuntu-core-15.04") c = ClickReviewLint(self.test_name) c.is_snap1 = True c.check_framework() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) @patch('clickreviews.remote.read_cr_file') def test_check_framework_fetches_remote_data(self, mock_read_cr_file): '''Test check_framework()''' mock_read_cr_file.return_value = { 'ubuntu-sdk-14.10-qml-dev2': 'available', } self.set_test_manifest("framework", "ubuntu-sdk-14.10-qml-dev2") c = ClickReviewLint(self.test_name) c.check_framework() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) # ensure no local fn is provided when reading frameworks mock_read_cr_file.assert_called_once_with( USER_DATA_FILE, FRAMEWORKS_DATA_URL) def test_check_framework_bad(self): '''Test check_framework() - bad''' self.patch_frameworks() self.set_test_manifest("framework", "nonexistent") c = ClickReviewLint(self.test_name) c.check_framework() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_framework_deprecated(self): '''Test check_framework() - deprecated''' self.patch_frameworks() self.set_test_manifest("framework", "ubuntu-sdk-13.10") c = ClickReviewLint(self.test_name) c.check_framework() r = c.click_report expected_counts = {'info': None, 'warn': 1, 'error': 0} self.check_results(r, expected_counts) def test_check_framework_obsolete(self): '''Test check_framework() - obsolete''' self.patch_frameworks() self.set_test_manifest("framework", "ubuntu-sdk-14.10-qml-dev1") c = ClickReviewLint(self.test_name) c.check_framework() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) @patch('clickreviews.remote.read_cr_file') def test_check_framework_with_overrides(self, mock_read_cr_file): '''Test check_framework() - using overrides''' mock_read_cr_file.return_value = { 'ubuntu-sdk-14.10-qml-dev2': 'available', } self.set_test_manifest("framework", "nonexistent") overrides = {'framework': {'nonexistent': {'state': 'available'}}} c = ClickReviewLint(self.test_name, overrides=overrides) c.check_framework() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) @patch('clickreviews.remote.read_cr_file') def test_check_framework_with_overrides_obsolete(self, mock_read_cr_file): '''Test check_framework() - using override obsoletes available''' fwk = 'ubuntu-sdk-14.10-qml-dev2' mock_read_cr_file.return_value = { '%s' % fwk: 'available', } self.set_test_manifest("framework", fwk) overrides = {'framework': {'%s' % fwk: {'state': 'obsolete'}}} c = ClickReviewLint(self.test_name, overrides=overrides) c.check_framework() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) @patch('clickreviews.remote.read_cr_file') def test_check_framework_with_overrides_deprecated(self, mock_read_cr_file): '''Test check_framework() - using override deprecates available''' fwk = 'ubuntu-sdk-14.10-qml-dev2' mock_read_cr_file.return_value = { '%s' % fwk: 'available', } self.set_test_manifest("framework", fwk) overrides = {'framework': {'%s' % fwk: {'state': 'deprecated'}}} c = ClickReviewLint(self.test_name, overrides=overrides) c.check_framework() r = c.click_report expected_counts = {'info': None, 'warn': 1, 'error': 0} self.check_results(r, expected_counts) @patch('clickreviews.remote.read_cr_file') def test_check_framework_with_malformed_overrides(self, mock_read_cr_file): '''Test check_framework() - using overrides''' mock_read_cr_file.return_value = { 'ubuntu-sdk-14.10-qml-dev2': 'available', } self.set_test_manifest("framework", "nonexistent") overrides = {'nonexistent': {'state': 'available'}} c = ClickReviewLint(self.test_name, overrides=overrides) c.check_framework() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_hooks(self): '''Test check_hooks()''' self.set_test_manifest("framework", "ubuntu-sdk-13.10") c = ClickReviewLint(self.test_name) c.check_hooks() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_hooks_snappy_1504(self): '''Test check_hooks() - snappy 15.04''' self.set_test_pkgfmt("snap", "15.04") self.set_test_manifest("framework", "ubuntu-sdk-13.10") c = ClickReviewLint(self.test_name) c.check_hooks() r = c.click_report expected_counts = {'info': 5, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_hooks_multiple_desktop_apps(self): '''Test check_hooks() - multiple desktop apps''' self.set_test_manifest("framework", "ubuntu-sdk-13.10") c = ClickReviewLint(self.test_name) tmp = c.manifest['hooks'][self.default_appname] c.manifest['hooks']["another-app"] = tmp c.check_hooks() r = c.click_report expected_counts = {'info': 9, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_hooks_multiple_apps(self): '''Test check_hooks() - multiple non-desktop apps''' self.set_test_manifest("framework", "ubuntu-sdk-13.10") c = ClickReviewLint(self.test_name) tmp = dict() for k in c.manifest['hooks'][self.default_appname].keys(): tmp[k] = c.manifest['hooks'][self.default_appname][k] tmp.pop('desktop') tmp['scope'] = "some-scope-exec" c.manifest['hooks']["some-scope"] = tmp tmp = dict() for k in c.manifest['hooks'][self.default_appname].keys(): tmp[k] = c.manifest['hooks'][self.default_appname][k] tmp.pop('desktop') tmp['push-helper'] = "push.json" c.manifest['hooks']["some-push-helper"] = tmp c.check_hooks() r = c.click_report expected_counts = {'info': 13, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_hooks_security_extension(self): '''Test check_hooks() - security extension''' self.set_test_manifest("framework", "ubuntu-sdk-13.10") c = ClickReviewLint(self.test_name) tmp = dict() for k in c.manifest['hooks'][self.default_appname].keys(): tmp[k] = c.manifest['hooks'][self.default_appname][k] tmp['apparmor'] = "%s.json" % self.default_appname c.manifest['hooks'][self.default_appname] = tmp c.check_hooks() r = c.click_report expected = dict() expected['info'] = dict() expected['warn'] = dict() expected['error'] = dict() name = c._get_check_name( 'sdk_security_extension', app='test-app') expected['info'][name] = { "text": "test-app.json does not end with .apparmor (ok if not using sdk)"} self.check_results(r, expected=expected) def test_check_hooks_bad_appname(self): '''Test check_hooks() - bad appname''' self.set_test_manifest("framework", "ubuntu-sdk-13.10") c = ClickReviewLint(self.test_name) tmp = c.manifest['hooks'][self.default_appname] del c.manifest['hooks'][self.default_appname] c.manifest['hooks']["b@d@ppn@m#"] = tmp c.check_hooks() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_hooks_missing_apparmor(self): '''Test check_hooks() - missing apparmor''' self.set_test_manifest("framework", "ubuntu-sdk-13.10") c = ClickReviewLint(self.test_name) del c.manifest['hooks'][self.default_appname]['apparmor'] c.check_hooks() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_hooks_missing_apparmor_with_apparmor_profile(self): '''Test check_hooks() - missing apparmor with apparmor-profile''' self.set_test_manifest("framework", "ubuntu-sdk-13.10") c = ClickReviewLint(self.test_name) del c.manifest['hooks'][self.default_appname]['apparmor'] c.manifest['hooks'][self.default_appname]['apparmor-profile'] = 'foo' c.check_hooks() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_hooks_missing_apparmor_with_puritine(self): '''Test check_hooks() - missing apparmor''' self.set_test_manifest("framework", "ubuntu-sdk-13.10") c = ClickReviewLint(self.test_name) del c.manifest['hooks'][self.default_appname]['apparmor'] c.manifest['hooks'][self.default_appname]['puritine'] = 'foo' c.check_hooks() r = c.click_report expected_counts = {'info': 4, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_hooks_has_desktop_and_scope(self): '''Test check_hooks() - desktop with scope''' self.set_test_manifest("framework", "ubuntu-sdk-13.10") c = ClickReviewLint(self.test_name) c.manifest['hooks'][self.default_appname]["scope"] = "some-binary" c.check_hooks() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_hooks_type_oem(self): '''Test check_hooks() - type: oem''' self.set_test_manifest("framework", "ubuntu-sdk-13.10") c = ClickReviewLint(self.test_name) c.is_snap_oem = True c.check_hooks() r = c.click_report # oem type has no hooks so these should all be '0' expected_counts = {'info': 0, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_hooks_unknown_nonexistent(self): '''Test check_hooks_unknown() - nonexistent''' self.set_test_manifest("framework", "ubuntu-sdk-13.10") c = ClickReviewLint(self.test_name) c.manifest['hooks'][self.default_appname]["nonexistant"] = "foo" c.check_hooks_unknown() r = c.click_report expected_counts = {'info': None, 'warn': 1, 'error': 0} self.check_results(r, expected_counts) def test_check_hooks_unknown_nonexistent_snappy_1504(self): '''Test check_hooks_unknown() - nonexistent - snappy 15.04''' self.set_test_pkgfmt("snap", "15.04") self.set_test_manifest("framework", "ubuntu-sdk-13.10") c = ClickReviewLint(self.test_name) c.check_hooks_unknown() r = c.click_report expected_counts = {'info': 3, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_hooks_unknown_good(self): '''Test check_hooks_unknown()''' self.set_test_manifest("framework", "ubuntu-sdk-13.10") c = ClickReviewLint(self.test_name) c.check_hooks_unknown() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_hooks_unknown_type_oem(self): '''Test check_hooks_unknown() - type: oem''' self.set_test_manifest("framework", "ubuntu-sdk-13.10") c = ClickReviewLint(self.test_name) c.is_snap_oem = True c.check_hooks_unknown() r = c.click_report # oem type has no hooks so these should all be '0' expected_counts = {'info': 0, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_hooks_redflagged_payui(self): '''Test check_hooks_redflagged() - pay-ui''' self.set_test_manifest("framework", "ubuntu-sdk-13.10") c = ClickReviewLint(self.test_name) c.manifest['hooks'][self.default_appname]["pay-ui"] = "foo" c.check_hooks_redflagged() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) name = c._get_check_name('hooks_redflag', app='test-app') self.check_manual_review(r, name) def test_check_hooks_redflagged_payui_snappy_1504(self): '''Test check_hooks_redflagged() - pay-ui - snappy 15.04''' self.set_test_pkgfmt("snap", "15.04") self.set_test_manifest("framework", "ubuntu-sdk-13.10") c = ClickReviewLint(self.test_name) c.check_hooks_redflagged() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_hooks_redflagged_apparmor_profile(self): '''Test check_hooks_redflagged() - apparmor-profile''' self.set_test_manifest("framework", "ubuntu-sdk-13.10") c = ClickReviewLint(self.test_name) c.manifest['hooks'][self.default_appname]["apparmor-profile"] = "foo" # snap checks are handled elsewhere c.is_snap1 = False c.check_hooks_redflagged() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) name = c._get_check_name('hooks_redflag', app='test-app') self.check_manual_review(r, name) def test_check_hooks_redflagged_puritine(self): '''Test check_hooks_redflagged() - puritine''' self.set_test_manifest("framework", "ubuntu-sdk-13.10") c = ClickReviewLint(self.test_name) c.manifest['hooks'][self.default_appname]["puritine"] = "foo" c.check_hooks_redflagged() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) name = c._get_check_name('hooks_redflag', app='test-app') self.check_manual_review(r, name) def test_pkgname_toplevel(self): '''Test check_pkgname - toplevel''' self.set_test_manifest("name", "foo") c = ClickReviewLint(self.test_name) c.check_pkgname() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_pkgname_flat(self): '''Test check_pkgname - flat''' self.set_test_manifest("name", "foo.bar") c = ClickReviewLint(self.test_name) c.is_snap1 = False c.check_pkgname() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_pkgname_reverse_domain(self): '''Test check_pkgname - reverse domain''' self.set_test_manifest("name", "com.ubuntu.develeper.baz.foo") c = ClickReviewLint(self.test_name) c.is_snap1 = False c.check_pkgname() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_snappy_name_toplevel_1504(self): '''Test check_snappy_name - toplevel - 15.04''' self.set_test_pkgfmt("snap", "15.04") self.set_test_pkg_yaml("name", "foo") c = ClickReviewLint(self.test_name) c.check_snappy_name() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_snappy_name_flat(self): '''Test check_snappy_name - obsoleted flat''' self.set_test_pkgfmt("snap", "15.04") self.set_test_pkg_yaml("name", "foo.bar") c = ClickReviewLint(self.test_name) c.check_snappy_name() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_snappy_name_reverse_domain(self): '''Test check_snappy_name - obsoleted reverse domain''' self.set_test_pkgfmt("snap", "15.04") self.set_test_pkg_yaml("name", "com.ubuntu.develeper.baz.foo") c = ClickReviewLint(self.test_name) c.check_snappy_name() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_snappy_name_bad(self): '''Test check_snappy_name - bad''' self.set_test_pkgfmt("snap", "15.04") self.set_test_pkg_yaml("name", "foo?bar") c = ClickReviewLint(self.test_name) c.check_snappy_name() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_snappy_name_bad2(self): '''Test check_snappy_name - empty''' self.set_test_pkgfmt("snap", "15.04") self.set_test_pkg_yaml("name", "") c = ClickReviewLint(self.test_name) c.check_snappy_name() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_snappy_name_bad3(self): '''Test check_snappy_name - list''' self.set_test_pkgfmt("snap", "15.04") self.set_test_pkg_yaml("name", []) c = ClickReviewLint(self.test_name) c.check_snappy_name() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_snappy_name_bad4(self): '''Test check_snappy_name - dict''' self.set_test_pkgfmt("snap", "15.04") self.set_test_pkg_yaml("name", {}) c = ClickReviewLint(self.test_name) c.check_snappy_name() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_snappy_version_1504(self): '''Test check_snappy_version - 15.04''' self.set_test_pkgfmt("snap", "15.04") self.set_test_pkg_yaml("version", 1) c = ClickReviewLint(self.test_name) c.check_snappy_version() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_snappy_version1(self): '''Test check_snappy_version - integer''' self.set_test_pkgfmt("snap", "15.04") self.set_test_pkg_yaml("version", 1) c = ClickReviewLint(self.test_name) c.check_snappy_version() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_snappy_version2(self): '''Test check_snappy_version - float''' self.set_test_pkgfmt("snap", "15.04") self.set_test_pkg_yaml("version", 1.0) c = ClickReviewLint(self.test_name) c.check_snappy_version() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_snappy_version3(self): '''Test check_snappy_version - MAJOR.MINOR.MICRO''' self.set_test_pkgfmt("snap", "15.04") self.set_test_pkg_yaml("version", "1.0.1") c = ClickReviewLint(self.test_name) c.check_snappy_version() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_snappy_version4(self): '''Test check_snappy_version - str''' self.set_test_pkgfmt("snap", "15.04") self.set_test_pkg_yaml("version", "1.0a") c = ClickReviewLint(self.test_name) c.check_snappy_version() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_snappy_version5(self): '''Test check_snappy_version - alpha''' self.set_test_pkgfmt("snap", "15.04") self.set_test_pkg_yaml("version", "a.b") c = ClickReviewLint(self.test_name) c.check_snappy_version() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_snappy_version_bad(self): '''Test check_snappy_version - bad''' self.set_test_pkgfmt("snap", "15.04") self.set_test_pkg_yaml("version", "foo?bar") c = ClickReviewLint(self.test_name) c.check_snappy_version() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_snappy_version_bad2(self): '''Test check_snappy_version - empty''' self.set_test_pkgfmt("snap", "15.04") self.set_test_pkg_yaml("version", "") c = ClickReviewLint(self.test_name) c.check_snappy_version() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_snappy_version_bad3(self): '''Test check_snappy_version - list''' self.set_test_pkgfmt("snap", "15.04") self.set_test_pkg_yaml("version", []) c = ClickReviewLint(self.test_name) c.check_snappy_version() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_snappy_version_bad4(self): '''Test check_snappy_version - dict''' self.set_test_pkgfmt("snap", "15.04") self.set_test_pkg_yaml("version", {}) c = ClickReviewLint(self.test_name) c.check_snappy_version() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_snappy_type(self): '''Test check_snappy_type - unspecified''' self.set_test_pkgfmt("snap", "15.04") self.set_test_pkg_yaml("type", None) c = ClickReviewLint(self.test_name) c.check_snappy_type() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_snappy_type_app_1504(self): '''Test check_snappy_type - app - 15.04''' self.set_test_pkgfmt("snap", "15.04") self.set_test_pkg_yaml("type", "app") c = ClickReviewLint(self.test_name) c.check_snappy_type() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_snappy_type_framework(self): '''Test check_snappy_type - framework''' self.set_test_pkgfmt("snap", "15.04") self.set_test_pkg_yaml("type", "framework") c = ClickReviewLint(self.test_name) c.check_snappy_type() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_snappy_type_oem(self): '''Test check_snappy_type - oem''' self.set_test_pkgfmt("snap", "15.04") self.set_test_pkg_yaml("type", "oem") c = ClickReviewLint(self.test_name) c.check_snappy_type() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_snappy_type_redflagged_1504(self): '''Test check_snappy_type_redflagged - unspecified - 15.04''' self.set_test_pkgfmt("snap", "15.04") self.set_test_pkg_yaml("type", None) c = ClickReviewLint(self.test_name) c.check_snappy_type_redflagged() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_snappy_type_redflagged_app(self): '''Test check_snappy_type_redflagged - app''' self.set_test_pkgfmt("snap", "15.04") self.set_test_pkg_yaml("type", "app") c = ClickReviewLint(self.test_name) c.check_snappy_type_redflagged() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_snappy_type_redflagged_framework(self): '''Test check_snappy_type_redflagged - framework''' self.set_test_pkgfmt("snap", "15.04") self.set_test_pkg_yaml("type", "framework") c = ClickReviewLint(self.test_name) c.check_snappy_type_redflagged() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_snappy_type_redflagged_oem(self): '''Test check_snappy_type_redflagged - oem''' self.set_test_pkgfmt("snap", "15.04") self.set_test_pkg_yaml("type", "oem") c = ClickReviewLint(self.test_name) c.check_snappy_type_redflagged() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_snappy_icon_1504(self): '''Test check_snappy_icon() - 15.04''' self.set_test_pkgfmt("snap", "15.04") self.set_test_pkg_yaml("icon", "someicon") c = ClickReviewLint(self.test_name) c.check_snappy_icon() r = c.click_report expected_counts = {'info': 3, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_snappy_icon_unspecified(self): '''Test check_snappy_icon() - unspecified''' self.set_test_pkgfmt("snap", "15.04") self.set_test_pkg_yaml("icon", None) c = ClickReviewLint(self.test_name) c.check_snappy_icon() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_snappy_icon_empty(self): '''Test check_snappy_icon() - empty''' self.set_test_pkgfmt("snap", "15.04") self.set_test_pkg_yaml("icon", "") c = ClickReviewLint(self.test_name) c.check_snappy_icon() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_snappy_icon_absolute_path(self): '''Test check_snappy_icon() - absolute path''' self.set_test_pkgfmt("snap", "15.04") self.set_test_pkg_yaml("icon", "/foo/bar/someicon") c = ClickReviewLint(self.test_name) c.check_snappy_icon() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_snappy_missing_arch(self): '''Test check_snappy_architecture() (missing)''' self.set_test_pkgfmt("snap", "15.04") self.set_test_pkg_yaml("architectures", None) c = ClickReviewLint(self.test_name) c.check_snappy_architecture() r = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_snappy_arch_all_deprecated(self): '''Test check_snappy_architecture() (deprecated, all)''' self.set_test_pkgfmt("snap", "15.04") self.set_test_pkg_yaml("architecture", "all") c = ClickReviewLint(self.test_name) c.check_snappy_architecture() r = c.click_report expected_counts = {'info': 1, 'warn': 1, 'error': 0} self.check_results(r, expected_counts) def test_check_snappy_arch_amd64_deprecated(self): '''Test check_snappy_architecture() (deprecated, all)''' self.set_test_pkgfmt("snap", "15.04") self.set_test_pkg_yaml("architecture", "amd64") c = ClickReviewLint(self.test_name) c.check_snappy_architecture() r = c.click_report expected_counts = {'info': 1, 'warn': 1, 'error': 0} self.check_results(r, expected_counts) def test_check_snappy_arch_all(self): '''Test check_snappy_architecture() (all)''' self.set_test_pkgfmt("snap", "15.04") self.set_test_pkg_yaml("architectures", ["all"]) c = ClickReviewLint(self.test_name) c.check_snappy_architecture() r = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_snappy_arch_single_armhf(self): '''Test check_snappy_architecture() (single arch, armhf)''' self.set_test_pkgfmt("snap", "15.04") self.set_test_pkg_yaml("architectures", ["armhf"]) c = ClickReviewLint(self.test_name) c.check_snappy_architecture() r = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_snappy_arch_single_arm64(self): '''Test check_snappy_architecture() (single arch, arm64)''' self.set_test_pkgfmt("snap", "15.04") self.set_test_pkg_yaml("architectures", ["arm64"]) c = ClickReviewLint(self.test_name) c.check_snappy_architecture() r = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_snappy_arch_single_i386(self): '''Test check_snappy_architecture() (single arch, i386)''' self.set_test_pkgfmt("snap", "15.04") self.set_test_pkg_yaml("architectures", ["i386"]) c = ClickReviewLint(self.test_name) c.check_snappy_architecture() r = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_snappy_arch_single_amd64(self): '''Test check_snappy_architecture() (single arch, amd64)''' self.set_test_pkgfmt("snap", "15.04") self.set_test_pkg_yaml("architectures", ["amd64"]) c = ClickReviewLint(self.test_name) c.check_snappy_architecture() r = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_snappy_arch_single_nonexistent(self): '''Test check_snappy_architecture() (single nonexistent arch)''' self.set_test_pkgfmt("snap", "15.04") self.set_test_pkg_yaml("architectures", ["nonexistent"]) c = ClickReviewLint(self.test_name) c.check_snappy_architecture() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_snappy_arch_single_multi(self): '''Test check_snappy_architecture() (single arch: invalid multi)''' self.set_test_pkgfmt("snap", "15.04") self.set_test_pkg_yaml("architectures", "multi") c = ClickReviewLint(self.test_name) c.check_snappy_architecture() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_snappy_valid_arch_multi(self): '''Test check_snappy_architecture() (valid multi)''' self.set_test_pkgfmt("snap", "15.04") self.set_test_pkg_yaml("architectures", ["armhf"]) test_name = "%s_%s_%s.snap" % (self.test_control['Package'], self.test_control['Version'], "armhf") c = ClickReviewLint(test_name) c.check_snappy_architecture() r = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_snappy_valid_arch_multi2(self): '''Test check_snappy_architecture() (valid multi2)''' self.set_test_pkgfmt("snap", "15.04") arch = "multi" self.set_test_pkg_yaml("architectures", ["armhf", "i386"]) test_name = "%s_%s_%s.snap" % (self.test_control['Package'], self.test_control['Version'], arch) c = ClickReviewLint(test_name) c.check_snappy_architecture() r = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_snappy_invalid_arch_multi_nonexistent(self): '''Test check_snappy_architecture() (invalid multi)''' self.set_test_pkgfmt("snap", "15.04") arch = "multi" self.set_test_pkg_yaml("architectures", ["armhf", "nonexistent"]) test_name = "%s_%s_%s.snap" % (self.test_control['Package'], self.test_control['Version'], arch) c = ClickReviewLint(test_name) c.check_snappy_architecture() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_snappy_invalid_arch_multi_all(self): '''Test check_snappy_architecture() (invalid all)''' self.set_test_pkgfmt("snap", "15.04") arch = "multi" self.set_test_pkg_yaml("architectures", ["armhf", "all"]) test_name = "%s_%s_%s.snap" % (self.test_control['Package'], self.test_control['Version'], arch) c = ClickReviewLint(test_name) c.check_snappy_architecture() r = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_snappy_invalid_arch_multi_multi(self): '''Test check_snappy_architecture() (invalid multi)''' self.set_test_pkgfmt("snap", "15.04") arch = "multi" self.set_test_pkg_yaml("architectures", ["multi", "armhf"]) test_name = "%s_%s_%s.snap" % (self.test_control['Package'], self.test_control['Version'], arch) c = ClickReviewLint(test_name) c.check_snappy_architecture() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_snappy_unknown_entries(self): '''Test check_snappy_unknown_entries - none''' self.set_test_pkgfmt("snap", "15.04") self.set_test_pkg_yaml("name", "foo") c = ClickReviewLint(self.test_name) c.check_snappy_unknown_entries() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_snappy_unknown_entries2(self): '''Test check_snappy_unknown_entries - one''' self.set_test_pkgfmt("snap", "15.04") self.set_test_pkg_yaml("nonexistent", "bar") c = ClickReviewLint(self.test_name) c.check_snappy_unknown_entries() r = c.click_report expected_counts = {'info': 0, 'warn': 1, 'error': 0} self.check_results(r, expected_counts) def test_check_snappy_unknown_obsoleted(self): '''Test check_snappy_unknown_entries - obsoleted''' self.set_test_pkgfmt("snap", "15.04") self.set_test_pkg_yaml("maintainer", "bar") c = ClickReviewLint(self.test_name) c.check_snappy_unknown_entries() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) # Lets check that the right warning is triggering name = c._get_check_name('snappy_unknown') m = r['error'][name]['text'] self.assertIn("unknown entries in package.yaml: 'maintainer' " "(maintainer obsoleted)", m) def test_check_snappy_readme_md(self): '''Test check_snappy_readme_md()''' self.set_test_pkgfmt("snap", "15.04") self.set_test_pkg_yaml("name", self.test_name.split('_')[0]) self.set_test_readme_md("%s - some description" % self.test_name.split('_')[0]) c = ClickReviewLint(self.test_name) c.check_snappy_readme_md() r = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_snappy_readme_md_bad(self): '''Test check_snappy_readme_md() - short''' self.set_test_pkgfmt("snap", "15.04") self.set_test_pkg_yaml("name", "prettylong.name") self.set_test_readme_md("abc") c = ClickReviewLint(self.test_name) c.check_snappy_readme_md() r = c.click_report expected_counts = {'info': 1, 'warn': 1, 'error': 0} self.check_results(r, expected_counts) def test_check_snappy_readme_md_bad2(self): '''Test check_snappy_readme_md() - missing''' self.set_test_pkgfmt("snap", "15.04") self.set_test_pkg_yaml("name", self.test_name.split('_')[0]) self.set_test_readme_md(None) c = ClickReviewLint(self.test_name) c.check_snappy_readme_md() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_snappy_services_and_binaries1(self): '''Test check_snappy_services_and_binaries() - different''' self.set_test_pkgfmt("snap", "15.04") self.set_test_pkg_yaml("name", self.test_name.split('_')[0]) self.set_test_pkg_yaml("services", [{"name": "foo", "start": "bin/foo"}]) self.set_test_pkg_yaml("binaries", [{"name": "bar"}]) c = ClickReviewLint(self.test_name) c.check_snappy_services_and_binaries() r = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_snappy_services_and_binaries2(self): '''Test check_snappy_services_and_binaries() - different (exec)''' self.set_test_pkgfmt("snap", "15.04") self.set_test_pkg_yaml("name", self.test_name.split('_')[0]) self.set_test_pkg_yaml("services", [{"name": "foo", "start": "bin/foo"}]) self.set_test_pkg_yaml("binaries", [{"name": "bar", "exec": "bin/foo"}]) c = ClickReviewLint(self.test_name) c.check_snappy_services_and_binaries() r = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_snappy_services_and_binaries3(self): '''Test check_snappy_services_and_binaries() - same''' self.set_test_pkgfmt("snap", "15.04") self.set_test_pkg_yaml("name", self.test_name.split('_')[0]) self.set_test_pkg_yaml("services", [{"name": "foo", "start": "bin/foo"}]) self.set_test_pkg_yaml("binaries", [{"name": "foo"}]) c = ClickReviewLint(self.test_name) c.check_snappy_services_and_binaries() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 2} self.check_results(r, expected_counts) name = c._get_check_name('snappy_in_services', extra='foo') m = r['error'][name]['text'] self.assertIn("'foo' in both 'services' and 'binaries'", m) name = c._get_check_name('snappy_in_binaries', extra='foo') m = r['error'][name]['text'] self.assertIn("'foo' in both 'services' and 'binaries'", m) def test_check_snappy_services_and_binaries4(self): '''Test check_snappy_services_and_binaries() - same (subdir)''' self.set_test_pkgfmt("snap", "15.04") self.set_test_pkg_yaml("name", self.test_name.split('_')[0]) self.set_test_pkg_yaml("services", [{"name": "foo", "start": "bin/foo"}]) self.set_test_pkg_yaml("binaries", [{"name": "bin/foo"}]) c = ClickReviewLint(self.test_name) c.check_snappy_services_and_binaries() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 2} self.check_results(r, expected_counts) name = c._get_check_name('snappy_in_services', extra='foo') m = r['error'][name]['text'] self.assertIn("'foo' in both 'services' and 'binaries'", m) name = c._get_check_name('snappy_in_binaries', extra='foo') m = r['error'][name]['text'] self.assertIn("'foo' in both 'services' and 'binaries'", m) def test_check_snappy_services_and_binaries5(self): '''Test check_snappy_services_and_binaries() - same (exec, subdir)''' self.set_test_pkgfmt("snap", "15.04") self.set_test_pkg_yaml("name", self.test_name.split('_')[0]) self.set_test_pkg_yaml("services", [{"name": "foo", "start": "bin/foo"}]) self.set_test_pkg_yaml("binaries", [{"name": "foo", "exec": "bin/foo"}]) c = ClickReviewLint(self.test_name) c.check_snappy_services_and_binaries() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 2} self.check_results(r, expected_counts) name = c._get_check_name('snappy_in_services', extra='foo') m = r['error'][name]['text'] self.assertIn("'foo' in both 'services' and 'binaries'", m) name = c._get_check_name('snappy_in_binaries', extra='foo') m = r['error'][name]['text'] self.assertIn("'foo' in both 'services' and 'binaries'", m) def test_check_snappy_hashes_click(self): '''Test check_snappy_hashes() - click''' self.set_test_pkgfmt("click", "0.4") c = ClickReviewLint(self.test_name) c.is_snap1 = False c.check_snappy_hashes() r = c.click_report # clicks don't have hashes.yaml, so should have no output expected_counts = {'info': 0, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_snappy_hashes_archive_sha512_missing(self): '''Test check_snappy_hashes() - archive-sha512 missing''' self.set_test_pkgfmt("snap", "15.04") c = ClickReviewLint(self.test_name) c.check_snappy_hashes() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_snappy_hashes_archive_sha512_invalid(self): '''Test check_snappy_hashes() - archive-sha512 invalid''' self.set_test_pkgfmt("snap", "15.04") c = ClickReviewLint(self.test_name) yaml = self._create_hashes_yaml() yaml['archive-sha512'] = 'deadbeef' self.set_test_hashes_yaml(yaml) c.check_snappy_hashes() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) name = c._get_check_name('hashes_archive-sha512_valid') m = r['error'][name]['text'] self.assertIn("hash mismatch: 'deadbeef' != '%s'" % self.sha512, m) def test_check_snappy_hashes_archive_files_missing(self): '''Test check_snappy_hashes() - files missing''' self.set_test_pkgfmt("snap", "15.04") c = ClickReviewLint(self.test_name) yaml = self._create_hashes_yaml() del yaml['files'] self.set_test_hashes_yaml(yaml) c.check_snappy_hashes() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) name = c._get_check_name('hashes_files_present') m = r['error'][name]['text'] self.assertIn("'files' not found in hashes.yaml", m) def test_check_snappy_hashes_archive_files_ok(self): '''Test check_snappy_hashes() - ok''' self.set_test_pkgfmt("snap", "15.04") c = ClickReviewLint(self.test_name) yaml = self._create_hashes_yaml() c.pkg_files = self._test_pkg_files self.set_test_hashes_yaml(yaml) c.check_snappy_hashes() r = c.click_report expected_counts = {'info': 4, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_snappy_hashes_1504(self): '''Test check_snappy_hashes() - 15.04''' self.set_test_pkgfmt("snap", "15.04") c = ClickReviewLint(self.test_name) yaml = self._create_hashes_yaml() c.pkg_files = self._test_pkg_files self.set_test_hashes_yaml(yaml) c.check_snappy_hashes() r = c.click_report expected_counts = {'info': 4, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_snappy_hashes_archive_files_missing_name(self): '''Test check_snappy_hashes() - missing name''' self.set_test_pkgfmt("snap", "15.04") c = ClickReviewLint(self.test_name) yaml = self._create_hashes_yaml() del yaml['files'][0]['name'] self.set_test_hashes_yaml(yaml) c.check_snappy_hashes() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_snappy_hashes_archive_files_missing_mode(self): '''Test check_snappy_hashes() - missing mode''' self.set_test_pkgfmt("snap", "15.04") c = ClickReviewLint(self.test_name) yaml = self._create_hashes_yaml() del yaml['files'][0]['mode'] self.set_test_hashes_yaml(yaml) c.check_snappy_hashes() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_snappy_hashes_archive_files_malformed_mode(self): '''Test check_snappy_hashes() - malformed mode''' self.set_test_pkgfmt("snap", "15.04") c = ClickReviewLint(self.test_name) yaml = self._create_hashes_yaml() yaml['files'][0]['mode'] += 'extra' self.set_test_hashes_yaml(yaml) c.check_snappy_hashes() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_snappy_hashes_archive_files_bad_mode_entry(self): '''Test check_snappy_hashes() - bad mode entry''' self.set_test_pkgfmt("snap", "15.04") c = ClickReviewLint(self.test_name) yaml = self._create_hashes_yaml() count = 0 orig_mode = None for e in yaml['files']: s = list(yaml['files'][count]['mode']) if e['name'] == 'bin/foo': # keep track of the other parts of the on disk mode orig_mode = s orig_mode[3] = 'S' s[3] = 'S' yaml['files'][count]['mode'] = "".join(s) count += 1 self.set_test_hashes_yaml(yaml) c.check_snappy_hashes() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) name = c._get_check_name('file_mode') m = r['error'][name]['text'] self.assertIn("found errors in hashes.yaml: unusual mode '%s' for entry 'bin/foo'" % "".join(orig_mode), m) def test_check_snappy_hashes_archive_files_mode_world_write(self): '''Test check_snappy_hashes() - mode world write''' self.set_test_pkgfmt("snap", "15.04") c = ClickReviewLint(self.test_name) yaml = self._create_hashes_yaml() count = 0 for e in yaml['files']: s = list(e['mode']) if e['name'] == 'bin/foo' or e['name'] == 'bin': s[-2] = 'w' s[-5] = 'w' yaml['files'][count]['mode'] = "".join(s) count += 1 self.set_test_hashes_yaml(yaml) c.check_snappy_hashes() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) name = c._get_check_name('file_mode') m = r['error'][name]['text'] self.assertIn("found errors in hashes.yaml: 'bin' is world-writable, mode 'frw-rw-rw-' for 'bin/foo' is world-writable", m) def test_check_snappy_hashes_archive_files_mode_mismatch(self): '''Test check_snappy_hashes() - mode mismatch''' self.set_test_pkgfmt("snap", "15.04") c = ClickReviewLint(self.test_name) yaml = self._create_hashes_yaml() count = 0 orig_mode = None for e in yaml['files']: if e['mode'].startswith('f'): # keep track of the other parts of the on disk mode orig_mode = e['mode'][1:] yaml['files'][count]['mode'] = "f---------" break count += 1 self.set_test_hashes_yaml(yaml) c.check_snappy_hashes() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) name = c._get_check_name('file_mode') m = r['error'][name]['text'] self.assertIn("found errors in hashes.yaml: mode '---------' != '%s' for 'bin/foo'" % orig_mode, m) def test_check_snappy_hashes_archive_files_mode_bad_symlink(self): '''Test check_snappy_hashes() - mode bad symlink''' self.set_test_pkgfmt("snap", "15.04") c = ClickReviewLint(self.test_name) yaml = self._create_hashes_yaml() yaml['files'].append({'name': 'badlink', 'mode': 'lrwxrwxr-x'}) self.set_test_hashes_yaml(yaml) c.check_snappy_hashes() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) name = c._get_check_name('file_mode') m = r['error'][name]['text'] self.assertIn("found errors in hashes.yaml: unusual mode 'lrwxrwxr-x' for entry 'badlink'", m) def test_check_snappy_hashes_archive_files_mode_devices(self): '''Test check_snappy_hashes() - mode devices''' self.set_test_pkgfmt("snap", "15.04") c = ClickReviewLint(self.test_name) yaml = self._create_hashes_yaml() yaml['files'].append({'name': 'badblock', 'mode': 'brw-rw-r--'}) yaml['files'].append({'name': 'badchar', 'mode': 'crw-rw-r--'}) self.set_test_hashes_yaml(yaml) c.check_snappy_hashes() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) name = c._get_check_name('file_mode') m = r['error'][name]['text'] self.assertIn("found errors in hashes.yaml: illegal file mode 'b': 'brw-rw-r--' for 'badblock', illegal file mode 'c': 'crw-rw-r--' for 'badchar'", m) def test_check_snappy_hashes_archive_files_missing_size(self): '''Test check_snappy_hashes() - missing size''' self.set_test_pkgfmt("snap", "15.04") c = ClickReviewLint(self.test_name) yaml = self._create_hashes_yaml() count = 0 for e in yaml['files']: if e['mode'].startswith('f'): del yaml['files'][count]['size'] break count += 1 self.set_test_hashes_yaml(yaml) c.check_snappy_hashes() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_snappy_hashes_archive_files_invalid_size(self): '''Test check_snappy_hashes() - invalid size''' self.set_test_pkgfmt("snap", "15.04") c = ClickReviewLint(self.test_name) yaml = self._create_hashes_yaml() count = 0 for e in yaml['files']: if e['mode'].startswith('f'): orig_size = e['size'] new_size = orig_size + 1 yaml['files'][count]['size'] = new_size break count += 1 self.set_test_hashes_yaml(yaml) c.check_snappy_hashes() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) name = c._get_check_name('file_mode') m = r['error'][name]['text'] self.assertIn("found errors in hashes.yaml: size " + "%d != %d for 'bin/foo'" % (new_size, orig_size), m) def test_check_snappy_hashes_archive_files_missing_sha512(self): '''Test check_snappy_hashes() - missing sha512''' self.set_test_pkgfmt("snap", "15.04") c = ClickReviewLint(self.test_name) yaml = self._create_hashes_yaml() count = 0 for e in yaml['files']: if e['mode'].startswith('f'): del yaml['files'][count]['sha512'] break count += 1 self.set_test_hashes_yaml(yaml) c.check_snappy_hashes() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_snappy_hashes_extra(self): '''Test check_snappy_hashes() - extra''' self.set_test_pkgfmt("snap", "15.04") c = ClickReviewLint(self.test_name) yaml = self._create_hashes_yaml() self.set_test_hashes_yaml(yaml) c.pkg_files = self._test_pkg_files c.pkg_files.append("extrafile") c.check_snappy_hashes() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) name = c._get_check_name('hashes_extra_files') m = r['error'][name]['text'] self.assertIn("found extra files not listed in hashes.yaml: extrafile", m) def test_snappy_config(self): '''Test check_snappy_config()''' self.set_test_pkgfmt("snap", "15.04") c = ClickReviewLint(self.test_name) c.unpack_dir = "/nonexistent" c.pkg_files.append(os.path.join(c.unpack_dir, 'meta/hooks/config')) c.check_snappy_config() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_snappy_config_nonexecutable(self): '''Test check_snappy_config() - not executable''' self.set_test_pkgfmt("snap", "15.04") c = ClickReviewLint(self.test_name) c.unpack_dir = "/nonexistent.nonexec" c.pkg_files.append(os.path.join(c.unpack_dir, 'meta/hooks/config')) c.check_snappy_config() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_external_symlinks_puritine(self): '''Test check_external_symlinks - puritine''' self.set_test_manifest("framework", "ubuntu-sdk-13.10") c = ClickReviewLint(self.test_name) c.manifest['hooks'][self.default_appname]["puritine"] = "foo" c.check_external_symlinks() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) name = c._get_check_name('external_symlinks') m = r['info'][name]['text'] self.assertIn("SKIPPED: puritine", m) def test_check_md5sums_puritine(self): '''Test check_md5sums - puritine''' self.set_test_manifest("framework", "ubuntu-sdk-13.10") c = ClickReviewLint(self.test_name) c.manifest['hooks'][self.default_appname]["puritine"] = "foo" c.check_md5sums() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) name = c._get_check_name('md5sums') m = r['info'][name]['text'] self.assertIn("SKIPPED: puritine", m) class ClickReviewLintTestCase(TestCase): """Tests without mocks where they are not needed.""" def setUp(self): # XXX cleanup_unpack() is required because global variables # UNPACK_DIR, RAW_UNPACK_DIR are initialised to None at module # load time, but updated when a real (non-Mock) test runs, such as # here. While, at the same time, two of the existing tests using # mocks depend on both global vars being None. Ideally, those # global vars should be refactored away. self.addCleanup(cleanup_unpack) super().setUp() def mkdtemp(self): """Create a temp dir which is cleaned up after test.""" tmp_dir = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, tmp_dir) return tmp_dir def test_check_dot_click_root(self): package = utils.make_click(extra_files=['.click/'], output_dir=self.mkdtemp()) c = ClickReviewLint(package) c.check_dot_click() errors = list(c.click_report['error'].keys()) self.assertEqual(errors, ['lint:dot_click']) click-reviewers-tools-0.44~16.04.1/clickreviews/tests/test_cr_security.py0000664000000000000000000033625112703476576023346 0ustar '''test_cr_security.py: tests for the cr_security module''' # # Copyright (C) 2013-2015 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from __future__ import print_function import sys from clickreviews.cr_security import ClickReviewSecurity import clickreviews.cr_tests as cr_tests class TestClickReviewSecurity(cr_tests.TestClickReview): """Tests for the security lint review tool.""" def setUp(self): super().setUp() self.default_security_json = "%s.apparmor" % \ self.default_appname def _set_yaml_binary(self, entries, name=None): d = dict() if name is None: d['name'] = self.default_appname else: d['name'] = name for (key, value) in entries: d[key] = value self.set_test_pkg_yaml("binaries", [d]) def test_check_policy_version_vendor(self): '''Test check_policy_version() - valid''' c = ClickReviewSecurity(self.test_name) self.set_test_security_manifest(self.default_appname, "policy_version", 1.0) c.check_policy_version() report = c.click_report expected_counts = {'info': 3, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_policy_version_vendor_snappy_1504(self): '''Test check_policy_version() - valid - snappy 15.04''' self.set_test_pkgfmt("snap", "15.04") n = "bin/%s" % self.default_appname self._set_yaml_binary([('caps', ['network-client'])], name=n) c = ClickReviewSecurity(self.test_name) self.set_test_security_manifest("binaries/%s" % n, "policy_version", 1.0) c.check_policy_version() report = c.click_report expected_counts = {'info': 3, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_policy_version_highest(self): '''Test check_policy_version() - highest''' c = ClickReviewSecurity(self.test_name) highest_version = c._get_highest_policy_version("ubuntu") version = highest_version self.set_test_security_manifest(self.default_appname, "policy_version", version) c.check_policy_version() report = c.click_report expected = dict() expected['info'] = dict() expected['warn'] = dict() expected['error'] = dict() name = c._get_check_name( 'policy_version_is_highest', extra="(%s, %s)" % (highest_version, self.default_security_json)) expected['info'][name] = {"text": "OK"} self.check_results(report, expected=expected) def test_check_policy_version_bad(self): '''Test check_policy_version() - bad version''' bad_version = 0.1 c = ClickReviewSecurity(self.test_name) self.set_test_security_manifest(self.default_appname, "policy_version", bad_version) highest = c._get_highest_policy_version("ubuntu") c.check_policy_version() report = c.click_report expected = dict() expected['info'] = dict() expected['warn'] = dict() expected['error'] = dict() name = c._get_check_name( 'policy_version_is_highest', extra="(%s, %s)" % (highest, self.default_security_json)) expected['info'][name] = {"text": "0.1 != %s" % highest} name = c._get_check_name( 'policy_version_exists', extra=self.default_security_json) expected['error'][name] = {"text": "could not find policy for ubuntu/%s" % str(bad_version)} self.check_results(report, expected=expected) def test_check_policy_version_low(self): '''Test check_policy_version() - low version''' c = ClickReviewSecurity(self.test_name) highest = c._get_highest_policy_version("ubuntu") version = 1.0 if version == highest: print("SKIPPED-- test version '%s' is already highest" % version, file=sys.stderr) return self.set_test_security_manifest(self.default_appname, "policy_version", version) c.check_policy_version() report = c.click_report expected = dict() expected['info'] = dict() expected['warn'] = dict() expected['error'] = dict() name = c._get_check_name( 'policy_version_is_highest', extra="(%s, %s)" % (highest, self.default_security_json)) expected['info'][name] = {"text": "%s != %s" % (version, highest)} self.check_results(report, expected=expected) def test_check_policy_version_unspecified(self): '''Test check_policy_version() - unspecified''' c = ClickReviewSecurity(self.test_name) self.set_test_security_manifest(self.default_appname, "policy_version", None) c.check_policy_version() report = c.click_report expected = dict() expected['info'] = dict() expected['warn'] = dict() expected['error'] = dict() name = c._get_check_name('policy_version_exists', extra=self.default_security_json) expected['error'][name] = {"text": "could not find policy_version in manifest"} self.check_results(report, expected=expected) def test_check_policy_version_framework(self): '''Test check_policy_version() - matching framework''' tmp = ClickReviewSecurity(self.test_name) # for each installed framework on the system, verify that the policy # matches the framework for f in tmp.valid_frameworks: self.set_test_manifest("framework", f) policy_version = 0 for k in tmp.major_framework_policy.keys(): if f.startswith(k): policy_version = tmp.major_framework_policy[k]['policy_version'] self.set_test_security_manifest(self.default_appname, "policy_version", policy_version) c = ClickReviewSecurity(self.test_name) c.check_policy_version() report = c.click_report expected_counts = {'info': 3, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_policy_version_framework_match_snappy_multiple(self): '''Test check_policy_version() - matching framework - multiple''' self.set_test_pkgfmt("snap", "15.04") self.set_test_manifest("framework", "foo,ubuntu-core-15.04") self.set_test_security_manifest(self.default_appname, "policy_vendor", "ubuntu-core") self.set_test_security_manifest(self.default_appname, "policy_version", 15.04) c = ClickReviewSecurity(self.test_name) c.check_policy_version() report = c.click_report expected_counts = {'info': 3, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_policy_version_framework_unmatch(self): '''Test check_policy_version() - unmatching framework (lower)''' self.set_test_manifest("framework", "ubuntu-sdk-14.04") self.set_test_security_manifest(self.default_appname, "policy_version", 1.0) c = ClickReviewSecurity(self.test_name) c.check_policy_version() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) expected = dict() expected['info'] = dict() expected['warn'] = dict() expected['error'] = dict() name = c._get_check_name( 'policy_version_matches_framework', extra=self.default_security_json) expected['error'][name] = {"text": "1.0 != 1.1 (ubuntu-sdk-14.04)"} self.check_results(report, expected=expected) def test_check_policy_version_framework_unmatch2(self): '''Test check_policy_version() - unmatching framework (higher)''' self.set_test_manifest("framework", "ubuntu-sdk-13.10") self.set_test_security_manifest(self.default_appname, "policy_version", 1.1) c = ClickReviewSecurity(self.test_name) c.check_policy_version() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) expected = dict() expected['info'] = dict() expected['warn'] = dict() expected['error'] = dict() name = c._get_check_name( 'policy_version_matches_framework', extra=self.default_security_json) expected['error'][name] = {"text": "1.1 != 1.0 (ubuntu-sdk-13.10)"} self.check_results(report, expected=expected) def test_check_policy_version_framework_unmatch3(self): '''Test check_policy_version() - unmatching framework (nonexistent)''' self.set_test_manifest("framework", "nonexistent") self.set_test_security_manifest(self.default_appname, "policy_version", 1.1) c = ClickReviewSecurity(self.test_name) c.check_policy_version() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) expected = dict() expected['info'] = dict() expected['warn'] = dict() expected['error'] = dict() name = c._get_check_name( 'policy_version_matches_framework', extra=self.default_security_json) expected['error'][name] = {"text": "Invalid framework 'nonexistent'"} self.check_results(report, expected=expected) def test_check_policy_version_framework_with_overrides(self): '''Test check_policy_version() - override framework (nonexistent)''' self.set_test_manifest("framework", "nonexistent") self.set_test_security_manifest(self.default_appname, "policy_version", 1.3) overrides = {'framework': {'nonexistent': {'state': 'available', 'policy_vendor': 'ubuntu', 'policy_version': 1.3}}} c = ClickReviewSecurity(self.test_name, overrides=overrides) c.check_policy_version() report = c.click_report expected_counts = {'info': 3, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_policy_version_framework_with_malformed_overrides(self): '''Test check_policy_version() - incorrectly override framework''' self.set_test_manifest("framework", "nonexistent") self.set_test_security_manifest(self.default_appname, "policy_version", 999999999.3) overrides = {'nonexistent': {'state': 'available', 'policy_vendor': 'ubuntu', 'policy_version': 999999999.3}} c = ClickReviewSecurity(self.test_name, overrides=overrides) c.check_policy_version() report = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 2} self.check_results(report, expected_counts) def test_check_policy_vendor_unspecified(self): '''Test check_policy_vendor() - unspecified''' c = ClickReviewSecurity(self.test_name) c.check_policy_vendor() report = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_policy_vendor_ubuntu_1504(self): '''Test check_policy_vendor() - ubuntu - 15.04''' self.set_test_pkgfmt("snap", "15.04") n = "bin/%s" % self.default_appname self._set_yaml_binary([('caps', ['network-client'])], name=n) c = ClickReviewSecurity(self.test_name) self.set_test_security_manifest("binaries/%s" % n, "policy_vendor", "ubuntu") c.check_policy_vendor() report = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_policy_vendor_ubuntu(self): '''Test check_policy_vendor() - ubuntu''' c = ClickReviewSecurity(self.test_name) self.set_test_security_manifest(self.default_appname, "policy_vendor", "ubuntu") c.check_policy_vendor() report = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_policy_vendor_ubuntu_snappy(self): '''Test check_policy_vendor() - ubuntu-core''' self.set_test_pkgfmt("snap", "15.04") self.set_test_manifest("framework", "ubuntu-core-15.04") c = ClickReviewSecurity(self.test_name) self.set_test_security_manifest(self.default_appname, "policy_vendor", "ubuntu-core") self.set_test_security_manifest(self.default_appname, "policy_version", 15.04) c.check_policy_vendor() report = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_policy_vendor_nonexistent(self): '''Test check_policy_vendor() - nonexistent''' self.set_test_manifest("framework", "nonexistent") c = ClickReviewSecurity(self.test_name) self.set_test_security_manifest(self.default_appname, "policy_vendor", "ubuntu") c.check_policy_vendor() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_policy_vendor_framework(self): '''Test check_policy_vendor() - matching framework''' tmp = ClickReviewSecurity(self.test_name) # for each installed framework on the system, verify that the policy # matches the framework for f in tmp.valid_frameworks: self.set_test_manifest("framework", f) policy_vendor = "ubuntu" for k in tmp.major_framework_policy.keys(): if f.startswith(k): if 'policy_vendor' not in tmp.major_framework_policy[k]: policy_vendor = 'ubuntu' else: policy_vendor = tmp.major_framework_policy[k]['policy_vendor'] self.set_test_security_manifest(self.default_appname, "policy_vendor", policy_vendor) c = ClickReviewSecurity(self.test_name) c.check_policy_vendor() report = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_policy_vendor_framwork_match_snappy_multiple(self): '''Test check_policy_vendor() - matching framework - multiple''' self.set_test_pkgfmt("snap", "15.04") self.set_test_manifest("framework", "foo,ubuntu-core-15.04") c = ClickReviewSecurity(self.test_name) self.set_test_security_manifest(self.default_appname, "policy_vendor", "ubuntu-core") self.set_test_security_manifest(self.default_appname, "policy_version", 15.04) c.check_policy_vendor() report = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_policy_vendor_framework_unmatch1(self): '''Test check_policy_vendor() - unmatching framework''' self.set_test_security_manifest(self.default_appname, "policy_vendor", "ubuntu-snappy") c = ClickReviewSecurity(self.test_name) c.check_policy_vendor() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) expected = dict() expected['info'] = dict() expected['warn'] = dict() expected['error'] = dict() name = c._get_check_name( 'policy_vendor_matches_framework', extra=self.default_security_json) expected['error'][name] = {"text": "ubuntu-snappy != ubuntu (ubuntu-sdk-13.10)"} self.check_results(report, expected=expected) def test_check_policy_vendor_framework_unmatch2(self): '''Test check_policy_vendor() - unmatching framework - nonexistent''' self.set_test_manifest("framework", "nonexistent") self.set_test_security_manifest(self.default_appname, "policy_vendor", "ubuntu") c = ClickReviewSecurity(self.test_name) c.check_policy_vendor() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) expected = dict() expected['info'] = dict() expected['warn'] = dict() expected['error'] = dict() name = c._get_check_name( 'policy_vendor_matches_framework', extra=self.default_security_json) expected['error'][name] = {"text": "Invalid framework 'nonexistent'"} self.check_results(report, expected=expected) def test_check_policy_vendor_framework_with_overrides(self): '''Test check_policy_vendor() - override framework (nonexistent)''' self.set_test_manifest("framework", "nonexistent") self.set_test_security_manifest(self.default_appname, "policy_vendor", "ubuntu") overrides = {'framework': {'nonexistent': {'state': 'available', 'policy_vendor': 'ubuntu', 'policy_version': 1.2}}} c = ClickReviewSecurity(self.test_name, overrides=overrides) c.check_policy_vendor() report = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_policy_vendor_framework_with_malformed_overrides(self): '''Test check_policy_vendor() - incorrectly override framework''' self.set_test_manifest("framework", "nonexistent") self.set_test_security_manifest(self.default_appname, "policy_vendor", "ubuntu") overrides = {'nonexistent': {'state': 'available', 'policy_vendor': 'ubuntu', 'policy_version': 1.2}} c = ClickReviewSecurity(self.test_name, overrides=overrides) c.check_policy_vendor() report = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_template_unspecified(self): '''Test check_template() - unspecified''' c = ClickReviewSecurity(self.test_name) self.set_test_security_manifest(self.default_appname, "template", None) c.check_template() report = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_template_ubuntu_sdk(self): '''Test check_template() - ubuntu-sdk''' c = ClickReviewSecurity(self.test_name) self.set_test_security_manifest(self.default_appname, "template", "ubuntu-sdk") c.check_template() report = c.click_report expected = dict() expected['info'] = dict() expected['warn'] = dict() expected['error'] = dict() name = c._get_check_name('template_with_policy_version', extra=self.default_security_json) expected['info'][name] = {"text": "OK"} name = c._get_check_name('template_exists', extra=self.default_security_json) expected['info'][name] = {"text": "OK"} name = c._get_check_name('template_valid', extra=self.default_security_json) expected['warn'][name] = {"text": "No need to specify 'ubuntu-sdk' template"} self.check_results(report, expected=expected) def test_check_template_default(self): '''Test check_template() - default specified with no vendor''' c = ClickReviewSecurity(self.test_name) self.set_test_security_manifest(self.default_appname, "template", "default") c.check_template() report = c.click_report expected_counts = {'info': None, 'warn': 1, 'error': 0} self.check_results(report, expected_counts) def test_check_template_default_with_ubuntu(self): '''Test check_template() - default specified with ubuntu vendor''' c = ClickReviewSecurity(self.test_name) self.set_test_security_manifest(self.default_appname, "template", "default") self.set_test_security_manifest(self.default_appname, "policy_vendor", "ubuntu") c.check_template() report = c.click_report expected_counts = {'info': None, 'warn': 1, 'error': 0} self.check_results(report, expected_counts) def test_check_template_default_with_snappy(self): '''Test check_template() - default specified with ubuntu-snappy vendor''' self.set_test_pkgfmt("snap", "15.04") c = ClickReviewSecurity(self.test_name) self.set_test_security_manifest(self.default_appname, "template", "default") self.set_test_security_manifest(self.default_appname, "policy_vendor", "ubuntu-snappy") self.set_test_security_manifest(self.default_appname, "policy_version", 1.3) c.check_template() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_template_nonexistent_with_snappy(self): '''Test check_template() - nonexistent with ubuntu-snappy vendor''' self.set_test_pkgfmt("snap", "15.04") c = ClickReviewSecurity(self.test_name) self.set_test_security_manifest(self.default_appname, "template", "nonexistent") self.set_test_security_manifest(self.default_appname, "policy_vendor", "ubuntu-snappy") self.set_test_security_manifest(self.default_appname, "policy_version", 1.3) c.check_template() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_template_snappy_framework_deprecated(self): '''Test check_template() - in deprecated framework declaration''' self.set_test_pkgfmt("snap", "15.04") self.set_test_pkg_yaml("framework", "fwk") c = ClickReviewSecurity(self.test_name) self.set_test_security_manifest(self.default_appname, "template", "fwk_foo") c.check_template() report = c.click_report expected_counts = {'info': 3, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_template_snappy_framework_deprecated2(self): '''Test check_template() - in deprecated framework declaration list''' self.set_test_pkgfmt("snap", "15.04") self.set_test_pkg_yaml("framework", "fwk, somethingelse") c = ClickReviewSecurity(self.test_name) self.set_test_security_manifest(self.default_appname, "template", "fwk_foo") c.check_template() report = c.click_report expected_counts = {'info': 3, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_template_snappy_frameworks(self): '''Test check_template() - in frameworks declaration''' self.set_test_pkgfmt("snap", "15.04") self.set_test_pkg_yaml("frameworks", ["fwk"]) c = ClickReviewSecurity(self.test_name) self.set_test_security_manifest(self.default_appname, "template", "fwk_foo") c.check_template() report = c.click_report expected_counts = {'info': 3, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_template_snappy_framework_type(self): '''Test check_template() - type framework''' self.set_test_pkgfmt("snap", "15.04") self.set_test_pkg_yaml("type", "framework") c = ClickReviewSecurity(self.test_name) self.set_test_security_manifest(self.default_appname, "template", "%s_foo" % self.test_name.split('_')[0]) c.check_template() report = c.click_report expected_counts = {'info': 3, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_template_webapp(self): '''Test check_template() - webapp''' c = ClickReviewSecurity(self.test_name) self.set_test_security_manifest(self.default_appname, "template", "ubuntu-webapp") c.check_template() report = c.click_report expected_counts = {'info': 3, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_template_unconfined(self): '''Test check_template() - unconfined''' c = ClickReviewSecurity(self.test_name) self.set_test_security_manifest(self.default_appname, "template", "unconfined") c.check_template() report = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) check_name = c._get_check_name( 'template_valid', app="%s.apparmor" % self.default_appname) self.check_manual_review(report, check_name) def test_check_policy_groups_webapps(self): '''Test check_policy_groups_webapps()''' self.set_test_security_manifest(self.default_appname, "template", "ubuntu-webapp") self.set_test_security_manifest(self.default_appname, "policy_groups", ["audio", "content_exchange", "keep-display-on", "location", "networking", "video", "webview"]) c = ClickReviewSecurity(self.test_name) c.check_policy_groups_webapps() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_policy_groups_webapps_ubuntu_sdk(self): '''Test check_policy_groups_webapps() - ubuntu-sdk template''' self.set_test_security_manifest(self.default_appname, "template", "ubuntu-sdk") self.set_test_security_manifest(self.default_appname, "policy_groups", ["audio", "content_exchange", "keep-display-on", "location", "networking", "video", "webview"]) c = ClickReviewSecurity(self.test_name) c.check_policy_groups_webapps() report = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_policy_groups_webapps_nonexistent(self): '''Test check_policy_groups_webapps() - nonexistent''' self.set_test_security_manifest(self.default_appname, "template", "ubuntu-webapp") self.set_test_security_manifest(self.default_appname, "policy_groups", ["networking", "nonexistent"]) c = ClickReviewSecurity(self.test_name) c.check_policy_groups_webapps() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_policy_groups_webapps_missing(self): '''Test check_policy_groups_webapps() - missing''' self.set_test_security_manifest(self.default_appname, "template", "ubuntu-webapp") self.set_test_security_manifest(self.default_appname, "policy_groups", None) c = ClickReviewSecurity(self.test_name) c.check_policy_groups_webapps() report = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_policy_groups_webapps_missing_webview(self): '''Test check_policy_groups_webapps() - missing webview''' self.set_test_manifest("framework", "ubuntu-sdk-14.04-qml-dev1") self.set_test_security_manifest(self.default_appname, "template", "ubuntu-webapp") self.set_test_security_manifest(self.default_appname, "policy_groups", ["networking"]) c = ClickReviewSecurity(self.test_name) c.check_policy_groups_webapps() report = c.click_report expected_counts = {'info': None, 'warn': 1, 'error': 0} self.check_results(report, expected_counts) def test_check_policy_groups_webapps_missing_webview_1310(self): '''Test check_policy_groups_webapps() - missing webview (13.10)''' self.set_test_manifest("framework", "ubuntu-sdk-13.10") self.set_test_security_manifest(self.default_appname, "template", "ubuntu-webapp") self.set_test_security_manifest(self.default_appname, "policy_groups", ["networking"]) c = ClickReviewSecurity(self.test_name) c.check_policy_groups_webapps() report = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_policy_groups_webapps_bad(self): '''Test check_policy_groups_webapps() - bad''' self.set_test_security_manifest(self.default_appname, "template", "ubuntu-webapp") self.set_test_security_manifest(self.default_appname, "policy_groups", ["video_files", "networking"]) c = ClickReviewSecurity(self.test_name) c.check_policy_groups_webapps() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_policy_groups_scopes_network(self): '''Test check_policy_groups_scopes() - network''' self.set_test_security_manifest(self.default_appname, "template", "ubuntu-scope-network") self.set_test_security_manifest(self.default_appname, "policy_groups", []) c = ClickReviewSecurity(self.test_name) c.check_policy_groups_scopes() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_policy_groups_scopes_network2(self): '''Test check_policy_groups_scopes() - network with networking''' self.set_test_security_manifest(self.default_appname, "template", "ubuntu-scope-network") self.set_test_security_manifest(self.default_appname, "policy_groups", ["networking"]) c = ClickReviewSecurity(self.test_name) c.check_policy_groups_scopes() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_policy_groups_scopes_network3(self): '''Test check_policy_groups_scopes() - network with accounts''' self.set_test_security_manifest(self.default_appname, "template", "ubuntu-scope-network") self.set_test_security_manifest(self.default_appname, "policy_groups", ["accounts"]) c = ClickReviewSecurity(self.test_name) c.check_policy_groups_scopes() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_policy_groups_scopes_network_missing(self): '''Test check_policy_groups_scopes() missing - network''' self.set_test_security_manifest(self.default_appname, "template", "ubuntu-scope-network") self.set_test_security_manifest(self.default_appname, "policy_groups", None) c = ClickReviewSecurity(self.test_name) c.check_policy_groups_scopes() report = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_policy_groups_scopes_network_bad(self): '''Test check_policy_groups_scopes() bad - network''' self.set_test_security_manifest(self.default_appname, "template", "ubuntu-scope-network") self.set_test_security_manifest(self.default_appname, "policy_groups", ["location"]) c = ClickReviewSecurity(self.test_name) c.check_policy_groups_scopes() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) # jdstrand, 2014-06-05: ubuntu-scope-local-content is no longer available # def test_check_policy_groups_scopes_localcontent(self): # '''Test check_policy_groups_scopes() - localcontent''' # self.set_test_security_manifest(self.default_appname, # "template", # "ubuntu-scope-local-content") # self.set_test_security_manifest(self.default_appname, # "policy_groups", []) # c = ClickReviewSecurity(self.test_name) # c.check_policy_groups_scopes() # report = c.click_report # expected_counts = {'info': None, 'warn': 0, 'error': 0} # self.check_results(report, expected_counts) # def test_check_policy_groups_scopes_localcontent_missing(self): # '''Test check_policy_groups_scopes() missing - localcontent''' # self.set_test_security_manifest(self.default_appname, # "template", # "ubuntu-scope-local-content") # self.set_test_security_manifest(self.default_appname, # "policy_groups", None) # c = ClickReviewSecurity(self.test_name) # c.check_policy_groups_scopes() # report = c.click_report # expected_counts = {'info': 0, 'warn': 0, 'error': 0} # self.check_results(report, expected_counts) # def test_check_policy_groups_scopes_localcontent_bad(self): # '''Test check_policy_groups_scopes() bad - localcontent''' # self.set_test_security_manifest(self.default_appname, # "template", # "ubuntu-scope-local-content") # self.set_test_security_manifest(self.default_appname, # "policy_groups", ["networking"]) # c = ClickReviewSecurity(self.test_name) # c.check_policy_groups_scopes() # report = c.click_report # expected_counts = {'info': None, 'warn': 0, 'error': 1} # self.check_results(report, expected_counts) def test_check_policy_groups(self): '''Test check_policy_groups()''' c = ClickReviewSecurity(self.test_name) c.check_policy_groups() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_policy_groups_multiple(self): '''Test check_policy_groups() - multiple''' self.set_test_security_manifest(self.default_appname, "policy_groups", ['networking', 'audio', 'video']) c = ClickReviewSecurity(self.test_name) c.check_policy_groups() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_policy_groups_multiple_kepp_display_on(self): '''Test check_policy_groups() - multiple with keep-display-on''' self.set_test_security_manifest(self.default_appname, "policy_groups", ['networking', 'audio', 'keep-display-on', 'video']) c = ClickReviewSecurity(self.test_name) c.check_policy_groups() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_policy_groups_duplicates(self): '''Test check_policy_groups() - duplicates''' self.set_test_security_manifest(self.default_appname, "policy_groups", ['networking', 'camera', 'microphone', 'camera', 'microphone', 'video']) c = ClickReviewSecurity(self.test_name) c.check_policy_groups() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_policy_groups_missing_policy_version(self): '''Test check_policy_groups() - missing policy_version''' self.set_test_security_manifest(self.default_appname, "policy_version", None) c = ClickReviewSecurity(self.test_name) c.check_policy_groups() report = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_policy_groups_missing(self): '''Test check_policy_groups() - missing''' self.set_test_security_manifest(self.default_appname, "policy_groups", None) c = ClickReviewSecurity(self.test_name) c.check_policy_groups() report = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_policy_groups_bad_policy_version(self): '''Test check_policy_groups() - bad policy_version''' self.set_test_security_manifest(self.default_appname, "policy_version", 0.1) c = ClickReviewSecurity(self.test_name) c.check_policy_groups() report = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_policy_groups_bad_policy_vendor(self): '''Test check_policy_groups() - bad policy_vendor''' self.set_test_security_manifest(self.default_appname, "policy_vendor", "nonexistent") c = ClickReviewSecurity(self.test_name) c.check_policy_groups() report = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_policy_groups_nonexistent(self): '''Test check_policy_groups() - nonexistent''' self.set_test_security_manifest(self.default_appname, "policy_groups", ["networking", "nonexistent"]) c = ClickReviewSecurity(self.test_name) c.check_policy_groups() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_policy_groups_reserved(self): '''Test check_policy_groups() - reserved''' self.set_test_security_manifest(self.default_appname, "policy_groups", ["video_files", "networking"]) c = ClickReviewSecurity(self.test_name) c.check_policy_groups() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) check_name = c._get_check_name( 'policy_groups_safe', app=self.default_appname, extra='video_files') self.check_manual_review(report, check_name) def test_check_policy_groups_debug(self): '''Test check_policy_groups() - debug''' self.set_test_security_manifest(self.default_appname, "policy_groups", ["debug"]) self.set_test_security_manifest(self.default_appname, "policy_version", 1.2) c = ClickReviewSecurity(self.test_name) c.check_policy_groups() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_policy_groups_empty(self): '''Test check_policy_groups() - empty''' self.set_test_security_manifest(self.default_appname, "policy_groups", ["", "networking"]) c = ClickReviewSecurity(self.test_name) c.check_policy_groups() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_policy_groups_snappy_framework_deprecated(self): '''Test check_policy_groups() - in deprecated framework declaration''' self.set_test_pkgfmt("snap", "15.04") self.set_test_pkg_yaml("framework", "fwk") c = ClickReviewSecurity(self.test_name) self.set_test_security_manifest(self.default_appname, "policy_groups", ["fwk_foo"]) c.check_policy_groups() report = c.click_report expected_counts = {'info': 4, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_policy_groups_snappy_framework_deprecated2(self): '''Test check_policy_groups() - in deprecated framework declaration list ''' self.set_test_pkgfmt("snap", "15.04") self.set_test_pkg_yaml("framework", "fwk, somethingelse") c = ClickReviewSecurity(self.test_name) self.set_test_security_manifest(self.default_appname, "policy_groups", ["fwk_foo"]) c.check_policy_groups() report = c.click_report expected_counts = {'info': 4, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_policy_groups_snappy_frameworks(self): '''Test check_policy_groups() - in frameworks declaration''' self.set_test_pkgfmt("snap", "15.04") self.set_test_pkg_yaml("frameworks", ["fwk"]) c = ClickReviewSecurity(self.test_name) self.set_test_security_manifest(self.default_appname, "policy_groups", ["fwk_foo"]) c.check_policy_groups() report = c.click_report expected_counts = {'info': 4, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_policy_groups_snappy_framework_type(self): '''Test check_policy_groups() - type framework''' self.set_test_pkgfmt("snap", "15.04") self.set_test_pkg_yaml("type", "framework") c = ClickReviewSecurity(self.test_name) self.set_test_security_manifest(self.default_appname, "policy_groups", ["%s_foo" % self.test_name.split('_')[0]]) c.check_policy_groups() report = c.click_report expected_counts = {'info': 4, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_policy_groups_pushhelper_no_hook(self): '''Test check_policy_groups_pushhelper() - no hook''' c = ClickReviewSecurity(self.test_name) c.check_policy_groups_push_helpers() report = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_policy_groups_pushhelper(self): '''Test check_policy_groups_pushhelper()''' self.set_test_push_helper(self.default_appname, "exec", "foo") self.set_test_security_manifest(self.default_appname, "policy_groups", ["push-notification-client"]) self.set_test_security_manifest(self.default_appname, "template", "ubuntu-push-helper") c = ClickReviewSecurity(self.test_name) c.check_policy_groups_push_helpers() report = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_policy_groups_pushhelper_wrong_template(self): '''Test check_policy_groups_pushhelper()''' self.set_test_push_helper(self.default_appname, "exec", "foo") self.set_test_security_manifest(self.default_appname, "policy_groups", ["push-notification-client"]) self.set_test_security_manifest(self.default_appname, "template", "ubuntu-sdk") c = ClickReviewSecurity(self.test_name) c.check_policy_groups_push_helpers() report = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_policy_groups_pushhelper_missing(self): '''Test check_policy_groups_pushhelper - missing''' self.set_test_push_helper(self.default_appname, "exec", "foo") self.set_test_security_manifest(self.default_appname, "policy_groups", None) self.set_test_security_manifest(self.default_appname, "template", "ubuntu-push-helper") c = ClickReviewSecurity(self.test_name) c.check_policy_groups_push_helpers() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_policy_groups_pushhelper_bad(self): '''Test check_policy_groups_pushhelper - bad''' self.set_test_push_helper(self.default_appname, "exec", "foo") self.set_test_security_manifest(self.default_appname, "policy_groups", ["video_files", "networking", "push-notification-client"]) self.set_test_security_manifest(self.default_appname, "template", "ubuntu-push-helper") c = ClickReviewSecurity(self.test_name) c.check_policy_groups_push_helpers() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_policy_groups_pushhelper_networking(self): '''Test check_policy_groups_pushhelper - networking''' self.set_test_push_helper(self.default_appname, "exec", "foo") self.set_test_security_manifest(self.default_appname, "policy_groups", ["networking", "push-notification-client"]) self.set_test_security_manifest(self.default_appname, "template", "ubuntu-push-helper") c = ClickReviewSecurity(self.test_name) c.check_policy_groups_push_helpers() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_peer_hooks(self): '''Test check_peer_hooks()''' c = ClickReviewSecurity(self.test_name) # create a new hooks database for our peer hooks tests tmp = dict() # add our hook tmp["apparmor"] = "foo.apparmor" # update the manifest and test_manifest c.manifest["hooks"][self.default_appname] = tmp self._update_test_manifest() # do the test c.check_peer_hooks() r = c.click_report # We should end up with 4 info expected_counts = {'info': 4, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_peer_hooks_disallowed(self): '''Test check_peer_hooks() - disallowed''' c = ClickReviewSecurity(self.test_name) # create a new hooks database for our peer hooks tests tmp = dict() # add our hook tmp["apparmor"] = "foo.apparmor" # add something not allowed tmp["framework"] = "foo.framework" c.manifest["hooks"][self.default_appname] = tmp self._update_test_manifest() # do the test c.check_peer_hooks() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_peer_hooks_disallowed_apparmor_profile(self): '''Test check_peer_hooks() - disallowed (apparmor-profile)''' c = ClickReviewSecurity(self.test_name) # create a new hooks database for our peer hooks tests tmp = dict() # add our hook tmp["apparmor"] = "foo.apparmor" # add something not allowed tmp["apparmor-profile"] = "foo.profile" c.manifest["hooks"][self.default_appname] = tmp self._update_test_manifest() # do the test c.check_peer_hooks() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_peer_hooks_aa_profile(self): '''Test check_peer_hooks() - apparmor-profile''' c = ClickReviewSecurity(self.test_name) # create a new hooks database for our peer hooks tests tmp = dict() # add our hook tmp["apparmor-profile"] = "foo.profile" # update the manifest and test_manifest c.manifest["hooks"][self.default_appname] = tmp self._update_test_manifest() # do the test c.check_peer_hooks() r = c.click_report # We should end up with 4 info expected_counts = {'info': 4, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_peer_hooks_aa_profile_disallowed(self): '''Test check_peer_hooks() - disallowed - apparmor-profile''' c = ClickReviewSecurity(self.test_name) # create a new hooks database for our peer hooks tests tmp = dict() # add our hook tmp["apparmor-profile"] = "foo.profile" # add something not allowed tmp["framework"] = "foo.framework" c.manifest["hooks"][self.default_appname] = tmp self._update_test_manifest() # do the test c.check_peer_hooks() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_redflag_policy_vendor_ubuntu(self): '''Test check_redflag() - policy_vendor - ubuntu''' c = ClickReviewSecurity(self.test_name) self.set_test_security_manifest(self.default_appname, "policy_vendor", "ubuntu") c.check_redflag() report = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_redflag_policy_vendor_ubuntu_snappy(self): '''Test check_redflag() - policy_vendor - ubuntu-snappy''' self.set_test_pkgfmt("snap", "15.04") c = ClickReviewSecurity(self.test_name) self.set_test_security_manifest(self.default_appname, "policy_vendor", "ubuntu-snappy") c.check_redflag() report = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_redflag_abstractions(self): '''Test check_redflag() - abstractions''' c = ClickReviewSecurity(self.test_name) self.set_test_security_manifest(self.default_appname, "abstractions", ["python"]) c.check_redflag() report = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_redflag_binary(self): '''Test check_redflag() - binary''' c = ClickReviewSecurity(self.test_name) self.set_test_security_manifest(self.default_appname, "binary", "/bin/foo") c.check_redflag() report = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_redflag_read_path(self): '''Test check_redflag() - read_path''' c = ClickReviewSecurity(self.test_name) self.set_test_security_manifest(self.default_appname, "read_path", ["/"]) c.check_redflag() report = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_redflag_template_variables(self): '''Test check_redflag() - template_variables''' c = ClickReviewSecurity(self.test_name) self.set_test_security_manifest(self.default_appname, "template_variables", {"FOO": "bar"}) c.check_redflag() report = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_redflag_write_path(self): '''Test check_redflag() - write_path''' c = ClickReviewSecurity(self.test_name) self.set_test_security_manifest(self.default_appname, "write_path", ["/"]) c.check_redflag() report = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_apparmor_profile(self): '''Test check_apparmor_profile()''' self.set_test_pkgfmt("snap", "15.04") policy = ''' ###VAR### ###PROFILEATTACH### { #include # Read-only for the install directory @{CLICK_DIR}/@{APP_PKGNAME}/@{APP_VERSION}/** mrklix, } ''' self.set_test_security_profile(self.default_appname, policy) c = ClickReviewSecurity(self.test_name) c.check_apparmor_profile() report = c.click_report expected_counts = {'info': 5, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_apparmor_profile_missing_var(self): '''Test check_apparmor_profile() - missing ###VAR###''' self.set_test_pkgfmt("snap", "15.04") policy = ''' ###PROFILEATTACH### { #include # Read-only for the install directory @{CLICK_DIR}/@{APP_PKGNAME}/@{APP_VERSION}/** mrklix, } ''' self.set_test_security_profile(self.default_appname, policy) c = ClickReviewSecurity(self.test_name) c.check_apparmor_profile() report = c.click_report expected_counts = {'info': None, 'warn': 1, 'error': 0} self.check_results(report, expected_counts) def test_check_apparmor_profile_missing_app_pkgname(self): '''Test check_apparmor_profile() - missing @{APP_PKGNAME}''' self.set_test_pkgfmt("snap", "15.04") policy = ''' ###VAR### ###PROFILEATTACH### { #include @{CLICK_DIR}/*/@{APP_VERSION}/** mrklix, } ''' self.set_test_security_profile(self.default_appname, policy) c = ClickReviewSecurity(self.test_name) c.check_apparmor_profile() report = c.click_report expected_counts = {'info': None, 'warn': 1, 'error': 0} self.check_results(report, expected_counts) def test_check_apparmor_profile_missing_vars_unconfined(self): '''Test check_apparmor_profile() - missing vars with unconfined boilerplate (first test) ''' self.set_test_pkgfmt("snap", "15.04") policy = ''' # Unrestricted AppArmor policy for fwk-name_svc ###VAR### ###PROFILEATTACH### { #include } ''' self.set_test_security_profile(self.default_appname, policy) c = ClickReviewSecurity(self.test_name) c.check_apparmor_profile() report = c.click_report expected_counts = {'info': 5, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_apparmor_profile_missing_var_unconfined2(self): '''Test check_apparmor_profile() - missing vars with unconfined boilerplate (second test) ''' self.set_test_pkgfmt("snap", "15.04") policy = ''' # This profile offers no protection ###VAR### ###PROFILEATTACH### { #include } ''' self.set_test_security_profile(self.default_appname, policy) c = ClickReviewSecurity(self.test_name) c.check_apparmor_profile() report = c.click_report expected_counts = {'info': 5, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_security_template_default(self): '''Test check_security_template() - default''' self.set_test_pkgfmt("snap", "15.04") self.set_test_security_manifest(self.default_appname, "template", None) self._set_yaml_binary([]) c = ClickReviewSecurity(self.test_name) c.check_security_template() report = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_security_template_default_with_exec(self): '''Test check_security_template() - default with exec''' self.set_test_pkgfmt("snap", "15.04") self.set_test_security_manifest(self.default_appname, "template", None) self._set_yaml_binary([('exec', 'bin/%s' % self.default_appname)]) c = ClickReviewSecurity(self.test_name) c.check_security_template() report = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_security_template_nondefault(self): '''Test check_security_template() - nondefault''' self.set_test_pkgfmt("snap", "15.04") self.set_test_security_manifest(self.default_appname, "template", "nondefault") self._set_yaml_binary([('security-template', 'nondefault')]) c = ClickReviewSecurity(self.test_name) c.check_security_template() report = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_security_template_nondefault_1504(self): '''Test check_security_template() - nondefault - 15.04''' self.set_test_pkgfmt("snap", "15.04") self.set_test_security_manifest(self.default_appname, "template", "nondefault") self._set_yaml_binary([('security-template', 'nondefault')]) c = ClickReviewSecurity(self.test_name) c.check_security_template() report = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_security_template_bad(self): '''Test check_security_template() - {}''' self.set_test_pkgfmt("snap", "15.04") self._set_yaml_binary([('security-template', {})]) c = ClickReviewSecurity(self.test_name) c.check_security_template() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_security_caps_default(self): '''Test check_security_caps() - default (networking)''' self.set_test_pkgfmt("snap", "15.04") self.set_test_security_manifest(self.default_appname, "caps", ['networking']) self._set_yaml_binary([('caps', ['networking'])]) c = ClickReviewSecurity(self.test_name) c.check_security_caps() report = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_security_caps_default2(self): '''Test check_security_caps() - default (network-client)''' self.set_test_pkgfmt("snap", "15.04") self.set_test_security_manifest(self.default_appname, "caps", ['network-client']) self._set_yaml_binary([('caps', ['network-client'])]) c = ClickReviewSecurity(self.test_name) c.check_security_caps() report = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_security_caps_default_with_exec(self): '''Test check_security_caps() - default with exec (networking)''' self.set_test_pkgfmt("snap", "15.04") self.set_test_security_manifest(self.default_appname, "caps", ['networking']) self._set_yaml_binary([('exec', 'bin/%s' % self.default_appname), ('caps', ['networking'])]) c = ClickReviewSecurity(self.test_name) c.check_security_caps() report = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_security_caps_default_with_exec2(self): '''Test check_security_caps() - default with exec (network-client)''' self.set_test_pkgfmt("snap", "15.04") self.set_test_security_manifest(self.default_appname, "caps", ['network-client']) self._set_yaml_binary([('exec', 'bin/%s' % self.default_appname), ('caps', ['network-client'])]) c = ClickReviewSecurity(self.test_name) c.check_security_caps() report = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_security_caps_nondefault(self): '''Test check_security_caps() - nondefault''' self.set_test_pkgfmt("snap", "15.04") self.set_test_security_manifest(self.default_appname, "caps", []) self._set_yaml_binary([('caps', [])]) c = ClickReviewSecurity(self.test_name) c.check_security_caps() report = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_security_caps_bad(self): '''Test check_security_caps() - {}''' self.set_test_pkgfmt("snap", "15.04") self._set_yaml_binary([('caps', {})]) c = ClickReviewSecurity(self.test_name) c.check_security_caps() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_security_yaml_and_click_name_relative(self): '''Test check_security_yaml_and_click() - relative path''' self.set_test_pkgfmt("snap", "15.04") self._set_yaml_binary([('caps', ['networking'])], name="bin/%s" % self.default_appname) c = ClickReviewSecurity(self.test_name) # update the manifest and test_manifest c.manifest["hooks"][self.default_appname]['bin-path'] = "bin/path" c.check_security_yaml_and_click() report = c.click_report expected_counts = {'info': 5, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_security_yaml_and_click_snappy_1504(self): '''Test check_security_yaml_and_click() - snappy 15.04''' self.set_test_pkgfmt("snap", "15.04") self._set_yaml_binary([('caps', ['networking'])], name="bin/%s" % self.default_appname) c = ClickReviewSecurity(self.test_name) # update the manifest and test_manifest c.manifest["hooks"][self.default_appname]['bin-path'] = "bin/path" c.check_security_yaml_and_click() report = c.click_report expected_counts = {'info': 5, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_security_yaml_and_click_name_exec(self): '''Test check_security_yaml_and_click() - uses exec''' self.set_test_pkgfmt("snap", "15.04") self._set_yaml_binary([('caps', ['networking']), ('exec', "bin/%s" % self.default_appname)], name=self.default_appname) c = ClickReviewSecurity(self.test_name) # update the manifest and test_manifest c.manifest["hooks"][self.default_appname]['bin-path'] = "bin/path" c.check_security_yaml_and_click() report = c.click_report expected_counts = {'info': 6, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_security_yaml_and_click_matching_template(self): '''Test check_security_yaml_and_click() - matching default template''' self.set_test_pkgfmt("snap", "15.04") self._set_yaml_binary([('caps', ['networking'])]) self.set_test_security_manifest(self.default_appname, "template", "default") c = ClickReviewSecurity(self.test_name) c.manifest["hooks"][self.default_appname]['bin-path'] = "bin/path" del c.pkg_yaml['binaries'][0]['security-template'] c.check_security_yaml_and_click() report = c.click_report expected_counts = {'info': 6, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_security_yaml_and_click_security_override(self): '''Test check_security_yaml_and_click() - security-override''' self.set_test_pkgfmt("snap", "15.04") self._set_yaml_binary([('security-template', 'default')]) self._set_yaml_binary([('security-override', {})]) self.set_test_security_manifest(self.default_appname, "template", "default") c = ClickReviewSecurity(self.test_name) c.manifest["hooks"][self.default_appname]['bin-path'] = "bin/path" c.check_security_yaml_and_click() report = c.click_report expected_counts = {'info': 3, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_security_yaml_and_click_security_policy(self): '''Test check_security_yaml_and_click() - security-policy''' self.set_test_pkgfmt("snap", "15.04") self._set_yaml_binary([('security-template', 'default')]) self._set_yaml_binary([('security-policy', {})]) self.set_test_security_manifest(self.default_appname, "template", "default") c = ClickReviewSecurity(self.test_name) c.manifest["hooks"][self.default_appname]['bin-path'] = "bin/path" c.check_security_yaml_and_click() report = c.click_report expected_counts = {'info': 3, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_security_yaml_and_click_matching_caps(self): '''Test check_security_yaml_and_click() - matching default caps''' self.set_test_pkgfmt("snap", "15.04") self._set_yaml_binary([('caps', [])]) self.set_test_security_manifest(self.default_appname, "policy_groups", ['networking']) c = ClickReviewSecurity(self.test_name) c.manifest["hooks"][self.default_appname]['bin-path'] = "bin/path" del c.pkg_yaml['binaries'][0]['caps'] c.check_security_yaml_and_click() report = c.click_report expected_counts = {'info': 5, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_security_yaml_and_click_matching_caps2(self): '''Test check_security_yaml_and_click() - matching default caps''' self.set_test_pkgfmt("snap", "15.04") self._set_yaml_binary([('caps', [])]) self.set_test_security_manifest(self.default_appname, "policy_groups", ['network-client']) c = ClickReviewSecurity(self.test_name) c.manifest["hooks"][self.default_appname]['bin-path'] = "bin/path" del c.pkg_yaml['binaries'][0]['caps'] c.check_security_yaml_and_click() report = c.click_report expected_counts = {'info': 5, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_security_yaml_and_click_matching_no_caps(self): '''Test check_security_yaml_and_click() - matching no caps''' self.set_test_pkgfmt("snap", "15.04") self._set_yaml_binary([('caps', [])]) self.set_test_security_manifest(self.default_appname, "policy_groups", None) c = ClickReviewSecurity(self.test_name) c.manifest["hooks"][self.default_appname]['bin-path'] = "bin/path" c.check_security_yaml_and_click() report = c.click_report expected_counts = {'info': 5, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_security_yaml_and_click_matching_no_caps_template(self): '''Test check_security_yaml_and_click() - matching no caps with template ''' self.set_test_pkgfmt("snap", "15.04") self._set_yaml_binary([('security-template', 'nondefault')]) self.set_test_security_manifest(self.default_appname, "policy_groups", None) self.set_test_security_manifest(self.default_appname, "template", "nondefault") c = ClickReviewSecurity(self.test_name) c.manifest["hooks"][self.default_appname]['bin-path'] = "bin/path" c.check_security_yaml_and_click() report = c.click_report expected_counts = {'info': 5, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_security_yaml_and_click_mismatch0(self): '''Test check_security_yaml_and_click() - missing app in hooks''' self.set_test_pkgfmt("snap", "15.04") self._set_yaml_binary([]) self.set_test_security_manifest(self.default_appname, "template", None) c = ClickReviewSecurity(self.test_name) del c.manifest["hooks"][self.default_appname] self._update_test_manifest() c.security_apps.remove(self.default_appname) c.check_security_yaml_and_click() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_security_yaml_and_click_mismatch2(self): '''Test check_security_yaml_and_click() - missing apparmor in hooks''' self.set_test_pkgfmt("snap", "15.04") self._set_yaml_binary([]) c = ClickReviewSecurity(self.test_name) c.manifest["hooks"][self.default_appname]['bin-path'] = "bin/path" del c.manifest["hooks"][self.default_appname]['apparmor'] c.security_apps.remove(self.default_appname) self._update_test_manifest() c.check_security_yaml_and_click() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_security_yaml_and_click_mismatch3(self): '''Test check_security_yaml_and_click() - missing security-template''' self.set_test_pkgfmt("snap", "15.04") self._set_yaml_binary([('caps', ['networking'])]) self.set_test_security_manifest(self.default_appname, "template", "nondefault") c = ClickReviewSecurity(self.test_name) c.manifest["hooks"][self.default_appname]['bin-path'] = "bin/path" del c.pkg_yaml['binaries'][0]['security-template'] c.check_security_yaml_and_click() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_security_yaml_and_click_mismatch4(self): '''Test check_security_yaml_and_click() - missing click template''' self.set_test_pkgfmt("snap", "15.04") self._set_yaml_binary([("security-template", "nondefault"), ('caps', ['networking'])], name=self.default_appname) self.set_test_security_manifest(self.default_appname, "template", None) c = ClickReviewSecurity(self.test_name) c.manifest["hooks"][self.default_appname]['bin-path'] = "bin/path" c.check_security_yaml_and_click() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_security_yaml_and_click_mismatch5(self): '''Test check_security_yaml_and_click() - different templates''' self.set_test_pkgfmt("snap", "15.04") self.set_test_security_manifest(self.default_appname, "template", "other") self._set_yaml_binary([("security-template", "nondefault"), ('caps', ['networking'])], name=self.default_appname) c = ClickReviewSecurity(self.test_name) c.manifest["hooks"][self.default_appname]['bin-path'] = "bin/path" c.check_security_yaml_and_click() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 2} self.check_results(report, expected_counts) def test_check_security_yaml_and_click_mismatch6(self): '''Test check_security_yaml_and_click() - missing caps in yaml''' self.set_test_pkgfmt("snap", "15.04") self._set_yaml_binary([('caps', [])]) self.set_test_security_manifest(self.default_appname, "policy_groups", ["1", "2"]) c = ClickReviewSecurity(self.test_name) c.manifest["hooks"][self.default_appname]['bin-path'] = "bin/path" del c.pkg_yaml['binaries'][0]['caps'] c.check_security_yaml_and_click() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_security_yaml_and_click_mismatch7(self): '''Test check_security_yaml_and_click() - missing policy_groups''' self.set_test_pkgfmt("snap", "15.04") self._set_yaml_binary([('caps', ['networking'])], name=self.default_appname) self.set_test_security_manifest(self.default_appname, "policy_groups", None) c = ClickReviewSecurity(self.test_name) c.manifest["hooks"][self.default_appname]['bin-path'] = "bin/path" c.check_security_yaml_and_click() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_security_yaml_and_click_mismatch8(self): '''Test check_security_yaml_and_click() - different caps/groups''' self.set_test_pkgfmt("snap", "15.04") self.set_test_security_manifest(self.default_appname, "policy_groups", ["1", "2"]) self._set_yaml_binary([('caps', ["3"])], name=self.default_appname) c = ClickReviewSecurity(self.test_name) c.manifest["hooks"][self.default_appname]['bin-path'] = "bin/path" c.check_security_yaml_and_click() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 2} self.check_results(report, expected_counts) def test_check_security_yaml_and_click_mismatch9(self): '''Test check_security_yaml_and_click() - unordered caps/groups''' self.set_test_pkgfmt("snap", "15.04") self.set_test_security_manifest(self.default_appname, "policy_groups", ["1", "2"]) self._set_yaml_binary([('caps', ["2", "1"])], name=self.default_appname) c = ClickReviewSecurity(self.test_name) c.manifest["hooks"][self.default_appname]['bin-path'] = "bin/path" c.check_security_yaml_and_click() report = c.click_report expected_counts = {'info': 5, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_security_yaml_and_click_mismatch10(self): '''Test check_security_yaml_and_click() - missing caps in both''' self.set_test_pkgfmt("snap", "15.04") self._set_yaml_binary([('caps', [])]) self.set_test_security_manifest(self.default_appname, "policy_groups", None) c = ClickReviewSecurity(self.test_name) c.manifest["hooks"][self.default_appname]['bin-path'] = "bin/path" del c.pkg_yaml['binaries'][0]['caps'] c.check_security_yaml_and_click() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_security_yaml_and_click_mismatch11(self): '''Test check_security_yaml_and_click() - default caps with template''' self.set_test_pkgfmt("snap", "15.04") self._set_yaml_binary([('security-template', 'nondefault')]) self.set_test_security_manifest(self.default_appname, "policy_groups", ['networking']) self.set_test_security_manifest(self.default_appname, "template", "nondefault") c = ClickReviewSecurity(self.test_name) c.manifest["hooks"][self.default_appname]['bin-path'] = "bin/path" del c.pkg_yaml['binaries'][0]['caps'] c.check_security_yaml_and_click() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_security_yaml_and_click_mismatch12(self): '''Test check_security_yaml_and_click() - default caps with template''' self.set_test_pkgfmt("snap", "15.04") self._set_yaml_binary([('security-template', 'nondefault')]) self.set_test_security_manifest(self.default_appname, "policy_groups", ['network-client']) self.set_test_security_manifest(self.default_appname, "template", "nondefault") c = ClickReviewSecurity(self.test_name) c.manifest["hooks"][self.default_appname]['bin-path'] = "bin/path" del c.pkg_yaml['binaries'][0]['caps'] c.check_security_yaml_and_click() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_security_yaml_and_click_invalid_template(self): '''Test check_security_yaml_and_click() - invalid template''' self.set_test_pkgfmt("snap", "15.04") self.set_test_security_manifest(self.default_appname, "template", "other") self._set_yaml_binary([("security-template", None), ('caps', ['networking'])], name=self.default_appname) c = ClickReviewSecurity(self.test_name) c.manifest["hooks"][self.default_appname]['bin-path'] = "bin/path" c.check_security_yaml_and_click() report = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_security_yaml_and_click_invalid_caps(self): '''Test check_security_yaml_and_click() - invalid caps''' self.set_test_pkgfmt("snap", "15.04") self.set_test_security_manifest(self.default_appname, "template", "other") self._set_yaml_binary([("security-template", "nondefault"), ('caps', None)], name=self.default_appname) c = ClickReviewSecurity(self.test_name) c.manifest["hooks"][self.default_appname]['bin-path'] = "bin/path" c.check_security_yaml_and_click() report = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_security_yaml_combinations(self): '''Test check_security_yaml_combinations()''' self.set_test_pkgfmt("snap", "15.04") self._set_yaml_binary([], name=self.default_appname) c = ClickReviewSecurity(self.test_name) c.check_security_yaml_combinations() report = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_security_yaml_combinations1(self): '''Test check_security_yaml_combinations() - template''' self.set_test_pkgfmt("snap", "15.04") self._set_yaml_binary([('security-template', 'foo')], name=self.default_appname) c = ClickReviewSecurity(self.test_name) c.check_security_yaml_combinations() report = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_security_yaml_combinations2(self): '''Test check_security_yaml_combinations() - caps''' self.set_test_pkgfmt("snap", "15.04") self._set_yaml_binary([('caps', ['networking'])], name=self.default_appname) c = ClickReviewSecurity(self.test_name) c.check_security_yaml_combinations() report = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_security_yaml_combinations3(self): '''Test check_security_yaml_combinations() - template,caps''' self.set_test_pkgfmt("snap", "15.04") self._set_yaml_binary([('security-template', 'foo'), ('caps', ['networking'])], name=self.default_appname) c = ClickReviewSecurity(self.test_name) c.check_security_yaml_combinations() report = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_security_yaml_combinations4(self): '''Test check_security_yaml_combinations() - override''' self.set_test_pkgfmt("snap", "15.04") self._set_yaml_binary([('security-override', {'apparmor': 'foo.aa', 'seccomp': 'foo.sc'})], name=self.default_appname) c = ClickReviewSecurity(self.test_name) c.check_security_yaml_combinations() report = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_security_yaml_combinations5(self): '''Test check_security_yaml_combinations() - override, template''' self.set_test_pkgfmt("snap", "15.04") self._set_yaml_binary([('security-template', 'foo'), ('security-override', {'apparmor': 'foo.aa', 'seccomp': 'foo.sc'})], name=self.default_appname) c = ClickReviewSecurity(self.test_name) c.check_security_yaml_combinations() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_security_yaml_combinations6(self): '''Test check_security_yaml_combinations() - override, caps''' self.set_test_pkgfmt("snap", "15.04") self._set_yaml_binary([('caps', ['networking']), ('security-override', {'apparmor': 'foo.aa', 'seccomp': 'foo.sc'})], name=self.default_appname) c = ClickReviewSecurity(self.test_name) c.check_security_yaml_combinations() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_security_yaml_combinations7(self): '''Test check_security_yaml_combinations() - override, caps, template ''' self.set_test_pkgfmt("snap", "15.04") self._set_yaml_binary([('security-template', 'foo'), ('caps', ['networking']), ('security-override', {'apparmor': 'foo.aa', 'seccomp': 'foo.sc'})], name=self.default_appname) c = ClickReviewSecurity(self.test_name) c.check_security_yaml_combinations() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_security_yaml_combinations8(self): '''Test check_security_yaml_combinations() - override, caps, template, policy ''' self.set_test_pkgfmt("snap", "15.04") self._set_yaml_binary([('security-template', 'foo'), ('caps', ['networking']), ('security-policy', {'apparmor': 'foo.aa', 'seccomp': 'foo.sc'}), ('security-override', {'apparmor': 'foo.aa', 'seccomp': 'foo.sc'})], name=self.default_appname) c = ClickReviewSecurity(self.test_name) c.check_security_yaml_combinations() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_security_yaml_combinations9(self): '''Test check_security_yaml_combinations() - policy''' self.set_test_pkgfmt("snap", "15.04") self._set_yaml_binary([('security-policy', {'apparmor': 'foo.aa', 'seccomp': 'foo.sc'})], name=self.default_appname) c = ClickReviewSecurity(self.test_name) c.check_security_yaml_combinations() report = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_security_yaml_combinations10(self): '''Test check_security_yaml_combinations() - policy, template''' self.set_test_pkgfmt("snap", "15.04") self._set_yaml_binary([('security-template', 'foo'), ('security-policy', {'apparmor': 'foo.aa', 'seccomp': 'foo.sc'})], name=self.default_appname) c = ClickReviewSecurity(self.test_name) c.check_security_yaml_combinations() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_security_yaml_combinations11(self): '''Test check_security_yaml_combinations() - policy, caps''' self.set_test_pkgfmt("snap", "15.04") self._set_yaml_binary([('caps', ['networking']), ('security-policy', {'apparmor': 'foo.aa', 'seccomp': 'foo.sc'})], name=self.default_appname) c = ClickReviewSecurity(self.test_name) c.check_security_yaml_combinations() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_security_yaml_combinations12(self): '''Test check_security_yaml_combinations() - policy, caps, template ''' self.set_test_pkgfmt("snap", "15.04") self._set_yaml_binary([('security-template', 'foo'), ('caps', ['networking']), ('security-policy', {'apparmor': 'foo.aa', 'seccomp': 'foo.sc'})], name=self.default_appname) c = ClickReviewSecurity(self.test_name) c.check_security_yaml_combinations() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_security_yaml_override_and_click(self): '''Test check_security_yaml_override_and_click()''' self.set_test_pkgfmt("snap", "15.04") self.set_test_security_manifest(self.default_appname, "template", None) self._set_yaml_binary([]) c = ClickReviewSecurity(self.test_name) c.check_security_yaml_override_and_click() report = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_security_yaml_override_and_click_1504(self): '''Test check_security_yaml_override_and_click() - 15.04''' self.set_test_pkgfmt("snap", "15.04") self.set_test_security_manifest(self.default_appname, "template", None) self._set_yaml_binary([]) c = ClickReviewSecurity(self.test_name) c.check_security_yaml_override_and_click() report = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_security_yaml_override_and_click_bad(self): '''Test check_security_yaml_override_and_click() - bad''' self.set_test_pkgfmt("snap", "15.04") self.set_test_security_manifest(self.default_appname, "template", None) self._set_yaml_binary([('security-override', {'apparmor': 'something.else'})], name=self.default_appname) c = ClickReviewSecurity(self.test_name) c.check_security_yaml_override_and_click() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_security_yaml_override(self): '''Test check_security_yaml_override()''' self.set_test_pkgfmt("snap", "15.04") self.set_test_security_manifest(self.default_appname, "template", None) self._set_yaml_binary([]) c = ClickReviewSecurity(self.test_name) c.check_security_yaml_override() report = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_security_yaml_override_1504(self): '''Test check_security_yaml_override() - 15.04''' self.set_test_pkgfmt("snap", "15.04") self.set_test_security_manifest(self.default_appname, "template", None) self._set_yaml_binary([]) c = ClickReviewSecurity(self.test_name) c.check_security_yaml_override() report = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_security_yaml_override2(self): '''Test check_security_yaml_override() - seccomp/apparmor specified''' self.set_test_pkgfmt("snap", "15.04") self._set_yaml_binary([('security-override', {'apparmor': 'aa', 'seccomp': 'sc'})], name=self.default_appname) c = ClickReviewSecurity(self.test_name) c.check_security_yaml_override() report = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_security_yaml_override4(self): '''Test check_security_yaml_override() - syscalls specified with 15.04 ''' self.set_test_pkgfmt("snap", "15.04") self._set_yaml_binary([('security-override', {'syscalls': 'foo'})], name=self.default_appname) c = ClickReviewSecurity(self.test_name) c.check_security_yaml_override() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_security_yaml_override_missing1(self): '''Test check_security_yaml_override() - missing apparmor''' self.set_test_pkgfmt("snap", "15.04") self._set_yaml_binary([('security-override', {'seccomp': 'sc'})], name=self.default_appname) c = ClickReviewSecurity(self.test_name) c.check_security_yaml_override() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_security_yaml_override_missing2(self): '''Test check_security_yaml_override() - missing seccomp''' self.set_test_pkgfmt("snap", "15.04") self._set_yaml_binary([('security-override', {'apparmor': 'aa'})], name=self.default_appname) c = ClickReviewSecurity(self.test_name) c.check_security_yaml_override() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_security_yaml_policy(self): '''Test check_security_yaml_policy()''' self.set_test_pkgfmt("snap", "15.04") self.set_test_security_manifest(self.default_appname, "template", None) self._set_yaml_binary([]) c = ClickReviewSecurity(self.test_name) c.check_security_yaml_policy() report = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_security_yaml_policy2(self): '''Test check_security_yaml_policy() - seccomp/apparmor specified''' self.set_test_pkgfmt("snap", "15.04") self._set_yaml_binary([('security-policy', {'apparmor': 'aa', 'seccomp': 'sc'})], name=self.default_appname) c = ClickReviewSecurity(self.test_name) c.check_security_yaml_policy() report = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) name = c._get_check_name('yaml_policy_present') m = report['error'][name]['text'] self.assertIn("(NEEDS REVIEW) 'security-policy' not allowed", m) def test_check_security_yaml_policy_missing1(self): '''Test check_security_yaml_policy() - missing apparmor''' self.set_test_pkgfmt("snap", "15.04") self._set_yaml_binary([('security-policy', {'seccomp': 'sc'})], name=self.default_appname) c = ClickReviewSecurity(self.test_name) c.check_security_yaml_policy() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 2} self.check_results(report, expected_counts) name = c._get_check_name('yaml_policy_present') m = report['error'][name]['text'] self.assertIn("(NEEDS REVIEW) 'security-policy' not allowed", m) name = c._get_check_name( 'yaml_policy_format', app='test-app') m = report['error'][name]['text'] self.assertIn("'apparmor' not specified in 'security-policy' " + "for 'test-app'", m) def test_check_security_yaml_policy_missing2(self): '''Test check_security_yaml_policy() - missing seccomp''' self.set_test_pkgfmt("snap", "15.04") self._set_yaml_binary([('security-policy', {'apparmor': 'aa'})], name=self.default_appname) c = ClickReviewSecurity(self.test_name) c.check_security_yaml_policy() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 2} self.check_results(report, expected_counts) name = c._get_check_name('yaml_policy_present') m = report['error'][name]['text'] self.assertIn("(NEEDS REVIEW) 'security-policy' not allowed", m) name = c._get_check_name( 'yaml_policy_format', app='test-app') m = report['error'][name]['text'] self.assertIn("'seccomp' not specified in 'security-policy' " + "for 'test-app'", m) def test_check_template_online_account_provider(self): '''Test check_template_online_account_provider''' self.set_test_account(self.default_appname, "account-provider", "foo") self.set_test_security_manifest(self.default_appname, "template", "ubuntu-account-plugin") self.set_test_security_manifest(self.default_appname, "policy_groups", ["accounts"]) c = ClickReviewSecurity(self.test_name) c.check_template_online_accounts_provider() report = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_template_online_account_provider_1504(self): '''Test check_template_online_account_provider - 15.04''' self.set_test_pkgfmt("snap", "15.04") self.set_test_account(self.default_appname, "account-provider", "foo") self.set_test_security_manifest(self.default_appname, "template", "ubuntu-account-plugin") self.set_test_security_manifest(self.default_appname, "policy_groups", ["accounts"]) c = ClickReviewSecurity(self.test_name) c.check_template_online_accounts_provider() report = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_template_online_account_provider_no_hook(self): '''Test check_template_online_account_provider''' self.set_test_security_manifest(self.default_appname, "template", "ubuntu-account-plugin") c = ClickReviewSecurity(self.test_name) c.check_template_online_accounts_provider() report = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_template_online_account_provider_wrong_template(self): '''Test check_template_online_account_provider - wrong template''' self.set_test_account(self.default_appname, "account-provider", "foo") self.set_test_security_manifest(self.default_appname, "template", "ubuntu-webapp") self.set_test_security_manifest(self.default_appname, "policy_groups", ["accounts"]) c = ClickReviewSecurity(self.test_name) c.check_template_online_accounts_provider() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_template_online_account_provider_wrong_template2(self): '''Test check_template_online_account_provider - default template''' self.set_test_account(self.default_appname, "account-provider", "foo") self.set_test_security_manifest(self.default_appname, "template", None) self.set_test_security_manifest(self.default_appname, "policy_groups", ["accounts"]) c = ClickReviewSecurity(self.test_name) c.check_template_online_accounts_provider() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_template_online_account_qml_plugin(self): '''Test check_template_online_account_qml_plugin''' self.set_test_account(self.default_appname, "account-qml-plugin", "foo") self.set_test_security_manifest(self.default_appname, "template", "ubuntu-account-plugin") self.set_test_security_manifest(self.default_appname, "policy_groups", ["accounts"]) c = ClickReviewSecurity(self.test_name) c.check_template_online_accounts_qml_plugin() report = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_template_online_account_qml_plugin_1504(self): '''Test check_template_online_account_qml_plugin - 15.04''' self.set_test_pkgfmt("snap", "15.04") self.set_test_account(self.default_appname, "account-qml-plugin", "foo") self.set_test_security_manifest(self.default_appname, "template", "ubuntu-account-plugin") self.set_test_security_manifest(self.default_appname, "policy_groups", ["accounts"]) c = ClickReviewSecurity(self.test_name) c.check_template_online_accounts_qml_plugin() report = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_template_online_account_qml_plugin_no_hook(self): '''Test check_template_online_account_qml_plugin''' self.set_test_security_manifest(self.default_appname, "template", "ubuntu-account-plugin") c = ClickReviewSecurity(self.test_name) c.check_template_online_accounts_qml_plugin() report = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_template_online_account_qml_plugin_wrong_template(self): '''Test check_template_online_account_qml_plugin - wrong template''' self.set_test_account(self.default_appname, "account-qml-plugin", "foo") self.set_test_security_manifest(self.default_appname, "template", "ubuntu-webapp") self.set_test_security_manifest(self.default_appname, "policy_groups", ["accounts"]) c = ClickReviewSecurity(self.test_name) c.check_template_online_accounts_qml_plugin() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_template_online_account_qml_plugin_wrong_template2(self): '''Test check_template_online_account_qml_plugin - default template''' self.set_test_account(self.default_appname, "account-qml-plugin", "foo") self.set_test_security_manifest(self.default_appname, "template", None) self.set_test_security_manifest(self.default_appname, "policy_groups", ["accounts"]) c = ClickReviewSecurity(self.test_name) c.check_template_online_accounts_qml_plugin() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_policy_groups_ubuntu_account_plugin_no_hook(self): '''Test check_policy_groups_ubuntu_account_plugin() - no hook''' c = ClickReviewSecurity(self.test_name) c.check_policy_groups_ubuntu_account_plugin() report = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_policy_groups_ubuntu_account_plugin(self): '''Test check_policy_groups_ubuntu_account_plugin()''' self.set_test_account(self.default_appname, "account-qml-plugin", "foo") self.set_test_security_manifest(self.default_appname, "template", "ubuntu-account-plugin") self.set_test_security_manifest(self.default_appname, "policy_groups", ["accounts", "networking", "webview"]) c = ClickReviewSecurity(self.test_name) c.check_policy_groups_ubuntu_account_plugin() report = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_policy_groups_ubuntu_account_plugin_missing(self): '''Test check_policy_groups_ubuntu_account_plugin - missing''' self.set_test_account(self.default_appname, "account-qml-plugin", "foo") self.set_test_security_manifest(self.default_appname, "template", "ubuntu-account-plugin") self.set_test_security_manifest(self.default_appname, "policy_groups", ["accounts", "webview"]) c = ClickReviewSecurity(self.test_name) c.check_policy_groups_ubuntu_account_plugin() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_policy_groups_ubuntu_account_plugin_bad(self): '''Test check_policy_groups_ubuntu_account_plugin - bad''' self.set_test_account(self.default_appname, "account-qml-plugin", "foo") self.set_test_security_manifest(self.default_appname, "template", "ubuntu-account-plugin") self.set_test_security_manifest(self.default_appname, "policy_groups", ["accounts", "networking", "push-notification-client"]) c = ClickReviewSecurity(self.test_name) c.check_policy_groups_ubuntu_account_plugin() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_apparmor_profile_name_length(self): '''Test check_apparmor_profile_name_length()''' c = ClickReviewSecurity(self.test_name) c.check_apparmor_profile_name_length() report = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_apparmor_profile_name_length_bad(self): '''Test check_apparmor_profile_name_length() - too long''' c = ClickReviewSecurity(self.test_name) c.click_pkgname += 'A' * 253 c.check_apparmor_profile_name_length() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_apparmor_profile_name_length_bad2(self): '''Test check_apparmor_profile_name_length() - longer than advised''' c = ClickReviewSecurity(self.test_name) c.click_pkgname += 'A' * 100 c.check_apparmor_profile_name_length() report = c.click_report expected_counts = {'info': None, 'warn': 1, 'error': 0} self.check_results(report, expected_counts) click-reviewers-tools-0.44~16.04.1/clickreviews/tests/test_aaa_example_cr_skeleton.py0000664000000000000000000001136212666350676025631 0ustar '''test_cr_skeleton.py: tests for the cr_skeleton module''' # # Copyright (C) 2014 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from clickreviews.cr_skeleton import ClickReviewSkeleton import clickreviews.cr_tests as cr_tests class TestClickReviewSkeleton(cr_tests.TestClickReview): """Tests for the lint review tool.""" def test_check_foo(self): '''Test check_foo()''' c = ClickReviewSkeleton(self.test_name) c.check_foo() r = c.click_report # We should end up with 1 info expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_bar(self): '''Test check_bar()''' c = ClickReviewSkeleton(self.test_name) c.check_bar() r = c.click_report # We should end up with 1 error expected_counts = {'info': 0, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_baz(self): '''Test check_baz()''' c = ClickReviewSkeleton(self.test_name) c.check_baz() r = c.click_report # We should end up with 1 warning expected_counts = {'info': 0, 'warn': 1, 'error': 0} self.check_results(r, expected_counts) # Check specific entries expected = dict() expected['info'] = dict() expected['warn'] = dict() name = c._get_check_name('baz') expected['warn'][name] = {"text": "TODO", "link": "http://example.com"} expected['error'] = dict() self.check_results(r, expected=expected) def test_output(self): '''Test output''' # Update the control field and output the changes self.set_test_control('Package', "my.mock.app.name") self.set_test_manifest('name', "my.mock.app.name") self._update_test_name() import pprint import json print(''' = test output = == Mock filename == %s == Mock control == %s == Mock manifest ==''' % (self.test_name, cr_tests.TEST_CONTROL)) pprint.pprint(json.loads(cr_tests.TEST_MANIFEST)) def test_check_peer_hooks(self): '''Test check_peer_hooks()''' c = ClickReviewSkeleton(self.test_name) # create a new hooks database for our peer hooks tests tmp = dict() # add our hook tmp["skeleton"] = "foo.skeleton" # add any required peer hooks tmp["desktop"] = "foo.desktop" tmp["apparmor"] = "foo.apparmor" # update the manifest and test_manifest c.manifest["hooks"][self.default_appname] = tmp self._update_test_manifest() # do the test c.check_peer_hooks() r = c.click_report # We should end up with 2 info expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_peer_hooks_disallowed(self): '''Test check_peer_hooks() - disallowed''' c = ClickReviewSkeleton(self.test_name) # create a new hooks database for our peer hooks tests tmp = dict() # add our hook tmp["skeleton"] = "foo.skeleton" # add any required peer hooks tmp["desktop"] = "foo.desktop" tmp["apparmor"] = "foo.apparmor" # add something not allowed tmp["nonexistent"] = "nonexistent-hook" c.manifest["hooks"][self.default_appname] = tmp self._update_test_manifest() # do the test c.check_peer_hooks() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_peer_hooks_required(self): '''Test check_peer_hooks() - required''' c = ClickReviewSkeleton(self.test_name) # create a new hooks database for our peer hooks tests tmp = dict() # add our hook tmp["skeleton"] = "foo.skeleton" # skip adding required hooks c.manifest["hooks"][self.default_appname] = tmp self._update_test_manifest() # do the test c.check_peer_hooks() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) click-reviewers-tools-0.44~16.04.1/clickreviews/tests/test_cr_systemd.py0000664000000000000000000011526312666350676023165 0ustar '''test_cr_systemd.py: tests for the cr_systemd module''' # # Copyright (C) 2015 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from clickreviews.cr_systemd import ClickReviewSystemd import clickreviews.cr_tests as cr_tests class TestClickReviewSystemd(cr_tests.TestClickReview): """Tests for the lint review tool.""" def setUp(self): super().setUp() self.set_test_pkgfmt("snap", "15.04") def _create_ports(self): ports = {'internal': {'int1': {"port": '8081/tcp', "negotiable": True}}, 'external': {'ext1': {"port": '80/tcp', "negotiable": False}, 'ext2': {"port": '88/udp'} } } return ports def _set_service(self, entries, name=None): d = dict() if name is None: d['name'] = self.default_appname else: d['name'] = name for (key, value) in entries: d[key] = value self.set_test_pkg_yaml("services", [d]) self.set_test_systemd(d['name'], 'name', d['name']) def test_check_snappy_required(self): '''Test check_required() - has start and description''' self.set_test_systemd(self.default_appname, key="start", value="bin/foo") self.set_test_systemd(self.default_appname, key="description", value="something") c = ClickReviewSystemd(self.test_name) c.check_snappy_required() r = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_snappy_required_empty_value(self): '''Test check_snappy_required() - empty start''' self.set_test_systemd(self.default_appname, key="start", value="") self.set_test_systemd(self.default_appname, key="description", value="something") c = ClickReviewSystemd(self.test_name) c.check_snappy_required() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_snappy_required_bad_value(self): '''Test check_snappy_required() - bad start''' self.set_test_systemd(self.default_appname, key="start", value=[]) self.set_test_systemd(self.default_appname, key="description", value="something") c = ClickReviewSystemd(self.test_name) c.check_snappy_required() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_snappy_required_multiple(self): '''Test check_snappy_required() - multiple''' self.set_test_systemd(self.default_appname, key="start", value="/bin/foo") self.set_test_systemd(self.default_appname, key="description", value="something") self.set_test_systemd(self.default_appname, key="stop", value="/bin/foo-stop") c = ClickReviewSystemd(self.test_name) c.check_snappy_required() r = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_snappy_required_multiple2(self): '''Test check_snappy_required() - multiple with nonexistent''' self.set_test_systemd(self.default_appname, key="start", value="/bin/foo") self.set_test_systemd(self.default_appname, key="description", value="something") self.set_test_systemd(self.default_appname, key="nonexistent", value="foo") c = ClickReviewSystemd(self.test_name) c.check_snappy_required() r = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_snappy_optional_none(self): '''Test check_snappy_optional() - start only''' self.set_test_systemd(self.default_appname, key="start", value="/bin/foo") c = ClickReviewSystemd(self.test_name) c.check_snappy_optional() r = c.click_report expected_counts = {'info': 9, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_snappy_optional_stop_empty(self): '''Test check_snappy_optional() - with empty stop''' self.set_test_systemd(self.default_appname, key="start", value="/bin/foo") self.set_test_systemd(self.default_appname, key="stop", value="") c = ClickReviewSystemd(self.test_name) c.check_snappy_optional() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_snappy_optional_stop_bad(self): '''Test check_snappy_optional() - with bad stop''' self.set_test_systemd(self.default_appname, key="start", value="/bin/foo") self.set_test_systemd(self.default_appname, key="stop", value=[]) c = ClickReviewSystemd(self.test_name) c.check_snappy_optional() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_snappy_optional_stop_nonexistent(self): '''Test check_snappy_optional() - with stop plus nonexistent''' self.set_test_systemd(self.default_appname, key="start", value="/bin/foo") self.set_test_systemd(self.default_appname, key="stop", value="bin/bar") self.set_test_systemd(self.default_appname, key="nonexistent", value="foo") c = ClickReviewSystemd(self.test_name) c.check_snappy_optional() r = c.click_report expected_counts = {'info': 9, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_snappy_optional_stop_without_start(self): '''Test check_snappy_optional() - with stop, no start''' self.set_test_systemd(self.default_appname, key="stop", value="/bin/bar") c = ClickReviewSystemd(self.test_name) c.check_snappy_optional() r = c.click_report expected_counts = {'info': 9, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_snappy_optional_stop_without_start2(self): '''Test check_snappy_optional() - with stop, nonexistent, no start''' self.set_test_systemd(self.default_appname, key="stop", value="/bin/bar") self.set_test_systemd(self.default_appname, key="nonexistent", value="example.com") c = ClickReviewSystemd(self.test_name) c.check_snappy_optional() r = c.click_report expected_counts = {'info': 9, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_snappy_unknown(self): '''Test check_snappy_unknown()''' self.set_test_systemd(self.default_appname, key="nonexistent", value="foo") c = ClickReviewSystemd(self.test_name) c.check_snappy_unknown() r = c.click_report expected_counts = {'info': 0, 'warn': 1, 'error': 0} self.check_results(r, expected_counts) def test_check_snappy_unknown_multiple(self): '''Test check_snappy_unknown() - multiple with nonexistent''' self.set_test_systemd(self.default_appname, key="start", value="/bin/foo") self.set_test_systemd(self.default_appname, key="stop", value="bin/bar") self.set_test_systemd(self.default_appname, key="nonexistent", value="foo") c = ClickReviewSystemd(self.test_name) c.check_snappy_unknown() r = c.click_report expected_counts = {'info': 0, 'warn': 1, 'error': 0} self.check_results(r, expected_counts) def test_check_snappy_service_description(self): '''Test check_snappy_service_description()''' self._set_service([("description", "some description")]) c = ClickReviewSystemd(self.test_name) c.check_snappy_service_description() r = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_snappy_service_description_unspecified(self): '''Test check_snappy_service_description() - unspecified''' # self._set_service([("description", None)]) c = ClickReviewSystemd(self.test_name) c.check_snappy_service_description() r = c.click_report # required check is done elsewhere, so no error expected_counts = {'info': 0, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_snappy_service_description_empty(self): '''Test check_snappy_service_description() - empty''' self._set_service([("description", "")]) c = ClickReviewSystemd(self.test_name) c.check_snappy_service_description() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_snappy_service_start(self): '''Test check_snappy_service_start()''' self._set_service([("start", "some/start")]) c = ClickReviewSystemd(self.test_name) c.check_snappy_service_start() r = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_snappy_service_start_unspecified(self): '''Test check_snappy_service_start() - unspecified''' # self._set_service([("start", None)]) c = ClickReviewSystemd(self.test_name) c.check_snappy_service_start() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_snappy_service_start_empty(self): '''Test check_snappy_service_start() - empty''' self._set_service([("start", "")]) c = ClickReviewSystemd(self.test_name) c.check_snappy_service_start() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_snappy_service_start_absolute_path(self): '''Test check_snappy_service_start() - absolute path''' self._set_service([("start", "/foo/bar/some/start")]) c = ClickReviewSystemd(self.test_name) c.check_snappy_service_start() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_snappy_service_stop(self): '''Test check_snappy_service_stop()''' self._set_service([("stop", "some/stop")]) c = ClickReviewSystemd(self.test_name) c.check_snappy_service_stop() r = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_snappy_service_stop_unspecified(self): '''Test check_snappy_service_stop() - unspecified''' # self._set_service([("stop", None)]) c = ClickReviewSystemd(self.test_name) c.check_snappy_service_stop() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_snappy_service_stop_empty(self): '''Test check_snappy_service_stop() - empty''' self._set_service([("stop", "")]) c = ClickReviewSystemd(self.test_name) c.check_snappy_service_stop() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_snappy_service_stop_absolute_path(self): '''Test check_snappy_service_stop() - absolute path''' self._set_service([("stop", "/foo/bar/some/stop")]) c = ClickReviewSystemd(self.test_name) c.check_snappy_service_stop() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_snappy_service_poststop(self): '''Test check_snappy_service_poststop()''' self._set_service([("poststop", "some/poststop")]) c = ClickReviewSystemd(self.test_name) c.check_snappy_service_poststop() r = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_snappy_service_poststop_unspecified(self): '''Test check_snappy_service_poststop() - unspecified''' # self._set_service([("poststop", None)]) c = ClickReviewSystemd(self.test_name) c.check_snappy_service_poststop() r = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_snappy_service_poststop_empty(self): '''Test check_snappy_service_poststop() - empty''' self._set_service([("poststop", "")]) c = ClickReviewSystemd(self.test_name) c.check_snappy_service_poststop() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_snappy_service_poststop_absolute_path(self): '''Test check_snappy_service_poststop() - absolute path''' self._set_service([("poststop", "/foo/bar/some/poststop")]) c = ClickReviewSystemd(self.test_name) c.check_snappy_service_poststop() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_snappy_service_stop_timeout(self): '''Test check_snappy_service_stop_timeout()''' self._set_service([("start", "bin/foo"), ("description", "something"), ("stop-timeout", 30)]) c = ClickReviewSystemd(self.test_name) c.check_snappy_service_stop_timeout() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_snappy_service_stop_timeout_granularity(self): '''Test check_snappy_service_stop_timeout()''' self._set_service([("start", "bin/foo"), ("description", "something"), ("stop-timeout", '30s')]) c = ClickReviewSystemd(self.test_name) c.check_snappy_service_stop_timeout() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_snappy_service_stop_timeout_empty(self): '''Test check_snappy_service_stop_timeout() - empty''' self._set_service([("start", "bin/foo"), ("description", "something"), ("stop-timeout", "")]) c = ClickReviewSystemd(self.test_name) c.check_snappy_service_stop_timeout() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_snappy_service_stop_timeout_bad(self): '''Test check_snappy_service_stop_timeout() - bad''' self._set_service([("start", "bin/foo"), ("description", "something"), ("stop-timeout", "a")]) c = ClickReviewSystemd(self.test_name) c.check_snappy_service_stop_timeout() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_snappy_service_stop_timeout_bad_granularity(self): '''Test check_snappy_service_stop_timeout() - bad with granularity''' self._set_service([("start", "bin/foo"), ("description", "something"), ("stop-timeout", "30a")]) c = ClickReviewSystemd(self.test_name) c.check_snappy_service_stop_timeout() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_snappy_service_stop_timeout_range_low(self): '''Test check_snappy_service_stop_timeout() - out of range (low)''' self._set_service([("start", "bin/foo"), ("description", "something"), ("stop-timeout", -1)]) c = ClickReviewSystemd(self.test_name) c.check_snappy_service_stop_timeout() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_snappy_service_stop_timeout_range_high(self): '''Test check_snappy_service_stop_timeout() - out of range (high)''' self._set_service([("start", "bin/foo"), ("description", "something"), ("stop-timeout", 61)]) c = ClickReviewSystemd(self.test_name) c.check_snappy_service_stop_timeout() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_snappy_service_bus_name_pkgname(self): '''Test check_snappy_service_bus_name() - pkgname''' name = self.test_name.split('_')[0] self.set_test_pkg_yaml("name", name) self._set_service([("start", "bin/test-app"), ("description", "something"), ("bus-name", name)]) self.set_test_pkg_yaml("type", 'framework') c = ClickReviewSystemd(self.test_name) c.check_snappy_service_bus_name() r = c.click_report expected_counts = {'info': 4, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_snappy_service_bus_name_appname(self): '''Test check_snappy_service_bus_name() - appname''' name = self.test_name.split('_')[0] self._set_service([("start", "bin/test-app"), ("description", "something"), ("bus-name", "%s.%s" % (name, "test-app"))]) self.set_test_pkg_yaml("type", 'framework') c = ClickReviewSystemd(self.test_name) c.check_snappy_service_bus_name() r = c.click_report expected_counts = {'info': 4, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_snappy_service_bus_name_missing_framework_app(self): '''Test check_snappy_service_bus_name() - missing framework (app)''' name = self.test_name.split('_')[0] self._set_service([("start", "bin/test-app"), ("description", "something"), ("bus-name", "%s.%s" % (name, "test-app"))]) self.set_test_pkg_yaml("type", 'app') c = ClickReviewSystemd(self.test_name) c.check_snappy_service_bus_name() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_snappy_service_bus_name_missing_framework_oem(self): '''Test check_snappy_service_bus_name() - missing framework (oem)''' name = self.test_name.split('_')[0] self._set_service([("start", "bin/test-app"), ("description", "something"), ("bus-name", "%s.%s" % (name, "test-app"))]) self.set_test_pkg_yaml("type", 'oem') c = ClickReviewSystemd(self.test_name) c.check_snappy_service_bus_name() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_snappy_service_bus_name_pkgname_bad(self): '''Test check_snappy_service_bus_name() - bad pkgname''' name = self.test_name.split('_')[0] self.set_test_pkg_yaml("name", name) self._set_service([("start", "bin/test-app"), ("description", "something"), ("bus-name", name + "-bad")]) self.set_test_pkg_yaml("type", 'framework') c = ClickReviewSystemd(self.test_name) c.check_snappy_service_bus_name() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_snappy_service_bus_name_appname_bad(self): '''Test check_snappy_service_bus_name() - bad appname''' name = self.test_name.split('_')[0] self._set_service([("start", "bin/test-app"), ("description", "something"), ("bus-name", "%s.%s-bad" % (name, "test-app"))]) self.set_test_pkg_yaml("type", 'framework') c = ClickReviewSystemd(self.test_name) c.check_snappy_service_bus_name() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_snappy_service_bus_name_empty(self): '''Test check_snappy_service_bus_name() - bad (empty)''' name = self.test_name.split('_')[0] self.set_test_pkg_yaml("name", name) self._set_service([("start", "bin/test-app"), ("description", "something"), ("bus-name", "")]) self.set_test_pkg_yaml("type", 'framework') c = ClickReviewSystemd(self.test_name) c.check_snappy_service_bus_name() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_snappy_service_bus_name_bad_regex(self): '''Test check_snappy_service_bus_name() - bad (regex)''' name = self.test_name.split('_')[0] self.set_test_pkg_yaml("name", name) self._set_service([("start", "bin/test-app"), ("description", "something"), ("bus-name", "name$")]) self.set_test_pkg_yaml("type", 'framework') c = ClickReviewSystemd(self.test_name) c.check_snappy_service_bus_name() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 2} self.check_results(r, expected_counts) def test_check_snappy_service_ports(self): '''Test check_snappy_service_ports()''' ports = self._create_ports() self.set_test_systemd(self.default_appname, "ports", ports) c = ClickReviewSystemd(self.test_name) c.check_snappy_service_ports() r = c.click_report expected_counts = {'info': 8, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_snappy_service_ports_internal(self): '''Test check_snappy_service_ports() - internal''' ports = self._create_ports() del ports['internal'] self.set_test_systemd(self.default_appname, "ports", ports) c = ClickReviewSystemd(self.test_name) c.check_snappy_service_ports() r = c.click_report expected_counts = {'info': 6, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_snappy_service_ports_external(self): '''Test check_snappy_service_ports() - external''' ports = self._create_ports() del ports['external'] self.set_test_systemd(self.default_appname, "ports", ports) c = ClickReviewSystemd(self.test_name) c.check_snappy_service_ports() r = c.click_report expected_counts = {'info': 4, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_snappy_service_ports_empty(self): '''Test check_snappy_service_ports() - empty''' ports = self._create_ports() del ports['internal'] del ports['external'] self.set_test_systemd(self.default_appname, "ports", ports) c = ClickReviewSystemd(self.test_name) c.check_snappy_service_ports() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_snappy_service_ports_bad_key(self): '''Test check_snappy_service_ports() - bad key''' ports = self._create_ports() ports['xternal'] = ports['external'] del ports['external'] self.set_test_systemd(self.default_appname, "ports", ports) c = ClickReviewSystemd(self.test_name) c.check_snappy_service_ports() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_snappy_service_ports_missing_internal(self): '''Test check_snappy_service_ports() - missing internal''' ports = self._create_ports() del ports['internal']['int1'] self.set_test_systemd(self.default_appname, "ports", ports) c = ClickReviewSystemd(self.test_name) c.check_snappy_service_ports() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_snappy_service_ports_missing_external(self): '''Test check_snappy_service_ports() - missing external''' ports = self._create_ports() del ports['external']['ext1'] del ports['external']['ext2'] self.set_test_systemd(self.default_appname, "ports", ports) c = ClickReviewSystemd(self.test_name) c.check_snappy_service_ports() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_snappy_service_ports_missing_external_subkey(self): '''Test check_snappy_service_ports() - missing external subkey''' ports = self._create_ports() del ports['external']['ext2']['port'] self.set_test_systemd(self.default_appname, "ports", ports) c = ClickReviewSystemd(self.test_name) c.check_snappy_service_ports() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_snappy_service_ports_invalid_internal_subkey(self): '''Test check_snappy_service_ports() - invalid internal subkey''' ports = self._create_ports() ports['internal']['int1']['prt'] = ports['internal']['int1']['port'] del ports['internal']['int1']['port'] del ports['internal']['int1']['negotiable'] self.set_test_systemd(self.default_appname, "ports", ports) c = ClickReviewSystemd(self.test_name) c.check_snappy_service_ports() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_snappy_service_ports_invalid_internal_port(self): '''Test check_snappy_service_ports() - invalid internal port''' ports = self._create_ports() ports['internal']['int1']['port'] = "bad/8080" self.set_test_systemd(self.default_appname, "ports", ports) c = ClickReviewSystemd(self.test_name) c.check_snappy_service_ports() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_snappy_service_ports_invalid_internal_low_port(self): '''Test check_snappy_service_ports() - invalid internal low port''' ports = self._create_ports() ports['internal']['int1']['port'] = "0/tcp" self.set_test_systemd(self.default_appname, "ports", ports) c = ClickReviewSystemd(self.test_name) c.check_snappy_service_ports() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_snappy_service_ports_invalid_internal_high_port(self): '''Test check_snappy_service_ports() - invalid internal high port''' ports = self._create_ports() ports['internal']['int1']['port'] = "65536/tcp" self.set_test_systemd(self.default_appname, "ports", ports) c = ClickReviewSystemd(self.test_name) c.check_snappy_service_ports() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_snappy_service_ports_invalid_internal_negotiable(self): '''Test check_snappy_service_ports() - invalid internal negotiable''' ports = self._create_ports() ports['internal']['int1']['negotiable'] = -99999999 self.set_test_systemd(self.default_appname, "ports", ports) c = ClickReviewSystemd(self.test_name) c.check_snappy_service_ports() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_snappy_service_listen_stream_abspkgname(self): '''Test check_snappy_service_listen_stream() - @pkgname''' name = self.test_name.split('_')[0] self.set_test_pkg_yaml("name", name) self._set_service([("start", "bin/test-app"), ("description", "something"), ("listen-stream", '@%s' % name)]) c = ClickReviewSystemd(self.test_name) c.check_snappy_service_listen_stream() r = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_snappy_service_listen_stream_abspkgname2(self): '''Test check_snappy_service_listen_stream() - @pkgname_''' name = self.test_name.split('_')[0] self.set_test_pkg_yaml("name", name) self._set_service([("start", "bin/test-app"), ("description", "something"), ("listen-stream", '@%s_something' % name)]) c = ClickReviewSystemd(self.test_name) c.check_snappy_service_listen_stream() r = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_snappy_service_listen_stream_bad_abstract(self): '''Test check_snappy_service_listen_stream() - bad (wrong name)''' name = self.test_name.split('_')[0] self.set_test_pkg_yaml("name", name) self._set_service([("start", "bin/test-app"), ("description", "something"), ("listen-stream", '@%s/nomatch' % name)]) c = ClickReviewSystemd(self.test_name) c.check_snappy_service_listen_stream() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_snappy_service_listen_stream_bad_relative(self): '''Test check_snappy_service_listen_stream() - bad (not / or @)''' name = self.test_name.split('_')[0] self.set_test_pkg_yaml("name", name) self._set_service([("start", "bin/test-app"), ("description", "something"), ("listen-stream", name)]) c = ClickReviewSystemd(self.test_name) c.check_snappy_service_listen_stream() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_snappy_service_listen_stream_empty(self): '''Test check_snappy_service_listen_stream() - empty''' name = self.test_name.split('_')[0] self.set_test_pkg_yaml("name", name) self._set_service([("start", "bin/test-app"), ("description", "something"), ("listen-stream", "")]) c = ClickReviewSystemd(self.test_name) c.check_snappy_service_listen_stream() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_snappy_service_socket_user(self): '''Test check_snappy_service_socket_user()''' name = self.test_name.split('_')[0] self.set_test_pkg_yaml("name", name) self._set_service([("start", "bin/test-app"), ("description", "something"), ("listen-stream", '@%s' % name), ("socket-user", name)]) c = ClickReviewSystemd(self.test_name) c.check_snappy_service_socket_user() r = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_snappy_service_socket_user_no_listen_stream(self): '''Test check_snappy_service_socket_user() - missing listen-stream''' name = self.test_name.split('_')[0] self.set_test_pkg_yaml("name", name) self._set_service([("start", "bin/test-app"), ("description", "something"), ("socket-user", name)]) c = ClickReviewSystemd(self.test_name) c.check_snappy_service_socket_user() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 2} self.check_results(r, expected_counts) def test_check_snappy_service_socket_user_bad(self): '''Test check_snappy_service_socket_user() - bad user''' name = self.test_name.split('_')[0] self.set_test_pkg_yaml("name", name) self._set_service([("start", "bin/test-app"), ("description", "something"), ("listen-stream", '@%s' % name), ("socket-user", name + "nomatch")]) c = ClickReviewSystemd(self.test_name) c.check_snappy_service_socket_user() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 2} self.check_results(r, expected_counts) def test_check_snappy_service_socket_group(self): '''Test check_snappy_service_socket_group()''' name = self.test_name.split('_')[0] self.set_test_pkg_yaml("name", name) self._set_service([("start", "bin/test-app"), ("description", "something"), ("listen-stream", '@%s' % name), ("socket-group", name)]) c = ClickReviewSystemd(self.test_name) c.check_snappy_service_socket_group() r = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_snappy_service_socket_group_no_listen_stream(self): '''Test check_snappy_service_socket_group() - missing listen-stream''' name = self.test_name.split('_')[0] self.set_test_pkg_yaml("name", name) self._set_service([("start", "bin/test-app"), ("description", "something"), ("socket-group", name)]) c = ClickReviewSystemd(self.test_name) c.check_snappy_service_socket_group() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 2} self.check_results(r, expected_counts) def test_check_snappy_service_socket_group_bad(self): '''Test check_snappy_service_socket_group() - bad group''' name = self.test_name.split('_')[0] self.set_test_pkg_yaml("name", name) self._set_service([("start", "bin/test-app"), ("description", "something"), ("listen-stream", '@%s' % name), ("socket-group", name + "nomatch")]) c = ClickReviewSystemd(self.test_name) c.check_snappy_service_socket_group() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 2} self.check_results(r, expected_counts) def test_check_snappy_service_socket(self): '''Test check_snappy_service_socket()''' name = self.test_name.split('_')[0] self.set_test_pkg_yaml("name", name) self._set_service([("start", "bin/test-app"), ("description", "something"), ("listen-stream", '@%s' % name), ("socket", True)]) c = ClickReviewSystemd(self.test_name) c.check_snappy_service_socket() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_snappy_service_socket_no_listen_stream(self): '''Test check_snappy_service_socket() - missing listen-stream''' name = self.test_name.split('_')[0] self.set_test_pkg_yaml("name", name) self._set_service([("start", "bin/test-app"), ("description", "something"), ("socket", True)]) c = ClickReviewSystemd(self.test_name) c.check_snappy_service_socket() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) click-reviewers-tools-0.44~16.04.1/clickreviews/tests/test_cr_bin_path.py0000664000000000000000000001542612666350676023261 0ustar '''test_cr_bin_path.py: tests for the cr_bin_path module''' # # Copyright (C) 2014 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from clickreviews.cr_bin_path import ClickReviewBinPath import clickreviews.cr_tests as cr_tests class TestClickReviewBinPath(cr_tests.TestClickReview): """Tests for the bin-path review tool.""" def setUp(self): super().setUp() self.set_test_pkgfmt("snap", "15.04") def _set_binary(self, entries, name=None): d = dict() if name is None: d['name'] = self.default_appname else: d['name'] = name for (key, value) in entries: d[key] = value self.set_test_pkg_yaml("binaries", [d]) if 'exec' in d: self.set_test_bin_path(d['name'], d['exec']) else: self.set_test_bin_path(d['name'], d['name']) def test_check_path(self): '''Test check_path()''' self.set_test_bin_path(self.default_appname, "bin/foo.exe") c = ClickReviewBinPath(self.test_name) c.check_path() r = c.click_report # We should end up with 1 info expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_path_nonexecutable(self): '''Test check_path() - nonexecutable''' self.set_test_bin_path(self.default_appname, "bin/foo.nonexec") c = ClickReviewBinPath(self.test_name) c.check_path() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_required(self): '''Test check_snappy_required()''' self._set_binary([("exec", "bin/foo")]) c = ClickReviewBinPath(self.test_name) c.check_snappy_required() r = c.click_report # Only 'name' is required at this time so 0s for all expected_counts = {'info': 0, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_required_empty_value(self): '''Test check_snappy_required() - empty exec''' self._set_binary([("exec", "")]) c = ClickReviewBinPath(self.test_name) c.check_snappy_required() r = c.click_report # Only 'name' is required at this time so 0s for all expected_counts = {'info': 0, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_required_bad_value(self): '''Test check_snappy_required() - bad exec''' self._set_binary([("exec", [])]) c = ClickReviewBinPath(self.test_name) c.check_snappy_required() r = c.click_report # Only 'name' is required at this time so 0s for all expected_counts = {'info': 0, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_required_multiple(self): '''Test check_snappy_required() - multiple''' self._set_binary([("exec", "bin/foo"), ("description", "foo desc")]) c = ClickReviewBinPath(self.test_name) c.check_snappy_required() r = c.click_report # Only 'name' is required at this time so 0s for all expected_counts = {'info': 0, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_snappy_optional(self): '''Test check_snappy_optional()''' self._set_binary([("description", "some description")]) c = ClickReviewBinPath(self.test_name) c.check_snappy_optional() r = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_snappy_optional_empty_value(self): '''Test check_snappy_optional() - empty description''' self._set_binary([("description", "")]) c = ClickReviewBinPath(self.test_name) c.check_snappy_optional() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_snappy_optional_bad_value(self): '''Test check_snappy_optional() - bad description''' self._set_binary([("description", [])]) c = ClickReviewBinPath(self.test_name) c.check_snappy_optional() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) def test_check_snappy_unknown(self): '''Test check_snappy_unknown()''' self._set_binary([("nonexistent", "foo")]) c = ClickReviewBinPath(self.test_name) c.check_snappy_unknown() r = c.click_report expected_counts = {'info': None, 'warn': 1, 'error': 0} self.check_results(r, expected_counts) def test_check_snappy_unknown_multiple(self): '''Test check_snappy_unknown() - multiple''' self._set_binary([("exec", "bin/foo"), ("nonexistent", "foo")]) c = ClickReviewBinPath(self.test_name) c.check_snappy_unknown() r = c.click_report expected_counts = {'info': None, 'warn': 1, 'error': 0} self.check_results(r, expected_counts) def test_check_binary_description(self): '''Test check_binary_description()''' self._set_binary([("description", "some description")]) c = ClickReviewBinPath(self.test_name) c.check_binary_description() r = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_binary_description_unspecified(self): '''Test check_binary_description() - unspecified''' self._set_binary([("exec", "foo")]) c = ClickReviewBinPath(self.test_name) c.check_binary_description() r = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(r, expected_counts) def test_check_binary_description_empty(self): '''Test check_binary_description() - empty''' self._set_binary([("description", "")]) c = ClickReviewBinPath(self.test_name) c.check_binary_description() r = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(r, expected_counts) click-reviewers-tools-0.44~16.04.1/clickreviews/tests/test_remote.py0000664000000000000000000000211512666350676022273 0ustar import time from unittest import TestCase from unittest.mock import patch from clickreviews.remote import UPDATE_INTERVAL, _update_is_necessary class RemoteTestCase(TestCase): def patch_path(self): p = patch('clickreviews.remote.os.path') self.mock_path = p.start() self.addCleanup(p.stop) def patch_time(self, now): p = patch('clickreviews.remote.time.time') self.mock_time = p.start() self.mock_time.return_value = now self.addCleanup(p.stop) def test_no_update_needed(self): now = time.time() self.patch_time(now) self.patch_path() # last update was 10 seconds ago self.mock_path.getmtime.return_value = now - 10 self.assertFalse(_update_is_necessary('some-file')) def test_update_needed(self): now = time.time() self.patch_time(now) self.patch_path() # last update was UPDATE_INTERVAL + 10 seconds ago self.mock_path.getmtime.return_value = now - UPDATE_INTERVAL - 10 self.assertTrue(_update_is_necessary('some-file')) click-reviewers-tools-0.44~16.04.1/clickreviews/tests/utils.py0000664000000000000000000002516012666354404021077 0ustar '''utils.py: test utils for click reviewer tools''' # # Copyright (C) 2013-2016 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from clickreviews.common import ( MKSQUASHFS_OPTS ) import json import os import shutil import subprocess import tempfile def make_snap2(name='test', pkgfmt_type='snap', pkgfmt_version='16.04', version='1.0', summary="An application", extra_files=None, output_dir=None): '''Return the path to a snap v2 package with the given data. Caller is responsible for deleting the output_dir afterwards. ''' assert(pkgfmt_type == "snap" and pkgfmt_version != "15.04") build_dir = tempfile.mkdtemp() try: make_dir_structure(build_dir, pkgfmt_type=pkgfmt_type, pkgfmt_version=pkgfmt_version, extra_files=extra_files) write_icon(build_dir) description = summary write_meta_data2(build_dir, name, version, summary, description) pkg_path = build_package(build_dir, name, version, pkgfmt_type, pkgfmt_version, output_dir=output_dir) finally: shutil.rmtree(build_dir) return pkg_path def make_click(name='test', pkgfmt_type='click', pkgfmt_version='0.4', package_types=None, version='1.0', title="An application", framework='ubuntu-sdk-15.04', extra_files=None, output_dir=None): """Return the path to a click/snap package with the given data. Caller is responsible for deleting the output_dir afterwards. """ assert(pkgfmt_type == "click" or (pkgfmt_type == "snap" and pkgfmt_version == "15.04")) is_snap1 = (pkgfmt_type == "snap") build_dir = tempfile.mkdtemp() package_types = package_types or [] try: make_dir_structure(build_dir, pkgfmt_type=pkgfmt_type, pkgfmt_version=pkgfmt_version, extra_files=extra_files) write_icon(build_dir) write_manifest(build_dir, name, version, title, framework, package_types, is_snap1) write_control(build_dir, name, version, title, pkgfmt_version) write_preinst(build_dir) write_apparmor_profile(build_dir, name) write_other_files(build_dir) if pkgfmt_type == 'snap': write_meta_data(build_dir, name, version, title, framework) pkg_path = build_package(build_dir, name, version, pkgfmt_type, pkgfmt_version, output_dir=output_dir) finally: shutil.rmtree(build_dir) return pkg_path def make_dir_structure(path, pkgfmt_type, pkgfmt_version, extra_files=None): '''Create the mandatory dir structure and extra_files. Format for extra_files: path/to/file create empty file in path path/to/dir/ create empty dir in path path/to/source,path/to/link create symlink in path path/to/source:path/to/link copy source to path For symlink and copy, source can be an absolute path for pointing outside of the dir (for symlinks) or copying into the package. ''' extra_files = extra_files or [] directories = ['meta'] # write_icon() and write_manifest() assume this if pkgfmt_type == 'click' or pkgfmt_version == 15.04: directories.append('DEBIAN') # enumerate the directories to create for extra_file in extra_files: if ',' in extra_file: extra = extra_file.split(',', 1)[1] elif ':' in extra_file: extra = extra_file.split(':', 1)[1] else: extra = extra_file if extra.startswith('/'): extra = extra[1:] if extra.endswith('/'): directories.append(extra) else: directories.append(os.path.dirname(extra)) # make the enumerated directories for directory in directories: directory = os.path.join(path, directory) if not os.path.exists(directory): os.makedirs(directory) for extra_file in extra_files: if extra_file.endswith('/'): # nothing more to do for directories continue source_link = None source_path = None if ',' in extra_file: (source_link, target_path) = extra_file.split(',', 1) elif ':' in extra_file: (source_path, target_path) = extra_file.split(':', 1) else: target_path = extra_file dirname, basename = os.path.split(target_path) if basename != '': if source_path: if not source_path.startswith('/'): source_path = os.path.join(path, source_path) shutil.copyfile(source_path, os.path.join(path, target_path)) elif source_link: cur = os.getcwd() if target_path.startswith('/'): target_path = os.path.join(path, target_path[1:]) else: os.chdir(path) os.symlink(source_link, target_path) os.chdir(cur) else: with open(os.path.join(path, target_path), 'wb'): pass def write_icon(path): source_path = os.path.join(os.getcwd(), 'clickreviews/data/icon.png') target_path = os.path.join(path, 'meta', 'icon.png') shutil.copyfile(source_path, target_path) def write_manifest(path, name, version, title, framework, types, is_snap): manifest_content = {'framework': framework, 'maintainer': 'Someone ', 'name': name, 'title': title, 'version': version, 'icon': 'meta/icon.png', 'hooks': {'app': {'apparmor': 'meta/{}.apparmor'.format(name), }, }, 'description': 'This is a test app.', } if types: if is_snap: manifest_content.update({'type': types[0]}) else: if "scope" in types: manifest_content['hooks']['app'].update({'scope': ""}) if "application" in types: manifest_content['hooks']['app'].update({'desktop': ""}) manifest_paths = [ os.path.join(path, 'DEBIAN', 'manifest'), os.path.join(path, 'manifest.json'), ] for manifest_path in manifest_paths: with open(manifest_path, 'w') as f: json.dump(manifest_content, f) def write_meta_data(path, name, version, title, framework): yaml_path = os.path.join(path, 'meta', 'package.yaml') content = """architectures: icon: meta/icon.png name: {} version: {} framework: {} vendor: 'Someone ', """.format(name, version, framework) # don't overwrite 'copy' via make_dir_structure() if not os.path.exists(yaml_path): with open(yaml_path, 'w') as f: f.write(content) with open(os.path.join(path, 'meta', 'readme.md'), 'w') as f: f.write(title) def write_meta_data2(path, name, version, summary, description, yaml=None): yaml_path = os.path.join(path, 'meta', 'snap.yaml') if yaml: content = yaml else: content = """architectures: [ all ] name: {} version: {} summary: {} description: {} """.format(name, version, summary, description) # don't overwrite 'copy' via make_dir_structure() if not os.path.exists(yaml_path): with open(yaml_path, 'w') as f: f.write(content) def write_control(path, name, version, title, pkgfmt_version): control_path = os.path.join(path, 'DEBIAN', 'control') control_content = {'Package': name, 'Version': version, 'Click-Version': pkgfmt_version, 'Architecture': 'all', 'Maintainer': 'Someone ', 'Installed-Size': '123', 'Description': title, } with open(control_path, 'w') as f: for key, value in control_content.items(): f.write(key + ": " + value + "\n") def write_preinst(path): preinst_path = os.path.join(path, 'DEBIAN', 'preinst') with open(preinst_path, 'w') as f: f.write("""#! /bin/sh echo "Click packages may not be installed directly using dpkg." echo "Use 'click install' instead." exit 1 """) os.chmod(preinst_path, 0o775) def write_apparmor_profile(path, name): profile_path = os.path.join(path, 'meta', '{}.apparmor'.format(name)) profile = { 'policy_version': 1.3, 'policy_groups': [], } with open(profile_path, 'w') as f: json.dump(profile, f) def write_other_files(path): def write_empty_file(path, perms=0o664): with open(path, 'wb'): pass os.chmod(path, perms) write_empty_file(os.path.join(path, 'DEBIAN', 'md5sums')) def build_package(path, name, version, pkgfmt_type, pkgfmt_version, output_dir=None): filename = "{}_{}_all.{}".format(name, version, pkgfmt_type) output_dir = output_dir or tempfile.mkdtemp() output_path = os.path.join(output_dir, filename) if pkgfmt_type == "snap" and pkgfmt_version != "15.04": args = ['mksquashfs', path, output_path] + MKSQUASHFS_OPTS # debugging # subprocess.check_call(args) # subprocess.check_call(['unsquashfs', '-lls', output_path]) subprocess.check_call(args, stdout=open(os.devnull, 'w')) else: # click and snap v1 # Note: We're not using 'click build' here as it corrects errors (such # as filtering out a .click directory present in the build). We want # to test with manually constructed, potentially tampered-with # clicks/snaps. Ideally, we'd be using click rather than dpkg to # construct the click without filtering any files in the build dir. subprocess.check_call(['dpkg-deb', '-b', path, output_path]) return output_path click-reviewers-tools-0.44~16.04.1/clickreviews/tests/test_sr_security.py0000664000000000000000000010247712733312662023353 0ustar '''test_sr_security.py: tests for the sr_security module''' # # Copyright (C) 2013-2016 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from __future__ import print_function from unittest import TestCase import os import shutil import tempfile from clickreviews.common import cleanup_unpack from clickreviews.common import check_results as common_check_results from clickreviews.sr_security import SnapReviewSecurity import clickreviews.sr_tests as sr_tests from clickreviews.tests import utils class TestSnapReviewSecurity(sr_tests.TestSnapReview): """Tests for the security lint review tool.""" def setUp(self): super().setUp() self.set_test_pkgfmt("snap", "16.04") def _create_top_plugs(self): plugs = {'iface-network': {'interface': 'network'}, 'network-bind': {}, } return plugs def _create_apps_plugs(self): plugs = {'app1': {'plugs': ['iface-network']}, 'app2': {'plugs': ['network-bind']}, 'app3': {'plugs': ['iface-network', 'network-bind']}, } return plugs def _create_top_slots(self): slots = {'iface-slot1': {'interface': 'network'}, 'network-bind': {}, } return slots def _create_apps_slots(self): slots = {'app1': {'slots': ['iface-slot1']}, 'app2': {'slots': ['network-bind']}, } return slots def test_all_checks_as_v2(self): '''Test snap v2 has checks''' self.set_test_pkgfmt("snap", "16.04") plugs = self._create_top_plugs() self.set_test_snap_yaml("plugs", plugs) c = SnapReviewSecurity(self.test_name) c.do_checks() sum = 0 for i in c.click_report: sum += len(c.click_report[i]) self.assertTrue(sum != 0) def test_all_checks_as_v1(self): '''Test snap v1 has no checks''' self.set_test_pkgfmt("snap", "15.04") c = SnapReviewSecurity(self.test_name) c.do_checks() sum = 0 for i in c.click_report: sum += len(c.click_report[i]) self.assertTrue(sum == 0) def test_all_checks_as_click(self): '''Test click format has no checks''' self.set_test_pkgfmt("click", "0.4") c = SnapReviewSecurity(self.test_name) c.do_checks() sum = 0 for i in c.click_report: sum += len(c.click_report[i]) self.assertTrue(sum == 0) def test_check_security_policy_vendor(self): '''Test check_security_policy_vendor()''' c = SnapReviewSecurity(self.test_name) c.check_security_policy_vendor() report = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_security_policy_vendor_missing(self): '''Test check_security_policy_vendor() - missing''' c = SnapReviewSecurity(self.test_name) c.aa_policy.pop("ubuntu-core", None) c.check_security_policy_vendor() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_security_policy_version(self): '''Test check_security_policy_vesion()''' c = SnapReviewSecurity(self.test_name) c.check_security_policy_version() report = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_security_policy_version_missing(self): '''Test check_security_policy_vesion()''' c = SnapReviewSecurity(self.test_name) c.aa_policy["ubuntu-core"].pop("16.04", None) c.check_security_policy_version() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_security_plugs(self): ''' Test check_security_plugs()''' plugs = self._create_top_plugs() self.set_test_snap_yaml("plugs", plugs) c = SnapReviewSecurity(self.test_name) c.check_security_plugs() report = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_security_plugs_none(self): ''' Test check_security_plugs() - None''' self.set_test_snap_yaml("plugs", None) c = SnapReviewSecurity(self.test_name) c.check_security_plugs() report = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_security_plugs_empty(self): ''' Test check_security_plugs() - empty''' self.set_test_snap_yaml("plugs", {}) c = SnapReviewSecurity(self.test_name) c.check_security_plugs() report = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_security_plugs_debug(self): ''' Test check_security_plugs() - debug''' plugs = self._create_top_plugs() plugs['debug'] = {} self.set_test_snap_yaml("plugs", plugs) c = SnapReviewSecurity(self.test_name) c.aa_policy["ubuntu-core"]["16.04"]["policy_groups"]["reserved"].append("debug") c.check_security_plugs() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_security_plugs_reserved(self): ''' Test check_security_plugs() - reserved''' plugs = self._create_top_plugs() plugs['rsrved'] = {} self.set_test_snap_yaml("plugs", plugs) c = SnapReviewSecurity(self.test_name) c.aa_policy["ubuntu-core"]["16.04"]["policy_groups"]["reserved"].append("rsrved") c.check_security_plugs() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_security_plugs_reserved_strict(self): ''' Test check_security_plugs() - reserved (strict)''' plugs = self._create_top_plugs() plugs['rsrved'] = {} self.set_test_snap_yaml("plugs", plugs) self.set_test_snap_yaml("confinement", "strict") c = SnapReviewSecurity(self.test_name) c.aa_policy["ubuntu-core"]["16.04"]["policy_groups"]["reserved"].append("rsrved") c.check_security_plugs() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_security_plugs_reserved_devmode(self): ''' Test check_security_plugs() - reserved (devmode)''' plugs = self._create_top_plugs() plugs['rsrved'] = {} self.set_test_snap_yaml("plugs", plugs) self.set_test_snap_yaml("confinement", "devmode") c = SnapReviewSecurity(self.test_name) c.aa_policy["ubuntu-core"]["16.04"]["policy_groups"]["reserved"].append("rsrved") c.check_security_plugs() report = c.click_report expected_counts = {'info': 3, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) expected = dict() expected['error'] = dict() expected['warn'] = dict() expected['info'] = dict() name = 'security-snap-v2:plug_safe:rsrved:rsrved' expected['info'][name] = {"text": "[ERROR] reserved interface " "'rsrved' for vetted applications " "only", "manual_review": False} self.check_results(report, expected=expected) def test_check_security_plugs_no_plugs(self): ''' Test check_security_plugs() - no plugs''' self.set_test_snap_yaml("plugs", None) c = SnapReviewSecurity(self.test_name) c.check_security_plugs() report = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_security_plugs_unknown_type(self): ''' Test check_security_plugs() - unknown type''' plugs = self._create_top_plugs() plugs['bad-type'] = {} self.set_test_snap_yaml("plugs", plugs) c = SnapReviewSecurity(self.test_name) c.aa_policy["ubuntu-core"]["16.04"]["policy_groups"]["nonexistent"] = ["bad-type"] c.check_security_plugs() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_security_plugs_nonexistent(self): ''' Test check_security_plugs() - nonexistent''' plugs = dict() plugs['nonexistent'] = {} self.set_test_snap_yaml("plugs", plugs) c = SnapReviewSecurity(self.test_name) c.check_security_plugs() report = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_security_apps_plugs(self): ''' Test check_security_apps_plugs()''' plugs = self._create_top_plugs() apps = self._create_apps_plugs() self.set_test_snap_yaml("plugs", plugs) self.set_test_snap_yaml("apps", apps) c = SnapReviewSecurity(self.test_name) c.check_security_apps_plugs() report = c.click_report expected_counts = {'info': 2, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) # Note: c.check_security_apps_plugs() only checks plugs that are not # referenced in the toplevel plugs so while self._create_apps_plugs() # has four apps, only two of the apps reference plugs not in the # toplevel plugs. expected = dict() expected['error'] = dict() expected['warn'] = dict() expected['info'] = dict() name = 'security-snap-v2:app_plug_safe:app2:network-bind' expected['info'][name] = {"text": "OK"} name = 'security-snap-v2:app_plug_safe:app3:network-bind' expected['info'][name] = {"text": "OK"} self.check_results(report, expected=expected) def test_check_security_apps_plugs_no_apps(self): ''' Test check_security_apps_plugs() - No apps''' self.set_test_snap_yaml("apps", None) c = SnapReviewSecurity(self.test_name) c.check_security_apps_plugs() report = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_security_apps_plugs_none(self): ''' Test check_security_apps_plugs() - app with no plugs''' apps = {'app1': {'plugs': {}}} self.set_test_snap_yaml("apps", apps) c = SnapReviewSecurity(self.test_name) c.check_security_apps_plugs() report = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_security_apps_plugs_bad(self): ''' Test check_security_apps_plugs() - bad''' apps = {'app1': {'plugs': [{}]}} self.set_test_snap_yaml("apps", apps) c = SnapReviewSecurity(self.test_name) c.check_security_apps_plugs() report = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_security_apps_plugs_empty(self): ''' Test check_security_apps_plugs() - empty''' apps = {'app1': {'plugs': []}} self.set_test_snap_yaml("apps", apps) c = SnapReviewSecurity(self.test_name) c.check_security_apps_plugs() report = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_security_slots(self): ''' Test check_security_slots()''' slots = self._create_top_slots() self.set_test_snap_yaml("slots", slots) c = SnapReviewSecurity(self.test_name) c.check_security_slots() report = c.click_report # specifying slots currently requires manual review expected_counts = {'info': 2, 'warn': 2, 'error': 0} self.check_results(report, expected_counts) def test_check_security_slots_mpris(self): ''' Test check_security_slots() - mpris''' slots = dict() slots['mpris'] = {} self.set_test_snap_yaml("slots", slots) c = SnapReviewSecurity(self.test_name) c.check_security_slots() report = c.click_report # specifying slots currently requires manual review, unless a safe slot expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_security_slots_content(self): ''' Test check_security_slots() - content''' slots = dict() slots['content'] = {} self.set_test_snap_yaml("slots", slots) c = SnapReviewSecurity(self.test_name) c.check_security_slots() report = c.click_report # specifying slots currently requires manual review, unless a safe slot expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_security_slots_none(self): ''' Test check_security_slots() - None''' self.set_test_snap_yaml("slots", None) c = SnapReviewSecurity(self.test_name) c.check_security_slots() report = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_security_slots_empty(self): ''' Test check_security_slots() - empty''' self.set_test_snap_yaml("slots", {}) c = SnapReviewSecurity(self.test_name) c.check_security_slots() report = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_security_slots_debug(self): ''' Test check_security_slots() - debug''' slots = self._create_top_slots() slots['debug'] = {} self.set_test_snap_yaml("slots", slots) c = SnapReviewSecurity(self.test_name) c.aa_policy["ubuntu-core"]["16.04"]["policy_groups"]["reserved"].append("debug") c.check_security_slots() report = c.click_report expected_counts = {'info': None, 'warn': None, 'error': 1} self.check_results(report, expected_counts) def test_check_security_slots_reserved(self): ''' Test check_security_slots() - reserved''' slots = self._create_top_slots() slots['rsrved'] = {} self.set_test_snap_yaml("slots", slots) c = SnapReviewSecurity(self.test_name) c.aa_policy["ubuntu-core"]["16.04"]["policy_groups"]["reserved"].append("rsrved") c.check_security_slots() report = c.click_report expected_counts = {'info': None, 'warn': None, 'error': 1} self.check_results(report, expected_counts) def test_check_security_slots_reserved_strict(self): ''' Test check_security_slots() - reserved (strict)''' slots = self._create_top_slots() slots['rsrved'] = {} self.set_test_snap_yaml("slots", slots) self.set_test_snap_yaml("confinement", "strict") c = SnapReviewSecurity(self.test_name) c.aa_policy["ubuntu-core"]["16.04"]["policy_groups"]["reserved"].append("rsrved") c.check_security_slots() report = c.click_report expected_counts = {'info': None, 'warn': 3, 'error': 1} self.check_results(report, expected_counts) def test_check_security_slots_reserved_devmode(self): ''' Test check_security_plugs() - reserved (devmode)''' slots = self._create_top_slots() slots['rsrved'] = {} self.set_test_snap_yaml("slots", slots) self.set_test_snap_yaml("confinement", "devmode") c = SnapReviewSecurity(self.test_name) c.aa_policy["ubuntu-core"]["16.04"]["policy_groups"]["reserved"].append("rsrved") c.check_security_slots() report = c.click_report expected_counts = {'info': 6, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) expected = dict() expected['error'] = dict() expected['warn'] = dict() expected['info'] = dict() name = 'security-snap-v2:slot_safe:rsrved:rsrved' expected['info'][name] = {"text": "[ERROR] reserved interface " "'rsrved' for vetted applications " "only", "manual_review": False} name = 'security-snap-v2:is_slot:rsrved:rsrved' expected['info'][name] = {"text": "[WARN] (NEEDS REVIEW) slots " "requires approval", "manual_review": False} self.check_results(report, expected=expected) def test_check_security_slots_unknown_type(self): ''' Test check_security_slots() - unknown type''' slots = self._create_top_slots() slots['bad-type'] = {} self.set_test_snap_yaml("slots", slots) c = SnapReviewSecurity(self.test_name) c.aa_policy["ubuntu-core"]["16.04"]["policy_groups"]["nonexistent"] = ["bad-type"] c.check_security_slots() report = c.click_report expected_counts = {'info': None, 'warn': None, 'error': 1} self.check_results(report, expected_counts) def test_check_security_slots_nonexistent(self): ''' Test check_security_slots() - nonexistent''' slots = dict() slots['nonexistent'] = {} self.set_test_snap_yaml("slots", slots) c = SnapReviewSecurity(self.test_name) c.check_security_slots() report = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_security_apps_slots(self): ''' Test check_security_apps_slots()''' slots = self._create_top_slots() apps = self._create_apps_slots() self.set_test_snap_yaml("slots", slots) self.set_test_snap_yaml("apps", apps) c = SnapReviewSecurity(self.test_name) c.check_security_apps_slots() report = c.click_report expected_counts = {'info': 1, 'warn': 1, 'error': 0} self.check_results(report, expected_counts) # Note: c.check_security_apps_slots() only checks slots that are not # referenced in the toplevel slots so while self._create_apps_slots() # has two apps, only one of the apps reference slots not in the # toplevel slots. expected = dict() expected['error'] = dict() expected['warn'] = dict() expected['info'] = dict() name = 'security-snap-v2:app_slot_safe:app2:network-bind' expected['info'][name] = {"text": "OK"} self.check_results(report, expected=expected) def test_check_security_apps_slots_none(self): ''' Test check_security_apps_slots() - None''' self.set_test_snap_yaml("apps", None) c = SnapReviewSecurity(self.test_name) c.check_security_apps_slots() report = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_security_apps_slots_bad(self): ''' Test check_security_apps_slots() - bad''' apps = {'app1': {'slots': [{}]}} self.set_test_snap_yaml("apps", apps) c = SnapReviewSecurity(self.test_name) c.check_security_apps_slots() report = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_security_apps_slots_empty(self): ''' Test check_security_apps_slots() - empty''' apps = {'app1': {'slots': []}} self.set_test_snap_yaml("apps", apps) c = SnapReviewSecurity(self.test_name) c.check_security_apps_slots() report = c.click_report expected_counts = {'info': 0, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_apparmor_profile_name_length(self): '''Test check_apparmor_profile_name_length()''' apps = {'app1': {'plugs': ['iface-caps']}} self.set_test_snap_yaml("apps", apps) c = SnapReviewSecurity(self.test_name) c.check_apparmor_profile_name_length() report = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_apparmor_profile_name_length_no_plugs(self): '''Test check_apparmor_profile_name_length()''' apps = {'app1': {}} self.set_test_snap_yaml("apps", apps) c = SnapReviewSecurity(self.test_name) c.check_apparmor_profile_name_length() report = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_apparmor_profile_name_length_bad(self): '''Test check_apparmor_profile_name_length() - too long''' self.set_test_snap_yaml('name', 'A' * 253) apps = {'app1': {'plugs': ['iface-caps']}} self.set_test_snap_yaml("apps", apps) c = SnapReviewSecurity(self.test_name) c.check_apparmor_profile_name_length() report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_apparmor_profile_name_length_bad2(self): '''Test check_apparmor_profile_name_length() - longer than advised''' self.set_test_snap_yaml('name', 'A' * 100) apps = {'app1': {'plugs': ['iface-caps']}} self.set_test_snap_yaml("apps", apps) c = SnapReviewSecurity(self.test_name) c.check_apparmor_profile_name_length() report = c.click_report expected_counts = {'info': None, 'warn': 1, 'error': 0} self.check_results(report, expected_counts) class TestSnapReviewSecurityNoMock(TestCase): """Tests without mocks where they are not needed.""" def setUp(self): # XXX cleanup_unpack() is required because global variables # UNPACK_DIR, RAW_UNPACK_DIR are initialised to None at module # load time, but updated when a real (non-Mock) test runs, such as # here. While, at the same time, two of the existing tests using # mocks depend on both global vars being None. Ideally, those # global vars should be refactored away. self.addCleanup(cleanup_unpack) super().setUp() def mkdtemp(self): """Create a temp dir which is cleaned up after test.""" tmp_dir = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, tmp_dir) return tmp_dir def check_results(self, report, expected_counts={'info': 1, 'warn': 0, 'error': 0}, expected=None): common_check_results(self, report, expected_counts, expected) def test_check_squashfs_resquash(self): '''Test check_squashfs_resquash()''' package = utils.make_snap2(output_dir=self.mkdtemp()) c = SnapReviewSecurity(package) c.check_squashfs_resquash() report = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_squashfs_resquash_no_fstime(self): '''Test check_squashfs_resquash() - no -fstime''' output_dir = self.mkdtemp() package = utils.make_snap2(output_dir=output_dir) c = SnapReviewSecurity(package) # fake unsquashfs unsquashfs = os.path.join(output_dir, 'unsquashfs') content = '''#!/bin/sh echo test error: -fstime failure exit 1 ''' with open(unsquashfs, 'w') as f: f.write(content) os.chmod(unsquashfs, 0o775) old_path = os.environ['PATH'] if old_path: os.environ['PATH'] = "%s:%s" % (output_dir, os.environ['PATH']) else: os.environ['PATH'] = output_dir # pragma: nocover c.check_squashfs_resquash() os.environ['PATH'] = old_path report = c.click_report expected_counts = {'info': None, 'warn': 1, 'error': 0} self.check_results(report, expected_counts) def test_check_squashfs_resquash_unsquashfs_fail(self): '''Test check_squashfs_resquash() - unsquashfs failure''' output_dir = self.mkdtemp() package = utils.make_snap2(output_dir=output_dir) c = SnapReviewSecurity(package) # fake unsquashfs unsquashfs = os.path.join(output_dir, 'unsquashfs') content = '''#!/bin/sh if [ "$1" = "-fstime" ]; then exit 0 fi echo test error: unsquashfs failure exit 1 ''' with open(unsquashfs, 'w') as f: f.write(content) os.chmod(unsquashfs, 0o775) old_path = os.environ['PATH'] if old_path: os.environ['PATH'] = "%s:%s" % (output_dir, os.environ['PATH']) else: os.environ['PATH'] = output_dir # pragma: nocover c.check_squashfs_resquash() os.environ['PATH'] = old_path report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_squashfs_resquash_mksquashfs_fail(self): '''Test check_squashfs_resquash() - mksquashfs failure''' output_dir = self.mkdtemp() package = utils.make_snap2(output_dir=output_dir) c = SnapReviewSecurity(package) # fake mksquashfs mksquashfs = os.path.join(output_dir, 'mksquashfs') content = '''#!/bin/sh echo test error: mksquashfs failure exit 1 ''' with open(mksquashfs, 'w') as f: f.write(content) os.chmod(mksquashfs, 0o775) old_path = os.environ['PATH'] if old_path: os.environ['PATH'] = "%s:%s" % (output_dir, os.environ['PATH']) else: os.environ['PATH'] = output_dir # pragma: nocover c.check_squashfs_resquash() os.environ['PATH'] = old_path report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_squashfs_resquash_sha512sum_fail(self): '''Test check_squashfs_resquash() - sha512sum failure''' output_dir = self.mkdtemp() package = utils.make_snap2(output_dir=output_dir) c = SnapReviewSecurity(package) # fake sha512sum sha512sum = os.path.join(output_dir, 'sha512sum') content = '''#!/bin/sh bn=`basename "$1"` if [ "$bn" = "test_1.0_all.snap" ]; then echo test error: sha512sum failure exit 1 fi exit 0 ''' with open(sha512sum, 'w') as f: f.write(content) os.chmod(sha512sum, 0o775) old_path = os.environ['PATH'] if old_path: os.environ['PATH'] = "%s:%s" % (output_dir, os.environ['PATH']) else: os.environ['PATH'] = output_dir # pragma: nocover c.check_squashfs_resquash() os.environ['PATH'] = old_path report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_squashfs_resquash_sha512sum_fail_repacked(self): '''Test check_squashfs_resquash() - sha512sum failure (repacked)''' output_dir = self.mkdtemp() package = utils.make_snap2(output_dir=output_dir) c = SnapReviewSecurity(package) # fake sha512sum sha512sum = os.path.join(output_dir, 'sha512sum') content = '''#!/bin/sh bn=`basename "$1"` if [ "$bn" != "test_1.0_all.snap" ]; then echo test error: sha512sum failure exit 1 fi echo deadbeef $1 exit 0 ''' with open(sha512sum, 'w') as f: f.write(content) os.chmod(sha512sum, 0o775) old_path = os.environ['PATH'] if old_path: os.environ['PATH'] = "%s:%s" % (output_dir, os.environ['PATH']) else: os.environ['PATH'] = output_dir # pragma: nocover c.check_squashfs_resquash() os.environ['PATH'] = old_path report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) def test_check_squashfs_resquash_sha512sum_mismatch(self): '''Test check_squashfs_resquash() - sha512sum mismatch''' output_dir = self.mkdtemp() package = utils.make_snap2(output_dir=output_dir) c = SnapReviewSecurity(package) # fake sha512sum sha512sum = os.path.join(output_dir, 'sha512sum') content = '''#!/bin/sh bn=`basename "$1"` if [ "$bn" = "test_1.0_all.snap" ]; then echo beefeeee $1 else echo deadbeef $1 fi exit 0 ''' with open(sha512sum, 'w') as f: f.write(content) os.chmod(sha512sum, 0o775) old_path = os.environ['PATH'] if old_path: os.environ['PATH'] = "%s:%s" % (output_dir, os.environ['PATH']) else: os.environ['PATH'] = output_dir # pragma: nocover c.check_squashfs_resquash() os.environ['PATH'] = old_path report = c.click_report # FIXME: this should error but we've turned it into an info until the # squashfs-tools bugs can be fixed # expected_counts = {'info': None, 'warn': 0, 'error': 1} expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_squashfs_resquash_sha512sum_mismatch_os(self): '''Test check_squashfs_resquash() - sha512sum mismatch - os snap''' output_dir = self.mkdtemp() package = utils.make_snap2(output_dir=output_dir) sy_path = os.path.join(output_dir, 'snap.yaml') content = ''' name: test version: 0.1 summary: some thing description: some desc architectures: [ amd64 ] type: os ''' with open(sy_path, 'w') as f: f.write(content) package = utils.make_snap2(output_dir=output_dir, extra_files=['%s:meta/snap.yaml' % sy_path] ) c = SnapReviewSecurity(package) # fake sha512sum sha512sum = os.path.join(output_dir, 'sha512sum') content = '''#!/bin/sh bn=`basename "$1"` if [ "$bn" = "test_1.0_all.snap" ]; then echo beefeeee $1 else echo deadbeef $1 fi exit 0 ''' with open(sha512sum, 'w') as f: f.write(content) os.chmod(sha512sum, 0o775) old_path = os.environ['PATH'] if old_path: os.environ['PATH'] = "%s:%s" % (output_dir, os.environ['PATH']) else: os.environ['PATH'] = output_dir # pragma: nocover c.check_squashfs_resquash() os.environ['PATH'] = old_path report = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) def test_check_squashfs_resquash_1555305(self): '''Test check_squashfs_resquash()''' package = utils.make_snap2(output_dir=self.mkdtemp(), extra_files=['/some/where,outside']) c = SnapReviewSecurity(package) c.check_squashfs_resquash() report = c.click_report expected_counts = {'info': 1, 'warn': 0, 'error': 0} self.check_results(report, expected_counts) expected = dict() expected['error'] = dict() expected['warn'] = dict() expected['info'] = dict() name = 'security-snap-v2:squashfs_resquash_1555305' expected['info'][name] = {"link": "https://launchpad.net/bugs/1555305"} self.check_results(report, expected=expected) def test_check_squashfs_resquash_unsquashfs_fail_1555305(self): '''Test check_squashfs_resquash() - unsquashfs failure''' output_dir = self.mkdtemp() package = utils.make_snap2(output_dir=output_dir) c = SnapReviewSecurity(package) # fake unsquashfs unsquashfs = os.path.join(output_dir, 'unsquashfs') content = '''#!/bin/sh if [ "$1" = "-fstime" ] || [ "$1" = "-lls" ]; then exit 0 fi echo test error: unsquashfs failure exit 1 ''' with open(unsquashfs, 'w') as f: f.write(content) os.chmod(unsquashfs, 0o775) old_path = os.environ['PATH'] if old_path: os.environ['PATH'] = "%s:%s" % (output_dir, os.environ['PATH']) else: os.environ['PATH'] = output_dir # pragma: nocover c.check_squashfs_resquash() os.environ['PATH'] = old_path report = c.click_report expected_counts = {'info': None, 'warn': 0, 'error': 1} self.check_results(report, expected_counts) click-reviewers-tools-0.44~16.04.1/clickreviews/cr_skeleton.py0000664000000000000000000000516612666350672021114 0ustar '''cr_skeleton.py: click skeleton''' # # Copyright (C) 2014-2015 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from __future__ import print_function from clickreviews.cr_common import ClickReview class ClickReviewSkeleton(ClickReview): '''This class represents click lint reviews''' def __init__(self, fn, overrides=None): # Many test classes are for verify click hooks. 'peer_hooks' is used # to declare what hooks may be use with my_hook. When using this # mechanism, ClickReview.check_peer_hooks() is run for you. peer_hooks = dict() my_hook = 'skeleton' peer_hooks[my_hook] = dict() peer_hooks[my_hook]['allowed'] = ["desktop", "apparmor", "urls"] peer_hooks[my_hook]['required'] = ["desktop", "apparmor"] ClickReview.__init__(self, fn, "skeleton", peer_hooks=peer_hooks, overrides=overrides) if not self.is_click and not self.is_snap1: return # If not a hooks test, skip the above and omit peer_hooks like so: # ClickReview.__init__(self, fn, "skeleton") def check_foo(self): '''Check foo''' if not self.is_click and not self.is_snap1: return t = 'info' n = self._get_check_name('foo') s = "OK" if False: t = 'error' s = "some message" self._add_result(t, n, s) def check_bar(self): '''Check bar''' if not self.is_click and not self.is_snap1: return t = 'info' n = self._get_check_name('bar') s = "OK" if True: t = 'error' s = "some message" self._add_result(t, n, s) def check_baz(self): '''Check baz''' if not self.is_click and not self.is_snap1: return n = self._get_check_name('baz') self._add_result('warn', n, 'TODO', link="http://example.com") # Spawn a shell to pause the script (run 'exit' to continue) # import subprocess # print(self.unpack_dir) # subprocess.call(['bash']) click-reviewers-tools-0.44~16.04.1/clickreviews/sr_skeleton.py0000664000000000000000000000356612666350672021136 0ustar '''sr_skeleton.py: click skeleton''' # # Copyright (C) 2014-2015 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from __future__ import print_function from clickreviews.sr_common import SnapReview class SnapReviewSkeleton(SnapReview): '''This class represents click lint reviews''' def __init__(self, fn, overrides=None): SnapReview.__init__(self, fn, "skeleton-snap-v2", overrides=overrides) def check_foo(self): '''Check foo''' if not self.is_snap2: return t = 'info' n = self._get_check_name('foo') s = "OK" if False: t = 'error' s = "some message" self._add_result(t, n, s) def check_bar(self): '''Check bar''' if not self.is_snap2: return t = 'info' n = self._get_check_name('bar') s = "OK" if True: t = 'error' s = "some message" self._add_result(t, n, s) def check_baz(self): '''Check baz''' if not self.is_snap2: return n = self._get_check_name('baz') self._add_result('warn', n, 'TODO', link="http://example.com") # Spawn a shell to pause the script (run 'exit' to continue) # import subprocess # print(self.unpack_dir) # subprocess.call(['bash']) click-reviewers-tools-0.44~16.04.1/clickreviews/sr_lint.py0000664000000000000000000013022412733306735020244 0ustar '''sr_lint.py: lint checks''' # # Copyright (C) 2013-2016 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from __future__ import print_function from clickreviews.sr_common import ( SnapReview, ) from clickreviews.common import ( find_external_symlinks, ) import glob import os import re class SnapReviewLint(SnapReview): '''This class represents snap lint reviews''' def __init__(self, fn, overrides=None): '''Set up the class.''' SnapReview.__init__(self, fn, "lint-snap-v2", overrides=overrides) if not self.is_snap2: return self.valid_compiled_architectures = ['armhf', 'i386', 'amd64', 'arm64', 'ppc64el', 's390x', ] self.valid_architectures = ['all'] + self.valid_compiled_architectures self.vcs_files = ['.bzr*', # '.excludes', # autogenerated by SDK '.git*', '.idea', '.svn*', '.hg', '.project', 'CVS*', 'RCS*' ] self._list_all_compiled_binaries() # Valid values for 'type' in packaging yaml # - app # - kernel # - gadget # - os self.valid_snap_types = ['app', 'kernel', 'gadget', 'os', ] self.redflagged_snap_types = ['kernel', 'gadget', 'os', ] def check_architectures(self): '''Check architectures in snap.yaml is valid''' if not self.is_snap2: return t = 'info' n = self._get_check_name('architecture_valid') s = 'OK' key = 'architectures' if key not in self.snap_yaml: s = 'OK (%s not specified)' % key self._add_result(t, n, s) return if not isinstance(self.snap_yaml[key], list): t = 'error' s = "invalid %s entry: %s (not a list)" % (key, self.snap_yaml[key]) else: bad_archs = [] for arch in self.snap_yaml[key]: if arch not in self.valid_architectures: bad_archs.append(arch) if len(bad_archs) > 0: t = 'error' s = "invalid multi architecture: %s" % ",".join(bad_archs) self._add_result(t, n, s) def check_description(self): '''Check description''' if not self.is_snap2: return key = 'description' t = 'info' n = self._get_check_name('%s_present' % key) s = 'OK' if key not in self.snap_yaml: s = 'OK (optional %s field not specified)' % key self._add_result(t, n, s) return self._add_result(t, n, s) t = 'info' n = self._get_check_name(key) s = 'OK' if not isinstance(self.snap_yaml[key], str): t = 'error' s = "invalid %s entry: %s (not a str)" % (key, self.snap_yaml[key]) self._add_result(t, n, s) return elif len(self.snap_yaml[key]) < 1: t = 'error' s = "invalid %s entry (empty)" % (key) elif len(self.snap_yaml[key]) < len(self.snap_yaml['name']): t = 'info' s = "%s is too short: '%s'" % (key, self.snap_yaml[key]) self._add_result(t, n, s) # TODO: verify this is a field def check_license_agreement(self): '''Check license-agreement''' if not self.is_snap2: return key = 'license-agreement' t = 'info' n = self._get_check_name('%s_present' % key) s = 'OK' if key not in self.snap_yaml: s = 'OK (optional %s field not specified)' % key self._add_result(t, n, s) return self._add_result(t, n, s) t = 'info' n = self._get_check_name(key) s = 'OK' if not isinstance(self.snap_yaml[key], str): t = 'error' s = "invalid %s entry: %s (not a str)" % (key, self.snap_yaml[key]) self._add_result(t, n, s) return elif len(self.snap_yaml[key]) < 1: t = 'error' s = "invalid %s entry (empty)" % (key) self._add_result(t, n, s) return self._add_result(t, n, s) def check_license_version(self): '''license-version''' if not self.is_snap2: return key = 'license-version' t = 'info' n = self._get_check_name('%s_present' % key) s = 'OK' if key not in self.snap_yaml: s = 'OK (optional %s field not specified)' % key self._add_result(t, n, s) return self._add_result(t, n, s) t = 'info' n = self._get_check_name(key) s = 'OK' if not isinstance(self.snap_yaml[key], str): t = 'error' s = "invalid %s entry: %s (not a str)" % (key, self.snap_yaml[key]) self._add_result(t, n, s) return elif len(self.snap_yaml[key]) < 1: t = 'error' s = "invalid %s entry (empty)" % (key) self._add_result(t, n, s) return self._add_result(t, n, s) def check_name(self): '''Check package name''' if not self.is_snap2: return t = 'info' n = self._get_check_name('name_valid') s = 'OK' if 'name' not in self.snap_yaml: t = 'error' s = "could not find 'name' in yaml" elif not isinstance(self.snap_yaml['name'], str): t = 'error' s = "malformed 'name': %s (not a str)" % (self.snap_yaml['name']) elif not self._verify_pkgname(self.snap_yaml['name']): t = 'error' s = "malformed 'name': '%s'" % self.snap_yaml['name'] self._add_result(t, n, s) def check_summary(self): '''Check summary''' if not self.is_snap2: return key = 'summary' t = 'info' n = self._get_check_name('%s_present' % key) s = 'OK' if key not in self.snap_yaml: s = 'OK (optional %s field not specified)' % key self._add_result(t, n, s) return self._add_result(t, n, s) t = 'info' n = self._get_check_name(key) s = 'OK' if not isinstance(self.snap_yaml[key], str): t = 'error' s = "invalid %s entry: %s (not a str)" % (key, self.snap_yaml[key]) self._add_result(t, n, s) return elif len(self.snap_yaml[key]) < 1: t = 'error' s = "invalid %s entry (empty)" % (key) elif len(self.snap_yaml[key]) < len(self.snap_yaml['name']): t = 'info' s = "%s is too short: '%s'" % (key, self.snap_yaml[key]) self._add_result(t, n, s) def check_type(self): '''Check type''' if not self.is_snap2 or 'type' not in self.snap_yaml: return t = 'info' n = self._get_check_name('snap_type_valid') s = 'OK' if self.snap_yaml['type'] not in self.valid_snap_types: t = 'error' s = "unknown 'type': '%s'" % self.snap_yaml['type'] self._add_result(t, n, s) def check_type_redflagged(self): '''Check if type is redflagged''' if not self.is_snap2 or 'type' not in self.snap_yaml: return t = 'info' n = self._get_check_name('snap_type_redflag') s = "OK" manual_review = False if self.snap_yaml['type'] in self.redflagged_snap_types: t = 'error' s = "(NEEDS REVIEW) type '%s' not allowed" % self.snap_yaml['type'] manual_review = True self._add_result(t, n, s, manual_review=manual_review) def check_version(self): '''Check package version''' if not self.is_snap2: return t = 'info' n = self._get_check_name('version_valid') s = 'OK' if 'version' not in self.snap_yaml: t = 'error' s = "could not find 'version' in yaml" elif not self._verify_pkgversion(self.snap_yaml['version']): t = 'error' s = "malformed 'version': '%s'" % self.snap_yaml['version'] self._add_result(t, n, s) def check_config(self): '''Check config''' if not self.is_snap2: return fn = os.path.join(self._get_unpack_dir(), 'meta/hooks/config') if fn not in self.pkg_files: return t = 'info' n = self._get_check_name('config_hook_executable') s = 'OK' if not self._check_innerpath_executable(fn): t = 'error' s = 'meta/hooks/config is not executable' self._add_result(t, n, s) def check_icon(self): '''Check icon''' # see docs/meta.md and docs/gadget.md if not self.is_snap2 or 'icon' not in self.snap_yaml: return # Snappy icons may be specified in the gadget snap.yaml, but not in # app snap.yaml. With apps, the icon may be in meta/gui/icon.png and # this file is optional. Therefore, for apps, there is nothing to do. t = 'info' n = self._get_check_name('icon_present') s = 'OK' if 'type' in self.snap_yaml and self.snap_yaml['type'] != "gadget": t = 'warn' s = 'icon only used with gadget snaps' self._add_result(t, n, s) return self._add_result(t, n, s) t = 'info' n = self._get_check_name('icon_empty') s = 'OK' if len(self.snap_yaml['icon']) == 0: t = 'error' s = "icon entry is empty" self._add_result(t, n, s) return self._add_result(t, n, s) t = 'info' n = self._get_check_name('icon_absolute_path') s = 'OK' if self.snap_yaml['icon'].startswith('/'): t = 'error' s = "icon entry '%s' should not specify absolute path" % \ self.snap_yaml['icon'] self._add_result(t, n, s) t = 'info' n = self._get_check_name('icon_exists') s = 'OK' fn = self._path_join(self._get_unpack_dir(), self.snap_yaml['icon']) if fn not in self.pkg_files: t = 'error' s = "icon entry '%s' does not exist" % self.snap_yaml['icon'] self._add_result(t, n, s) def check_unknown_entries(self): '''Check for any unknown fields''' if not self.is_snap2: return t = 'info' n = self._get_check_name('unknown_field') s = 'OK' unknown = [] for f in self.snap_yaml: if f not in self.snappy_required + self.snappy_optional: unknown.append(f) if len(unknown) > 0: t = 'warn' s = "unknown entries in snap.yaml: '%s'" % \ (",".join(sorted(unknown))) self._add_result(t, n, s) def check_apps(self): '''Check apps''' if not self.is_snap2: return key = 'apps' t = 'info' n = self._get_check_name('%s_present' % key) s = 'OK' if key not in self.snap_yaml: s = 'OK (optional %s field not specified)' % key self._add_result(t, n, s) return self._add_result(t, n, s) t = 'info' n = self._get_check_name(key) s = 'OK' if not isinstance(self.snap_yaml[key], dict): t = 'error' s = "invalid %s entry: %s (not a dict)" % (key, self.snap_yaml[key]) self._add_result(t, n, s) return elif len(self.snap_yaml[key].keys()) < 1: t = 'error' s = "invalid %s entry (empty)" % (key) self._add_result(t, n, s) return self._add_result(t, n, s) for app in self.snap_yaml[key]: t = 'info' n = self._get_check_name('%s_entry' % key, app=app) s = 'OK' if not isinstance(self.snap_yaml[key][app], dict): t = 'error' s = "invalid entry: %s (not a dict)" % ( self.snap_yaml[key][app]) self._add_result(t, n, s) continue elif len(self.snap_yaml[key][app].keys()) < 1: t = 'error' s = "invalid entry for '%s' (empty)" % (app) self._add_result(t, n, s) continue elif not self._verify_appname(app): t = 'error' s = "malformed app name: '%s'" % app self._add_result(t, n, s) continue self._add_result(t, n, s) for field in self.apps_required: t = 'info' n = self._get_check_name('%s_required' % key, app=app) s = 'OK' if field not in self.snap_yaml[key][app]: t = 'error' s = "required field '%s' not specified" % field self._add_result(t, n, s) t = 'info' n = self._get_check_name('%s_unknown' % key, app=app) s = 'OK' unknown = [] for field in self.snap_yaml[key][app]: if field not in self.apps_required + self.apps_optional: unknown.append(field) if len(unknown) > 0: t = 'warn' s = "unknown fields for app '%s': '%s'" % ( app, ",".join(sorted(unknown))) self._add_result(t, n, s) def _verify_value_is_file(self, app, key): t = 'info' n = self._get_check_name('%s' % key, app=app) s = 'OK' if not isinstance(self.snap_yaml['apps'][app][key], str): t = 'error' s = "%s '%s' (not a str)" % (key, self.snap_yaml['apps'][app][key]) self._add_result(t, n, s) elif len(self.snap_yaml['apps'][app][key]) < 1: t = 'error' s = "invalid %s (empty)" % (key) self._add_result(t, n, s) else: fn = self._path_join(self._get_unpack_dir(), os.path.normpath( self.snap_yaml['apps'][app][key])) if fn not in self.pkg_files: print("does not exist") print(self.pkg_files) t = 'error' s = "%s does not exist" % ( self.snap_yaml['apps'][app][key]) self._add_result(t, n, s) def check_apps_command(self): '''Check apps - command''' if not self.is_snap2 or 'apps' not in self.snap_yaml: return for app in self.snap_yaml['apps']: key = 'command' if key not in self.snap_yaml['apps'][app]: # We check for required elsewhere continue self._verify_value_is_file(app, key) def check_apps_stop_command(self): '''Check apps - stop-command''' if not self.is_snap2 or 'apps' not in self.snap_yaml: return for app in self.snap_yaml['apps']: key = 'stop-command' if key not in self.snap_yaml['apps'][app]: # We check for required elsewhere continue self._verify_value_is_file(app, key) def check_apps_post_stop_command(self): '''Check apps - post-stop-command''' if not self.is_snap2 or 'apps' not in self.snap_yaml: return for app in self.snap_yaml['apps']: key = 'post-stop-command' if key not in self.snap_yaml['apps'][app]: # We check for required elsewhere continue self._verify_value_is_file(app, key) def check_apps_stop_timeout(self): '''Check apps - stop-timeout''' if not self.is_snap2 or 'apps' not in self.snap_yaml: return for app in self.snap_yaml['apps']: key = 'stop-timeout' if key not in self.snap_yaml['apps'][app]: # We check for required elsewhere continue t = 'info' n = self._get_check_name('%s' % key, app=app) s = "OK" if not isinstance(self.snap_yaml['apps'][app][key], int) and \ not isinstance(self.snap_yaml['apps'][app][key], str): t = 'error' s = "'%s' is not a string or integer" % key elif not re.search(r'[0-9]+[ms]?$', str(self.snap_yaml['apps'][app][key])): t = 'error' s = "'%s' is not of form NN[ms] (%s)" % \ (self.snap_yaml['apps'][app][key], key) self._add_result(t, n, s) if t == 'error': continue t = 'info' n = self._get_check_name('%s_range' % key, app=app) s = "OK" st = int(str(self.snap_yaml['apps'][app][key]).rstrip(r'[ms]')) if st < 0 or st > 60: t = 'error' s = "stop-timeout '%d' out of range (0-60)" % \ self.snap_yaml['apps'][app][key] self._add_result(t, n, s) def _verify_valid_values(self, app, key, valid): '''Verify valid values for key in app''' t = 'info' n = self._get_check_name('%s' % key, app=app) s = 'OK' if not isinstance(self.snap_yaml['apps'][app][key], str): t = 'error' s = "%s '%s' (not a str)" % (key, self.snap_yaml['apps'][app][key]) self._add_result(t, n, s) elif len(self.snap_yaml['apps'][app][key]) < 1: t = 'error' s = "invalid %s (empty)" % (key) self._add_result(t, n, s) elif self.snap_yaml['apps'][app][key] not in valid: t = 'error' s = "invalid %s: '%s'" % (key, self.snap_yaml['apps'][app][key]) self._add_result(t, n, s) def check_apps_daemon(self): '''Check apps - daemon''' if not self.is_snap2 or 'apps' not in self.snap_yaml: return valid = ["simple", "forking", "oneshot", "dbus", ] for app in self.snap_yaml['apps']: key = 'daemon' if key not in self.snap_yaml['apps'][app]: # We check for required elsewhere continue self._verify_valid_values(app, key, valid) def check_apps_nondaemon(self): '''Check apps - non-daemon''' if not self.is_snap2 or 'apps' not in self.snap_yaml: return # Certain options require 'daemon' so list the keys that are shared # by services and binaries ok_keys = ['command', 'environment', 'plugs', 'slots'] for app in self.snap_yaml['apps']: needs_daemon = [] for key in self.snap_yaml['apps'][app]: if key not in self.apps_optional or \ key == 'daemon' or \ key in ok_keys or \ 'daemon' in self.snap_yaml['apps'][app]: continue needs_daemon.append(key) t = 'info' n = self._get_check_name('daemon_required', app=app) s = "OK" if len(needs_daemon) > 0: t = 'error' s = "'%s' must be used with 'daemon'" % ",".join(needs_daemon) self._add_result(t, n, s) def check_apps_restart_condition(self): '''Check apps - restart-condition''' if not self.is_snap2 or 'apps' not in self.snap_yaml: return valid = ["always", "never", "on-abnormal", "on-abort", "on-failure", "on-success", ] for app in self.snap_yaml['apps']: key = 'restart-condition' if key not in self.snap_yaml['apps'][app]: # We check for required elsewhere continue self._verify_valid_values(app, key, valid) def check_apps_ports(self): '''Check apps - ports''' if not self.is_snap2 or 'apps' not in self.snap_yaml: return valid_keys = ['internal', 'external'] valid_subkeys = ['port', 'negotiable'] for app in self.snap_yaml['apps']: if 'ports' not in self.snap_yaml['apps'][app]: # We check for required elsewhere continue t = 'info' n = self._get_check_name('ports', app=app) s = 'OK' l = None if not isinstance(self.snap_yaml['apps'][app]['ports'], dict): t = 'error' s = "ports '%s' (not a dict)" % ( self.snap_yaml['apps'][app]['ports']) elif len(self.snap_yaml['apps'][app]['ports'].keys()) < 1: t = 'error' s = "'ports' must contain 'internal' and/or 'external'" self._add_result(t, n, s, l) if t == 'error': continue # unknown unknown = [] for key in self.snap_yaml['apps'][app]['ports']: if key not in valid_keys: unknown.append(key) if len(unknown) > 0: t = 'error' n = self._get_check_name('ports_unknown_key', extra=key, app=app) s = "Unknown '%s' for ports" % (",".join(unknown)) self._add_result(t, n, s) port_pat = re.compile(r'^[0-9]+/[a-z0-9\-]+$') for key in valid_keys: if key not in self.snap_yaml['apps'][app]['ports']: continue if len(self.snap_yaml['apps'][app]['ports'][key].keys()) < 1: t = 'error' n = self._get_check_name('ports', extra=key, app=app) s = 'Could not find any %s ports' % key self._add_result(t, n, s) continue for tagname in self.snap_yaml['apps'][app]['ports'][key]: entry = self.snap_yaml['apps'][app]['ports'][key][tagname] if len(entry.keys()) < 1 or ('negotiable' not in entry and 'port' not in entry): t = 'error' n = self._get_check_name('ports', extra=key, app=app) s = "Could not find 'port' or 'negotiable' in '%s'" % \ tagname self._add_result(t, n, s) continue # unknown unknown = [] for subkey in entry: if subkey not in valid_subkeys: unknown.append(subkey) if len(unknown) > 0: t = 'error' n = self._get_check_name('ports_unknown_subkey', extra=key, app=app) s = "Unknown '%s' for %s" % (",".join(unknown), tagname) self._add_result(t, n, s) # port subkey = 'port' t = 'info' n = self._get_check_name('ports_%s_format' % tagname, extra=subkey) s = 'OK' if subkey not in entry: s = 'OK (skipped, not found)' elif not isinstance(entry[subkey], str): t = 'error' s = "invalid entry: %s (not a str)" % (entry[subkey]) else: tmp = entry[subkey].split('/') if not port_pat.search(entry[subkey]) or \ int(tmp[0]) < 1 or int(tmp[0]) > 65535: t = 'error' s = "'%s' should be of form " % entry[subkey] + \ "'port/protocol' where port is an integer " + \ "(1-65535) and protocol is found in " + \ "/etc/protocols" self._add_result(t, n, s) # negotiable subkey = 'negotiable' t = 'info' n = self._get_check_name('ports_%s_format' % tagname, extra=subkey) s = 'OK' if subkey not in entry: s = 'OK (skipped, not found)' elif not isinstance(entry[subkey], bool): t = 'error' s = "'%s: %s' should be either 'yes' or 'no'" % \ (subkey, entry[subkey]) self._add_result(t, n, s) def check_apps_socket(self): '''Check apps - socket''' if not self.is_snap2 or 'apps' not in self.snap_yaml: return for app in self.snap_yaml['apps']: key = 'socket' if key not in self.snap_yaml['apps'][app]: # We check for required elsewhere continue t = 'info' n = self._get_check_name(key, app=app) s = 'OK' if not isinstance(self.snap_yaml['apps'][app][key], bool): t = 'error' s = "'%s: %s' should be either 'yes' or 'no'" % ( key, self.snap_yaml['apps'][app][key]) elif 'listen-stream' not in self.snap_yaml['apps'][app]: t = 'error' s = "'socket' specified without 'listen-stream'" self._add_result(t, n, s) def check_apps_listen_stream(self): '''Check apps - listen-stream''' if not self.is_snap2 or 'apps' not in self.snap_yaml: return for app in self.snap_yaml['apps']: key = 'listen-stream' if key not in self.snap_yaml['apps'][app]: # We check for required elsewhere continue t = 'info' n = self._get_check_name(key, app=app) s = 'OK' if not isinstance(self.snap_yaml['apps'][app][key], str): t = 'error' s = "invalid entry: %s (not a str)" % ( self.snap_yaml['apps'][app][key]) elif len(self.snap_yaml['apps'][app][key]) == 0: t = 'error' s = "'%s' is empty" % key self._add_result(t, n, s) if t == 'error': continue t = 'info' n = self._get_check_name('%s_matches_name' % key, app=app) s = 'OK' sock = self.snap_yaml['apps'][app][key] pkgname = self.snap_yaml['name'] if sock.startswith('@'): if sock != '@%s' % pkgname and \ not sock.startswith('@%s_' % pkgname): t = 'error' s = ("abstract socket '%s' is neither '%s' nor starts " "with '%s'" % (sock, '@%s' % pkgname, '@%s_' % pkgname)) elif sock.startswith('/'): found = False for path in ["/tmp/", "/var/lib/snaps/%s/" % pkgname, "/var/lib/snaps/%s." % pkgname, "/run/shm/snaps/%s/" % pkgname, "/run/shm/snaps/%s." % pkgname]: if sock.startswith(path): found = True break if not found: t = 'error' s = ("named socket '%s' should be in a writable " "app-specific area or /tmp" % sock) else: t = 'error' s = ("'%s' does not specify an abstract socket (starts " "with '@') or absolute filename" % (sock)) self._add_result(t, n, s) def _verify_valid_socket(self, app, key): '''Verify valid values for socket key''' t = 'info' n = self._get_check_name(key, app=app) s = 'OK' if not isinstance(self.snap_yaml['apps'][app][key], str): t = 'error' s = "invalid entry: %s (not a str)" % ( self.snap_yaml['apps'][app][key]) elif len(self.snap_yaml['apps'][app][key]) == 0: t = 'error' s = "'%s' is empty" % key elif 'listen-stream' not in self.snap_yaml['apps'][app]: t = 'error' s = "'%s' specified without 'listen-stream'" % key self._add_result(t, n, s) if t == 'error': return t = 'error' n = self._get_check_name('%s_reserved' % key, app=app) s = "'%s' should not be used until snappy supports per-app users" \ % key self._add_result(t, n, s) t = 'info' n = self._get_check_name("%s_matches_name" % key, app=app) s = 'OK' if self.snap_yaml['apps'][app][key] != self.snap_yaml['name']: t = 'error' s = "'%s' != '%s'" % (self.snap_yaml['apps'][app][key], self.snap_yaml['name']) self._add_result(t, n, s) def check_apps_socket_user(self): '''Check apps - socket-user''' if not self.is_snap2 or 'apps' not in self.snap_yaml: return for app in self.snap_yaml['apps']: key = 'socket-user' if key not in self.snap_yaml['apps'][app]: # We check for required elsewhere continue self._verify_valid_socket(app, key) def check_apps_socket_group(self): '''Check apps - socket-group''' if not self.is_snap2 or 'apps' not in self.snap_yaml: return for app in self.snap_yaml['apps']: key = 'socket-group' if key not in self.snap_yaml['apps'][app]: # We check for required elsewhere continue self._verify_valid_socket(app, key) def _verify_interfaces(self, iface_type): for iface in self.snap_yaml[iface_type]: # If the 'interface' name is the same as the 'iface' name, then # 'interface' is optional since the interface name and the iface # name are the same interface = iface spec = self.snap_yaml[iface_type][iface] if isinstance(spec, str): # Abbreviated syntax (no attributes) # : # : interface = spec elif 'interface' in spec: # Full specification. # : # : # interface: interface = spec['interface'] # Validate indirect (specified via alias) interfaces: if interface != iface: key = 'interface' t = 'info' n = self._get_check_name(iface_type, app=key, extra=interface) s = 'OK' if not isinstance(interface, str): t = 'error' s = "invalid %s: %s (not a str)" % (key, interface) elif len(interface) == 0: t = 'error' s = "'%s' is empty" % key self._add_result(t, n, s) if t == 'error': continue # Check interfaces whitelist. t = 'info' n = self._get_check_name(iface_type, app=interface, extra=iface) s = 'OK' if interface not in self.interfaces: t = 'error' s = "unknown interface '%s'" % interface self._add_result(t, n, s) if t == 'error': continue # Abbreviated interfaces don't have attributes, done checking. if isinstance(spec, str): continue # Check interface attributes. for attrib in spec: if attrib == 'interface': continue t = 'info' n = self._get_check_name('%s_attributes' % iface_type, app=iface, extra=attrib) s = "OK" attrib_key = "%s/%s" % (attrib, iface_type) if attrib_key not in self.interfaces[interface]: t = 'error' s = "unknown attribute '%s' for interface '%s' (%s)" % ( attrib, interface, iface_type) elif not isinstance( spec[attrib], type(self.interfaces[interface][attrib_key])): t = 'error' s = "'%s' is not '%s'" % \ (attrib, type(self.interfaces[interface][attrib_key]).__name__) self._add_result(t, n, s) def check_plugs(self): '''Check plugs''' iface_type = 'plugs' if not self.is_snap2 or iface_type not in self.snap_yaml: return self._verify_interfaces(iface_type) def _verify_app_interfaces(self, app, key): t = 'info' n = self._get_check_name("app_%s" % key, app=app) s = "OK" if not isinstance(self.snap_yaml['apps'][app][key], list): t = 'error' s = "invalid '%s' entry: '%s' (not a list)" % ( key, self.snap_yaml['apps'][app][key]) elif len(self.snap_yaml['apps'][app][key]) < 1: t = 'error' s = "invalid %s entry (empty)" % (key) self._add_result(t, n, s) if t == 'error': return # The interface referenced in the app's 'key' field (plugs/slots) can # either be a known interface (when the interface name reference and # the interface is the same) or can reference a name in the snap's # toplevel 'key' (plugs/slots) mapping for ref in self.snap_yaml['apps'][app][key]: t = 'info' n = self._get_check_name('app_%s_plug_reference' % key, app=app, extra=ref) s = "OK" if not isinstance(ref, str): t = 'error' s = "invalid %s interface name reference: '%s' (not a str)" \ % (key, ref) elif ref not in self.interfaces and \ (key not in self.snap_yaml or ref not in self.snap_yaml[key]): t = 'error' s = "unknown %s interface name reference '%s'" % (key, ref) self._add_result(t, n, s) def check_apps_plugs(self): '''Check apps plugs''' if not self.is_snap2 or 'apps' not in self.snap_yaml: return for app in self.snap_yaml['apps']: key = 'plugs' if key not in self.snap_yaml['apps'][app]: continue self._verify_app_interfaces(app, key) def check_slots(self): '''Check slots''' iface_type = 'slots' if not self.is_snap2 or iface_type not in self.snap_yaml: return self._verify_interfaces(iface_type) def check_apps_slots(self): '''Check apps slots''' if not self.is_snap2 or 'apps' not in self.snap_yaml: return for app in self.snap_yaml['apps']: key = 'slots' if key not in self.snap_yaml['apps'][app]: continue self._verify_app_interfaces(app, key) def check_external_symlinks(self): '''Check snap for external symlinks''' if not self.is_snap2: return # Note: unclear if gadget snaps can legitimately have external # symlinks, but err on side of caution. kernel snaps for reference # kernels ship a dangling lib/modules/.../build (like on desktop) but # also may have legitimate symlinks in lib/firmware, so just allow # them. if 'type' in self.snap_yaml and (self.snap_yaml['type'] == 'os' or self.snap_yaml['type'] == 'kernel'): return t = 'info' n = self._get_check_name('external_symlinks') s = 'OK' links = find_external_symlinks(self._get_unpack_dir(), self.pkg_files) if len(links) > 0: t = 'error' s = 'package contains external symlinks: %s' % ', '.join(links) self._add_result(t, n, s) def check_architecture_all(self): '''Check if actually architecture all''' if not self.is_snap2: return if 'architectures' in self.snap_yaml and \ 'all' not in self.snap_yaml['architectures']: return t = 'info' n = self._get_check_name('valid_contents_for_architecture') s = 'OK' # look for compiled code x_binaries = [] for i in self.pkg_bin_files: # .pyc files are arch-independent if i.endswith(".pyc"): continue x_binaries.append(os.path.relpath(i, self._get_unpack_dir())) if len(x_binaries) > 0: t = 'error' s = "found binaries for architecture 'all': %s" % \ ", ".join(x_binaries) self._add_result(t, n, s) def check_architecture_specified_needed(self): '''Check if the specified architecture is actually needed''' if not self.is_snap2 or 'architectures' not in self.snap_yaml: return if 'all' in self.snap_yaml['architectures']: return for arch in self.snap_yaml['architectures']: t = 'info' n = self._get_check_name('architecture_specified_needed', extra=arch) s = 'OK' if len(self.pkg_bin_files) == 0: t = 'warn' s = "Could not find compiled binaries for architecture '%s'" \ % arch self._add_result(t, n, s) def check_vcs(self): '''Check for VCS files in the package''' if not self.is_snap2: return t = 'info' n = self._get_check_name('vcs_files') s = 'OK' found = [] for d in self.vcs_files: entries = glob.glob("%s/%s" % (self._get_unpack_dir(), d)) if len(entries) > 0: for i in entries: found.append(os.path.relpath(i, self.unpack_dir)) if len(found) > 0: t = 'warn' s = 'found VCS files in package: %s' % ", ".join(found) self._add_result(t, n, s) def check_epoch(self): '''Check epoch''' if not self.is_snap2 or 'epoch' not in self.snap_yaml: return t = 'info' n = self._get_check_name('epoch_valid') s = 'OK' if not isinstance(self.snap_yaml['epoch'], int): t = 'error' s = "malformed 'epoch': %s (not an integer)" % ( self.snap_yaml['epoch']) elif int(self.snap_yaml['epoch']) < 0: t = 'error' s = "malformed 'epoch': '%s' should be positive integer" % ( self.snap_yaml['epoch']) self._add_result(t, n, s) def check_confinement(self): '''Check confinement''' if not self.is_snap2 or 'confinement' not in self.snap_yaml: return allowed = ['strict', 'devmode'] t = 'info' n = self._get_check_name('confinement_valid') s = 'OK' if not isinstance(self.snap_yaml['confinement'], str): t = 'error' s = "malformed 'confinement': %s (not a string)" % ( self.snap_yaml['confinement']) elif self.snap_yaml['confinement'] not in allowed: t = 'error' s = "malformed 'confinement': '%s' should be one of '%s'" % ( self.snap_yaml['confinement'], ", ".join(allowed)) elif self.snap_yaml['type'] not in ['app', 'kernel']: t = 'error' s = "'confinement' should only be specified with 'type: app'" self._add_result(t, n, s) def _verify_env(self, env, app=None): t = 'info' n = self._get_check_name('environment_valid', app=app) s = 'OK' if not isinstance(env, dict): t = 'error' s = "invalid environment: %s (not a dict)" % env self._add_result(t, n, s) # http://pubs.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap08.html invalid = ['=', '\0'] portable_pat = re.compile(r'^[A-Z_][A-Z0-9_]*$') lenient_pat = re.compile(r'^[a-zA-Z0-9_]+$') for key in env: t = 'info' n = self._get_check_name('environment_key_valid', app=app, extra=key) s = 'OK' l = None invalid_chars = [] for c in invalid: if c in key: invalid_chars.append(c) if len(invalid_chars) > 0: t = 'error' s = "found invalid characters '%s'" % ", ".join(invalid_chars) elif not portable_pat.search(key) and lenient_pat.search(key): t = 'info' s = "'%s' is not shell portable" % key l = "http://pubs.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap08.html" elif not lenient_pat.search(key): t = 'warn' s = "unusual characters in '%s' " % key + \ "(should be '^[a-zA-Z0-9_]+$')" self._add_result(t, n, s, link=l) # The only limit on the contents of an arg appear to be the length # but that is goint to be language and system dependent, so don't # worry about it here (this would simply be a bug in the software) t = 'info' n = n = self._get_check_name('environment_value_valid', app=app, extra=key) s = 'OK' if not isinstance(env[key], str): t = 'error' s = "invalid environment value for '%s': %s" % (key, env[key]) self._add_result(t, n, s) def check_environment(self): '''Check environment''' if not self.is_snap2 or 'environment' not in self.snap_yaml: return self._verify_env(self.snap_yaml['environment']) def check_apps_environment(self): '''Check apps environment''' if not self.is_snap2 or 'apps' not in self.snap_yaml: return for app in self.snap_yaml['apps']: key = 'environment' if key not in self.snap_yaml['apps'][app]: continue self._verify_env(self.snap_yaml['apps'][app]['environment'], app=app) click-reviewers-tools-0.44~16.04.1/clickreviews/cr_framework.py0000664000000000000000000002351512666350672021263 0ustar '''cr_framework.py: click framework''' # # Copyright (C) 2014-2015 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from __future__ import print_function from clickreviews.cr_common import ClickReview, error, open_file_read import glob import os import re class ClickReviewFramework(ClickReview): '''This class represents click framework reviews''' def __init__(self, fn, overrides=None): ClickReview.__init__(self, fn, "framework", overrides=overrides) self.frameworks_file = dict() self.frameworks = dict() if not self.is_snap1: return if self.manifest is not None: for app in self.manifest['hooks']: if 'framework' not in self.manifest['hooks'][app]: # msg("Skipped missing framework hook for '%s'" % app) continue if not isinstance(self.manifest['hooks'][app]['framework'], str): error("manifest malformed: hooks/%s/framework is not str" % app) (full_fn, data) = self._extract_framework(app) self.frameworks_file[app] = full_fn self.frameworks[app] = data self.framework_policy_dirs = ['apparmor', 'seccomp'] self.framework_policy_subdirs = ['templates', 'policygroups'] (self.framework_policy, self.framework_policy_unknown) = \ self._extract_framework_policy() def _extract_framework(self, app): '''Get framework for app''' rel = self.manifest['hooks'][app]['framework'] fn = os.path.join(self.unpack_dir, rel) if not os.path.exists(fn): error("Could not find '%s'" % rel) data = dict() fh = open_file_read(fn) for line in fh.readlines(): tmp = line.split(':') if len(tmp) != 2: continue data[tmp[0].strip()] = tmp[1].strip() fh.close() return (fn, data) def _extract_framework_policy(self): '''Get framework policy files''' policy_dict = dict() unknown = [] fpdir = os.path.join(self.unpack_dir, "meta", "framework-policy") for i in glob.glob("%s/*" % fpdir): rel_i = os.path.basename(i) if not os.path.isdir(i) or rel_i not in self.framework_policy_dirs: unknown.append(os.path.relpath(i, self.unpack_dir)) continue policy_dict[rel_i] = dict() for j in glob.glob("%s/*" % i): rel_j = os.path.basename(j) if not os.path.isdir(j) or \ rel_j not in self.framework_policy_subdirs: unknown.append(os.path.relpath(j, self.unpack_dir)) continue policy_dict[rel_i][rel_j] = dict() for k in glob.glob("%s/*" % j): rel_k = os.path.basename(k) if not os.path.isfile(k): unknown.append(os.path.relpath(k, self.unpack_dir)) continue fh = open_file_read(k) policy_dict[rel_i][rel_j][rel_k] = fh.read() fh.close() return (policy_dict, unknown) def _has_framework_in_metadir(self): '''Check if snap has meta/.framework''' if not self.is_snap1: return False return os.path.exists(os.path.join(self.unpack_dir, 'meta', '%s.framework' % self.pkg_yaml['name'])) def check_framework_hook_obsolete(self): '''Check manifest doesn't specify 'framework' hook''' if not self.is_snap1: return t = 'info' n = self._get_check_name("obsolete_declaration") s = "OK" if len(self.frameworks) > 0: t = 'error' s = "framework hook found for '%s'" % \ ",".join(sorted(self.frameworks)) self._add_result(t, n, s) def check_snappy_framework_file_obsolete(self): '''Check snap doesn't ship .framework file''' if not self.is_snap1 or self.pkg_yaml['type'] != 'framework': return t = 'info' n = self._get_check_name("obsolete_framework_file") s = "OK" if self._has_framework_in_metadir(): t = 'warn' s = "found '%s.framework' (safe to remove)" % self.pkg_yaml['name'] self._add_result(t, n, s) def check_snappy_framework_depends(self): '''Check framework doesn't depend on other frameworks''' if not self.is_snap1 or self.pkg_yaml['type'] != 'framework': return t = 'info' n = self._get_check_name("dependency") s = "OK" if "frameworks" in self.pkg_yaml: t = 'error' s = "'type: framework' may not specify 'frameworks'" self._add_result(t, n, s) def check_snappy_framework_policy(self): '''Check framework ships at least some policy''' if not self.is_snap1 or self.pkg_yaml['type'] != 'framework': return t = 'info' n = self._get_check_name("policies") s = "OK" found = False for i in self.framework_policy_dirs: if i not in self.framework_policy: continue for j in self.framework_policy_subdirs: if j not in self.framework_policy[i]: continue if len(self.framework_policy[i][j].keys()) > 0: found = True if not found: t = 'warn' s = "security policy not found" self._add_result(t, n, s) t = 'info' n = self._get_check_name("policy_unknown") s = "OK" if len(self.framework_policy_unknown) > 0: t = 'warn' s = "framework policy has unexpected entries: '%s'" % \ ",".join(self.framework_policy_unknown) self._add_result(t, n, s) def check_snappy_framework_policy_metadata(self): '''Check framework policy has expected meta data''' if not self.is_snap1 or self.pkg_yaml['type'] != 'framework': return t = 'info' n = self._get_check_name("policy_metadata") s = "OK" msgs = [] for term in ["# Description: ", "# Usage: "]: for i in self.framework_policy_dirs: if i not in self.framework_policy: continue for j in self.framework_policy_subdirs: if j not in self.framework_policy[i]: continue for k in self.framework_policy[i][j].keys(): found = False for l in self.framework_policy[i][j][k].splitlines(): if l.startswith(term): found = True if not found: msgs.append("'%s' in '%s/%s/%s'" % (term, i, j, k)) if len(msgs) > 0: t = 'error' s = "Could not find meta data: %s" % ",".join(msgs) self._add_result(t, n, s) def check_snappy_framework_policy_matching(self): '''Check framework policy ships apparmor and seccomp for each''' if not self.is_snap1 or self.pkg_yaml['type'] != 'framework': return t = 'info' n = self._get_check_name("has_all_policy") s = "OK" if len(self.framework_policy.keys()) == 0: s = "OK (skipped missing policy)" self._add_result(t, n, s) return for i in self.framework_policy: for j in self.framework_policy[i]: for k in self.framework_policy[i][j]: for other in self.framework_policy_dirs: if t == i: continue t = 'info' n = self._get_check_name( "policy", extra="%s/%s/%s" % (i, j, k)) s = "OK" if j not in self.framework_policy[other] or \ k not in self.framework_policy[other][j]: t = 'error' s = "Could not find matching '%s/%s/%s'" % (other, j, k) self._add_result(t, n, s) def check_snappy_framework_policy_filenames(self): '''Check framework policy file names''' if not self.is_snap1 or self.pkg_yaml['type'] != 'framework': return for i in self.framework_policy: for j in self.framework_policy[i]: for k in self.framework_policy[i][j]: f = "%s/%s/%s" % (i, j, k) t = 'info' n = self._get_check_name( "policy_valid_name", extra=f) s = "OK" if not re.search(r'^[a-z0-9][a-z0-9+\.-]+$', k): t = 'error' s = "'%s' should match '^[a-z0-9][a-z0-9+\.-]+$'" % f elif k.startswith(self.pkg_yaml['name']): t = 'warn' s = "'%s' should not begin with package name" % f self._add_result(t, n, s) click-reviewers-tools-0.44~16.04.1/clickreviews/cr_url_dispatcher.py0000664000000000000000000001500612666350672022272 0ustar '''cr_url dispatcher.py: click url_dispatcher''' # # Copyright (C) 2014-2015 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from __future__ import print_function from clickreviews.cr_common import ClickReview, error, open_file_read import json import os # https://wiki.ubuntu.com/URLDispatcher class ClickReviewUrlDispatcher(ClickReview): '''This class represents click lint reviews''' def __init__(self, fn, overrides=None): peer_hooks = dict() my_hook = 'urls' peer_hooks[my_hook] = dict() peer_hooks[my_hook]['allowed'] = ClickReview.app_allowed_peer_hooks peer_hooks[my_hook]['required'] = [] ClickReview.__init__(self, fn, "url_dispatcher", peer_hooks=peer_hooks, overrides=overrides) if not self.is_click and not self.is_snap1: return self.required_keys = ['protocol'] self.optional_keys = ['domain-suffix'] self.url_dispatcher_files = dict() # click-show-files and tests self.url_dispatcher = dict() if self.manifest is None: return for app in self.manifest['hooks']: if 'urls' not in self.manifest['hooks'][app]: # msg("Skipped missing urls hook for '%s'" % app) continue if not isinstance(self.manifest['hooks'][app]['urls'], str): error("manifest malformed: hooks/%s/urls is not str" % app) (full_fn, jd) = self._extract_url_dispatcher(app) self.url_dispatcher_files[app] = full_fn self.url_dispatcher[app] = jd def _extract_url_dispatcher(self, app): '''Get url dispatcher json''' u = self.manifest['hooks'][app]['urls'] if not u: error("'urls' definition is empty for '%s'" % app) fn = os.path.join(self.unpack_dir, u) if not os.path.isfile(fn): error("'%s' is not a file" % fn) bn = os.path.basename(fn) if not os.path.exists(fn): error("Could not find '%s'" % bn) fh = open_file_read(fn) contents = "" for line in fh.readlines(): contents += line fh.close() try: jd = json.loads(contents) except Exception as e: error("url-dispatcher json unparseable: %s (%s):\n%s" % (bn, str(e), contents)) if not isinstance(jd, list): error("url-dispatcher json is malformed: %s:\n%s" % (bn, contents)) return (fn, jd) def check_required(self): '''Check url-dispatcher required fields''' if not self.is_click and not self.is_snap1: return for app in sorted(self.url_dispatcher): for r in self.required_keys: found = False t = 'info' n = self._get_check_name('required_entry', app=app, extra=r) s = "OK" for entry in self.url_dispatcher[app]: if not isinstance(entry, dict): t = 'error' s = "'%s' is not a dict" % str(entry) self._add_result(t, n, s) continue if r in entry: if not isinstance(entry[r], str): t = 'error' s = "'%s' is not a string" % r elif entry[r] == "": t = 'error' s = "'%s' is empty" % r else: found = True if not found and t != 'error': t = 'error' s = "Missing required field '%s'" % r self._add_result(t, n, s) def check_optional(self): '''Check url-dispatcher optional fields''' if not self.is_click and not self.is_snap1: return for app in sorted(self.url_dispatcher): for o in self.optional_keys: found = False t = 'info' n = self._get_check_name('optional_entry', app=app, extra=o) s = "OK" for entry in self.url_dispatcher[app]: if not isinstance(entry, dict): t = 'error' s = "'%s' is not a dict" % str(entry) self._add_result(t, n, s) continue if o in entry: if not isinstance(entry[o], str): t = 'error' s = "'%s' is not a string" % o elif entry[o] == "": t = 'error' s = "'%s' is empty" % o else: found = True if not found and t != 'error': s = "OK (skip missing)" self._add_result(t, n, s) def check_unknown(self): '''Check url-dispatcher unknown fields''' if not self.is_click and not self.is_snap1: return for app in sorted(self.url_dispatcher): unknown = [] for entry in self.url_dispatcher[app]: t = 'info' n = self._get_check_name('unknown_entry', app=app) s = "OK" if not isinstance(entry, dict): t = 'error' s = "'%s' is not a dict" % str(entry) self._add_result(t, n, s) continue for f in entry.keys(): if f not in self.required_keys and \ f not in self.optional_keys: unknown.append(f) if len(unknown) == 1: t = 'warn' s = "Unknown field '%s'" % unknown[0] elif len(unknown) > 1: t = 'warn' s = "Unknown fields '%s'" % ", ".join(unknown) self._add_result(t, n, s) click-reviewers-tools-0.44~16.04.1/clickreviews/cr_security.py0000664000000000000000000017137112666401043021126 0ustar '''cr_security.py: click security checks''' # # Copyright (C) 2013-2016 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from __future__ import print_function from clickreviews.common import ( AA_PROFILE_NAME_MAXLEN, AA_PROFILE_NAME_ADVLEN, ) from clickreviews.cr_common import ClickReview, error, open_file_read import clickreviews.cr_common as cr_common import clickreviews.apparmor_policy as apparmor_policy import copy import json import os class ClickReviewSecurity(ClickReview): '''This class represents click lint reviews''' def __init__(self, fn, overrides=None): peer_hooks = dict() my_hook = 'apparmor' peer_hooks[my_hook] = dict() # Basically, everything except frameworks peer_hooks[my_hook]['allowed'] = ClickReview.app_allowed_peer_hooks + \ ClickReview.scope_allowed_peer_hooks + \ ClickReview.service_allowed_peer_hooks + \ ['pay-ui'] peer_hooks[my_hook]['required'] = [] my_hook2 = 'apparmor-profile' peer_hooks[my_hook2] = dict() # Basically, everything except frameworks peer_hooks[my_hook2]['allowed'] = \ ClickReview.service_allowed_peer_hooks peer_hooks[my_hook2]['required'] = [] ClickReview.__init__(self, fn, "security", peer_hooks=peer_hooks, overrides=overrides) if not self.is_click and not self.is_snap1: return # If local_copy is None, then this will check the server to see if # we are up to date. However, if we are working within the development # tree, use it unconditionally. local_copy = None branch_fn = os.path.join(os.path.dirname(__file__), '../data/apparmor-easyprof-ubuntu.json') if os.path.exists(branch_fn): local_copy = branch_fn p = apparmor_policy.ApparmorPolicy(local_copy) self.aa_policy = p.policy self.all_fields = ['abstractions', 'author', 'binary', 'comment', 'copyright', 'name', 'policy_groups', 'policy_vendor', 'policy_version', 'read_path', 'template', 'template_variables', 'write_path'] self.ignored_fields = ['author', 'comment', 'copyright', 'name'] self.required_fields = ['policy_version'] self.redflag_fields = ['abstractions', 'binary', 'read_path', 'template_variables', 'write_path'] self.allowed_webapp_policy_groups = ['accounts', 'audio', 'camera', 'connectivity', 'content_exchange', 'content_exchange_source', 'keep-display-on', 'location', 'microphone', 'networking', 'video', 'webview'] self.allowed_push_helper_policy_groups = ['push-notification-client'] self.allowed_network_scope_policy_groups = ['accounts', 'networking'] self.required_ubuntu_account_plugin_policy_groups = ['accounts', 'networking'] self.allowed_ubuntu_account_plugin_policy_groups = \ self.required_ubuntu_account_plugin_policy_groups + \ ['audio', 'webview'] self.redflag_templates = ['unconfined'] # TODO: how to deal with other vendors self.extraneous_ubuntu_templates = ['ubuntu-sdk', 'default'] # framework policy is based on major framework version. In 13.10, there # was only 'ubuntu-sdk-13.10', but in 14.04, there will be several, # like 'ubuntu-sdk-14.04-html5', 'ubuntu-sdk-14.04-platform', etc # TODO: in 15.10 we will be using the policy version will match the # release framework, so we can simplify these checks self.major_framework_policy = { 'ubuntu-sdk-13.10': { 'policy_version': 1.0, }, 'ubuntu-sdk-14.04': { 'policy_version': 1.1, }, 'ubuntu-sdk-14.10': { 'policy_version': 1.2, }, 'ubuntu-sdk-15.04': { 'policy_version': 1.3, }, 'ubuntu-core-15.04': { 'policy_vendor': 'ubuntu-core', 'policy_version': 15.04, }, } framework_overrides = self.overrides.get('framework', {}) self._override_framework_policies(framework_overrides) # snappy self.sec_skipped_types = ['oem', 'os', 'kernel'] # these don't need security items self.security_manifests = dict() self.security_apps = [] self.security_profiles = dict() self.security_apps_profiles = [] if self.manifest is None and self.is_snap1: for exe_t in ['services', 'binaries']: if exe_t not in self.pkg_yaml: continue for item in self.pkg_yaml[exe_t]: if 'name' not in item: continue app = "%s/%s" % (exe_t, item['name']) if 'security-policy' in item: if 'apparmor' not in item['security-policy']: continue rel_fn = item['security-policy']['apparmor'] self.security_profiles[rel_fn] = \ self._extract_security_profile(app) self.security_apps_profiles.append(app) continue # Fake a security manifest for code reuse # FIXME: this needs to be updated when we have 'target' m = dict() m['policy_vendor'] = "ubuntu-core" m['policy_version'] = self._pkgfmt_version() if 'security-template' in item: m['template'] = item['security-template'] else: m['template'] = 'default' if 'caps' in item: m['policy_groups'] = item['caps'] elif self._pkgfmt_version() == "15.04": m['policy_groups'] = ['networking'] else: m['policy_groups'] = ['network-client'] self.security_manifests[app] = m self.security_apps.append(app) else: self.security_manifests = dict() self.security_apps = [] for app in self.manifest['hooks']: if 'apparmor' not in self.manifest['hooks'][app]: # msg("Skipped missing apparmor hook for '%s'" % app) continue if not isinstance(self.manifest['hooks'][app]['apparmor'], str): error("manifest malformed: hooks/%s/apparmor is not str" % app) rel_fn = self.manifest['hooks'][app]['apparmor'] self.security_manifests[rel_fn] = \ self._extract_security_manifest(app) self.security_apps.append(app) for app in self.manifest['hooks']: if 'apparmor-profile' not in self.manifest['hooks'][app]: # msg("Skipped missing apparmor hook for '%s'" % app) continue if not isinstance(self.manifest['hooks'][app]['apparmor-profile'], str): error("manifest malformed: hooks/%s/apparmor-profile is not " "str" % app) rel_fn = self.manifest['hooks'][app]['apparmor-profile'] self.security_profiles[rel_fn] = \ self._extract_security_profile(app) self.security_apps_profiles.append(app) def _override_framework_policies(self, overrides): # override major framework policies self.major_framework_policy.update(overrides) # override apparmor policies for name, data in overrides.items(): vendor = data.get('policy_vendor') version = str(data.get('policy_version')) if vendor not in self.aa_policy: self.aa_policy[vendor] = {} if version not in self.aa_policy[vendor]: # just ensure the version is defined # TODO: add support to override templates and policy groups self.aa_policy[vendor][version] = {} def _extract_security_manifest(self, app): '''Extract security manifest and verify it has the expected structure''' d = self.manifest['hooks'][app]['apparmor'] fn = os.path.join(self.unpack_dir, d) rel_fn = self.manifest['hooks'][app]['apparmor'] try: m = json.load(cr_common.open_file_read(fn)) except Exception: error("Could not load '%s'. Is it properly formatted?" % rel_fn) mp = json.dumps(m, sort_keys=True, indent=2, separators=(',', ': ')) if not isinstance(m, dict): error("'%s' malformed:\n%s" % (rel_fn, mp)) for k in sorted(m): if k not in self.all_fields: error("'%s' malformed: unsupported field '%s':\n%s" % (rel_fn, k, mp)) if k in ['abstractions', 'policy_groups', 'read_path', 'write_path']: if not isinstance(m[k], list): error("'%s' malformed: '%s' is not list:\n%s" % (rel_fn, k, mp)) elif k == 'template_variables': if not isinstance(m[k], dict): error("'%s' malformed: '%s' is not dict:\n%s" % (rel_fn, k, mp)) elif k == "policy_version": # python and Qt don't agree on the JSON output of floats that # are integers (ie, 1.0 vs 1). LP: #1214618 if not isinstance(m[k], float) and not isinstance(m[k], int): error("'%s' malformed: '%s' is not a JSON number:\n%s" % (rel_fn, k, mp)) if isinstance(m[k], int): m[k] = float(m[k]) else: if not isinstance(m[k], str): error("'%s' malformed: '%s' is not str:\n%s" % (rel_fn, k, mp)) return m def _get_security_manifest(self, app): '''Get the security manifest for app''' if app not in self.manifest['hooks']: error("Could not find '%s' in click manifest" % app) elif 'apparmor' not in self.manifest['hooks'][app]: error("Could not find apparmor hook for '%s' in click manifest" % app) f = self.manifest['hooks'][app]['apparmor'] m = self.security_manifests[f] return (f, m) def _extract_security_profile(self, app): '''Extract security profile''' rel_fn = self.manifest['hooks'][app]['apparmor-profile'] fn = os.path.join(self.unpack_dir, rel_fn) if not os.path.exists(fn): error("Could not find '%s'" % rel_fn) fh = open_file_read(fn) contents = "" for line in fh.readlines(): contents += line fh.close() # We could try to run this through apparmor_parser, but that is going # to be system dependent (eg, a profile may reference features on a # new parser and fail here on the local parser) return contents def _get_security_profile(self, app): '''Get the security profile for app''' if app not in self.manifest['hooks']: error("Could not find '%s' in click manifest" % app) elif 'apparmor-profile' not in self.manifest['hooks'][app]: error("Could not find apparmor-profile hook for '%s' in click " "manifest" % app) f = self.manifest['hooks'][app]['apparmor-profile'] p = self.security_profiles[f] return (f, p) def _get_highest_policy_version(self, vendor): '''Determine highest policy version for the vendor''' if vendor not in self.aa_policy: error("Could not find vendor '%s'" % vendor, do_exit=False) return None return float(sorted(self.aa_policy[vendor].keys())[-1]) def check_policy_vendor(self): '''Check policy_vendor''' if not self.is_click and not self.is_snap1: return for app in sorted(self.security_apps): (f, m) = self._get_security_manifest(app) t = 'info' n = self._get_check_name('policy_vendor', extra=f) s = "OK" if 'policy_vendor' in m and \ m['policy_vendor'] not in self.aa_policy: t = 'error' s = "policy_vendor '%s' not found" % m['policy_vendor'] self._add_result(t, n, s) t = 'info' n = self._get_check_name('policy_vendor_matches_framework', extra=f) s = "OK" if 'policy_vendor' in m: # policy_vendor is optional found_major = False for name, data in self.major_framework_policy.items(): framework = self.manifest['framework'] # snappy compat manifest supports comma-separated list # for framework if self.is_snap1 and ',' in framework: # For now, we know the release framework is appended. # TODO: fix for multiple frameworks framework = framework.split(',')[-1] if not framework.startswith(name): continue elif 'policy_vendor' not in data: # when not specified, default to 'ubuntu' data['policy_vendor'] = "ubuntu" found_major = True if m['policy_vendor'] != data['policy_vendor']: t = 'error' s = '%s != %s (%s)' % (str(m['policy_vendor']), data['policy_vendor'], framework) if not found_major: t = 'error' s = "Invalid framework '%s'" % framework self._add_result(t, n, s) def check_policy_version(self): '''Check policy version''' if not self.is_click and not self.is_snap1: return for app in sorted(self.security_apps): (f, m) = self._get_security_manifest(app) n = self._get_check_name('policy_version_exists', extra=f) if 'policy_version' not in m: self._add_result('error', n, 'could not find policy_version in manifest') continue t = 'info' s = "OK" vendor = "ubuntu" if 'policy_vendor' in m: vendor = m['policy_vendor'] version = str(m['policy_version']) if vendor not in self.aa_policy or \ not self._has_policy_version(vendor, version): t = 'error' s = 'could not find policy for %s/%s' % (vendor, version) self._add_result(t, n, s) highest = self._get_highest_policy_version(vendor) t = 'info' n = self._get_check_name( 'policy_version_is_highest', extra='(%s, %s)' % (str(highest), f)) s = "OK" l = None if float(m['policy_version']) != highest: t = 'info' l = 'http://askubuntu.com/q/562116/94326' s = '%s != %s' % (str(m['policy_version']), str(highest)) self._add_result(t, n, s, l) t = 'info' n = self._get_check_name('policy_version_matches_framework', extra=f) s = "OK" found_major = False for name, data in self.major_framework_policy.items(): framework = self.manifest['framework'] # snappy compat manifest supports comma-separated list # for framework if self.is_snap1 and ',' in framework: # For now, we know the release framework is appended. # TODO: fix for multiple frameworks framework = framework.split(',')[-1] if not framework.startswith(name): continue found_major = True if m['policy_version'] != data['policy_version']: t = 'error' l = 'http://askubuntu.com/q/686347' s = '%s != %s (%s)' % (str(m['policy_version']), data['policy_version'], framework) if not found_major: t = 'error' s = "Invalid framework '%s'" % framework self._add_result(t, n, s, l) def check_template(self): '''Check template''' if not self.is_click and not self.is_snap1: return for app in sorted(self.security_apps): (f, m) = self._get_security_manifest(app) t = 'info' n = self._get_check_name('template_with_policy_version', extra=f) s = "OK" if 'policy_version' not in m: self._add_result('error', n, 'could not find policy_version in manifest') continue self._add_result(t, n, s) t = 'info' n = self._get_check_name('template_valid', extra=f) s = "OK" l = 'http://askubuntu.com/q/671403' manual_review = False if 'template' not in m: # If template not specified, we just use the default self._add_result(t, n, 'OK (none specified)') continue elif m['template'] in self.redflag_templates: t = 'error' s = "(NEEDS REVIEW) '%s' not allowed" % m['template'] manual_review = True elif ('policy_vendor' not in m or m['policy_vendor'] == 'ubuntu') \ and m['template'] in self.extraneous_ubuntu_templates: t = 'warn' s = "No need to specify '%s' template" % m['template'] self._add_result(t, n, s, l, manual_review=manual_review) t = 'info' n = self._get_check_name('template_exists', extra=f) s = "OK" vendor = "ubuntu" if 'policy_vendor' in m: vendor = m['policy_vendor'] version = str(m['policy_version']) templates = self._get_templates(vendor, version) if len(templates) < 1: t = 'error' s = 'could not find templates' self._add_result(t, n, s) continue self._add_result(t, n, s) found = False if m['template'] in self._get_templates(vendor, version): found = True elif self.is_snap1: frameworks = [] if 'framework' in self.pkg_yaml: frameworks = [x.strip() for x in self.pkg_yaml['framework'].split(',')] elif 'frameworks' in self.pkg_yaml: frameworks = self.pkg_yaml['frameworks'] elif 'type' in self.pkg_yaml and \ self.pkg_yaml['type'] == 'framework': # frameworks may reference their own policy groups frameworks.append(self.pkg_yaml['name']) for f in frameworks: if m['template'].startswith("%s_" % f): found = True break if not found: t = 'error' s = "specified unsupported template '%s'" % m['template'] self._add_result(t, n, s) def check_policy_groups_webapps(self): '''Check policy_groups for webapps''' if not self.is_click and not self.is_snap1: return for app in sorted(self.security_apps): (f, m) = self._get_security_manifest(app) t = 'info' n = self._get_check_name('policy_groups_webapp', extra=f) s = "OK" webapp_template = "ubuntu-webapp" if 'template' not in m or m['template'] != webapp_template: # self._add_result(t, n, s) continue if 'policy_groups' not in m or \ 'networking' not in m['policy_groups']: self._add_result('error', n, "required group 'networking' not found") continue bad = [] for p in m['policy_groups']: if p not in self.allowed_webapp_policy_groups: bad.append(p) if len(bad) > 0: t = 'error' s = "found unusual policy groups: %s" % ", ".join(bad) self._add_result(t, n, s) t = 'info' n = self._get_check_name('policy_groups_webapp_webview', extra=f) s = "OK" if self.manifest['framework'] == "ubuntu-sdk-13.10": s = "SKIPPED (webview not available in 13.10)" elif 'webview' not in m['policy_groups']: t = 'warn' s = "'webview' not specified. Webapp may not function" self._add_result(t, n, s) def check_policy_groups_push_helpers(self): '''Check policy groups for push-helpers''' if not self.is_click and not self.is_snap1: return for app in sorted(self.security_apps): (f, m) = self._get_security_manifest(app) t = 'info' n = self._get_check_name('policy_groups_push_helper', extra=f) s = "OK" if 'template' not in m or m['template'] != 'ubuntu-push-helper': continue if 'policy_groups' not in m or \ 'push-notification-client' not in m['policy_groups']: self._add_result('error', n, "required group 'push-notification-client' " "not found") continue bad = [] for p in m['policy_groups']: if p not in self.allowed_push_helper_policy_groups: bad.append(p) elif p == "networking" or p == "network-client": # The above covers this, but let's be very explicit and # never allow networking with push-helpers bad.append(p) if len(bad) > 0: t = 'error' s = "found unusual policy groups: %s" % ", ".join(bad) self._add_result(t, n, s) def check_policy_groups_scopes(self): '''Check policy_groups for scopes''' if not self.is_click and not self.is_snap1: return for app in sorted(self.security_apps): (f, m) = self._get_security_manifest(app) t = 'info' n = self._get_check_name('policy_groups_scopes', extra=f) s = "OK" # jdstrand, 2014-06-05: ubuntu-scope-local-content is no longer available # scope_templates = ['ubuntu-scope-network', # 'ubuntu-scope-local-content'] scope_templates = ['ubuntu-scope-network'] if 'template' not in m or m['template'] not in scope_templates: continue if 'policy_groups' not in m: continue bad = [] for p in m['policy_groups']: if m['template'] == 'ubuntu-scope-network': # networking scopes should have extremely limited access if p not in self.allowed_network_scope_policy_groups: bad.append(p) # jdstrand, 2014-06-05: ubuntu-scope-local-content is no longer available # elif m['template'] == 'ubuntu-scope-local-content': # if p == 'networking': # bad.append(p) if len(bad) > 0: t = 'error' s = "found inappropriate policy groups: %s" % ", ".join(bad) self._add_result(t, n, s) def check_policy_groups_ubuntu_account_plugin(self): '''Check policy_groups for ubuntu-account-plugin template''' if not self.is_click and not self.is_snap1: return for app in sorted(self.security_apps): (f, m) = self._get_security_manifest(app) if 'template' not in m or m['template'] != 'ubuntu-account-plugin': continue t = 'info' n = self._get_check_name( 'required_policy_groups_ubuntu_account_plugin', extra=f) s = "OK" if 'policy_groups' not in m: self._add_result('error', n, "required policy groups not found") continue missing = [] for p in self.required_ubuntu_account_plugin_policy_groups: if p not in m['policy_groups']: missing.append(p) if len(missing) > 0: t = 'error' s = "missing required policy groups: %s" % ", ".join(missing) self._add_result(t, n, s) t = 'info' n = self._get_check_name( 'policy_groups_ubuntu_account_plugin', extra=f) s = "OK" bad = [] for p in m['policy_groups']: if p not in self.allowed_ubuntu_account_plugin_policy_groups: bad.append(p) if len(bad) > 0: t = 'error' s = "found unusual policy groups: %s" % ", ".join(bad) self._add_result(t, n, s) def check_policy_groups(self): '''Check policy_groups''' if not self.is_click and not self.is_snap1: return for app in sorted(self.security_apps): (f, m) = self._get_security_manifest(app) t = 'info' n = self._get_check_name('policy_groups_exists', app=app, extra=f) if 'policy_groups' not in m: # If template not specified, we just use the default self._add_result('info', n, 'no policy groups specified') continue elif 'policy_version' not in m: self._add_result('error', n, 'could not find policy_version in manifest') continue s = "OK" vendor = "ubuntu" if 'policy_vendor' in m: vendor = m['policy_vendor'] version = str(m['policy_version']) policy_groups = self._get_policy_groups(version=version, vendor=vendor) if len(policy_groups) < 1: t = 'error' s = 'could not find policy groups' self._add_result(t, n, s) continue self._add_result(t, n, s) # Check for duplicates t = 'info' n = self._get_check_name('policy_groups_duplicates', app=app, extra=f) s = 'OK' tmp = [] for p in m['policy_groups']: if m['policy_groups'].count(p) > 1 and p not in tmp: tmp.append(p) if len(tmp) > 0: tmp.sort() t = 'error' s = 'duplicate policy groups found: %s' % ", ".join(tmp) self._add_result(t, n, s) frameworks = [] if self.is_snap1: if 'framework' in self.pkg_yaml: frameworks = [x.strip() for x in self.pkg_yaml['framework'].split(',')] elif 'frameworks' in self.pkg_yaml: frameworks = self.pkg_yaml['frameworks'] elif 'type' in self.pkg_yaml and \ self.pkg_yaml['type'] == 'framework': # frameworks may reference their own policy groups frameworks.append(self.pkg_yaml['name']) # If we got here, we can see if valid policy groups were specified for i in m['policy_groups']: t = 'info' n = self._get_check_name('policy_groups_valid', app=app, extra=i) s = 'OK' # SDK will leave and empty policy group, report but don't # deny if i == "": t = 'error' s = 'found empty policy group' self._add_result(t, n, s) continue found = False framework_found = False for j in policy_groups: if i == os.path.basename(j): found = True break else: for f in frameworks: if i.startswith("%s_" % f): framework_found = True break if framework_found: found = True break if not found: t = 'error' s = "unsupported policy_group '%s'" % i self._add_result(t, n, s) if found: t = 'info' n = self._get_check_name( 'policy_groups_safe', app=app, extra=i) s = 'OK' l = None manual_review = False if framework_found: aa_type = 'framework' else: aa_type = self._get_policy_group_type(vendor, version, i) if i == "debug": t = 'error' s = "(REJECT) %s policy group " % aa_type + \ "'%s': not for production use" % (i) elif aa_type == "reserved": t = 'error' s = "(NEEDS REVIEW) %s policy group " % aa_type + \ "'%s': vetted applications only" % (i) if i == "debug": l = 'http://askubuntu.com/a/562123/94326' manual_review = True elif aa_type == 'framework': s = "OK (matches '%s' framework)" % i.split('_')[0] elif aa_type != "common": t = 'error' s = "policy group '%s' has " % i + \ "unknown type '%s'" % (aa_type) self._add_result(t, n, s, l, manual_review=manual_review) def check_ignored(self): '''Check ignored fields''' if not self.is_click and not self.is_snap1: return for app in sorted(self.security_apps): (f, m) = self._get_security_manifest(app) t = 'info' n = self._get_check_name('ignored_fields', extra=f) s = "OK" found = [] for i in self.ignored_fields: if i in m: found.append(i) if len(found) > 0: t = 'warn' s = "found ignored fields: %s" % ", ".join(found) self._add_result(t, n, s) def check_redflag(self): '''Check redflag fields''' if not self.is_click and not self.is_snap1: return for app in sorted(self.security_apps): (f, m) = self._get_security_manifest(app) t = 'info' n = self._get_check_name('redflag_fields', extra=f) s = "OK" found = [] for i in self.redflag_fields: if i in m: if i == 'policy_vendor' and \ m[i] in ['ubuntu', 'ubuntu-snappy']: continue found.append(i) if len(found) > 0: t = 'error' s = "found redflagged fields (needs human review): %s" % \ ", ".join(found) self._add_result(t, n, s) def check_required(self): '''Check required fields''' if not self.is_click and not self.is_snap1: return for app in sorted(self.security_apps): (f, m) = self._get_security_manifest(app) t = 'info' n = self._get_check_name('required_fields', extra=f) s = "OK" not_found = [] for i in self.required_fields: if i not in m: not_found.append(i) if len(not_found) > 0: t = 'error' s = "missing required fields: %s" % ", ".join(not_found) self._add_result(t, n, s) def check_apparmor_profile(self): '''Check apparmor-profile''' if not self.is_click and not self.is_snap1: return for app in sorted(self.security_apps_profiles): (f, p) = self._get_security_profile(app) searches = ['###VAR###', '###PROFILEATTACH###', '@{CLICK_DIR}', '@{APP_PKGNAME}', '@{APP_VERSION}', ] for v in searches: t = 'info' n = self._get_check_name( 'apparmor_profile', extra='%s (%s)' % (v, f)) s = "OK" if v not in p: if v.startswith('@') and \ ("# Unrestricted AppArmor policy" in p or "# This profile offers no protection" in p): self._add_result('info', n, "SKIPPED for '%s' (boilerplate)" % v) else: self._add_result('warn', n, "could not find '%s' in profile" % v) continue self._add_result(t, n, s) # This will be really nice to get rid of when the click compat manifest # is gone def _compare_security_yamls(self, yaml, click_m): '''Compare two security yamls''' def find_match(name, key, value, my_dict): if 'name' in my_dict and my_dict['name'] == name and \ key in my_dict and my_dict[key] == value: return True return False for first in [yaml, click_m]: if first == yaml: second = click_m second_m = "click-manifest" first_m = "package.yaml" else: second = yaml first_m = "click-manifest" second_m = "package.yaml" for exe_t in ['binaries', 'services']: t = 'info' n = self._get_check_name('yaml_%s' % exe_t) s = 'OK' if exe_t in first and exe_t not in second: t = 'error' s = "%s missing '%s'" % (second_m, exe_t) elif exe_t not in first and exe_t in second: t = 'error' s = "%s has extra '%s'" % (second_m, exe_t) self._add_result(t, n, s) if t == 'error': continue elif exe_t not in first and exe_t not in second: continue t = 'info' n = self._get_check_name('yaml_%s_entries' % exe_t) s = 'OK' if len(first[exe_t]) < len(second[exe_t]): t = 'error' s = "%s has extra '%s' entries" % (second_m, exe_t) self._add_result(t, n, s) for fapp in first[exe_t]: t = 'info' n = self._get_check_name( 'yaml_%s' % exe_t, app=fapp['name']) s = 'OK' sapp = None for tmp in second[exe_t]: if tmp['name'] == fapp['name']: sapp = tmp if sapp is None: t = 'error' s = "%s missing '%s'" % (second_m, fapp['name']) self._add_result(t, n, s) continue elif first == yaml and "security-override" in fapp or \ second == yaml and "security-override" in sapp: # no reason to check security-override since apparmor # hook entry will point to this file continue elif first == yaml and "security-policy" in fapp or \ second == yaml and "security-policy" in sapp: # no reason to check security-policy since apparmor # profile hook is used instead continue elif 'caps' not in fapp and 'caps' not in sapp and \ second == yaml and 'security-template' not in sapp: # no caps in yaml, policy_groups is empty in click # manifest, unless security-template is in yaml t = 'error' s = "'policy_groups' not found in click manifest " + \ "(should default to ['network-client'])" self._add_result(t, n, s) continue self._add_result(t, n, s) for key in ['security-template', 'caps']: t = 'info' n = self._get_check_name( 'yaml_%s' % exe_t, extra=second_m) s = 'OK' if key not in fapp: continue if key == 'caps': fapp['caps'] = set(fapp['caps']) if 'caps' in sapp: sapp['caps'] = set(sapp['caps']) if not find_match(fapp['name'], key, fapp[key], sapp): # handle snappy defaults for security-template # and caps if key == 'security-template' and \ second == yaml and key not in sapp and \ key in fapp and fapp[key] == 'default': # if yaml missing security-template, click may # specify 'default' self._add_result(t, n, s) continue elif key == 'caps' and second == yaml and \ 'security-template' in sapp and \ key not in sapp and \ (key not in fapp or key in fapp and fapp[key] == set([])): # when security-template is specified, then # caps won't default to 'networking' when # missing, so click manifest can omit or be [] self._add_result(t, n, s) continue elif key == 'caps' and second == yaml and \ 'security-template' not in sapp and \ key not in sapp and key in fapp and \ (fapp[key] == set(['networking']) or fapp[key] == set(['network-client'])): # no caps in yaml, policy_groups is networking # in click manifest when security-template not # specified in yaml self._add_result(t, n, s) continue elif key == 'caps' and second == click_m and \ key not in sapp and key in fapp and \ fapp[key] == set([]): # no caps in click manifest, caps is [] in yaml self._add_result(t, n, s) continue t = 'error' s = "%s has different '%s' for '%s'" % \ (second_m, key, fapp['name']) + \ " - '%s:%s' vs '%s:%s'" % (first_m, fapp, second_m, sapp) self._add_result(t, n, s) def _convert_click_security_to_yaml(self): '''Convert click manifest to yaml''' converted = dict() # The click compat manifest does not have a 1 to 1 mapping to # package.yaml (ie, can't differentiate between services and # binaries) so look the appname up in the package.yaml to know # if it is a service or binary yaml_services = [] if 'services' in self.pkg_yaml: yaml_services = [e['name'] for e in self.pkg_yaml['services'] if 'name' in e] yaml_binaries = [] if 'binaries' in self.pkg_yaml: for e in self.pkg_yaml['binaries']: if 'exec' in e: yaml_binaries.append(e['name']) else: yaml_binaries.append(e['name'].split('/')[-1]) for app in sorted(self.security_apps): if app == 'snappy-config': # the apparmor policy for snappy-config is autogenerated and # not represented in the yaml continue if app in yaml_services: key = 'services' elif app in yaml_binaries: key = 'binaries' else: t = 'error' n = self._get_check_name('yaml_and_click', app=app) s = "'%s' in click manifest missing from package.yaml" % app self._add_result(t, n, s) continue if key not in converted: converted[key] = [] tmp = dict() tmp['name'] = app (f, m) = self._get_security_manifest(app) if 'template' in m: tmp['security-template'] = m['template'] if 'policy_groups' in m: tmp['caps'] = m['policy_groups'] converted[key].append(copy.deepcopy(tmp)) for app in sorted(self.security_apps_profiles): if app in yaml_services: key = 'services' elif app in yaml_binaries: key = 'binaries' else: t = 'error' n = self._get_check_name('yaml_and_click', app=app) s = "'%s' in click manifest missing from package.yaml" % app self._add_result(t, n, s) continue if key not in converted: converted[key] = [] tmp = dict() tmp['name'] = app (f, p) = self._get_security_profile(app) tmp['security-policy'] = {'apparmor': f} converted[key].append(copy.deepcopy(tmp)) return copy.deepcopy(converted) def check_security_yaml_and_click(self): '''Verify click and security yaml are in sync (not including override) ''' if not self.is_click and not self.is_snap1: return if self.is_click or self.pkg_yaml['type'] in self.sec_skipped_types: return # setup a small dict that is a subset of self.pkg_yaml yml = dict() for exe_t in ['binaries', 'services']: if exe_t not in self.pkg_yaml: continue yml[exe_t] = copy.deepcopy(self.pkg_yaml[exe_t]) for item in yml[exe_t]: # account for binaries doing different things with 'name/exec' if exe_t == 'binaries' and 'exec' not in item and \ '/' in item['name']: item['name'] = os.path.basename(item['name']) converted = self._convert_click_security_to_yaml() # don't compare the security yaml and the click if the yaml isn't # formatted right. This avoids confusing errors for the user error = False for exe_t in yml.keys(): for item in yml[exe_t]: if 'security-template' in item and \ not isinstance(item['security-template'], str): error = True continue elif 'caps' in item and not isinstance(item['caps'], list): error = True continue if error: t = 'info' n = self._get_check_name('yaml_and_click') s = "SKIPPED (yaml errors)" self._add_result(t, n, s) return self._compare_security_yamls(yml, converted) def check_security_yaml_override_and_click(self): '''Verify click and security yaml override are in sync''' if not self.is_click and not self.is_snap1: return if self.is_click or self.pkg_yaml['type'] in self.sec_skipped_types: return for exe_t in ['services', 'binaries']: if exe_t not in self.pkg_yaml: continue for item in self.pkg_yaml[exe_t]: if 'name' not in item: t = 'error' n = self._get_check_name('yaml_override_click_name') s = "package.yaml malformed. Could not find 'name' " + \ "for entry in '%s'" % item self._add_result(t, n, s) continue app = item['name'] t = 'info' n = self._get_check_name('yaml_override_click', app=app) s = "OK" if 'security-override' not in item: s = "OK (skipping unspecified override)" elif 'apparmor' not in item['security-override']: t = 'error' s = "'apparmor' not specified in 'security-override' " + \ "for '%s'" % app elif item['security-override']['apparmor'] not in \ self.security_manifests: t = 'error' s = "'%s' not found in click manifest for '%s'" % \ (item['security-override']['apparmor'], app) # NOTE: we skip 'seccomp' because there isn't currently a # click hook for it self._add_result(t, n, s) def check_security_yaml_override(self): '''Verify security yaml override''' if not self.is_click and not self.is_snap1: return if not self.is_snap1: return for exe_t in ['services', 'binaries']: if exe_t not in self.pkg_yaml: continue for item in self.pkg_yaml[exe_t]: if 'name' not in item: t = 'error' n = self._get_check_name('yaml_override_name') s = "package.yaml malformed. Could not find 'name' " + \ "for entry in '%s'" % item self._add_result(t, n, s) continue app = item['name'] t = 'info' n = self._get_check_name('yaml_override_format', app=app) s = "OK" if 'security-override' not in item: s = "OK (skipping unspecified override)" else: if 'apparmor' not in item['security-override']: t = 'error' s = "'apparmor' not specified in 'security-override' " + \ "for '%s'" % app elif 'seccomp' not in item['security-override']: t = 'error' s = "'seccomp' not specified in 'security-override' " + \ "for '%s'" % app self._add_result(t, n, s) def check_security_yaml_policy(self): '''Verify security yaml policy''' if not self.is_click and not self.is_snap1: return if not self.is_snap1: return for exe_t in ['services', 'binaries']: if exe_t not in self.pkg_yaml: continue for item in self.pkg_yaml[exe_t]: if 'name' not in item: t = 'error' n = self._get_check_name('yaml_policy_name') s = "package.yaml malformed. Could not find 'name' " + \ "for entry in '%s'" % item self._add_result(t, n, s) continue app = item['name'] t = 'info' n = self._get_check_name('yaml_policy_format', app=app) s = "OK" if 'security-policy' not in item: s = "OK (skipping unspecified policy)" elif 'apparmor' not in item['security-policy']: t = 'error' s = "'apparmor' not specified in 'security-policy' " + \ "for '%s'" % app elif 'seccomp' not in item['security-policy']: t = 'error' s = "'seccomp' not specified in 'security-policy' for " + \ "'%s'" % app self._add_result(t, n, s) if 'security-policy' in item: t = 'error' n = self._get_check_name('yaml_policy_present') s = "(NEEDS REVIEW) 'security-policy' not allowed" l = 'https://developer.ubuntu.com/en/snappy/guides/security-policy/' m = True self._add_result(t, n, s, link=l, manual_review=m) def check_security_yaml_combinations(self): '''Verify security yaml uses valid combinations''' if not self.is_click and not self.is_snap1: return if not self.is_snap1 or self.pkg_yaml['type'] in self.sec_skipped_types: return for exe_t in ['services', 'binaries']: if exe_t not in self.pkg_yaml: continue for item in self.pkg_yaml[exe_t]: if 'name' not in item: t = 'error' n = self._get_check_name('yaml_combinations_name') s = "package.yaml malformed. Could not find 'name' " + \ "for entry in '%s'" % item self._add_result(t, n, s) continue app = item['name'] t = 'info' n = self._get_check_name('yaml_combinations', app=app) s = "OK" if "security-policy" in item: for i in ['security-override', 'security-template', 'caps']: if i in item: t = 'error' s = "Found '%s' with 'security-policy'" % (i) break elif "security-override" in item: for i in ['security-policy', 'security-template', 'caps']: if i in item: t = 'error' s = "Found '%s' with 'security-override'" % (i) break self._add_result(t, n, s) def check_security_template(self): '''Check snap security-template''' if not self.is_click and not self.is_snap1: return if not self.is_snap1 or self.pkg_yaml['type'] in self.sec_skipped_types: return for exe_t in ['services', 'binaries']: if exe_t not in self.pkg_yaml: continue for item in self.pkg_yaml[exe_t]: if 'security-template' not in item: tmpl = "" else: tmpl = item['security-template'] if 'name' not in item: t = 'error' n = self._get_check_name('yaml_security-template_name') s = "package.yaml malformed. Could not find 'name' " + \ "for entry in '%s'" % item self._add_result(t, n, s) continue # Handle bin/exec concept with bianries app = os.path.basename(item['name']) t = 'info' n = self._get_check_name('yaml_security-template', app=app) s = "OK" if not isinstance(tmpl, str): t = 'error' s = "'%s/%s' malformed: '%s' is not str" % (exe_t, app, tmpl) self._add_result(t, n, s) continue self._add_result(t, n, s) if self.is_snap1: t = 'info' n = self._get_check_name('yaml_security-template_in_manifest', app=app) s = "OK" if app not in self.manifest['hooks']: t = 'error' s = "'%s' not found in click manifest" % app self._add_result(t, n, s) continue elif 'apparmor' not in self.manifest['hooks'][app] and \ 'apparmor-profile' not in self.manifest['hooks'][app]: t = 'error' s = "'apparmor' not found in click manifest for '%s'" % app self._add_result(t, n, s) continue # TODO: error if not 'common' or is 'unconfined' def check_security_caps(self): '''Check snap caps''' if not self.is_click and not self.is_snap1: return if not self.is_snap1 or self.pkg_yaml['type'] in self.sec_skipped_types: return for exe_t in ['services', 'binaries']: if exe_t not in self.pkg_yaml: continue for item in self.pkg_yaml[exe_t]: if 'caps' not in item: tmpl = [] else: tmpl = item['caps'] if 'name' not in item: t = 'error' n = self._get_check_name('yaml_caps_name') s = "package.yaml malformed. Could not find 'name' " + \ "for entry in '%s'" % item self._add_result(t, n, s) continue # Handle bin/exec concept with bianries app = os.path.basename(item['name']) t = 'info' n = self._get_check_name('yaml_caps', app=app) s = "OK" if not isinstance(tmpl, list): t = 'error' s = "'%s/%s' malformed: '%s' is not list" % (exe_t, app, tmpl) self._add_result(t, n, s) continue self._add_result(t, n, s) if self.is_snap1: t = 'info' n = self._get_check_name('yaml_caps_in_manifest', app=app) s = "OK" if app not in self.manifest['hooks']: t = 'error' s = "'%s' not found in click manifest" % app self._add_result(t, n, s) continue elif 'apparmor' not in self.manifest['hooks'][app] and \ 'apparmor-profile' not in self.manifest['hooks'][app]: t = 'error' s = "'apparmor' not found in click manifest for '%s'" % app self._add_result(t, n, s) continue # TODO: error if not 'common' def check_template_online_accounts_provider(self): '''Check template for online accounts account-provider''' if not self.is_click and not self.is_snap1: return for app in sorted(self.security_apps): (f, m) = self._get_security_manifest(app) t = 'info' n = self._get_check_name('template_account_provider', extra=f) s = "OK" if 'account-provider' not in self.manifest['hooks'][app]: continue if 'template' not in m or m['template'] != "ubuntu-account-plugin": t = 'error' s = "template is not 'ubuntu-account-plugin'" self._add_result(t, n, s) def check_template_online_accounts_qml_plugin(self): '''Check template for online accounts account-qml-plugin''' if not self.is_click and not self.is_snap1: return for app in sorted(self.security_apps): (f, m) = self._get_security_manifest(app) t = 'info' n = self._get_check_name('template_account_qml_plugin', extra=f) s = "OK" if 'account-qml-plugin' not in self.manifest['hooks'][app]: continue if 'template' not in m or m['template'] != "ubuntu-account-plugin": t = 'error' s = "template is not 'ubuntu-account-plugin'" self._add_result(t, n, s) def check_apparmor_profile_name_length(self): '''Check AppArmor profile name length''' if not self.is_click and not self.is_snap1: return maxlen = AA_PROFILE_NAME_MAXLEN advlen = AA_PROFILE_NAME_ADVLEN for app in sorted(self.security_apps): (f, m) = self._get_security_manifest(app) t = 'info' n = self._get_check_name('profile_name_length', extra=f) s = "OK" profile = "%s_%s_%s" % (self.click_pkgname, app, self.click_version) if len(profile) > maxlen: t = 'error' s = ("'%s' too long (exceeds %d characters). Please shorten " "'%s', '%s' and/or '%s'" % (profile, maxlen, self.click_pkgname, app, self.click_version)) elif len(profile) > advlen: t = 'warn' s = ("'%s' is long (exceeds %d characters) and thus could be " "problematic in certain environments. Please consider " "shortening '%s', '%s' and/or '%s'" % (profile, advlen, self.click_pkgname, app, self.click_version) ) self._add_result(t, n, s) click-reviewers-tools-0.44~16.04.1/clickreviews/cr_systemd.py0000664000000000000000000006161712666350672020763 0ustar '''cr_systemd.py: click systemd''' # # Copyright (C) 2015 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from __future__ import print_function from clickreviews.cr_common import ClickReview, error import copy import re class ClickReviewSystemd(ClickReview): '''This class represents click lint reviews''' def __init__(self, fn, overrides=None): # systemd isn't implemented as a hook any more so don't setup peerhooks ClickReview.__init__(self, fn, "snappy-systemd", overrides=overrides) self.systemd_files = dict() # click-show-files and tests self.systemd = dict() if not self.is_snap1: return # snappy-systemd currently only allows specifying: # - start (required) # - description (required) # - stop # - poststop # - stop-timeout # - caps (checked in in cr_security.py) # - security-template (checked in in cr_security.py) # - security-override (checked in in cr_security.py) # - security-policy (checked in in cr_security.py) self.required_keys = ['start', 'description'] self.optional_keys = ['stop', 'poststop', 'stop-timeout', 'bus-name', 'listen-stream', 'socket', 'socket-user', 'socket-group', 'ports' ] + self.snappy_exe_security if self.is_snap1 and 'services' in self.pkg_yaml: if len(self.pkg_yaml['services']) == 0: error("package.yaml malformed: 'services' is empty") for service in self.pkg_yaml['services']: if 'name' not in service: error("package.yaml malformed: required 'name' not found " "for entry in %s" % self.pkg_yaml['services']) elif not isinstance(service['name'], str): error("package.yaml malformed: required 'name' is not str" "for entry in %s" % self.pkg_yaml['services']) app = service['name'] self.systemd[app] = copy.deepcopy(service) del self.systemd[app]['name'] def _verify_required(self, my_dict, test_str): for app in sorted(my_dict): for r in self.required_keys: found = False t = 'info' n = self._get_check_name( '%s_required_key' % test_str, extra=r, app=app) s = "OK" if r in my_dict[app]: if not isinstance(my_dict[app][r], str): t = 'error' s = "'%s' is not a string" % r elif my_dict[app][r] == "": t = 'error' s = "'%s' is empty" % r else: found = True if not found and t != 'error': t = 'error' s = "Missing required field '%s'" % r self._add_result(t, n, s) def check_snappy_required(self): '''Check for package.yaml required fields''' if not self.is_snap1 or 'services' not in self.pkg_yaml: return self._verify_required(self._create_dict(self.pkg_yaml['services']), 'package_yaml') def _verify_optional(self, my_dict, test_str): for app in sorted(my_dict): for o in self.optional_keys: if o in self.snappy_exe_security: continue # checked in cr_security.py found = False t = 'info' n = self._get_check_name( '%s_optional_key' % test_str, extra=o, app=app) s = "OK" if o in my_dict[app]: if o == 'stop-timeout': if isinstance(my_dict[app][o], int): found = True elif not isinstance(my_dict[app][o], str): t = 'error' s = "'%s' is not a string or integer" % o elif not re.search(r'[0-9]+[ms]?$', my_dict[app][o]): t = 'error' s = "'%s' is not of form NN[ms] (%s)" % \ (my_dict[app][o], o) else: found = True elif o == 'ports': if not isinstance(my_dict[app][o], dict): t = 'error' s = "'%s' is not dictionary" % o elif o == 'socket': if not isinstance(my_dict[app][o], bool): t = 'error' s = "'%s' is not boolean" % o elif not isinstance(my_dict[app][o], str): t = 'error' s = "'%s' is not a string" % o elif my_dict[app][o] == "": t = 'error' s = "'%s' is empty" % o else: found = True if not found and t != 'error': s = "OK (skip missing)" self._add_result(t, n, s) def check_snappy_optional(self): '''Check snappy packate.yaml optional fields''' if not self.is_snap1 or 'services' not in self.pkg_yaml: return self._verify_optional(self._create_dict(self.pkg_yaml['services']), 'package_yaml') def _verify_unknown(self, my_dict, test_str): for app in sorted(my_dict): unknown = [] t = 'info' n = self._get_check_name('%s_unknown_key' % test_str, app=app) s = "OK" for f in my_dict[app].keys(): if f not in self.required_keys and \ f not in self.optional_keys: unknown.append(f) if len(unknown) == 1: t = 'warn' s = "Unknown field '%s'" % unknown[0] elif len(unknown) > 1: t = 'warn' s = "Unknown fields '%s'" % ", ".join(unknown) self._add_result(t, n, s) def check_snappy_unknown(self): '''Check snappy package.yaml unknown fields''' if not self.is_snap1 or 'services' not in self.pkg_yaml: return self._verify_unknown(self._create_dict(self.pkg_yaml['services']), 'package_yaml') def _verify_service_description(self, my_dict, test_str): '''Check snappy systemd description''' for app in sorted(my_dict): t = 'info' n = self._get_check_name('%s_description_present' % test_str, app=app) s = 'OK' if 'description' not in my_dict[app]: s = 'required description field not specified' self._add_result('error', n, s) return self._add_result(t, n, s) t = 'info' n = self._get_check_name('%s_description_empty' % test_str, app=app) s = 'OK' if len(my_dict[app]['description']) == 0: t = 'error' s = "description is empty" self._add_result(t, n, s) def check_snappy_service_description(self): '''Check snappy package.yaml description''' if not self.is_snap1 or 'services' not in self.pkg_yaml: return self._verify_service_description(self._create_dict( self.pkg_yaml['services']), 'package_yaml') def _verify_entry(self, my_dict, d, test_str): for app in sorted(my_dict): if d not in my_dict[app]: continue t = 'info' n = self._get_check_name('%s_empty' % test_str, extra=d, app=app) s = 'OK' if len(my_dict[app][d]) == 0: t = 'error' s = "%s entry is empty" % d self._add_result(t, n, s) continue self._add_result(t, n, s) t = 'info' n = self._get_check_name('%s_absolute_path' % test_str, extra=d, app=app) s = 'OK' if my_dict[app][d].startswith('/'): t = 'error' s = "'%s' should not specify absolute path" % my_dict[app][d] self._add_result(t, n, s) def check_snappy_service_start(self): '''Check snappy package.yaml start''' if not self.is_snap1 or 'services' not in self.pkg_yaml: return self._verify_entry(self._create_dict(self.pkg_yaml['services']), 'start', 'package_yaml') def check_snappy_service_stop(self): '''Check snappy package.yaml stop''' if not self.is_snap1 or 'services' not in self.pkg_yaml: return self._verify_entry(self._create_dict(self.pkg_yaml['services']), 'stop', 'package_yaml') def check_snappy_service_poststop(self): '''Check snappy package.yaml poststop''' if not self.is_snap1 or 'services' not in self.pkg_yaml: return self._verify_entry(self._create_dict(self.pkg_yaml['services']), 'poststop', 'package_yaml') def _verify_service_stop_timeout(self, my_dict, test_str): for app in sorted(my_dict): t = 'info' n = self._get_check_name('%s_stop_timeout' % test_str, app=app) s = "OK" if 'stop-timeout' not in my_dict[app]: s = "OK (skip missing)" self._add_result(t, n, s) return st = my_dict[app]['stop-timeout'] if not isinstance(st, int) and not isinstance(st, str): t = 'error' s = 'stop-timeout is not a string or integer' self._add_result(t, n, s) return if isinstance(st, str): if re.search(r'[0-9]+[ms]?$', st): st = int(st.rstrip(r'[ms]')) else: t = 'error' s = "'%s' is not of form NN[ms] (%s)" % (my_dict[app], st) self._add_result(t, n, s) return if st < 0 or st > 60: t = 'error' s = "stop-timeout '%d' out of range (0-60)" % \ my_dict[app]['stop-timeout'] self._add_result(t, n, s) def check_snappy_service_stop_timeout(self): '''Check snappy package.yaml stop-timeout''' if not self.is_snap1 or 'services' not in self.pkg_yaml: return self._verify_service_stop_timeout(self._create_dict( self.pkg_yaml['services']), 'package_yaml') def _verify_service_bus_name(self, pkgname, my_dict, test_str, is_fwk): for app in sorted(my_dict): if 'bus-name' not in my_dict[app]: continue t = 'info' n = self._get_check_name('%s_bus-name_framework' % test_str, app=app) s = 'OK' if not is_fwk: t = 'error' s = "Use of bus-name requires package be of 'type: framework'" self._add_result(t, n, s) t = 'info' n = self._get_check_name('%s_bus-name_empty' % test_str, app=app) s = 'OK' if len(my_dict[app]['bus-name']) == 0: t = 'error' s = "'bus-name' is empty" self._add_result(t, n, s) continue self._add_result(t, n, s) t = 'info' n = self._get_check_name('%s_bus-name_format' % test_str, app=app) l = None s = 'OK' if not re.search(r'^[A-Za-z0-9][A-Za-z0-9_-]*(\.[A-Za-z0-9][A-Za-z0-9_-]*)+$', my_dict[app]['bus-name']): t = 'error' l = 'http://dbus.freedesktop.org/doc/dbus-specification.html' s = "'%s' is not of form '^[A-Za-z0-9][A-Za-z0-9_-]*(\\.[A-Za-z0-9][A-Za-z0-9_-]*)+$'" % \ (my_dict[app]['bus-name']) self._add_result(t, n, s, l) t = 'info' n = self._get_check_name('%s_bus-name_matches_name' % test_str, app=app) s = 'OK' suggested = [pkgname, "%s.%s" % (pkgname, app) ] found = False for name in suggested: if my_dict[app]['bus-name'].endswith(name): found = True break if not found: t = 'error' s = "'%s' doesn't end with one of: %s" % \ (my_dict[app]['bus-name'], ", ".join(suggested)) self._add_result(t, n, s) def check_snappy_service_bus_name(self): '''Check snappy package.yaml bus-name''' if not self.is_snap1 or 'services' not in self.pkg_yaml: return is_framework = False if 'type' in self.pkg_yaml and self.pkg_yaml['type'] == 'framework': is_framework = True self._verify_service_bus_name(self.pkg_yaml['name'], self._create_dict( self.pkg_yaml['services']), 'package_yaml', is_framework) def _verify_service_ports(self, pkgname, my_dict, test_str): for app in sorted(my_dict): if 'ports' not in my_dict[app]: continue t = 'info' n = self._get_check_name('%s_ports_empty' % test_str, app=app) s = 'OK' if len(my_dict[app]['ports'].keys()) == 0: t = 'error' s = "'ports' must contain 'internal' and/or 'external'" self._add_result(t, n, s) continue self._add_result(t, n, s) t = 'info' n = self._get_check_name('%s_ports_bad_key' % test_str, app=app) s = 'OK' badkeys = [] for i in my_dict[app]['ports'].keys(): if i not in ['internal', 'external']: badkeys.append(i) if len(badkeys) > 0: t = 'error' s = "Unknown '%s' found in 'ports'" % ",".join(badkeys) self._add_result(t, n, s) port_pat = re.compile(r'^[0-9]+/[a-z0-9\-]+$') for key in ['internal', 'external']: if key not in my_dict[app]['ports']: continue if len(my_dict[app]['ports'][key].keys()) < 1: t = 'error' n = self._get_check_name('%s_ports' % test_str, extra=key, app=app) s = 'Could not find any %s ports' % key self._add_result(t, n, s) continue for tagname in my_dict[app]['ports'][key]: entry = my_dict[app]['ports'][key][tagname] if len(entry.keys()) < 1: t = 'error' n = self._get_check_name('%s_ports' % test_str, extra=key, app=app) s = 'Could not find any subkeys for %s' % tagname self._add_result(t, n, s) continue # Annoyingly, the snappy-systemd file uses 'Port' and # 'Negotiable' instead of 'port' and 'negotiable' from the # yaml if (test_str == 'package_yaml' and 'negotiable' not in entry and 'port' not in entry) or \ (test_str == 'hook' and 'Negotiable' not in entry and 'Port' not in entry): t = 'error' n = self._get_check_name('%s_ports_invalid' % test_str, extra=key, app=app) s = "Must specify specify at least 'port' or " + \ "'negotiable'" self._add_result(t, n, s) continue # port subkey = 'port' if test_str == 'hook': subkey = 'Port' t = 'info' n = self._get_check_name('%s_ports_%s_format' % (test_str, tagname), extra=subkey) s = 'OK' if subkey not in entry: s = 'OK (skipped, not found)' else: tmp = entry[subkey].split('/') if not port_pat.search(entry[subkey]) or \ int(tmp[0]) < 1 or int(tmp[0]) > 65535: t = 'error' s = "'%s' should be of form " % entry[subkey] + \ "'port/protocol' where port is an integer " + \ "(1-65535) and protocol is found in " + \ "/etc/protocols" self._add_result(t, n, s) # negotiable subkey = 'negotiable' if test_str == 'hook': subkey = 'Negotiable' t = 'info' n = self._get_check_name('%s_ports_%s_format' % (test_str, tagname), extra=subkey) s = 'OK' if subkey not in entry: s = 'OK (skipped, not found)' elif entry[subkey] not in [True, False]: t = 'error' s = "'%s: %s' should be either 'yes' or 'no'" % \ (subkey, entry[subkey]) self._add_result(t, n, s) def check_snappy_service_ports(self): '''Check snappy package.yaml ports''' if not self.is_snap1 or 'services' not in self.pkg_yaml: return self._verify_service_ports(self.pkg_yaml['name'], self._create_dict( self.pkg_yaml['services']), 'package_yaml') def _verify_service_listen_stream(self, pkgname, my_dict, test_str): for app in sorted(my_dict): if 'listen-stream' not in my_dict[app]: continue t = 'info' n = self._get_check_name('%s_listen-stream_empty' % test_str, app=app) s = 'OK' if len(my_dict[app]['listen-stream']) == 0: t = 'error' s = "'listen-stream' is empty" self._add_result(t, n, s) continue self._add_result(t, n, s) t = 'info' n = self._get_check_name('%s_listen-stream_matches_name' % test_str, app=app) s = 'OK' sock = my_dict[app]['listen-stream'] if sock.startswith('@'): if sock != '@%s' % pkgname and \ not sock.startswith('@%s_' % pkgname): t = 'error' s = ("abstract socket '%s' is neither '%s' nor starts " "with '%s'" % (sock, '@%s' % pkgname, '@%s_' % pkgname)) elif sock.startswith('/'): found = False for path in ["/tmp/", "/var/lib/apps/%s/" % pkgname, "/var/lib/apps/%s." % pkgname, "/run/shm/snaps/%s/" % pkgname, "/run/shm/snaps/%s." % pkgname]: if sock.startswith(path): found = True break if not found: t = 'error' s = ("named socket '%s' should be in a writable" "app-specific area or /tmp" % sock) else: t = 'error' s = ("'%s' does not specify an abstract socket (starts " "with '@') or absolute filename" % (sock)) self._add_result(t, n, s) def check_snappy_service_listen_stream(self): '''Check snappy package.yaml listen-stream''' if not self.is_snap1 or 'services' not in self.pkg_yaml: return self._verify_service_listen_stream(self.pkg_yaml['name'], self._create_dict( self.pkg_yaml['services']), 'package_yaml') def check_snappy_service_socket_user(self): '''Check snappy package.yaml socket-user''' if not self.is_snap1 or 'services' not in self.pkg_yaml: return my_dict = self._create_dict(self.pkg_yaml['services']) for app in sorted(my_dict): if 'socket-user' not in my_dict[app]: continue t = 'error' n = self._get_check_name('package_yaml_socket-user', app=app) s = "'socket-user' should not be used until snappy supports " + \ "per-app users" self._add_result(t, n, s, manual_review=True) t = 'info' n = self._get_check_name('package_yaml_socket-user_matches', app=app) s = "OK" if my_dict[app]['socket-user'] != self.pkg_yaml['name']: t = 'error' s = "'%s' != '%s'" % (my_dict[app]['socket-user'], self.pkg_yaml['name']) self._add_result(t, n, s) t = 'info' n = self._get_check_name('package_yaml_socket-user_listen-stream', app=app) s = "OK" if 'listen-stream' not in my_dict[app]: t = 'error' s = "'socket-user' specified without 'listen-stream'" self._add_result(t, n, s) def check_snappy_service_socket_group(self): '''Check snappy package.yaml socket-group''' if not self.is_snap1 or 'services' not in self.pkg_yaml: return my_dict = self._create_dict(self.pkg_yaml['services']) for app in sorted(my_dict): if 'socket-group' not in my_dict[app]: continue t = 'error' n = self._get_check_name('package_yaml_socket-group', app=app) s = "'socket-group' should not be used until snappy supports " + \ "per-app groups" self._add_result(t, n, s, manual_review=True) t = 'info' n = self._get_check_name('package_yaml_socket-group_matches', app=app) s = "OK" if my_dict[app]['socket-group'] != self.pkg_yaml['name']: t = 'error' s = "'%s' != '%s'" % (my_dict[app]['socket-group'], self.pkg_yaml['name']) self._add_result(t, n, s) t = 'info' n = self._get_check_name('package_yaml_socket-group_listen-stream', app=app) s = "OK" if 'listen-stream' not in my_dict[app]: t = 'error' s = "'socket-group' specified without 'listen-stream'" self._add_result(t, n, s) def check_snappy_service_socket(self): '''Check snappy package.yaml socket''' if not self.is_snap1 or 'services' not in self.pkg_yaml: return my_dict = self._create_dict(self.pkg_yaml['services']) for app in sorted(my_dict): if 'socket' not in my_dict[app]: continue t = 'info' n = self._get_check_name('package_yaml_socket_listen-stream', app=app) s = "OK" if 'listen-stream' not in my_dict[app]: t = 'error' s = "'socket' specified without 'listen-stream'" self._add_result(t, n, s) click-reviewers-tools-0.44~16.04.1/clickreviews/common.py0000664000000000000000000005377612744147072020101 0ustar '''common.py: common classes and functions''' # # Copyright (C) 2013-2016 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from __future__ import print_function import atexit import codecs import inspect import json import logging import magic import os import re import shutil import subprocess import sys import tempfile import types DEBUGGING = False UNPACK_DIR = None RAW_UNPACK_DIR = None TMP_DIR = None VALID_SYSCALL = r'^[a-z0-9_]{2,64}$' # This needs to match up with snapcraft MKSQUASHFS_OPTS = ['-noappend', '-comp', 'xz', '-all-root', '-no-xattrs'] # There are quite a few kernel interfaces that can cause problems with # long profile names. These are outlined in # https://launchpad.net/bugs/1499544. The big issue is that the audit # message must fit within PAGE_SIZE (at least 4096 on supported archs), # so long names could push the audit message to be too big, which would # result in a denial for that rule (but, only if the rule would've # allowed it). Giving a hard-error on maxlen since we know that this # will be a problem. The advisory length is what it is since we know # that compound labels are sometimes logged and so a snappy system # running an app in a snappy container or a QA testbed running apps # under LXC AA_PROFILE_NAME_MAXLEN = 230 # 245 minus a bit for child profiles AA_PROFILE_NAME_ADVLEN = 100 def cleanup_unpack(): global UNPACK_DIR if UNPACK_DIR is not None and os.path.isdir(UNPACK_DIR): recursive_rm(UNPACK_DIR) UNPACK_DIR = None global RAW_UNPACK_DIR if RAW_UNPACK_DIR is not None and os.path.isdir(RAW_UNPACK_DIR): recursive_rm(RAW_UNPACK_DIR) RAW_UNPACK_DIR = None global TMP_DIR if TMP_DIR is not None and os.path.isdir(TMP_DIR): recursive_rm(TMP_DIR) TMP_DIR = None atexit.register(cleanup_unpack) # # Utility classes # class ReviewException(Exception): '''This class represents Review exceptions''' def __init__(self, value): self.value = value def __str__(self): return repr(self.value) class Review(object): '''Common review class''' magic_binary_file_descriptions = [ 'application/x-executable; charset=binary', 'application/x-sharedlib; charset=binary', 'application/x-object; charset=binary', 'application/octet-stream; charset=binary' ] def __init__(self, fn, review_type, overrides=None): self.pkg_filename = fn self._check_package_exists() self.review_type = review_type # TODO: rename as pkg_report self.click_report = dict() self.result_types = ['info', 'warn', 'error'] for r in self.result_types: self.click_report[r] = dict() self.click_report_output = "json" global UNPACK_DIR if UNPACK_DIR is None: UNPACK_DIR = unpack_pkg(fn) self.unpack_dir = UNPACK_DIR global RAW_UNPACK_DIR if RAW_UNPACK_DIR is None: RAW_UNPACK_DIR = raw_unpack_pkg(fn) self.raw_unpack_dir = RAW_UNPACK_DIR self.is_click = False self.is_snap1 = False self.is_snap2 = False self.pkgfmt = {"type": "", "version": ""} (self.pkgfmt["type"], pkgver) = detect_package(fn, self.unpack_dir) if self._pkgfmt_type() == "snap": if pkgver < 2: self.is_snap1 = True self.pkgfmt["version"] = "15.04" else: self.is_snap2 = True self.pkgfmt["version"] = "16.04" elif self._pkgfmt_type() == "click": self.pkgfmt["version"] = "0.4" self.is_click = True else: error("Unknown package type: '%s'" % self._pkgfmt_type()) # Get a list of all unpacked files self.pkg_files = [] self._list_all_files() # Setup what is needed to get a list of all unpacked compiled binaries self.mime = magic.open(magic.MAGIC_MIME) self.mime.load() self.pkg_bin_files = [] # Don't run this here since only cr_lint.py and cr_functional.py need # it now # self._list_all_compiled_binaries() self.overrides = overrides if overrides is not None else {} self.override_result_type = None def _check_innerpath_executable(self, fn): '''Check that the provided path exists and is executable''' return os.access(fn, os.X_OK) def _extract_statinfo(self, fn): '''Extract statinfo from file''' try: st = os.stat(fn) except Exception: return None return st def _path_join(self, dirname, rest): return os.path.join(dirname, rest) def _get_sha512sum(self, fn): '''Get sha512sum of file''' (rc, out) = cmd(['sha512sum', fn]) if rc != 0: return None return out.split()[0] def _pkgfmt_type(self): '''Return the package format type''' if "type" not in self.pkgfmt: return "" return self.pkgfmt["type"] def _pkgfmt_version(self): '''Return the package format version''' if "version" not in self.pkgfmt: return "" return self.pkgfmt["version"] def _check_package_exists(self): '''Check that the provided package exists''' if not os.path.exists(self.pkg_filename): error("Could not find '%s'" % self.pkg_filename) def _list_all_files(self): '''List all files included in this click package.''' for root, dirnames, filenames in os.walk(self.unpack_dir): for f in filenames: self.pkg_files.append(os.path.join(root, f)) def _check_if_message_catalog(self, fn): '''Check if file is a message catalog (.mo file).''' if fn.endswith('.mo'): return True return False def _list_all_compiled_binaries(self): '''List all compiled binaries in this click package.''' for i in self.pkg_files: res = self.mime.file(i) if res in self.magic_binary_file_descriptions and \ not self._check_if_message_catalog(i): self.pkg_bin_files.append(i) def _get_check_name(self, name, app='', extra=''): name = ':'.join([self.review_type, name]) if app: name += ':' + app if extra: name += ':' + extra return name def _verify_pkgversion(self, v): '''Verify package name''' if not isinstance(v, (str, int, float)): return False re_valid_version = re.compile(r'^((\d+):)?' # epoch '([A-Za-z0-9.+:~-]+?)' # upstream '(-([A-Za-z0-9+.~]+))?$') # debian if re_valid_version.match(str(v)): return True return False def _get_policy_versions(self, vendor): '''Get the supported AppArmor policy versions''' if not self.aa_policy: return None if vendor not in self.aa_policy: error("Could not find vendor '%s'" % vendor, do_exit=False) return None supported_policy_versions = [] for i in self.aa_policy[vendor].keys(): supported_policy_versions.append("%.1f" % float(i)) return sorted(supported_policy_versions) def _get_templates(self, vendor, version, aa_type="all"): '''Get templates by type''' if not self.aa_policy: return None templates = [] if aa_type == "all": for k in self.aa_policy[vendor][version]['templates'].keys(): templates += self.aa_policy[vendor][version]['templates'][k] else: templates = self.aa_policy[vendor][version]['templates'][aa_type] return sorted(templates) def _has_policy_version(self, vendor, version): '''Determine if has specified policy version''' if not self.aa_policy: return None if vendor not in self.aa_policy: error("Could not find vendor '%s'" % vendor, do_exit=False) return False if str(version) not in self.aa_policy[vendor]: return False return True def _get_policy_groups(self, vendor, version, aa_type="all"): '''Get policy groups by type''' if not self.aa_policy: return None groups = [] if vendor not in self.aa_policy: error("Could not find vendor '%s'" % vendor, do_exit=False) return groups if not self._has_policy_version(vendor, version): error("Could not find version '%s'" % version, do_exit=False) return groups v = str(version) if aa_type == "all": for k in self.aa_policy[vendor][v]['policy_groups'].keys(): groups += self.aa_policy[vendor][v]['policy_groups'][k] else: groups = self.aa_policy[vendor][v]['policy_groups'][aa_type] return sorted(groups) def _get_policy_group_type(self, vendor, version, policy_group): '''Return policy group type''' if not self.aa_policy: return None for t in self.aa_policy[vendor][version]['policy_groups']: if policy_group in self.aa_policy[vendor][version]['policy_groups'][t]: return t return None def _get_template_type(self, vendor, version, template): '''Return template type''' if not self.aa_policy: return None for t in self.aa_policy[vendor][version]['templates']: if template in self.aa_policy[vendor][version]['templates'][t]: return t return None # click_report[][] = # result_type: info, warn, error # review_name: name of the check (prefixed with self.review_type) # result: contents of the review # link: url for more information # manual_review: force manual review # override_result_type: prefix results with [] and set # result_type to override_result_type def _add_result(self, result_type, review_name, result, link=None, manual_review=False, override_result_type=None): '''Add result to report''' if result_type not in self.result_types: error("Invalid result type '%s'" % result_type) prefix = "" if override_result_type is not None: if override_result_type not in self.result_types: error("Invalid override result type '%s'" % override_result_type) prefix = "[%s] " % result_type.upper() result_type = override_result_type if review_name not in self.click_report[result_type]: # log info about check so it can be collected into the # check-names.list file # format should be # CHECK|| msg = 'CHECK|{}|{}' name = ':'.join(review_name.split(':')[:2]) link_text = link if link is not None else "" logging.debug(msg.format(name, link_text)) self.click_report[result_type][review_name] = dict() self.click_report[result_type][review_name].update({ 'text': "%s%s" % (prefix, result), 'manual_review': manual_review, }) if link is not None: self.click_report[result_type][review_name]["link"] = link def do_report(self): '''Print report''' if self.click_report_output == "console": # TODO: format better import pprint pprint.pprint(self.click_report) elif self.click_report_output == "json": import json msg(json.dumps(self.click_report, sort_keys=True, indent=2, separators=(',', ': '))) rc = 0 if len(self.click_report['error']): rc = 2 elif len(self.click_report['warn']): rc = 1 return rc def do_checks(self): '''Run all methods that start with check_''' methodList = [name for name, member in inspect.getmembers(self, inspect.ismethod) if isinstance(member, types.MethodType)] for methodname in methodList: if not methodname.startswith("check_"): continue func = getattr(self, methodname) func() def set_review_type(self, name): '''Set review name''' self.review_type = name # # Utility functions # def error(out, exit_code=1, do_exit=True): '''Print error message and exit''' try: print("ERROR: %s" % (out), file=sys.stderr) except IOError: pass if do_exit: sys.exit(exit_code) def warn(out): '''Print warning message''' try: print("WARN: %s" % (out), file=sys.stderr) except IOError: pass def msg(out, output=sys.stdout): '''Print message''' try: print("%s" % (out), file=output) except IOError: pass def debug(out): '''Print debug message''' global DEBUGGING if DEBUGGING: try: print("DEBUG: %s" % (out), file=sys.stderr) except IOError: pass def cmd(command): '''Try to execute the given command.''' debug(command) try: sp = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) except OSError as ex: return [127, str(ex)] if sys.version_info[0] >= 3: out = sp.communicate()[0].decode('ascii', 'ignore') else: out = sp.communicate()[0] return [sp.returncode, out] def cmd_pipe(command1, command2): '''Try to pipe command1 into command2.''' try: sp1 = subprocess.Popen(command1, stdout=subprocess.PIPE) sp2 = subprocess.Popen(command2, stdin=sp1.stdout) except OSError as ex: return [127, str(ex)] if sys.version_info[0] >= 3: out = sp2.communicate()[0].decode('ascii', 'ignore') else: out = sp2.communicate()[0] return [sp2.returncode, out] def _unpack_cmd(cmd_args, d, dest): '''Low level unpack helper''' curdir = os.getcwd() os.chdir(d) (rc, out) = cmd(cmd_args) os.chdir(curdir) if rc != 0: if os.path.isdir(d): recursive_rm(d) error("unpacking failed with '%d':\n%s" % (rc, out)) if dest is None: dest = d else: shutil.move(d, dest) return dest def _unpack_snap_squashfs(snap_pkg, dest): '''Unpack a squashfs based snap package to dest''' d = tempfile.mkdtemp(prefix='review-') return _unpack_cmd(['unsquashfs', '-f', '-d', d, os.path.abspath(snap_pkg)], d, dest) def _unpack_click_deb(pkg, dest): d = tempfile.mkdtemp(prefix='review-') return _unpack_cmd(['dpkg-deb', '-R', os.path.abspath(pkg), d], d, dest) def unpack_pkg(fn, dest=None): '''Unpack package''' if not os.path.isfile(fn): error("Could not find '%s'" % fn) pkg = fn if not pkg.startswith('/'): pkg = os.path.abspath(pkg) if dest is not None and os.path.exists(dest): error("'%s' exists. Aborting." % dest) # check if its a squashfs based snap if is_squashfs(pkg): return _unpack_snap_squashfs(fn, dest) return _unpack_click_deb(fn, dest) def is_squashfs(filename): '''Return true if the given filename as a squashfs header''' with open(filename, 'rb') as f: header = f.read(10) return header.startswith(b"hsqs") def raw_unpack_pkg(fn, dest=None): '''Unpack raw package''' if not os.path.isfile(fn): error("Could not find '%s'" % fn) pkg = fn if not pkg.startswith('/'): pkg = os.path.abspath(pkg) # nothing to do for squashfs images if is_squashfs(pkg): return "" if dest is not None and os.path.exists(dest): error("'%s' exists. Aborting." % dest) d = tempfile.mkdtemp(prefix='review-') curdir = os.getcwd() os.chdir(d) (rc, out) = cmd(['ar', 'x', pkg]) os.chdir(curdir) if rc != 0: if os.path.isdir(d): recursive_rm(d) error("'ar x' failed with '%d':\n%s" % (rc, out)) if dest is None: dest = d else: shutil.move(d, dest) return dest def create_tempdir(): '''Create/reuse a temporary directory that is automatically cleaned up''' global TMP_DIR if TMP_DIR is None: TMP_DIR = tempfile.mkdtemp(prefix='review-') return TMP_DIR def open_file_read(path): '''Open specified file read-only''' try: orig = codecs.open(path, 'r', "UTF-8") except Exception: raise return orig def recursive_rm(dirPath, contents_only=False): '''recursively remove directory''' names = os.listdir(dirPath) for name in names: path = os.path.join(dirPath, name) if os.path.islink(path) or not os.path.isdir(path): os.unlink(path) else: recursive_rm(path) if contents_only is False: os.rmdir(dirPath) def run_check(cls): if len(sys.argv) < 2: error("Must give path to package") # extract args fn = sys.argv[1] if len(sys.argv) > 2: overrides = json.loads(sys.argv[2]) else: overrides = None review = cls(fn, overrides=overrides) review.do_checks() rc = review.do_report() sys.exit(rc) def detect_package(fn, dir=None): '''Detect what type of package this is''' pkgtype = None pkgver = None if not os.path.isfile(fn): error("Could not find '%s'" % fn) if dir is None: unpack_dir = unpack_pkg(fn) else: unpack_dir = dir if not os.path.isdir(unpack_dir): error("Could not find '%s'" % unpack_dir) pkg = fn if not pkg.startswith('/'): pkg = os.path.abspath(pkg) # check if its a squashfs based snap if is_squashfs(pkg): # 16.04+ squashfs snaps pkgtype = "snap" pkgver = 2 elif os.path.exists(os.path.join(unpack_dir, "meta/package.yaml")): # 15.04 ar-based snaps pkgtype = "snap" pkgver = 1 else: pkgtype = "click" pkgver = 1 if dir is None and os.path.isdir(unpack_dir): recursive_rm(unpack_dir) return (pkgtype, pkgver) def find_external_symlinks(unpack_dir, pkg_files): '''Check if symlinks in the package go out to the system.''' common = '(-[0-9.]+)?\.so(\.[0-9.]+)?' libc6_libs = ['ld-*.so', 'libanl', 'libBrokenLocale', 'libc', 'libcidn', 'libcrypt', 'libdl', 'libmemusage', 'libm', 'libmvec', 'libnsl', 'libnss_compat', 'libnss_dns', 'libnss_files', 'libnss_hesiod', 'libnss_nisplus', 'libnss_nis', 'libpcprofile', 'libpthread', 'libresolv', 'librt', 'libSegFault', 'libthread_db', 'libutil', ] libc6_pats = [] for lib in libc6_libs: libc6_pats.append(re.compile(r'%s%s' % (lib, common))) libc6_pats.append(re.compile(r'ld-*.so$')) libc6_pats.append(re.compile(r'ld-linux-.*.so\.[0-9.]+$')) def _in_patterns(pats, f): for pat in pats: if pat.search(f): return True return False external_symlinks = list(filter(lambda link: not os.path.realpath(link).startswith( unpack_dir) and not _in_patterns(libc6_pats, os.path.basename(link)), pkg_files)) return [os.path.relpath(i, unpack_dir) for i in external_symlinks] # check_results(report, expected_counts, expected) # Verify exact counts of types # expected_counts={'info': 1, 'warn': 0, 'error': 0} # self.check_results(report, expected_counts) # Verify counts of warn and error types # expected_counts={'info': None, 'warn': 0, 'error': 0} # self.check_results(report, expected_counts) # Verify exact messages: # expected = dict() # expected['info'] = dict() # expected['warn'] = dict() # expected['warn']['skeleton_baz'] = "TODO" # expected['error'] = dict() # self.check_results(r, expected=expected) def check_results(testobj, report, expected_counts={'info': 1, 'warn': 0, 'error': 0}, expected=None): if expected is not None: for t in expected.keys(): for r in expected[t]: testobj.assertTrue(r in report[t], "Could not find '%s' (%s) in:\n%s" % (r, t, json.dumps(report, indent=2))) for k in expected[t][r]: testobj.assertTrue(k in report[t][r], "Could not find '%s' (%s) in:\n%s" % (k, r, json.dumps(report, indent=2))) testobj.assertEqual(expected[t][r][k], report[t][r][k]) else: for k in expected_counts.keys(): if expected_counts[k] is None: continue testobj.assertEqual(len(report[k]), expected_counts[k], "(%s not equal)\n%s" % (k, json.dumps(report, indent=2))) click-reviewers-tools-0.44~16.04.1/clickreviews/sr_tests.py0000664000000000000000000001705312703500173020431 0ustar '''sr_tests.py: common setup and tests for test modules''' # # Copyright (C) 2013-2016 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import io import os import yaml from unittest.mock import patch from unittest import TestCase from clickreviews.common import ( check_results as common_check_results ) # These should be set in the test cases TEST_SNAP_YAML = "" TEST_PKGFMT_TYPE = "snap" TEST_PKGFMT_VERSION = "16.04" TEST_UNPACK_DIR = "/fake" TEST_SECURITY_PROFILES = dict() # # Mock override functions # def _mock_func(self): '''Fake test function''' return def _extract_snap_yaml(self): '''Pretend we read the package.yaml file''' return io.StringIO(TEST_SNAP_YAML) def _path_join(self, d, fn): '''Pretend we have a tempdir''' return os.path.join("/fake", fn) def _check_innerpath_executable(self, fn): '''Pretend we a file''' if '.nonexec' in fn: return False return True def _pkgfmt_type(self): '''Pretend we found the pkgfmt type''' return TEST_PKGFMT_TYPE def _detect_package(self, fn): '''Pretend we detected the package''' ver = 2 if TEST_PKGFMT_VERSION == "15.04": ver = 1 return (TEST_PKGFMT_TYPE, ver) def __get_unpack_dir(self): '''Pretend we found the unpack dir''' return TEST_UNPACK_DIR def create_patches(): # http://docs.python.org/3.4/library/unittest.mock-examples.html # Mock patching. Don't use decorators but instead patch in setUp() of the # child. patches = [] patches.append(patch('clickreviews.common.Review._check_package_exists', _mock_func)) patches.append(patch( 'clickreviews.sr_common.SnapReview._extract_snap_yaml', _extract_snap_yaml)) patches.append(patch( 'clickreviews.sr_common.SnapReview._path_join', _path_join)) patches.append(patch('clickreviews.common.unpack_pkg', _mock_func)) patches.append(patch('clickreviews.common.raw_unpack_pkg', _mock_func)) patches.append(patch('clickreviews.common.detect_package', _detect_package)) patches.append(patch('clickreviews.sr_common.SnapReview._list_all_files', _mock_func)) patches.append(patch( 'clickreviews.sr_common.SnapReview._list_all_compiled_binaries', _mock_func)) patches.append(patch('clickreviews.common.Review._list_all_files', _mock_func)) patches.append(patch( 'clickreviews.common.Review._list_all_compiled_binaries', _mock_func)) patches.append(patch( 'clickreviews.common.Review._check_innerpath_executable', _check_innerpath_executable)) # sr_common patches.append(patch('clickreviews.sr_common.SnapReview._get_unpack_dir', __get_unpack_dir)) # pkgfmt patches.append(patch("clickreviews.sr_common.SnapReview._pkgfmt_type", _pkgfmt_type)) return patches class TestSnapReview(TestCase): """Tests for the snap review tool.""" def __init__(self, *args): TestCase.__init__(self, *args) self._reset_test_data() def _reset_test_data(self): self.test_snap_yaml = dict() self.set_test_pkgfmt("snap", "16.04") self.set_test_snap_yaml("name", "foo") self.set_test_snap_yaml("version", "0.1") self.set_test_snap_yaml("description", "Test description") self.set_test_snap_yaml("summary", "Test summary") self.set_test_snap_yaml("architectures", ["all"]) apps = dict() apps["bar"] = dict() apps["bar"]["command"] = "bin/bar" self.set_test_snap_yaml("apps", apps) # mockup a package name self._update_test_name() # reset the security profiles self.test_security_profiles = dict() def _update_test_snap_yaml(self): global TEST_SNAP_YAML TEST_SNAP_YAML = yaml.dump(self.test_snap_yaml, default_flow_style=False, indent=4) def _update_test_security_profiles(self): global TEST_SECURITY_PROFILES TEST_SECURITY_PROFILES = dict() if len(self.test_security_profiles.keys()) == 0: TEST_SECURITY_PROFILES = dict() else: for plug in self.test_security_profiles.keys(): TEST_SECURITY_PROFILES[plug] = \ self.test_security_profiles[plug] def _update_test_name(self): self.test_name = "%s.origin_%s_%s.snap" % ( self.test_snap_yaml["name"], self.test_snap_yaml["version"], self.test_snap_yaml["architectures"][0]) def check_results(self, report, expected_counts={'info': 1, 'warn': 0, 'error': 0}, expected=None): common_check_results(self, report, expected_counts, expected) def check_manual_review(self, report, check_name, result_type='error', manual_review=True): result = report[result_type][check_name] self.assertEqual(result['manual_review'], manual_review) def set_test_snap_yaml(self, key, value): '''Set key in meta/snap.yaml to value. If value is None, remove key''' if value is None: if key in self.test_snap_yaml: self.test_snap_yaml.pop(key, None) else: self.test_snap_yaml[key] = value self._update_test_snap_yaml() def set_test_security_profile(self, plug, key, policy): '''Set policy in security profile for key''' if policy is None: if plug in self.test_security_profiles and \ key in self.test_security_profiles[plug]: self.test_security_profiles[plug].pop(key) elif key is None: if plug in self.test_security_profiles: self.test_security_profiles.pop(plug) elif plug is None: self.test_security_profiles = dict() else: if plug not in self.test_security_profiles: self.test_security_profiles[plug] = dict() self.test_security_profiles[plug][key] = policy self._update_test_security_profiles() def set_test_pkgfmt(self, t, v): global TEST_PKGFMT_TYPE global TEST_PKGFMT_VERSION TEST_PKGFMT_TYPE = t TEST_PKGFMT_VERSION = v def set_test_unpack_dir(self, d): global TEST_UNPACK_DIR TEST_UNPACK_DIR = d def setUp(self): '''Make sure our patches are applied everywhere''' patches = create_patches() for p in patches: p.start() self.addCleanup(p.stop) def tearDown(self): '''Make sure we reset everything to known good values''' global TEST_SNAP_YAML TEST_SNAP_YAML = "" global TEST_SECURITY_PROFILES TEST_SECURITY_PROFILES = dict() global TEST_PKGFMT_TYPE TEST_PKGFMT_TYPE = "snap" global TEST_PKGFMT_VERSION TEST_PKGFMT_VERSION = "16.04" global TEST_UNPACK_DIR TEST_UNPACK_DIR = "/fake" self._reset_test_data() click-reviewers-tools-0.44~16.04.1/clickreviews/cr_content_hub.py0000664000000000000000000001135512666350672021575 0ustar '''cr_content_hub.py: click content-hub checks''' # # Copyright (C) 2014-2015 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from __future__ import print_function from clickreviews.cr_common import ClickReview, error, open_file_read import json import os class ClickReviewContentHub(ClickReview): '''This class represents click lint reviews''' def __init__(self, fn, overrides=None): peer_hooks = dict() my_hook = 'content-hub' peer_hooks[my_hook] = dict() peer_hooks[my_hook]['allowed'] = ClickReview.app_allowed_peer_hooks peer_hooks[my_hook]['required'] = [] ClickReview.__init__(self, fn, "content_hub", peer_hooks=peer_hooks, overrides=overrides) if not self.is_click and not self.is_snap1: return self.valid_keys = ['destination', 'share', 'source'] self.content_hub_files = dict() # click-show-files and tests self.content_hub = dict() if self.manifest is None: return for app in self.manifest['hooks']: if 'content-hub' not in self.manifest['hooks'][app]: # msg("Skipped missing content-hub hook for '%s'" % app) continue if not isinstance(self.manifest['hooks'][app]['content-hub'], str): error("manifest malformed: hooks/%s/urls is not str" % app) (full_fn, jd) = self._extract_content_hub(app) self.content_hub_files[app] = full_fn self.content_hub[app] = jd def _extract_content_hub(self, app): '''Get content-hub hook content''' c = self.manifest['hooks'][app]['content-hub'] fn = os.path.join(self.unpack_dir, c) bn = os.path.basename(fn) if not os.path.exists(fn): error("Could not find '%s'" % bn) fh = open_file_read(fn) contents = "" for line in fh.readlines(): contents += line fh.close() try: jd = json.loads(contents) except Exception as e: error("content-hub json unparseable: %s (%s):\n%s" % (bn, str(e), contents)) if not isinstance(jd, dict): error("content-hub json is malformed: %s:\n%s" % (bn, contents)) return (fn, jd) def check_valid(self): '''Check validity of content-hub entries''' if not self.is_click and not self.is_snap1: return for app in sorted(self.content_hub): for k in self.content_hub[app].keys(): t = "info" n = self._get_check_name('valid', app=app, extra=k) s = "OK" if not isinstance(self.content_hub[app][k], list): t = "error" s = "'%s' is not a list" % k elif len(self.content_hub[app][k]) < 1: t = "error" s = "'%s' is empty" % k self._add_result(t, n, s) if t == "error": continue for v in self.content_hub[app][k]: t = "info" n = self._get_check_name('valid_value', app=app, extra=k) s = "OK" if not isinstance(v, str): t = "error" s = "'%s' is not a string" % k elif v == "": t = "error" s = "'%s' is empty" % k self._add_result(t, n, s) def check_unknown_keys(self): '''Check unknown''' if not self.is_click and not self.is_snap1: return for app in sorted(self.content_hub): unknown = [] t = "info" n = self._get_check_name('unknown', app=app) s = "OK" for key in self.content_hub[app].keys(): if key not in self.valid_keys: unknown.append(key) if len(unknown) == 1: t = "warn" s = "Unknown field '%s'" % unknown[0] elif len(unknown) > 1: t = "warn" s = "Unknown fields '%s'" % ", ".join(unknown) self._add_result(t, n, s) click-reviewers-tools-0.44~16.04.1/clickreviews/apparmor_policy.py0000664000000000000000000000274612750115316021771 0ustar # # Copyright (C) 2014 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import os import clickreviews.remote USER_DATA_FILE = os.path.join(clickreviews.remote.DATA_DIR, 'apparmor-easyprof-ubuntu.json') # XXX: This is a hack and will be gone, as soon as myapps has an API for this. AA_POLICY_DATA_URL = \ "http://bazaar.launchpad.net/~store-reviewers/click-reviewers-tools/trunk/download/head:/apparmoreasyprofubun-20140711222314-oeohtxzvf9a58fa6-1/apparmor-easyprof-ubuntu.json" def get_policy_file(fn): if fn is None: fn = USER_DATA_FILE clickreviews.remote.get_remote_file(fn, AA_POLICY_DATA_URL) class ApparmorPolicy(object): def __init__(self, local_copy_fn=None): self.policy = clickreviews.remote.read_cr_file(USER_DATA_FILE, AA_POLICY_DATA_URL, local_copy_fn) click-reviewers-tools-0.44~16.04.1/clickreviews/cr_functional.py0000664000000000000000000001633512666350672021432 0ustar '''cr_functional.py: click functional''' # # Copyright (C) 2013-2015 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from __future__ import print_function import binascii import os import re from clickreviews.cr_common import ClickReview, open_file_read # TODO: for QML apps, see if i18n.domain('%s') matches X-Ubuntu-Gettext-Domain # compiled apps can use organizationName to match X-Ubuntu-Gettext-Domain class ClickReviewFunctional(ClickReview): '''This class represents click lint reviews''' def __init__(self, fn, overrides=None): ClickReview.__init__(self, fn, "functional", overrides=overrides) if not self.is_click and not self.is_snap1: return self.qml_files = [] for i in self.pkg_files: if i.endswith(".qml"): self.qml_files.append(i) self._list_all_compiled_binaries() def check_applicationName(self): '''Check applicationName matches click manifest''' if not self.is_click and not self.is_snap1: return if self.manifest is None: return t = 'info' n = self._get_check_name('qml_applicationName_matches_manifest') s = "OK" l = None # find file with MainView in the QML mv = '\s*MainView\s*(\s+{)?' pat_mv = re.compile(r'\n%s' % mv) qmls = dict() for i in self.qml_files: qml = open_file_read(i).read() if pat_mv.search(qml): qmls[i] = qml # LP: #1256841 - QML apps with C++ using QSettings shouldn't # typically set applicationName in the QML for i in self.pkg_bin_files: f = open(i, 'rb') data = str(binascii.b2a_qp(f.read())) f.close() if 'QSettings' in data: s = "OK (binary uses QSettings)" self._add_result(t, n, s) return if len(self.qml_files) == 0: s = "OK (not QML)" self._add_result(t, n, s) return elif len(qmls) == 0: s = "SKIP: could not find MainView in QML files" self._add_result(t, n, s) return pat_mvl = re.compile(r'^%s' % mv) pat_appname = re.compile(r'^\s*applicationName\s*:\s*["\']') ok = False appnames = dict() for k in qmls.keys(): in_mainview = False for line in qmls[k].splitlines(): if in_mainview and pat_appname.search(line): appname = line.split(':', 1)[1].strip('"\' \t\n\r\f\v;') appnames[os.path.relpath(k, self.unpack_dir)] = appname if appname == self.click_pkgname: ok = True break elif pat_mvl.search(line): in_mainview = True if ok: break if len(appnames) == 0 or not ok: if len(self.pkg_bin_files) == 0: t = "warn" l = 'http://askubuntu.com/questions/417371/what-does-functional-qml-applicationname-matches-manifest-mean/417372' if len(appnames) == 0: s = "could not find applicationName in: %s" % \ ", ".join(sorted(list(map( lambda x: os.path.relpath(x, self.unpack_dir), qmls)))) else: # not ok s = "click manifest name '%s' not found in: " % \ self.click_pkgname + "%s" % \ ", ".join(sorted(list(map( lambda x: "%s ('%s')" % (x, appnames[x]), appnames)))) if len(self.pkg_bin_files) == 0: s += ". Application may not work properly when confined." else: s += ". May be ok (detected as compiled application)." self._add_result(t, n, s, l) def check_qtwebkit(self): '''Check that QML applications don't use QtWebKit''' if not self.is_click and not self.is_snap1: return t = 'info' n = self._get_check_name('qml_application_uses_QtWebKit') s = "OK" l = None qmls = [] pat_mv = re.compile(r'\n\s*import\s+QtWebKit') for i in self.qml_files: qml = open_file_read(i).read() if pat_mv.search(qml): qmls.append(os.path.relpath(i, self.unpack_dir)) if len(qmls) > 0: t = 'warn' s = "Found files that use unsupported QtWebKit (should use " + \ "UbuntuWebview (Ubuntu.Components.Extras.Browser >= " + \ "0.2) or Oxide instead): %s" % " ,".join(qmls) l = "http://askubuntu.com/questions/417342/what-does-functional-qml-application-uses-qtwebkit-mean/417343" self._add_result(t, n, s, l) t = 'info' n = self._get_check_name('qml_application_uses_UbuntuWebView_0.2') s = "OK" l = None if self.manifest is not None and \ self.manifest['framework'] == "ubuntu-sdk-13.10": s = "SKIPPED (Oxide not available in ubuntu-sdk-13.10)" else: qmls = [] pat_mv = re.compile(r'\n\s*import\s+Ubuntu\.Components\.Extras\.Browser\s+0\.1\s*\n') for i in self.qml_files: qml = open_file_read(i).read() if pat_mv.search(qml): qmls.append(os.path.relpath(i, self.unpack_dir)) if len(qmls) > 0: t = 'warn' s = "Found files that use unsupported QtWebKit via " + \ "'Ubuntu.Components.Extras.Browser 0.1' (should use " + \ "Ubuntu.Components.Extras.Browser >= 0.2 or " + \ "Oxide instead): %s" % " ,".join(qmls) l = "http://askubuntu.com/questions/417342/what-does-functional-qml-application-uses-qtwebkit-mean/417343" self._add_result(t, n, s, l) def check_friends(self): '''Check that QML applications don't use deprecated Friends API''' if not self.is_click and not self.is_snap1: return t = 'info' n = self._get_check_name('qml_application_uses_friends') s = "OK" l = None qmls = [] pat_mv = re.compile(r'\n\s*import\s+Friends') for i in self.qml_files: qml = open_file_read(i).read() if pat_mv.search(qml): qmls.append(os.path.relpath(i, self.unpack_dir)) if len(qmls) > 0: t = 'error' s = "Found files that use deprecated Friends API: %s" % " ,".join(qmls) l = "http://askubuntu.com/questions/497551/what-does-functional-qml-application-uses-friends-mean" self._add_result(t, n, s, l) click-reviewers-tools-0.44~16.04.1/clickreviews/cr_online_accounts.py0000664000000000000000000003724112666351155022447 0ustar '''cr_online_accounts.py: click online accounts''' # # Copyright (C) 2013-2015 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from __future__ import print_function from clickreviews.cr_common import ClickReview, error, open_file_read import json import os import re # http://lxml.de/tutorial.html import lxml.etree as etree class ClickReviewAccounts(ClickReview): '''This class represents click lint reviews''' def __init__(self, fn, overrides=None): peer_hooks = dict() peer_hooks['account-application'] = dict() peer_hooks['account-application']['allowed'] = \ ClickReview.app_allowed_peer_hooks + \ ClickReview.scope_allowed_peer_hooks peer_hooks['account-application']['required'] = ['apparmor'] peer_hooks['account-service'] = dict() peer_hooks['account-service']['required'] = ['account-application', 'apparmor' ] peer_hooks['account-service']['allowed'] = \ ClickReview.app_allowed_peer_hooks + \ ClickReview.scope_allowed_peer_hooks peer_hooks['accounts'] = dict() peer_hooks['accounts']['allowed'] = \ [h for h in (ClickReview.app_allowed_peer_hooks + ClickReview.scope_allowed_peer_hooks) if not h.startswith('account-')] peer_hooks['accounts']['required'] = ['apparmor'] peer_hooks['account-provider'] = dict() peer_hooks['account-provider']['required'] = ['account-qml-plugin', 'apparmor' ] peer_hooks['account-provider']['allowed'] = \ peer_hooks['account-provider']['required'] peer_hooks['account-qml-plugin'] = dict() peer_hooks['account-qml-plugin']['required'] = ['account-provider', 'apparmor' ] peer_hooks['account-qml-plugin']['allowed'] = \ peer_hooks['account-qml-plugin']['required'] ClickReview.__init__(self, fn, "online_accounts", peer_hooks=peer_hooks, overrides=overrides, peer_hooks_link="https://wiki.ubuntu.com/SecurityTeam/Specifications/OnlineAccountsConfinement") if not self.is_click and not self.is_snap1: return self.accounts_files = dict() self.accounts = dict() self.account_hooks = ['accounts', 'account-application', 'account-provider', 'account-qml-plugin', 'account-service'] if self.manifest is None: return for app in self.manifest['hooks']: for h in self.account_hooks: if h not in self.manifest['hooks'][app]: # msg("Skipped missing %s hook for '%s'" % (h, app)) continue if not isinstance(self.manifest['hooks'][app][h], str): error("manifest malformed: hooks/%s/%s is not a str" % ( app, h)) (full_fn, parsed) = self._extract_account(app, h) if app not in self.accounts_files: self.accounts_files[app] = dict() self.accounts_files[app][h] = full_fn if app not in self.accounts: self.accounts[app] = dict() self.accounts[app][h] = parsed self.required_keys = dict() self.allowed_keys = dict() self.required_keys["service"] = [ ('provider', str), ] self.allowed_keys["service"] = [ ('auth', dict), ('name', str), ('description', str), ] self.required_keys["plugin"] = [ ('name', str), ('icon', str), ('qml', str), ] self.allowed_keys["plugin"] = [ ('auth', dict), ] self.provider_re = re.compile('^[a-zA-Z0-9_.-]+$') def _extract_account(self, app, account_type): '''Extract accounts''' if self.manifest is None: return a = self.manifest['hooks'][app][account_type] fn = os.path.join(self.unpack_dir, a) bn = os.path.basename(fn) if not os.path.exists(fn): error("Could not find '%s'" % bn) # qml-plugin points to a QML file, so just set that we have the # the hook present for now if account_type == "account-qml-plugin": return (fn, True) elif account_type == "accounts": fh = open_file_read(fn) contents = "" for line in fh.readlines(): contents += line fh.close() try: jd = json.loads(contents) except Exception as e: error("accounts json unparseable: %s (%s):\n%s" % (bn, str(e), contents)) if not isinstance(jd, dict): error("accounts json is malformed: %s:\n%s" % (bn, contents)) return (fn, jd) else: try: tree = etree.parse(fn) xml = tree.getroot() except Exception as e: error("accounts xml unparseable: %s (%s)" % (bn, str(e))) return (fn, xml) def check_hooks_versions(self): '''Check hooks versions''' if not self.is_click and not self.is_snap1: return if self.manifest is None: return framework = self.manifest['framework'] if not framework.startswith("ubuntu-sdk"): return t = "error" if framework < "ubuntu-sdk-15.04.1": for app in sorted(self.accounts.keys()): for hook in self.accounts[app].keys(): if hook == "accounts": n = self._get_check_name('%s_hook' % hook, app=app) s = "'accounts' hook is not available in '%s' (must be 15.04.1 or later)" % \ (framework) self._add_result(t, n, s) return hook_state = "disallowed" if framework < "ubuntu-sdk-16.10": t = "warn" hook_state = "deprecated" for app in sorted(self.accounts.keys()): for hook in self.accounts[app].keys(): if hook.startswith("account-"): n = self._get_check_name('%s_hook' % hook, app=app) s = "'%s' is %s in %s: use 'accounts' hook instead" % \ (hook, hook_state, framework) self._add_result(t, n, s) def _check_object(self, obj_type, obj, n): t = "info" s = "OK" if not isinstance(obj, dict): t = "error" s = "%s is not an object" % obj_type self._add_result(t, n, s) return for (k, vt) in self.required_keys[obj_type]: if k not in obj.keys(): t = "error" s = "required key '%s' is missing" % k self._add_result(t, n, s) if t == "error": return known_keys = self.required_keys[obj_type] + self.allowed_keys[obj_type] for (k, v) in obj.items(): type_list = [kk[1] for kk in known_keys if kk[0] == k] if len(type_list) < 1: t = "error" s = "unrecognized key '%s'" % k self._add_result(t, n, s) continue if not isinstance(v, type_list[0]): t = "error" s = "value for '%s' must be of type %s" % (k, type_list[0]) self._add_result(t, n, s) continue if k == 'provider' and not self.provider_re.match(v): t = "error" s = "'provider' must only consist of alphanumeric characters" self._add_result(t, n, s) self._add_result(t, n, s) def _check_object_list(self, app, key, obj_type, obj_list): t = 'info' n = self._get_check_name('accounts_%s' % key, app=app) if not isinstance(obj_list, list): t = "error" s = "'%s' is not a list" % key elif len(obj_list) < 1: t = "error" s = "'%s' is empty" % key if t == "error": self._add_result(t, n, s) return for (i, obj) in enumerate(obj_list): n = self._get_check_name('accounts_%s' % (obj_type), app=app, extra=str(i)) self._check_object(obj_type, obj, n) def check_manifest(self): '''Check manifest''' if not self.is_click and not self.is_snap1: return for app in sorted(self.accounts.keys()): account_type = "accounts" t = 'info' n = self._get_check_name('%s_root' % account_type, app=app) s = "OK" if account_type not in self.accounts[app]: s = "OK (missing)" self._add_result(t, n, s) continue has_services_or_plugin = False n = self._get_check_name('%s_services' % account_type, app=app) if 'services' in self.accounts[app][account_type]: has_services_or_plugin = True services = self.accounts[app][account_type]['services'] self._check_object_list(app, "services", "service", services) n = self._get_check_name('%s_plugin' % account_type, app=app) if 'plugin' in self.accounts[app][account_type]: has_services_or_plugin = True plugin = self.accounts[app][account_type]['plugin'] self._check_object("plugin", plugin, n) if not has_services_or_plugin: t = "error" s = "either 'services' or 'plugin' key must be present" self._add_result(t, n, s) def check_application(self): '''Check application''' if not self.is_click and not self.is_snap1: return for app in sorted(self.accounts.keys()): account_type = "account-application" t = 'info' n = self._get_check_name('%s_root' % account_type, app=app) s = "OK" if account_type not in self.accounts[app]: s = "OK (missing)" self._add_result(t, n, s) continue root_tag = self.accounts[app][account_type].tag.lower() if root_tag != "application": t = 'error' s = "'%s' is not 'application'" % root_tag self._add_result(t, n, s) t = 'info' n = self._get_check_name('%s_id' % account_type, app=app) s = "OK" if "id" in self.accounts[app][account_type].keys(): t = 'warn' s = "Found 'id' in application tag" self._add_result(t, n, s) t = 'info' n = self._get_check_name('%s_services' % account_type, app=app) s = "OK" if self.accounts[app][account_type].find("services") is None: t = 'error' s = "Could not find '' tag" self._add_result(t, n, s) if t == 'error': continue t = 'info' n = self._get_check_name('%s_service' % account_type, app=app) s = "OK" if self.accounts[app][account_type].find("./services/service") \ is None: t = 'error' s = "Could not find '' tag under " self._add_result(t, n, s) def check_service(self): '''Check service''' if not self.is_click and not self.is_snap1: return for app in sorted(self.accounts.keys()): account_type = "account-service" t = 'info' n = self._get_check_name('%s_root' % account_type, app=app) s = "OK" if account_type not in self.accounts[app]: s = "OK (missing)" self._add_result(t, n, s) continue root_tag = self.accounts[app][account_type].tag.lower() if root_tag != "service": t = 'error' s = "'%s' is not 'service'" % root_tag self._add_result(t, n, s) t = 'info' n = self._get_check_name('%s_id' % account_type, app=app) s = "OK" if "id" in self.accounts[app][account_type].keys(): t = 'warn' s = "Found 'id' in service tag" self._add_result(t, n, s) if t == 'error': continue l = 'http://askubuntu.com/q/697173' for tag in ['name', 'provider']: t = 'info' n = self._get_check_name( '%s_%s' % (account_type, tag), app=app) s = "OK" if self.accounts[app][account_type].find(tag) is None: t = 'error' s = "Could not find '<%s>' tag" % tag self._add_result(t, n, s, l) def check_provider(self): '''Check provider''' if not self.is_click and not self.is_snap1: return for app in sorted(self.accounts.keys()): account_type = "account-provider" t = 'info' n = self._get_check_name(account_type, app=app) s = "OK" manual_review = False if account_type not in self.accounts[app]: s = "OK (missing)" self._add_result(t, n, s) continue self._add_result(t, n, s, manual_review=manual_review) t = 'info' n = self._get_check_name('%s_root' % account_type, app=app) s = "OK" root_tag = self.accounts[app][account_type].tag.lower() if root_tag != "provider": t = 'error' s = "'%s' is not 'provider'" % root_tag self._add_result(t, n, s) t = 'info' n = self._get_check_name('%s_id' % account_type, app=app) s = "OK" if "id" in self.accounts[app][account_type].keys(): t = 'warn' s = "Found 'id' in provider tag" self._add_result(t, n, s) def check_qml_plugin(self): '''Check qml-plugin''' if not self.is_click and not self.is_snap1: return for app in sorted(self.accounts.keys()): account_type = "account-qml-plugin" t = 'info' n = self._get_check_name(account_type, app=app) s = "OK" manual_review = False if account_type not in self.accounts[app]: s = "OK (missing)" self._add_result(t, n, s) continue self._add_result(t, n, s, manual_review=manual_review) click-reviewers-tools-0.44~16.04.1/clickreviews/cr_push_helper.py0000664000000000000000000001274012666350672021602 0ustar '''cr_push_helper.py: click push-helper checks''' # # Copyright (C) 2014-2015 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from __future__ import print_function from clickreviews.cr_common import ClickReview, error, open_file_read import json import os class ClickReviewPushHelper(ClickReview): '''This class represents click lint reviews''' def __init__(self, fn, overrides=None): peer_hooks = dict() my_hook = 'push-helper' peer_hooks[my_hook] = dict() peer_hooks[my_hook]['allowed'] = ['apparmor'] peer_hooks[my_hook]['required'] = ['apparmor'] ClickReview.__init__(self, fn, "push_helper", peer_hooks=peer_hooks, overrides=overrides) if not self.is_click and not self.is_snap1: return self.required_keys = ['exec'] self.optional_keys = ['app_id'] self.push_helper_files = dict() # click-show-files and tests self.push_helper = dict() if self.manifest is None: return for app in self.manifest['hooks']: if 'push-helper' not in self.manifest['hooks'][app]: # msg("Skipped missing push-helper hook for '%s'" % app) continue if not isinstance(self.manifest['hooks'][app]['push-helper'], str): error("manifest malformed: hooks/%s/push-helper is not str" % app) (full_fn, jd) = self._extract_push_helper(app) self.push_helper_files[app] = full_fn self.push_helper[app] = jd def _extract_push_helper(self, app): '''Get push-helper hook content''' c = self.manifest['hooks'][app]['push-helper'] fn = os.path.join(self.unpack_dir, c) bn = os.path.basename(fn) if not os.path.exists(fn): error("Could not find '%s'" % bn) fh = open_file_read(fn) contents = "" for line in fh.readlines(): contents += line fh.close() try: jd = json.loads(contents) except Exception as e: error("push-helper json unparseable: %s (%s):\n%s" % (bn, str(e), contents)) if not isinstance(jd, dict): error("push-helper json is malformed: %s:\n%s" % (bn, contents)) return (fn, jd) def check_valid(self): '''Check validity of push-helper entries''' if not self.is_click and not self.is_snap1: return for app in sorted(self.push_helper): for k in self.push_helper[app].keys(): t = "info" n = self._get_check_name("valid", app=app, extra=k) s = "OK" if not isinstance(self.push_helper[app][k], str): t = "error" s = "'%s' is not a string" % k elif self.push_helper[app][k] == "": t = "error" s = "'%s' is empty" % k self._add_result(t, n, s) for k in self.required_keys: t = "info" n = self._get_check_name("valid_required", app=app, extra=k) s = "OK" if k not in self.push_helper[app]: t = "error" s = "'%s' is missing" % k self._add_result(t, n, s) def check_unknown_keys(self): '''Check unknown''' if not self.is_click and not self.is_snap1: return for app in sorted(self.push_helper): unknown = [] t = "info" n = self._get_check_name("unknown", app=app) s = "OK" for key in self.push_helper[app].keys(): if key not in self.required_keys and \ key not in self.optional_keys: unknown.append(key) if len(unknown) == 1: t = "warn" s = "Unknown field '%s'" % unknown[0] elif len(unknown) > 1: t = "warn" s = "Unknown fields '%s'" % ", ".join(unknown) self._add_result(t, n, s) def check_hooks(self): '''Verify combinations of click hooks with the push-helper hook''' if not self.is_click and not self.is_snap1: return if self.manifest is None: return for app in sorted(self.manifest['hooks']): if app not in self.push_helper: continue t = "info" n = self._get_check_name("other_hooks", app=app) s = "OK" bad = [] for hook in self.manifest['hooks'][app]: # Only the apparmor hook can be used with the push-helper if hook not in ['push-helper', 'apparmor']: bad.append(hook) if len(bad) > 0: t = 'error' s = "Found unusual hooks with push-helper: %s" % ", ".join(bad) self._add_result(t, n, s) click-reviewers-tools-0.44~16.04.1/clickreviews/.cr_security.py.swp0000664000000000000000000004000012750115065021774 0ustar b0VIM 7.4#ÚV÷QvFwjamielily~jamie/bzr-pulls/click-reviewers-tools.trunk/clickreviews/cr_security.py 3210#"! Utp]ÿÿÿÿÿÿÿÿR^þÿÿÿÿÿÿÿO°ýÿÿÿÿÿÿÿYÿüÿÿÿÿÿÿÿRXûÿÿÿÿÿÿÿ[ªúÿÿÿÿÿÿÿ[ùÿÿÿÿÿÿÿ^`øÿÿÿÿÿÿÿV¾÷ÿÿÿÿÿÿÿhöÿÿÿÿÿÿÿS|õÿÿÿÿÿÿÿDÏôÿÿÿÿÿÿÿeóÿÿÿÿÿÿÿ^xòÿÿÿÿÿÿÿYÖñÿÿÿÿÿÿÿX/ðÿÿÿÿÿÿÿJ‡ad§]ÔÒ©§`ßÝ›Zë é ¥ ] \ 6 5  ÷ Û Ù “ h 1 %    å ² † j M ( ö ¦ q : # ö õ Ï © w K  êéŸml8%$ÙŠbH œyB ôϪ„\9 á´Œe4 ⸌e3Û¯zMÞ§¦ 'camera', 'audio', self.allowed_webapp_policy_groups = ['accounts', 'write_path'] 'template_variables', 'read_path', 'binary', self.redflag_fields = ['abstractions', self.required_fields = ['policy_version'] 'name'] 'copyright', 'comment', self.ignored_fields = ['author', 'write_path'] 'template_variables', 'template', 'read_path', 'policy_version', 'policy_vendor', 'policy_groups', 'name', 'copyright', 'comment', 'binary', 'author', self.all_fields = ['abstractions', self.aa_policy = p.policy p = apparmor_policy.ApparmorPolicy(local_copy) local_copy = branch_fn if os.path.exists(branch_fn): '../data/apparmor-easyprof-ubuntu.json') branch_fn = os.path.join(os.path.dirname(__file__), local_copy = None # tree, use it unconditionally. # we are up to date. However, if we are working within the development # If local_copy is None, then this will check the server to see if return if not self.is_click and not self.is_snap1: overrides=overrides) ClickReview.__init__(self, fn, "security", peer_hooks=peer_hooks, peer_hooks[my_hook2]['required'] = [] ClickReview.service_allowed_peer_hooks peer_hooks[my_hook2]['allowed'] = \ # Basically, everything except frameworks peer_hooks[my_hook2] = dict() my_hook2 = 'apparmor-profile' peer_hooks[my_hook]['required'] = [] ['pay-ui'] ClickReview.service_allowed_peer_hooks + \ ClickReview.scope_allowed_peer_hooks + \ peer_hooks[my_hook]['allowed'] = ClickReview.app_allowed_peer_hooks + \ # Basically, everything except frameworks peer_hooks[my_hook] = dict() my_hook = 'apparmor' peer_hooks = dict() def __init__(self, fn, overrides=None): '''This class represents click lint reviews'''class ClickReviewSecurity(ClickReview):import osimport jsonimport copyimport clickreviews.apparmor_policy as apparmor_policyimport clickreviews.cr_common as cr_commonfrom clickreviews.cr_common import ClickReview, error, open_file_read) AA_PROFILE_NAME_ADVLEN, AA_PROFILE_NAME_MAXLEN,from clickreviews.common import (from __future__ import print_function# along with this program. If not, see .# You should have received a copy of the GNU General Public License## GNU General Public License for more details.# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the# but WITHOUT ANY WARRANTY; without even the implied warranty of# This program is distributed in the hope that it will be useful,## the Free Software Foundation; version 3 of the License.# it under the terms of the GNU General Public License as published by# This program is free software: you can redistribute it and/or modify## Copyright (C) 2013-2016 Canonical Ltd.#'''cr_security.py: click security checks'''ad—ÛJ³c?빘—qp9÷ à ° ¯ € J 3 è Ó t $  Ê ¤ £ j & ò ß Þ ¯ y b  ¸ Ÿ O 3 õÏΜk7$#ûÓÒ£mVü¿‡aE÷µk%ýâ“Fù©hÛÚ self._add_result(t, n, s) ) self.click_version) app, self.click_pkgname, "shortening '%s', '%s' and/or '%s'" % (profile, advlen, "problematic in certain environments. Please consider " s = ("'%s' is long (exceeds %d characters) and thus could be " t = 'warn' elif len(profile) > advlen: self.click_version)) self.click_pkgname, app, "'%s', '%s' and/or '%s'" % (profile, maxlen, s = ("'%s' too long (exceeds %d characters). Please shorten " t = 'error' if len(profile) > maxlen: self.click_version) profile = "%s_%s_%s" % (self.click_pkgname, app, s = "OK" n = self._get_check_name('profile_name_length', extra=f) t = 'info' (f, m) = self._get_security_manifest(app) for app in sorted(self.security_apps): advlen = AA_PROFILE_NAME_ADVLEN maxlen = AA_PROFILE_NAME_MAXLEN return if not self.is_click and not self.is_snap1: '''Check AppArmor profile name length''' def check_apparmor_profile_name_length(self): self._add_result(t, n, s) s = "template is not 'ubuntu-account-plugin'" t = 'error' if 'template' not in m or m['template'] != "ubuntu-account-plugin": continue if 'account-qml-plugin' not in self.manifest['hooks'][app]: s = "OK" n = self._get_check_name('template_account_qml_plugin', extra=f) t = 'info' (f, m) = self._get_security_manifest(app) for app in sorted(self.security_apps): return if not self.is_click and not self.is_snap1: '''Check template for online accounts account-qml-plugin''' def check_template_online_accounts_qml_plugin(self): self._add_result(t, n, s) s = "template is not 'ubuntu-account-plugin'" t = 'error' if 'template' not in m or m['template'] != "ubuntu-account-plugin": continue if 'account-provider' not in self.manifest['hooks'][app]: s = "OK" n = self._get_check_name('template_account_provider', extra=f) t = 'info' (f, m) = self._get_security_manifest(app) for app in sorted(self.security_apps): return if not self.is_click and not self.is_snap1: '''Check template for online accounts account-provider''' def check_template_online_accounts_provider(self): # TODO: error if not 'common' continue self._add_result(t, n, s) s = "'apparmor' not found in click manifest for '%s'" % app t = 'error' 'apparmor-profile' not in self.manifest['hooks'][app]: elif 'apparmor' not in self.manifest['hooks'][app] and \click-reviewers-tools-0.44~16.04.1/clickreviews/cr_bin_path.py0000664000000000000000000001707712666350672021060 0ustar '''cr_bin_path.py: click bin-path''' # # Copyright (C) 2014-2015 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from __future__ import print_function from clickreviews.cr_common import ClickReview, error import os class ClickReviewBinPath(ClickReview): '''This class represents click lint reviews''' def __init__(self, fn, overrides=None): # bin-path is ignored by snappy install so don't bother with peerhooks ClickReview.__init__(self, fn, "bin-path", overrides=overrides) self.bin_paths_files = dict() self.bin_paths = dict() if not self.is_snap1: return # snappy yaml currently only allows specifying: # - exec (optional) # - description (optional) self.required_keys = [] self.optional_keys = ['description', 'exec'] + self.snappy_exe_security if self.is_snap1 and 'binaries' in self.pkg_yaml: if len(self.pkg_yaml['binaries']) == 0: error("package.yaml malformed: 'binaries' is empty") for binary in self.pkg_yaml['binaries']: if 'name' not in binary: error("package.yaml malformed: required 'name' not found " "for entry in %s" % self.pkg_yaml['binaries']) elif not isinstance(binary['name'], str): error("package.yaml malformed: required 'name' is not str" "for entry in %s" % self.pkg_yaml['binaries']) app = os.path.basename(binary['name']) if 'exec' in binary: rel = binary['exec'] else: rel = binary['name'] self.bin_paths[app] = rel self.bin_paths_files[app] = self._extract_bin_path(app) def _extract_bin_path(self, app): '''Get bin-path for app''' rel = self.bin_paths[app] fn = os.path.join(self.unpack_dir, rel) if not os.path.exists(fn): error("Could not find '%s'" % rel) return fn def _check_bin_path_executable(self, app): '''Check that the provided path exists''' fn = self.bin_paths_files[app] return os.access(fn, os.X_OK) def _verify_required(self, my_dict, test_str): for app in sorted(my_dict): for r in self.required_keys: found = False t = 'info' n = self._get_check_name( '%s_required_key' % test_str, app=app, extra=r) s = "OK" if r in my_dict[app]: if not isinstance(my_dict[app][r], str): t = 'error' s = "'%s' is not a string" % r elif my_dict[app][r] == "": t = 'error' s = "'%s' is empty" % r else: found = True if not found and t != 'error': t = 'error' s = "Missing required field '%s'" % r self._add_result(t, n, s) def check_snappy_required(self): '''Check for package.yaml required fields''' if not self.is_snap1 or 'binaries' not in self.pkg_yaml: return self._verify_required(self._create_dict(self.pkg_yaml['binaries']), 'package_yaml') def _verify_optional(self, my_dict, test_str): for app in sorted(my_dict): for o in self.optional_keys: if o in self.snappy_exe_security: continue # checked in cr_security.py found = False t = 'info' n = self._get_check_name( '%s_optional_key' % test_str, app=app, extra=o) s = "OK" if o in my_dict[app]: if not isinstance(my_dict[app][o], str): t = 'error' s = "'%s' is not a string" % o elif my_dict[app][o] == "": t = 'error' s = "'%s' is empty" % o else: found = True if not found and t != 'error': s = "OK (skip missing)" self._add_result(t, n, s) def check_snappy_optional(self): '''Check snappy packate.yaml optional fields''' if not self.is_snap1 or 'binaries' not in self.pkg_yaml: return self._verify_optional(self._create_dict(self.pkg_yaml['binaries']), 'package_yaml') def _verify_unknown(self, my_dict, test_str): for app in sorted(my_dict): unknown = [] t = 'info' n = self._get_check_name( '%s_unknown_key' % test_str, app=app) s = "OK" for f in my_dict[app].keys(): if f not in self.required_keys and \ f not in self.optional_keys: unknown.append(f) if len(unknown) == 1: t = 'warn' s = "Unknown field '%s'" % unknown[0] elif len(unknown) > 1: t = 'warn' s = "Unknown fields '%s'" % ", ".join(unknown) self._add_result(t, n, s) def check_snappy_unknown(self): '''Check snappy package.yaml unknown fields''' if not self.is_snap1 or 'binaries' not in self.pkg_yaml: return self._verify_unknown(self._create_dict(self.pkg_yaml['binaries']), 'package_yaml') def check_path(self): '''Check path exists''' if not self.is_snap1: return t = 'info' n = self._get_check_name('path_exists') s = "OK" for app in sorted(self.bin_paths_files): t = 'info' n = self._get_check_name('path_executable') s = "OK" if not self._check_bin_path_executable(app): t = 'error' s = "'%s' is not executable" % \ os.path.relpath(self.bin_paths_files[app], self.unpack_dir) self._add_result(t, n, s) def check_binary_description(self): '''Check package.yaml binary description''' if not self.is_snap1 or 'binaries' not in self.pkg_yaml: return my_dict = self._create_dict(self.pkg_yaml['binaries']) for app in sorted(my_dict): t = 'info' n = self._get_check_name( 'package_yaml_description_present', app=app) s = 'OK' if 'description' not in my_dict[app]: s = 'OK (skip missing)' self._add_result('info', n, s) return self._add_result(t, n, s) t = 'info' n = self._get_check_name( 'package_yaml_description_empty', app=app) s = 'OK' if len(my_dict[app]['description']) == 0: t = 'error' s = "description is empty" self._add_result(t, n, s) click-reviewers-tools-0.44~16.04.1/bin/0000775000000000000000000000000012735271035014270 5ustar click-reviewers-tools-0.44~16.04.1/bin/snap-check-skeleton0000775000000000000000000000161712657004405020057 0ustar #!/usr/bin/python3 '''snap-check-skeleton: perform snap skeleton checks''' # # Copyright (C) 2013-2015 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from __future__ import print_function import clickreviews.common as common import clickreviews.sr_skeleton as sr_skeleton if __name__ == "__main__": common.run_check(sr_skeleton.SnapReviewSkeleton) click-reviewers-tools-0.44~16.04.1/bin/click-check-skeleton0000775000000000000000000000162212656743640020211 0ustar #!/usr/bin/python3 '''click-check-skeleton: perform click skeleton checks''' # # Copyright (C) 2013-2015 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from __future__ import print_function import clickreviews.common as common import clickreviews.cr_skeleton as cr_skeleton if __name__ == "__main__": common.run_check(cr_skeleton.ClickReviewSkeleton) click-reviewers-tools-0.44~16.04.1/bin/click-check-url-dispatcher0000775000000000000000000000166512656743640021322 0ustar #!/usr/bin/python3 '''click-check-url_dispatcher: perform click url dispatcher checks''' # # Copyright (C) 2014-2015 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from __future__ import print_function import clickreviews.common as common import clickreviews.cr_url_dispatcher as cr_url_dispatcher if __name__ == "__main__": common.run_check(cr_url_dispatcher.ClickReviewUrlDispatcher) click-reviewers-tools-0.44~16.04.1/bin/click-check-push-helper0000775000000000000000000000164312656743640020624 0ustar #!/usr/bin/python3 '''click-check-push-helper: perform click push-helper checks''' # # Copyright (C) 2014-2015 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from __future__ import print_function import clickreviews.common as common import clickreviews.cr_push_helper as cr_push_helper if __name__ == "__main__": common.run_check(cr_push_helper.ClickReviewPushHelper) click-reviewers-tools-0.44~16.04.1/bin/unpack-package0000775000000000000000000000051412656743640017100 0ustar #!/usr/bin/python3 import os import sys from clickreviews import common if __name__ == '__main__': if len(sys.argv) != 3: common.error("%s " % os.path.basename(sys.argv[0])) pkg = sys.argv[1] dir = sys.argv[2] common.unpack_pkg(pkg, dir) print("Successfully unpacked to '%s'" % dir) click-reviewers-tools-0.44~16.04.1/bin/click-check-security0000775000000000000000000000161512656743640020236 0ustar #!/usr/bin/python3 '''click-check-security: perform click security checks''' # # Copyright (C) 2013 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from __future__ import print_function import clickreviews.common as common import clickreviews.cr_security as cr_security if __name__ == "__main__": common.run_check(cr_security.ClickReviewSecurity) click-reviewers-tools-0.44~16.04.1/bin/update-apparmor-policy0000775000000000000000000000215412543074466020624 0ustar #!/usr/bin/python3 # # Copyright (C) 2014 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from clickreviews import apparmor_policy import sys def main(filename=None): apparmor_policy.get_policy_file(filename) if __name__ == '__main__': try: filename = None if len(sys.argv) > 2: print("Usage: %s [file]" % sys.argv[0]) sys.exit(1) elif len(sys.argv) == 2: filename = sys.argv[1] main(filename) except KeyboardInterrupt: print('Aborted.', file=sys.stderr) sys.exit(1) click-reviewers-tools-0.44~16.04.1/bin/click-check-framework0000775000000000000000000000163012656743640020361 0ustar #!/usr/bin/python3 '''click-check-framework: perform click framework checks''' # # Copyright (C) 2014-2015 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from __future__ import print_function import clickreviews.common as common import clickreviews.cr_framework as cr_framework if __name__ == "__main__": common.run_check(cr_framework.ClickReviewFramework) click-reviewers-tools-0.44~16.04.1/bin/repack-click0000775000000000000000000000410112656743640016552 0ustar #!/usr/bin/python3 # Repack a click package by using lowlevel tools so as not to change anything # in the package (ie, don't use 'click build .' to avoid updating DEBIAN/, etc) import sys import os from clickreviews import common from debian.deb822 import Deb822 import glob import shutil import tempfile def repack_click(unpack_dir, click_package): '''Repack the click package''' if not os.path.isdir(unpack_dir): common.error("'%s' does not exist" % unpack_dir) if os.path.exists(click_package): common.error("'%s' exists" % click_package) control_fn = os.path.join(unpack_dir, "DEBIAN/control") if not os.path.exists(control_fn): common.error("Could not find '%s'" % control_fn) fh = common.open_file_read(control_fn) tmp = list(Deb822.iter_paragraphs(fh.readlines())) fh.close() if len(tmp) != 1: common.error("malformed control file: too many paragraphs") control = tmp[0] click_fn = "%s_%s_%s.click" % (control['Package'], control['Version'], control['Architecture']) if os.path.basename(click_package) != click_fn: common.warn("'%s' should be '%s'" % (click_package, click_fn)) tmpdir = tempfile.mkdtemp(prefix='clickreview-') curdir = os.getcwd() os.chdir(tmpdir) (rc, out) = common.cmd(['dpkg-deb', '-b', '--nocheck', '-Zgzip', os.path.abspath(unpack_dir), os.path.join(tmpdir, click_fn)]) os.chdir(curdir) if rc != 0: common.recursive_rm(tmpdir) common.error("dpkg-deb -b failed with '%d':\n%s" % (rc, out)) debfile = glob.glob("%s/*.click" % tmpdir)[0] shutil.move(debfile, os.path.abspath(click_package)) common.recursive_rm(tmpdir) if __name__ == '__main__': if len(sys.argv) != 3: common.error("%s " % os.path.basename(sys.argv[0])) dir = sys.argv[1] pkg = sys.argv[2] repack_click(dir, pkg) print("Successfully repacked to '%s'" % pkg) click-reviewers-tools-0.44~16.04.1/bin/click-check-desktop0000775000000000000000000000157612656743640020046 0ustar #!/usr/bin/python3 '''click-check-desktop: perform click desktop checks''' # # Copyright (C) 2013-2015 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from __future__ import print_function from clickreviews import common from clickreviews import cr_desktop if __name__ == "__main__": common.run_check(cr_desktop.ClickReviewDesktop) click-reviewers-tools-0.44~16.04.1/bin/empty-click0000775000000000000000000000301212511545773016440 0ustar #!/usr/bin/python3 import subprocess import tempfile import glob import json import sys import os def create_new_manifest(click_file): try: output = subprocess.check_output(["click", "info", click_file]) except subprocess.CalledProcessError: print >> sys.stderr, "click info %s failed." % click_file sys.exit(1) manifest = json.loads(output.decode()) manifest["version"] = manifest["version"] + "+security" return manifest def create_new_click(manifest): pwd = os.getcwd() directory = tempfile.mkdtemp() os.chdir(directory) with open("manifest.json", "w") as f: f.write(json.dumps(manifest)) subprocess.call(["click", "build", "."]) new_click_file = os.path.abspath(glob.glob("*.click")[0]) subprocess.call(["mv", new_click_file, pwd]) os.chdir(pwd) subprocess.call(["rm", "-r", directory]) return os.path.abspath(os.path.join(pwd, os.path.basename(new_click_file))) def main(): if len(sys.argv) != 2: print("Usage: %s " % sys.argv[0]) sys.exit(1) click_file = sys.argv[1] if not os.path.exists(click_file): print("%s does not exist." % click_file) sys.exit(1) manifest = create_new_manifest(click_file) path = create_new_click(manifest) print("Updated click package generated at: %s" % path) if __name__ == '__main__': try: main() except KeyboardInterrupt: print >> sys.stderr, "Aborted." sys.exit(1) click-reviewers-tools-0.44~16.04.1/bin/click-check-scope0000775000000000000000000000160012656743640017472 0ustar #!/usr/bin/python3 '''click-check-scope: perform click scope checks''' # # Copyright (C) 2014-2015 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from __future__ import print_function import clickreviews.common as common import clickreviews.cr_scope as cr_scope if __name__ == "__main__": common.run_check(cr_scope.ClickReviewScope) click-reviewers-tools-0.44~16.04.1/bin/click-run-checks0000775000000000000000000000240212657004405017337 0ustar #!/bin/sh if [ -z "$1" ]; then echo "Please specific path to click package" exit 1 fi c="$1" if [ ! -f "$c" ]; then echo "Could not find '$c'" exit 1 fi # prefer local script for testing, otherwise use system location CHECKS_BIN_PATH="$(dirname $(readlink -f "$0"))" prefer_local() { path="${CHECKS_BIN_PATH}/$1" if [ -x $path ]; then "$path" "$2" || set_rc "$?" return fi "$(which $1)" "$2" || set_rc "$?" } rc="0" set_rc() { # return worst offending rc if [ "$1" = "1" ]; then if [ "$rc" != "2" ]; then rc="$1" fi elif [ "$1" = "2" ]; then rc="$1" fi } prefer_local click-show-files "$c" CHECKS=$(ls ${CHECKS_BIN_PATH}/click-check-* ${CHECKS_BIN_PATH}/snap-check-* | egrep -v '(click-check-skeleton|click-check-lint|snap-check-skeleton)') CHECKS="click-check-lint snap-check-lint ${CHECKS}" for check_path in ${CHECKS}; do check=$(basename ${check_path} ${CHECKS_BIN_PATH}) echo "" echo "=" $check "=" prefer_local $check "$c" done echo "" echo "" if [ "$rc" = "1" ]; then echo "** Warnings found **" elif [ "$rc" = "2" ]; then echo "** Errors found **" fi echo -n "$c: " if [ "$rc" = "0" ]; then echo "pass" else echo "FAIL" fi exit $rc click-reviewers-tools-0.44~16.04.1/bin/click-show-files0000775000000000000000000001524612657004405017367 0ustar #!/usr/bin/python3 '''check-show-files: show files''' # # Copyright (C) 2014-2015 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from __future__ import print_function import os import sys from clickreviews import common from clickreviews import cr_desktop from clickreviews import cr_lint from clickreviews import cr_security from clickreviews import cr_url_dispatcher from clickreviews import cr_scope from clickreviews import cr_content_hub from clickreviews import cr_online_accounts from clickreviews import cr_push_helper from clickreviews import cr_bin_path from clickreviews import cr_framework from clickreviews import cr_systemd # This script just dumps important files to stdout if __name__ == "__main__": if len(sys.argv) < 2: common.error("Must give path to package") review = cr_lint.ClickReviewLint(sys.argv[1]) fn = os.path.join(review.unpack_dir, "meta", "snap.yaml") if os.path.exists(fn): # just show snap.yaml for snap v2+ snaps print("= %s =" % os.path.basename(fn)) fh = common.open_file_read(fn) for line in fh.readlines(): print(line, end="") fh.close() print("") sys.exit(0) for i in sorted(review.control_files): fh = common.open_file_read(review.control_files[i]) print("= %s =" % os.path.basename(i)) for line in fh.readlines(): print(line, end="") fh.close() print("") fn = os.path.join(review.unpack_dir, "meta", "package.yaml") if os.path.exists(fn): print("= %s =" % os.path.basename(fn)) fh = common.open_file_read(fn) for line in fh.readlines(): print(line, end="") fh.close() print("") print("= hooks =") review_content_hub = cr_content_hub.ClickReviewContentHub(sys.argv[1]) for app in sorted(review_content_hub.content_hub_files): f = review_content_hub.content_hub_files[app] fh = common.open_file_read(os.path.join( review_content_hub.unpack_dir, f)) print("== content_hub: %s ==" % os.path.basename(f)) for line in fh.readlines(): print(line, end="") fh.close() print("") review_desktop = cr_desktop.ClickReviewDesktop(sys.argv[1]) for app in sorted(review_desktop.desktop_files): f = review_desktop.desktop_files[app] fh = common.open_file_read(os.path.join(review_desktop.unpack_dir, f)) print("== desktop: %s ==" % os.path.basename(f)) for line in fh.readlines(): print(line, end="") fh.close() print("") review_accounts = cr_online_accounts.ClickReviewAccounts(sys.argv[1]) for app in sorted(review_accounts.accounts_files): for account_type in review_accounts.account_hooks: if account_type not in review_accounts.accounts_files[app]: continue f = review_accounts.accounts_files[app][account_type] fh = common.open_file_read(os.path.join( review_accounts.unpack_dir, f)) print("== online %s: %s ==" % (account_type, os.path.basename(f))) for line in fh.readlines(): print(line, end="") fh.close() print("") review_push_helper = cr_push_helper.ClickReviewPushHelper(sys.argv[1]) for app in sorted(review_push_helper.push_helper_files): f = review_push_helper.push_helper_files[app] fh = common.open_file_read(os.path.join( review_push_helper.unpack_dir, f)) print("== push_helper: %s ==" % os.path.basename(f)) for line in fh.readlines(): print(line, end="") fh.close() print("") review_scope = cr_scope.ClickReviewScope(sys.argv[1]) for app in sorted(review_scope.scopes): f = review_scope.scopes[app]["ini_file"] fh = common.open_file_read(os.path.join(review_scope.unpack_dir, f)) print("== scope .INI: %s ==" % os.path.basename(f)) for line in fh.readlines(): print(line, end="") fh.close() print("") review_framework = cr_framework.ClickReviewFramework(sys.argv[1]) for app in sorted(review_framework.frameworks_file): f = os.path.join(review_framework.unpack_dir, review_framework.frameworks_file[app]) fh = common.open_file_read(os.path.join(review_framework.unpack_dir, f)) print("== click .framework: %s ==" % os.path.basename(f)) for line in fh.readlines(): print(line, end="") fh.close() print("") review_bin_path = cr_bin_path.ClickReviewBinPath(sys.argv[1]) for app in sorted(review_bin_path.bin_paths): f = os.path.join(review_bin_path.unpack_dir, review_bin_path.bin_paths[app]) print("== bin_path: %s ==" % os.path.relpath(f, review_bin_path.unpack_dir)) print("") review_apparmor = cr_security.ClickReviewSecurity(sys.argv[1]) for f in sorted(review_apparmor.security_manifests): fh = common.open_file_read(os.path.join(review_apparmor.unpack_dir, f)) print("== security: %s ==" % os.path.basename(f)) for line in fh.readlines(): print(line, end="") fh.close() print("") review_systemd = cr_systemd.ClickReviewSystemd(sys.argv[1]) for app in sorted(review_systemd.systemd_files): f = review_systemd.systemd_files[app] fh = common.open_file_read(os.path.join(review_systemd.unpack_dir, f)) print("== systemd: %s ==" % os.path.basename(f)) for line in fh.readlines(): print(line, end="") fh.close() print("") review_url_dispatcher = cr_url_dispatcher.ClickReviewUrlDispatcher(sys.argv[1]) for app in sorted(review_url_dispatcher.url_dispatcher_files): f = review_url_dispatcher.url_dispatcher_files[app] fh = common.open_file_read(os.path.join(review_url_dispatcher.unpack_dir, f)) print("== url_dispatcher: %s ==" % os.path.basename(f)) for line in fh.readlines(): print(line, end="") fh.close() print("") # Cleanup our unpack directory common.cleanup_unpack() click-reviewers-tools-0.44~16.04.1/bin/click-check-bin-path0000775000000000000000000000162112656743640020066 0ustar #!/usr/bin/python3 '''click-check-bin-path: perform click bin-path checks''' # # Copyright (C) 2014-2015 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from __future__ import print_function import clickreviews.common as common import clickreviews.cr_bin_path as cr_bin_path if __name__ == "__main__": common.run_check(cr_bin_path.ClickReviewBinPath) click-reviewers-tools-0.44~16.04.1/bin/snap-check-security0000775000000000000000000000161712662664277020121 0ustar #!/usr/bin/python3 '''snap-check-security: perform snap security checks''' # # Copyright (C) 2013-2016 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from __future__ import print_function import clickreviews.common as common import clickreviews.sr_security as sr_security if __name__ == "__main__": common.run_check(sr_security.SnapReviewSecurity) click-reviewers-tools-0.44~16.04.1/bin/click-check-systemd0000775000000000000000000000161412656743640020056 0ustar #!/usr/bin/python3 '''click-check-systemd: perform click systemd checks''' # # Copyright (C) 2013-2015 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from __future__ import print_function import clickreviews.common as common import clickreviews.cr_systemd as cr_systemd if __name__ == "__main__": common.run_check(cr_systemd.ClickReviewSystemd) click-reviewers-tools-0.44~16.04.1/bin/download-click0000775000000000000000000001000612656743640017115 0ustar #!/usr/bin/python3 '''download-click: download click apps''' # # Copyright (C) 2013 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import urllib.request import optparse import os import shutil import simplejson import sys import time from clickreviews import common top_url = "https://search.apps.ubuntu.com" def get_store_json(url): '''Download json from the store and turn it into a python obj''' req = urllib.request.Request(url) opener = urllib.request.build_opener() f = opener.open(req) obj = simplejson.load(f) return obj def download(entry, download_dir=None): '''Download a click app''' if 'resource_url' not in entry: common.error("Could not find 'resource_url' in:\n%s" % entry) resource_url = top_url + entry['resource_url'] res = get_store_json(resource_url) if 'download_url' not in res: common.error("Could not find 'download_url' in:\n%s" % res) download_url = res['download_url'] if download_dir is None: download_dir = os.getcwd() elif not os.path.exists(download_dir): os.mkdir(download_dir) fn = os.path.join(download_dir, download_url.split('/')[-1]) if os.path.exists(fn): common.warn("'%s' already exists, skipping" % os.path.basename(fn)) return True common.msg("Downloading %s" % os.path.basename(fn)) # FIXME: on 2013-10-15 this will break url = download_url + "?noauth=1" common.msg("-- Downloading %s" % url) # attempt to deal with intermittent failures count = 0 max_tries = 10 result = False err_str = "" while not result and count < max_tries: try: (tmp, headers) = urllib.request.urlretrieve(url) shutil.move(tmp, fn) result = True common.msg("-- Success!") except urllib.error.HTTPError as error: err_str = "-- urlretrieve() failed: %d: %s" % (error.code, error.reason) count += 1 time.sleep(5) if not result: common.warn("%s (tried %d times)" % (err_str, max_tries)) return result if __name__ == "__main__": parser = optparse.OptionParser() parser.add_option("--all", help="Download all published apps", action='store_true', default=False) parser.add_option("-d", "--download-dir", dest="download_dir", help="Specifiy download directory", metavar="DIR", default=None) (opt, args) = parser.parse_args() if not opt.all and len(args) < 1: common.error("%s --all|" % os.path.basename(sys.argv[0])) url = top_url + "/api/v1/search?q=" items = get_store_json(url) if not isinstance(items, list): common.error("Didn't get valid result from: %s" % url) errors = False if opt.all: for entry in items: if not download(entry, opt.download_dir): errors = True else: for pkgname in args: entry = None for i in items: if i['name'] == pkgname: entry = i break if not entry: common.warn("Could not find '%s', skipping" % pkgname) continue if not download(entry, opt.download_dir): errors = True if errors: common.error("problem downloading") click-reviewers-tools-0.44~16.04.1/bin/snap-check-lint0000775000000000000000000000156712660650204017203 0ustar #!/usr/bin/python3 '''snap-check-lint: perform snap lint checks''' # # Copyright (C) 2013-2016 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from __future__ import print_function import clickreviews.common as common import clickreviews.sr_lint as sr_lint if __name__ == "__main__": common.run_check(sr_lint.SnapReviewLint) click-reviewers-tools-0.44~16.04.1/bin/click-check-functional0000775000000000000000000000165212656743640020532 0ustar #!/usr/bin/python3 '''click-check-functional: perform functional checks on click packages''' # # Copyright (C) 2013-2015 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from __future__ import print_function import clickreviews.common as common import clickreviews.cr_functional as cr_functional if __name__ == "__main__": common.run_check(cr_functional.ClickReviewFunctional) click-reviewers-tools-0.44~16.04.1/bin/update-frameworks0000775000000000000000000000214612511545773017666 0ustar #!/usr/bin/python3 # # Copyright (C) 2014 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from clickreviews import frameworks import sys def main(filename=None): frameworks.get_frameworks_file(filename) if __name__ == '__main__': try: filename = None if len(sys.argv) > 2: print("Usage: %s [file]" % sys.argv[0]) sys.exit(1) elif len(sys.argv) == 2: filename = sys.argv[1] main(filename) except KeyboardInterrupt: print('Aborted.', file=sys.stderr) sys.exit(1) click-reviewers-tools-0.44~16.04.1/bin/detect-package0000775000000000000000000000044012656743640017065 0ustar #!/usr/bin/python3 import os import sys from clickreviews import common if __name__ == '__main__': if len(sys.argv) != 2: common.error("%s " % os.path.basename(sys.argv[0])) pkg = sys.argv[1] (t, v) = common.detect_package(pkg) print ("%s\t%d" % (t, v)) click-reviewers-tools-0.44~16.04.1/bin/click-check-online-accounts0000775000000000000000000000166512656743640021475 0ustar #!/usr/bin/python3 '''click-check-online-accounts: perform click online accounts checks''' # # Copyright (C) 2014-2015 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from __future__ import print_function import clickreviews.common as common import clickreviews.cr_online_accounts as cr_online_accounts if __name__ == "__main__": common.run_check(cr_online_accounts.ClickReviewAccounts) click-reviewers-tools-0.44~16.04.1/bin/click-review0000775000000000000000000001312612735267057016616 0ustar #!/usr/bin/python3 from clickreviews import modules import argparse import json import os import sys import textwrap def print_findings(results, description): ''' Print a summary of the issues found. ''' if not description or not results: return '' print(description) print(''.center(len(description), '-')) for key in sorted(results.keys()): print(' - %s' % key) print('\t%s' % results[key]['text']) if 'link' in results[key]: print('\t%s' % results[key]['link']) class Results(object): results = {} errors = {} warnings = {} info = {} rc = 0 def __init__(self, args): self.args = args self.pkg_fn = self.args.filename self.modules = modules.get_modules() def _sumarise_results(self): for module in self.results: for key in self.results[module]['error']: self.errors[key] = self.results[module]['error'][key] for key in self.results[module]['warn']: self.warnings[key] = self.results[module]['warn'][key] if self.args.verbose: for key in self.results[module]['info']: self.info[key] = self.results[module]['info'][key] def _complete_report(self): self._sumarise_results() if self.args.json: print(json.dumps(self.results, sort_keys=True, indent=2, separators=(',', ': '))) else: print_findings(self.errors, 'Errors') print_findings(self.warnings, 'Warnings') if self.args.verbose: print_findings(self.info, 'Info') if self.rc == 1: print('%s: RUNTIME ERROR' % self.args.filename) elif self.warnings or self.errors: print('%s: FAIL' % self.args.filename) else: print('%s: pass' % self.args.filename) if self.rc == 1: # always exit(1) if there are errors pass elif self.errors: self.rc = 2 elif self.warnings: self.rc = 3 def _report_module(self, section): ''' This is currently only used in the --sdk option. It will print the output for each section when it's available. This will prevent the SDK from having to wait until all checks have been run. ''' output = self.results[section] print('= %s =' % section) print(json.dumps(output, sort_keys=True, indent=2, separators=(',', ': '))) if output['error'] or output['warn']: self.rc = 1 def _run_module_checks(self, module, overrides): # What we are doing here is basically what all the # ./bin/click-check-* scripts do as well, so for # example something like: # # review = cr_push_helper.ClickReviewPushHelper(sys.argv[1]) # review.do_checks() # rc = review.do_report() # section = module.replace('cr_', 'click,snap.v1_') section = section.replace('sr_', 'snap.v2_') try: review = modules.init_main_class(module, self.pkg_fn, overrides=overrides) if review: review.do_checks() self.results[section] = review.click_report return section except Exception: self.rc = 1 return None def run_all_checks(self, overrides): if self.args.sdk: for module in self.modules: section = self._run_module_checks(module, overrides) if section: self._report_module(section) else: for module in self.modules: self._run_module_checks(module, overrides) self._complete_report() def main(): parser = argparse.ArgumentParser( prog='click-review', formatter_class=argparse.RawDescriptionHelpFormatter, description='Check a click or snap package for errors', epilog=textwrap.dedent('''\ RETURN CODES 0 found no errors or warnings 1 checks not run due to fatal error 2 found errors and/or warnings 3 found warnings ''')) parser.add_argument('filename', type=str, help='file to be inspected') parser.add_argument('overrides', type=str, nargs='?', help='overrides to apply (eg, framework, security ' 'policies, etc)', default=None) parser.add_argument('-v', '--verbose', help='increase output verbosity', action='store_true') parser.add_argument('--json', help='print json output', action='store_true') parser.add_argument('--sdk', help='use output format suitable for the Ubuntu SDK', action='store_true') args = parser.parse_args() if not os.path.exists(args.filename): print(".click file '%s' does not exist." % args.filename) sys.exit(1) results = Results(args) if not results.modules: print("No 'clickreviews' modules found.") sys.exit(1) overrides = None if args.overrides: overrides = json.loads(args.overrides) results.run_all_checks(overrides) sys.exit(results.rc) if __name__ == '__main__': try: main() except KeyboardInterrupt: print("Aborted.") sys.exit(1) click-reviewers-tools-0.44~16.04.1/bin/click-check-content-hub0000775000000000000000000000164312656743640020616 0ustar #!/usr/bin/python3 '''click-check-content-hub: perform click content-hub checks''' # # Copyright (C) 2014-2015 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from __future__ import print_function import clickreviews.common as common import clickreviews.cr_content_hub as cr_content_hub if __name__ == "__main__": common.run_check(cr_content_hub.ClickReviewContentHub) click-reviewers-tools-0.44~16.04.1/bin/click-check-lint0000775000000000000000000000156512656743640017341 0ustar #!/usr/bin/python3 '''click-check-lint: perform click lint checks''' # # Copyright (C) 2013 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . from __future__ import print_function import clickreviews.common as common import clickreviews.cr_lint as cr_lint if __name__ == "__main__": common.run_check(cr_lint.ClickReviewLint) click-reviewers-tools-0.44~16.04.1/debian/0000775000000000000000000000000012750123421014732 5ustar click-reviewers-tools-0.44~16.04.1/debian/copyright0000664000000000000000000000163412511545773016706 0ustar Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: click-reviewers-tools Upstream-Contact: Ubuntu App Developers Source: https://launchpad.net/click-reviewers-tools Files: * Copyright: 2013 Canonical Ltd. License: GPL-3 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. . This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. . On Debian systems, the complete text of the GNU General Public License version 3 can be found in the /usr/share/common-licenses/GPL-3 file. click-reviewers-tools-0.44~16.04.1/debian/control0000664000000000000000000000232312674263545016356 0ustar Source: click-reviewers-tools Section: devel Priority: optional Maintainer: Ubuntu Appstore Developers Build-Depends: debhelper (>= 9~), pep8, pyflakes3, python3-all (>= 3.2~), python3-apt, python3-debian, python3-lxml, python3-magic, python3-setuptools, python3-simplejson, python3-xdg, python3-yaml, squashfs-tools Standards-Version: 3.9.5 Homepage: https://launchpad.net/click-reviewers-tools Vcs-Bzr: lp:click-reviewers-tools Vcs-Browser: http://bazaar.launchpad.net/~click-reviewers/click-reviewers-tools/trunk/files X-Python3-Version: >= 3.2 Package: click-reviewers-tools Architecture: all Depends: binutils, python3-apt, python3-debian, python3-lxml, python3-magic, python3-simplejson, python3-xdg, python3-yaml, ${misc:Depends}, ${python3:Depends}, squashfs-tools Description: tools to review click packages These scripts can be used to review click packages both manually and in a programmatic fashion. click-reviewers-tools-0.44~16.04.1/debian/changelog0000664000000000000000000010005612750121643016612 0ustar click-reviewers-tools (0.44~16.04.1) xenial-proposed; urgency=medium [ Jamie Strandboge ] * data/apparmor-easyprof-ubuntu.json: - add pulseaudio interface - add bluetooth policy group for Touch for 15.04 and higher - add location-observe and location-control - move all core interfaces to 'common' - add gsettings interface - set home to auto-approve - add mpris interface - add camera interface - add optical-drive interface - add serial-port interface - add content interface * clickreviews/common.py: - don't fail on libmvec.so since it comes from libc6 too (LP: #1584346) - extend the regex to also match ld-linux-x86-64.so.2 * sr_common.py: - update _verify_pkgname() and _verify_appname() to use the same regex as in snapd and various tests - update attributes to be slots or plugs side and cleanup code for specifying attributes * bin/click-review, clickreviews/modules.py: exit '1' if error with init_object or running checks * sr_lint.py: - support 'environment' key in yaml (LP: #1583259) - support 'confinement' key in yaml * sr_security.py: - specifying mpris slot should not warn - adjust profile name length checks to use series 16 security label format * run_tests: exit non-zero with failures, errors or unexpectedSuccesses * cr_lint.py: - 'puritine' is a known, but redflagged hook - skip external symlinks and md5sums checks for puritine (since we expect external symlinks and the hash checks fail on broken symlinks) - 'puritine' hook should not be used with 'apparmor' * clickreviews/apparmor_policy.py: adjust for rename of store team (LP: #1608943) [ Celso Providelo ] * support for interface abbreviated syntax (LP: #1595184) -- Jamie Strandboge Tue, 02 Aug 2016 08:43:31 -0500 click-reviewers-tools (0.43) yakkety; urgency=medium [ Jamie Strandboge ] * sr_lint.py: - kernel snaps may have external symlinks - handle top-level plugs and slots with yaml data as 'null' (LP: #1579201) - add epoch checks (LP: #1583298) - .pyc are arch-independent, so don't complain about them - add confinement checks (LP: #1580819) * data/apparmor-easyprof-ubuntu.json: - add opengl interface as 'common' (LP: #1572140) - add reserved bluez, network-manager and location-observe interfaces * sr_security.py: - remove last reference to 'cap' - turn resquash test into info for now until the squashfs-tools bugs are fixed and this is a reliable check * when 'confinement' is 'devmode', override the result type to 'info' - common.py: add override_result_type to allow in support of 'confinement' overrides - sr_common.py: add _devmode_override() - sr_security.py: use override_result_type if in devmode -- Jamie Strandboge Fri, 20 May 2016 16:01:16 -0500 click-reviewers-tools (0.42) xenial; urgency=medium * add test to verify snapd-control is reserved * sr_lint.py: implement interface slots checks * sr_security.py: implement interface slots checks (force manual review for now when specifying slots) * debian/links: create snap-review symlink to click-review -- Jamie Strandboge Thu, 21 Apr 2016 09:10:27 -0500 click-reviewers-tools (0.41) xenial; urgency=medium * sr_lint.py: verify key name in the apps dictionary (LP: #1570914) -- Jamie Strandboge Fri, 15 Apr 2016 10:24:17 -0500 click-reviewers-tools (0.40) xenial; urgency=medium * sr_lint.py: ppc64el and s390x are valid architectures * add bool-file interface tests * remove obsoleted old-security interface checks * properly handle app 'plugs' without toplevel 'plugs' (LP: #1569226) * implement native plugs and app plugs checks -- Jamie Strandboge Wed, 13 Apr 2016 15:19:47 -0500 click-reviewers-tools (0.39) xenial; urgency=medium * bin/click-review: - exit 1 if fatal error, 2 if found errors/warnings and 3 if found warnings (LP: #1523255) - make help output more verbose (including return codes) - add overrides as optional positional argument * [cs]r_lint.py: change the order of the checks so that an obsolete or deprecated in the override takes precedence over 'available' * migrate from skills to interfaces (LP: #1549427) * sr_security.py: - improve requash failure error message - short-circuit if squashfs has symlinks (LP: 1555305) - os snap is not built with -all-root, ignore checksum mismatch * cr_security.py: webapps may now use camera, microphone and connectivity * sr_lint.py: - adjust for stop -> stop-command and poststop -> post-stop-command - make unknown field warning for apps more clear - normalize path specified in snap.yaml for command * remove type framework, frameworks and bus-name checks since frameworks aren't supported on 16.04 (LP: #1557126) * debian/control: Build-Depends on pyflakes3 instead of pyflakes -- Jamie Strandboge Tue, 22 Mar 2016 10:37:09 -0500 click-reviewers-tools (0.38) xenial; urgency=medium * sr_security.py: add check_squashfs_resquash() -- Jamie Strandboge Fri, 26 Feb 2016 08:41:28 -0600 click-reviewers-tools (0.37) xenial; urgency=medium [ Jamie Strandboge ] * cr_lint.py: 'accounts' hook was added in 15.04.1. The other checks are already in place since r553 which missed this addition * refactor and make less click-centric such that click and snap v1 tests use existing scripts and snap v2 will use new scripts. The cr_* tests have 16.04 checks removed (since this simplifies them and this code won't be run any way) * add bin/detect-package and detect_package() * rename unpack-click as unpack-package * add snap v2 lint checks (LP: #1532842) * add snap v2 security checks * squashfs snaps no longer require manual review * debian/control: bump squashfs-tools to Depends and add to Build-Depends [ James Tait ] * cr_lint.py: Don't check for the presence of readme.md if the package is a squashfs filesystem. Snappy 2.0 uses squashfs as its file format, and doesn't require readme.md. -- Jamie Strandboge Mon, 22 Feb 2016 16:41:14 -0600 click-reviewers-tools (0.36) xenial; urgency=medium [ Daniel Holbach ] * Add check if suspected (using python-magic) compiled binaries aren't actually just message catalogs (.mo files) (LP: #1530894). [ Martin Albisetti ] * add gadget type [ Michael Vogt ] * Merge partial support for snap.yaml in 16.04 -- Jamie Strandboge Mon, 01 Feb 2016 11:37:35 -0600 click-reviewers-tools (0.35.1) xenial; urgency=medium * No change rebuild for newer python3 -- Jamie Strandboge Mon, 01 Feb 2016 10:07:35 -0600 click-reviewers-tools (0.35) xenial; urgency=medium [ Jamie Strandboge ] * clickreviews/cr_systemd.py: - add checks for listen-stream, socket, socket-user and socket-group - remove vendor checks with bus-name (LP: #1510522) * clickreviews/cr_security.py: - make sure that the generated profile name is under the current 253 character maximum. This might have to be adjusted after the AppArmor stacking work is completed (LP: #1499544) - adjust for xenial snappy defaulting to using 'network-client' instead of 'networking' - use 'NEEDS REVIEW' instead of 'MANUAL REVIEW' * clickreviews/cr_lint.py: - check if package ships .click directory - add a few more vcs files - remove vendor-specific checks. 'vendor' is still allowed for compatibility with older snappy versions, but no formatting checks are performed (LP: #1510522) - 'Maintainer' checks in the click manifest should only be done with click packages (LP: #1510522) - don't prompt manual review when find .excludes file - add kernel and os as valid snap types - remove package filename checks. They were meaningless and hard to maintain - sort unknown snappy yaml keys - use 'NEEDS REVIEW' instead of 'MANUAL REVIEW' * clickreviews/cr_common.py: - add valid yaml keys for kernel snaps - add a couple more mime types for detecting binaries (useful for arm kernels) * update data/apparmor-easyprof-ubuntu.json for 16.04 policy * Makefile: add json syntax check * several changes for squashfs snaps that won't have a click manifest, etc. Importantly, this means that only package.yaml is looked at and a lot of click specific tests can be skipped - cr_common.py: + rename a few variable to not be click specific + add self.pkgfmt + adjust __init__() to conditionally use package.yaml on squashfs, otherwise click manifest + make click data structure initialization conditional on if click or not (eg, don't run hooks code on squashfs images) - adjust clickreviews/cr_* to conditionally run certain click-only tests on click packages - adjust architecture checks to use self.pkg_arch and rename control_architecture_specified_needed as architecture_specified_needed - cr_security.py: + revamp to use package.yaml on non-click instead of now nonexistent security manifest + update push-helper template test to not make hooks specific + network-client should not be allowed with push helpers either + conditionally look for INSTALL_DIR on 16.04 systems in security-policy + adjust security-override checks on 16.04 to follow 16.04 yaml + make click manifest checks conditional on if click - cr_tests.py: mock _pkgfmt_type(), _pkgfmt_version() and _is_squashfs() [ Michael Nelson ] * add support for non-mocked tests [ Michael Vogt ] * add support for squashfs snaps (currently will trigger manual review) [ Daniel Holbach ] * Pass absolute path of click or snap file - that way it's safe even if we chdir (LP: #1514346). * Allow translated scope .ini fields to have 3 letters as their lang_code identifier, ie. 'ast'. (LP: #1517017) * Ensure "urls" is not empty (LP: #1522777) [ James Tait ] * Add a handful of links to askubuntu questions to explain some of the rejection messages. [ Alberto Mardegan ] * Allow "accounts" hook since the 15.04.1 framework * Online Accounts: update to latest plugin hook format (LP: #1520605) [ Marcus Tomlinson ] * Forbid the internal "DebugMode" scope.ini key from making its way into the store (LP: #1511063) -- Jamie Strandboge Mon, 14 Dec 2015 16:09:52 -0600 click-reviewers-tools (0.34) wily; urgency=medium [ Jamie Strandboge ] * multiple 'desktop' hooks should only be 'info' these days (LP: #1496402) * verify snaps that use 'bus-name' are of 'type: framework' * clickreviews/cr_lint.py: - snappy package.yaml defaults to 'architectures' and 'architecture' is deprecated. Adjust and add a warning for deprecation. - arm64 is a valid architecture now - don't warn on libc6 libraries with check_external_symlinks - don't traceback on broken symlinks when checking for hardcoded paths (LP: #1502962) * clickreviews/cr_security.py: don't complain about missing AppArmor template vars if we detect this is unconfined boilerplate policy -- Jamie Strandboge Fri, 09 Oct 2015 17:47:39 -0500 click-reviewers-tools (0.33) wily; urgency=medium [ Alberto Mardegan ] * clickreviews/cr_online_accounts.py: Do not check for "type" element in OA .service files * clickreviews/cr_online_accounts.py: Support the new "accounts" hook [ Jamie Strandboge ] * clickreviews/cr_common.py: add peer_hooks_link to __init__ and use it as the link for missing and disallowed hooks * clickreviews/cr_online_accounts.py: set peer_hooks_link to use https://wiki.ubuntu.com/SecurityTeam/Specifications/OnlineAccountsConfinement * clickreviews/tests/test_cr_online_accounts.py: don't stub or check for "type" element in OA .service files * Makefile: make sure check-names.list is up to date via 'make check' [ Ricardo Kirkner ] * Refactor to abstract check name generation. This will be used in a follow up branch to normalize check names in a way that allows extracting semantic meaning of check names from review results data. * build name from review_type, prefix, app and extra parts using : as separator * list all possible check types by running tests and extracting seen check names [ Daniel Holbach ] * Fix pep8 issues. -- Jamie Strandboge Thu, 10 Sep 2015 11:17:00 -0500 click-reviewers-tools (0.32) wily; urgency=medium * data/apparmor-easyprof-ubuntu.json: add "keep-display-on" to ubuntu common policy * cr_security.py: - webapps may use "keep-display-on" - error if security-policy specified in snaps * cr_lint.py: - give link to frameworks guide if framework specified - do not error if apparmor-profile specified with snap (handled by above change) -- Jamie Strandboge Thu, 09 Jul 2015 08:57:26 -0500 click-reviewers-tools (0.31) wily; urgency=medium * cr_security.py: - webview is not required with ubuntu-account-plugin - bin-path and systemd hooks shouldn't be used any more to ascertain if an app is a service or binary since snappy build is no longer adding them and snappy install ignores them (LP: #1472296) * cr_common.py: comment that snappy-systemd hook is deprecated * cr_lint.py: comment that snappy-systemd hook is deprecated * cr_systemd.py: - directly parse package.yaml instead of parsing deprecated snappy-systemd hook - remove snappy-systemd hook checks now that it is ignored by snappy install in stable releases * cr_bin_path.py: remove bin-path hook checks now that it is ignored by snappy install in stable releases -- Jamie Strandboge Tue, 07 Jul 2015 15:11:15 -0500 click-reviewers-tools (0.30) wily; urgency=medium * cr_security.py: verify required and allowed policy groups with the ubuntu-account-plugin template (LP: #1468792) * cr_systemd.py: whitespace pep8 fixes for trusty to fix FTBFS in SDK staging ppa -- Jamie Strandboge Fri, 26 Jun 2015 09:27:09 -0500 click-reviewers-tools (0.29) wily; urgency=medium * README: add notes on where to upload review tools to keep projects in sync * cr_online_accounts.py: account-provider and account-qml-plugin can now be allowed if used with apparmor (LP: #1219644) * cr_security.py: - verify when account-provider and account-qml-plugin are used that the security manifest uses the "ubuntu-account-plugin" template - correctly update the cached json if needed * apparmor_policy.py: fix bug that prevented get_policy_file() from working -- Jamie Strandboge Thu, 25 Jun 2015 17:54:21 -0500 click-reviewers-tools (0.28) wily; urgency=medium [ Jamie Strandboge ] * Makefile: perform run-pyflakes in check target * cr_systemd.py: add bus-name checks and update testsuite * add security yaml checks * cr_lint.py: don't allow same key in 'binaries' and 'services' * cr_lint.py: implement hashes.yaml checks * update README * cr_desktop.py: add check to help transition away from obsoleted ubuntu-html5-app-launcher * cr_common.py: remove snappy 'integration' checks * cr_systemd.py: implement ports checks * cr_systemd.py, cr_bin_path.py: error out if services or binaries is empty, repectively * cr_lint.py: update pkgname checks for snaps-- shouldn't have '.' in the name * cr_lint.py: add snappy-config checks * cr_lint.py: maintainer isn't needed in compat click manifest for snaps that don't specify vendor * debian/control: Depends on binutils (for 'ar') [ Marcus Tomlinson ] * cr_scope.py: add "keywords" to the list of optional scope .ini keys -- Jamie Strandboge Wed, 10 Jun 2015 16:07:33 -0500 click-reviewers-tools (0.27) wily; urgency=medium * cr_security.py: add ubuntu-sdk-15.04 framework and policy version (LP: #1449368) -- Jamie Strandboge Fri, 01 May 2015 10:46:57 -0500 click-reviewers-tools (0.26) vivid; urgency=medium [ Jamie Strandboge ] * adjust tests for new format for systemd stop-timeout * add network-service and network-client policygroups for 15.04 policy * require existence of hashes.yaml for snaps. More checks coming * skip security yaml checks in bin-path and systemd checks * cr_security.py: handle multiple frameworks in compat manifest * cr_lint.py: handle multiple frameworks with snaps -- Jamie Strandboge Fri, 01 May 2015 10:22:06 -0500 click-reviewers-tools (0.25) vivid; urgency=medium [ Michael Vogt ] * Fixed a number of issues raised by pyflakes. [ Ricardo Kirkner ] * support overrides in all click-check scripts * refactored click checks to avoid duplication * handle checks from branch as well as installed system-wide when running all checks [ Jamie Strandboge ] * update bin-path tests for new binaries yaml * 'oem' is a valid type * handle missing 'hooks' in manifest with oem snaps (LP: #1434279) * cr_common.py: add config, immutable-config and oem in support of oem snaps * obsolete framework click hook and meta/*.framework * don't allow 'type: framework' to specify 'frameworks' * fix click-show-files with native snaps * click-show-files should show package.yaml * add framework policy checks * update systemd tests to check package.yaml * .strip() whitespace in control_description_match * check_package_filename() store downloads packages with _all instead of _multi. Account for that. We may want to remove this check entirely. * cr_security.py: adjust for ubuntu-core/15.04 policy changes * cr_security.py: policy_vendor is no longer redflagged * cr_lint.py: don't strip 'all' from compat architecture list on snappy * cr_lint.py: don't review unused control['Architecture'] on snappy [ Fabian Ezequiel Gallina ] * fix missing import on clickreviews/cr_framework.py * add test for non-string framework [ Alex Abreu ] * fix webapp exec with no homepage url or with exec field code (LP: #1441185) [ James Westby ] * Drop the checks on the package name in the filename. The filename doesn't matter, and the store generates it anyway, so checking it is a waste, and keeps breaking as we change the rules. -- Daniel Holbach Mon, 20 Apr 2015 17:26:18 +0200 click-reviewers-tools (0.24) vivid; urgency=medium * don't fail if DEBIAN/md5sums doesn't exist with snap packages. The snap package format uses a different method for integrity checking * add bin/click-check-systemd * adjust bin/click-run-checks to call click-check-systemd -- Jamie Strandboge Wed, 18 Mar 2015 14:27:51 -0500 click-reviewers-tools (0.23) vivid; urgency=medium * fix pep8 warning when building on trusty -- Jamie Strandboge Mon, 09 Mar 2015 15:42:08 -0500 click-reviewers-tools (0.22) vivid; urgency=medium [ Alexandre Abreu ] * Relax the rule that states that webapps with a model search path shouldn't have url patterns listed in the command line. In order to avoid confusion, we allow this to happen (and it already works fine the command line patterns being appended to the locally defined ones). (LP: #1406643) [ Jamie Strandboge ] * add testsuite test to verify apparmor-profile can't be specified with apparmor * add apparmor-profile hook tests * fix test_check_optional_domain_suffix_without_protocol2() to actually test with 'nonexistent' key * debian/control: - add python3-yaml to Build-Depends and Depends - update Vcs-Bzr to point to lp:click-reviewers-tools * add snappy-systemd hook tests and update the testsuite accordingly * apparmor-profile hook may be used anywhere apparmor can be, but not with apparmor itself (apparmor-profile is still redflagged) * implement snappy package.yaml lint tests * implement snappy package.yaml services tests * implement snappy readme.md lint tests * implement snappy package.yaml binaries tests * one more snappy workaround for check_package_filename() -- Jamie Strandboge Mon, 09 Mar 2015 15:08:44 -0500 click-reviewers-tools (0.21) vivid; urgency=medium [ Pete Woods ] * Add childscopes field to recognised list. * Add documentation link of the scope config files: CONFIGFILES in lp:unity-scopes-api. [ Michael Vogt ] * snappy: add two new optional fields: source, type. [ Jamie Strandboge ] * also use ubuntu-devel-discuss@lists.ubuntu.com to signify a core-app * calculate arch correctly in check_package_filename() * add ubuntu-core-15.04 to self.major_framework_policy * add checks for self.major_framework_policy to policy_vendor checks * bin-path should no longer require snappy-systemd hook * warn, don't error, on 'Could not find compiled binaries for architecture' since it might be ok to, for example, ship a shell script but you only want it on ARM devices * apparmor-profile is an allowed hook, but a redflagged one * don't error that apparmor is missing if apparmor-profile is present [ Daniel Holbach ] * Deal with multi-arch clicks properly. (LP: #1395204) -- Daniel Holbach Tue, 03 Mar 2015 14:17:13 +0100 click-reviewers-tools (0.20) vivid; urgency=medium [ Martin Albisetti ] * Remove checks that validate namespaces and email addresses, those are better suited for the store, which knows the information about the uploading user. (LP: #1408644) -- Daniel Holbach Wed, 14 Jan 2015 12:12:25 +0100 click-reviewers-tools (0.19) vivid; urgency=medium [ Ricardo Kirkner ] * fetch framework data before running framework related checks * use mtime instead of ctime to check remote file freshness * allow specifying overrides for framework checks * handle case when overrides data is malformed [ Alexandre Abreu ] * add support for local html5 app launch mode for webapp-container (LP: #1388988) [ Jamie Strandboge ] * open scopes .ini file as utf8 (LP: #1371692) * allow for translatable fields in the scopes .ini file (LP: #1392133) * don't require desktop hook with systemd or framework * com.ubuntu.snappy can use ubuntu-devel-discuss@lists.ubuntu.com (LP: #1395007) * add bin-path click hook checks and tests (LP: #1395001) * add preliminary framework hook checks and tests (LP: #1395004) * refactor hooks checks into parent class (LP: #1395005) * sort click-review results in print_findings * add preliminary systemd hook checks and tests * update apparmor policy json and adjust security checks to properly handle different policy vendors * update data/apparmor-easyprof-ubuntu.json for 1.3 * don't warn if specifying 'default' with ubuntu-snappy vendor * systemd hook renamed to snappy-systemd * allow filenames to end with .snap * allow flat namesapces in check_maintainer_email() [ Daniel Holbach ] * Add askubuntu explanation for policy_version_is_highest. * Add askubuntu explanation for debug builds. (LP: #1390163) -- Daniel Holbach Tue, 16 Dec 2014 17:07:36 +0100 click-reviewers-tools (0.18) utopic; urgency=medium * Let setup.py handle non-ascii characters in d/changelog. -- Daniel Holbach Wed, 15 Oct 2014 10:32:57 +0200 click-reviewers-tools (0.17) utopic; urgency=medium * webapps may use content_exchange_source (LP: #1380694) * online accounts shouldn't specify id and should warn when they do. (LP: #1380534) * click-show-files: cleanup unpack directory at end -- Jamie Strandboge Tue, 14 Oct 2014 11:35:43 -0500 click-reviewers-tools (0.16) utopic; urgency=medium * add i386 and amd64 to self.valid_control_architectures -- Jamie Strandboge Thu, 09 Oct 2014 09:02:55 -0500 click-reviewers-tools (0.15) utopic; urgency=medium * don't error in check_application() if no scope or desktop hook when pay-ui hook is present * updates for push security checks: - apps may specify push-notification-client - push-helpers must use the new 'ubuntu-push-helper' template -- Jamie Strandboge Wed, 08 Oct 2014 15:15:53 -0500 click-reviewers-tools (0.14) utopic; urgency=medium * don't error when account-provider and account-qml-plugin does not also have apparmor policy. There is no policy for these yet so the errors are confusing * add Makefile for some convenience functions * reuse the unpacked click dir -- Jamie Strandboge Thu, 02 Oct 2014 15:15:58 -0500 click-reviewers-tools (0.13) utopic; urgency=medium * reduce to 'info' when security policy does not end with .apparmor (LP: #1358317) -- Jamie Strandboge Wed, 01 Oct 2014 08:09:42 -0500 click-reviewers-tools (0.12) utopic; urgency=medium [ Jamie Strandboge ] * traceback in a more friendly way if the json can't be parsed * adjust click-review --sdk to start reporting again (LP: #1375787) * add additional tests for online accounts (LP: #1357211) * explicitly mark 'networking' as bad policy group when using push-notification-client (it was already implicitly bad) -- Jamie Strandboge Wed, 01 Oct 2014 07:14:33 -0500 click-reviewers-tools (0.11) utopic; urgency=medium [ Jamie Strandboge ] * allow 'accounts' policy group with network scopes. * fix fetch URL for apparmor json to point to json file, not html page (LP: #1375326) * check if security policy does not end with .apparmor (LP: #1358317) * cleanup all the temp directories on shutdown (LP: #1370577) * shouldn't warn when app is coreapp when it uses x-source or x-test (LP: #1371180) [ Daniel Holbach ] * be clearer about unloadable ClickReview classes. -- Jamie Strandboge Mon, 29 Sep 2014 17:01:58 -0500 click-reviewers-tools (0.10) utopic; urgency=medium [ Daniel Holbach ] * Split out code to find Click*Review classes in the clickreviews package into its own module, add tests for it. * Refactor bin/click-review to make it easier to extend. * Add --sdk option, so the SDK can start using it. (LP: #1363857) * Safeguard against broken clickreviews check modules, or modules that are still in development. (LP: #1364449) [ Jamie Strandboge ] * There is now a special pay-ui hook instead of the payui app reusing the desktop hook. We added a check for manual review for when the 'pay-ui' hook was implemented in previous commits, but now we should adjust the cr_desktop.py hook to not error when the pay-ui hook is specified but the desktop hook is not. * The accounts policy group is now a common policy group (14.10) and webapps more fully integrate with accounts these days, so don't flag accounts as unusual any more. * Mark checks requiring manual review by using a special key in the json data. * Add commented out camera policy group to list of ok policygroups for webapps. [ Ricardo Kirkner ] * Updated frameworks.json using myapps api. (LP: #1363096) -- Daniel Holbach Wed, 24 Sep 2014 16:10:43 +0200 click-reviewers-tools (0.9) utopic; urgency=medium [ Jamie Strandboge ] * data/frameworks.json: add ubuntu-sdk-14.10-qml-dev3 * make missing --enable-back-forward informational for webapp desktop file checks [ Daniel Holbach ] * special-case 'com.ubuntu.scopes'. [ Pete Woods ] * Match scope review with actual ini file specifications. (LP: #1350427) * Point to the correct scope ini path. [ Daniel Holbach ] * Add 'click-review', a more versatile approach to what 'click-run-checks' was doing. (LP: #1355215) * Run pep8 during the build. -- Daniel Holbach Wed, 20 Aug 2014 16:03:35 +0200 click-reviewers-tools (0.8) utopic; urgency=medium [ Zoltan Balogh ] * Give an error if the app is using deprecated Friends API (LP: #1340869) [ Martin Albisetti, Daniel Holbach ] * refactor the way we handle frameworks into a central static list which should be easy to update. [ Jamie Strandboge ] * updated clickreviews/cr_tests.py for 14.10*dev2 * bin/repack-click: use -Zgzip when repacking to remain compatible with debfile (ie, click install) * warn on new hooks * implement url-dispatcher hook checks * implement scope hook checks * implement content-hub hook checks * debian/control: Build-Depends and Depends on python3-lxml * implement account-* hook checks * redflag the upcoming pay-ui hook * update security tests to not require apparmor-easyprof-ubuntu or apparmor-easyprof by using a static list to ease updating * debian/control: drop Build-Depends and Depends on apparmor-easyprof and apparmor-easyprof-ubuntu * update data/apparmor-easyprof-ubuntu.json to not include friends policy group in 1.2 (LP: #1340869) * refactor the way we handle apparmor policy into a central static list which should be easy to update. * implement push-helper tests (LP: #1346481) [ Daniel Holbach ] * refer to documentation about click in case we encounter .deb packages. * fix some pep8 warnings. -- Daniel Holbach Fri, 25 Jul 2014 16:20:24 +0200 click-reviewers-tools (0.7.1) utopic; urgency=medium * Merge r198: [ Jamie Strandboge ] - ubuntu-scope-local-content template is no longer available. -- Daniel Holbach Thu, 05 Jun 2014 16:21:33 +0200 click-reviewers-tools (0.7) utopic; urgency=medium [ Daniel Holbach ] * clickreviews/cr_lint.py: add link to more info about "Please use newer framework". Thanks Alan Pope. [ Jamie Strandboge ] * add 14.10 frameworks. Thanks Martin Albisetti for initial patch * 13.10 frameworks should be deprecated instead of obsolete and warn when using deprecated framework * add click scopes checks * special case ubuntu-devel-discuss@lists.ubuntu.com * implement check_hooks() lint tests * debian/control: Depends on apparmor-easyprof-ubuntu >= 1.2.2 (LP: #1324121) -- Jamie Strandboge Wed, 28 May 2014 23:48:04 +0200 click-reviewers-tools (0.6) utopic; urgency=medium [ Daniel Holbach ] * d/control: bump apparmor-easyprof-ubuntu requirement to 1.0.44. This should safeguard against #1292418 (test-suite failing on saucy). * clickreviews/cr_desktop.py: check for deprecated execs, add cordova-ubuntu-2.8 to the list. (LP: #1307533) [ Jamie Strandboge ] * clickreviews/cr_security.py: - webview policy can be used by webapps - content_exchange policy can be used by webapps (LP: #1308184) - clickreviews/tests/test_cr_security.py: tests for above - warn if webview not used with ubuntu-webapp template on non-13.10 frameworks * clickreviews/cr_lint.py: obsolete ubuntu-sdk-13.10 framework * clickreviews/cr_functional.py: warn if using UbuntuWebView 0.1 -- Jamie Strandboge Mon, 28 Apr 2014 13:01:08 -0500 click-reviewers-tools (0.5) trusty; urgency=medium [ Jamie Strandboge ] * mock self.supported_policy_versions * support multiple frameworks on system in security tests * add/update tests for multiple frameworks in security tests -- Daniel Holbach Thu, 27 Feb 2014 15:30:51 +0100 click-reviewers-tools (0.4) trusty; urgency=medium [ Daniel Holbach ] * Check for broken icon paths in .desktop files. (LP: #1257429) * Add initial set of askubuntu answers. * Add ubuntu-html5-app-launcher to expected_execs. [ Jamie Strandboge ] * Documented and clarified the use of the scripts. * Fix crash in __del__. (LP: #1282652) * Add webapp-container tests. * Document bzr hook to run tests. -- Daniel Holbach Wed, 22 Jan 2014 17:59:26 +0100 click-reviewers-tools (0.3) trusty; urgency=medium * d/compat: bump to 9. * d/control: - bump Standards-Version, - drop X-Python-Version, we have X-Python3-Version, - programmatical -> programmatic * d/copyright: fix license mistake (GPL-3+ vs. GPL-3) -- Daniel Holbach Wed, 22 Jan 2014 17:38:47 +0100 click-reviewers-tools (0.2) trusty; urgency=low * Initial release (LP: #1230248) -- Daniel Holbach Wed, 25 Sep 2013 14:32:32 +0200 click-reviewers-tools-0.44~16.04.1/debian/links0000664000000000000000000000005112706157366016011 0ustar usr/bin/click-review usr/bin/snap-review click-reviewers-tools-0.44~16.04.1/debian/rules0000775000000000000000000000251112513764517016027 0ustar #!/usr/bin/make -f # -*- makefile -*- # Uncomment this to turn on verbose mode. export DH_VERBOSE=1 %: dh $@ --with python3 --buildsystem pybuild PY3REQUESTED := $(shell py3versions -r) PY3DEFAULT := $(shell py3versions -d) # Run setup.py with the default python3 last so that the scripts use # #!/usr/bin/python3 and not #!/usr/bin/python3.X. PY3 := $(filter-out $(PY3DEFAULT),$(PY3REQUESTED)) python3 override_dh_auto_clean: dh_clean rm -rf build *.egg-info .pybuild find -name \*.pyc -print0 | xargs -0r rm -f find -name __pycache__ -print0 | xargs -0r rm -rf -$(shell python3 ./bin/update-apparmor-policy ./data/apparmor-easyprof-ubuntu.json) override_dh_auto_build: dh_auto_build set -ex; for python in $(PY3); do \ $$python setup.py build; \ done override_dh_auto_install: # setuptools likes to leave some debris around, which confuses things. find build -name \*.pyc -print0 | xargs -0r rm -f find build -name __pycache__ -print0 | xargs -0r rm -rf find build -name \*.egg-info -print0 | xargs -0r rm -rf dh_auto_install set -ex; for python in $(PY3); do \ $$python setup.py install --install-layout=deb \ --root=$(CURDIR)/debian/tmp; \ done ifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS))) override_dh_auto_test: set -ex; for python in $(PY3); do \ $$python setup.py test; \ done ./run-pep8 ./run-pyflakes endif click-reviewers-tools-0.44~16.04.1/debian/compat0000664000000000000000000000000212511545773016145 0ustar 9 click-reviewers-tools-0.44~16.04.1/debian/docs0000664000000000000000000000000712511545773015617 0ustar README click-reviewers-tools-0.44~16.04.1/check-names.list0000664000000000000000000002366012736745065016614 0ustar bin-path:package_yaml_description_empty| bin-path:package_yaml_description_present| bin-path:package_yaml_optional_key| bin-path:package_yaml_unknown_key| bin-path:path_executable| content_hub:peer_hooks_disallowed| content_hub:peer_hooks_required| content_hub:unknown| content_hub:valid| content_hub:valid_value| desktop:Exec| desktop:Exec_webapp_args_minimal_chrome| desktop:Exec_webapp_args_required| desktop:Exec_webapp_container| desktop:Exec_webapp_container_13.10| desktop:Exec_webapp_container_webapp| desktop:Exec_webbrowser| desktop:Exec_webbrowser_13.10| desktop:Exec_webbrowser_target_exists| desktop:Exec_webbrowser_target_netloc_matches_patterns| desktop:Exec_webbrowser_target_scheme_matches_patterns| desktop:Exec_webbrowser_webapp| desktop:Exec_webbrowser_webapp_manifest| desktop:Exec_webbrowser_webapp_manifest_includes_present| desktop:Exec_webbrowser_webapp_manifest_wellformed| desktop:Exec_webbrowser_webappModelSearchPath_present| desktop:Exec_webbrowser_webappUrlPatterns| desktop:Exec_webbrowser_webapp_url_patterns_has_https| desktop:Exec_webbrowser_webapp_url_patterns_uses_safe_glob| desktop:Exec_webbrowser_webapp_url_patterns_uses_trailing_glob| desktop:Exec_webbrowser_webapp_url_patterns_uses_unsafe_glob| desktop:files_usable| desktop:peer_hooks_disallowed| desktop:peer_hooks_required| desktop:required_fields_not_empty| desktop:required_keys| desktop:validates|http://askubuntu.com/questions/417377/what-does-desktop-validates-mean/417378 desktop:X-Ubuntu-Gettext-Domain| framework:dependency| framework:obsolete_declaration| framework:obsolete_framework_file| framework:policies| framework:policy| framework:policy_metadata| framework:policy_unknown| framework:policy_valid_name| lint:architecture_specified_needed| lint:click_local_extensions| lint:control_architecture_match| lint:control_architecture_valid| lint:control_architecture_valid_contents| lint:control_click_version_up_to_date|http://askubuntu.com/questions/417366/what-does-lint-control-click-version-up-to-date-mean/417367 lint:control_description_match| lint:control_has_field| lint:control_installed_size| lint:control_maintainer_match| lint:control_package_match| lint:control_structure| lint:control_version_match| lint:dot_click| lint:exclusive_hooks| lint:external_symlinks| lint:file_mode| lint:framework|http://askubuntu.com/questions/460512/what-framework-should-i-use-in-my-manifest-file lint:hashes_archive-sha512_present| lint:hashes_archive-sha512_valid| lint:hashes_extra_files| lint:hashes_files_present| lint:hooks| lint:hooks_known| lint:hooks_multiple_apps| lint:hooks_redflag| lint:hooks_valid| lint:maintainer_format|http://askubuntu.com/questions/417351/what-does-lint-maintainer-format-mean/417352 lint:maintainer_present| lint:manifest_architecture_valid| lint:manifest_icon_absolute_path| lint:manifest_icon_empty| lint:manifest_icon_present| lint:md5sums| lint:package yaml_architecture_valid| lint:package_yaml_icon_absolute_path| lint:package_yaml_icon_empty| lint:package_yaml_icon_present| lint:pkgname_valid| lint:sdk_security_extension| lint:sha512sums| lint:snappy_architecture_deprecated|https://developer.ubuntu.com/en/snappy/guides/package-metadata/ lint:snappy_config_hook_executable| lint:snappy_in_binaries| lint:snappy_in_services| lint:snappy_name_valid| lint:snappy_readme.md| lint:snappy_readme.md_length| lint:snappy_type_redflag|https://developer.ubuntu.com/en/snappy/guides/frameworks/ lint:snappy_type_valid| lint:snappy_unknown| lint:snappy_version_valid| lint-snap-v2:app_plugs| lint-snap-v2:app_plugs_plug_reference| lint-snap-v2:apps| lint-snap-v2:apps_entry| lint-snap-v2:app_slots| lint-snap-v2:app_slots_plug_reference| lint-snap-v2:apps_present| lint-snap-v2:apps_required| lint-snap-v2:apps_unknown| lint-snap-v2:architecture_specified_needed| lint-snap-v2:architecture_valid| lint-snap-v2:command| lint-snap-v2:config_hook_executable| lint-snap-v2:confinement_valid| lint-snap-v2:daemon| lint-snap-v2:daemon_required| lint-snap-v2:description| lint-snap-v2:description_present| lint-snap-v2:environment_key_valid|http://pubs.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap08.html lint-snap-v2:environment_valid| lint-snap-v2:environment_value_valid| lint-snap-v2:epoch_valid| lint-snap-v2:external_symlinks| lint-snap-v2:icon_absolute_path| lint-snap-v2:icon_empty| lint-snap-v2:icon_exists| lint-snap-v2:icon_present| lint-snap-v2:license-agreement| lint-snap-v2:license-agreement_present| lint-snap-v2:license-version| lint-snap-v2:license-version_present| lint-snap-v2:listen-stream| lint-snap-v2:listen-stream_matches_name| lint-snap-v2:name_valid| lint-snap-v2:plugs| lint-snap-v2:plugs_attributes| lint-snap-v2:ports| lint-snap-v2:ports_ext1_format| lint-snap-v2:ports_ext2_format| lint-snap-v2:ports_int1_format| lint-snap-v2:ports_unknown_key| lint-snap-v2:ports_unknown_subkey| lint-snap-v2:post-stop-command| lint-snap-v2:restart-condition| lint-snap-v2:slots| lint-snap-v2:slots_attributes| lint-snap-v2:snap_type_redflag| lint-snap-v2:snap_type_valid| lint-snap-v2:socket| lint-snap-v2:socket-group| lint-snap-v2:socket-group_matches_name| lint-snap-v2:socket-group_reserved| lint-snap-v2:socket-user| lint-snap-v2:socket-user_matches_name| lint-snap-v2:socket-user_reserved| lint-snap-v2:stop-command| lint-snap-v2:stop-timeout| lint-snap-v2:stop-timeout_range| lint-snap-v2:summary| lint-snap-v2:summary_present| lint-snap-v2:unknown_field| lint-snap-v2:valid_contents_for_architecture| lint-snap-v2:vcs_files| lint-snap-v2:version_valid| online_accounts:account-application_hook| online_accounts:account-application_id| online_accounts:account-application_root| online_accounts:account-application_service| online_accounts:account-application_services| online_accounts:account-provider| online_accounts:account-provider_id| online_accounts:account-provider_root| online_accounts:account-qml-plugin| online_accounts:account-service_hook| online_accounts:account-service_id| online_accounts:account-service_name|http://askubuntu.com/q/697173 online_accounts:account-service_provider|http://askubuntu.com/q/697173 online_accounts:account-service_root| online_accounts:accounts_plugin| online_accounts:accounts_service| online_accounts:accounts_services| online_accounts:peer_hooks_disallowed|https://wiki.ubuntu.com/SecurityTeam/Specifications/OnlineAccountsConfinement online_accounts:peer_hooks_required|https://wiki.ubuntu.com/SecurityTeam/Specifications/OnlineAccountsConfinement push_helper:other_hooks| push_helper:peer_hooks_disallowed| push_helper:peer_hooks_required| push_helper:unknown| push_helper:valid| push_helper:valid_required| scope:ini_scope_internal_fields| scope:ini_scope_required_fields| scope:ini_scope_section| scope:ini_scope_unknown_fields| scope:peer_hooks_disallowed| scope:peer_hooks_required| security:apparmor_profile| security:peer_hooks_disallowed| security:peer_hooks_required| security:policy_groups_duplicates| security:policy_groups_exists| security:policy_groups_push_helper| security:policy_groups_safe| security:policy_groups_scopes| security:policy_groups_ubuntu_account_plugin| security:policy_groups_valid| security:policy_groups_webapp| security:policy_groups_webapp_webview| security:policy_vendor| security:policy_vendor_matches_framework| security:policy_version_exists| security:policy_version_is_highest|http://askubuntu.com/q/562116/94326 security:policy_version_matches_framework|http://askubuntu.com/q/686347 security:profile_name_length| security:redflag_fields| security:required_policy_groups_ubuntu_account_plugin| security-snap-v2:app_plug_safe| security-snap-v2:app_slot_safe| security-snap-v2:is_slot| security-snap-v2:plug_safe|http://askubuntu.com/a/562123/94326 security-snap-v2:policy-vendor| security-snap-v2:policy-version| security-snap-v2:profile_name_length| security-snap-v2:slot_safe|http://askubuntu.com/a/562123/94326 security-snap-v2:squashfs_lls| security-snap-v2:squashfs_repack_checksum| security-snap-v2:squashfs_resquash| security-snap-v2:squashfs_resquash_1555305|https://launchpad.net/bugs/1555305 security-snap-v2:squashfs_supports_fstime| security:template_account_provider| security:template_account_qml_plugin| security:template_exists| security:template_valid|http://askubuntu.com/q/671403 security:template_with_policy_version| security:yaml_and_click| security:yaml_binaries| security:yaml_binaries_entries| security:yaml_caps| security:yaml_combinations| security:yaml_override_click| security:yaml_override_format| security:yaml_policy_format| security:yaml_policy_present|https://developer.ubuntu.com/en/snappy/guides/security-policy/ security:yaml_security-template| security:yaml_services| snappy-systemd:package_yaml_absolute_path| snappy-systemd:package_yaml_bus-name_empty| snappy-systemd:package_yaml_bus-name_format|http://dbus.freedesktop.org/doc/dbus-specification.html snappy-systemd:package_yaml_bus-name_framework| snappy-systemd:package_yaml_bus-name_matches_name| snappy-systemd:package_yaml_description_empty| snappy-systemd:package_yaml_description_present| snappy-systemd:package_yaml_empty| snappy-systemd:package_yaml_listen-stream_empty| snappy-systemd:package_yaml_listen-stream_matches_name| snappy-systemd:package_yaml_optional_key| snappy-systemd:package_yaml_ports| snappy-systemd:package_yaml_ports_bad_key| snappy-systemd:package_yaml_ports_empty| snappy-systemd:package_yaml_ports_ext1_format| snappy-systemd:package_yaml_ports_ext2_format| snappy-systemd:package_yaml_ports_int1_format| snappy-systemd:package_yaml_ports_invalid| snappy-systemd:package_yaml_required_key| snappy-systemd:package_yaml_socket-group| snappy-systemd:package_yaml_socket-group_listen-stream| snappy-systemd:package_yaml_socket-group_matches| snappy-systemd:package_yaml_socket_listen-stream| snappy-systemd:package_yaml_socket-user| snappy-systemd:package_yaml_socket-user_listen-stream| snappy-systemd:package_yaml_socket-user_matches| snappy-systemd:package_yaml_stop_timeout| snappy-systemd:package_yaml_unknown_key| url_dispatcher:optional_entry| url_dispatcher:peer_hooks_disallowed| url_dispatcher:peer_hooks_required| url_dispatcher:required_entry| url_dispatcher:unknown_entry| click-reviewers-tools-0.44~16.04.1/README0000664000000000000000000000702012660650204014373 0ustar Runnable click and snap v1 tests: - bin/click-check-bin-path: snappy bin-path tests - bin/click-check-content-hub: content-hub hook tests - bin/click-check-desktop: desktop hook tests - bin/click-check-framework: click framework tests - bin/click-check-functional: a few functional tests - bin/click-check-lint: lint tests - bin/click-check-online-accounts: online accounts tests - bin/click-check-push-helper: push-helper tests - bin/click-check-scope: scope tests - bin/click-check-security: security hook tests - bin/click-check-systemd: snappy systemd tests - bin/click-check-url-dispatcher: url-dispatcher hook tests - bin/click-run-checks: all tests Runnable snap v2 tests: - bin/snap-check-lint: lint tests - bin/snap-run-checks: all tests This gives an alternate view on bin/click-run-checks: - bin/click-review Running tests locally: $ PYTHONPATH=$PWD ./bin/click-review /path/to/click Importable tests: - clickreviews/cr_lint.py: lint tests - clickreviews/cr_security.py: security hook tests - clickreviews/cr_desktop.py: desktop hook tests - ... In general, add or modify tests and report by using: self._add_result(, , ) Where is one of 'info', 'warn', 'error'. is the name of the test (prefixed by _), which is set when creating a ClickReview object. After all tests are run, if there are any errors, the exit status is '2', if there are no errors but some warnings, the exit status is '1', otherwise it is '0. See click-check-skeleton and cr_skeleton.py for how to create new tests. In short: * create a click-check- and a cr_.py script based off of the skeleton. IMPORTANT: the new script must be click-check- so other tools that use click-reviewers-tools (eg, ubuntu-sdk) can find them. * modify click-check- to use cr_.py * add tests to cr_.py. If you name the tests 'check_' ClickReview.do_checks() will enumerate and run them automatically (substitute 'sr_' for 'cr_' for snap v2 checks) To run tests, just execute: $ ./run-tests # all tests $ ./run-tests test_cr_security.py # only security tests If you are going to develop the tools regularly, you might want to add a bzr hook to run the testsuite before committing. Eg, add something like this to ~/.bazaar/plugins/hooks/__init__.py: #!/usr/bin/python from bzrlib.branch import Branch def run_tests_crt(local, master, old_revno, old_revid, new_revno, new_revid, seven, eight): #print local, master, old_revno, old_revid, new_revno, new_revid, seven, eight if 'click-reviewers-tools' in master.base: import subprocess print '' rc = subprocess.call(['./run-tests']) if rc != 0: import sys sys.exit(1) Branch.hooks.install_named_hook('pre_commit', run_tests_crt, 'click-reviewers-tools tests') Keeping projects in sync ------------------------ In addition to archive uploads and SRUs, the following should also be updated: - https://launchpad.net/~snappy-dev/+archive/ubuntu/tools-proposed then keep an eye on them and when ok, ask someone to push to: https://launchpad.net/~snappy-dev/+archive/ubuntu/tools - https://launchpad.net/~ubuntu-sdk-team/+archive/ubuntu/staging then keep an eye on them and when ok, ask someone to push to: https://launchpad.net/~ubuntu-sdk-team/+archive/ubuntu/ppa - the Ubuntu Store (ping beuno or pindonga to pull the changes from trunk) click-reviewers-tools-0.44~16.04.1/setup.cfg0000664000000000000000000000005212511545773015343 0ustar # E501 line too long [pep8] ignore = E501 click-reviewers-tools-0.44~16.04.1/data/0000775000000000000000000000000012733311377014433 5ustar click-reviewers-tools-0.44~16.04.1/data/apparmor-easyprof-ubuntu.json0000664000000000000000000002002612733310516022307 0ustar { "ubuntu": { "1.0": { "templates": { "common": [ "default", "ubuntu-sdk", "ubuntu-webapp" ], "reserved": [ "unconfined" ] }, "policy_groups": { "common": [ "audio", "camera", "connectivity", "content_exchange", "content_exchange_source", "keep-display-on", "location", "microphone", "networking", "sensors", "usermetrics", "video" ], "reserved": [ "accounts", "calendar", "contacts", "friends", "history", "music_files", "music_files_read", "picture_files", "picture_files_read", "video_files", "video_files_read" ] } }, "1.1": { "templates": { "common": [ "default", "ubuntu-sdk", "ubuntu-webapp" ], "reserved": [ "unconfined" ] }, "policy_groups": { "common": [ "audio", "camera", "connectivity", "content_exchange", "content_exchange_source", "keep-display-on", "location", "microphone", "networking", "sensors", "usermetrics", "video", "webview" ], "reserved": [ "accounts", "calendar", "contacts", "debug", "friends", "history", "music_files", "music_files_read", "picture_files", "picture_files_read", "video_files", "video_files_read" ] } }, "1.2": { "templates": { "common": [ "default", "ubuntu-account-plugin", "ubuntu-push-helper", "ubuntu-scope-network", "ubuntu-sdk", "ubuntu-webapp" ], "reserved": [ "unconfined" ] }, "policy_groups": { "common": [ "accounts", "audio", "camera", "connectivity", "content_exchange", "content_exchange_source", "keep-display-on", "location", "microphone", "networking", "push-notification-client", "sensors", "usermetrics", "video", "webview" ], "reserved": [ "calendar", "contacts", "debug", "history", "music_files", "music_files_read", "picture_files", "picture_files_read", "video_files", "video_files_read" ] } }, "1.3": { "templates": { "common": [ "default", "ubuntu-account-plugin", "ubuntu-push-helper", "ubuntu-scope-network", "ubuntu-sdk", "ubuntu-webapp" ], "reserved": [ "unconfined" ] }, "policy_groups": { "common": [ "accounts", "audio", "camera", "connectivity", "content_exchange", "content_exchange_source", "in-app-purchases", "keep-display-on", "location", "microphone", "networking", "push-notification-client", "sensors", "usermetrics", "video", "webview" ], "reserved": [ "bluetooth", "calendar", "contacts", "debug", "history", "music_files", "music_files_read", "picture_files", "picture_files_read", "video_files", "video_files_read" ] } }, "15.10": { "templates": { "common": [ "default", "ubuntu-account-plugin", "ubuntu-push-helper", "ubuntu-scope-network", "ubuntu-sdk", "ubuntu-webapp" ], "reserved": [ "unconfined" ] }, "policy_groups": { "common": [ "accounts", "audio", "camera", "connectivity", "content_exchange", "content_exchange_source", "in-app-purchases", "keep-display-on", "location", "microphone", "networking", "push-notification-client", "sensors", "usermetrics", "video", "webview" ], "reserved": [ "bluetooth", "calendar", "contacts", "debug", "history", "music_files", "music_files_read", "picture_files", "picture_files_read", "video_files", "video_files_read" ] } }, "16.04": { "templates": { "common": [ "default", "ubuntu-account-plugin", "ubuntu-push-helper", "ubuntu-scope-network", "ubuntu-sdk", "ubuntu-webapp" ], "reserved": [ "unconfined" ] }, "policy_groups": { "common": [ "accounts", "audio", "camera", "connectivity", "content_exchange", "content_exchange_source", "in-app-purchases", "keep-display-on", "location", "microphone", "networking", "push-notification-client", "sensors", "usermetrics", "video", "webview" ], "reserved": [ "bluetooth", "calendar", "contacts", "debug", "history", "music_files", "music_files_read", "picture_files", "picture_files_read", "video_files", "video_files_read" ] } } }, "ubuntu-snappy": { "1.3": { "templates": { "common": [ "default" ], "reserved": [ "unconfined" ] }, "policy_groups": { "common": [ "networking", "network-service", "network-client" ], "reserved": [] } } }, "ubuntu-core": { "15.04": { "templates": { "common": [ "default" ], "reserved": [ "unconfined" ] }, "policy_groups": { "common": [ "network-client", "network-service", "networking" ], "reserved": [ "bluez_client", "docker_client", "network-admin", "network-firewall", "network-status", "snapd" ] } }, "15.10": { "templates": { "common": [ "default" ], "reserved": [ "unconfined" ] }, "policy_groups": { "common": [ "network-client", "network-service", "networking" ], "reserved": [ "bluez_client", "docker_client", "network-admin", "network-firewall", "network-status", "snapd" ] } }, "16.04": { "templates": { "common": [ "default" ], "reserved": [] }, "policy_groups": { "common": [ "camera", "bool-file", "content", "cups-control", "firewall-control", "gsettings", "home", "locale-control", "log-observe", "mount-observe", "mpris", "network", "network-bind", "network-control", "network-observe", "opengl", "optical-drive", "ppp", "pulseaudio", "serial-port", "snapd-control", "system-observe", "timeserver-control", "timezone-control", "unity7", "x11" ], "reserved": [ "bluez", "location-control", "location-observe", "network-manager", "modem-manager" ] } } } } click-reviewers-tools-0.44~16.04.1/setup.py0000775000000000000000000000123512713206312015226 0ustar #! /usr/bin/env python3 from setuptools import setup, find_packages import codecs import glob import os import re # look/set what version we have changelog = 'debian/changelog' if os.path.exists(changelog): head = codecs.open(changelog, encoding='utf-8').readline() match = re.compile('.*\((.*)\).*').match(head) if match: version = match.group(1) scripts = glob.glob('bin/click-*') + glob.glob('bin/snap-*') scripts.remove('bin/click-check-skeleton') scripts.remove('bin/snap-check-skeleton') setup( name='click-reviewers-tools', version=version, scripts=scripts, packages=find_packages(), test_suite='clickreviews.tests', ) click-reviewers-tools-0.44~16.04.1/run-pyflakes0000775000000000000000000000033712513764517016077 0ustar #!/bin/sh set -e echo "= pyflakes3 =" for i in ./bin/update-* ./bin/click-check-* ./bin/click-show-files ./bin/click-review \ ./clickreviews/*py ./clickreviews/tests/*py ; do echo "Checking $i" pyflakes3 $i done click-reviewers-tools-0.44~16.04.1/run-tests0000775000000000000000000000225512732766062015424 0ustar #!/usr/bin/python3 '''run-tests: run the test suite''' # # Copyright (C) 2013 Canonical Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import sys import unittest # NOTE: changes to this file may also need to be made to # 'collect-check-names-from-tests' test_directory = 'clickreviews/tests/' test_filename = 'test_*' if len(sys.argv) > 1: test_filename = sys.argv[1] suite = unittest.TestLoader().discover(test_directory, pattern=test_filename) test_result = unittest.TextTestRunner(verbosity=2).run(suite) if len(test_result.errors) > 0 or len(test_result.failures) > 0 or \ len(test_result.unexpectedSuccesses) > 0: sys.exit(1) click-reviewers-tools-0.44~16.04.1/collect-check-names0000775000000000000000000000034412565665111017253 0ustar #!/bin/sh (./collect-check-names-from-tests 2>&1 | grep 'CHECK|' | cut -d '|' -f 2- \ | egrep -v '(skeleton|some-check)' | sort \ | awk -F '|' '(l && $1 != l1) {print l} {l1=$1; l=$0} END {print l}') > check-names.list click-reviewers-tools-0.44~16.04.1/run-pep80000775000000000000000000000041012526452332015116 0ustar #!/bin/sh set -e echo "= pep8 =" for i in ./clickreviews/*py \ ./clickreviews/tests/*py \ ./bin/update-* \ ./bin/click-check-* \ ./bin/click-show-files \ ./bin/click-review ; do echo "Checking $i" pep8 $i done click-reviewers-tools-0.44~16.04.1/COPYING0000664000000000000000000010451312511545773014564 0ustar GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read .