← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~stevenk/launchpad/destroy-security-contact into lp:launchpad

 

Steve Kowalik has proposed merging lp:~stevenk/launchpad/destroy-security-contact into lp:launchpad.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)
Related bugs:
  Bug #1039592 in Launchpad itself: "Sharing make the security contact role obsolete"
  https://bugs.launchpad.net/launchpad/+bug/1039592

For more details, see:
https://code.launchpad.net/~stevenk/launchpad/destroy-security-contact/+merge/120685

With the advent of sharing, and the ability to structurally subscribe to a product or distribution's bug filtered by their information_type, the security_contact field is now not needed and can be killed. This also kills BugRoleMixin and its plethora of XSSes.

This can not land until writable sharing is turned on for production.
-- 
https://code.launchpad.net/~stevenk/launchpad/destroy-security-contact/+merge/120685
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~stevenk/launchpad/destroy-security-contact into lp:launchpad.
=== removed file 'lib/lp/bugs/browser/bugrole.py'
--- lib/lp/bugs/browser/bugrole.py	2012-08-16 05:18:54 +0000
+++ lib/lp/bugs/browser/bugrole.py	1970-01-01 00:00:00 +0000
@@ -1,80 +0,0 @@
-# Copyright 2010-2012 Canonical Ltd.  This software is licensed under the
-# GNU Affero General Public License version (see the file LICENSE).
-
-"""Common classes to support bug roles."""
-
-__metaclass__ = type
-
-__all__ = [
-    'BugRoleMixin',
-    ]
-
-from lp.services.webapp.menu import structured
-from lp.services.webapp.publisher import canonical_url
-
-
-class BugRoleMixin:
-
-    INVALID_PERSON = object()
-    OTHER_USER = object()
-    OTHER_TEAM = object()
-    OK = object()
-
-    def _getFieldState(self, current_role, field_name, data):
-        """Return the enum that summarises the field state."""
-        # The field_name will not be in the data if the user did not enter
-        # a person in the ValidPersonOrTeam vocabulary.
-        if field_name not in data:
-            return self.INVALID_PERSON
-        role = data[field_name]
-        user = self.user
-        # If no data was changed, the field is OK regardless of who the
-        # current user is.
-        if current_role == role:
-            return self.OK
-        # The user may assign the role to None, himself, or a team he admins.
-        if role is None or self.context.userCanAlterSubscription(role, user):
-            return self.OK
-        # The user is not an admin of the team, or he entered another user.
-        if role.is_team:
-            return self.OTHER_TEAM
-        else:
-            return self.OTHER_USER
-
-    def changeSecurityContact(self, security_contact):
-        if self.context.security_contact != security_contact:
-            self.context.security_contact = security_contact
-
-    def validateSecurityContact(self, data):
-        """Validates the new security contact.
-
-        Verify that the value is None, the user, or a team he administers,
-        otherwise, set a field error.
-        """
-        field_state = self._getFieldState(
-            self.context.security_contact, 'security_contact', data)
-        if field_state is self.INVALID_PERSON:
-            error = (
-                'You must choose a valid person or team to be the '
-                'security contact for %s.' % self.context.displayname)
-        elif field_state is self.OTHER_TEAM:
-            supervisor = data['security_contact']
-            team_url = canonical_url(
-                supervisor, rootsite='mainsite', view_name='+members')
-            error = structured(
-                'You cannot set %(team)s as the security contact for '
-                '%(target)s because you are not an administrator of that '
-                'team.<br />If you believe that %(team)s should be the '
-                'security contact for %(target)s, notify one of the '
-                '<a href="%(url)s">%(team)s administrators</a>.',
-                team=supervisor.displayname,
-                target=self.context.displayname,
-                url=team_url)
-        elif field_state is self.OTHER_USER:
-            error = structured(
-                'You cannot set another person as the security contact for '
-                '%(target)s.', target=self.context.displayname)
-        else:
-            # field_state is self.OK.
-            return
-        self.setFieldError('security_contact', error)

=== modified file 'lib/lp/bugs/browser/bugsupervisor.py'
--- lib/lp/bugs/browser/bugsupervisor.py	2012-08-16 05:18:54 +0000
+++ lib/lp/bugs/browser/bugsupervisor.py	2012-08-22 01:27:20 +0000
@@ -16,7 +16,6 @@
     action,
     LaunchpadEditFormView,
     )
-from lp.bugs.browser.bugrole import BugRoleMixin
 from lp.bugs.interfaces.bugsupervisor import IHasBugSupervisor
 from lp.services.webapp.menu import structured
 from lp.services.webapp.publisher import canonical_url
@@ -32,7 +31,7 @@
         IHasBugSupervisor['bug_supervisor'], readonly=False)
 
 
-class BugSupervisorEditView(BugRoleMixin, LaunchpadEditFormView):
+class BugSupervisorEditView(LaunchpadEditFormView):
     """Browser view class for editing the bug supervisor."""
 
     schema = BugSupervisorEditSchema

=== modified file 'lib/lp/bugs/browser/bugtarget.py'
--- lib/lp/bugs/browser/bugtarget.py	2012-08-16 05:18:54 +0000
+++ lib/lp/bugs/browser/bugtarget.py	2012-08-22 01:27:20 +0000
@@ -76,7 +76,6 @@
     GhostWidget,
     ProductBugTrackerWidget,
     )
-from lp.bugs.browser.bugrole import BugRoleMixin
 from lp.bugs.browser.structuralsubscription import (
     expose_structural_subscription_data_to_js,
     )
@@ -105,7 +104,6 @@
     UNRESOLVED_BUGTASK_STATUSES,
     )
 from lp.bugs.interfaces.bugtracker import IBugTracker
-from lp.bugs.interfaces.securitycontact import IHasSecurityContact
 from lp.bugs.model.bugtask import BugTask
 from lp.bugs.model.structuralsubscription import (
     get_structural_subscriptions_for_target,
@@ -161,7 +159,6 @@
 
     bug_supervisor = copy_field(
         IHasBugSupervisor['bug_supervisor'], readonly=False)
-    security_contact = copy_field(IHasSecurityContact['security_contact'])
     private_bugs = copy_field(
         IProduct['private_bugs'], readonly=False)
     official_malone = copy_field(ILaunchpadUsage['official_malone'])
@@ -184,8 +181,7 @@
     return product
 
 
-class ProductConfigureBugTrackerView(BugRoleMixin,
-                                     ProductPrivateBugsMixin,
+class ProductConfigureBugTrackerView(ProductPrivateBugsMixin,
                                      ProductConfigureBase):
     """View class to configure the bug tracker for a project."""
 
@@ -210,15 +206,13 @@
             "private_bugs"
             ]
         if check_permission("launchpad.Edit", self.context):
-            field_names.extend(["bug_supervisor", "security_contact"])
+            field_names.append("bug_supervisor")
 
         return field_names
 
     def validate(self, data):
         """Constrain bug expiration to Launchpad Bugs tracker."""
         super(ProductConfigureBugTrackerView, self).validate(data)
-        if check_permission("launchpad.Edit", self.context):
-            self.validateSecurityContact(data)
         # enable_bug_expiration is disabled by JavaScript when bugtracker
         # is not 'In Launchpad'. The constraint is enforced here in case the
         # JavaScript fails to activate or run. Note that the bugtracker
@@ -230,12 +224,6 @@
 
     @action("Change", name='change')
     def change_action(self, action, data):
-        # security_contact requires a transition method, so it must be
-        # handled separately and removed for the updateContextFromData call
-        # to work as expected.
-        if check_permission("launchpad.Edit", self.context):
-            self.changeSecurityContact(data['security_contact'])
-            del data['security_contact']
         self.updateContextFromData(data)
 
 

=== modified file 'lib/lp/bugs/browser/bugtask.py'
--- lib/lp/bugs/browser/bugtask.py	2012-08-17 19:19:30 +0000
+++ lib/lp/bugs/browser/bugtask.py	2012-08-22 01:27:20 +0000
@@ -2507,11 +2507,6 @@
     def bugsupervisor(self):
         return Link('+bugsupervisor', 'Change bug supervisor', icon='edit')
 
-    @enabled_with_permission('launchpad.Edit')
-    def securitycontact(self):
-        return Link(
-            '+securitycontact', 'Change security contact', icon='edit')
-
     def nominations(self):
         return Link('+nominations', 'Review nominations', icon='bug')
 

=== modified file 'lib/lp/bugs/browser/configure.zcml'
--- lib/lp/bugs/browser/configure.zcml	2012-08-03 08:02:41 +0000
+++ lib/lp/bugs/browser/configure.zcml	2012-08-22 01:27:20 +0000
@@ -379,12 +379,6 @@
         class="lp.bugs.browser.bugtask.BugNominationsView"
         permission="zope.Public"
         template="../templates/series-bug-nominations.pt"/>
-    <browser:page
-        name="+securitycontact"
-        for="lp.bugs.interfaces.securitycontact.IHasSecurityContact"
-        class="lp.bugs.browser.securitycontact.SecurityContactEditView"
-        permission="launchpad.Edit"
-        template="../templates/securitycontact-edit.pt"/>
     <adapter
         for="lp.registry.interfaces.product.IProduct"
         provides=".bugtarget.IProductBugConfiguration"

=== removed file 'lib/lp/bugs/browser/securitycontact.py'
--- lib/lp/bugs/browser/securitycontact.py	2012-01-01 02:58:52 +0000
+++ lib/lp/bugs/browser/securitycontact.py	1970-01-01 00:00:00 +0000
@@ -1,68 +0,0 @@
-# Copyright 2009-2010 Canonical Ltd.  This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-"""Browser view classes for security contacts."""
-
-__metaclass__ = type
-
-__all__ = [
-    "SecurityContactEditView",
-    ]
-
-from lp.app.browser.launchpadform import (
-    action,
-    LaunchpadFormView,
-    )
-from lp.bugs.browser.bugrole import BugRoleMixin
-from lp.bugs.interfaces.securitycontact import IHasSecurityContact
-from lp.services.webapp.publisher import canonical_url
-
-
-class SecurityContactEditView(BugRoleMixin, LaunchpadFormView):
-    """Browser view for editing the security contact.
-
-    self.context is assumed to implement IHasSecurityContact.
-    """
-
-    schema = IHasSecurityContact
-    field_names = ['security_contact']
-
-    @property
-    def label(self):
-        """See `LaunchpadFormView`."""
-        return 'Edit %s security contact' % self.context.displayname
-
-    @property
-    def page_title(self):
-        """The page title."""
-        return self.label
-
-    @property
-    def initial_values(self):
-        return {
-            'security_contact': self.context.security_contact}
-
-    def validate(self, data):
-        """See `LaunchpadFormView`."""
-        self.validateSecurityContact(data)
-
-    @action('Change', name='change')
-    def change_action(self, action, data):
-        security_contact = data['security_contact']
-        if self.context.security_contact == security_contact:
-            return
-
-        self.context.security_contact = security_contact
-        if security_contact:
-            self.request.response.addNotification(
-                "Successfully changed the security contact to %s." %
-                security_contact.displayname)
-        else:
-            self.request.response.addNotification(
-                "Successfully removed the security contact.")
-
-    @property
-    def next_url(self):
-        return canonical_url(self.context)
-
-    cancel_url = next_url

=== modified file 'lib/lp/bugs/browser/tests/test_bugtarget_configure.py'
--- lib/lp/bugs/browser/tests/test_bugtarget_configure.py	2012-08-16 05:18:54 +0000
+++ lib/lp/bugs/browser/tests/test_bugtarget_configure.py	2012-08-22 01:27:20 +0000
@@ -32,7 +32,6 @@
     def _makeForm(self):
         return {
             'field.bug_supervisor': 'boing-owner',
-            'field.security_contact': 'boing-owner',
             'field.bugtracker': 'malone',
             'field.enable_bug_expiration': 'on',
             'field.remote_product': 'sf-boing',
@@ -52,7 +51,7 @@
             'bugtracker', 'enable_bug_expiration', 'remote_product',
             'bug_reporting_guidelines', 'bug_reported_acknowledgement',
             'enable_bugfiling_duplicate_search', 'private_bugs',
-            'bug_supervisor', 'security_contact']
+            'bug_supervisor']
         self.assertEqual(fields, view.field_names)
         self.assertEqual('http://launchpad.dev/boing', view.next_url)
         self.assertEqual('http://launchpad.dev/boing', view.cancel_url)
