diff -Nru click-reviewers-tools-0.7/bin/click-check-content-hub click-reviewers-tools-0.8/bin/click-check-content-hub
--- click-reviewers-tools-0.7/bin/click-check-content-hub 1970-01-01 00:00:00.000000000 +0000
+++ click-reviewers-tools-0.8/bin/click-check-content-hub 2014-07-25 13:47:22.000000000 +0000
@@ -0,0 +1,31 @@
+#!/usr/bin/python3
+'''click-check-content-hub: perform click content-hub checks'''
+#
+# 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 __future__ import print_function
+import sys
+
+import clickreviews.cr_common as cr_common
+import clickreviews.cr_content_hub as cr_content_hub
+
+if __name__ == "__main__":
+ if len(sys.argv) < 2:
+ cr_common.error("Must give path to click package")
+
+ review = cr_content_hub.ClickReviewContentHub(sys.argv[1])
+ review.do_checks()
+ rc = review.do_report()
+ sys.exit(rc)
diff -Nru click-reviewers-tools-0.7/bin/click-check-online-accounts click-reviewers-tools-0.8/bin/click-check-online-accounts
--- click-reviewers-tools-0.7/bin/click-check-online-accounts 1970-01-01 00:00:00.000000000 +0000
+++ click-reviewers-tools-0.8/bin/click-check-online-accounts 2014-07-25 13:47:22.000000000 +0000
@@ -0,0 +1,31 @@
+#!/usr/bin/python3
+'''click-check-online-accounts: perform click online accounts checks'''
+#
+# 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 __future__ import print_function
+import sys
+
+import clickreviews.cr_common as cr_common
+import clickreviews.cr_online_accounts as cr_online_accounts
+
+if __name__ == "__main__":
+ if len(sys.argv) < 2:
+ cr_common.error("Must give path to click package")
+
+ review = cr_online_accounts.ClickReviewAccounts(sys.argv[1])
+ review.do_checks()
+ rc = review.do_report()
+ sys.exit(rc)
diff -Nru click-reviewers-tools-0.7/bin/click-check-push-helper click-reviewers-tools-0.8/bin/click-check-push-helper
--- click-reviewers-tools-0.7/bin/click-check-push-helper 1970-01-01 00:00:00.000000000 +0000
+++ click-reviewers-tools-0.8/bin/click-check-push-helper 2014-07-25 13:47:22.000000000 +0000
@@ -0,0 +1,31 @@
+#!/usr/bin/python3
+'''click-check-push-helper: perform click push-helper checks'''
+#
+# 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 __future__ import print_function
+import sys
+
+import clickreviews.cr_common as cr_common
+import clickreviews.cr_push_helper as cr_push_helper
+
+if __name__ == "__main__":
+ if len(sys.argv) < 2:
+ cr_common.error("Must give path to click package")
+
+ review = cr_push_helper.ClickReviewPushHelper(sys.argv[1])
+ review.do_checks()
+ rc = review.do_report()
+ sys.exit(rc)
diff -Nru click-reviewers-tools-0.7/bin/click-check-scope click-reviewers-tools-0.8/bin/click-check-scope
--- click-reviewers-tools-0.7/bin/click-check-scope 1970-01-01 00:00:00.000000000 +0000
+++ click-reviewers-tools-0.8/bin/click-check-scope 2014-07-25 13:47:22.000000000 +0000
@@ -0,0 +1,31 @@
+#!/usr/bin/python3
+'''click-check-scope: perform click scope checks'''
+#
+# 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 __future__ import print_function
+import sys
+
+import clickreviews.cr_common as cr_common
+import clickreviews.cr_scope as cr_scope
+
+if __name__ == "__main__":
+ if len(sys.argv) < 2:
+ cr_common.error("Must give path to click package")
+
+ review = cr_scope.ClickReviewScope(sys.argv[1])
+ review.do_checks()
+ rc = review.do_report()
+ sys.exit(rc)
diff -Nru click-reviewers-tools-0.7/bin/click-check-url-dispatcher click-reviewers-tools-0.8/bin/click-check-url-dispatcher
--- click-reviewers-tools-0.7/bin/click-check-url-dispatcher 1970-01-01 00:00:00.000000000 +0000
+++ click-reviewers-tools-0.8/bin/click-check-url-dispatcher 2014-07-25 13:47:22.000000000 +0000
@@ -0,0 +1,31 @@
+#!/usr/bin/python3
+'''click-check-url_dispatcher: perform click url dispatcher checks'''
+#
+# 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 __future__ import print_function
+import sys
+
+import clickreviews.cr_common as cr_common
+import clickreviews.cr_url_dispatcher as cr_url_dispatcher
+
+if __name__ == "__main__":
+ if len(sys.argv) < 2:
+ cr_common.error("Must give path to click package")
+
+ review = cr_url_dispatcher.ClickReviewUrlDispatcher(sys.argv[1])
+ review.do_checks()
+ rc = review.do_report()
+ sys.exit(rc)
diff -Nru click-reviewers-tools-0.7/bin/clickreviews/apparmor_policy.py click-reviewers-tools-0.8/bin/clickreviews/apparmor_policy.py
--- click-reviewers-tools-0.7/bin/clickreviews/apparmor_policy.py 1970-01-01 00:00:00.000000000 +0000
+++ click-reviewers-tools-0.8/bin/clickreviews/apparmor_policy.py 2014-07-25 13:47:22.000000000 +0000
@@ -0,0 +1,37 @@
+#
+# 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/~click-reviewers/click-reviewers-tools/trunk/view/head:/data/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)
diff -Nru click-reviewers-tools-0.7/bin/clickreviews/cr_common.py click-reviewers-tools-0.8/bin/clickreviews/cr_common.py
--- click-reviewers-tools-0.7/bin/clickreviews/cr_common.py 2014-05-05 08:33:55.000000000 +0000
+++ click-reviewers-tools-0.8/bin/clickreviews/cr_common.py 2014-07-25 13:47:22.000000000 +0000
@@ -60,6 +60,10 @@
self.click_package = fn
self._check_path_exists()
if not self.click_package.endswith(".click"):
+ if self.click_package.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.review_type = review_type
diff -Nru click-reviewers-tools-0.7/bin/clickreviews/cr_content_hub.py click-reviewers-tools-0.8/bin/clickreviews/cr_content_hub.py
--- click-reviewers-tools-0.7/bin/clickreviews/cr_content_hub.py 1970-01-01 00:00:00.000000000 +0000
+++ click-reviewers-tools-0.8/bin/clickreviews/cr_content_hub.py 2014-07-25 13:47:22.000000000 +0000
@@ -0,0 +1,115 @@
+'''cr_content_hub.py: click content-hub checks'''
+#
+# 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 __future__ import print_function
+
+from clickreviews.cr_common import ClickReview, error, open_file_read, msg
+import json
+import os
+
+
+class ClickReviewContentHub(ClickReview):
+ '''This class represents click lint reviews'''
+ def __init__(self, fn):
+ ClickReview.__init__(self, fn, "content_hub")
+
+ self.valid_keys = ['destination', 'share', 'source']
+
+ self.content_hub_files = dict() # click-show-files and tests
+ self.content_hub = dict()
+ 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'''
+ for app in sorted(self.content_hub):
+ for k in self.content_hub[app].keys():
+ t = "info"
+ n = "valid_%s_%s" % (app, 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 = "valid_%s_%s_value" % (app, 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'''
+ for app in sorted(self.content_hub):
+ unknown = []
+ t = "info"
+ n = "unknown_%s" % 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)
diff -Nru click-reviewers-tools-0.7/bin/clickreviews/cr_desktop.py click-reviewers-tools-0.8/bin/clickreviews/cr_desktop.py
--- click-reviewers-tools-0.7/bin/clickreviews/cr_desktop.py 2014-05-05 08:33:55.000000000 +0000
+++ click-reviewers-tools-0.8/bin/clickreviews/cr_desktop.py 2014-07-25 13:47:22.000000000 +0000
@@ -16,7 +16,7 @@
from __future__ import print_function
-from clickreviews.cr_common import ClickReview, error, open_file_read
+from clickreviews.cr_common import ClickReview, error, open_file_read, msg
import glob
import json
import os
@@ -36,7 +36,15 @@
self.desktop_hook_entries = 0
for app in self.manifest['hooks']:
if 'desktop' not in self.manifest['hooks'][app]:
- error("could not find desktop hook for '%s'" % app)
+ if 'scope' in self.manifest['hooks'][app]:
+ # msg("Skipped missing desktop hook for scope '%s'" % app)
+ continue
+ if 'push-helper' in self.manifest['hooks'][app]:
+ # msg("Skipped missing desktop hook for push-helper '%s'" %
+ # app)
+ continue
+ else:
+ error("could not find desktop hook for '%s'" % app)
if not isinstance(self.manifest['hooks'][app]['desktop'], str):
error("manifest malformed: hooks/%s/desktop is not str" % app)
self.desktop_hook_entries += 1
@@ -102,19 +110,13 @@
def check_desktop_file(self):
'''Check desktop file'''
t = 'info'
- n = 'files_available'
- s = 'OK'
- if len(self._get_desktop_files().keys()) < 1:
- t = 'error'
- s = 'No .desktop files available.'
- self._add_result(t, n, s)
-
- t = 'info'
n = '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):
diff -Nru click-reviewers-tools-0.7/bin/clickreviews/cr_functional.py click-reviewers-tools-0.8/bin/clickreviews/cr_functional.py
--- click-reviewers-tools-0.7/bin/clickreviews/cr_functional.py 2014-05-05 08:33:55.000000000 +0000
+++ click-reviewers-tools-0.8/bin/clickreviews/cr_functional.py 2014-07-25 13:47:22.000000000 +0000
@@ -167,3 +167,24 @@
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'''
+ t = 'info'
+ n = '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)
diff -Nru click-reviewers-tools-0.7/bin/clickreviews/cr_lint.py click-reviewers-tools-0.8/bin/clickreviews/cr_lint.py
--- click-reviewers-tools-0.7/bin/clickreviews/cr_lint.py 2014-05-05 08:33:55.000000000 +0000
+++ click-reviewers-tools-0.8/bin/clickreviews/cr_lint.py 2014-07-25 13:47:22.000000000 +0000
@@ -21,6 +21,7 @@
import os
import re
+from clickreviews.frameworks import Frameworks
from clickreviews.cr_common import ClickReview, open_file_read, cmd
CONTROL_FILE_NAMES = ["control", "manifest", "md5sums", "preinst"]
@@ -60,6 +61,20 @@
self._list_all_compiled_binaries()
+ self.known_hooks = ['account-application',
+ 'account-provider',
+ 'account-qml-plugin',
+ 'account-service',
+ 'apparmor',
+ 'content-hub',
+ 'desktop',
+ 'pay-ui',
+ 'push-helper',
+ 'scope',
+ 'urls']
+
+ self.redflagged_hooks = ['pay-ui']
+
def _list_control_files(self):
'''List all control files with their full path.'''
for i in CONTROL_FILE_NAMES:
@@ -267,11 +282,20 @@
# Some checks are already handled in
# cr_common.py:_verify_manifest_structure()
- # We don't support multiple apps in 13.10
- if len(self.manifest['hooks'].keys()) != 1:
- self._add_result('error', 'hooks',
- "more than one app key specified in hooks")
- return
+ # While we support multiple apps in the hooks db, we don't support
+ # multiple apps specifying desktop hooks. Eg, it is ok to specify a
+ # scope, an app and a push-helper, but it isn't ok to specify two apps
+ t = 'info'
+ n = '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:
+ t = 'error'
+ 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']:
@@ -284,22 +308,78 @@
self._add_result(t, n, s)
# Verify we have the required hooks
- required = ['apparmor', 'desktop']
+ required = ['apparmor']
for f in required:
for app in self.manifest['hooks']:
t = 'info'
n = 'hooks_%s_%s' % (app, f)
s = "OK"
- if f == "apparmor":
+ 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]:
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']:
+ 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)
+
+ def check_hooks_unknown(self):
+ '''Check if have any unknown hooks'''
+ t = 'info'
+ n = '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 = 'hooks_%s_%s_known' % (app, 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'''
+ t = 'info'
+ n = 'redflagged hooks'
+ s = 'OK'
+
+ for app in self.manifest['hooks']:
+ found = []
+ t = 'info'
+ n = 'hooks_redflag_%s' % (app)
+ s = "OK"
+ for hook in self.manifest['hooks'][app]:
+ if hook in self.redflagged_hooks:
+ found.append(hook)
+ if len(found) > 0:
+ t = 'error'
+ s = "(MANUAL REVIEW) '%s' not allowed" % ", ".join(found)
+ self._add_result(t, n, s)
+
def check_external_symlinks(self):
'''Check if symlinks in the click package go out to the system.'''
t = 'info'
@@ -447,6 +527,9 @@
t = 'info'
s = "OK (email '%s' long, but special case of core apps " \
"'com.ubuntu.*')" % self.email
+ elif self.email == "ubuntu-devel-discuss@lists.ubuntu.com":
+ t = 'info'
+ s = "OK (email '%s' long, but special case" % self.email
else:
t = 'error'
s = "(EMAIL NEEDS HUMAN REVIEW) email domain too " \
@@ -515,26 +598,35 @@
def check_framework(self):
'''Check framework()'''
- t = 'info'
n = 'framework'
- s = 'OK'
- if self.manifest['framework'] not in self.valid_frameworks:
- t = 'error'
- s = "'%s' is not a supported framework" % \
+ l = "http://askubuntu.com/questions/460512/what-framework-should-i-use-in-my-manifest-file"
+ local_copy = os.path.join(os.path.dirname(__file__),
+ '../data/frameworks.json')
+ frameworks = Frameworks(local_copy)
+ if self.manifest['framework'] in frameworks.AVAILABLE_FRAMEWORKS:
+ t = 'info'
+ s = 'OK'
+ self._add_result(t, n, s)
+ # If it's an available framework, we're done checking
+ return
+ elif self.manifest['framework'] in frameworks.DEPRECATED_FRAMEWORKS:
+ t = 'warn'
+ s = "'%s' is deprecated. Please use a newer framework" % \
self.manifest['framework']
- self._add_result(t, n, s)
-
- obsolete_frameworks = ['ubuntu-sdk-13.10']
- t = 'info'
- n = 'current framework'
- s = 'OK'
- l = None
- if self.manifest['framework'] in obsolete_frameworks:
+ self._add_result(t, n, s, l)
+ return
+ elif self.manifest['framework'] in frameworks.OBSOLETE_FRAMEWORKS:
t = 'error'
s = "'%s' is obsolete. Please use a newer framework" % \
self.manifest['framework']
- l = "http://askubuntu.com/questions/460512/what-framework-should-i-use-in-my-manifest-file"
- self._add_result(t, n, s, l)
+ self._add_result(t, n, s, l)
+ return
+ else:
+ # None of the above checks triggered, this is an unknown framework
+ t = 'error'
+ s = "'%s' is not a supported framework" % \
+ self.manifest['framework']
+ self._add_result(t, n, s, l)
def check_click_local_extensions(self):
'''Report any click local extensions'''
diff -Nru click-reviewers-tools-0.7/bin/clickreviews/cr_online_accounts.py click-reviewers-tools-0.8/bin/clickreviews/cr_online_accounts.py
--- click-reviewers-tools-0.7/bin/clickreviews/cr_online_accounts.py 1970-01-01 00:00:00.000000000 +0000
+++ click-reviewers-tools-0.8/bin/clickreviews/cr_online_accounts.py 2014-07-25 13:47:22.000000000 +0000
@@ -0,0 +1,231 @@
+'''cr_online_accounts.py: click online accounts'''
+#
+# 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
+
+from clickreviews.cr_common import ClickReview, error, open_file_read, msg
+import os
+# http://lxml.de/tutorial.html
+import lxml.etree as etree
+
+
+class ClickReviewAccounts(ClickReview):
+ '''This class represents click lint reviews'''
+ def __init__(self, fn):
+ ClickReview.__init__(self, fn, "online_accounts")
+
+ self.accounts_files = dict()
+ self.accounts = dict()
+
+ self.account_hooks = ['account-application',
+ 'account-provider',
+ 'account-qml-plugin',
+ 'account-service']
+ 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, xml) = 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] = xml
+
+ def _extract_account(self, app, account_type):
+ '''Extract accounts'''
+ 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)
+ else:
+ try:
+ tree = etree.parse(fn)
+ xml = tree.getroot()
+ except Exception as e:
+ error("accounts xml unparseable: %s (%s):\n%s" % (bn, str(e),
+ contents))
+ return (fn, xml)
+
+ def check_application(self):
+ '''Check application'''
+ for app in sorted(self.accounts.keys()):
+ account_type = "account-application"
+
+ t = 'info'
+ n = '%s_%s_root' % (app, account_type)
+ s = "OK"
+ if not account_type 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 = '%s_%s_id' % (app, account_type)
+ s = "OK"
+ expected_id = "%s_%s" % (self.manifest["name"], app)
+ if "id" not in self.accounts[app][account_type].keys():
+ t = 'error'
+ s = "Could not find 'id' in application tag"
+ elif self.accounts[app][account_type].get("id") != expected_id:
+ t = 'error'
+ s = "id '%s' != '%s'" % (
+ self.accounts[app][account_type].get("id"),
+ expected_id)
+ self._add_result(t, n, s)
+
+ t = 'info'
+ n = '%s_%s_services' % (app, account_type)
+ 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 = '%s_%s_service' % (app, account_type)
+ 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'''
+ for app in sorted(self.accounts.keys()):
+ account_type = "account-service"
+
+ t = 'info'
+ n = '%s_%s_root' % (app, account_type)
+ s = "OK"
+ if not account_type 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 = '%s_%s_id' % (app, account_type)
+ s = "OK"
+ expected_id = "%s_%s" % (self.manifest["name"], app)
+ if "id" not in self.accounts[app][account_type].keys():
+ t = 'error'
+ s = "Could not find 'id' in service tag"
+ elif self.accounts[app][account_type].get("id") != expected_id:
+ t = 'error'
+ s = "id '%s' != '%s'" % (
+ self.accounts[app][account_type].get("id"),
+ expected_id)
+ self._add_result(t, n, s)
+
+ if t == 'error':
+ continue
+
+ for tag in ['type', 'name', 'provider']:
+ t = 'info'
+ n = '%s_%s_%s' % (app, account_type, tag)
+ 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)
+
+ def check_provider(self):
+ '''Check provider'''
+ for app in sorted(self.accounts.keys()):
+ account_type = "account-provider"
+
+ t = 'info'
+ n = '%s_%s' % (app, account_type)
+ s = "OK"
+ if not account_type in self.accounts[app]:
+ s = "OK (missing)"
+ self._add_result(t, n, s)
+ continue
+ else:
+ t = 'error'
+ s = "(MANUAL REVIEW) '%s' not allowed" % account_type
+ self._add_result(t, n, s)
+
+ t = 'info'
+ n = '%s_%s_root' % (app, account_type)
+ 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 = '%s_%s_id' % (app, account_type)
+ s = "OK"
+ expected_id = "%s_%s" % (self.manifest["name"], app)
+ if "id" not in self.accounts[app][account_type].keys():
+ t = 'error'
+ s = "Could not find 'id' in provider tag"
+ elif self.accounts[app][account_type].get("id") != expected_id:
+ t = 'error'
+ s = "id '%s' != '%s'" % (
+ self.accounts[app][account_type].get("id"),
+ expected_id)
+ self._add_result(t, n, s)
+
+ def check_qml_plugin(self):
+ '''Check qml-plugin'''
+ for app in sorted(self.accounts.keys()):
+ account_type = "account-qml-plugin"
+
+ t = 'info'
+ n = '%s_%s' % (app, account_type)
+ s = "OK"
+ if not account_type in self.accounts[app]:
+ s = "OK (missing)"
+ self._add_result(t, n, s)
+ continue
+ else:
+ t = 'error'
+ s = "(MANUAL REVIEW) '%s' not allowed" % account_type
+ self._add_result(t, n, s)
diff -Nru click-reviewers-tools-0.7/bin/clickreviews/cr_push_helper.py click-reviewers-tools-0.8/bin/clickreviews/cr_push_helper.py
--- click-reviewers-tools-0.7/bin/clickreviews/cr_push_helper.py 1970-01-01 00:00:00.000000000 +0000
+++ click-reviewers-tools-0.8/bin/clickreviews/cr_push_helper.py 2014-07-25 13:47:22.000000000 +0000
@@ -0,0 +1,132 @@
+'''cr_push_helper.py: click push-helper checks'''
+#
+# 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 __future__ import print_function
+
+from clickreviews.cr_common import ClickReview, error, open_file_read, msg
+import json
+import os
+
+
+class ClickReviewPushHelper(ClickReview):
+ '''This class represents click lint reviews'''
+ def __init__(self, fn):
+ ClickReview.__init__(self, fn, "push_helper")
+
+ self.required_keys = ['exec']
+ self.optional_keys = ['app_id']
+
+ self.push_helper_files = dict() # click-show-files and tests
+ self.push_helper = dict()
+ 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/urls 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'''
+ for app in sorted(self.push_helper):
+ for k in self.push_helper[app].keys():
+ t = "info"
+ n = "valid_%s_%s" % (app, 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 = "valid_%s_required_%s" % (app, 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'''
+ for app in sorted(self.push_helper):
+ unknown = []
+ t = "info"
+ n = "unknown_%s" % 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'''
+ for app in sorted(self.manifest['hooks']):
+ if app not in self.push_helper:
+ continue
+
+ t = "info"
+ n = "other_hooks_%s" % 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)
diff -Nru click-reviewers-tools-0.7/bin/clickreviews/cr_scope.py click-reviewers-tools-0.8/bin/clickreviews/cr_scope.py
--- click-reviewers-tools-0.7/bin/clickreviews/cr_scope.py 1970-01-01 00:00:00.000000000 +0000
+++ click-reviewers-tools-0.8/bin/clickreviews/cr_scope.py 2014-07-25 13:47:22.000000000 +0000
@@ -0,0 +1,135 @@
+'''cr_scope.py: click scope'''
+#
+# 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 __future__ import print_function
+
+from clickreviews.cr_common import ClickReview, error, msg
+import configparser
+import os
+
+
+class ClickReviewScope(ClickReview):
+ '''This class represents click lint reviews'''
+ def __init__(self, fn):
+ ClickReview.__init__(self, fn, "scope")
+
+ self.scopes = dict()
+ 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.ini" % self.manifest['name'])
+ 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(ini_fn)
+ except Exception:
+ 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'''
+ for app in sorted(self.scopes.keys()):
+ t = 'info'
+ n = 'ini_%s_scope_section' % app
+ s = "OK"
+
+ if len(self.scopes[app]["scope_config"].sections()) > 1:
+ t = 'error'
+ s = "'%s' has too many sections: %s" % (
+ self.scopes[app]["ini_file_rel"],
+ ", ".join(self.scopes[app]["scope_config"].sections()))
+ elif "ScopeConfig" not in \
+ self.scopes[app]["scope_config"].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 = ['scoperunner',
+ 'displayname',
+ 'icon',
+ 'searchhint']
+ optional = ['description',
+ 'author',
+ 'art']
+
+ missing = []
+ t = 'info'
+ n = 'ini_%s_scope_required_fields' % (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 = 'ini_%s_scope_unknown_fields' % (app)
+ s = 'OK'
+ unknown = []
+ for f in self.scopes[app]["scope_config"]['ScopeConfig'].keys():
+ if f.lower() not in required and f.lower() not in optional:
+ unknown.append(f.lower())
+
+ 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)
diff -Nru click-reviewers-tools-0.7/bin/clickreviews/cr_security.py click-reviewers-tools-0.8/bin/clickreviews/cr_security.py
--- click-reviewers-tools-0.7/bin/clickreviews/cr_security.py 2014-05-05 08:33:55.000000000 +0000
+++ click-reviewers-tools-0.8/bin/clickreviews/cr_security.py 2014-07-25 13:47:22.000000000 +0000
@@ -18,25 +18,21 @@
from clickreviews.cr_common import ClickReview, error, warn
import clickreviews.cr_common as cr_common
+import clickreviews.apparmor_policy as apparmor_policy
import glob
import json
import os
-easyprof_dir = "/usr/share/apparmor/easyprof"
-if not os.path.isdir(easyprof_dir):
- error("Error importing easyprof. Please install apparmor-easyprof")
-if not os.path.isdir(os.path.join(easyprof_dir, "templates/ubuntu")):
- error("Error importing easyprof. Please install apparmor-easyprof-ubuntu")
-
-import apparmor.easyprof
-
class ClickReviewSecurity(ClickReview):
'''This class represents click lint reviews'''
def __init__(self, fn):
ClickReview.__init__(self, fn, "security")
- self.supported_policy_versions = self._get_supported_policy_versions()
+ local_copy = os.path.join(os.path.dirname(__file__),
+ '../data/apparmor-easyprof-ubuntu.json')
+ p = apparmor_policy.ApparmorPolicy(local_copy)
+ self.aa_policy = p.policy
self.all_fields = ['abstractions',
'author',
@@ -70,6 +66,8 @@
'video',
'webview']
+ self.allowed_push_helper_policy_groups = ['push-notification-client']
+
self.redflag_templates = ['unconfined']
self.extraneous_templates = ['ubuntu-sdk',
'default']
@@ -79,6 +77,7 @@
# like 'ubuntu-sdk-14.04-html5', 'ubuntu-sdk-14.04-platform', etc
self.major_framework_policy = {'ubuntu-sdk-13.10': 1.0,
'ubuntu-sdk-14.04': 1.1,
+ 'ubuntu-sdk-14.10': 1.2,
}
self.security_manifests = dict()
@@ -132,31 +131,6 @@
k, mp))
return m
- def _get_policy_group_meta(self, group, meta, vendor, version):
- '''Get meta-information from the policy group'''
- cmd_args = ['--show-policy-group', '--policy-groups=%s' % group,
- '--policy-version=%s' % version,
- '--policy-vendor=%s' % vendor]
- (options, args) = apparmor.easyprof.parse_args(cmd_args)
- try:
- easyp = apparmor.easyprof.AppArmorEasyProfile(None, options)
- tmp = easyp.get_policygroup(group)
- except apparmor.easyprof.AppArmorException:
- warn("'%s' failed" % " ".join(cmd_args))
- return ""
-
- text = ""
- for line in tmp.splitlines():
- if line.startswith("# %s: " % meta):
- text = line.split(':', 1)[1].strip()
- elif text != "":
- if line.startswith("# "):
- text += line[2:]
- else:
- break
-
- return text
-
def _get_security_manifest(self, app):
'''Get the security manifest for app'''
if app not in self.manifest['hooks']:
@@ -168,21 +142,72 @@
m = self.security_manifests[f]
return (f, m)
- def _get_supported_policy_versions(self):
+ def _get_policy_versions(self, vendor):
'''Get the supported AppArmor policy versions'''
- version_dirs = sorted(glob.glob("%s/templates/ubuntu/*" %
- easyprof_dir))
+ if vendor not in self.aa_policy:
+ error("Could not find vendor '%s'" % vendor, do_exit=False)
+ return None
+
supported_policy_versions = []
- for d in version_dirs:
- if not os.path.isdir(d):
- continue
- try:
- supported_policy_versions.append(float(os.path.basename(d)))
- except TypeError:
- continue
- supported_policy_versions = sorted(supported_policy_versions)
+ for i in self.aa_policy[vendor].keys():
+ supported_policy_versions.append("%.1f" % float(i))
+
+ return sorted(supported_policy_versions)
- return supported_policy_versions
+ def _get_templates(self, vendor, version, aa_type="all"):
+ '''Get templates by type'''
+ 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 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_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 _get_policy_groups(self, vendor, version, aa_type="all"):
+ '''Get policy groups by type'''
+ 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'''
+ for t in self.aa_policy[vendor][version]['policy_groups']:
+ if policy_group in self.aa_policy[vendor][version]['policy_groups'][t]:
+ return t
def check_policy_vendor(self):
'''Check policy_vendor'''
@@ -213,17 +238,13 @@
if 'policy_vendor' in m:
vendor = m['policy_vendor']
version = str(m['policy_version'])
- cmd_args = ['--list-templates', '--policy-vendor=%s' % vendor,
- '--policy-version=%s' % version]
- (options, args) = apparmor.easyprof.parse_args(cmd_args)
- try:
- apparmor.easyprof.AppArmorEasyProfile(None, options)
- except Exception:
+ 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 = sorted(self.supported_policy_versions)[-1]
+ highest = self._get_highest_policy_version(vendor)
t = 'info'
n = 'policy_version_is_highest (%s, %s)' % (str(highest), f)
s = "OK"
@@ -287,31 +308,16 @@
if 'policy_vendor' in m:
vendor = m['policy_vendor']
version = str(m['policy_version'])
- cmd_args = ['--list-templates', '--policy-vendor=%s' % vendor,
- '--policy-version=%s' % version]
- (options, args) = apparmor.easyprof.parse_args(cmd_args)
- templates = []
- try:
- easyp = apparmor.easyprof.AppArmorEasyProfile(None, options)
- templates = easyp.get_templates()
- except Exception:
- t = 'error'
- s = 'could not find policy for %s/%s' % (vendor, version)
- self._add_result(t, n, s)
- continue
+
+ 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)
- # If we got here, we can see if a valid template was specified
- found = False
- for i in templates:
- if os.path.basename(i) == m['template']:
- found = True
- break
- if not found:
+ if m['template'] not in self._get_templates(vendor, version):
t = 'error'
s = "specified unsupported template '%s'" % m['template']
@@ -353,13 +359,85 @@
self._add_result(t, n, s)
+ def check_template_push_helpers(self):
+ '''Check template for push-helpers'''
+ for app in sorted(self.manifest['hooks']):
+ (f, m) = self._get_security_manifest(app)
+ t = 'info'
+ n = 'template_push_helper(%s)' % f
+ s = "OK"
+ if 'push-helper' not in self.manifest['hooks'][app]:
+ continue
+ if 'template' in m and m['template'] != "ubuntu-sdk":
+ t = 'error'
+ s = "template is not 'ubuntu-sdk'"
+ self._add_result(t, n, s)
+
+ def check_policy_groups_push_helpers(self):
+ '''Check policy_groups for push-helpers'''
+ for app in sorted(self.manifest['hooks']):
+ (f, m) = self._get_security_manifest(app)
+ t = 'info'
+ n = 'policy_groups_push_helper(%s)' % f
+ s = "OK"
+ if 'push-helper' not in self.manifest['hooks'][app]:
+ 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)
+ 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'''
+ for app in sorted(self.manifest['hooks']):
+ (f, m) = self._get_security_manifest(app)
+ t = 'info'
+ n = 'policy_groups_scopes (%s)' % 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 shouldn't have access to anything
+ # (for now, this may change with trust store (eg, location)
+ if p != 'networking':
+ 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(self):
'''Check policy_groups'''
for app in sorted(self.manifest['hooks']):
(f, m) = self._get_security_manifest(app)
t = 'info'
- n = 'policy_groups_exists (%s)' % f
+ n = 'policy_groups_exists_%s (%s)' % (app, f)
if 'policy_groups' not in m:
# If template not specified, we just use the default
self._add_result('warn', n, 'no policy groups specified')
@@ -374,18 +452,8 @@
if 'policy_vendor' in m:
vendor = m['policy_vendor']
version = str(m['policy_version'])
- cmd_args = ['--list-policy-groups', '--policy-vendor=%s' % vendor,
- '--policy-version=%s' % version]
- (options, args) = apparmor.easyprof.parse_args(cmd_args)
- policy_groups = []
- try:
- easyp = apparmor.easyprof.AppArmorEasyProfile(None, options)
- policy_groups = easyp.get_policy_groups()
- except Exception:
- t = 'error'
- s = 'could not find policy for %s/%s' % (vendor, version)
- self._add_result(t, n, s)
- continue
+
+ policy_groups = self._get_policy_groups(version=version, vendor=vendor)
if len(policy_groups) < 1:
t = 'error'
s = 'could not find policy groups'
@@ -395,7 +463,7 @@
# Check for duplicates
t = 'info'
- n = 'policy_groups_duplicates (%s)' % f
+ n = 'policy_groups_duplicates_%s (%s)' % (app, f)
s = 'OK'
tmp = []
for p in m['policy_groups']:
@@ -410,7 +478,7 @@
# If we got here, we can see if valid policy groups were specified
for i in m['policy_groups']:
t = 'info'
- n = 'policy_groups_valid (%s)' % i
+ n = 'policy_groups_valid_%s (%s)' % (app, i)
s = 'OK'
# SDK will leave and empty policy group, report but don't
@@ -433,16 +501,22 @@
if found:
t = 'info'
- n = 'policy_groups_safe (%s)' % i
+ n = 'policy_groups_safe_%s (%s)' % (app, i)
s = 'OK'
- usage = self._get_policy_group_meta(i, "Usage",
- vendor, version)
- if usage != "common":
- desc = self._get_policy_group_meta(i, "Description",
- vendor, version)
+
+ 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 = "(MANUAL REVIEW) %s policy group " % aa_type + \
+ "'%s': vetted applications only" % (i)
+ elif aa_type != "common":
t = 'error'
- s = "(MANUAL REVIEW) %s policy group " % usage + \
- "'%s': %s" % (i, desc)
+ s = "policy group '%s' has" % i + \
+ "unknown type '%s'" % (aa_type)
self._add_result(t, n, s)
def check_ignored(self):
diff -Nru click-reviewers-tools-0.7/bin/clickreviews/cr_skeleton.py click-reviewers-tools-0.8/bin/clickreviews/cr_skeleton.py
--- click-reviewers-tools-0.7/bin/clickreviews/cr_skeleton.py 2014-05-05 08:33:55.000000000 +0000
+++ click-reviewers-tools-0.8/bin/clickreviews/cr_skeleton.py 2014-07-25 13:47:22.000000000 +0000
@@ -1,6 +1,6 @@
'''cr_skeleton.py: click skeleton'''
#
-# Copyright (C) 2013 Canonical Ltd.
+# 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
diff -Nru click-reviewers-tools-0.7/bin/clickreviews/cr_tests.py click-reviewers-tools-0.8/bin/clickreviews/cr_tests.py
--- click-reviewers-tools-0.7/bin/clickreviews/cr_tests.py 2014-05-05 08:33:55.000000000 +0000
+++ click-reviewers-tools-0.8/bin/clickreviews/cr_tests.py 2014-07-25 13:47:22.000000000 +0000
@@ -32,6 +32,14 @@
TEST_SECURITY = dict()
TEST_DESKTOP = dict()
TEST_WEBAPP_MANIFESTS = dict()
+TEST_URLS = dict()
+TEST_SCOPES = dict()
+TEST_CONTENT_HUB = dict()
+TEST_ACCOUNTS_APPLICATION = dict()
+TEST_ACCOUNTS_PROVIDER = dict()
+TEST_ACCOUNTS_QML_PLUGIN = dict()
+TEST_ACCOUNTS_SERVICE = dict()
+TEST_PUSH_HELPER = dict()
#
@@ -59,6 +67,18 @@
"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",
]
@@ -74,7 +94,7 @@
def _get_security_supported_policy_versions(self):
'''Pretend we read the contens of /usr/share/apparmor/easyprof'''
- return [1.0, 1.1]
+ return [1.0, 1.1, 1.2]
def _extract_desktop_entry(self, app):
@@ -92,6 +112,48 @@
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 == "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])
+
+
# 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. Set up a list of patches, but don't start them. Create the helper
@@ -140,9 +202,6 @@
patches.append(patch(
'clickreviews.cr_security.ClickReviewSecurity._get_security_manifest',
_get_security_manifest))
-patches.append(patch(
- 'clickreviews.cr_security.ClickReviewSecurity._get_supported_policy_versions',
- _get_security_supported_policy_versions))
# desktop overrides
patches.append(patch(
@@ -155,6 +214,31 @@
'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))
+
def mock_patch():
'''Call in setup of child'''
@@ -208,11 +292,21 @@
"%s.json" % 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()
# hooks
self.test_security_manifests = dict()
self.test_desktop_files = dict()
+ self.test_url_dispatcher = dict()
+ self.test_scopes = dict()
+ self.test_content_hub = 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()
for app in self.test_manifest["hooks"].keys():
# setup security manifest for each app
self.set_test_security_manifest(app, 'policy_groups',
@@ -233,8 +327,34 @@
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 content-hub entries in manifest
+ 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 content-hub entries in manifest
+ self.set_test_push_helper(app, None, None)
+
self._update_test_security_manifests()
self._update_test_desktop_files()
+ self._update_test_url_dispatcher()
+ self._update_test_scopes()
+ self._update_test_content_hub()
+ 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()
# webapps manifests (leave empty for now)
self.test_webapp_manifests = dict()
@@ -280,6 +400,75 @@
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_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_name(self):
self.test_name = "%s_%s_%s.click" % (self.test_control['Package'],
self.test_control['Version'],
@@ -370,7 +559,6 @@
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()
@@ -386,6 +574,90 @@
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 == "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 == "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 content-hub 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 setUp(self):
'''Make sure our patches are applied everywhere'''
global patches
@@ -402,6 +674,22 @@
TEST_SECURITY = 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_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_APPLICATION = dict()
+ global TEST_PUSH_HELPER
+ TEST_PUSH_HELPER = dict()
self._reset_test_data()
cr_common.recursive_rm(self.desktop_tmpdir)
diff -Nru click-reviewers-tools-0.7/bin/clickreviews/cr_url_dispatcher.py click-reviewers-tools-0.8/bin/clickreviews/cr_url_dispatcher.py
--- click-reviewers-tools-0.7/bin/clickreviews/cr_url_dispatcher.py 1970-01-01 00:00:00.000000000 +0000
+++ click-reviewers-tools-0.8/bin/clickreviews/cr_url_dispatcher.py 2014-07-25 13:47:22.000000000 +0000
@@ -0,0 +1,152 @@
+'''cr_url dispatcher.py: click url_dispatcher'''
+#
+# 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 __future__ import print_function
+
+from clickreviews.cr_common import ClickReview, error, open_file_read, msg
+import json
+import os
+
+# https://wiki.ubuntu.com/URLDispatcher
+
+
+class ClickReviewUrlDispatcher(ClickReview):
+ '''This class represents click lint reviews'''
+ def __init__(self, fn):
+ ClickReview.__init__(self, fn, "url_dispatcher")
+
+ self.required_keys = ['protocol']
+ self.optional_keys = ['domain-suffix']
+
+ self.url_dispatcher_files = dict() # click-show-files and tests
+ self.url_dispatcher = dict()
+ 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']
+ fn = os.path.join(self.unpack_dir, u)
+
+ 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'''
+ for app in sorted(self.url_dispatcher):
+ for r in self.required_keys:
+ found = False
+ t = 'info'
+ n = 'required_entry_%s_%s' % (app, 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'''
+ for app in sorted(self.url_dispatcher):
+ for o in self.optional_keys:
+ found = False
+ t = 'info'
+ n = 'optional_entry_%s_%s' % (app, 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'''
+ for app in sorted(self.url_dispatcher):
+ unknown = []
+ for entry in self.url_dispatcher[app]:
+ t = 'info'
+ n = 'unknown_entry_%s' % 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)
diff -Nru click-reviewers-tools-0.7/bin/clickreviews/frameworks.py click-reviewers-tools-0.8/bin/clickreviews/frameworks.py
--- click-reviewers-tools-0.7/bin/clickreviews/frameworks.py 1970-01-01 00:00:00.000000000 +0000
+++ click-reviewers-tools-0.8/bin/clickreviews/frameworks.py 2014-07-25 13:47:22.000000000 +0000
@@ -0,0 +1,48 @@
+#
+# 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')
+
+# XXX: This is a hack and will be gone, as soon as myapps has an API for this.
+FRAMEWORKS_DATA_URL = \
+ "http://bazaar.launchpad.net/~ubuntu-core-dev/+junk/frameworks/view/head:/frameworks.json"
+
+
+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, local_copy_fn=None):
+ self.FRAMEWORKS = clickreviews.remote.read_cr_file(USER_DATA_FILE,
+ FRAMEWORKS_DATA_URL,
+ local_copy_fn)
+
+ for k, v in self.FRAMEWORKS.items():
+ if v == 'deprecated':
+ self.DEPRECATED_FRAMEWORKS.append(k)
+ elif v == 'obsolete':
+ self.OBSOLETE_FRAMEWORKS.append(k)
+ elif v == 'available':
+ self.AVAILABLE_FRAMEWORKS.append(k)
diff -Nru click-reviewers-tools-0.7/bin/clickreviews/remote.py click-reviewers-tools-0.8/bin/clickreviews/remote.py
--- click-reviewers-tools-0.7/bin/clickreviews/remote.py 1970-01-01 00:00:00.000000000 +0000
+++ click-reviewers-tools-0.8/bin/clickreviews/remote.py 2014-07-25 13:47:22.000000000 +0000
@@ -0,0 +1,96 @@
+#
+# 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.getctime(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_file(fn, url, data_dir=DATA_DIR):
+ try:
+ f = request.urlopen(url)
+ except (HTTPError, URLError) as error:
+ abort('Data not retrieved because %s.' % error)
+ except timeout:
+ abort('Socket timed out.')
+ html = f.read()
+ # 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"))
+ f = request.urlopen(download_link)
+ if not f:
+ abort()
+ 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(f.read())
+
+
+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):
+ j = json.loads(open(local_copy_fn, 'r').read())
+ else:
+ if _update_is_necessary(fn) and _update_is_possible(url):
+ get_remote_file(fn, url)
+ if os.path.exists(fn):
+ j = json.loads(open(fn, 'r').read())
+ return j
diff -Nru click-reviewers-tools-0.7/bin/clickreviews/tests/test_aaa_example_cr_skeleton.py click-reviewers-tools-0.8/bin/clickreviews/tests/test_aaa_example_cr_skeleton.py
--- click-reviewers-tools-0.7/bin/clickreviews/tests/test_aaa_example_cr_skeleton.py 2014-05-05 08:33:55.000000000 +0000
+++ click-reviewers-tools-0.8/bin/clickreviews/tests/test_aaa_example_cr_skeleton.py 2014-07-25 13:47:22.000000000 +0000
@@ -1,6 +1,6 @@
'''test_cr_skeleton.py: tests for the cr_skeleton module'''
#
-# Copyright (C) 2013 Canonical Ltd.
+# 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
diff -Nru click-reviewers-tools-0.7/bin/clickreviews/tests/test_cr_content_hub.py click-reviewers-tools-0.8/bin/clickreviews/tests/test_cr_content_hub.py
--- click-reviewers-tools-0.7/bin/clickreviews/tests/test_cr_content_hub.py 1970-01-01 00:00:00.000000000 +0000
+++ click-reviewers-tools-0.8/bin/clickreviews/tests/test_cr_content_hub.py 2014-07-25 13:47:22.000000000 +0000
@@ -0,0 +1,111 @@
+'''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 setUp(self):
+ # Monkey patch various file access classes. stop() is handled with
+ # addCleanup in super()
+ cr_tests.mock_patch()
+ super()
+
+ 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)
diff -Nru click-reviewers-tools-0.7/bin/clickreviews/tests/test_cr_desktop.py click-reviewers-tools-0.8/bin/clickreviews/tests/test_cr_desktop.py
--- click-reviewers-tools-0.7/bin/clickreviews/tests/test_cr_desktop.py 2014-05-05 08:33:55.000000000 +0000
+++ click-reviewers-tools-0.8/bin/clickreviews/tests/test_cr_desktop.py 2014-07-25 13:47:22.000000000 +0000
@@ -36,7 +36,6 @@
expected['warn'] = dict()
expected['error'] = dict()
expected['info']['desktop_files_usable'] = {"text": "OK"}
- expected['info']['desktop_files_available'] = {"text": "OK"}
self.check_results(r, expected=expected)
def test_check_desktop_file_valid(self):
diff -Nru click-reviewers-tools-0.7/bin/clickreviews/tests/test_cr_lint.py click-reviewers-tools-0.8/bin/clickreviews/tests/test_cr_lint.py
--- click-reviewers-tools-0.7/bin/clickreviews/tests/test_cr_lint.py 2014-05-05 08:33:55.000000000 +0000
+++ click-reviewers-tools-0.8/bin/clickreviews/tests/test_cr_lint.py 2014-07-25 13:47:22.000000000 +0000
@@ -506,6 +506,27 @@
expected_counts = {'info': None, 'warn': 1, 'error': 0}
self.check_results(r, expected_counts)
+ def test_check_maintainer_email_special(self):
+ '''Test check_maintainer() - ubuntu-devel-discuss@lists.ubuntu.com'''
+ self.set_test_control("Package", "com.canonical.app")
+ self.set_test_manifest("maintainer",
+ "Ubuntu Core Developers "
+ "")
+ 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)
+
+ expected = dict()
+ expected['info'] = dict()
+ expected['warn'] = dict()
+ expected['error'] = dict()
+ expected['info']['lint_maintainer_domain'] = \
+ {"text": "OK (email 'ubuntu-devel-discuss@lists.ubuntu.com' long, "
+ "but special case"}
+ self.check_results(r, expected=expected)
+
def test_check_icon(self):
'''Test check_icon()'''
self.set_test_manifest("icon", "someicon")
@@ -580,11 +601,11 @@
def test_check_framework(self):
'''Test check_framework()'''
- self.set_test_manifest("framework", "ubuntu-sdk-14.04-qml-dev1")
+ 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': 2, 'warn': 0, 'error': 0}
+ expected_counts = {'info': 1, 'warn': 0, 'error': 0}
self.check_results(r, expected_counts)
def test_check_framework_bad(self):
@@ -596,11 +617,123 @@
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.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.set_test_manifest("framework", "ubuntu-sdk-13.10")
+ 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)
+
+ 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_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': None, 'warn': 0, 'error': 1}
+ 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': 7, 'warn': 0, 'error': 0}
+ self.check_results(r, expected_counts)
+
+ 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_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_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_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_redflagged(self):
+ '''Test check_hooks_redflagged()'''
+ 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)
diff -Nru click-reviewers-tools-0.7/bin/clickreviews/tests/test_cr_online_accounts.py click-reviewers-tools-0.8/bin/clickreviews/tests/test_cr_online_accounts.py
--- click-reviewers-tools-0.7/bin/clickreviews/tests/test_cr_online_accounts.py 1970-01-01 00:00:00.000000000 +0000
+++ click-reviewers-tools-0.8/bin/clickreviews/tests/test_cr_online_accounts.py 2014-07-25 13:47:22.000000000 +0000
@@ -0,0 +1,317 @@
+'''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 lxml.etree as etree
+
+
+class TestClickReviewAccounts(cr_tests.TestClickReview):
+ """Tests for the lint review tool."""
+ def setUp(self):
+ # Monkey patch various file access classes. stop() is handled with
+ # addCleanup in super()
+ cr_tests.mock_patch()
+ super()
+
+ def _stub_application(self, root=None, id=None, do_subtree=True):
+ '''Stub application xml'''
+ if id is None:
+ id = "%s_%s" % (self.test_manifest["name"], self.default_appname)
+ if root is None:
+ root = "application"
+ if id == "":
+ xml = etree.Element(root)
+ else:
+ xml = etree.Element(root, id="%s" % id)
+ if do_subtree:
+ services = etree.SubElement(xml, "services")
+ elem1 = etree.SubElement(services, "service", id="element1")
+ desc1 = etree.SubElement(elem1, "description")
+ desc1.text = "elem1 description"
+ 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 id is None:
+ id = "%s_%s" % (self.test_manifest["name"], self.default_appname)
+ if root is None:
+ root = "service"
+ if id == "":
+ xml = etree.Element(root)
+ else:
+ xml = etree.Element(root, id="%s" % id)
+ if do_subtree:
+ service_type = etree.SubElement(xml, "type")
+ service_type.text = "webapps"
+ 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 id is None:
+ id = "%s_%s" % (self.test_manifest["name"], self.default_appname)
+ if root is None:
+ root = "provider"
+ if id == "":
+ 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_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_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_wrong_id(self):
+ '''Test check_application() - wrong id'''
+ xml = self._stub_application(id="nomatch")
+ 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_id(self):
+ '''Test check_application() - missing id'''
+ xml = self._stub_application(id="")
+ 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_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)
+ c = ClickReviewAccounts(self.test_name)
+ c.check_service()
+ r = c.click_report
+ expected_counts = {'info': 5, 'warn': 0, 'error': 0}
+ self.check_results(r, expected_counts)
+
+ def test_check_service_not_specified(self):
+ '''Test check_service() - no 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_wrong_id(self):
+ '''Test check_service() - wrong id'''
+ xml = self._stub_service(id="nomatch")
+ self.set_test_account(self.default_appname, "account-service", 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_id(self):
+ '''Test check_service() - missing id'''
+ xml = self._stub_service(id="")
+ self.set_test_account(self.default_appname, "account-service", 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_wrong_root(self):
+ '''Test check_service() - wrong root'''
+ xml = self._stub_service(root="wrongroot")
+ self.set_test_account(self.default_appname, "account-service", 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_type(self):
+ '''Test check_service() - missing type'''
+ xml = self._stub_service(do_subtree=False)
+ service_name = etree.SubElement(xml, "name")
+ service_name.text = "Foo"
+ service_provider = etree.SubElement(xml, "provider")
+ service_provider.text = "some-provider"
+ self.set_test_account(self.default_appname, "account-service", 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_type = etree.SubElement(xml, "type")
+ service_type.text = "webapps"
+ service_provider = etree.SubElement(xml, "provider")
+ service_provider.text = "some-provider"
+ self.set_test_account(self.default_appname, "account-service", 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_type = etree.SubElement(xml, "type")
+ service_type.text = "webapps"
+ service_name = etree.SubElement(xml, "name")
+ service_name.text = "Foo"
+ self.set_test_account(self.default_appname, "account-service", 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)
+ c = ClickReviewAccounts(self.test_name)
+ c.check_provider()
+ r = c.click_report
+ # provider prompts manual review, so for now, need to have error as 1
+ expected_counts = {'info': 2, 'warn': 0, 'error': 1}
+ self.check_results(r, expected_counts)
+
+ def test_check_provider_not_specified(self):
+ '''Test check_provider() - no specified'''
+ c = ClickReviewAccounts(self.test_name)
+ c.check_provider()
+ r = c.click_report
+ expected_counts = {'info': 0, 'warn': 0, 'error': 0}
+ self.check_results(r, expected_counts)
+
+ def test_check_provider_missing_id(self):
+ '''Test check_provider() - missing id'''
+ xml = self._stub_provider(id="")
+ self.set_test_account(self.default_appname, "account-provider", xml)
+ c = ClickReviewAccounts(self.test_name)
+ c.check_provider()
+ r = c.click_report
+ # provider prompts manual review, so for now, need to have error as +1
+ expected_counts = {'info': None, 'warn': 0, 'error': 2}
+ self.check_results(r, expected_counts)
+
+ def test_check_provider_wrong_id(self):
+ '''Test check_provider() - wrong id'''
+ xml = self._stub_provider(id="wrongid")
+ self.set_test_account(self.default_appname, "account-provider", xml)
+ c = ClickReviewAccounts(self.test_name)
+ c.check_provider()
+ r = c.click_report
+ # provider prompts manual review, so for now, need to have error as +1
+ expected_counts = {'info': None, 'warn': 0, 'error': 2}
+ 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)
+ c = ClickReviewAccounts(self.test_name)
+ c.check_qml_plugin()
+ r = c.click_report
+ # provider prompts manual review, so for now, need to have error as 1
+ expected_counts = {'info': 0, 'warn': 0, 'error': 1}
+ self.check_results(r, expected_counts)
+
+ def test_check_qml_plugin_not_specified(self):
+ '''Test check_qml_plugin() - no specified'''
+ c = ClickReviewAccounts(self.test_name)
+ c.check_qml_plugin()
+ r = c.click_report
+ expected_counts = {'info': 0, 'warn': 0, 'error': 0}
+ self.check_results(r, expected_counts)
diff -Nru click-reviewers-tools-0.7/bin/clickreviews/tests/test_cr_push_helper.py click-reviewers-tools-0.8/bin/clickreviews/tests/test_cr_push_helper.py
--- click-reviewers-tools-0.7/bin/clickreviews/tests/test_cr_push_helper.py 1970-01-01 00:00:00.000000000 +0000
+++ click-reviewers-tools-0.8/bin/clickreviews/tests/test_cr_push_helper.py 2014-07-25 13:47:22.000000000 +0000
@@ -0,0 +1,138 @@
+'''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 setUp(self):
+ # Monkey patch various file access classes. stop() is handled with
+ # addCleanup in super()
+ cr_tests.mock_patch()
+ super()
+
+ 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)
diff -Nru click-reviewers-tools-0.7/bin/clickreviews/tests/test_cr_scope.py click-reviewers-tools-0.8/bin/clickreviews/tests/test_cr_scope.py
--- click-reviewers-tools-0.7/bin/clickreviews/tests/test_cr_scope.py 1970-01-01 00:00:00.000000000 +0000
+++ click-reviewers-tools-0.8/bin/clickreviews/tests/test_cr_scope.py 2014-07-25 13:47:22.000000000 +0000
@@ -0,0 +1,145 @@
+'''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 setUp(self):
+ # Monkey patch various file access classes. stop() is handled with
+ # addCleanup in super()
+ cr_tests.mock_patch()
+ super()
+
+ 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.ini" % (scope["dir_rel"],
+ self.default_appname)
+ 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,
+ 'DisplayName': 'Foo',
+ 'Description': 'Some description',
+ 'Author': 'Foo Ltd.',
+ 'Art': '',
+ 'Icon': 'foo.svg',
+ 'SearchHint': 'Search Foo',
+ }
+
+ 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': 3, 'warn': 0, 'error': 0}
+ self.check_results(r, expected_counts)
+
+ def test_check_scope_ini_missing_required1(self):
+ '''Test check_scope_ini() - missing ScopeRunner'''
+ config = self._stub_config()
+ del config['ScopeRunner']
+ 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 Icon'''
+ config = self._stub_config()
+ del config['Icon']
+ 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 SearchHint'''
+ config = self._stub_config()
+ del config['SearchHint']
+ 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_required5(self):
+ '''Test check_scope_ini() - missing multiple'''
+ config = self._stub_config()
+ del config['ScopeRunner']
+ del config['DisplayName']
+ del config['Icon']
+ del config['SearchHint']
+ 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_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)
diff -Nru click-reviewers-tools-0.7/bin/clickreviews/tests/test_cr_security.py click-reviewers-tools-0.8/bin/clickreviews/tests/test_cr_security.py
--- click-reviewers-tools-0.7/bin/clickreviews/tests/test_cr_security.py 2014-05-05 08:33:55.000000000 +0000
+++ click-reviewers-tools-0.8/bin/clickreviews/tests/test_cr_security.py 2014-07-25 13:47:22.000000000 +0000
@@ -46,7 +46,7 @@
def test_check_policy_version_highest(self):
'''Test check_policy_version() - highest'''
c = ClickReviewSecurity(self.test_name)
- highest_version = sorted(c.supported_policy_versions)[-1]
+ highest_version = c._get_highest_policy_version("ubuntu")
version = highest_version
self.set_test_security_manifest(self.default_appname,
"policy_version", version)
@@ -68,7 +68,7 @@
self.set_test_security_manifest(self.default_appname,
"policy_version", bad_version)
- highest = sorted(c.supported_policy_versions)[-1]
+ highest = c._get_highest_policy_version("ubuntu")
c.check_policy_version()
report = c.click_report
@@ -88,7 +88,7 @@
def test_check_policy_version_low(self):
'''Test check_policy_version() - low version'''
c = ClickReviewSecurity(self.test_name)
- highest = sorted(c.supported_policy_versions)[-1]
+ highest = c._get_highest_policy_version("ubuntu")
version = 1.0
if version == highest:
print("SKIPPED-- test version '%s' is already highest" % version,
@@ -390,6 +390,94 @@
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_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", ["accounts"])
+ 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)
@@ -469,7 +557,7 @@
self.check_results(report, expected_counts)
def test_check_policy_groups_nonexistent(self):
- '''Test check_policy_groups_webapps() - nonexistent'''
+ '''Test check_policy_groups() - nonexistent'''
self.set_test_security_manifest(self.default_appname,
"policy_groups",
["networking", "nonexistent"])
@@ -480,7 +568,7 @@
self.check_results(report, expected_counts)
def test_check_policy_groups_reserved(self):
- '''Test check_policy_groups_webapps() - reserved'''
+ '''Test check_policy_groups() - reserved'''
self.set_test_security_manifest(self.default_appname,
"policy_groups",
["video_files", "networking"])
@@ -490,8 +578,20 @@
expected_counts = {'info': None, 'warn': 0, 'error': 1}
self.check_results(report, expected_counts)
+ 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_webapps() - empty'''
+ '''Test check_policy_groups() - empty'''
self.set_test_security_manifest(self.default_appname,
"policy_groups",
["", "networking"])
@@ -500,3 +600,87 @@
report = c.click_report
expected_counts = {'info': None, 'warn': 0, 'error': 1}
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"])
+ 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_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)
+ 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"])
+ 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_template_pushhelper(self):
+ '''Test check_template_pushhelper'''
+ self.set_test_push_helper(self.default_appname, "exec", "foo")
+ self.set_test_security_manifest(self.default_appname,
+ "template", "ubuntu-sdk")
+ self.set_test_security_manifest(self.default_appname,
+ "policy_groups",
+ ["push-notification-client"])
+ c = ClickReviewSecurity(self.test_name)
+ c.check_template_push_helpers()
+ report = c.click_report
+ expected_counts = {'info': 1, 'warn': 0, 'error': 0}
+ self.check_results(report, expected_counts)
+
+ def test_check_template_pushhelper_no_hook(self):
+ '''Test check_template_pushhelper'''
+ self.set_test_security_manifest(self.default_appname,
+ "template", "ubuntu-sdk")
+ c = ClickReviewSecurity(self.test_name)
+ c.check_template_push_helpers()
+ report = c.click_report
+ expected_counts = {'info': 0, 'warn': 0, 'error': 0}
+ self.check_results(report, expected_counts)
+
+ def test_check_template_pushhelper_wrong_template(self):
+ '''Test check_template_pushhelper - wrong template()'''
+ self.set_test_push_helper(self.default_appname, "exec", "foo")
+ self.set_test_security_manifest(self.default_appname,
+ "template", "ubuntu-webapp")
+ self.set_test_security_manifest(self.default_appname,
+ "policy_groups",
+ ["push-notification-client"])
+ c = ClickReviewSecurity(self.test_name)
+ c.check_template_push_helpers()
+ report = c.click_report
+ expected_counts = {'info': None, 'warn': 0, 'error': 1}
+ self.check_results(report, expected_counts)
diff -Nru click-reviewers-tools-0.7/bin/clickreviews/tests/test_cr_url_dispatcher.py click-reviewers-tools-0.8/bin/clickreviews/tests/test_cr_url_dispatcher.py
--- click-reviewers-tools-0.7/bin/clickreviews/tests/test_cr_url_dispatcher.py 1970-01-01 00:00:00.000000000 +0000
+++ click-reviewers-tools-0.8/bin/clickreviews/tests/test_cr_url_dispatcher.py 2014-07-25 13:47:22.000000000 +0000
@@ -0,0 +1,211 @@
+'''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 setUp(self):
+ # Monkey patch various file access classes. stop() is handled with
+ # addCleanup in super()
+ cr_tests.mock_patch()
+ super()
+
+ 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(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="domain-suffix",
+ 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)
diff -Nru click-reviewers-tools-0.7/bin/click-run-checks click-reviewers-tools-0.8/bin/click-run-checks
--- click-reviewers-tools-0.7/bin/click-run-checks 2014-05-05 08:33:55.000000000 +0000
+++ click-reviewers-tools-0.8/bin/click-run-checks 2014-07-25 13:47:22.000000000 +0000
@@ -40,16 +40,36 @@
prefer_local click-check-lint "$c"
echo ""
+echo "= click-check-content-hub ="
+prefer_local click-check-content-hub "$c"
+
+echo ""
echo "= click-check-desktop ="
prefer_local click-check-desktop "$c"
echo ""
+echo "= click-check-functional ="
+prefer_local click-check-functional "$c"
+
+echo ""
+echo "= click-check-online-accounts ="
+prefer_local click-check-online-accounts "$c"
+
+echo ""
+echo "= click-check-push-helper ="
+prefer_local click-check-push-helper "$c"
+
+echo ""
+echo "= click-check-scope ="
+prefer_local click-check-scope "$c"
+
+echo ""
echo "= click-check-security ="
prefer_local click-check-security "$c"
echo ""
-echo "= click-check-functional ="
-prefer_local click-check-functional "$c"
+echo "= click-check-url-dispatcher ="
+prefer_local click-check-url-dispatcher "$c"
echo ""
echo ""
diff -Nru click-reviewers-tools-0.7/bin/click-show-files click-reviewers-tools-0.8/bin/click-show-files
--- click-reviewers-tools-0.7/bin/click-show-files 2014-05-05 08:33:55.000000000 +0000
+++ click-reviewers-tools-0.8/bin/click-show-files 2014-07-25 13:47:22.000000000 +0000
@@ -1,7 +1,7 @@
#!/usr/bin/python3
-'''check-skeleton: perform click skeleton checks'''
+'''check-show-files: show files'''
#
-# Copyright (C) 2013 Canonical Ltd.
+# 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
@@ -23,6 +23,11 @@
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
# This script just dumps important files to stdout
@@ -42,11 +47,13 @@
cr_common.cleanup_unpack()
print("= hooks =")
- review_apparmor = cr_security.ClickReviewSecurity(sys.argv[1])
- for f in sorted(review_apparmor.security_manifests):
- fh = cr_common.open_file_read(os.path.join(review_apparmor.unpack_dir,
- f))
- print("== security: %s ==" % os.path.basename(f))
+
+ 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 = cr_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()
@@ -62,5 +69,66 @@
for line in fh.readlines():
print(line, end="")
fh.close()
+ print("")
+ cr_common.cleanup_unpack()
+
+ 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 = cr_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("")
+ cr_common.cleanup_unpack()
+
+ 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 = cr_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("")
+ cr_common.cleanup_unpack()
+
+ review_scope = cr_scope.ClickReviewScope(sys.argv[1])
+ for app in sorted(review_scope.scopes):
+ f = review_scope.scopes[app]["ini_file"]
+ fh = cr_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("")
+ cr_common.cleanup_unpack()
+
+ review_apparmor = cr_security.ClickReviewSecurity(sys.argv[1])
+ for f in sorted(review_apparmor.security_manifests):
+ fh = cr_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("")
+ cr_common.cleanup_unpack()
+
+ 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 = cr_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("")
cr_common.cleanup_unpack()
diff -Nru click-reviewers-tools-0.7/bin/repack-click click-reviewers-tools-0.8/bin/repack-click
--- click-reviewers-tools-0.7/bin/repack-click 2014-05-05 08:33:55.000000000 +0000
+++ click-reviewers-tools-0.8/bin/repack-click 2014-07-25 13:47:22.000000000 +0000
@@ -39,7 +39,7 @@
tmpdir = tempfile.mkdtemp(prefix='clickreview-')
curdir = os.getcwd()
os.chdir(tmpdir)
- (rc, out) = cr_common.cmd(['dpkg-deb', '-b', '--nocheck',
+ (rc, out) = cr_common.cmd(['dpkg-deb', '-b', '--nocheck', '-Zgzip',
os.path.abspath(unpack_dir),
os.path.join(tmpdir, click_fn)])
os.chdir(curdir)
diff -Nru click-reviewers-tools-0.7/bin/update-apparmor-policy click-reviewers-tools-0.8/bin/update-apparmor-policy
--- click-reviewers-tools-0.7/bin/update-apparmor-policy 1970-01-01 00:00:00.000000000 +0000
+++ click-reviewers-tools-0.8/bin/update-apparmor-policy 2014-07-25 13:47:22.000000000 +0000
@@ -0,0 +1,34 @@
+#!/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)
diff -Nru click-reviewers-tools-0.7/bin/update-frameworks click-reviewers-tools-0.8/bin/update-frameworks
--- click-reviewers-tools-0.7/bin/update-frameworks 1970-01-01 00:00:00.000000000 +0000
+++ click-reviewers-tools-0.8/bin/update-frameworks 2014-07-25 13:47:22.000000000 +0000
@@ -0,0 +1,34 @@
+#!/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)
diff -Nru click-reviewers-tools-0.7/clickreviews/apparmor_policy.py click-reviewers-tools-0.8/clickreviews/apparmor_policy.py
--- click-reviewers-tools-0.7/clickreviews/apparmor_policy.py 1970-01-01 00:00:00.000000000 +0000
+++ click-reviewers-tools-0.8/clickreviews/apparmor_policy.py 2014-07-25 13:47:22.000000000 +0000
@@ -0,0 +1,37 @@
+#
+# 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/~click-reviewers/click-reviewers-tools/trunk/view/head:/data/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)
diff -Nru click-reviewers-tools-0.7/clickreviews/cr_common.py click-reviewers-tools-0.8/clickreviews/cr_common.py
--- click-reviewers-tools-0.7/clickreviews/cr_common.py 2014-05-05 08:33:55.000000000 +0000
+++ click-reviewers-tools-0.8/clickreviews/cr_common.py 2014-07-25 13:47:22.000000000 +0000
@@ -60,6 +60,10 @@
self.click_package = fn
self._check_path_exists()
if not self.click_package.endswith(".click"):
+ if self.click_package.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.review_type = review_type
diff -Nru click-reviewers-tools-0.7/clickreviews/cr_content_hub.py click-reviewers-tools-0.8/clickreviews/cr_content_hub.py
--- click-reviewers-tools-0.7/clickreviews/cr_content_hub.py 1970-01-01 00:00:00.000000000 +0000
+++ click-reviewers-tools-0.8/clickreviews/cr_content_hub.py 2014-07-25 13:47:22.000000000 +0000
@@ -0,0 +1,115 @@
+'''cr_content_hub.py: click content-hub checks'''
+#
+# 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 __future__ import print_function
+
+from clickreviews.cr_common import ClickReview, error, open_file_read, msg
+import json
+import os
+
+
+class ClickReviewContentHub(ClickReview):
+ '''This class represents click lint reviews'''
+ def __init__(self, fn):
+ ClickReview.__init__(self, fn, "content_hub")
+
+ self.valid_keys = ['destination', 'share', 'source']
+
+ self.content_hub_files = dict() # click-show-files and tests
+ self.content_hub = dict()
+ 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'''
+ for app in sorted(self.content_hub):
+ for k in self.content_hub[app].keys():
+ t = "info"
+ n = "valid_%s_%s" % (app, 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 = "valid_%s_%s_value" % (app, 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'''
+ for app in sorted(self.content_hub):
+ unknown = []
+ t = "info"
+ n = "unknown_%s" % 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)
diff -Nru click-reviewers-tools-0.7/clickreviews/cr_desktop.py click-reviewers-tools-0.8/clickreviews/cr_desktop.py
--- click-reviewers-tools-0.7/clickreviews/cr_desktop.py 2014-05-05 08:33:55.000000000 +0000
+++ click-reviewers-tools-0.8/clickreviews/cr_desktop.py 2014-07-25 13:47:22.000000000 +0000
@@ -16,7 +16,7 @@
from __future__ import print_function
-from clickreviews.cr_common import ClickReview, error, open_file_read
+from clickreviews.cr_common import ClickReview, error, open_file_read, msg
import glob
import json
import os
@@ -36,7 +36,15 @@
self.desktop_hook_entries = 0
for app in self.manifest['hooks']:
if 'desktop' not in self.manifest['hooks'][app]:
- error("could not find desktop hook for '%s'" % app)
+ if 'scope' in self.manifest['hooks'][app]:
+ # msg("Skipped missing desktop hook for scope '%s'" % app)
+ continue
+ if 'push-helper' in self.manifest['hooks'][app]:
+ # msg("Skipped missing desktop hook for push-helper '%s'" %
+ # app)
+ continue
+ else:
+ error("could not find desktop hook for '%s'" % app)
if not isinstance(self.manifest['hooks'][app]['desktop'], str):
error("manifest malformed: hooks/%s/desktop is not str" % app)
self.desktop_hook_entries += 1
@@ -102,19 +110,13 @@
def check_desktop_file(self):
'''Check desktop file'''
t = 'info'
- n = 'files_available'
- s = 'OK'
- if len(self._get_desktop_files().keys()) < 1:
- t = 'error'
- s = 'No .desktop files available.'
- self._add_result(t, n, s)
-
- t = 'info'
n = '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):
diff -Nru click-reviewers-tools-0.7/clickreviews/cr_functional.py click-reviewers-tools-0.8/clickreviews/cr_functional.py
--- click-reviewers-tools-0.7/clickreviews/cr_functional.py 2014-05-05 08:33:55.000000000 +0000
+++ click-reviewers-tools-0.8/clickreviews/cr_functional.py 2014-07-25 13:47:22.000000000 +0000
@@ -167,3 +167,24 @@
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'''
+ t = 'info'
+ n = '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)
diff -Nru click-reviewers-tools-0.7/clickreviews/cr_lint.py click-reviewers-tools-0.8/clickreviews/cr_lint.py
--- click-reviewers-tools-0.7/clickreviews/cr_lint.py 2014-05-05 08:33:55.000000000 +0000
+++ click-reviewers-tools-0.8/clickreviews/cr_lint.py 2014-07-25 13:47:22.000000000 +0000
@@ -21,6 +21,7 @@
import os
import re
+from clickreviews.frameworks import Frameworks
from clickreviews.cr_common import ClickReview, open_file_read, cmd
CONTROL_FILE_NAMES = ["control", "manifest", "md5sums", "preinst"]
@@ -60,6 +61,20 @@
self._list_all_compiled_binaries()
+ self.known_hooks = ['account-application',
+ 'account-provider',
+ 'account-qml-plugin',
+ 'account-service',
+ 'apparmor',
+ 'content-hub',
+ 'desktop',
+ 'pay-ui',
+ 'push-helper',
+ 'scope',
+ 'urls']
+
+ self.redflagged_hooks = ['pay-ui']
+
def _list_control_files(self):
'''List all control files with their full path.'''
for i in CONTROL_FILE_NAMES:
@@ -267,11 +282,20 @@
# Some checks are already handled in
# cr_common.py:_verify_manifest_structure()
- # We don't support multiple apps in 13.10
- if len(self.manifest['hooks'].keys()) != 1:
- self._add_result('error', 'hooks',
- "more than one app key specified in hooks")
- return
+ # While we support multiple apps in the hooks db, we don't support
+ # multiple apps specifying desktop hooks. Eg, it is ok to specify a
+ # scope, an app and a push-helper, but it isn't ok to specify two apps
+ t = 'info'
+ n = '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:
+ t = 'error'
+ 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']:
@@ -284,22 +308,78 @@
self._add_result(t, n, s)
# Verify we have the required hooks
- required = ['apparmor', 'desktop']
+ required = ['apparmor']
for f in required:
for app in self.manifest['hooks']:
t = 'info'
n = 'hooks_%s_%s' % (app, f)
s = "OK"
- if f == "apparmor":
+ 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]:
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']:
+ 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)
+
+ def check_hooks_unknown(self):
+ '''Check if have any unknown hooks'''
+ t = 'info'
+ n = '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 = 'hooks_%s_%s_known' % (app, 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'''
+ t = 'info'
+ n = 'redflagged hooks'
+ s = 'OK'
+
+ for app in self.manifest['hooks']:
+ found = []
+ t = 'info'
+ n = 'hooks_redflag_%s' % (app)
+ s = "OK"
+ for hook in self.manifest['hooks'][app]:
+ if hook in self.redflagged_hooks:
+ found.append(hook)
+ if len(found) > 0:
+ t = 'error'
+ s = "(MANUAL REVIEW) '%s' not allowed" % ", ".join(found)
+ self._add_result(t, n, s)
+
def check_external_symlinks(self):
'''Check if symlinks in the click package go out to the system.'''
t = 'info'
@@ -447,6 +527,9 @@
t = 'info'
s = "OK (email '%s' long, but special case of core apps " \
"'com.ubuntu.*')" % self.email
+ elif self.email == "ubuntu-devel-discuss@lists.ubuntu.com":
+ t = 'info'
+ s = "OK (email '%s' long, but special case" % self.email
else:
t = 'error'
s = "(EMAIL NEEDS HUMAN REVIEW) email domain too " \
@@ -515,26 +598,35 @@
def check_framework(self):
'''Check framework()'''
- t = 'info'
n = 'framework'
- s = 'OK'
- if self.manifest['framework'] not in self.valid_frameworks:
- t = 'error'
- s = "'%s' is not a supported framework" % \
+ l = "http://askubuntu.com/questions/460512/what-framework-should-i-use-in-my-manifest-file"
+ local_copy = os.path.join(os.path.dirname(__file__),
+ '../data/frameworks.json')
+ frameworks = Frameworks(local_copy)
+ if self.manifest['framework'] in frameworks.AVAILABLE_FRAMEWORKS:
+ t = 'info'
+ s = 'OK'
+ self._add_result(t, n, s)
+ # If it's an available framework, we're done checking
+ return
+ elif self.manifest['framework'] in frameworks.DEPRECATED_FRAMEWORKS:
+ t = 'warn'
+ s = "'%s' is deprecated. Please use a newer framework" % \
self.manifest['framework']
- self._add_result(t, n, s)
-
- obsolete_frameworks = ['ubuntu-sdk-13.10']
- t = 'info'
- n = 'current framework'
- s = 'OK'
- l = None
- if self.manifest['framework'] in obsolete_frameworks:
+ self._add_result(t, n, s, l)
+ return
+ elif self.manifest['framework'] in frameworks.OBSOLETE_FRAMEWORKS:
t = 'error'
s = "'%s' is obsolete. Please use a newer framework" % \
self.manifest['framework']
- l = "http://askubuntu.com/questions/460512/what-framework-should-i-use-in-my-manifest-file"
- self._add_result(t, n, s, l)
+ self._add_result(t, n, s, l)
+ return
+ else:
+ # None of the above checks triggered, this is an unknown framework
+ t = 'error'
+ s = "'%s' is not a supported framework" % \
+ self.manifest['framework']
+ self._add_result(t, n, s, l)
def check_click_local_extensions(self):
'''Report any click local extensions'''
diff -Nru click-reviewers-tools-0.7/clickreviews/cr_online_accounts.py click-reviewers-tools-0.8/clickreviews/cr_online_accounts.py
--- click-reviewers-tools-0.7/clickreviews/cr_online_accounts.py 1970-01-01 00:00:00.000000000 +0000
+++ click-reviewers-tools-0.8/clickreviews/cr_online_accounts.py 2014-07-25 13:47:22.000000000 +0000
@@ -0,0 +1,231 @@
+'''cr_online_accounts.py: click online accounts'''
+#
+# 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
+
+from clickreviews.cr_common import ClickReview, error, open_file_read, msg
+import os
+# http://lxml.de/tutorial.html
+import lxml.etree as etree
+
+
+class ClickReviewAccounts(ClickReview):
+ '''This class represents click lint reviews'''
+ def __init__(self, fn):
+ ClickReview.__init__(self, fn, "online_accounts")
+
+ self.accounts_files = dict()
+ self.accounts = dict()
+
+ self.account_hooks = ['account-application',
+ 'account-provider',
+ 'account-qml-plugin',
+ 'account-service']
+ 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, xml) = 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] = xml
+
+ def _extract_account(self, app, account_type):
+ '''Extract accounts'''
+ 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)
+ else:
+ try:
+ tree = etree.parse(fn)
+ xml = tree.getroot()
+ except Exception as e:
+ error("accounts xml unparseable: %s (%s):\n%s" % (bn, str(e),
+ contents))
+ return (fn, xml)
+
+ def check_application(self):
+ '''Check application'''
+ for app in sorted(self.accounts.keys()):
+ account_type = "account-application"
+
+ t = 'info'
+ n = '%s_%s_root' % (app, account_type)
+ s = "OK"
+ if not account_type 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 = '%s_%s_id' % (app, account_type)
+ s = "OK"
+ expected_id = "%s_%s" % (self.manifest["name"], app)
+ if "id" not in self.accounts[app][account_type].keys():
+ t = 'error'
+ s = "Could not find 'id' in application tag"
+ elif self.accounts[app][account_type].get("id") != expected_id:
+ t = 'error'
+ s = "id '%s' != '%s'" % (
+ self.accounts[app][account_type].get("id"),
+ expected_id)
+ self._add_result(t, n, s)
+
+ t = 'info'
+ n = '%s_%s_services' % (app, account_type)
+ 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 = '%s_%s_service' % (app, account_type)
+ 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'''
+ for app in sorted(self.accounts.keys()):
+ account_type = "account-service"
+
+ t = 'info'
+ n = '%s_%s_root' % (app, account_type)
+ s = "OK"
+ if not account_type 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 = '%s_%s_id' % (app, account_type)
+ s = "OK"
+ expected_id = "%s_%s" % (self.manifest["name"], app)
+ if "id" not in self.accounts[app][account_type].keys():
+ t = 'error'
+ s = "Could not find 'id' in service tag"
+ elif self.accounts[app][account_type].get("id") != expected_id:
+ t = 'error'
+ s = "id '%s' != '%s'" % (
+ self.accounts[app][account_type].get("id"),
+ expected_id)
+ self._add_result(t, n, s)
+
+ if t == 'error':
+ continue
+
+ for tag in ['type', 'name', 'provider']:
+ t = 'info'
+ n = '%s_%s_%s' % (app, account_type, tag)
+ 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)
+
+ def check_provider(self):
+ '''Check provider'''
+ for app in sorted(self.accounts.keys()):
+ account_type = "account-provider"
+
+ t = 'info'
+ n = '%s_%s' % (app, account_type)
+ s = "OK"
+ if not account_type in self.accounts[app]:
+ s = "OK (missing)"
+ self._add_result(t, n, s)
+ continue
+ else:
+ t = 'error'
+ s = "(MANUAL REVIEW) '%s' not allowed" % account_type
+ self._add_result(t, n, s)
+
+ t = 'info'
+ n = '%s_%s_root' % (app, account_type)
+ 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 = '%s_%s_id' % (app, account_type)
+ s = "OK"
+ expected_id = "%s_%s" % (self.manifest["name"], app)
+ if "id" not in self.accounts[app][account_type].keys():
+ t = 'error'
+ s = "Could not find 'id' in provider tag"
+ elif self.accounts[app][account_type].get("id") != expected_id:
+ t = 'error'
+ s = "id '%s' != '%s'" % (
+ self.accounts[app][account_type].get("id"),
+ expected_id)
+ self._add_result(t, n, s)
+
+ def check_qml_plugin(self):
+ '''Check qml-plugin'''
+ for app in sorted(self.accounts.keys()):
+ account_type = "account-qml-plugin"
+
+ t = 'info'
+ n = '%s_%s' % (app, account_type)
+ s = "OK"
+ if not account_type in self.accounts[app]:
+ s = "OK (missing)"
+ self._add_result(t, n, s)
+ continue
+ else:
+ t = 'error'
+ s = "(MANUAL REVIEW) '%s' not allowed" % account_type
+ self._add_result(t, n, s)
diff -Nru click-reviewers-tools-0.7/clickreviews/cr_push_helper.py click-reviewers-tools-0.8/clickreviews/cr_push_helper.py
--- click-reviewers-tools-0.7/clickreviews/cr_push_helper.py 1970-01-01 00:00:00.000000000 +0000
+++ click-reviewers-tools-0.8/clickreviews/cr_push_helper.py 2014-07-25 13:47:22.000000000 +0000
@@ -0,0 +1,132 @@
+'''cr_push_helper.py: click push-helper checks'''
+#
+# 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 __future__ import print_function
+
+from clickreviews.cr_common import ClickReview, error, open_file_read, msg
+import json
+import os
+
+
+class ClickReviewPushHelper(ClickReview):
+ '''This class represents click lint reviews'''
+ def __init__(self, fn):
+ ClickReview.__init__(self, fn, "push_helper")
+
+ self.required_keys = ['exec']
+ self.optional_keys = ['app_id']
+
+ self.push_helper_files = dict() # click-show-files and tests
+ self.push_helper = dict()
+ 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/urls 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'''
+ for app in sorted(self.push_helper):
+ for k in self.push_helper[app].keys():
+ t = "info"
+ n = "valid_%s_%s" % (app, 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 = "valid_%s_required_%s" % (app, 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'''
+ for app in sorted(self.push_helper):
+ unknown = []
+ t = "info"
+ n = "unknown_%s" % 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'''
+ for app in sorted(self.manifest['hooks']):
+ if app not in self.push_helper:
+ continue
+
+ t = "info"
+ n = "other_hooks_%s" % 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)
diff -Nru click-reviewers-tools-0.7/clickreviews/cr_scope.py click-reviewers-tools-0.8/clickreviews/cr_scope.py
--- click-reviewers-tools-0.7/clickreviews/cr_scope.py 1970-01-01 00:00:00.000000000 +0000
+++ click-reviewers-tools-0.8/clickreviews/cr_scope.py 2014-07-25 13:47:22.000000000 +0000
@@ -0,0 +1,135 @@
+'''cr_scope.py: click scope'''
+#
+# 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 __future__ import print_function
+
+from clickreviews.cr_common import ClickReview, error, msg
+import configparser
+import os
+
+
+class ClickReviewScope(ClickReview):
+ '''This class represents click lint reviews'''
+ def __init__(self, fn):
+ ClickReview.__init__(self, fn, "scope")
+
+ self.scopes = dict()
+ 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.ini" % self.manifest['name'])
+ 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(ini_fn)
+ except Exception:
+ 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'''
+ for app in sorted(self.scopes.keys()):
+ t = 'info'
+ n = 'ini_%s_scope_section' % app
+ s = "OK"
+
+ if len(self.scopes[app]["scope_config"].sections()) > 1:
+ t = 'error'
+ s = "'%s' has too many sections: %s" % (
+ self.scopes[app]["ini_file_rel"],
+ ", ".join(self.scopes[app]["scope_config"].sections()))
+ elif "ScopeConfig" not in \
+ self.scopes[app]["scope_config"].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 = ['scoperunner',
+ 'displayname',
+ 'icon',
+ 'searchhint']
+ optional = ['description',
+ 'author',
+ 'art']
+
+ missing = []
+ t = 'info'
+ n = 'ini_%s_scope_required_fields' % (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 = 'ini_%s_scope_unknown_fields' % (app)
+ s = 'OK'
+ unknown = []
+ for f in self.scopes[app]["scope_config"]['ScopeConfig'].keys():
+ if f.lower() not in required and f.lower() not in optional:
+ unknown.append(f.lower())
+
+ 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)
diff -Nru click-reviewers-tools-0.7/clickreviews/cr_security.py click-reviewers-tools-0.8/clickreviews/cr_security.py
--- click-reviewers-tools-0.7/clickreviews/cr_security.py 2014-05-05 08:33:55.000000000 +0000
+++ click-reviewers-tools-0.8/clickreviews/cr_security.py 2014-07-25 13:47:22.000000000 +0000
@@ -18,25 +18,21 @@
from clickreviews.cr_common import ClickReview, error, warn
import clickreviews.cr_common as cr_common
+import clickreviews.apparmor_policy as apparmor_policy
import glob
import json
import os
-easyprof_dir = "/usr/share/apparmor/easyprof"
-if not os.path.isdir(easyprof_dir):
- error("Error importing easyprof. Please install apparmor-easyprof")
-if not os.path.isdir(os.path.join(easyprof_dir, "templates/ubuntu")):
- error("Error importing easyprof. Please install apparmor-easyprof-ubuntu")
-
-import apparmor.easyprof
-
class ClickReviewSecurity(ClickReview):
'''This class represents click lint reviews'''
def __init__(self, fn):
ClickReview.__init__(self, fn, "security")
- self.supported_policy_versions = self._get_supported_policy_versions()
+ local_copy = os.path.join(os.path.dirname(__file__),
+ '../data/apparmor-easyprof-ubuntu.json')
+ p = apparmor_policy.ApparmorPolicy(local_copy)
+ self.aa_policy = p.policy
self.all_fields = ['abstractions',
'author',
@@ -70,6 +66,8 @@
'video',
'webview']
+ self.allowed_push_helper_policy_groups = ['push-notification-client']
+
self.redflag_templates = ['unconfined']
self.extraneous_templates = ['ubuntu-sdk',
'default']
@@ -79,6 +77,7 @@
# like 'ubuntu-sdk-14.04-html5', 'ubuntu-sdk-14.04-platform', etc
self.major_framework_policy = {'ubuntu-sdk-13.10': 1.0,
'ubuntu-sdk-14.04': 1.1,
+ 'ubuntu-sdk-14.10': 1.2,
}
self.security_manifests = dict()
@@ -132,31 +131,6 @@
k, mp))
return m
- def _get_policy_group_meta(self, group, meta, vendor, version):
- '''Get meta-information from the policy group'''
- cmd_args = ['--show-policy-group', '--policy-groups=%s' % group,
- '--policy-version=%s' % version,
- '--policy-vendor=%s' % vendor]
- (options, args) = apparmor.easyprof.parse_args(cmd_args)
- try:
- easyp = apparmor.easyprof.AppArmorEasyProfile(None, options)
- tmp = easyp.get_policygroup(group)
- except apparmor.easyprof.AppArmorException:
- warn("'%s' failed" % " ".join(cmd_args))
- return ""
-
- text = ""
- for line in tmp.splitlines():
- if line.startswith("# %s: " % meta):
- text = line.split(':', 1)[1].strip()
- elif text != "":
- if line.startswith("# "):
- text += line[2:]
- else:
- break
-
- return text
-
def _get_security_manifest(self, app):
'''Get the security manifest for app'''
if app not in self.manifest['hooks']:
@@ -168,21 +142,72 @@
m = self.security_manifests[f]
return (f, m)
- def _get_supported_policy_versions(self):
+ def _get_policy_versions(self, vendor):
'''Get the supported AppArmor policy versions'''
- version_dirs = sorted(glob.glob("%s/templates/ubuntu/*" %
- easyprof_dir))
+ if vendor not in self.aa_policy:
+ error("Could not find vendor '%s'" % vendor, do_exit=False)
+ return None
+
supported_policy_versions = []
- for d in version_dirs:
- if not os.path.isdir(d):
- continue
- try:
- supported_policy_versions.append(float(os.path.basename(d)))
- except TypeError:
- continue
- supported_policy_versions = sorted(supported_policy_versions)
+ for i in self.aa_policy[vendor].keys():
+ supported_policy_versions.append("%.1f" % float(i))
+
+ return sorted(supported_policy_versions)
- return supported_policy_versions
+ def _get_templates(self, vendor, version, aa_type="all"):
+ '''Get templates by type'''
+ 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 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_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 _get_policy_groups(self, vendor, version, aa_type="all"):
+ '''Get policy groups by type'''
+ 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'''
+ for t in self.aa_policy[vendor][version]['policy_groups']:
+ if policy_group in self.aa_policy[vendor][version]['policy_groups'][t]:
+ return t
def check_policy_vendor(self):
'''Check policy_vendor'''
@@ -213,17 +238,13 @@
if 'policy_vendor' in m:
vendor = m['policy_vendor']
version = str(m['policy_version'])
- cmd_args = ['--list-templates', '--policy-vendor=%s' % vendor,
- '--policy-version=%s' % version]
- (options, args) = apparmor.easyprof.parse_args(cmd_args)
- try:
- apparmor.easyprof.AppArmorEasyProfile(None, options)
- except Exception:
+ 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 = sorted(self.supported_policy_versions)[-1]
+ highest = self._get_highest_policy_version(vendor)
t = 'info'
n = 'policy_version_is_highest (%s, %s)' % (str(highest), f)
s = "OK"
@@ -287,31 +308,16 @@
if 'policy_vendor' in m:
vendor = m['policy_vendor']
version = str(m['policy_version'])
- cmd_args = ['--list-templates', '--policy-vendor=%s' % vendor,
- '--policy-version=%s' % version]
- (options, args) = apparmor.easyprof.parse_args(cmd_args)
- templates = []
- try:
- easyp = apparmor.easyprof.AppArmorEasyProfile(None, options)
- templates = easyp.get_templates()
- except Exception:
- t = 'error'
- s = 'could not find policy for %s/%s' % (vendor, version)
- self._add_result(t, n, s)
- continue
+
+ 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)
- # If we got here, we can see if a valid template was specified
- found = False
- for i in templates:
- if os.path.basename(i) == m['template']:
- found = True
- break
- if not found:
+ if m['template'] not in self._get_templates(vendor, version):
t = 'error'
s = "specified unsupported template '%s'" % m['template']
@@ -353,13 +359,85 @@
self._add_result(t, n, s)
+ def check_template_push_helpers(self):
+ '''Check template for push-helpers'''
+ for app in sorted(self.manifest['hooks']):
+ (f, m) = self._get_security_manifest(app)
+ t = 'info'
+ n = 'template_push_helper(%s)' % f
+ s = "OK"
+ if 'push-helper' not in self.manifest['hooks'][app]:
+ continue
+ if 'template' in m and m['template'] != "ubuntu-sdk":
+ t = 'error'
+ s = "template is not 'ubuntu-sdk'"
+ self._add_result(t, n, s)
+
+ def check_policy_groups_push_helpers(self):
+ '''Check policy_groups for push-helpers'''
+ for app in sorted(self.manifest['hooks']):
+ (f, m) = self._get_security_manifest(app)
+ t = 'info'
+ n = 'policy_groups_push_helper(%s)' % f
+ s = "OK"
+ if 'push-helper' not in self.manifest['hooks'][app]:
+ 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)
+ 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'''
+ for app in sorted(self.manifest['hooks']):
+ (f, m) = self._get_security_manifest(app)
+ t = 'info'
+ n = 'policy_groups_scopes (%s)' % 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 shouldn't have access to anything
+ # (for now, this may change with trust store (eg, location)
+ if p != 'networking':
+ 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(self):
'''Check policy_groups'''
for app in sorted(self.manifest['hooks']):
(f, m) = self._get_security_manifest(app)
t = 'info'
- n = 'policy_groups_exists (%s)' % f
+ n = 'policy_groups_exists_%s (%s)' % (app, f)
if 'policy_groups' not in m:
# If template not specified, we just use the default
self._add_result('warn', n, 'no policy groups specified')
@@ -374,18 +452,8 @@
if 'policy_vendor' in m:
vendor = m['policy_vendor']
version = str(m['policy_version'])
- cmd_args = ['--list-policy-groups', '--policy-vendor=%s' % vendor,
- '--policy-version=%s' % version]
- (options, args) = apparmor.easyprof.parse_args(cmd_args)
- policy_groups = []
- try:
- easyp = apparmor.easyprof.AppArmorEasyProfile(None, options)
- policy_groups = easyp.get_policy_groups()
- except Exception:
- t = 'error'
- s = 'could not find policy for %s/%s' % (vendor, version)
- self._add_result(t, n, s)
- continue
+
+ policy_groups = self._get_policy_groups(version=version, vendor=vendor)
if len(policy_groups) < 1:
t = 'error'
s = 'could not find policy groups'
@@ -395,7 +463,7 @@
# Check for duplicates
t = 'info'
- n = 'policy_groups_duplicates (%s)' % f
+ n = 'policy_groups_duplicates_%s (%s)' % (app, f)
s = 'OK'
tmp = []
for p in m['policy_groups']:
@@ -410,7 +478,7 @@
# If we got here, we can see if valid policy groups were specified
for i in m['policy_groups']:
t = 'info'
- n = 'policy_groups_valid (%s)' % i
+ n = 'policy_groups_valid_%s (%s)' % (app, i)
s = 'OK'
# SDK will leave and empty policy group, report but don't
@@ -433,16 +501,22 @@
if found:
t = 'info'
- n = 'policy_groups_safe (%s)' % i
+ n = 'policy_groups_safe_%s (%s)' % (app, i)
s = 'OK'
- usage = self._get_policy_group_meta(i, "Usage",
- vendor, version)
- if usage != "common":
- desc = self._get_policy_group_meta(i, "Description",
- vendor, version)
+
+ 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 = "(MANUAL REVIEW) %s policy group " % aa_type + \
+ "'%s': vetted applications only" % (i)
+ elif aa_type != "common":
t = 'error'
- s = "(MANUAL REVIEW) %s policy group " % usage + \
- "'%s': %s" % (i, desc)
+ s = "policy group '%s' has" % i + \
+ "unknown type '%s'" % (aa_type)
self._add_result(t, n, s)
def check_ignored(self):
diff -Nru click-reviewers-tools-0.7/clickreviews/cr_skeleton.py click-reviewers-tools-0.8/clickreviews/cr_skeleton.py
--- click-reviewers-tools-0.7/clickreviews/cr_skeleton.py 2014-05-05 08:33:55.000000000 +0000
+++ click-reviewers-tools-0.8/clickreviews/cr_skeleton.py 2014-07-25 13:47:22.000000000 +0000
@@ -1,6 +1,6 @@
'''cr_skeleton.py: click skeleton'''
#
-# Copyright (C) 2013 Canonical Ltd.
+# 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
diff -Nru click-reviewers-tools-0.7/clickreviews/cr_tests.py click-reviewers-tools-0.8/clickreviews/cr_tests.py
--- click-reviewers-tools-0.7/clickreviews/cr_tests.py 2014-05-05 08:33:55.000000000 +0000
+++ click-reviewers-tools-0.8/clickreviews/cr_tests.py 2014-07-25 13:47:22.000000000 +0000
@@ -32,6 +32,14 @@
TEST_SECURITY = dict()
TEST_DESKTOP = dict()
TEST_WEBAPP_MANIFESTS = dict()
+TEST_URLS = dict()
+TEST_SCOPES = dict()
+TEST_CONTENT_HUB = dict()
+TEST_ACCOUNTS_APPLICATION = dict()
+TEST_ACCOUNTS_PROVIDER = dict()
+TEST_ACCOUNTS_QML_PLUGIN = dict()
+TEST_ACCOUNTS_SERVICE = dict()
+TEST_PUSH_HELPER = dict()
#
@@ -59,6 +67,18 @@
"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",
]
@@ -74,7 +94,7 @@
def _get_security_supported_policy_versions(self):
'''Pretend we read the contens of /usr/share/apparmor/easyprof'''
- return [1.0, 1.1]
+ return [1.0, 1.1, 1.2]
def _extract_desktop_entry(self, app):
@@ -92,6 +112,48 @@
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 == "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])
+
+
# 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. Set up a list of patches, but don't start them. Create the helper
@@ -140,9 +202,6 @@
patches.append(patch(
'clickreviews.cr_security.ClickReviewSecurity._get_security_manifest',
_get_security_manifest))
-patches.append(patch(
- 'clickreviews.cr_security.ClickReviewSecurity._get_supported_policy_versions',
- _get_security_supported_policy_versions))
# desktop overrides
patches.append(patch(
@@ -155,6 +214,31 @@
'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))
+
def mock_patch():
'''Call in setup of child'''
@@ -208,11 +292,21 @@
"%s.json" % 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()
# hooks
self.test_security_manifests = dict()
self.test_desktop_files = dict()
+ self.test_url_dispatcher = dict()
+ self.test_scopes = dict()
+ self.test_content_hub = 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()
for app in self.test_manifest["hooks"].keys():
# setup security manifest for each app
self.set_test_security_manifest(app, 'policy_groups',
@@ -233,8 +327,34 @@
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 content-hub entries in manifest
+ 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 content-hub entries in manifest
+ self.set_test_push_helper(app, None, None)
+
self._update_test_security_manifests()
self._update_test_desktop_files()
+ self._update_test_url_dispatcher()
+ self._update_test_scopes()
+ self._update_test_content_hub()
+ 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()
# webapps manifests (leave empty for now)
self.test_webapp_manifests = dict()
@@ -280,6 +400,75 @@
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_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_name(self):
self.test_name = "%s_%s_%s.click" % (self.test_control['Package'],
self.test_control['Version'],
@@ -370,7 +559,6 @@
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()
@@ -386,6 +574,90 @@
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 == "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 == "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 content-hub 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 setUp(self):
'''Make sure our patches are applied everywhere'''
global patches
@@ -402,6 +674,22 @@
TEST_SECURITY = 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_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_APPLICATION = dict()
+ global TEST_PUSH_HELPER
+ TEST_PUSH_HELPER = dict()
self._reset_test_data()
cr_common.recursive_rm(self.desktop_tmpdir)
diff -Nru click-reviewers-tools-0.7/clickreviews/cr_url_dispatcher.py click-reviewers-tools-0.8/clickreviews/cr_url_dispatcher.py
--- click-reviewers-tools-0.7/clickreviews/cr_url_dispatcher.py 1970-01-01 00:00:00.000000000 +0000
+++ click-reviewers-tools-0.8/clickreviews/cr_url_dispatcher.py 2014-07-25 13:47:22.000000000 +0000
@@ -0,0 +1,152 @@
+'''cr_url dispatcher.py: click url_dispatcher'''
+#
+# 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 __future__ import print_function
+
+from clickreviews.cr_common import ClickReview, error, open_file_read, msg
+import json
+import os
+
+# https://wiki.ubuntu.com/URLDispatcher
+
+
+class ClickReviewUrlDispatcher(ClickReview):
+ '''This class represents click lint reviews'''
+ def __init__(self, fn):
+ ClickReview.__init__(self, fn, "url_dispatcher")
+
+ self.required_keys = ['protocol']
+ self.optional_keys = ['domain-suffix']
+
+ self.url_dispatcher_files = dict() # click-show-files and tests
+ self.url_dispatcher = dict()
+ 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']
+ fn = os.path.join(self.unpack_dir, u)
+
+ 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'''
+ for app in sorted(self.url_dispatcher):
+ for r in self.required_keys:
+ found = False
+ t = 'info'
+ n = 'required_entry_%s_%s' % (app, 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'''
+ for app in sorted(self.url_dispatcher):
+ for o in self.optional_keys:
+ found = False
+ t = 'info'
+ n = 'optional_entry_%s_%s' % (app, 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'''
+ for app in sorted(self.url_dispatcher):
+ unknown = []
+ for entry in self.url_dispatcher[app]:
+ t = 'info'
+ n = 'unknown_entry_%s' % 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)
diff -Nru click-reviewers-tools-0.7/clickreviews/frameworks.py click-reviewers-tools-0.8/clickreviews/frameworks.py
--- click-reviewers-tools-0.7/clickreviews/frameworks.py 1970-01-01 00:00:00.000000000 +0000
+++ click-reviewers-tools-0.8/clickreviews/frameworks.py 2014-07-25 13:47:22.000000000 +0000
@@ -0,0 +1,48 @@
+#
+# 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')
+
+# XXX: This is a hack and will be gone, as soon as myapps has an API for this.
+FRAMEWORKS_DATA_URL = \
+ "http://bazaar.launchpad.net/~ubuntu-core-dev/+junk/frameworks/view/head:/frameworks.json"
+
+
+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, local_copy_fn=None):
+ self.FRAMEWORKS = clickreviews.remote.read_cr_file(USER_DATA_FILE,
+ FRAMEWORKS_DATA_URL,
+ local_copy_fn)
+
+ for k, v in self.FRAMEWORKS.items():
+ if v == 'deprecated':
+ self.DEPRECATED_FRAMEWORKS.append(k)
+ elif v == 'obsolete':
+ self.OBSOLETE_FRAMEWORKS.append(k)
+ elif v == 'available':
+ self.AVAILABLE_FRAMEWORKS.append(k)
diff -Nru click-reviewers-tools-0.7/clickreviews/remote.py click-reviewers-tools-0.8/clickreviews/remote.py
--- click-reviewers-tools-0.7/clickreviews/remote.py 1970-01-01 00:00:00.000000000 +0000
+++ click-reviewers-tools-0.8/clickreviews/remote.py 2014-07-25 13:47:22.000000000 +0000
@@ -0,0 +1,96 @@
+#
+# 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.getctime(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_file(fn, url, data_dir=DATA_DIR):
+ try:
+ f = request.urlopen(url)
+ except (HTTPError, URLError) as error:
+ abort('Data not retrieved because %s.' % error)
+ except timeout:
+ abort('Socket timed out.')
+ html = f.read()
+ # 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"))
+ f = request.urlopen(download_link)
+ if not f:
+ abort()
+ 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(f.read())
+
+
+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):
+ j = json.loads(open(local_copy_fn, 'r').read())
+ else:
+ if _update_is_necessary(fn) and _update_is_possible(url):
+ get_remote_file(fn, url)
+ if os.path.exists(fn):
+ j = json.loads(open(fn, 'r').read())
+ return j
diff -Nru click-reviewers-tools-0.7/clickreviews/tests/test_aaa_example_cr_skeleton.py click-reviewers-tools-0.8/clickreviews/tests/test_aaa_example_cr_skeleton.py
--- click-reviewers-tools-0.7/clickreviews/tests/test_aaa_example_cr_skeleton.py 2014-05-05 08:33:55.000000000 +0000
+++ click-reviewers-tools-0.8/clickreviews/tests/test_aaa_example_cr_skeleton.py 2014-07-25 13:47:22.000000000 +0000
@@ -1,6 +1,6 @@
'''test_cr_skeleton.py: tests for the cr_skeleton module'''
#
-# Copyright (C) 2013 Canonical Ltd.
+# 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
diff -Nru click-reviewers-tools-0.7/clickreviews/tests/test_cr_content_hub.py click-reviewers-tools-0.8/clickreviews/tests/test_cr_content_hub.py
--- click-reviewers-tools-0.7/clickreviews/tests/test_cr_content_hub.py 1970-01-01 00:00:00.000000000 +0000
+++ click-reviewers-tools-0.8/clickreviews/tests/test_cr_content_hub.py 2014-07-25 13:47:22.000000000 +0000
@@ -0,0 +1,111 @@
+'''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 setUp(self):
+ # Monkey patch various file access classes. stop() is handled with
+ # addCleanup in super()
+ cr_tests.mock_patch()
+ super()
+
+ 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)
diff -Nru click-reviewers-tools-0.7/clickreviews/tests/test_cr_desktop.py click-reviewers-tools-0.8/clickreviews/tests/test_cr_desktop.py
--- click-reviewers-tools-0.7/clickreviews/tests/test_cr_desktop.py 2014-05-05 08:33:55.000000000 +0000
+++ click-reviewers-tools-0.8/clickreviews/tests/test_cr_desktop.py 2014-07-25 13:47:22.000000000 +0000
@@ -36,7 +36,6 @@
expected['warn'] = dict()
expected['error'] = dict()
expected['info']['desktop_files_usable'] = {"text": "OK"}
- expected['info']['desktop_files_available'] = {"text": "OK"}
self.check_results(r, expected=expected)
def test_check_desktop_file_valid(self):
diff -Nru click-reviewers-tools-0.7/clickreviews/tests/test_cr_lint.py click-reviewers-tools-0.8/clickreviews/tests/test_cr_lint.py
--- click-reviewers-tools-0.7/clickreviews/tests/test_cr_lint.py 2014-05-05 08:33:55.000000000 +0000
+++ click-reviewers-tools-0.8/clickreviews/tests/test_cr_lint.py 2014-07-25 13:47:22.000000000 +0000
@@ -506,6 +506,27 @@
expected_counts = {'info': None, 'warn': 1, 'error': 0}
self.check_results(r, expected_counts)
+ def test_check_maintainer_email_special(self):
+ '''Test check_maintainer() - ubuntu-devel-discuss@lists.ubuntu.com'''
+ self.set_test_control("Package", "com.canonical.app")
+ self.set_test_manifest("maintainer",
+ "Ubuntu Core Developers "
+ "")
+ 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)
+
+ expected = dict()
+ expected['info'] = dict()
+ expected['warn'] = dict()
+ expected['error'] = dict()
+ expected['info']['lint_maintainer_domain'] = \
+ {"text": "OK (email 'ubuntu-devel-discuss@lists.ubuntu.com' long, "
+ "but special case"}
+ self.check_results(r, expected=expected)
+
def test_check_icon(self):
'''Test check_icon()'''
self.set_test_manifest("icon", "someicon")
@@ -580,11 +601,11 @@
def test_check_framework(self):
'''Test check_framework()'''
- self.set_test_manifest("framework", "ubuntu-sdk-14.04-qml-dev1")
+ 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': 2, 'warn': 0, 'error': 0}
+ expected_counts = {'info': 1, 'warn': 0, 'error': 0}
self.check_results(r, expected_counts)
def test_check_framework_bad(self):
@@ -596,11 +617,123 @@
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.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.set_test_manifest("framework", "ubuntu-sdk-13.10")
+ 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)
+
+ 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_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': None, 'warn': 0, 'error': 1}
+ 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': 7, 'warn': 0, 'error': 0}
+ self.check_results(r, expected_counts)
+
+ 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_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_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_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_redflagged(self):
+ '''Test check_hooks_redflagged()'''
+ 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)
diff -Nru click-reviewers-tools-0.7/clickreviews/tests/test_cr_online_accounts.py click-reviewers-tools-0.8/clickreviews/tests/test_cr_online_accounts.py
--- click-reviewers-tools-0.7/clickreviews/tests/test_cr_online_accounts.py 1970-01-01 00:00:00.000000000 +0000
+++ click-reviewers-tools-0.8/clickreviews/tests/test_cr_online_accounts.py 2014-07-25 13:47:22.000000000 +0000
@@ -0,0 +1,317 @@
+'''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 lxml.etree as etree
+
+
+class TestClickReviewAccounts(cr_tests.TestClickReview):
+ """Tests for the lint review tool."""
+ def setUp(self):
+ # Monkey patch various file access classes. stop() is handled with
+ # addCleanup in super()
+ cr_tests.mock_patch()
+ super()
+
+ def _stub_application(self, root=None, id=None, do_subtree=True):
+ '''Stub application xml'''
+ if id is None:
+ id = "%s_%s" % (self.test_manifest["name"], self.default_appname)
+ if root is None:
+ root = "application"
+ if id == "":
+ xml = etree.Element(root)
+ else:
+ xml = etree.Element(root, id="%s" % id)
+ if do_subtree:
+ services = etree.SubElement(xml, "services")
+ elem1 = etree.SubElement(services, "service", id="element1")
+ desc1 = etree.SubElement(elem1, "description")
+ desc1.text = "elem1 description"
+ 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 id is None:
+ id = "%s_%s" % (self.test_manifest["name"], self.default_appname)
+ if root is None:
+ root = "service"
+ if id == "":
+ xml = etree.Element(root)
+ else:
+ xml = etree.Element(root, id="%s" % id)
+ if do_subtree:
+ service_type = etree.SubElement(xml, "type")
+ service_type.text = "webapps"
+ 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 id is None:
+ id = "%s_%s" % (self.test_manifest["name"], self.default_appname)
+ if root is None:
+ root = "provider"
+ if id == "":
+ 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_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_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_wrong_id(self):
+ '''Test check_application() - wrong id'''
+ xml = self._stub_application(id="nomatch")
+ 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_id(self):
+ '''Test check_application() - missing id'''
+ xml = self._stub_application(id="")
+ 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_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)
+ c = ClickReviewAccounts(self.test_name)
+ c.check_service()
+ r = c.click_report
+ expected_counts = {'info': 5, 'warn': 0, 'error': 0}
+ self.check_results(r, expected_counts)
+
+ def test_check_service_not_specified(self):
+ '''Test check_service() - no 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_wrong_id(self):
+ '''Test check_service() - wrong id'''
+ xml = self._stub_service(id="nomatch")
+ self.set_test_account(self.default_appname, "account-service", 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_id(self):
+ '''Test check_service() - missing id'''
+ xml = self._stub_service(id="")
+ self.set_test_account(self.default_appname, "account-service", 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_wrong_root(self):
+ '''Test check_service() - wrong root'''
+ xml = self._stub_service(root="wrongroot")
+ self.set_test_account(self.default_appname, "account-service", 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_type(self):
+ '''Test check_service() - missing type'''
+ xml = self._stub_service(do_subtree=False)
+ service_name = etree.SubElement(xml, "name")
+ service_name.text = "Foo"
+ service_provider = etree.SubElement(xml, "provider")
+ service_provider.text = "some-provider"
+ self.set_test_account(self.default_appname, "account-service", 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_type = etree.SubElement(xml, "type")
+ service_type.text = "webapps"
+ service_provider = etree.SubElement(xml, "provider")
+ service_provider.text = "some-provider"
+ self.set_test_account(self.default_appname, "account-service", 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_type = etree.SubElement(xml, "type")
+ service_type.text = "webapps"
+ service_name = etree.SubElement(xml, "name")
+ service_name.text = "Foo"
+ self.set_test_account(self.default_appname, "account-service", 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)
+ c = ClickReviewAccounts(self.test_name)
+ c.check_provider()
+ r = c.click_report
+ # provider prompts manual review, so for now, need to have error as 1
+ expected_counts = {'info': 2, 'warn': 0, 'error': 1}
+ self.check_results(r, expected_counts)
+
+ def test_check_provider_not_specified(self):
+ '''Test check_provider() - no specified'''
+ c = ClickReviewAccounts(self.test_name)
+ c.check_provider()
+ r = c.click_report
+ expected_counts = {'info': 0, 'warn': 0, 'error': 0}
+ self.check_results(r, expected_counts)
+
+ def test_check_provider_missing_id(self):
+ '''Test check_provider() - missing id'''
+ xml = self._stub_provider(id="")
+ self.set_test_account(self.default_appname, "account-provider", xml)
+ c = ClickReviewAccounts(self.test_name)
+ c.check_provider()
+ r = c.click_report
+ # provider prompts manual review, so for now, need to have error as +1
+ expected_counts = {'info': None, 'warn': 0, 'error': 2}
+ self.check_results(r, expected_counts)
+
+ def test_check_provider_wrong_id(self):
+ '''Test check_provider() - wrong id'''
+ xml = self._stub_provider(id="wrongid")
+ self.set_test_account(self.default_appname, "account-provider", xml)
+ c = ClickReviewAccounts(self.test_name)
+ c.check_provider()
+ r = c.click_report
+ # provider prompts manual review, so for now, need to have error as +1
+ expected_counts = {'info': None, 'warn': 0, 'error': 2}
+ 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)
+ c = ClickReviewAccounts(self.test_name)
+ c.check_qml_plugin()
+ r = c.click_report
+ # provider prompts manual review, so for now, need to have error as 1
+ expected_counts = {'info': 0, 'warn': 0, 'error': 1}
+ self.check_results(r, expected_counts)
+
+ def test_check_qml_plugin_not_specified(self):
+ '''Test check_qml_plugin() - no specified'''
+ c = ClickReviewAccounts(self.test_name)
+ c.check_qml_plugin()
+ r = c.click_report
+ expected_counts = {'info': 0, 'warn': 0, 'error': 0}
+ self.check_results(r, expected_counts)
diff -Nru click-reviewers-tools-0.7/clickreviews/tests/test_cr_push_helper.py click-reviewers-tools-0.8/clickreviews/tests/test_cr_push_helper.py
--- click-reviewers-tools-0.7/clickreviews/tests/test_cr_push_helper.py 1970-01-01 00:00:00.000000000 +0000
+++ click-reviewers-tools-0.8/clickreviews/tests/test_cr_push_helper.py 2014-07-25 13:47:22.000000000 +0000
@@ -0,0 +1,138 @@
+'''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 setUp(self):
+ # Monkey patch various file access classes. stop() is handled with
+ # addCleanup in super()
+ cr_tests.mock_patch()
+ super()
+
+ 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)
diff -Nru click-reviewers-tools-0.7/clickreviews/tests/test_cr_scope.py click-reviewers-tools-0.8/clickreviews/tests/test_cr_scope.py
--- click-reviewers-tools-0.7/clickreviews/tests/test_cr_scope.py 1970-01-01 00:00:00.000000000 +0000
+++ click-reviewers-tools-0.8/clickreviews/tests/test_cr_scope.py 2014-07-25 13:47:22.000000000 +0000
@@ -0,0 +1,145 @@
+'''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 setUp(self):
+ # Monkey patch various file access classes. stop() is handled with
+ # addCleanup in super()
+ cr_tests.mock_patch()
+ super()
+
+ 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.ini" % (scope["dir_rel"],
+ self.default_appname)
+ 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,
+ 'DisplayName': 'Foo',
+ 'Description': 'Some description',
+ 'Author': 'Foo Ltd.',
+ 'Art': '',
+ 'Icon': 'foo.svg',
+ 'SearchHint': 'Search Foo',
+ }
+
+ 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': 3, 'warn': 0, 'error': 0}
+ self.check_results(r, expected_counts)
+
+ def test_check_scope_ini_missing_required1(self):
+ '''Test check_scope_ini() - missing ScopeRunner'''
+ config = self._stub_config()
+ del config['ScopeRunner']
+ 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 Icon'''
+ config = self._stub_config()
+ del config['Icon']
+ 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 SearchHint'''
+ config = self._stub_config()
+ del config['SearchHint']
+ 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_required5(self):
+ '''Test check_scope_ini() - missing multiple'''
+ config = self._stub_config()
+ del config['ScopeRunner']
+ del config['DisplayName']
+ del config['Icon']
+ del config['SearchHint']
+ 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_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)
diff -Nru click-reviewers-tools-0.7/clickreviews/tests/test_cr_security.py click-reviewers-tools-0.8/clickreviews/tests/test_cr_security.py
--- click-reviewers-tools-0.7/clickreviews/tests/test_cr_security.py 2014-05-05 08:33:55.000000000 +0000
+++ click-reviewers-tools-0.8/clickreviews/tests/test_cr_security.py 2014-07-25 13:47:22.000000000 +0000
@@ -46,7 +46,7 @@
def test_check_policy_version_highest(self):
'''Test check_policy_version() - highest'''
c = ClickReviewSecurity(self.test_name)
- highest_version = sorted(c.supported_policy_versions)[-1]
+ highest_version = c._get_highest_policy_version("ubuntu")
version = highest_version
self.set_test_security_manifest(self.default_appname,
"policy_version", version)
@@ -68,7 +68,7 @@
self.set_test_security_manifest(self.default_appname,
"policy_version", bad_version)
- highest = sorted(c.supported_policy_versions)[-1]
+ highest = c._get_highest_policy_version("ubuntu")
c.check_policy_version()
report = c.click_report
@@ -88,7 +88,7 @@
def test_check_policy_version_low(self):
'''Test check_policy_version() - low version'''
c = ClickReviewSecurity(self.test_name)
- highest = sorted(c.supported_policy_versions)[-1]
+ highest = c._get_highest_policy_version("ubuntu")
version = 1.0
if version == highest:
print("SKIPPED-- test version '%s' is already highest" % version,
@@ -390,6 +390,94 @@
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_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", ["accounts"])
+ 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)
@@ -469,7 +557,7 @@
self.check_results(report, expected_counts)
def test_check_policy_groups_nonexistent(self):
- '''Test check_policy_groups_webapps() - nonexistent'''
+ '''Test check_policy_groups() - nonexistent'''
self.set_test_security_manifest(self.default_appname,
"policy_groups",
["networking", "nonexistent"])
@@ -480,7 +568,7 @@
self.check_results(report, expected_counts)
def test_check_policy_groups_reserved(self):
- '''Test check_policy_groups_webapps() - reserved'''
+ '''Test check_policy_groups() - reserved'''
self.set_test_security_manifest(self.default_appname,
"policy_groups",
["video_files", "networking"])
@@ -490,8 +578,20 @@
expected_counts = {'info': None, 'warn': 0, 'error': 1}
self.check_results(report, expected_counts)
+ 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_webapps() - empty'''
+ '''Test check_policy_groups() - empty'''
self.set_test_security_manifest(self.default_appname,
"policy_groups",
["", "networking"])
@@ -500,3 +600,87 @@
report = c.click_report
expected_counts = {'info': None, 'warn': 0, 'error': 1}
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"])
+ 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_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)
+ 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"])
+ 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_template_pushhelper(self):
+ '''Test check_template_pushhelper'''
+ self.set_test_push_helper(self.default_appname, "exec", "foo")
+ self.set_test_security_manifest(self.default_appname,
+ "template", "ubuntu-sdk")
+ self.set_test_security_manifest(self.default_appname,
+ "policy_groups",
+ ["push-notification-client"])
+ c = ClickReviewSecurity(self.test_name)
+ c.check_template_push_helpers()
+ report = c.click_report
+ expected_counts = {'info': 1, 'warn': 0, 'error': 0}
+ self.check_results(report, expected_counts)
+
+ def test_check_template_pushhelper_no_hook(self):
+ '''Test check_template_pushhelper'''
+ self.set_test_security_manifest(self.default_appname,
+ "template", "ubuntu-sdk")
+ c = ClickReviewSecurity(self.test_name)
+ c.check_template_push_helpers()
+ report = c.click_report
+ expected_counts = {'info': 0, 'warn': 0, 'error': 0}
+ self.check_results(report, expected_counts)
+
+ def test_check_template_pushhelper_wrong_template(self):
+ '''Test check_template_pushhelper - wrong template()'''
+ self.set_test_push_helper(self.default_appname, "exec", "foo")
+ self.set_test_security_manifest(self.default_appname,
+ "template", "ubuntu-webapp")
+ self.set_test_security_manifest(self.default_appname,
+ "policy_groups",
+ ["push-notification-client"])
+ c = ClickReviewSecurity(self.test_name)
+ c.check_template_push_helpers()
+ report = c.click_report
+ expected_counts = {'info': None, 'warn': 0, 'error': 1}
+ self.check_results(report, expected_counts)
diff -Nru click-reviewers-tools-0.7/clickreviews/tests/test_cr_url_dispatcher.py click-reviewers-tools-0.8/clickreviews/tests/test_cr_url_dispatcher.py
--- click-reviewers-tools-0.7/clickreviews/tests/test_cr_url_dispatcher.py 1970-01-01 00:00:00.000000000 +0000
+++ click-reviewers-tools-0.8/clickreviews/tests/test_cr_url_dispatcher.py 2014-07-25 13:47:22.000000000 +0000
@@ -0,0 +1,211 @@
+'''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 setUp(self):
+ # Monkey patch various file access classes. stop() is handled with
+ # addCleanup in super()
+ cr_tests.mock_patch()
+ super()
+
+ 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(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="domain-suffix",
+ 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)
diff -Nru click-reviewers-tools-0.7/data/apparmor-easyprof-ubuntu.json click-reviewers-tools-0.8/data/apparmor-easyprof-ubuntu.json
--- click-reviewers-tools-0.7/data/apparmor-easyprof-ubuntu.json 1970-01-01 00:00:00.000000000 +0000
+++ click-reviewers-tools-0.8/data/apparmor-easyprof-ubuntu.json 2014-07-25 13:47:58.000000000 +0000
@@ -0,0 +1,129 @@
+{
+ "ubuntu": {
+ "1.0": {
+ "templates": {
+ "common": [
+ "default",
+ "ubuntu-sdk",
+ "ubuntu-webapp"
+ ],
+ "reserved": [
+ "unconfined"
+ ]
+ },
+ "policy_groups": {
+ "common": [
+ "audio",
+ "camera",
+ "connectivity",
+ "content_exchange",
+ "content_exchange_source",
+ "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",
+ "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-scope-network",
+ "ubuntu-sdk",
+ "ubuntu-webapp"
+ ],
+ "reserved": [
+ "unconfined"
+ ]
+ },
+ "policy_groups": {
+ "common": [
+ "audio",
+ "camera",
+ "connectivity",
+ "content_exchange",
+ "content_exchange_source",
+ "location",
+ "microphone",
+ "networking",
+ "push-notification-client",
+ "sensors",
+ "usermetrics",
+ "video",
+ "webview"
+ ],
+ "reserved": [
+ "accounts",
+ "calendar",
+ "contacts",
+ "debug",
+ "history",
+ "music_files",
+ "music_files_read",
+ "picture_files",
+ "picture_files_read",
+ "video_files",
+ "video_files_read"
+ ]
+ }
+ }
+ }
+}
diff -Nru click-reviewers-tools-0.7/data/frameworks.json click-reviewers-tools-0.8/data/frameworks.json
--- click-reviewers-tools-0.7/data/frameworks.json 1970-01-01 00:00:00.000000000 +0000
+++ click-reviewers-tools-0.8/data/frameworks.json 2014-07-25 13:47:57.000000000 +0000
@@ -0,0 +1,19 @@
+{
+ "ubuntu-sdk-13.10": "deprecated",
+ "ubuntu-sdk-14.04-dev1": "deprecated",
+ "ubuntu-sdk-14.04-html-dev1": "deprecated",
+ "ubuntu-sdk-14.04-papi-dev1": "deprecated",
+ "ubuntu-sdk-14.04-qml-dev1": "deprecated",
+ "ubuntu-sdk-14.04": "available",
+ "ubuntu-sdk-14.04-html": "available",
+ "ubuntu-sdk-14.04-papi": "available",
+ "ubuntu-sdk-14.04-qml": "available",
+ "ubuntu-sdk-14.10-dev1": "obsolete",
+ "ubuntu-sdk-14.10-html-dev1": "obsolete",
+ "ubuntu-sdk-14.10-papi-dev1": "obsolete",
+ "ubuntu-sdk-14.10-qml-dev1": "obsolete",
+ "ubuntu-sdk-14.10-dev2": "available",
+ "ubuntu-sdk-14.10-html-dev2": "available",
+ "ubuntu-sdk-14.10-papi-dev2": "available",
+ "ubuntu-sdk-14.10-qml-dev2": "available"
+}
diff -Nru click-reviewers-tools-0.7/debian/bzr-builder.manifest click-reviewers-tools-0.8/debian/bzr-builder.manifest
--- click-reviewers-tools-0.7/debian/bzr-builder.manifest 2014-05-05 08:33:55.000000000 +0000
+++ click-reviewers-tools-0.8/debian/bzr-builder.manifest 2014-07-25 13:47:23.000000000 +0000
@@ -1,2 +1,2 @@
-# bzr-builder format 0.3 deb-version {debupstream}-0~194
-lp:click-reviewers-tools revid:daniel.holbach@canonical.com-20140505081411-j0unfdezjcp18mwt
+# bzr-builder format 0.3 deb-version {debupstream}-0~214
+lp:click-reviewers-tools revid:jamie@ubuntu.com-20140725133725-z9bl03us55byg6lw
diff -Nru click-reviewers-tools-0.7/debian/changelog click-reviewers-tools-0.8/debian/changelog
--- click-reviewers-tools-0.7/debian/changelog 2014-05-05 08:33:55.000000000 +0000
+++ click-reviewers-tools-0.8/debian/changelog 2014-07-25 13:47:23.000000000 +0000
@@ -1,15 +1,69 @@
-click-reviewers-tools (0.7-0~194~ubuntu14.04.1) trusty; urgency=low
+click-reviewers-tools (0.8-0~214~ubuntu14.04.1) trusty; urgency=low
* Auto build.
- -- Daniel Holbach Mon, 05 May 2014 08:33:55 +0000
+ -- Daniel Holbach Fri, 25 Jul 2014 13:47:23 +0000
-click-reviewers-tools (0.7) UNRELEASED; urgency=medium
+click-reviewers-tools (0.8) UNRELEASED; urgency=medium
- * clickreviews/cr_lint.py: add link to more info about "Please use newer
+ [ 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.
+
+ -- Jamie Strandboge Thu, 24 Jul 2014 14:04:23 -0500
+
+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.
- -- Daniel Holbach Tue, 29 Apr 2014 16:37:24 +0200
+ [ 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
diff -Nru click-reviewers-tools-0.7/debian/control click-reviewers-tools-0.8/debian/control
--- click-reviewers-tools-0.7/debian/control 2014-05-05 08:33:55.000000000 +0000
+++ click-reviewers-tools-0.8/debian/control 2014-07-25 13:47:22.000000000 +0000
@@ -2,12 +2,11 @@
Section: devel
Priority: optional
Maintainer: Ubuntu Appstore Developers
-Build-Depends: apparmor-easyprof,
- apparmor-easyprof-ubuntu (>= 1.0.44),
- debhelper (>= 9~),
+Build-Depends: debhelper (>= 9~),
python3-all (>= 3.2~),
python3-apt,
python3-debian,
+ python3-lxml,
python3-magic,
python3-setuptools,
python3-simplejson,
@@ -20,10 +19,9 @@
Package: click-reviewers-tools
Architecture: all
-Depends: apparmor-easyprof,
- apparmor-easyprof-ubuntu (>= 1.0.44),
- python3-apt,
+Depends: python3-apt,
python3-debian,
+ python3-lxml,
python3-magic,
python3-simplejson,
python3-xdg,
diff -Nru click-reviewers-tools-0.7/debian/rules click-reviewers-tools-0.8/debian/rules
--- click-reviewers-tools-0.7/debian/rules 2014-05-05 08:33:55.000000000 +0000
+++ click-reviewers-tools-0.8/debian/rules 2014-07-25 13:47:22.000000000 +0000
@@ -18,7 +18,8 @@
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-frameworks ./data/frameworks.json)
+ -$(shell python3 ./bin/update-apparmor-policy ./data/apparmor-easyprof-ubuntu.json)
override_dh_auto_build:
dh_auto_build