lp-scanner-team team mailing list archive
-
lp-scanner-team team
-
Mailing list archive
-
Message #00017
lp:~mike-powerthroughwords/lp-scanner/bug-1174157-security-permission-exempt-teams into lp:lp-scanner
Mike Heald has proposed merging lp:~mike-powerthroughwords/lp-scanner/bug-1174157-security-permission-exempt-teams into lp:lp-scanner.
Requested reviews:
The Launchpad Security Scanner Dev Team (lp-scanner-team)
Related bugs:
Bug #1174157 in lp-scanner: "Add config option to exempt Private Security permission check"
https://bugs.launchpad.net/lp-scanner/+bug/1174157
For more details, see:
https://code.launchpad.net/~mike-powerthroughwords/lp-scanner/bug-1174157-security-permission-exempt-teams/+merge/164407
Rewrote the tests. They pass now, and, there are some.
Added some changes for python3.
Added new config section that allows you to exempt certain teams from giving their project team access to the security bugs.
--
https://code.launchpad.net/~mike-powerthroughwords/lp-scanner/bug-1174157-security-permission-exempt-teams/+merge/164407
Your team The Launchpad Security Scanner Dev Team is requested to review the proposed merge of lp:~mike-powerthroughwords/lp-scanner/bug-1174157-security-permission-exempt-teams into lp:lp-scanner.
=== modified file 'security-scanner.py'
--- security-scanner.py 2013-04-25 05:02:23 +0000
+++ security-scanner.py 2013-05-17 14:01:29 +0000
@@ -3,7 +3,8 @@
# Copyright 2010 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
#
-# Original Ugly Proof of Concept Code by Joey Stanford @ UDS Maverick 2010-05-14
+# Original Ugly Proof of Concept Code by Joey Stanford @ UDS Maverick
+# 2010-05-14
# Dusting and Cleaning by Brad Crittenden 2010-05-17
# Heavy Overhaul by Karl Fogel 2010-05-21
# Tests added by Diogo Matsubara 2010-10-18
@@ -14,7 +15,7 @@
from __future__ import print_function
-USAGE="""\
+USAGE = """
Scan projects and report on potential security exposures.
Run '%prog --help' for detailed usage.
@@ -22,7 +23,15 @@
"""
-import cStringIO
+# python2/3 compatibility
+try:
+ import cStringIO
+ from urllib import urlopen
+except ImportError:
+ from io import StringIO as cStringIO
+ from urllib.request import urlopen
+
+import collections
import functools
import optparse
import os
@@ -30,13 +39,14 @@
import re
import sys
import unittest
-from urllib import urlopen
from launchpadlib.launchpad import Launchpad
-from launchpadlib.errors import *
-from lazr.restfulclient.errors import *
-
-
+from launchpadlib.errors import HTTPError
+
+
+# this implements ConfigParser in a way that isn't compatible with
+# ConfigParser. Replacing this would mean changing our config files
+# so, leaving for the moment.
class ConfigParser():
"""Temporarily re-implement a subset of Python's ConfigParser.
Python 2.7 and 3.0 will have the fix for http://bugs.python.org/issue7005
@@ -109,6 +119,8 @@
# Real ConfigParser handles multiline options in which
# leading whitespace is significant. We don't bother with
# that here -- we just strip both sides and then parse it.
+ # ALSO: comments in the staging-config.cfg make the real
+ # ConfigParser choke.
line = line.strip()
if len(line) == 0 or line[0] == ';' or line[0] == '#':
continue
@@ -126,11 +138,10 @@
def read(self, file_or_filenames):
lst = []
successful_files = []
- if type(file_or_filenames) == type(""):
+ if isinstance(file_or_filenames, str):
lst.append(file_or_filenames)
- elif (type(file_or_filenames) == type([]) or
- type(file_or_filenames) == type(())):
- lst = file_or_filenames
+ elif isinstance(file_or_filenames, collections.Iterable):
+ lst = file_or_filenames
for fname in lst:
try:
self.readfp(open(fname)) # GC closes filehandle, right?
@@ -143,14 +154,14 @@
return self._sections
def has_section(self, section_name):
- return self._sections.has_key(section_name)
+ return section_name in self._sections
def options(self, section):
return self._sections[section].keys()
def has_option(self, section_name, option_name):
if (self.has_section(section_name)
- and self._sections[section_name].has_key(option_name)):
+ and option_name in self._sections[section_name]):
return True
else:
return False
@@ -179,7 +190,7 @@
service_root=service_name.lower(),
version="devel",
allow_access_levels=access_levels)
- except HTTPError, e:
+ except HTTPError as e:
print_HTTPError(e)
raise e
return lp
@@ -214,83 +225,110 @@
"""Print information about possible security leaks."""
printf = functools.partial(print, file=outfile)
- project_groups = config.options('project-groups')
+ project_groups = config.options('project-groups')
suspicious_drivers = config.options('suspicious-drivers')
- allowed_owners = config.options('allowed-owners')
- branch_exceptions = config.options('branch-exceptions')
- bug_exceptions = config.options('bug-exceptions')
- ppa_exceptions = config.options('ppa-exceptions')
+ allowed_owners = config.options('allowed-owners')
+ branch_exceptions = config.options('branch-exceptions')
+ bug_exceptions = config.options('bug-exceptions')
+ ppa_exceptions = config.options('ppa-exceptions')
allowed_mailing_lists = config.options('mailinglist-exceptions')
nonstandard_project_teams = config.options('nonstandard-project-teams')
+ private_security_exceptions = config.options(
+ 'security-bug-sharing-exceptions')
# Traverse the project groups.
for project_group in sorted(project_groups):
# Collect results, so we can output them grouped and sorted (that
# way results from different runs can be meaningfully compared).
- unset_drivers = {} # Map project objects to None.
- wrong_drivers = {} # Map project objects to None.
- wrong_drivers_owners = {} # Map project objects to None.
- wrong_owners = {} # Map project objects to None.
+ unset_drivers = {} # Map project objects to None.
+ wrong_drivers = {} # Map project objects to None.
+ wrong_drivers_owners = {} # Map project objects to None.
+ wrong_owners = {} # Map project objects to None.
public_branches = {} # Map branch objects to project objects.
- public_bugs = {} # Map bug objects to project objects.
- public_ppas = {} # Map PPA objects to project objects.
- project_team_private_security = {} # Map project objects to sharing policy
+ public_bugs = {} # Map bug objects to project objects.
+ public_ppas = {} # Map PPA objects to project objects.
+ project_team_private_security = {} # project objects to sharing policy
exposed_mailing_lists = {} # Map team driver to None
# Get list of projects associated with the project group.
for project in lp.project_groups[project_group].projects:
+
if project.driver is None:
+ # Missing project driver
unset_drivers[project] = None
elif project.driver.name in suspicious_drivers:
+ # Driver that is a bit suspicious
wrong_drivers[project] = None
elif (project.driver.team_owner and
project.driver.team_owner.name not in allowed_owners):
+ # Team owner that's not allowed to own this
wrong_drivers_owners[project] = None
+
if project.owner.name not in allowed_owners:
+ # Project owner that's not allowed to own the project
wrong_owners[project] = None
+
for branch in project.getBranches():
if (not branch.private and
- branch.name not in branch_exceptions):
+ branch.name not in branch_exceptions):
+ # Public branch that we haven't made an exception for
public_branches[branch] = project
+
if project.name not in bug_exceptions:
- bug_tasks = project.searchTasks(order_by='id') #split for performance
+ bug_tasks = project.searchTasks(order_by='id')
+ # public bugs where the project isn't allowed to
+ # have public bugs and the bug itself isn't an exception also
for bug_task in bug_tasks:
- if not bug_task.bug.private: #split for performance
- if str(bug_task.bug.id) not in bug_exceptions:
- public_bugs[bug_task.bug] = project
+ if not bug_task.bug.private:
+ if str(bug_task.bug.id) not in bug_exceptions:
+ public_bugs[bug_task.bug] = project
+
for ppa in project.owner.ppas:
+ # any ppas that are public and not listed as an exception
if (not ppa.private and
- ppa.name not in ppa_exceptions):
+ ppa.name not in ppa_exceptions):
public_ppas[ppa] = project
+
project_team_grantee = False
+
for grantee in sharing.getPillarGranteeData(pillar=project):
+ # if the grantee is the project team and has not been
+ # granted permission to private security bugs
name = grantee["name"]
if (name == project.name + "-team" or
- name in nonstandard_project_teams):
+ name in nonstandard_project_teams):
project_team_grantee = True
permissions = grantee["permissions"]
- if ("PRIVATESECURITY" not in permissions
- or permissions["PRIVATESECURITY"] != "ALL"):
+ if (name not in private_security_exceptions
+ and (
+ "PRIVATESECURITY" not in permissions
+ or permissions["PRIVATESECURITY"] != "ALL")):
project_team_private_security[project] = None
break
+
if not project_team_grantee:
project_team_private_security[project] = None
+
# check for rogue mailing lists on the project driver
if (project.driver and
- project.driver.name not in allowed_mailing_lists):
- targeturl = "https://lists.launchpad.net/"+project.driver.name
+ project.driver.name not in allowed_mailing_lists):
+ targeturl = "https://lists.launchpad.net/%s" % (
+ project.driver.name)
if urlopen(targeturl).getcode() != 404:
- exposed_mailing_lists[project.driver] = None
+ exposed_mailing_lists[project.driver] = None
# Only print output for this project group if there were problems.
printed_project_section_start = [False]
+
def header(message):
if not printed_project_section_start[0]:
printf("* In project group %s:\n" % project_group)
printed_project_section_start[0] = True
printf(" === %s ===\n" % message)
+
def footer(message):
printf("\n (%s)" % message)
+
def maybe_custom_message(section):
"If there is a custom message for a section, print it."
if config.has_option("custom-messages", section):
@@ -304,6 +342,7 @@
footer("The driver should be set to the project team.")
maybe_custom_message("unset-drivers")
printf("")
+
if wrong_drivers:
header("Probably Wrong Driver")
for key in sorted(wrong_drivers.keys()):
@@ -312,6 +351,7 @@
footer("The driver should probably be set to the project team.")
maybe_custom_message("wrong-drivers")
printf("")
+
if wrong_owners:
header("Wrong Maintainer (Owner)")
for key in sorted(wrong_owners.keys()):
@@ -320,6 +360,7 @@
footer("Are those the right owners?")
maybe_custom_message("wrong-owners")
printf("")
+
if wrong_drivers_owners:
header("Wrong Team Owner")
for key in sorted(wrong_drivers_owners.keys()):
@@ -328,6 +369,7 @@
footer("Are those the right owners?")
maybe_custom_message("wrong-owners")
printf("")
+
if public_branches:
header("Exposed Branches")
for key in sorted(public_branches.keys()):
@@ -336,6 +378,7 @@
footer("Those branches are exposed -- should any be private?")
maybe_custom_message("public-branches")
printf("")
+
if public_bugs:
header("Exposed Bugs")
for key in sorted(public_bugs.keys()):
@@ -344,6 +387,7 @@
footer("Those bugs are exposed -- should any be private?")
maybe_custom_message("public-bugs")
printf("")
+
if public_ppas:
header("Exposed PPAs")
for key in sorted(public_ppas.keys()):
@@ -352,6 +396,7 @@
footer("Those PPAs are exposed -- should any be private?")
maybe_custom_message("public-ppas")
printf("")
+
if project_team_private_security:
header("Project does not share Private Security bugs with "
"project team")
@@ -362,6 +407,7 @@
"the project team")
maybe_custom_message("project-team-private-security")
printf("")
+
if exposed_mailing_lists:
header("Public Mailing Lists")
for key in sorted(exposed_mailing_lists.keys()):
@@ -371,106 +417,6 @@
printf("")
-class TestSecurityScanner(unittest.TestCase):
-
- def setUp(self):
- # Set up objects used by tests.
- self._create_objects()
- # Get the read-only instance.
- self.lp = get_lp_instance('staging')
- self.config = ConfigParser()
- self.config.read('staging-config.cfg')
-
- def _create_objects(self):
- """Set up test objects used by tests."""
- # To set up test objects we need a writable lp instance.
- lp = get_lp_instance('staging', read_only=False)
-
- # It's not possible to create a project group using the API, so let's
- # modify an existing one.
- test_project = lp.project_groups['test-project']
- # Remove all projects from this project group.
- for project in test_project.projects:
- project.project_group = None
- project.lp_save()
-
- # Create a couple of new projects, if they don't exist already.
- sample_project_names = ["project1234", "project2341"]
- self.sample_projects = []
- for name in sample_project_names:
- try:
- project = lp.projects.new_project(
- name=name, display_name=name, summary=name, title=name)
- project.lp_save()
- except HTTPError, e:
- # Project already exists.
- project = lp.projects[name]
- # XXX 2010-10-18 matsubara: Due to bug 662740, we can't set an
- # attribute for this object without fetching the
- # representation first.
- ignored = project.name
- self.sample_projects.append(project)
-
- # Add them to the test-project project group.
- for project in self.sample_projects:
- project.project_group = test_project
- project.lp_save()
-
- # Make all bugs private on those projects.
- for project in self.sample_projects:
- for btask in project.searchTasks():
- bug = btask.bug
- if not bug.private:
- bug.private = True
- bug.lp_save()
-
- # File new bugs on those projects.
- self.sample_bugs = []
- self.sample_bugs.append(lp.bugs.createBug(
- target=self.sample_projects[0], description='foo', title='bar'))
- self.sample_bugs.append(lp.bugs.createBug(
- target=self.sample_projects[1], description='foo', title='bar'))
-
- def _get_sample_report(self):
- """Fill data in sample report from objects created by tests."""
- security_report = open('staging-report.txt').read()
- # XXX 2010-10-18 matsubara: Missing data for PPA and Branch in the
- # report since I can't create those through the API.
- data = {
- 'project1': self.sample_projects[0].name,
- 'project2': self.sample_projects[1].name,
- 'bug1': self.sample_bugs[0].id,
- 'bug2': self.sample_bugs[1].id,
- 'owner': self.lp.me.display_name
- }
- return security_report % data
-
- def test_read_config(self):
- expected_sections = [
- 'allowed-owners', 'branch-exceptions', 'bug-exceptions',
- 'custom-messages', 'ppa-exceptions', 'project-groups',
- 'suspicious-drivers']
- sections = sorted(self.config.sections().keys())
- self.assertEqual(sections, expected_sections)
-
- def test_security_scanner(self):
- outfile = cStringIO.StringIO()
- security_scanner(self.lp, self.config, outfile)
- outfile.seek(0)
- security_report = outfile.read()
- sample_security_report = self._get_sample_report()
- # XXX 2010-10-15 matsubara: Use self.assertMultiLineEqual() here when
- # python2.7 is available. We'll get better output (i.e. diff-like)
- # when there's a test failure.
- self.assertEqual(security_report, sample_security_report)
-
-
-def _selftest():
- """Run tests for this script using the staging web service."""
- suite = unittest.TestLoader().loadTestsFromTestCase(TestSecurityScanner)
- unittest.TextTestRunner(verbosity=2).run(suite)
-
-
def main():
# Login to service.
opts, args = parse_args()
@@ -493,3 +439,303 @@
if __name__ == "__main__":
main()
+
+
+try:
+ import mocker
+
+ class TestSecurityScanner(mocker.MockerTestCase, unittest.TestCase):
+
+ def setUp(self):
+ super(TestSecurityScanner, self).setUp()
+ self.lp = self.mocker.mock()
+ self.sharing = self.mocker.mock()
+ self.config = ConfigParser()
+ self.config.read('staging-config.cfg')
+
+ def _setup_owner(
+ self, name="Owner Name", display_name="Owner Display Name",
+ ppas=[]):
+ mock_owner = self.mocker.mock()
+ mock_owner.name
+ self.mocker.result(name)
+ self.mocker.count(0, None)
+ mock_owner.display_name
+ self.mocker.result(display_name)
+ self.mocker.count(0, None)
+ mock_owner.ppas
+ self.mocker.result(ppas)
+ self.mocker.count(0, None)
+ return mock_owner
+
+ def _setup_driver(
+ self, name="Driver Name", team_owner=None,
+ mailing_list_status_code=404):
+ mock_driver = self.mocker.mock()
+ mock_driver.name
+ self.mocker.result(name)
+ self.mocker.count(0, None)
+ mock_driver.team_owner
+ self.mocker.result(team_owner)
+ self.mocker.count(0, None)
+ urlopen_mock = self.mocker.replace(urlopen)
+ urlopen_mock(
+ "https://lists.launchpad.net/" + name).getcode()
+ self.mocker.result(mailing_list_status_code)
+ self.mocker.count(0, None)
+ return mock_driver
+
+ def _setup_project(
+ self, driver=None, owner=None, branches=[],
+ name="project-name", tasks=[]):
+ if owner is None:
+ owner = self._setup_owner()
+ mock_project = self.mocker.mock()
+ mock_project.driver
+ self.mocker.result(driver)
+ self.mocker.count(0, None)
+ mock_project.owner
+ self.mocker.result(owner)
+ self.mocker.count(0, None)
+ mock_project.getBranches()
+ self.mocker.result(branches)
+ mock_project.name
+ self.mocker.result(name)
+ self.mocker.count(0, None)
+ mock_project.searchTasks(order_by='id')
+ self.mocker.result(tasks)
+ return mock_project
+
+ def _setup_ppa(self, name="mock-ppa", private=False):
+ mock_ppa = self.mocker.mock()
+ mock_ppa.private
+ self.mocker.result(private)
+ self.mocker.count(0, None)
+ mock_ppa.name
+ self.mocker.result(name)
+ self.mocker.count(0, None)
+ return mock_ppa
+
+ def _setup_branch(self, name="branch", private=False):
+ mock_branch = self.mocker.mock()
+ mock_branch.private
+ self.mocker.result(private)
+ mock_branch.name
+ self.mocker.result(name)
+ self.mocker.count(0, None)
+ return mock_branch
+
+ def _setup_bug(self, id=42, private=False):
+ mock_bug = self.mocker.mock()
+ mock_bug.id
+ self.mocker.result(id)
+ self.mocker.count(0, None)
+ mock_bug.private
+ self.mocker.result(private)
+ self.mocker.count(0, None)
+ return mock_bug
+
+ def _setup_task(self, bug=None):
+ mock_task = self.mocker.mock()
+ mock_task.bug
+ self.mocker.result(bug)
+ self.mocker.count(0, None)
+ return mock_task
+
+ def _setup_project_group(self, group_name, projects=[]):
+ self.lp.project_groups[group_name].projects
+ self.mocker.result(projects)
+
+ def _setup_project_grantee(
+ self, project, name="Mr Grantee", permissions=[]):
+ mock_grantee = self.mocker.mock()
+ mock_grantee["name"]
+ self.mocker.result(name)
+ self.mocker.count(0, None)
+ mock_grantee["permissions"]
+ self.mocker.result(permissions)
+ self.mocker.count(0, None)
+ self.sharing.getPillarGranteeData(pillar=project)
+ self.mocker.result([mock_grantee, ])
+ self.mocker.count(0, None)
+
+ def test_read_config(self):
+ expected_sections = [
+ 'allowed-owners', 'branch-exceptions', 'bug-exceptions',
+ 'custom-messages', 'mailinglist-exceptions',
+ 'nonstandard-project-teams', 'ppa-exceptions',
+ 'project-groups', 'security-bug-sharing-exceptions',
+ 'suspicious-drivers']
+ sections = self.config.sections().keys()
+ self.assertEqual(set(sections), set(expected_sections))
+
+ def test_project_with_no_driver(self):
+ mock_project = self._setup_project()
+ self._setup_project_group("test-project", [mock_project, ])
+ self._setup_project_grantee(mock_project)
+ self.mocker.replay()
+ outfile = cStringIO.StringIO()
+ security_scanner(self.lp, self.sharing, self.config, outfile)
+ outfile.seek(0)
+ security_report = outfile.read()
+ self.assertTrue("Driver Not Set" in security_report)
+ self.assertTrue(
+ "project project-name has no driver" in security_report)
+
+ def test_project_with_wrong_driver(self):
+ mock_project = self._setup_project(
+ driver=self._setup_driver(name="kfogel"))
+ self._setup_project_group("test-project", [mock_project, ])
+ self._setup_project_grantee(mock_project)
+ self.mocker.replay()
+ outfile = cStringIO.StringIO()
+ security_scanner(self.lp, self.sharing, self.config, outfile)
+ outfile.seek(0)
+ security_report = outfile.read()
+ self.assertTrue("Probably Wrong Driver" in security_report)
+ self.assertTrue(
+ "project project-name has driver kfogel" in security_report)
+
+ def test_project_with_wrong_maintainer(self):
+ mock_project = self._setup_project(
+ driver=self._setup_driver(name="Driver"), name="Test",
+ owner=self._setup_owner(
+ name="not-joey", display_name="Not Joey"))
+ self._setup_project_group("test-project", [mock_project, ])
+ self._setup_project_grantee(mock_project)
+ self.mocker.replay()
+ outfile = cStringIO.StringIO()
+ security_scanner(self.lp, self.sharing, self.config, outfile)
+ outfile.seek(0)
+ security_report = outfile.read()
+ self.assertTrue("Wrong Maintainer (Owner)" in security_report)
+ self.assertTrue(
+ "project Test has owner Not Joey" in security_report)
+
+ def test_project_with_public_branches(self):
+ mock_branch = self._setup_branch(private=False, name="pubbranch")
+ mock_project = self._setup_project(
+ branches=[mock_branch, ], name="project")
+ self._setup_project_group("test-project", [mock_project, ])
+ self._setup_project_grantee(mock_project)
+ self.mocker.replay()
+ outfile = cStringIO.StringIO()
+ security_scanner(self.lp, self.sharing, self.config, outfile)
+ outfile.seek(0)
+ security_report = outfile.read()
+ self.assertTrue("Exposed Branches" in security_report)
+ self.assertTrue(
+ "branch pubbranch (on project project) is public"
+ in security_report)
+
+ def test_project_with_public_bugs(self):
+ mock_bug = self._setup_bug(private=False)
+ mock_task = self._setup_task(bug=mock_bug)
+ mock_project = self._setup_project(
+ name="project", tasks=[mock_task, ])
+ self._setup_project_group("test-project", [mock_project, ])
+ self._setup_project_grantee(mock_project)
+ self.mocker.replay()
+ outfile = cStringIO.StringIO()
+ security_scanner(self.lp, self.sharing, self.config, outfile)
+ outfile.seek(0)
+ security_report = outfile.read()
+ self.assertTrue("Exposed Bugs" in security_report)
+ self.assertTrue(
+ "bug 42 (on project project) is public"
+ in security_report)
+
+ def test_project_with_public_ppas(self):
+ mock_ppa = self._setup_ppa(private=False)
+ mock_owner = self._setup_owner(ppas=[mock_ppa, ])
+ mock_project = self._setup_project(
+ name="project", owner=mock_owner)
+ self._setup_project_group("test-project", [mock_project, ])
+ self._setup_project_grantee(mock_project)
+ self.mocker.replay()
+ outfile = cStringIO.StringIO()
+ security_scanner(self.lp, self.sharing, self.config, outfile)
+ outfile.seek(0)
+ security_report = outfile.read()
+ self.assertTrue("Exposed PPAs" in security_report)
+ self.assertTrue(
+ "PPA mock-ppa (on project project) is public"
+ in security_report)
+
+ def test_project_not_sharing_security_bugs(self):
+ mock_project = self._setup_project(name="project")
+ self._setup_project_group("test-project", [mock_project, ])
+ self._setup_project_grantee(
+ mock_project, name="project-team", permissions=[])
+ self.mocker.replay()
+ outfile = cStringIO.StringIO()
+ security_scanner(self.lp, self.sharing, self.config, outfile)
+ outfile.seek(0)
+ security_report = outfile.read()
+ self.assertTrue(
+ "project project does not share Private Security bugs "
+ "with the project team"
+ in security_report)
+
+ def test_project_with_rogue_mailing_lists_on_driver(self):
+ driver = self._setup_driver(
+ name="not-in-allowed-mailing-lists",
+ mailing_list_status_code=200)
+ mock_project = self._setup_project(name="project", driver=driver)
+ self._setup_project_group("test-project", [mock_project, ])
+ self._setup_project_grantee(
+ mock_project, name="project-team", permissions=[])
+ self.mocker.replay()
+ outfile = cStringIO.StringIO()
+ security_scanner(self.lp, self.sharing, self.config, outfile)
+ outfile.seek(0)
+ security_report = outfile.read()
+ self.assertTrue(
+ "Team not-in-allowed-mailing-lists "
+ "has a public mailing list" in security_report)
+
+ def test_project_that_doesnt_have_any_issues(self):
+ mock_project = self._setup_project(
+ name="ok-project",
+ driver=self._setup_driver(name="valid-driver"),
+ owner=self._setup_owner(
+ name="joey", ppas=[self._setup_ppa(private=True), ]),
+ branches=[self._setup_branch(private=True), ],
+ tasks=[self._setup_task(bug=self._setup_bug(private=True)), ]
+ )
+ self._setup_project_group("test-project", [mock_project, ])
+ self._setup_project_grantee(
+ mock_project, name="ok-project-team",
+ permissions={"PRIVATESECURITY": "ALL"})
+ self.mocker.replay()
+ outfile = cStringIO.StringIO()
+ security_scanner(self.lp, self.sharing, self.config, outfile)
+ outfile.seek(0)
+ security_report = outfile.read()
+ self.assertEqual("", security_report)
+
+ def test_project_not_sharing_security_bugs_exception(self):
+ mock_project = self._setup_project(name="some-project")
+ self._setup_project_group("test-project", [mock_project, ])
+ self._setup_project_grantee(
+ mock_project, name="some-project-team", permissions={})
+ self.mocker.replay()
+ outfile = cStringIO.StringIO()
+ security_scanner(self.lp, self.sharing, self.config, outfile)
+ outfile.seek(0)
+ security_report = outfile.read()
+ self.assertFalse(
+ "project some-project does not share Private Security bugs "
+ "with the project team"
+ in security_report)
+
+
+except ImportError:
+ if __name__ == "security-scanner":
+ raise
+
+
+def _selftest():
+ """Run tests for this script using the staging web service."""
+ suite = unittest.TestLoader().loadTestsFromTestCase(TestSecurityScanner)
+ unittest.TextTestRunner(verbosity=2).run(suite)
=== modified file 'staging-config.cfg'
--- staging-config.cfg 2010-12-01 23:44:52 +0000
+++ staging-config.cfg 2013-05-17 14:01:29 +0000
@@ -53,3 +53,8 @@
#
[custom-messages]
wrong-owners = NOTE: You've got yourself a wrong owner there buddy!
+
+[security-bug-sharing-exceptions]
+some-project-team
+
+[nonstandard-project-teams]
Follow ups