@@ -73,14 +72,13 @@
 
     def test_all_data_change(self):
         # Verify that the composed interface supports all fields.
-        # This is a sanity check. The bug_supervisor, security_contact and
-        # bugtracker field are rigorously tested in their respective tests.
+        # This is a sanity check. The bug_supervisor and bugtracker field
+        # are rigorously tested in their respective tests.
         form = self._makeForm()
         view = create_initialized_view(
             self.product, name='+configure-bugtracker', form=form)
         self.assertEqual([], view.errors)
         self.assertEqual(self.owner, self.product.bug_supervisor)
-        self.assertEqual(self.owner, self.product.security_contact)
         self.assertEqual(
             ServiceUsage.LAUNCHPAD,
             self.product.bug_tracking_usage)
@@ -92,17 +90,6 @@
             self.product.bug_reported_acknowledgement)
         self.assertFalse(self.product.enable_bugfiling_duplicate_search)
 
-    def test_security_contact_invalid(self):
-        # Verify that invalid security_contact states are reported.
-        # This is a sanity check. The security_contact is rigorously tested
-        # in its own test.
-        other_person = self.factory.makePerson()
-        form = self._makeForm()
-        form['field.security_contact'] = other_person.name
-        view = create_initialized_view(
-            self.product, name='+configure-bugtracker', form=form)
-        self.assertEqual(1, len(view.errors))
-
     def test_enable_bug_expiration_with_launchpad(self):
         # Verify that enable_bug_expiration can be True bugs are tracked
         # in Launchpad.
@@ -139,8 +126,7 @@
 
     def test_bug_role_non_admin_can_edit(self):
         # Verify that a member of an owning team who is not an admin of
-        # the bug supervisor team or security_contact team can change bug
-        # reporting guidelines.
+        # the bug supervisor team can change bug reporting guidelines.
         owning_team = self.factory.makeTeam(
             owner=self.owner,
             membership_policy=TeamMembershipPolicy.RESTRICTED)
@@ -153,12 +139,10 @@
         bug_team.addMember(weak_owner, self.owner)
         self.product.owner = owning_team
         self.product.bug_supervisor = bug_team
-        self.product.security_contact = bug_team
         login_person(weak_owner)
         form = self._makeForm()
         # Only the bug_reporting_guidelines are different.
         form['field.bug_supervisor'] = bug_team.name
-        form['field.security_contact'] = bug_team.name
         form['field.bug_reporting_guidelines'] = 'new guidelines'
         view = create_initialized_view(
             self.product, name='+configure-bugtracker', form=form)

=== removed file 'lib/lp/bugs/browser/tests/test_securitycontact.py'
--- lib/lp/bugs/browser/tests/test_securitycontact.py	2012-08-13 19:34:10 +0000
+++ lib/lp/bugs/browser/tests/test_securitycontact.py	1970-01-01 00:00:00 +0000
@@ -1,158 +0,0 @@
-# Copyright 2010 Canonical Ltd.  This software is licensed under the
-# GNU Affero General Public License version (see the file LICENSE).
-
-"""Unit tests for security contact views."""
-
-__metaclass__ = type
-
-from zope.app.form.interfaces import ConversionError
-
-from lp.registry.interfaces.person import TeamMembershipPolicy
-from lp.testing import (
-    login,
-    login_person,
-    TestCaseWithFactory,
-    )
-from lp.testing.layers import DatabaseFunctionalLayer
-from lp.testing.views import create_initialized_view
-
-
-class TestSecurityContactEditView(TestCaseWithFactory):
-
-    layer = DatabaseFunctionalLayer
-
-    def setUp(self):
-        super(TestSecurityContactEditView, self).setUp()
-        self.owner = self.factory.makePerson(
-            name='splat', displayname='<splat />')
-        self.product = self.factory.makeProduct(
-            name="boing", displayname='<boing />', owner=self.owner)
-        self.team = self.factory.makeTeam(
-            name='thud', owner=self.owner,
-            membership_policy=TeamMembershipPolicy.RESTRICTED)
-        login_person(self.owner)
-
-    def _makeForm(self, person):
-        if person is None:
-            name = ''
-        else:
-            name = person.name
-        return {
-            'field.security_contact': name,
-            'field.actions.change': 'Change',
-            }
-
-    def test_view_attributes(self):
-        self.product.displayname = 'Boing'
-        view = create_initialized_view(
-            self.product, name='+securitycontact')
-        label = 'Edit Boing security contact'
-        self.assertEqual(label, view.label)
-        self.assertEqual(label, view.page_title)
-        fields = ['security_contact']
-        self.assertEqual(fields, view.field_names)
-        self.assertEqual('http://launchpad.dev/boing', view.next_url)
-        self.assertEqual('http://launchpad.dev/boing', view.cancel_url)
-
-    def test_owner_appoint_self_from_none(self):
-        # This also verifies that displaynames are escaped.
-        form = self._makeForm(self.owner)
-        view = create_initialized_view(
-            self.product, name='+securitycontact', form=form)
-        self.assertEqual([], view.errors)
-        self.assertEqual(self.product.security_contact, self.owner)
-        notifications = view.request.response.notifications
-        self.assertEqual(1, len(notifications))
-        expected = (
-            'Successfully changed the security contact to &lt;splat /&gt;.')
-        self.assertEqual(expected, notifications.pop().message)
-
-    def test_owner_appoint_self_from_another(self):
-        self.product.security_contact = self.team
-        form = self._makeForm(self.owner)
-        view = create_initialized_view(
-            self.product, name='+securitycontact', form=form)
-        self.assertEqual([], view.errors)
-        self.assertEqual(self.owner, self.product.security_contact)
-
-    def test_owner_appoint_none(self):
-        self.product.security_contact = self.owner
-        form = self._makeForm(None)
-        view = create_initialized_view(
-            self.product, name='+securitycontact', form=form)
-        self.assertEqual([], view.errors)
-        self.assertEqual(self.product.security_contact, None)
-        notifications = view.request.response.notifications
-        self.assertEqual(1, len(notifications))
-        expected = ('Successfully removed the security contact.')
-        self.assertEqual(expected, notifications.pop().message)
-
-    def test_owner_appoint_his_team(self):
-        form = self._makeForm(self.team)
-        view = create_initialized_view(
-            self.product, name='+securitycontact', form=form)
-        self.assertEqual([], view.errors)
-        self.assertEqual(self.product.security_contact, self.team)
-        notifications = view.request.response.notifications
-        self.assertEqual(1, len(notifications))
-        expected = ('Successfully changed the security contact to Thud.')
-        self.assertEqual(expected, notifications.pop().message)
-
-    def test_owner_cannot_appoint_another_team(self):
-        team = self.factory.makeTeam(
-            name='smack', displayname='<smack />',
-            membership_policy=TeamMembershipPolicy.RESTRICTED)
-        form = self._makeForm(team)
-        view = create_initialized_view(
-            self.product, name='+securitycontact', form=form)
-        self.assertEqual(1, len(view.errors))
-        expected = (
-            'You cannot set &lt;smack /&gt; as the security contact for '
-            '&lt;boing /&gt; because you are not an administrator of that '
-            'team.<br />If you believe that &lt;smack /&gt; should be the '
-            'security contact for &lt;boing /&gt;, notify one of the '
-            '<a href="http://launchpad.dev/~smack/+members";>&lt;smack /&gt; '
-            'administrators</a>.')
-        self.assertEqual(expected, view.errors.pop())
-
-    def test_owner_cannot_appoint_a_nonvalid_user(self):
-        form = self._makeForm(None)
-        form['field.security_contact'] = 'fnord'
-        view = create_initialized_view(
-            self.product, name='+securitycontact', form=form)
-        self.assertEqual(2, len(view.errors))
-        expected = (
-            'You must choose a valid person or team to be the '
-            'security contact for &lt;boing /&gt;.')
-        self.assertEqual(expected, view.errors.pop())
-        self.assertTrue(isinstance(view.errors.pop(), ConversionError))
-
-    def test_owner_cannot_appoint_another_user(self):
-        another_user = self.factory.makePerson()
-        form = self._makeForm(another_user)
-        view = create_initialized_view(
-            self.product, name='+securitycontact', form=form)
-        self.assertEqual(1, len(view.errors))
-        expected = (
-            'You cannot set another person as the security contact for '
-            '&lt;boing /&gt;.')
-        self.assertEqual(expected, view.errors.pop())
-
-    def test_admin_appoint_another_user(self):
-        another_user = self.factory.makePerson()
-        login('admin@xxxxxxxxxxxxx')
-        form = self._makeForm(another_user)
-        view = create_initialized_view(
-            self.product, name='+securitycontact', form=form)
-        self.assertEqual([], view.errors)
-        self.assertEqual(another_user, self.product.security_contact)
-
-    def test_admin_appoint_another_team(self):
-        another_team = self.factory.makeTeam(
-            membership_policy=TeamMembershipPolicy.RESTRICTED)
-        login('admin@xxxxxxxxxxxxx')
-        form = self._makeForm(another_team)
-        view = create_initialized_view(
-            self.product, name='+securitycontact', form=form)
-        self.assertEqual([], view.errors)
-        self.assertEqual(another_team, self.product.security_contact)

=== modified file 'lib/lp/bugs/doc/bug-private-by-default.txt'
--- lib/lp/bugs/doc/bug-private-by-default.txt	2012-07-17 06:34:59 +0000
+++ lib/lp/bugs/doc/bug-private-by-default.txt	2012-08-22 01:27:20 +0000
@@ -17,8 +17,6 @@
     >>> landscape.private_bugs
     True
 
