← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~stevenk/launchpad/bugs-remove-old-privacy into lp:launchpad

 

Steve Kowalik has proposed merging lp:~stevenk/launchpad/bugs-remove-old-privacy into lp:launchpad.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)
Related bugs:
  Bug #933766 in Launchpad itself: "Update bug to use information_visibility_policy"
  https://bugs.launchpad.net/launchpad/+bug/933766

For more details, see:
https://code.launchpad.net/~stevenk/launchpad/bugs-remove-old-privacy/+merge/98974

As part of the slow march towards un-conflating privacy and security of bugs, we are moving everything from using Bug.private and Bug.security_related to Bug.information_type.

This branch drops Bug._{private,security_related} as well as moving the code from IBug.setPrivacyAndSecurityRelated() to a new function, IBug.transistionToInformationType() -- I've not exported it on the API, but that is easy enough to change.

I have dropped private and security from CreateBugParams and replaced it with information_type. I have hacked around that in the factory and a few other places to make this branch a little more manageable.

This branch requires DB patches -11-4 and -12-3 to be on production (and therefore in devel) before it can land.
-- 
https://code.launchpad.net/~stevenk/launchpad/bugs-remove-old-privacy/+merge/98974
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~stevenk/launchpad/bugs-remove-old-privacy into lp:launchpad.
=== modified file 'lib/lp/bugs/adapters/bug.py'
--- lib/lp/bugs/adapters/bug.py	2010-08-20 20:31:18 +0000
+++ lib/lp/bugs/adapters/bug.py	2012-03-23 06:19:21 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2012 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Resources having to do with Launchpad bugs."""
@@ -7,11 +7,14 @@
 __all__ = [
     'bugcomment_to_entry',
     'bugtask_to_privacy',
+    'convert_to_information_type',
     ]
 
 from lazr.restful.interfaces import IEntry
 from zope.component import getMultiAdapter
 
+from lp.registry.enums import InformationType
+
 
 def bugcomment_to_entry(comment, version):
     """Will adapt to the bugcomment to the real IMessage.
@@ -22,9 +25,21 @@
     return getMultiAdapter(
         (comment.bugtask.bug.messages[comment.index], version), IEntry)
 
+
 def bugtask_to_privacy(bugtask):
     """Adapt the bugtask to the underlying bug (which implements IPrivacy).
 
     Needed because IBugTask does not implement IPrivacy.
     """
     return bugtask.bug
+
+
+def convert_to_information_type(private, security_related):
+    if private and security_related:
+        return InformationType.EMBARGOEDSECURITY
+    elif security_related:
+        return InformationType.UNEMBARGOEDSECURITY
+    elif private:
+        return InformationType.USERDATA
+    else:
+        return InformationType.PUBLIC

=== modified file 'lib/lp/bugs/browser/bugtarget.py'
--- lib/lp/bugs/browser/bugtarget.py	2012-02-21 22:46:28 +0000
+++ lib/lp/bugs/browser/bugtarget.py	2012-03-23 06:19:21 +0000
@@ -77,6 +77,7 @@
     GhostWidget,
     ProductBugTrackerWidget,
     )
+from lp.bugs.adapters.bug import convert_to_information_type
 from lp.bugs.browser.bugrole import BugRoleMixin
 from lp.bugs.browser.structuralsubscription import (
     expose_structural_subscription_data_to_js,
@@ -117,6 +118,7 @@
     ProductConfigureBase,
     ProductPrivateBugsMixin,
     )