-    >>> login('foo.bar@xxxxxxxxxxxxx')
-    >>> landscape.security_contact = name16
     >>> login('no-priv@xxxxxxxxxxxxx')
 
     >>> bug_params = CreateBugParams(
@@ -59,9 +57,6 @@
     >>> security_bug_task.bug.security_related
     True
 
-    >>> landscape.security_contact.name
-    u'name16'
-
     >>> sorted(
     ...     sub.name for sub in security_bug_task.bug.getDirectSubscribers())
-    [u'name16', u'no-priv']
+    [u'name12', u'no-priv']

=== modified file 'lib/lp/bugs/doc/bugsubscription.txt'
--- lib/lp/bugs/doc/bugsubscription.txt	2012-08-16 12:13:30 +0000
+++ lib/lp/bugs/doc/bugsubscription.txt	2012-08-22 01:27:20 +0000
@@ -625,11 +625,7 @@
     >>> getSubscribers(new_bug)
     ['foo.bar@xxxxxxxxxxxxx']
 
-The distro contact will also be subscribed to private bugs, because
-there is no security contact:
-
-    >>> ubuntu.security_contact is None
-    True
+The distro contact will also be subscribed to private bugs.
 
     >>> from lp.services.mail import stub
     >>> transaction.commit()
@@ -711,10 +707,7 @@
     foo.bar@xxxxxxxxxxxxx
 
 The upstream maintainer will be subscribed to security-related private
-bugs, because upstream has no security contact, in this case.
-
-    >>> firefox.security_contact is None
-    True
+bugs.
 
     >>> params = CreateBugParams(
     ...     title="a test bug", comment="a test description",
@@ -777,10 +770,7 @@
     ['foo.bar@xxxxxxxxxxxxx', 'support@xxxxxxxxxx']
 
 The distribution maintainer, Ubuntu Team, gets subscribed to the private
-security bug filed on a package, because Ubuntu has no security contact:
-
-    >>> ubuntu.security_contact is None
-    True
+security bug filed on a package.
 
     >>> params = CreateBugParams(
     ...     title="yet another test bug",

=== removed file 'lib/lp/bugs/doc/security-teams.txt'
--- lib/lp/bugs/doc/security-teams.txt	2012-08-08 07:23:58 +0000
+++ lib/lp/bugs/doc/security-teams.txt	1970-01-01 00:00:00 +0000
@@ -1,309 +0,0 @@
-Security Teams
-==============
-
-Responsibility for security-related bugs, are modelled in Launchpad with
-a "security contact" on a Distribution or a Product.
-
-    >>> from itertools import chain
-    >>> from zope.component import getUtility
-    >>> from lp.bugs.interfaces.securitycontact import IHasSecurityContact
-    >>> from lp.registry.interfaces.distribution import IDistributionSet
-    >>> from lp.registry.interfaces.person import IPersonSet
-    >>> from lp.registry.interfaces.product import IProductSet
-
-    >>> personset = getUtility(IPersonSet)
-    >>> productset = getUtility(IProductSet)
-    >>> ubuntu = getUtility(IDistributionSet).get(1)
-    >>> firefox = productset.get(4)
-
-    >>> IHasSecurityContact.providedBy(ubuntu)
-    True
-    >>> IHasSecurityContact.providedBy(firefox)
-    True
-
-    >>> mark = personset.get(1)
-    >>> ubuntu_team = personset.get(17)
-
-Security contacts are set through properties.
-
-    >>> login("foo.bar@xxxxxxxxxxxxx")
-
-    >>> ubuntu.security_contact = mark
-    >>> firefox.security_contact = ubuntu_team
-
-    >>> print ubuntu.security_contact.name
-    mark
-
-    >>> print firefox.security_contact.name
-    ubuntu-team
-
-When creating a bug, use the information_type enum to indicate that the
-bug is a security vulnerability, and the security contact should be
-subscribed to the bug, even when it's marked private.
-
-    >>> from lp.services.webapp.interfaces import ILaunchBag
-    >>> from lp.bugs.interfaces.bug import CreateBugParams
-    >>> from lp.registry.enums import InformationType
-
-    >>> ubuntu_firefox = ubuntu.getSourcePackage("mozilla-firefox")
-    >>> params = CreateBugParams(
-    ...     owner=getUtility(ILaunchBag).user,
-    ...     title="a security bug",
-    ...     comment="this is an example security bug",
-    ...     information_type=InformationType.PRIVATESECURITY)
-    >>> bug = ubuntu.createBug(params)
-
-    >>> bug.security_related
-    True
-    >>> bug.private
-    True
-
-The reporter, Foo Bar, and the Ubuntu security contact, Mark
-Shuttleworth are both subscribed to the bug.
-
-    >>> def subscriber_names(bug):
-    ...     subscribers = chain(
-    ...         bug.getDirectSubscribers(),
-    ...         bug.getIndirectSubscribers())
-    ...     return sorted(subscriber.name for subscriber in subscribers)
-
-    >>> subscriber_names(bug)
-    [u'mark', u'name16']
-
-If the bug were not reported as security-related, only Foo Bar would
-have been subscribed:
-
-    >>> from lp.services.webapp.interfaces import ILaunchBag
-
-    >>> ubuntu_firefox = ubuntu.getSourcePackage("mozilla-firefox")
-    >>> params = CreateBugParams(
-    ...     owner=getUtility(ILaunchBag).user,
-    ...     title="a security bug",
-    ...     comment="this is an example security bug",
-    ...     information_type=InformationType.PUBLIC)
-    >>> bug = ubuntu.createBug(params)
-
-    >>> bug.security_related
-    False
-
-    >>> subscriber_names(bug)
-    [u'name16']
-
-Likewise, filing a security-related bug on Firefox will subscribe the
-security contact, the Ubuntu team, to the bug.
-
-    >>> params = CreateBugParams(
-    ...     owner=getUtility(ILaunchBag).user,
-    ...     title="another security bug",
-    ...     comment="this is another security bug",
-    ...     information_type=InformationType.PRIVATESECURITY)
-    >>> bug = firefox.createBug(params)
-
-    >>> bug.security_related
-    True
-    >>> bug.private
-    True
-
-    >>> subscriber_names(bug)
-    [u'name16', u'ubuntu-team']
-
-Again, if the bug were not reported as security-related, the security
-contact, the Ubuntu Team, would not have been subscribed:
-
-    >>> params = CreateBugParams(
-    ...     owner=getUtility(ILaunchBag).user,
-    ...     title="another security bug",
-    ...     comment="this is another security bug",
-    ...     information_type=InformationType.PUBLIC)
-    >>> bug = firefox.createBug(params)
-
-    >>> bug.security_related
-    False
-
-    >>> subscriber_names(bug)
-    [u'name16']
-
-When no security contact exists, only the reporter and product
-registrant get subscribed.
-
-    >>> firefox.security_contact = None
-
-    >>> print firefox.owner.name
-    name12
-
-    >>> params = CreateBugParams(
-    ...     owner=getUtility(ILaunchBag).user,
-    ...     title="another security bug",
-    ...     comment="this is another security bug",
-    ...     information_type=InformationType.PRIVATESECURITY)
-
-    >>> bug = firefox.createBug(params)
-
-    >>> bug.security_related
-    True
-
-    >>> subscriber_names(bug)
-    [u'name12', u'name16']
-
-When a bug is reported in another package or upstream, the security
-contact for that package or upstream is automatically subscribed to the
-bug, *if the bug is public*. Malone never auto-subscribes anyone to
-private bugs, except when the user chooses that option when filing a
-security bug.
-
-Let's first demonstrate adding a task to a public bug causing the
-security contact of the new product to be subscribed.
-
-    >>> evolution = productset.get(5)
-
-We'll set lifeless as the security_contact for evolution.
-
-    >>> from lp.bugs.interfaces.bugtask import IBugTaskSet
-
-(Make the bug public to ensure the security contact will get
-subscribed.)
-
-    >>> bug.setPrivate(False, getUtility(ILaunchBag).user)
-    True
-
-    >>> lifeless = personset.get(2)
-    >>> print lifeless.name
-    lifeless
-    >>> evolution.security_contact = lifeless
-
-    >>> foobar = personset.get(16)
-    >>> print foobar.name
-    name16
-
-    >>> bugtaskset = getUtility(IBugTaskSet)
-    >>> bug_in_evolution = bugtaskset.createTask(bug, foobar, evolution)
-
-lifeless is subscribed to the public security bug when a task is added
-for evolution.
-
-    >>> subscriber_names(bug)
-    [u'lifeless', u'name12', u'name16']
-
-But if we repeat the process, using a private bug, he won't be
-subscribed.
-
-    >>> params = CreateBugParams(
-    ...     owner=getUtility(ILaunchBag).user,
-    ...     title="another security bug",
-    ...     comment="this is private security bug",
-    ...     information_type=InformationType.PRIVATESECURITY)
-    >>> bug = firefox.createBug(params)
-
-    >>> bug.security_related
-    True
-
-    >>> bug.private
-    True
-
-    >>> subscriber_names(bug)
-    [u'name12', u'name16']
-
-    >>> bug_in_evolution = bugtaskset.createTask(bug, foobar, evolution)
-    >>> subscriber_names(bug)
-    [u'name12', u'name16']
-
-Finally, reassigning a public bug to a different product will subscribe
-the new security contact, if present and if the original bug was marked
-as a security issue. Let's set stub to the security contact for
-thunderbird to see how this works.
-
-    >>> thunderbird = productset.get(8)
-    >>> print thunderbird.name
-    thunderbird
-
-    >>> stub = personset.get(22)
-    >>> print stub.name
-    stub
-
-    >>> thunderbird.security_contact = stub
-
-    >>> from zope.event import notify
-    >>> from lazr.lifecycle.event import ObjectModifiedEvent
-    >>> from lazr.lifecycle.snapshot import Snapshot
-    >>> from lp.bugs.interfaces.bugtask import IBugTask
-
-    >>> old_state = Snapshot(bug_in_evolution, providing=IBugTask)
-    >>> bug_in_evolution.transitionToTarget(thunderbird, stub)
-    >>> bug_product_changed = ObjectModifiedEvent(
-    ...     bug_in_evolution, old_state, ["product"])
-
-First, let's set the bug to non security related with the bug still marked,
-private and notice that the subscription list doesn't change:
-
-    >>> bug.private
-    True
-
-    >>> bug.setSecurityRelated(False, getUtility(ILaunchBag).user)
-    True
-
-    >>> subscriber_names(bug)
-    [u'name12', u'name16']
-
-Now the bug is marked as security related, when also marked public does cause
-stub to get subscribed:
-
-    >>> bug.setPrivate(False, getUtility(ILaunchBag).user)
-    True
-
-    >>> bug.setSecurityRelated(True, getUtility(ILaunchBag).user)
-    True
-
-    >>> bug.security_related
-    True
-
-    >>> subscriber_names(bug)
-    [u'name12', u'name16', u'stub']
-
-But if it is not a security issue originally, stub does not get
-subscribed when moving it to the new project.
-
-    >>> bug.unsubscribe(stub, stub)
-
-    >>> subscriber_names(bug)
-    [u'name12', u'name16']
-
-    >>> bug.setSecurityRelated(False, getUtility(ILaunchBag).user)
-    True
-
-    >>> bug.security_related
-    False
-
-    >>> notify(bug_product_changed)
-
-    >>> subscriber_names(bug)
-    [u'name12', u'name16']
-
-
-When a bug becomes security-related, the security contacts for the pillars it
-affects are subscribed to it.
-
-    >>> from zope.event import notify
-    >>> from lazr.lifecycle.event import ObjectModifiedEvent
-    >>> from lazr.lifecycle.snapshot import Snapshot
-    >>> from lp.bugs.interfaces.bug import IBug
-
-    >>> product = factory.makeProduct()
-    >>> product.security_contact = factory.makePerson(
-    ...     displayname='Product Security Contact')
-    >>> distribution = factory.makeDistribution()
-    >>> distribution.security_contact = factory.makePerson(
-    ...     displayname='Distribution Security Contact')
-    >>> reporter = factory.makePerson(displayname=u'Bug Reporter')
-    >>> bug = factory.makeBug(target=product, owner=reporter)
-    >>> bug.addTask(owner=reporter, target=distribution)
-    <BugTask ...>
-    >>> old_state = Snapshot(bug, providing=IBug)
-    >>> bug.setSecurityRelated(True, getUtility(ILaunchBag).user)
-    True
-    >>> notify(ObjectModifiedEvent(bug, old_state, ['security_related']))
-    >>> for subscriber_name in sorted(
-    ...     s.displayname for s in bug.getDirectSubscribers()):
-    ...         print subscriber_name
-    Bug Reporter
-    Distribution Security Contact
-    Product Security Contact

=== modified file 'lib/lp/bugs/interfaces/personsubscriptioninfo.py'
--- lib/lp/bugs/interfaces/personsubscriptioninfo.py	2011-12-24 16:54:44 +0000
+++ lib/lp/bugs/interfaces/personsubscriptioninfo.py	2012-08-22 01:27:20 +0000
@@ -52,11 +52,6 @@
        description=_("Is the principal the bug reporter."),
        default=False, readonly=True)
 
-    security_contact_tasks = Attribute(
-        """A collection of targets of the info's bug for which the
-        principal is a security contact (which causes direct subscriptions for
-        security related bugs at this time).""")
-
     bug_supervisor_tasks = Attribute(
         """A collection of targets of the info's bug for which the
         principal is a bug supervisor (which causes direct subscriptions for

=== removed file 'lib/lp/bugs/interfaces/securitycontact.py'
--- lib/lp/bugs/interfaces/securitycontact.py	2011-12-24 16:54:44 +0000
+++ lib/lp/bugs/interfaces/securitycontact.py	1970-01-01 00:00:00 +0000
@@ -1,32 +0,0 @@
-# Copyright 2010 Canonical Ltd.  This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-# pylint: disable-msg=E0211,E0213
-
-"""Security contact interfaces."""
-
-__metaclass__ = type
-
-__all__ = [
-    'IHasSecurityContact',
-    ]
-
-from lazr.restful.declarations import exported
-from zope.interface import Interface
-
-from lp import _
-from lp.services.fields import PublicPersonChoice
-
-
-class IHasSecurityContact(Interface):
-    """An object that has a security contact."""
-
-    security_contact = exported(PublicPersonChoice(
-        title=_("Security Contact"),
-        description=_(
-            "The Launchpad id of the person or restricted or moderated "
-            "team (preferred) who handles security-related bug reports. "
-            "The security contact will be subscribed to all bugs marked as "
-            "a security vulnerability and will receive email about all "
-            "activity on all security bugs."),
-        required=False, vocabulary='ValidPillarOwner'))

=== modified file 'lib/lp/bugs/javascript/subscription.js'
--- lib/lp/bugs/javascript/subscription.js	2011-09-21 06:38:51 +0000
+++ lib/lp/bugs/javascript/subscription.js	2012-08-22 01:27:20 +0000
@@ -1016,14 +1016,12 @@
                         // everything on the fly. Luckily, we don't need
                         // most of it, and we think it is alright to not
                         // include the extra information about
-                        // principal_is_reporter, security_contact_pillars,
-                        // and bug_supervisor_pillars.
+                        // principal_is_reporter and bug_supervisor_pillars.
                         info.direct.personal.push(
                             {principal: {},
                             bug: {},
                             subscription: sub,
                             principal_is_reporter: false,
-                            security_contact_pillars: [],
                             bug_supervisor_pillars: []
                            });
                        info.direct.count += 1;

=== modified file 'lib/lp/bugs/javascript/tests/test_subscription.js'
--- lib/lp/bugs/javascript/tests/test_subscription.js	2012-07-31 06:14:21 +0000
+++ lib/lp/bugs/javascript/tests/test_subscription.js	2012-08-22 01:27:20 +0000
@@ -1208,33 +1208,6 @@
                 direct_info.increases);
         },
 
-        test_direct_subscription_for_security_contact: function() {
-            // The simple direct subscription.
-            var sub = {
-                bug: {
-                    security_related: true
-                },
-                subscription: {bug_notification_level: 'Discussion'}
-            };
-            var info = {
-                direct: _constructCategory([sub]),
-                count: 1
-            };
-            var direct_info = module._get_direct_subscription_information(info);
-            Y.Assert.areSame(
-                module._reasons.YOU_SUBSCRIBED_SECURITY_CONTACT,
-                direct_info.reason);
-            Y.ArrayAssert.itemsAreEqual(
-                ['mute-direct-subscription',
-                 'select-only-direct-subscription-metadata',
-                 'select-only-direct-subscription-lifecycle',
-                 'remove-direct-subscription'],
-                direct_info.reductions);
-            Y.ArrayAssert.itemsAreEqual(
-                [],
-                direct_info.increases);
-        },
-
         test_direct_subscription_and_other_subscriptions: function() {
             // Other subscriptions are present along with the simple direct
             // subscription.

=== modified file 'lib/lp/bugs/model/bug.py'
--- lib/lp/bugs/model/bug.py	2012-08-15 02:30:46 +0000
+++ lib/lp/bugs/model/bug.py	2012-08-22 01:27:20 +0000
@@ -1,8 +1,6 @@
 # Copyright 2009-2012 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-# pylint: disable-msg=E0611,W0212
-
 """Launchpad bug-related database table classes."""
 
 __metaclass__ = type
@@ -1744,9 +1742,9 @@
 
         # We have to capture subscribers that must exist after transition. In
         # the case of a transition to USERDATA, we want the bug supervisor or
-        # maintainer. For SECURITY types, we want the security contact or
-        # maintainer. In either case, if the driver is already subscribed,
-        # then the driver is also required.
+        # maintainer. For SECURITY types, we want the maintainer. In either
+        # case, if the driver is already subscribed, then the driver is also
+        # required.
         # Ubuntu is special: we don't want to add required subscribers in that
         # case.
         if information_type == InformationType.USERDATA:
@@ -1763,10 +1761,7 @@
             for pillar in pillars:
                 if pillar.driver in subscribers:
                     required_subscribers.add(pillar.driver)
-                if pillar.security_contact is not None:
-                    required_subscribers.add(pillar.security_contact)
-                else:
-                    required_subscribers.add(pillar.owner)
+                required_subscribers.add(pillar.owner)
 
         # If we've made the bug private, we need to do some cleanup.
         # Required subscribers must be given access.
@@ -2655,10 +2650,7 @@
 
         if params.information_type in SECURITY_INFORMATION_TYPES:
             pillar = params.target.pillar
-            if pillar.security_contact:
-                bug.subscribe(pillar.security_contact, params.owner)
-            else:
-                bug.subscribe(pillar.owner, params.owner)
+            bug.subscribe(pillar.owner, params.owner)
         # XXX: ElliotMurphy 2007-06-14: If we ever allow filing private
         # non-security bugs, this test might be simplified to checking
         # params.private.

=== modified file 'lib/lp/bugs/model/bugtask.py'
--- lib/lp/bugs/model/bugtask.py	2012-08-07 03:44:05 +0000
+++ lib/lp/bugs/model/bugtask.py	2012-08-22 01:27:20 +0000
@@ -1,8 +1,6 @@
 # Copyright 2009-2012 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-# pylint: disable-msg=E0611,W0212
-
 """Classes that implement IBugTask and its related interfaces."""
 
 __metaclass__ = type
@@ -1586,10 +1584,6 @@
             validate_new_target(bug, target)
             pillars.add(target.pillar)
             target_keys.append(bug_target_to_key(target))
-        if bug.information_type == InformationType.PUBLICSECURITY:
-            for pillar in pillars:
-                if pillar.security_contact:
-                    bug.subscribe(pillar.security_contact, owner)
 
         values = [
             (bug, owner, key['product'], key['productseries'],

=== modified file 'lib/lp/bugs/model/personsubscriptioninfo.py'
--- lib/lp/bugs/model/personsubscriptioninfo.py	2011-12-19 23:38:16 +0000
+++ lib/lp/bugs/model/personsubscriptioninfo.py	2012-08-22 01:27:20 +0000
@@ -42,7 +42,6 @@
         self.bug = bug
         self.subscription = subscription
         self.principal_is_reporter = False
-        self.security_contact_tasks = []
         self.bug_supervisor_tasks = []
 
 
@@ -137,18 +136,14 @@
             for info in infos:
                 info.principal_is_reporter = True
 
-    def annotateBugTaskResponsibilities(
-        self, bugtask, pillar, security_contact, bug_supervisor):
-        for principal, collection_name in (
-            (security_contact, 'security_contact_tasks'),
-            (bug_supervisor, 'bug_supervisor_tasks')):
-            if principal is not None:
-                key = (principal, bugtask.bug)
-                infos = self._principal_bug_to_infos.get(key)
-                if infos is not None:
-                    value = {'task': bugtask, 'pillar': pillar}
-                    for info in infos:
-                        getattr(info, collection_name).append(value)
+    def annotateBugTaskResponsibilities(self, bugtask, pillar, bug_supervisor):
+        if bug_supervisor is not None:
+            key = (bug_supervisor, bugtask.bug)
+            infos = self._principal_bug_to_infos.get(key)
+            if infos is not None:
+                value = {'task': bugtask, 'pillar': pillar}
+                for info in infos:
+                    getattr(info, 'bug_supervisor_tasks').append(value)
 
 
 class PersonSubscriptions(object):
@@ -208,18 +203,16 @@
             collection.add(
                 subscriber, subscribed_bug, subscription)
         for bug in bugs:
-            # indicate the reporter, bug_supervisor, and security_contact
+            # indicate the reporter and bug_supervisor
             duplicates.annotateReporter(bug, bug.owner)
             direct.annotateReporter(bug, bug.owner)
             for task in bug.bugtasks:
-                # get security_contact and bug_supervisor
+                # Get bug_supervisor.
                 pillar = self._getTaskPillar(task)
                 duplicates.annotateBugTaskResponsibilities(
-                    task, pillar,
-                    pillar.security_contact, pillar.bug_supervisor)
+                    task, pillar, pillar.bug_supervisor)
                 direct.annotateBugTaskResponsibilities(
-                    task, pillar,
-                    pillar.security_contact, pillar.bug_supervisor)
+                    task, pillar, pillar.bug_supervisor)
         return (direct, duplicates)
 
     def _isMuted(self, person, bug):
@@ -297,9 +290,6 @@
                 'subscription': get_id(info.subscription),
                 'principal_is_reporter': info.principal_is_reporter,
                 # We won't add bugtasks yet unless we need them.
-                'security_contact_pillars': sorted(set(
-                    get_id(d['pillar']) for d
-                    in info.security_contact_tasks)),
                 'bug_supervisor_pillars': sorted(set(
                     get_id(d['pillar']) for d
                     in info.bug_supervisor_tasks)),

=== modified file 'lib/lp/bugs/model/tests/test_bug.py'
--- lib/lp/bugs/model/tests/test_bug.py	2012-08-16 05:18:54 +0000
+++ lib/lp/bugs/model/tests/test_bug.py	2012-08-22 01:27:20 +0000
@@ -603,11 +603,9 @@
             name='bugsupervisor', email='bugsupervisor@xxxxxxxxxxx')
         product_owner = self.factory.makePerson(name='productowner')
         product_driver = self.factory.makePerson(name='productdriver')
-        security_contact = self.factory.makePerson(
-            name='securitycontact', email='securitycontact@xxxxxxxxxxx')
         bug_product = self.factory.makeProduct(
             owner=product_owner, bug_supervisor=bug_supervisor,
-            driver=product_driver, security_contact=security_contact)
+            driver=product_driver)
         if self.private_project:
             removeSecurityProxy(bug_product).private_bugs = True
         bug = self.factory.makeBug(owner=bug_owner, target=bug_product)
@@ -635,7 +633,6 @@
         # When a bug is marked as PRIVATESECURITY, the direct subscribers
         # should include:
         # - the bug reporter
-        # - the bugtask pillar security contacts (if set)
         # - the person changing the state
         # - and bug/pillar owners, drivers if they are already subscribed
 
@@ -643,8 +640,7 @@
             self.createBugTasksAndSubscribers())
         initial_subscribers = set((
             self.factory.makePerson(name='subscriber'), bugtask_a.owner,
-            bug_owner, bugtask_a.pillar.security_contact,
-            bugtask_a.pillar.driver))
+            bug_owner, bugtask_a.pillar.driver))
         initial_subscribers.update(bug.getDirectSubscribers())
 
         with person_logged_in(bug_owner):
@@ -655,17 +651,13 @@
                 InformationType.PRIVATESECURITY, who=who)
             subscribers = bug.getDirectSubscribers()
         expected_subscribers = set((
-            default_bugtask.pillar.driver,
-            default_bugtask.pillar.security_contact,
-            bug_owner,
-            who))
+            default_bugtask.pillar.driver, bug_owner, who))
         self.assertContentEqual(expected_subscribers, subscribers)
 
     def test_transition_to_USERDATA_information_type(self):
         # When a bug is marked as USERDATA, the direct subscribers should
         # include:
         # - the bug reporter
-        # - the bugtask pillar bug supervisors (if set)
         # - the person changing the state
         # - and bug/pillar owners, drivers if they are already subscribed
 
@@ -673,7 +665,7 @@
                 self.createBugTasksAndSubscribers())
         initial_subscribers = set((
             self.factory.makePerson(name='subscriber'), bug_owner,
-            bugtask_a.pillar.security_contact, bugtask_a.pillar.driver))
+            bugtask_a.pillar.driver))
 
         with person_logged_in(bug_owner):
             for subscriber in initial_subscribers:
@@ -692,14 +684,12 @@
         # When a security bug is unembargoed, direct subscribers should
         # include:
         # - the bug reporter
-        # - the bugtask pillar security contacts (if set)
         # - and bug/pillar owners, drivers if they are already subscribed
 
         (bug, bug_owner, bugtask_a, bugtask_b, default_bugtask) = (
             self.createBugTasksAndSubscribers(private_security_related=True))
         initial_subscribers = set((
-            self.factory.makePerson(), bug_owner,
-            bugtask_a.pillar.security_contact, bugtask_a.pillar.driver,
+            self.factory.makePerson(), bug_owner, bugtask_a.pillar.driver,
             bugtask_a.pillar.bug_supervisor))
 
         with person_logged_in(bug_owner):
@@ -709,10 +699,8 @@
             bug.transitionToInformationType(
                 InformationType.PUBLICSECURITY, who)
             subscribers = bug.getDirectSubscribers()
-        expected_subscribers = set((
-            default_bugtask.pillar.driver,
-            default_bugtask.pillar.security_contact,
-            bug_owner))
+        pillar = default_bugtask.pillar
+        expected_subscribers = set((pillar.owner, pillar.driver, bug_owner))
         expected_subscribers.update(initial_subscribers)
         self.assertContentEqual(expected_subscribers, subscribers)
 
@@ -724,7 +712,7 @@
             self.createBugTasksAndSubscribers(private_security_related=True))
         initial_subscribers = set((
             self.factory.makePerson(name='subscriber'), bug_owner,
-            bugtask_a.pillar.security_contact, bugtask_a.pillar.driver))
+            bugtask_a.pillar.driver))
 
         with person_logged_in(bug_owner):
             for subscriber in initial_subscribers:
@@ -752,10 +740,9 @@
             set((naked_bugtask.pillar.owner, bug_owner, who)),
             subscribers)
 
-    def test_setPillarOwnerSubscribedIfNoSecurityContact(self):
-        # The pillar owner is subscribed if the security contact is not set
-        # and the bug is marked as PRIVATESECURITY.
-
+    def test_setPillarOwnerSubscribed(self):
+        # The pillar owner is subscribed if the bug is marked as
+        # PRIVATESECURITY.
         bug_owner = self.factory.makePerson(name='bugowner')
         bug = self.factory.makeBug(owner=bug_owner)
         with person_logged_in(bug_owner):

=== modified file 'lib/lp/bugs/model/tests/test_personsubscriptioninfo.py'
--- lib/lp/bugs/model/tests/test_personsubscriptioninfo.py	2012-08-13 19:34:10 +0000
+++ lib/lp/bugs/model/tests/test_personsubscriptioninfo.py	2012-08-22 01:27:20 +0000
@@ -91,17 +91,15 @@
         self.assertEqual(info.pillar, pillar)
         self.assertContentEqual(info.tasks, bugtasks)
 
-    def assertRealSubscriptionInfoMatches(
-        self, info, bug, principal,
-        principal_is_reporter, security_contact_tasks, bug_supervisor_tasks):
+    def assertRealSubscriptionInfoMatches(self, info, bug, principal,
+                                          principal_is_reporter,
+                                          bug_supervisor_tasks):
         # Make sure that the real subscription info has expected values.
         self.assertEqual(info.bug, bug)
         self.assertEqual(info.principal, principal)
         self.assertEqual(info.principal_is_reporter, principal_is_reporter)
         self.assertContentEqual(
             info.bug_supervisor_tasks, bug_supervisor_tasks)
-        self.assertContentEqual(
-            info.security_contact_tasks, security_contact_tasks)
 
     def test_no_subscriptions(self):
         # Load a `PersonSubscriptionInfo`s for a subscriber and a bug.
@@ -222,7 +220,7 @@
             self.subscriptions.direct, personal=1)
         self.assertRealSubscriptionInfoMatches(
             self.subscriptions.direct.personal[0],
-            self.bug, self.subscriber, False, [], [])
+            self.bug, self.subscriber, False, [])
 
     def test_direct_getDataForClient(self):
         # Subscribed directly to the bug.
@@ -237,7 +235,6 @@
         self.assertEqual(references[personal['bug']], self.bug)
         self.assertEqual(references[personal['subscription']], subscription)
         self.assertEqual(personal['principal_is_reporter'], False)
-        self.assertEqual(personal['security_contact_pillars'], [])
         self.assertEqual(personal['bug_supervisor_pillars'], [])
 
     def test_direct_through_team(self):
@@ -255,7 +252,7 @@
             self.subscriptions.direct, as_team_member=1)
         self.assertRealSubscriptionInfoMatches(
             self.subscriptions.direct.as_team_member[0],
-            self.bug, team, False, [], [])
+            self.bug, team, False, [])
 
     def test_direct_through_team_getDataForClient(self):
         # Subscribed to the bug through membership in a team.
@@ -286,7 +283,7 @@
             self.subscriptions.direct, as_team_admin=1)
         self.assertRealSubscriptionInfoMatches(
             self.subscriptions.direct.as_team_admin[0],
-            self.bug, team, False, [], [])
+            self.bug, team, False, [])
 
     def test_direct_through_team_as_admin_getDataForClient(self):
         # Subscribed to the bug through membership in a team
@@ -314,7 +311,7 @@
             self.subscriptions.from_duplicate, personal=1)
         self.assertRealSubscriptionInfoMatches(
             self.subscriptions.from_duplicate.personal[0],
-            duplicate, self.subscriber, False, [], [])
+            duplicate, self.subscriber, False, [])
 
     def test_duplicate_direct_reverse(self):
         # Subscribed directly to the primary bug, and a duplicate bug changes.
@@ -349,10 +346,10 @@
             self.subscriptions.from_duplicate, personal=2)
         self.assertRealSubscriptionInfoMatches(
             self.subscriptions.from_duplicate.personal[0],
-            duplicate1, self.subscriber, False, [], [])
+            duplicate1, self.subscriber, False, [])
         self.assertRealSubscriptionInfoMatches(
             self.subscriptions.from_duplicate.personal[1],
-            duplicate2, self.subscriber, False, [], [])
+            duplicate2, self.subscriber, False, [])
 
     def test_duplicate_through_team(self):
         # Subscribed to a duplicate bug through team membership.
@@ -370,7 +367,7 @@
             self.subscriptions.from_duplicate, as_team_member=1)
         self.assertRealSubscriptionInfoMatches(
             self.subscriptions.from_duplicate.as_team_member[0],
-            duplicate, team, False, [], [])
+            duplicate, team, False, [])
 
     def test_duplicate_through_team_as_admin(self):
         # Subscribed to a duplicate bug through team membership
@@ -392,7 +389,7 @@
             self.subscriptions.from_duplicate, as_team_admin=1)
         self.assertRealSubscriptionInfoMatches(
             self.subscriptions.from_duplicate.as_team_admin[0],
-            duplicate, team, False, [], [])
+            duplicate, team, False, [])
 
     def test_subscriber_is_reporter(self):
         self.bug = self.factory.makeBug(owner=self.subscriber)
@@ -405,21 +402,7 @@
         self.subscriptions.reload()
         self.assertRealSubscriptionInfoMatches(
             self.subscriptions.direct.personal[0],
-            self.bug, self.subscriber, True, [], [])
-
-    def test_subscriber_is_security_contact(self):
-        target = self.bug.default_bugtask.target
-        removeSecurityProxy(target).security_contact = self.subscriber
-        # Subscribed directly to the bug.
-        with person_logged_in(self.subscriber):
-            self.bug.subscribe(self.subscriber, self.subscriber)
-
-        # Load a `PersonSubscriptionInfo`s for subscriber and a bug.
-        self.subscriptions.reload()
-        self.assertRealSubscriptionInfoMatches(
-            self.subscriptions.direct.personal[0],
-            self.bug, self.subscriber, False,
-             [{'task': self.bug.default_bugtask, 'pillar': target}], [])
+            self.bug, self.subscriber, True, [])
 
     def test_subscriber_is_bug_supervisor(self):
         target = self.bug.default_bugtask.target
@@ -433,7 +416,7 @@
         self.assertRealSubscriptionInfoMatches(
             self.subscriptions.direct.personal[0],
             self.bug, self.subscriber, False,
-             [], [{'task': self.bug.default_bugtask, 'pillar': target}])
+             [{'task': self.bug.default_bugtask, 'pillar': target}])
 
     def test_owner(self):
         # Bug is targeted to a pillar with no supervisor set.

=== modified file 'lib/lp/bugs/stories/bug-privacy/xx-bug-privacy.txt'
--- lib/lp/bugs/stories/bug-privacy/xx-bug-privacy.txt	2012-08-07 02:31:56 +0000
+++ lib/lp/bugs/stories/bug-privacy/xx-bug-privacy.txt	2012-08-22 01:27:20 +0000
@@ -27,8 +27,7 @@
     >>> browser = setupBrowser("Basic foo.bar@xxxxxxxxxxxxx:test")
     >>> browser.open("http://launchpad.dev/ubuntu/+filebug";)
 
-Ubuntu has no security contact, so the Ubuntu maintainer, Ubuntu Team,
-will be subscribed instead.
+The Ubuntu maintainer, Ubuntu Team, will be subscribed.
 
     >>> browser.getControl(name="field.title", index=0).value = (
     ...     "a private bug")
@@ -72,41 +71,6 @@
 
     >>> logout()
 
-Now let's set an Ubuntu security team, and see how the filebug page and
-subscriptions differ.
-
-    >>> browser.open("http://launchpad.dev/ubuntu/+securitycontact";)
-    >>> browser.getControl("Security Contact").value = "name12"
-    >>> browser.getControl("Change").click()
-
-    >>> browser.open("http://launchpad.dev/ubuntu/+filebug";)
-
-    >>> browser.getControl(name="field.title", index=0).value = (
-    ...     "a private bug")
-    >>> browser.getControl('Continue').click()
-
-    >>> browser.getControl(name="packagename_option").value = ["choose"]
-    >>> browser.getControl(name="field.packagename").value = "evolution"
-    >>> browser.getControl(name="field.comment").value = "secret info"
-    >>> browser.getControl("Private Security").selected = True
-    >>> browser.getControl("Submit Bug Report").click()
-
-    >>> other_bug_id = browser.url.split("/")[-1]
-    >>> print browser.url.replace(other_bug_id, "BUG-ID")
-    http://bugs.launchpad.dev/ubuntu/+source/evolution/+bug/BUG-ID
-
-
-Now only the reporter and /security contact/ are subscribed.
-
-    >>> login("foo.bar@xxxxxxxxxxxxx")
-
-    >>> bug = getUtility(IBugSet).get(other_bug_id)
-
-    >>> sorted(subscriber.name for subscriber in bug.getDirectSubscribers())
-    [u'name12', u'name16']
-
-    >>> logout()
-
 
 Anonymous users cannot see private bugs filed on distros, of course!
 

=== removed file 'lib/lp/bugs/stories/bugs/xx-malone-security-contacts.txt'
--- lib/lp/bugs/stories/bugs/xx-malone-security-contacts.txt	2010-05-25 21:19:24 +0000
+++ lib/lp/bugs/stories/bugs/xx-malone-security-contacts.txt	1970-01-01 00:00:00 +0000
@@ -1,20 +0,0 @@
-A security contact gets all email about security-related bugs.
-
-A security contact can be set on a distribution.
-
-    >>> browser = setupBrowser(auth="Basic foo.bar@xxxxxxxxxxxxx:test")
-    >>> browser.open("http://localhost:9000/ubuntu/+securitycontact";)
-    >>> browser.getControl("Security Contact").value = "name16"
-    >>> browser.getControl("Change").click()
-    >>> for message in get_feedback_messages(browser.contents):
-    ...     print extract_text(message)
-    Successfully changed the security contact to Foo Bar.
-
-Or on an upstream.
-
-    >>> browser.open("http://localhost:9000/firefox/+securitycontact";)
-    >>> browser.getControl("Security Contact").value = "name21"
-    >>> browser.getControl("Change").click()
-    >>> for message in get_feedback_messages(browser.contents):
-    ...     print extract_text(message)
-    Successfully changed the security contact to Hoary Gnome Team.

=== modified file 'lib/lp/bugs/stories/upstream-bugprivacy/xx-upstream-bug-privacy.txt'
--- lib/lp/bugs/stories/upstream-bugprivacy/xx-upstream-bug-privacy.txt	2012-07-17 06:34:59 +0000
+++ lib/lp/bugs/stories/upstream-bugprivacy/xx-upstream-bug-privacy.txt	2012-08-22 01:27:20 +0000
@@ -58,37 +58,6 @@
     ...BUG-ID...
     ...Undecided...
 
-When the upstream has a security contact, the upstream will be
-subscribed instead.
-
-    >>> browser.open("http://launchpad.dev/firefox/+securitycontact";)
-
-    >>> browser.getControl("Security Contact").value = "mark"
-    >>> browser.getControl("Change").click()
-
-    >>> browser.open("http://localhost:9000/firefox/+filebug";)
-    >>> browser.getControl('Summary', index=0).value = (
-    ...     "this is a newly created private bug")
-    >>> browser.getControl("Continue").click()
-
-    >>> browser.getControl(name="field.comment").value = (
-    ...     "very secret info here")
-    >>> browser.getControl("Private Security").selected = True
-    >>> browser.getControl("Submit Bug Report").click()
-
-    >>> other_bug_id = browser.url.split("/")[-1]
-    >>> print browser.url.replace(other_bug_id, "BUG-ID")
-    http://bugs.launchpad.dev/firefox/+bug/BUG-ID
-
-    >>> login("foo.bar@xxxxxxxxxxxxx")
-
-    >>> bug = getUtility(IBugSet).get(other_bug_id)
-
-    >>> sorted(subscriber.name for subscriber in bug.getDirectSubscribers())
-    [u'mark', u'name16']
-
-    >>> logout()
-
 == Checking basic access to the private bug pages ==
 
 Trying to access the task edit page of a task on a private bug

=== modified file 'lib/lp/bugs/stories/webservice/xx-bug-target.txt'
--- lib/lp/bugs/stories/webservice/xx-bug-target.txt	2011-12-24 15:18:32 +0000
+++ lib/lp/bugs/stories/webservice/xx-bug-target.txt	2012-08-22 01:27:20 +0000
@@ -176,44 +176,3 @@
     HTTP/1.1 401 Unauthorized
     ...
     <BLANKLINE>
-
-== security_contact ==
-
-We can retrieve or set a person or team as the security contact for projects.
-
-    >>> firefox_project = webservice.get('/firefox').jsonBody()
-    >>> print firefox_project['security_contact_link']
-    None
-
-    >>> print webservice.patch(
-    ...     '/firefox', 'application/json',
-    ...     dumps({'security_contact_link': firefox_project['owner_link']}))
-    HTTP/1.1 209 Content Returned...
-
-    >>> firefox_project = webservice.get('/firefox').jsonBody()
-    >>> print firefox_project['security_contact_link']
-    http://api.launchpad.dev/beta/~name12
-
-We can also do this for distributions.
-
-    >>> ubuntutest_dist = webservice.get('/ubuntutest').jsonBody()
-    >>> print ubuntutest_dist['security_contact_link']
-    None
-
-    >>> print webservice.patch(
-    ...     '/ubuntutest', 'application/json',
-    ...     dumps({'security_contact_link': ubuntutest_dist['owner_link']}))
-    HTTP/1.1 209 Content Returned...
-
-    >>> ubuntutest_dist = webservice.get('/ubuntutest').jsonBody()
-    >>> print ubuntutest_dist['security_contact_link']
-    http://api.launchpad.dev/beta/~ubuntu-team
-
-Setting the security contact is restricted to owners and launchpad admins.
-
-    >>> print user_webservice.patch(
-    ...     '/ubuntutest', 'application/json',
-    ...     dumps({'security_contact_link': None}))
-    HTTP/1.1 401 Unauthorized
-    ...
-    <BLANKLINE>

=== modified file 'lib/lp/bugs/subscribers/bugtask.py'
--- lib/lp/bugs/subscribers/bugtask.py	2011-12-30 06:14:56 +0000
+++ lib/lp/bugs/subscribers/bugtask.py	2012-08-22 01:27:20 +0000
@@ -4,7 +4,6 @@
 __metaclass__ = type
 __all__ = [
     'notify_bugtask_edited',
-    'update_security_contact_subscriptions',
     ]
 
 
@@ -14,39 +13,11 @@
     send_bug_details_to_new_bug_subscribers,
     )
 from lp.registry.interfaces.person import IPerson
-from lp.registry.interfaces.product import IProduct
 from lp.services.database.sqlbase import block_implicit_flushes
 from lp.services.webapp.publisher import canonical_url
 
 
 @block_implicit_flushes
-def update_security_contact_subscriptions(event):
-    """Subscribe the new security contact when a bugtask's product changes.
-
-    Only subscribes the new security contact if the bug was marked a
-    security issue originally.
-
-    No change is made for private bugs.
-    """
-    if event.object.bug.private:
-        return
-
-    if not IProduct.providedBy(event.object.target):
-        return
-
-    bugtask_before_modification = event.object_before_modification
-    bugtask_after_modification = event.object
-
-    if (bugtask_before_modification.product !=
-        bugtask_after_modification.product):
-        new_product = bugtask_after_modification.product
-        if (bugtask_before_modification.bug.security_related and
-            new_product.security_contact):
-            bugtask_after_modification.bug.subscribe(
-                new_product.security_contact, IPerson(event.user))
-
-
-@block_implicit_flushes
 def notify_bugtask_edited(modified_bugtask, event):
     """Notify CC'd subscribers of this bug that something has changed
     on this task.
@@ -75,5 +46,3 @@
     send_bug_details_to_new_bug_subscribers(
         event.object.bug, previous_subscribers, current_subscribers,
         event_creator=event_creator)
-
-    update_security_contact_subscriptions(event)

=== modified file 'lib/lp/bugs/templates/buglisting-default.pt'
--- lib/lp/bugs/templates/buglisting-default.pt	2012-08-14 17:55:38 +0000
+++ lib/lp/bugs/templates/buglisting-default.pt	2012-08-22 01:27:20 +0000
@@ -175,24 +175,6 @@
             </tal:edit-bug-supervisor>
           </dd>
         </dl>
-        <dl tal:define="securitycontact context/security_contact">
-          <dt id="bug-security">Security contact:</dt>
-          <dd>
-            <tal:none condition="not:securitycontact">None set</tal:none>
-            <a tal:condition="securitycontact"
-               tal:replace="structure securitycontact/fmt:link">
-              Billy Anderson
-            </a>
-            <tal:edit-securitycontact
-                condition="context/menu:bugs/securitycontact|nothing">
-              <a tal:define="link context/menu:bugs/securitycontact"
-                 tal:condition="link/enabled"
-                 tal:attributes="href link/url; title link/text">
-                <img tal:attributes="alt link/text" src="/@@/edit" />
-              </a>
-            </tal:edit-securitycontact>
-          </dd>
-        </dl>
       </div>
       <tal:menu replace="structure view/@@+related-pages" />
       <tal:do_not_show_portlets_advanced_form

=== removed file 'lib/lp/bugs/templates/securitycontact-edit.pt'
--- lib/lp/bugs/templates/securitycontact-edit.pt	2010-05-18 21:24:27 +0000
+++ lib/lp/bugs/templates/securitycontact-edit.pt	1970-01-01 00:00:00 +0000
@@ -1,23 +0,0 @@
-<html
-  xmlns="http://www.w3.org/1999/xhtml";
-  xmlns:tal="http://xml.zope.org/namespaces/tal";
-  xmlns:metal="http://xml.zope.org/namespaces/metal";
-  xmlns:i18n="http://xml.zope.org/namespaces/i18n";
-  metal:use-macro="view/macro:page/main_only"
-  i18n:domain="launchpad"
->
-
-<body>
-
-<div metal:fill-slot="main">
-  <div metal:use-macro="context/@@launchpad_form/form">
-
-    <p metal:fill-slot="extra_info">
-      When a bug is reported as a security vulnerability, the security
-      contact is automatically subscribed to the bug.
-    </p>
-  </div>
-
-</div>
-</body>
-</html>

=== modified file 'lib/lp/registry/browser/distribution.py'
--- lib/lp/registry/browser/distribution.py	2012-07-03 00:23:26 +0000
+++ lib/lp/registry/browser/distribution.py	2012-08-22 01:27:20 +0000
@@ -501,12 +501,7 @@
 
     @property
     def links(self):
-        links = [
-            'bugsupervisor',
-            'securitycontact',
-            'cve',
-            'filebug',
-            ]
+        links = ['bugsupervisor', 'cve', 'filebug']
         add_subscribe_link(links)
         return links
 

=== modified file 'lib/lp/registry/browser/pillar.py'
--- lib/lp/registry/browser/pillar.py	2012-08-16 03:58:39 +0000
+++ lib/lp/registry/browser/pillar.py	2012-08-22 01:27:20 +0000
@@ -254,11 +254,6 @@
         text = 'Report a bug'
         return Link('+filebug', text, icon='bug')
 
-    @enabled_with_permission('launchpad.Edit')
-    def securitycontact(self):
-        text = 'Change security contact'
-        return Link('+securitycontact', text, icon='edit')
-
 
 class PillarViewMixin():
     """A mixin for pillar views to populate the json request cache."""

=== modified file 'lib/lp/registry/browser/product.py'
--- lib/lp/registry/browser/product.py	2012-08-16 04:36:09 +0000
+++ lib/lp/registry/browser/product.py	2012-08-22 01:27:20 +0000
@@ -619,12 +619,7 @@
 
     @cachedproperty
     def links(self):
-        links = [
-            'filebug',
-            'bugsupervisor',
-            'securitycontact',
-            'cve',
-            ]
+        links = ['filebug', 'bugsupervisor', 'cve']
         add_subscribe_link(links)
         links.append('configure_bugtracker')
         return links

=== modified file 'lib/lp/registry/browser/tests/test_team.py'
--- lib/lp/registry/browser/tests/test_team.py	2012-08-15 22:01:39 +0000
+++ lib/lp/registry/browser/tests/test_team.py	2012-08-22 01:27:20 +0000
@@ -329,16 +329,6 @@
         self._test_edit_team_view_expected_subscription_vocab(
             setup_team, EXCLUSIVE_TEAM_POLICY)
 
-    def test_edit_team_view_pillar_security_contact(self):
-        # The edit view renders only closed membership policy choices when
-        # the team is a pillar security contact.
-
-        def setup_team(team):
-            self.factory.makeProduct(security_contact=team)
-
-        self._test_edit_team_view_expected_subscription_vocab(
-            setup_team, EXCLUSIVE_TEAM_POLICY)
-
     def test_edit_team_view_has_ppas(self):
         # The edit view renders only closed membership policy choices when
         # the team has any ppas.

=== modified file 'lib/lp/registry/configure.zcml'
--- lib/lp/registry/configure.zcml	2012-08-16 05:18:54 +0000
+++ lib/lp/registry/configure.zcml	2012-08-22 01:27:20 +0000
@@ -1270,7 +1270,6 @@
                 redeemSubscriptionVoucher
                 releaseroot
                 screenshotsurl
-                security_contact
                 sourceforgeproject
                 summary
                 title
@@ -1581,7 +1580,6 @@
                 official_malone
                 owner
                 package_derivatives_email
-                security_contact
                 summary
                 title"/>
         <require

=== modified file 'lib/lp/registry/interfaces/distribution.py'
--- lib/lp/registry/interfaces/distribution.py	2012-08-01 06:10:33 +0000
+++ lib/lp/registry/interfaces/distribution.py	2012-08-22 01:27:20 +0000
@@ -1,8 +1,6 @@
 # Copyright 2009-2011 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-# pylint: disable-msg=E0211,E0213
-
 """Interfaces including and related to IDistribution."""
 
 __metaclass__ = type
@@ -72,7 +70,6 @@
     IOfficialBugTagTargetPublic,
     IOfficialBugTagTargetRestricted,
     )
-from lp.bugs.interfaces.securitycontact import IHasSecurityContact
 from lp.bugs.interfaces.structuralsubscription import (
     IStructuralSubscriptionTarget,
     )
@@ -135,10 +132,10 @@
 class IDistributionPublic(
     IBugTarget, ICanGetMilestonesDirectly, IHasAppointedDriver,
     IHasBuildRecords, IHasDrivers, IHasMilestones,
-    IHasOOPSReferences, IHasOwner, IHasSecurityContact, IHasSprints,
-    IHasTranslationImports, ITranslationPolicy, IKarmaContext,
-    ILaunchpadUsage, IMakesAnnouncements, IOfficialBugTagTargetPublic,
-    IPillar, IServiceUsage, ISpecificationTarget):
+    IHasOOPSReferences, IHasOwner, IHasSprints, IHasTranslationImports,
+    ITranslationPolicy, IKarmaContext, ILaunchpadUsage, IMakesAnnouncements,
+    IOfficialBugTagTargetPublic, IPillar, IServiceUsage,
+    ISpecificationTarget):
     """Public IDistribution properties."""
 
     id = Attribute("The distro's unique number.")

=== modified file 'lib/lp/registry/interfaces/person.py'
--- lib/lp/registry/interfaces/person.py	2012-08-14 21:21:50 +0000
+++ lib/lp/registry/interfaces/person.py	2012-08-22 01:27:20 +0000
@@ -1,8 +1,6 @@
 # Copyright 2009-2012 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-# pylint: disable-msg=E0211,E0213
-
 """Person interfaces."""
 
 __metaclass__ = type
@@ -1146,9 +1144,6 @@
     def isAnyPillarOwner():
         """Is this person the owner of any pillar?"""
 
-    def isAnySecurityContact():
-        """Is this person the security contact of any pillar?"""
-
     def getAllCommercialSubscriptionVouchers(voucher_proxy=None):
         """Return all commercial subscription vouchers.
 

=== modified file 'lib/lp/registry/interfaces/product.py'
--- lib/lp/registry/interfaces/product.py	2012-08-16 05:18:54 +0000
+++ lib/lp/registry/interfaces/product.py	2012-08-22 01:27:20 +0000
@@ -93,7 +93,6 @@
     IOfficialBugTagTargetRestricted,
     )
 from lp.bugs.interfaces.bugtracker import IHasExternalBugTracker
-from lp.bugs.interfaces.securitycontact import IHasSecurityContact
 from lp.bugs.interfaces.structuralsubscription import (
     IStructuralSubscriptionTarget,
     )
@@ -429,11 +428,10 @@
     IBugTarget, ICanGetMilestonesDirectly, IHasAppointedDriver, IHasBranches,
     IHasBranchVisibilityPolicy, IHasDrivers, IHasExternalBugTracker, IHasIcon,
     IHasLogo, IHasMergeProposals, IHasMilestones,
-    IHasMugshot, IHasOwner, IHasSecurityContact, IHasSprints,
-    IHasTranslationImports, ITranslationPolicy, IKarmaContext,
-    ILaunchpadUsage, IMakesAnnouncements, IOfficialBugTagTargetPublic,
-    IHasOOPSReferences, IPillar, ISpecificationTarget, IHasRecipes,
-    IHasCodeImports, IServiceUsage):
+    IHasMugshot, IHasOwner, IHasSprints, IHasTranslationImports,
+    ITranslationPolicy, IKarmaContext, ILaunchpadUsage, IMakesAnnouncements,
+    IOfficialBugTagTargetPublic, IHasOOPSReferences, IPillar,
+    ISpecificationTarget, IHasRecipes, IHasCodeImports, IServiceUsage):
     """Public IProduct properties."""
 
     id = Int(title=_('The Project ID'))

=== modified file 'lib/lp/registry/interfaces/role.py'
--- lib/lp/registry/interfaces/role.py	2012-08-16 13:35:52 +0000
+++ lib/lp/registry/interfaces/role.py	2012-08-22 01:27:20 +0000
@@ -1,7 +1,6 @@
 # Copyright 2009-2012 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-# pylint: disable-msg=E0211,E0213,W0611
 """Interfaces that define common roles associated with objects."""
 
 __metaclass__ = type
@@ -130,9 +129,6 @@
     def isBugSupervisor(obj):
         """Is this person the bug supervisor of the object?"""
 
-    def isSecurityContact(obj):
-        """Is this person the security contact of the object?"""
-
     def isOneOfDrivers(obj):
         """Is this person on of the drivers of the object?
 

=== modified file 'lib/lp/registry/interfaces/series.py'
--- lib/lp/registry/interfaces/series.py	2011-12-24 16:54:44 +0000
+++ lib/lp/registry/interfaces/series.py	2012-08-22 01:27:20 +0000
@@ -1,8 +1,6 @@
 # Copyright 2009 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-# pylint: disable-msg=E0211,E0213
-
 """Interfaces including common IDistroSeries and IProductSeries classes."""
 
 __metaclass__ = type
@@ -26,10 +24,7 @@
 from lp import _
 from lp.registry.interfaces.person import IPerson
 from lp.registry.interfaces.role import IHasDrivers
-from lp.services.fields import (
-    PublicPersonChoice,
-    Summary,
-    )
+from lp.services.fields import Summary
 
 
 class SeriesStatus(DBEnumeratedType):
@@ -129,9 +124,3 @@
                 'supervisor.'),
         readonly=True,
         value_type=Reference(schema=IPerson))
-
-    security_contact = PublicPersonChoice(
-        title=_('Security Contact'),
-        description=_('Currently just a reference to the parent '
-                      'security contact.'),
-        required=False, vocabulary='ValidPersonOrTeam')

=== modified file 'lib/lp/registry/model/distribution.py'
--- lib/lp/registry/model/distribution.py	2012-08-17 05:05:37 +0000
+++ lib/lp/registry/model/distribution.py	2012-08-22 01:27:20 +0000
@@ -1,7 +1,6 @@
 # Copyright 2009-2012 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-# pylint: disable-msg=E0611,W0212
 """Database classes for implementing distribution items."""
 
 __metaclass__ = type
@@ -236,10 +235,6 @@
         default=None)
     bug_reporting_guidelines = StringCol(default=None)
     bug_reported_acknowledgement = StringCol(default=None)
-    security_contact = ForeignKey(
-        dbName='security_contact', foreignKey='Person',
-        storm_validator=validate_person_or_closed_team, notNull=False,
-        default=None)
     driver = ForeignKey(
         dbName="driver", foreignKey="Person",
         storm_validator=validate_public_person, notNull=False, default=None)

=== modified file 'lib/lp/registry/model/person.py'
--- lib/lp/registry/model/person.py	2012-08-15 17:45:15 +0000
+++ lib/lp/registry/model/person.py	2012-08-22 01:27:20 +0000
@@ -1,7 +1,5 @@
 # Copyright 2009-2012 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