+from lp.registry.enums import InformationType
 from lp.registry.interfaces.distribution import IDistribution
 from lp.registry.interfaces.distributionsourcepackage import (
     IDistributionSourcePackage,
@@ -582,10 +584,11 @@
 
         # Security bugs are always private when filed, but can be disclosed
         # after they've been reported.
+        # This will change when the UI does.
         if security_related:
-            private = True
+            information_type = InformationType.EMBARGOEDSECURITY
         else:
-            private = False
+            information_type = InformationType.PUBLIC
 
         linkified_ack = structured(FormattersAPI(
             self.getAcknowledgementMessage(self.context)).text_to_html(
@@ -593,7 +596,7 @@
         notifications = [linkified_ack]
         params = CreateBugParams(
             title=title, comment=comment, owner=self.user,
-            security_related=security_related, private=private,
+            information_type=information_type,
             tags=data.get('tags'))
         if IDistribution.providedBy(context) and packagename:
             # We don't know if the package name we got was a source or binary
@@ -623,7 +626,8 @@
                 'Additional information was added to the bug description.')
 
         if extra_data.private:
-            params.private = extra_data.private
+            if params.information_type is InformationType.PUBLIC:
+                params.information_type = InformationType.USERDATA
 
         # Apply any extra options given by privileged users.
         if BugTask.userHasBugSupervisorPrivilegesContext(context, self.user):

=== modified file 'lib/lp/bugs/doc/bug-private-by-default.txt'
--- lib/lp/bugs/doc/bug-private-by-default.txt	2012-01-24 12:36:15 +0000
+++ lib/lp/bugs/doc/bug-private-by-default.txt	2012-03-23 06:19:21 +0000
@@ -5,6 +5,7 @@
 (this is enforced by a DB constraint).
 
     >>> from lp.bugs.interfaces.bug import CreateBugParams
+    >>> from lp.registry.enums import InformationType
     >>> from lp.registry.interfaces.person import IPersonSet
     >>> from lp.registry.interfaces.product import IProductSet
     >>> from lp.testing.dbuser import switch_dbuser
@@ -46,7 +47,7 @@
     >>> security_bug_params = CreateBugParams(
     ...     owner=no_priv, title='Security bug',
     ...     comment='A monkey took my GPG keys.',
-    ...     security_related=True)
+    ...     information_type=InformationType.EMBARGOEDSECURITY)
     >>> security_bug = landscape.createBug(security_bug_params)
     >>> [security_bug_task] = security_bug.bugtasks
     >>> security_bug_task.bug.private

=== modified file 'lib/lp/bugs/doc/bug.txt'
--- lib/lp/bugs/doc/bug.txt	2011-12-30 06:14:56 +0000
+++ lib/lp/bugs/doc/bug.txt	2012-03-23 06:19:21 +0000
@@ -90,6 +90,7 @@
 The event can be seen when we file a bug.
 
     >>> from lp.bugs.interfaces.bug import CreateBugParams
+    >>> from lp.registry.enums import InformationType
 
     >>> steve_harris = factory.makePerson(name='steve-harris')
     >>> params = CreateBugParams(
@@ -477,7 +478,7 @@
 
     >>> params = CreateBugParams(
     ...     title="test firefox bug", comment="blah blah blah", owner=foobar,
-    ...     private=True)
+    ...     information_type=InformationType.USERDATA)
     >>> params.setBugTarget(product=firefox)
     >>> added_bug = getUtility(IBugSet).createBug(params)
     >>> private_bug = bugset.get(added_bug.id)
@@ -507,7 +508,7 @@
     >>> evolution = spnset.get(9)
     >>> params = CreateBugParams(
     ...     title="test firefox bug", comment="blah blah blah",
-    ...     owner=foobar, private=True)
+    ...     owner=foobar, information_type=InformationType.USERDATA)
     >>> params.setBugTarget(distribution=ubuntu, sourcepackagename=evolution)
     >>> added_bug = getUtility(IBugSet).createBug(params)
     >>> private_bug = bugset.get(added_bug.id)
@@ -824,6 +825,7 @@
 
 We need a feature flag for this test since multi-pillar bugs shouldn't be
 private by default.
+
     >>> from lp.services.features.testing import FeatureFixture
     >>> feature_flag = {
     ...     'disclosure.allow_multipillar_private_bugs.enabled': 'on'}
@@ -850,6 +852,7 @@
 
 Changing bug security.
 
+    >>> ign = firefox_bug.setPrivate(False, getUtility(ILaunchBag).user)
     >>> bug_before_modification = Snapshot(firefox_bug, providing=IBug)
 
     >>> firefox_bug.security_related

=== modified file 'lib/lp/bugs/doc/bugnotification-sending.txt'
--- lib/lp/bugs/doc/bugnotification-sending.txt	2012-03-13 00:45:33 +0000
+++ lib/lp/bugs/doc/bugnotification-sending.txt	2012-03-23 06:19:21 +0000
@@ -352,6 +352,7 @@
 We will need a fresh new bug.
 
     >>> from lp.bugs.interfaces.bug import CreateBugParams
+    >>> from lp.registry.enums import InformationType
     >>> from lp.registry.interfaces.distribution import IDistributionSet
     >>> ubuntu = getUtility(IDistributionSet).getByName('ubuntu')
     >>> description = getUtility(IMessageSet).fromText(
@@ -418,7 +419,7 @@
     ...     sec_vuln_bug = ubuntu.createBug(CreateBugParams(
     ...         msg=sec_vuln_description, owner=sample_person,
     ...         title='Zero-day on Frobulator',
-    ...         security_related=True, private=True))
+    ...         information_type=InformationType.EMBARGOEDSECURITY))
 
     >>> sec_vuln_bug.security_related
     True

=== modified file 'lib/lp/bugs/doc/bugsubscription.txt'
--- lib/lp/bugs/doc/bugsubscription.txt	2012-03-13 00:45:33 +0000
+++ lib/lp/bugs/doc/bugsubscription.txt	2012-03-23 06:19:21 +0000
@@ -48,6 +48,7 @@
 
     >>> from lp.services.webapp.interfaces import ILaunchBag
     >>> from lp.bugs.interfaces.bug import CreateBugParams
+    >>> from lp.registry.enums import InformationType
     >>> from lp.registry.interfaces.distribution import IDistributionSet
     >>> from lp.registry.interfaces.person import IPersonSet
     >>> ubuntu = getUtility(IDistributionSet).getByName("ubuntu")
@@ -717,7 +718,7 @@
 
     >>> params = CreateBugParams(
     ...     title="a test bug", comment="a test description",
-    ...     owner=foobar, security_related=True, private=True)
+    ...     owner=foobar, information_type=InformationType.EMBARGOEDSECURITY)
     >>> new_bug = ubuntu.createBug(params)
 
     >>> getSubscribers(new_bug)
@@ -805,7 +806,7 @@
 
     >>> params = CreateBugParams(
     ...     title="a test bug", comment="a test description",
-    ...     owner=foobar, security_related=True, private=True)
+    ...     owner=foobar, information_type=InformationType.EMBARGOEDSECURITY)
     >>> new_bug = firefox.createBug(params)
 
     >>> getSubscribers(new_bug)
@@ -871,7 +872,7 @@
     >>> params = CreateBugParams(
     ...     title="yet another test bug",
     ...     comment="yet another test description",
-    ...     owner=foobar, security_related=True, private=True)
+    ...     owner=foobar, information_type=InformationType.EMBARGOEDSECURITY)
     >>> new_bug = evolution.createBug(params)
 
     >>> getSubscribers(new_bug)

=== modified file 'lib/lp/bugs/doc/security-teams.txt'
--- lib/lp/bugs/doc/security-teams.txt	2011-12-24 17:49:30 +0000
+++ lib/lp/bugs/doc/security-teams.txt	2012-03-23 06:19:21 +0000
@@ -37,19 +37,20 @@
     >>> print firefox.security_contact.name
     ubuntu-team
 
-When creating a bug, use the security_related flag to indicate that the
+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",
-    ...     security_related=True, private=True)
+    ...     information_type=InformationType.EMBARGOEDSECURITY)
     >>> bug = ubuntu.createBug(params)
 
     >>> bug.security_related
@@ -79,7 +80,7 @@
     ...     owner=getUtility(ILaunchBag).user,
     ...     title="a security bug",
     ...     comment="this is an example security bug",
-    ...     security_related=False)
+    ...     information_type=InformationType.PUBLIC)
     >>> bug = ubuntu.createBug(params)
 
     >>> bug.security_related
@@ -95,7 +96,7 @@
     ...     owner=getUtility(ILaunchBag).user,
     ...     title="another security bug",
     ...     comment="this is another security bug",
-    ...     security_related=True, private=True)
+    ...     information_type=InformationType.EMBARGOEDSECURITY)
     >>> bug = firefox.createBug(params)
 
     >>> bug.security_related
@@ -113,7 +114,7 @@
     ...     owner=getUtility(ILaunchBag).user,
     ...     title="another security bug",
     ...     comment="this is another security bug",
-    ...     security_related=False)
+    ...     information_type=InformationType.PUBLIC)
     >>> bug = firefox.createBug(params)
 
     >>> bug.security_related
@@ -134,7 +135,7 @@
     ...     owner=getUtility(ILaunchBag).user,
     ...     title="another security bug",
     ...     comment="this is another security bug",
-    ...     security_related=True, private=True)
+    ...     information_type=InformationType.EMBARGOEDSECURITY)
 
     >>> bug = firefox.createBug(params)
 
@@ -190,7 +191,7 @@
     ...     owner=getUtility(ILaunchBag).user,
     ...     title="another security bug",
     ...     comment="this is private security bug",
-    ...     private=True, security_related=True)
+    ...     information_type=InformationType.EMBARGOEDSECURITY)
     >>> bug = firefox.createBug(params)
 
     >>> bug.security_related

=== modified file 'lib/lp/bugs/interfaces/bug.py'
--- lib/lp/bugs/interfaces/bug.py	2012-03-21 01:17:50 +0000
+++ lib/lp/bugs/interfaces/bug.py	2012-03-23 06:19:21 +0000
@@ -100,9 +100,9 @@
 class CreateBugParams:
     """The parameters used to create a bug."""
 