-# vars() causes W0612
-# pylint: disable-msg=E0611,W0212,W0612,C0322
 
 """Implementation classes for a Person."""
 
@@ -1209,30 +1207,6 @@
         )
         return rs.one()
 
-    def isAnySecurityContact(self):
-        """See IPerson."""
-        with_sql = [
-            With("teams", SQL("""
-                 SELECT team FROM TeamParticipation
-                 WHERE TeamParticipation.person = %d
-                """ % self.id)),
-            With("owned_entities", SQL("""
-                 SELECT Product.id
-                 FROM Product
-                 WHERE Product.security_contact IN (SELECT team FROM teams)
-                 UNION ALL
-                 SELECT Distribution.id
-                 FROM Distribution
-                 WHERE Distribution.security_contact
-                    IN (SELECT team FROM teams)
-                """))
-           ]
-        store = IStore(self)
-        rs = store.with_(with_sql).using("owned_entities").find(
-            SQL("count(*) > 0"),
-        )
-        return rs.one()
-
     def getAllCommercialSubscriptionVouchers(self, voucher_proxy=None):
         """See `IPerson`."""
         if voucher_proxy is None:
@@ -1846,17 +1820,12 @@
         if not self.is_team:
             raise ValueError("This method must only be used for teams.")
 
-        # Does this team own or is the security contact for any pillars?
+        # Does this team own any pillars?
         if self.isAnyPillarOwner():
             raise TeamMembershipPolicyError(
                 "The team membership policy cannot be %s because it "
                 "maintains one or more projects, project groups, or "
                 "distributions." % policy)