-    def __init__(self, owner, title, comment=None, description=None, msg=None,
-                 status=None, datecreated=None, security_related=False,
-                 private=False, subscribers=(),
+    def __init__(self, owner, title, comment=None, description=None,
+                 msg=None, status=None, datecreated=None,
+                 information_type=InformationType.PUBLIC, subscribers=(),
                  tags=None, subscribe_owner=True, filed_by=None,
                  importance=None, milestone=None, assignee=None, cve=None):
         self.owner = owner
@@ -112,8 +112,7 @@
         self.msg = msg
         self.status = status
         self.datecreated = datecreated
-        self.security_related = security_related
-        self.private = private
+        self.information_type = information_type
         self.subscribers = subscribers
         self.product = None
         self.distribution = None
@@ -907,6 +906,13 @@
         Return (private_changed, security_related_changed) tuple.
         """
 
+    def transitionToInformationType(information_type, who):
+        """Set the information type for this bug.
+
+        :information_type: The `InformationType` to transition to.
+        :who: The `IPerson` who is making the change.
+        """
+
     @operation_parameters(
         submission=Reference(
             Interface, title=_('A HWDB submission'), required=True))

=== modified file 'lib/lp/bugs/mail/commands.py'
--- lib/lp/bugs/mail/commands.py	2012-01-01 02:58:52 +0000
+++ lib/lp/bugs/mail/commands.py	2012-03-23 06:19:21 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2011 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2012 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 __metaclass__ = type
@@ -46,6 +46,7 @@
     IllegalTarget,
     )
 from lp.bugs.interfaces.cve import ICveSet
+from lp.registry.enums import InformationType
 from lp.registry.interfaces.distribution import IDistribution
 from lp.registry.interfaces.distributionsourcepackage import (
     IDistributionSourcePackage,
@@ -184,10 +185,15 @@
                 stop_processing=True)
 
         if isinstance(context, CreateBugParams):
-            if context.security_related:
-                # BugSet.createBug() requires new security bugs to be private.
-                private = True
-            context.private = private
+            if private and (
+                context.information_type is InformationType.PUBLIC):
+                context.information_type = InformationType.USERDATA
+            elif (
+                context.information_type is
+                InformationType.EMBARGOEDSECURITY):
+                pass
+            else:
+                context.information_type = InformationType.PUBLIC
             return context, current_event
 
         # Snapshot.
@@ -240,10 +246,10 @@
                 stop_processing=True)
 
         if isinstance(context, CreateBugParams):
-            context.security_related = security_related
             if security_related:
-                # BugSet.createBug() requires new security bugs to be private.
-                context.private = True
+                context.information_type = InformationType.EMBARGOEDSECURITY
+            else:
+                context.information_type = InformationType.PUBLIC
             return context, current_event
 
         # Take a snapshot.

=== modified file 'lib/lp/bugs/mail/tests/test_commands.py'
--- lib/lp/bugs/mail/tests/test_commands.py	2012-01-01 02:58:52 +0000
+++ lib/lp/bugs/mail/tests/test_commands.py	2012-03-23 06:19:21 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2011 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2012 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 from lazr.lifecycle.interfaces import (
@@ -19,6 +19,7 @@
     TagEmailCommand,
     UnsubscribeEmailCommand,
     )
+from lp.registry.enums import InformationType
 from lp.services.mail.interfaces import (
     BugTargetNotFound,
     EmailProcessingError,
@@ -364,7 +365,8 @@
         dummy_event = object()
         params, event = command.execute(bug_params, dummy_event)
         self.assertEqual(bug_params, params)
-        self.assertEqual(True, bug_params.private)
+        self.assertEqual(
+            InformationType.USERDATA, bug_params.information_type)
         self.assertEqual(dummy_event, event)
 
     def test_execute_bug_params_with_security(self):
@@ -372,12 +374,14 @@
         user = self.factory.makePerson()
         login_person(user)
         bug_params = CreateBugParams(
-            title='bug title', owner=user, security_related='yes')
+            title='bug title', owner=user,
+            information_type=InformationType.EMBARGOEDSECURITY)
         command = PrivateEmailCommand('private', ['no'])
         dummy_event = object()
         params, event = command.execute(bug_params, dummy_event)
         self.assertEqual(bug_params, params)
-        self.assertEqual(True, bug_params.private)
+        self.assertEqual(
+            InformationType.EMBARGOEDSECURITY, bug_params.information_type)
         self.assertEqual(dummy_event, event)
 
 

=== modified file 'lib/lp/bugs/model/bug.py'
--- lib/lp/bugs/model/bug.py	2012-03-21 01:17:50 +0000
+++ lib/lp/bugs/model/bug.py	2012-03-23 06:19:21 +0000
@@ -89,6 +89,7 @@
     )
 from lp.app.interfaces.launchpad import ILaunchpadCelebrities
 from lp.app.validators import LaunchpadValidationError
+from lp.bugs.adapters.bug import convert_to_information_type
 from lp.bugs.adapters.bugchange import (
     BranchLinkedToBug,
     BranchUnlinkedFromBug,
@@ -234,11 +235,10 @@
     return Snapshot(
         bug_params, names=[
             "owner", "title", "comment", "description", "msg",
-            "datecreated", "security_related", "private",
-            "distribution", "sourcepackagename",
-            "product", "status", "subscribers", "tags",
-            "subscribe_owner", "filed_by", "importance",
-            "milestone", "assignee", "cve"])
+            "datecreated", "information_type", "distribution",
+            "sourcepackagename", "product", "status", "subscribers", "tags",
+            "subscribe_owner", "filed_by", "importance", "milestone",
+            "assignee", "cve"])
 
 
 class BugTag(SQLBase):
@@ -351,15 +351,12 @@
         dbName='duplicateof', foreignKey='Bug', default=None)
     datecreated = UtcDateTimeCol(notNull=True, default=UTC_NOW)
     date_last_updated = UtcDateTimeCol(notNull=True, default=UTC_NOW)
-    _private = BoolCol(dbName='private', notNull=True, default=False)
     date_made_private = UtcDateTimeCol(notNull=False, default=None)
     who_made_private = ForeignKey(
         dbName='who_made_private', foreignKey='Person',
         storm_validator=validate_public_person, default=None)
-    _security_related = BoolCol(
-        dbName='security_related', notNull=True, default=False)
     information_type = EnumCol(
-        enum=InformationType, default=InformationType.PUBLIC)
+        enum=InformationType, notNull=True, default=InformationType.PUBLIC)
 
     # useful Joins
     activity = SQLMultipleJoin('BugActivity', joinColumn='bug', orderBy='id')
@@ -396,17 +393,11 @@
 
     @property
     def private(self):
-        if self.information_type:
-            return self.information_type in PRIVATE_INFORMATION_TYPES
-        else:
-            return self._private
+        return self.information_type in PRIVATE_INFORMATION_TYPES
 
     @property
     def security_related(self):
-        if self.information_type:
-            return self.information_type in SECURITY_INFORMATION_TYPES
-        else:
-            return self._security_related
+        return self.information_type in SECURITY_INFORMATION_TYPES
 
     @cachedproperty
     def _subscriber_cache(self):
@@ -1716,102 +1707,11 @@
 
         return bugtask
 
-    def _setInformationType(self):
-        if self._private and self._security_related:
-            self.information_type = InformationType.EMBARGOEDSECURITY
-        elif self._private:
-            self.information_type = InformationType.USERDATA
-        elif self._security_related:
-            self.information_type = InformationType.UNEMBARGOEDSECURITY
-        else:
-            self.information_type = InformationType.PUBLIC
-
     def setPrivacyAndSecurityRelated(self, private, security_related, who):
         """ See `IBug`."""
-        private_changed = False
-        security_related_changed = False
-        bug_before_modification = Snapshot(self, providing=providedBy(self))
-
-        f_flag_str = 'disclosure.enhanced_private_bug_subscriptions.enabled'
-        f_flag = bool(getFeatureFlag(f_flag_str))
-        if f_flag:
-            # Before we update the privacy or security_related status, we
-            # need to reconcile the subscribers to avoid leaking private
-            # information.
-            if (self.private != private
-                    or self.security_related != security_related):
-                self.reconcileSubscribers(private, security_related, who)
-
-        if self.private != private:
-            # We do not allow multi-pillar private bugs except for those teams
-            # who want to shoot themselves in the foot.
-            if private:
-                allow_multi_pillar_private = bool(getFeatureFlag(
-                    'disclosure.allow_multipillar_private_bugs.enabled'))
-                if (not allow_multi_pillar_private
-                        and len(self.affected_pillars) > 1):
-                    raise BugCannotBePrivate(
-                        "Multi-pillar bugs cannot be private.")
-            private_changed = True
-            self._private = private
-
-            if private:
-                self.who_made_private = who
-                self.date_made_private = UTC_NOW
-            else:
-                self.who_made_private = None
-                self.date_made_private = None
-
-            # XXX: This should be a bulk update. RBC 20100827
-            # bug=https://bugs.launchpad.net/storm/+bug/625071
-            for attachment in self.attachments_unpopulated:
-                attachment.libraryfile.restricted = private
-
-        if self.security_related != security_related:
-            security_related_changed = True
-            self._security_related = security_related
-
-        if private_changed or security_related_changed:
-            # Correct the heat for the bug immediately, so that we don't have
-            # to wait for the next calculation job for the adjusted heat.
-            self.updateHeat()
-
-        self._setInformationType()
-
-        if private_changed or security_related_changed:
-            changed_fields = []
-
-            if private_changed:
-                changed_fields.append('private')
-                if not f_flag and private:
-                    # If we didn't call reconcileSubscribers, we may have
-                    # bug supervisors who should be on this bug, but aren't.
-                    supervisors = set()
-                    for bugtask in self.bugtasks:
-                        supervisors.add(bugtask.pillar.bug_supervisor)
-                    if None in supervisors:
-                        supervisors.remove(None)
-                    for s in supervisors:
-                        subscriptions = get_structural_subscriptions_for_bug(
-                                            self, s)
-                        if subscriptions != []:
-                            self.subscribe(s, who)
-
-            if security_related_changed:
-                changed_fields.append('security_related')
-                if not f_flag and security_related:
-                    # The bug turned out to be security-related, subscribe the
-                    # security contact. We do it here only if the feature flag
-                    # is not set, otherwise it's done in
-                    # reconcileSubscribers().
-                    for pillar in self.affected_pillars:
-                        if pillar.security_contact is not None:
-                            self.subscribe(pillar.security_contact, who)
-
-            notify(ObjectModifiedEvent(
-                    self, bug_before_modification, changed_fields, user=who))
-
-        return private_changed, security_related_changed
+        ret = self.transitionToInformationType(
+            convert_to_information_type(private, security_related), who)
+        return (ret, ret)
 
     def setPrivate(self, private, who):
         """See `IBug`.
@@ -1827,7 +1727,58 @@
         return self.setPrivacyAndSecurityRelated(
             self.private, security_related, who)[1]
 
-    def getRequiredSubscribers(self, for_private, for_security_related, who):
+    def transitionToInformationType(self, information_type, who):
+        """See `IBug`."""
+        bug_before_modification = Snapshot(self, providing=providedBy(self))
+        if self.information_type is information_type:
+            return False
+        f_flag_str = 'disclosure.enhanced_private_bug_subscriptions.enabled'
+        f_flag = bool(getFeatureFlag(f_flag_str))
+        if f_flag:
+            self.reconcileSubscribers(information_type, who)
+        if information_type in PRIVATE_INFORMATION_TYPES:
+            allow_multi_pillar_private = bool(getFeatureFlag(
+                'disclosure.allow_multipillar_private_bugs.enabled'))
+            if (not allow_multi_pillar_private
+                    and len(self.affected_pillars) > 1):
+                raise BugCannotBePrivate(
+                    "Multi-pillar bugs cannot be private.")
+            self.who_made_private = who
+            self.date_made_private = UTC_NOW
+        else:
+            self.who_made_private = None
+            self.date_made_private = None
+        # XXX: This should be a bulk update. RBC 20100827
+        # bug=https://bugs.launchpad.net/storm/+bug/625071
+        for attachment in self.attachments_unpopulated:
+            attachment.libraryfile.restricted = (
+                information_type in PRIVATE_INFORMATION_TYPES)
+        self.updateHeat()
+        if not f_flag and information_type is InformationType.USERDATA:
+            # If we didn't call reconcileSubscribers(), we may have
+            # bug supervisors who should be on this bug, but aren't.
+            supervisors = set()
+            for bugtask in self.bugtasks:
+                supervisors.add(bugtask.pillar.bug_supervisor)
+            if None in supervisors:
+                supervisors.remove(None)
+            for s in supervisors:
+                if not get_structural_subscriptions_for_bug(self, s):
+                    self.subscribe(s, who)
+        if not f_flag and information_type in SECURITY_INFORMATION_TYPES:
+            # The bug turned out to be security-related, subscribe the
+            # security contact. We do it here only if the feature flag
+            # is not set, otherwise it's done in
+            # reconcileSubscribers().
+            for pillar in self.affected_pillars:
+                if pillar.security_contact is not None:
+                    self.subscribe(pillar.security_contact, who)
+        self.information_type = information_type
+        notify(ObjectModifiedEvent(
+                self, bug_before_modification, [information_type], user=who))
+        return True
+
+    def getRequiredSubscribers(self, information_type, who):
         """Return the mandatory subscribers for a bug with given attributes.
 
         When a bug is marked as private or security related, it is required
@@ -1843,23 +1794,23 @@
         If bug supervisor or security contact is unset, fallback to bugtask
         reporter/owner.
         """
-        if not for_private and not for_security_related:
+        if information_type is InformationType.PUBLIC:
             return set()
         result = set()
         result.add(self.owner)
         for bugtask in self.bugtasks:
             maintainer = bugtask.pillar.owner
-            if for_security_related:
+            if information_type in SECURITY_INFORMATION_TYPES:
                 result.add(bugtask.pillar.security_contact or maintainer)
-            if for_private:
+            if information_type in PRIVATE_INFORMATION_TYPES:
                 result.add(bugtask.pillar.bug_supervisor or maintainer)
-        if for_private:
+        if information_type in PRIVATE_INFORMATION_TYPES:
             subscribers_for_who = self.getSubscribersForPerson(who)
             if subscribers_for_who.is_empty():
                 result.add(who)
         return result
 
-    def getAutoRemovedSubscribers(self, for_private, for_security_related):
+    def getAutoRemovedSubscribers(self, information_type):
         """Return the to be removed subscribers for bug with given attributes.
 
         When a bug's privacy or security related attributes change, some
@@ -1873,6 +1824,8 @@
         """
         bug_supervisors = []
         security_contacts = []
+        for_security_related = information_type in SECURITY_INFORMATION_TYPES
+        for_private = information_type in PRIVATE_INFORMATION_TYPES
         for pillar in self.affected_pillars:
             if (self.security_related and not for_security_related
                 and pillar.security_contact):
@@ -1882,7 +1835,7 @@
                     bug_supervisors.append(pillar.bug_supervisor)
         return bug_supervisors, security_contacts
 
-    def reconcileSubscribers(self, for_private, for_security_related, who):
+    def reconcileSubscribers(self, information_type, who):
         """ Ensure only appropriate people are subscribed to private bugs.
 
         When a bug is marked as either private = True or security_related =
@@ -1902,9 +1855,9 @@
         current_direct_subscribers = (
             self.getSubscriptionInfo().direct_subscribers)
         required_subscribers = self.getRequiredSubscribers(
-            for_private, for_security_related, who)
+            information_type, who)
         removed_bug_supervisors, removed_security_contacts = (
-            self.getAutoRemovedSubscribers(for_private, for_security_related))
+            self.getAutoRemovedSubscribers(information_type))
         for subscriber in removed_bug_supervisors:
             recipients = BugNotificationRecipients()
             recipients.addBugSupervisor(subscriber)
@@ -1937,7 +1890,8 @@
         # unsubscribe any unauthorised direct subscribers.
         pillar = self.default_bugtask.pillar
         private_project = IProduct.providedBy(pillar) and pillar.private_bugs
-        if private_project and (for_private or for_security_related):
+        privleged_info = information_type is not InformationType.PUBLIC
+        if private_project and privleged_info:
             allowed_subscribers = set()
             allowed_subscribers.add(self.owner)
             for bugtask in self.bugtasks:
@@ -2821,13 +2775,12 @@
         if params.product and params.product.private_bugs:
             # If the private_bugs flag is set on a product, then
             # force the new bug report to be private.
-            params.private = True
+            if params.information_type is InformationType.PUBLIC:
+                params.information_type = InformationType.USERDATA
 
         bug, event = self.createBugWithoutTarget(params)
 
-        if params.security_related:
-            assert params.private, (
-                "A security related bug should always be private by default.")
+        if params.information_type in SECURITY_INFORMATION_TYPES:
             if params.product:
                 context = params.product
             else:
@@ -2874,8 +2827,6 @@
         if notify_event:
             notify(event)
 
-        bug._setInformationType()
-
         # Calculate the bug's initial heat.
         bug.updateHeat()
 
@@ -2915,7 +2866,7 @@
             params.description = params.msg.text_contents
 
         extra_params = {}
-        if params.private:
+        if params.information_type in PRIVATE_INFORMATION_TYPES:
             # We add some auditing information. After bug creation
             # time these attributes are updated by Bug.setPrivate().
             extra_params.update(
@@ -2924,9 +2875,8 @@
 
         bug = Bug(
             title=params.title, description=params.description,
-            _private=params.private, owner=params.owner,
-            datecreated=params.datecreated,
-            _security_related=params.security_related,
+            owner=params.owner, datecreated=params.datecreated,
+            information_type=params.information_type,
             **extra_params)
 
         if params.subscribe_owner:

=== modified file 'lib/lp/bugs/model/bugtask.py'
--- lib/lp/bugs/model/bugtask.py	2012-03-21 17:31:45 +0000
+++ lib/lp/bugs/model/bugtask.py	2012-03-23 06:19:21 +0000
@@ -1698,7 +1698,8 @@
     def getStatusCountsForProductSeries(self, user, product_series):
         """See `IBugTaskSet`."""
         if user is None:
-            bug_privacy_filter = 'AND Bug.private IS FALSE'
+            # 1 = PUBLIC, 2 = UNEMBARGOEDSECURITY.
+            bug_privacy_filter = 'AND Bug.information_type IN (1, 2)'
         else:
             # Since the count won't reveal sensitive information, and
             # since the get_bug_privacy_filter() check for non-admins is

=== modified file 'lib/lp/bugs/model/bugtasksearch.py'
--- lib/lp/bugs/model/bugtasksearch.py	2012-03-20 09:43:46 +0000
+++ lib/lp/bugs/model/bugtasksearch.py	2012-03-23 06:19:21 +0000
@@ -1413,14 +1413,15 @@
         returns BugTask objects.
     """
     if user is None:
-        return "Bug.private IS FALSE", _nocache_bug_decorator
+        return "Bug.information_type IN (1, 2)", _nocache_bug_decorator
     admin_team = getUtility(ILaunchpadCelebrities).admin
     if user.inTeam(admin_team):
         return "", _nocache_bug_decorator
 
     public_bug_filter = ''
     if not private_only:
-        public_bug_filter = 'Bug.private IS FALSE OR'
+        # 1 == PUBLIC, 2 == UNEMBARGOEDSECURITY
+        public_bug_filter = 'Bug.information_type IN (1, 2) OR'
 
     # A subselect is used here because joining through
     # TeamParticipation is only relevant to the "user-aware"

=== modified file 'lib/lp/bugs/model/tests/test_bugtask.py'
--- lib/lp/bugs/model/tests/test_bugtask.py	2012-03-08 12:03:22 +0000
+++ lib/lp/bugs/model/tests/test_bugtask.py	2012-03-23 06:19:21 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2011 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2012 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 __metaclass__ = type
@@ -922,9 +922,9 @@
         # clause if the specified user is None, regardless of the value of the
         # private_only parameter.
         filter = get_bug_privacy_filter(None)
-        self.assertIn('Bug.private IS FALSE', filter)
+        self.assertIn('Bug.information_type IN (1, 2)', filter)
         filter = get_bug_privacy_filter(None, private_only=True)
-        self.assertIn('Bug.private IS FALSE', filter)
+        self.assertIn('Bug.information_type IN (1, 2)', filter)
 
     def test_bug_privacy_filter_private_only_param_with_user(self):
         # The bug privacy filter expression omits has the "private is false"
@@ -932,9 +932,9 @@
         # specified.
         any_user = self.factory.makePerson()
         filter = get_bug_privacy_filter(any_user)
-        self.assertIn('Bug.private IS FALSE', filter)
+        self.assertIn('Bug.information_type IN (1, 2)', filter)
         filter = get_bug_privacy_filter(any_user, private_only=True)
-        self.assertNotIn('Bug.private IS FALSE', filter)
+        self.assertNotIn('Bug.information_type IN (1, 2)', filter)
 
     def test_no_tasks(self):
         # A brand new bug target has no tasks.

=== modified file 'lib/lp/bugs/scripts/bugimport.py'
--- lib/lp/bugs/scripts/bugimport.py	2012-03-22 23:21:24 +0000
+++ lib/lp/bugs/scripts/bugimport.py	2012-03-23 06:19:21 +0000
@@ -40,10 +40,15 @@
 from lp.app.interfaces.launchpad import ILaunchpadCelebrities
 from lp.services.librarian.interfaces import ILibraryFileAliasSet
 from lp.services.messages.interfaces.message import IMessageSet
+<<<<<<< TREE
 from lp.bugs.interfaces.bug import (
     CreateBugParams,
     IBugSet,
     )
+=======
+from lp.bugs.adapters.bug import convert_to_information_type
+from lp.bugs.interfaces.bug import CreateBugParams, IBugSet
+>>>>>>> MERGE-SOURCE
 from lp.bugs.interfaces.bugactivity import IBugActivitySet
 from lp.bugs.interfaces.bugattachment import (
     BugAttachmentType,
@@ -295,6 +300,8 @@
         # If the product has private_bugs, we force private to True.
         if self.product.private_bugs:
             private = True
+        information_type = convert_to_information_type(
+            private, security_related)
 
         if owner is None:
             owner = self.bug_importer
@@ -302,12 +309,8 @@
         msg = self.createMessage(commentnode, defaulttitle=title)
 
         bug = self.product.createBug(CreateBugParams(
-            msg=msg,
-            datecreated=datecreated,
-            title=title,
-            private=private or security_related,
-            security_related=security_related,
-            owner=owner))
+            msg=msg, datecreated=datecreated, title=title,
+            information_type=information_type, owner=owner))
         bugtask = bug.bugtasks[0]
         self.logger.info('Creating Launchpad bug #%d', bug.id)
 

=== modified file 'lib/lp/bugs/stories/bugs/xx-bug-activity.txt'
--- lib/lp/bugs/stories/bugs/xx-bug-activity.txt	2011-12-02 17:44:47 +0000
+++ lib/lp/bugs/stories/bugs/xx-bug-activity.txt	2012-03-23 06:19:21 +0000
@@ -123,6 +123,12 @@
     public => private
     --------
 
+    >>> admin_browser.open(
+    ...     'http://bugs.launchpad.dev/redfish/+bug/15/+secrecy')
+    >>> admin_browser.getControl(
+    ...     "This bug report should be private").selected = False
+    >>> admin_browser.getControl("Change").click()
+
 Clean up the feature flag.
     >>> flags.cleanUp()
 

=== modified file 'lib/lp/bugs/xmlrpc/bug.py'
--- lib/lp/bugs/xmlrpc/bug.py	2012-01-01 02:58:52 +0000
+++ lib/lp/bugs/xmlrpc/bug.py	2012-03-23 06:19:21 +0000
@@ -1,10 +1,13 @@
-# Copyright 2009-2011 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2012 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """XML-RPC APIs for Malone."""
 
 __metaclass__ = type
-__all__ = ["FileBugAPI", "ExternalBugTrackerTokenAPI"]
+__all__ = [
+    "ExternalBugTrackerTokenAPI",
+    "FileBugAPI",
+    ]
 
 from zope.component import getUtility
 from zope.interface import implements