-        if self.isAnySecurityContact():
-            raise TeamMembershipPolicyError(
-                "The team membership policy cannot be %s because it "
-                "is the security contact for one or more projects, "
-                "project groups, or distributions." % policy)
 
         # Does this team have any PPAs
         for ppa in self.ppas:

=== modified file 'lib/lp/registry/model/personroles.py'
--- lib/lp/registry/model/personroles.py	2011-12-14 03:06:38 +0000
+++ lib/lp/registry/model/personroles.py	2012-08-22 01:27:20 +0000
@@ -15,7 +15,6 @@
 
 from lp.app.interfaces.launchpad import ILaunchpadCelebrities
 from lp.bugs.interfaces.bugsupervisor import IHasBugSupervisor
-from lp.bugs.interfaces.securitycontact import IHasSecurityContact
 from lp.registry.interfaces.person import IPerson
 from lp.registry.interfaces.role import (
     IHasDrivers,
@@ -58,11 +57,6 @@
         return (IHasBugSupervisor.providedBy(obj)
                 and self.inTeam(obj.bug_supervisor))
 
-    def isSecurityContact(self, obj):
-        """See IPersonRoles."""
-        return (IHasSecurityContact.providedBy(obj)
-                and self.inTeam(obj.security_contact))
-
     def isDriver(self, obj):
         """See IPersonRoles."""
         return self.inTeam(obj.driver)

=== modified file 'lib/lp/registry/model/pillaraffiliation.py'
--- lib/lp/registry/model/pillaraffiliation.py	2012-01-06 11:08:30 +0000
+++ lib/lp/registry/model/pillaraffiliation.py	2012-08-22 01:27:20 +0000
@@ -173,7 +173,6 @@
         - owner of bugtask pillar
         - driver of bugtask pillar
         - bug supervisor of bugtask pillar
-        - security contact of bugtask pillar
         """
         super_instance = super(BugTaskPillarAffiliation, self)
         result = super_instance._getAffiliationTeamRoles(pillars)
@@ -182,10 +181,6 @@
                 self.getIconUrl(pillar),
                 pillar.displayname,
                 'bug supervisor')] = [pillar.bug_supervisor]
-            result[BadgeDetails(
-                self.getIconUrl(pillar),
-                pillar.displayname,
-                'security contact')] = [pillar.security_contact]
         return result
 
 

=== modified file 'lib/lp/registry/model/product.py'
--- lib/lp/registry/model/product.py	2012-08-20 13:26:21 +0000
+++ lib/lp/registry/model/product.py	2012-08-22 01:27:20 +0000
@@ -1,6 +1,5 @@
 # Copyright 2009-2012 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
-# pylint: disable-msg=E0611,W0212
 
 """Database classes including and related to Product."""
 
@@ -343,10 +342,6 @@
         storm_validator=validate_person,
         notNull=False,
         default=None)
-    security_contact = ForeignKey(
-        dbName='security_contact', foreignKey='Person',
-        storm_validator=validate_person_or_closed_team, notNull=False,
-        default=None)
     driver = ForeignKey(
         dbName="driver", foreignKey="Person",
         storm_validator=validate_person,
@@ -1739,8 +1734,7 @@
                 ['development_focusID'])
             # Only need the objects for canonical_url, no need for validity.
             bulk.load_related(Person, products.values(),
-                ['_ownerID', 'registrantID', 'bug_supervisorID', 'driverID',
-                 'security_contactID'])
+                ['_ownerID', 'registrantID', 'bug_supervisorID', 'driverID'])
         return DecoratedResultSet(result, pre_iter_hook=eager_load)
 
     def search_sqlobject(self, text):

=== modified file 'lib/lp/registry/model/series.py'
--- lib/lp/registry/model/series.py	2010-11-26 18:12:16 +0000
+++ lib/lp/registry/model/series.py	2012-08-22 01:27:20 +0000
@@ -47,11 +47,6 @@
         return self.parent.bug_supervisor
 
     @property
-    def security_contact(self):
-        """See `ISeriesMixin`."""
-        return self.parent.security_contact
-
-    @property
     def drivers(self):
         """See `IHasDrivers`."""
         drivers = set()

=== modified file 'lib/lp/registry/stories/webservice/xx-distribution.txt'
--- lib/lp/registry/stories/webservice/xx-distribution.txt	2012-05-22 07:25:23 +0000
+++ lib/lp/registry/stories/webservice/xx-distribution.txt	2012-08-22 01:27:20 +0000
@@ -47,7 +47,6 @@
     owner_link: u'http://.../~ubuntu-team'
     registrant_link: u'http://.../~registry'
     resource_type_link: u'http://.../#distribution'
-    security_contact_link: None
     self_link: u'http://.../ubuntu'
     series_collection_link: u'http://.../ubuntu/series'
     summary: u'Ubuntu is a new approach to Linux Distribution...'

=== modified file 'lib/lp/registry/stories/webservice/xx-project-registry.txt'
--- lib/lp/registry/stories/webservice/xx-project-registry.txt	2012-07-08 17:29:39 +0000
+++ lib/lp/registry/stories/webservice/xx-project-registry.txt	2012-08-22 01:27:20 +0000
@@ -188,7 +188,6 @@
     resource_type_link: u'http://.../#project'
     reviewer_whiteboard: None
     screenshots_url: None
-    security_contact_link: None
     self_link: u'http://.../firefox'
     series_collection_link: u'http://.../firefox/series'
     sourceforge_project: None

=== modified file 'lib/lp/registry/tests/test_distribution.py'
--- lib/lp/registry/tests/test_distribution.py	2012-08-14 23:27:07 +0000
+++ lib/lp/registry/tests/test_distribution.py	2012-08-22 01:27:20 +0000
@@ -91,20 +91,6 @@
             closed_team = self.factory.makeTeam(membership_policy=policy)
             self.factory.makeDistribution(owner=closed_team)
 
-    def test_security_contact_cannot_be_open_team(self):
-        """Distro security contacts cannot be open teams."""
-        for policy in INCLUSIVE_TEAM_POLICY:
-            open_team = self.factory.makeTeam(membership_policy=policy)
-            self.assertRaises(
-                InclusiveTeamLinkageError, self.factory.makeDistribution,
-                security_contact=open_team)
-
-    def test_security_contact_can_be_closed_team(self):
-        """Distro security contacts can be exclusive teams."""
-        for policy in EXCLUSIVE_TEAM_POLICY:
-            closed_team = self.factory.makeTeam(membership_policy=policy)
-            self.factory.makeDistribution(security_contact=closed_team)
-
     def test_distribution_repr_ansii(self):
         # Verify that ANSI displayname is ascii safe.
         distro = self.factory.makeDistribution(

=== modified file 'lib/lp/registry/tests/test_person.py'
--- lib/lp/registry/tests/test_person.py	2012-08-14 21:52:26 +0000
+++ lib/lp/registry/tests/test_person.py	2012-08-22 01:27:20 +0000
@@ -572,22 +572,6 @@
         self.assertTrue(owner.isAnyPillarOwner())
         self.assertFalse(person.isAnyPillarOwner())
 
-    def test_product_isAnySecurityContact(self):
-        # Test isAnySecurityContact for products
-        person = self.factory.makePerson()
-        contact = self.factory.makePerson()
-        self.factory.makeProduct(security_contact=contact)
-        self.assertTrue(contact.isAnySecurityContact())
-        self.assertFalse(person.isAnySecurityContact())
-
-    def test_distribution_isAnySecurityContact(self):
-        # Test isAnySecurityContact for distributions
-        person = self.factory.makePerson()
-        contact = self.factory.makePerson()
-        self.factory.makeDistribution(security_contact=contact)
-        self.assertTrue(contact.isAnySecurityContact())
-        self.assertFalse(person.isAnySecurityContact())
-
     def test_has_current_commercial_subscription(self):
         # IPerson.hasCurrentCommercialSubscription() checks for one.
         team = self.factory.makeTeam(

=== modified file 'lib/lp/registry/tests/test_personroles.py'
--- lib/lp/registry/tests/test_personroles.py	2012-01-01 02:58:52 +0000
+++ lib/lp/registry/tests/test_personroles.py	2012-08-22 01:27:20 +0000
@@ -121,12 +121,6 @@
         roles = IPersonRoles(self.person)
         self.assertTrue(roles.isBugSupervisor(product))
 
-    def test_isSecurityContact(self):
-        # The person can be the security contact of something, e.g. a product.
-        product = self.factory.makeProduct(security_contact=self.person)
-        roles = IPersonRoles(self.person)
-        self.assertTrue(roles.isSecurityContact(product))
-
     def test_isOneOf(self):
         # Objects may have multiple roles that a person can fulfill.
         # Specifications are such a case.

=== modified file 'lib/lp/registry/tests/test_pillaraffiliation.py'
--- lib/lp/registry/tests/test_pillaraffiliation.py	2012-01-01 02:58:52 +0000
+++ lib/lp/registry/tests/test_pillaraffiliation.py	2012-08-22 01:27:20 +0000
@@ -61,14 +61,6 @@
         distro = self.factory.makeDistribution(driver=team, name='pting')
         self._check_affiliated_with_distro(person, distro, 'driver')
 
-    def test_no_distro_security_contact_affiliation(self):
-        # A person who is the security contact for a distro is not affiliated
-        # for simple distro affiliation checks.
-        person = self.factory.makePerson()
-        distro = self.factory.makeDistribution(security_contact=person)
-        self.assertEqual(
-            [], IHasAffiliation(distro).getAffiliationBadges([person])[0])
-
     def test_no_distro_bug_supervisor_affiliation(self):
         # A person who is the bug supervisor for a distro is not affiliated
         # for simple distro affiliation checks.
@@ -122,14 +114,6 @@
         product = self.factory.makeProduct(project=project, name='pting')
         self._check_affiliated_with_product(person, product, 'driver')
 
-    def test_no_product_security_contact_affiliation(self):
-        # A person who is the security contact for a product is is not
-        # affiliated for simple product affiliation checks.
-        person = self.factory.makePerson()
-        product = self.factory.makeProduct(security_contact=person)
-        self.assertEqual(
-            [], IHasAffiliation(product).getAffiliationBadges([person])[0])
-
     def test_no_product_bug_supervisor_affiliation(self):
         # A person who is the bug supervisor for a product is is not
         # affiliated for simple product affiliation checks.
@@ -182,13 +166,6 @@
 
 class _TestBugTaskorBranchMixin:
 
-    def test_distro_security_contact_affiliation(self):
-        # A person who is the security contact for a distro is affiliated.
-        person = self.factory.makePerson()
-        distro = self.factory.makeDistribution(
-            security_contact=person, name='pting')
-        self._check_affiliated_with_distro(person, distro, 'security contact')
-
     def test_distro_bug_supervisor_affiliation(self):
         # A person who is the bug supervisor for a distro is affiliated.
         person = self.factory.makePerson()
@@ -196,14 +173,6 @@
             bug_supervisor=person, name='pting')
         self._check_affiliated_with_distro(person, distro, 'bug supervisor')
 
-    def test_product_security_contact_affiliation(self):
-        # A person who is the security contact for a distro is affiliated.
-        person = self.factory.makePerson()
-        product = self.factory.makeProduct(
-            security_contact=person, name='pting')
-        self._check_affiliated_with_product(
-            person, product, 'security contact')
-
     def test_product_bug_supervisor_affiliation(self):
         # A person who is the bug supervisor for a distro is affiliated.
         person = self.factory.makePerson()

=== modified file 'lib/lp/registry/tests/test_product.py'
--- lib/lp/registry/tests/test_product.py	2012-08-20 13:26:21 +0000
+++ lib/lp/registry/tests/test_product.py	2012-08-22 01:27:20 +0000
@@ -1,7 +1,6 @@
 # Copyright 2009-2012 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-
 __metaclass__ = type
 
 from cStringIO import StringIO
@@ -265,20 +264,6 @@
             closed_team = self.factory.makeTeam(membership_policy=policy)
             self.factory.makeProduct(owner=closed_team)
 
-    def test_security_contact_cannot_be_open_team(self):
-        """Product security contacts cannot be open teams."""
-        for policy in INCLUSIVE_TEAM_POLICY:
-            open_team = self.factory.makeTeam(membership_policy=policy)
-            self.assertRaises(
-                InclusiveTeamLinkageError, self.factory.makeProduct,
-                security_contact=open_team)
-
-    def test_security_contact_can_be_closed_team(self):
-        """Product security contacts can be exclusive teams."""
-        for policy in EXCLUSIVE_TEAM_POLICY:
-            closed_team = self.factory.makeTeam(membership_policy=policy)
-            self.factory.makeProduct(security_contact=closed_team)
-
     def test_private_bugs_on_not_allowed_for_anonymous(self):
         # Anonymous cannot turn on private bugs.
         product = self.factory.makeProduct()

=== modified file 'lib/lp/registry/tests/test_team.py'
--- lib/lp/registry/tests/test_team.py	2012-08-14 23:27:07 +0000
+++ lib/lp/registry/tests/test_team.py	2012-08-22 01:27:20 +0000
@@ -433,16 +433,6 @@
             TeamMembershipPolicyError, self.field.validate,
             TeamMembershipPolicy.OPEN)
 
-    def test_closed_team_security_contact_cannot_become_open(self):
-        # The team cannot become open if it is a security contact.
-        self.setUpTeams()
-        self.factory.makeProduct(security_contact=self.team)
-        self.assertFalse(
-            self.field.constraint(TeamMembershipPolicy.OPEN))
-        self.assertRaises(
-            TeamMembershipPolicyError, self.field.validate,
-            TeamMembershipPolicy.OPEN)
-
 
 class TestTeamMembershipPolicyChoiceRestrcted(
                                    TestTeamMembershipPolicyChoiceModerated):

=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py	2012-08-16 13:08:43 +0000
+++ lib/lp/testing/factory.py	2012-08-22 01:27:20 +0000
@@ -5,8 +5,6 @@
 # Copyright 2009-2012 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-# pylint: disable-msg=F0401
-
 """Testing infrastructure for the Launchpad application.
 
 This module should not contain tests (but it should be tested).
@@ -955,8 +953,8 @@
         licenses=None, owner=None, registrant=None,
         title=None, summary=None, official_malone=None,
         translations_usage=None, bug_supervisor=None, private_bugs=False,
-        driver=None, security_contact=None, icon=None,
-        bug_sharing_policy=None, branch_sharing_policy=None):
+        driver=None, icon=None, bug_sharing_policy=None,
+        branch_sharing_policy=None):
         """Create and return a new, arbitrary Product."""
         if owner is None:
             owner = self.makePerson()
@@ -995,8 +993,6 @@
             naked_product.bug_supervisor = bug_supervisor
         if driver is not None:
             naked_product.driver = driver
-        if security_contact is not None:
-            naked_product.security_contact = security_contact
         if private_bugs:
             naked_product.private_bugs = private_bugs
         if branch_sharing_policy:
@@ -2415,9 +2411,9 @@
     def makeDistribution(self, name=None, displayname=None, owner=None,
                          registrant=None, members=None, title=None,
                          aliases=None, bug_supervisor=None, driver=None,
-                         security_contact=None, publish_root_dir=None,
-                         publish_base_url=None, publish_copy_base_url=None,
-                         no_pubconf=False, icon=None, summary=None):
+                         publish_root_dir=None, publish_base_url=None,
+                         publish_copy_base_url=None, no_pubconf=False,
+                         icon=None, summary=None):
         """Make a new distribution."""
         if name is None:
             name = self.getUniqueString(prefix="distribution")
@@ -2445,8 +2441,6 @@
             naked_distro.driver = driver
         if bug_supervisor is not None:
             naked_distro.bug_supervisor = bug_supervisor
-        if security_contact is not None:
-            naked_distro.security_contact = security_contact
         if not no_pubconf:
             self.makePublisherConfig(
                 distro, publish_root_dir, publish_base_url,


Follow ups