@@ -12,6 +15,7 @@
 from lp.app.errors import NotFoundError
 from lp.bugs.interfaces.bug import CreateBugParams
 from lp.bugs.interfaces.externalbugtracker import IExternalBugTrackerTokenAPI
+from lp.registry.enums import InformationType
 from lp.registry.interfaces.distribution import IDistributionSet
 from lp.registry.interfaces.person import IPersonSet
 from lp.registry.interfaces.product import IProductSet
@@ -100,14 +104,14 @@
                 else:
                     subscriber_list.append(subscriber)
 
-        security_related = bool(security_related)
-
-        # Privacy is always set the same as security, by default.
-        private = security_related
+        if security_related:
+            information_type = InformationType.EMBARGOEDSECURITY
+        else:
+            information_type = InformationType.PUBLIC
 
         params = CreateBugParams(
             owner=self.user, title=summary, comment=comment,
-            security_related=security_related, private=private,
+            information_type=information_type,
             subscribers=subscriber_list)
 
         bug = target.createBug(params)

=== modified file 'lib/lp/systemhomes.py'
--- lib/lp/systemhomes.py	2011-12-30 07:32:58 +0000
+++ lib/lp/systemhomes.py	2012-03-23 06:19:21 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2011 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2012 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Content classes for the 'home pages' of the subsystems of Launchpad."""
@@ -24,6 +24,7 @@
 from zope.component import getUtility
 from zope.interface import implements
 
+from lp.bugs.adapters.bug import convert_to_information_type
 from lp.bugs.errors import InvalidBugTargetType
 from lp.bugs.interfaces.bug import (
     CreateBugParams,
@@ -126,9 +127,11 @@
     def createBug(self, owner, title, description, target,
                   security_related=False, private=False, tags=None):
         """See `IMaloneApplication`."""
+        information_type = convert_to_information_type(
+            private, security_related)
         params = CreateBugParams(
             title=title, comment=description, owner=owner,
-            security_related=security_related, private=private, tags=tags)
+            information_type=information_type, tags=tags)
         if IProduct.providedBy(target):
             params.setBugTarget(product=target)
         elif IDistribution.providedBy(target):

=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py	2012-03-15 14:35:58 +0000
+++ lib/lp/testing/factory.py	2012-03-23 06:19:21 +0000
@@ -78,6 +78,7 @@
     )
 from lp.blueprints.interfaces.specification import ISpecificationSet
 from lp.blueprints.interfaces.sprint import ISprintSet
+from lp.bugs.adapters.bug import convert_to_information_type
 from lp.bugs.interfaces.bug import (
     CreateBugParams,
     IBugSet,
@@ -1689,9 +1690,11 @@
             self.makeSourcePackagePublishingHistory(
                 distroseries=distribution.currentseries,
                 sourcepackagename=sourcepackagename)
+        # Factory changes delayed for a seperate branch.
+        information_type = convert_to_information_type(
+            private, security_related)
         create_bug_params = CreateBugParams(
-            owner, title, comment=comment, private=private,
-            security_related=security_related,
+            owner, title, comment=comment, information_type=information_type,
             datecreated=date_created, description=description,
             status=status, tags=tags)
         create_bug_params.setBugTarget(