← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~abentley/launchpad/hide-sprint-blueprints into lp:launchpad

 

Aaron Bentley has proposed merging lp:~abentley/launchpad/hide-sprint-blueprints into lp:launchpad with lp:~abentley/launchpad/specification-cleanup as a prerequisite.

Commit message:
Respect privacy listing specifications for sprints.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)
Related bugs:
  Bug #1051029 in Launchpad itself: "PROPRIETARY specifications break meeting listings"
  https://bugs.launchpad.net/launchpad/+bug/1051029

For more details, see:
https://code.launchpad.net/~abentley/launchpad/hide-sprint-blueprints/+merge/126792

= Summary =
Fix bug #1051029: Fix meeting listings with PROPRIETARY specs

== Proposed fix ==
Sprint.specification filters un-viewable specs out of listings

== Pre-implementation notes ==
None

== LOC Rationale ==
Part of private projects

== Implementation details ==
IHasSpecifications.specifications() and all implementations accept a user as a mandatory parameter (but that user may be None if no Person is logged in).

Sprint.specifications uses the user to invoke get_specification_privacy_filter()

All callers are updated to supply a user.  View methods supply self.user, and model methods accept an additional parameter.  _all_specifications and _valid_specifications, as model properties, cannot accept parameters, so they use ILaunchBag.user.

== Tests ==
Everything

== Demo and Q/A ==
Create a PROPRIETARY blueprint.  Link it to a sprint.  Go to the sprint page.  You should see the blueprint.  Log out.  It should stop being listed, but the page should not be broken.


= Launchpad lint =

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/blueprints/templates/specifications-portlet-latestregistered.pt
  lib/lp/blueprints/tests/test_hasspecifications.py
  lib/lp/services/database/stormexpr.py
  lib/lp/registry/doc/milestone.txt
  lib/lp/registry/model/projectgroup.py
  lib/lp/registry/doc/distribution.txt
  lib/lp/registry/model/productseries.py
  lib/lp/blueprints/interfaces/specificationtarget.py
  lib/lp/app/browser/root.py
  lib/lp/blueprints/templates/specificationtarget-assignments.pt
  lib/lp/blueprints/browser/specificationtarget.py
  lib/lp/blueprints/model/tests/test_sprint.py
  lib/lp/bugs/model/tests/test_bugtask.py
  lib/lp/blueprints/templates/person-specworkload.pt
  lib/lp/blueprints/browser/sprint.py
  lib/lp/blueprints/templates/specifications-portlet-stats.pt
  lib/lp/testing/factory.py
  lib/lp/registry/doc/projectgroup.txt
  lib/lp/blueprints/model/specification.py
  lib/lp/registry/browser/__init__.py
  lib/lp/blueprints/model/sprint.py
  lib/lp/blueprints/templates/specifications-portlet-latestcompleted.pt
  lib/lp/testing/_webservice.py
  lib/lp/registry/model/product.py
  lib/lp/blueprints/browser/configure.zcml
  lib/lp/blueprints/browser/tests/test_sprint.py
  lib/lp/blueprints/doc/specification.txt
  lib/lp/blueprints/browser/specification.py
  lib/lp/registry/browser/person.py
  lib/lp/registry/model/person.py
  lib/lp/registry/model/distroseries.py
  lib/lp/_schema_circular_imports.py
  lib/lp/registry/model/distribution.py
  lib/lp/blueprints/interfaces/specification.py

./lib/lp/registry/model/product.py
     408: redefinition of function 'date_next_suggest_packaging' from line 400

^^^ This is an acceptable case of re-definition. It's creating a setter.
-- 
https://code.launchpad.net/~abentley/launchpad/hide-sprint-blueprints/+merge/126792
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~abentley/launchpad/hide-sprint-blueprints into lp:launchpad.
=== modified file 'lib/lp/app/browser/root.py'
--- lib/lp/app/browser/root.py	2012-09-27 20:24:22 +0000
+++ lib/lp/app/browser/root.py	2012-09-27 20:24:22 +0000
@@ -130,7 +130,7 @@
     @property
     def blueprint_count(self):
         """The total blueprint count in all of Launchpad."""
-        return getUtility(ISpecificationSet).specificationCount()
+        return getUtility(ISpecificationSet).specificationCount(self.user)
 
     @property
     def answer_count(self):

=== modified file 'lib/lp/blueprints/browser/specification.py'
--- lib/lp/blueprints/browser/specification.py	2012-09-27 20:24:22 +0000
+++ lib/lp/blueprints/browser/specification.py	2012-09-27 20:24:22 +0000
@@ -1532,17 +1532,17 @@
     @property
     def latest_specifications(self):
         return self.context.specifications(
-            sort=SpecificationSort.DATE, quantity=5)
+            self.user, sort=SpecificationSort.DATE, quantity=5)
 
     @property
     def latest_completed_specifications(self):
         return self.context.specifications(
-            sort=SpecificationSort.DATE, quantity=5,
+            self.user, sort=SpecificationSort.DATE, quantity=5,
             filter=[SpecificationFilter.COMPLETE])
 
     @property
     def specification_count(self):
-        return self.context.specificationCount()
+        return self.context.specificationCount(self.user)
 
     @safe_action
     @action('Find blueprints', name="search")

=== modified file 'lib/lp/blueprints/browser/specificationtarget.py'
--- lib/lp/blueprints/browser/specificationtarget.py	2012-09-27 20:24:22 +0000
+++ lib/lp/blueprints/browser/specificationtarget.py	2012-09-27 20:24:22 +0000
@@ -259,11 +259,11 @@
 
     @cachedproperty
     def has_any_specifications(self):
-        return self.context._all_specifications.count() != 0
+        return not self.context._all_specifications.is_empty()
 
     @cachedproperty
     def all_specifications(self):
-        return shortlist(self.context.all_specifications)
+        return shortlist(self.context.all_specifications(self.user))
 
     @cachedproperty
     def searchrequested(self):
@@ -348,7 +348,7 @@
                 and not check_permission('launchpad.View', self.context)):
             return []
         filter = self.spec_filter
-        return self.context.specifications(filter=filter)
+        return self.context.specifications(self.user, filter=filter)
 
     @cachedproperty
     def specs_batched(self):
@@ -364,7 +364,7 @@
     def documentation(self):
         filter = [SpecificationFilter.COMPLETE,
                   SpecificationFilter.INFORMATIONAL]
-        return shortlist(self.context.specifications(filter=filter))
+        return shortlist(self.context.specifications(self.user, filter=filter))
 
     @cachedproperty
     def categories(self):
@@ -405,8 +405,9 @@
         Only ACCEPTED specifications are returned.  This list is used by the
         +portlet-latestspecs view.
         """
-        return self.context.specifications(sort=SpecificationSort.DATE,
-            quantity=quantity, prejoin_people=False)
+        return self.context.specifications(self.user,
+            sort=SpecificationSort.DATE, quantity=quantity,
+            prejoin_people=False)
 
 
 class SpecificationAssignmentsView(HasSpecificationsView):

=== modified file 'lib/lp/blueprints/browser/sprint.py'
--- lib/lp/blueprints/browser/sprint.py	2012-01-01 02:58:52 +0000
+++ lib/lp/blueprints/browser/sprint.py	2012-09-27 20:24:22 +0000
@@ -216,7 +216,7 @@
     @cachedproperty
     def latest_approved(self):
         filter = [SpecificationFilter.ACCEPTED]
-        return self.context.specifications(filter=filter,
+        return self.context.specifications(self.user, filter=filter,
                     quantity=self.latest_specs_limit,
                     sort=SpecificationSort.DATE)
 
@@ -464,7 +464,7 @@
 
         model_specs = []
         for spec in self.context.specifications(
-            filter=[SpecificationFilter.ACCEPTED]):
+            self.user, filter=[SpecificationFilter.ACCEPTED]):
 
             # skip sprints with no priority or less than low:
             if spec.priority < SpecificationPriority.UNDEFINED:

=== modified file 'lib/lp/blueprints/browser/tests/test_sprint.py'
--- lib/lp/blueprints/browser/tests/test_sprint.py	2012-09-27 20:24:22 +0000
+++ lib/lp/blueprints/browser/tests/test_sprint.py	2012-09-27 20:24:22 +0000
@@ -8,6 +8,7 @@
 from storm.locals import Store
 from testtools.matchers import Equals
 
+from lp.app.enums import InformationType
 from lp.testing import BrowserTestCase
 from lp.testing.layers import DatabaseFunctionalLayer
 from lp.testing.matchers import BrowsesWithQueryLimit, HasQueryCount
@@ -40,3 +41,15 @@
         with QueryCollector() as recorder:
             self.getViewBrowser(sprint)
         self.assertThat(recorder, HasQueryCount(Equals(30)))
+
+    def test_proprietary_blueprint_listing_query_count(self):
+        """Set a maximum number of queries for sprint blueprint lists."""
+        sprint = self.factory.makeSprint()
+        for count in range(10):
+            blueprint = self.factory.makeSpecification(
+                information_type=InformationType.PROPRIETARY)
+            link = blueprint.linkSprint(sprint, blueprint.owner)
+            link.acceptBy(sprint.owner)
+        with QueryCollector() as recorder:
+            self.getViewBrowser(sprint)
+        self.assertThat(recorder, HasQueryCount(Equals(22)))

=== modified file 'lib/lp/blueprints/doc/specification.txt'
--- lib/lp/blueprints/doc/specification.txt	2012-09-27 20:24:22 +0000
+++ lib/lp/blueprints/doc/specification.txt	2012-09-27 20:24:22 +0000
@@ -222,7 +222,7 @@
 We can filter for specifications that contain specific text, across all
 specifications:
 
-    >>> for spec in specset.specifications(filter=['install']):
+    >>> for spec in specset.specifications(None, filter=['install']):
     ...     print spec.name, spec.target.name
     cluster-installation kubuntu
     extension-manager-upgrades firefox
@@ -239,7 +239,7 @@
     >>> unlink_source_packages(upstream_firefox)
     >>> upstream_firefox.active = False
     >>> flush_database_updates()
-    >>> for spec in specset.specifications(filter=['install']):
+    >>> for spec in specset.specifications(None, filter=['install']):
     ...     print spec.name, spec.target.name
     cluster-installation kubuntu
     media-integrity-check ubuntu

=== modified file 'lib/lp/blueprints/interfaces/specification.py'
--- lib/lp/blueprints/interfaces/specification.py	2012-09-27 20:24:22 +0000
+++ lib/lp/blueprints/interfaces/specification.py	2012-09-27 20:24:22 +0000
@@ -674,7 +674,7 @@
 
     coming_sprints = Attribute("The next 5 sprints in the system.")
 
-    def specificationCount():
+    def specificationCount(user):
         """The total number of blueprints in Launchpad"""
 
     def getStatusCountsForProductSeries(product_series):

=== modified file 'lib/lp/blueprints/interfaces/specificationtarget.py'
--- lib/lp/blueprints/interfaces/specificationtarget.py	2012-09-27 20:24:22 +0000
+++ lib/lp/blueprints/interfaces/specificationtarget.py	2012-09-27 20:24:22 +0000
@@ -60,10 +60,11 @@
                 'have not been accepted for that goal'))),
         exported_as="valid_specifications", as_of="devel")
 
-    def specifications(quantity=None, sort=None, filter=None,
+    def specifications(user, quantity=None, sort=None, filter=None,
                        prejoin_people=True):
         """Specifications for this target.
 
+        The user specifies which user to use for calculation of visibility.
         The sort is a dbschema which indicates the preferred sort order. The
         filter is an indicator of the kinds of specs to be returned, and
         appropriate filters depend on the kind of object this method is on.

=== modified file 'lib/lp/blueprints/model/specification.py'
--- lib/lp/blueprints/model/specification.py	2012-09-27 20:24:22 +0000
+++ lib/lp/blueprints/model/specification.py	2012-09-27 20:24:22 +0000
@@ -109,6 +109,7 @@
     cachedproperty,
     get_property_cache,
     )
+from lp.services.webapp.interfaces import ILaunchBag
 
 
 def recursive_blocked_query(spec):
@@ -957,7 +958,7 @@
     for other classes that have specifications.
     """
 
-    def specifications(self, sort=None, quantity=None, filter=None,
+    def specifications(self, user, sort=None, quantity=None, filter=None,
                        prejoin_people=True):
         """See IHasSpecifications."""
         # this should be implemented by the actual context class
@@ -1024,16 +1025,19 @@
     @property
     def _all_specifications(self):
         """See IHasSpecifications."""
-        return self.specifications(filter=[SpecificationFilter.ALL])
+        user = getUtility(ILaunchBag).user
+        return self.specifications(user, filter=[SpecificationFilter.ALL])
 
     @property
     def _valid_specifications(self):
         """See IHasSpecifications."""
-        return self.specifications(filter=[SpecificationFilter.VALID])
+        user = getUtility(ILaunchBag).user
+        return self.specifications(user, filter=[SpecificationFilter.VALID])
 
-    def specificationCount(self):
+    def specificationCount(self, user):
         """See IHasSpecifications."""
-        return self.specifications(filter=[SpecificationFilter.ALL]).count()
+        return self.specifications(user,
+                                   filter=[SpecificationFilter.ALL]).count()
 
 
 class SpecificationSet(HasSpecificationsMixin):
@@ -1072,7 +1076,7 @@
         """See ISpecificationSet."""
         return iter(self.all_specifications)
 
-    def specifications(self, sort=None, quantity=None, filter=None,
+    def specifications(self, user, sort=None, quantity=None, filter=None,
                        prejoin_people=True):
         """See IHasSpecifications."""
 

=== modified file 'lib/lp/blueprints/model/sprint.py'
--- lib/lp/blueprints/model/sprint.py	2012-09-27 20:24:22 +0000
+++ lib/lp/blueprints/model/sprint.py	2012-09-27 20:24:22 +0000
@@ -41,7 +41,10 @@
     ISprint,
     ISprintSet,
     )
-from lp.blueprints.model.specification import HasSpecificationsMixin
+from lp.blueprints.model.specification import (
+    get_specification_privacy_filter,
+    HasSpecificationsMixin,
+    )
 from lp.blueprints.model.sprintattendance import SprintAttendance
 from lp.blueprints.model.sprintspecification import SprintSpecification
 from lp.registry.interfaces.person import (
@@ -111,7 +114,7 @@
         # Only really used in tests.
         return [a.attendee for a in self.attendances]
 
-    def spec_filter_clause(self, filter=None):
+    def spec_filter_clause(self, user, filter=None):
         """Figure out the appropriate query for specifications on a sprint.
 
         We separate out the query generation from the normal
@@ -126,6 +129,7 @@
                  Or(Specification.product == None,
                     Not(Specification.productID.is_in(Select(Product.id,
                         Product.active == False))))]
+        query.append(get_specification_privacy_filter(user))
         if not filter:
             # filter could be None or [] then we decide the default
             # which for a sprint is to show everything approved
@@ -169,7 +173,10 @@
                 query.append(fti_search(Specification, constraint))
         return query
 
-    def specifications(self, sort=None, quantity=None, filter=None,
+    def all_specifications(self, user):
+        return self.specifications(user, filter=[SpecificationFilter.ALL])
+
+    def specifications(self, user, sort=None, quantity=None, filter=None,
                        prejoin_people=False):
         """See IHasSpecifications."""
         # prejoin_people  is provided only for interface compatibility and
@@ -177,7 +184,7 @@
         assert not prejoin_people
         if filter is None:
             filter = set([SpecificationFilter.ACCEPTED])
-        query = self.spec_filter_clause(filter=filter)
+        query = self.spec_filter_clause(user, filter=filter)
         # import here to avoid circular deps
         from lp.blueprints.model.specification import Specification
         results = Store.of(self).find(Specification, *query)
@@ -200,7 +207,7 @@
 
     def specificationLinks(self, filter=None):
         """See `ISprint`."""
-        query = self.spec_filter_clause(filter=filter)
+        query = self.spec_filter_clause(None, filter=filter)
         result = Store.of(self).find(SprintSpecification, *query)
         return result
 
@@ -227,7 +234,7 @@
         # queue
         flush_database_updates()
 
-        return self.specifications(
+        return self.specifications(decider,
                         filter=[SpecificationFilter.PROPOSED]).count()
 
     def declineSpecificationLinks(self, idlist, decider):
@@ -241,7 +248,7 @@
         # queue
         flush_database_updates()
 
-        return self.specifications(
+        return self.specifications(decider,
                         filter=[SpecificationFilter.PROPOSED]).count()
 
     # attendance

=== modified file 'lib/lp/blueprints/model/tests/test_sprint.py'
--- lib/lp/blueprints/model/tests/test_sprint.py	2012-09-27 20:24:22 +0000
+++ lib/lp/blueprints/model/tests/test_sprint.py	2012-09-27 20:24:22 +0000
@@ -9,8 +9,10 @@
 import datetime
 
 from pytz import utc
+from zope.component import getUtility
 from zope.security.proxy import removeSecurityProxy
 
+from lp.app.enums import InformationType
 from lp.blueprints.enums import (
     NewSpecificationDefinitionStatus,
     SpecificationDefinitionStatus,
@@ -18,12 +20,13 @@
     SpecificationPriority,
     SpecificationSort,
     )
+from lp.registry.interfaces.accesspolicy import IAccessPolicySource
 from lp.testing import TestCaseWithFactory
 from lp.testing.layers import DatabaseFunctionalLayer
 
 
-def list_result(sprint, filter=None):
-    result = sprint.specifications(SpecificationSort.DATE, filter=filter)
+def list_result(sprint, filter=None, user=None):
+    result = sprint.specifications(user, SpecificationSort.DATE, filter=filter)
     return list(result)
 
 
@@ -38,11 +41,12 @@
     def makeSpec(self, sprint=None, date_decided=0, date_created=0,
                  proposed=False, declined=False, title=None,
                  status=NewSpecificationDefinitionStatus.NEW,
-                 name=None, priority=None):
+                 name=None, priority=None, information_type=None):
         if sprint is None:
             sprint = self.factory.makeSprint()
         blueprint = self.factory.makeSpecification(
-            title=title, status=status, name=name, priority=priority)
+            title=title, status=status, name=name, priority=priority,
+            information_type=information_type)
         link = blueprint.linkSprint(sprint, blueprint.owner)
         naked_link = removeSecurityProxy(link)
         if declined:
@@ -61,10 +65,11 @@
         sprint = self.factory.makeSprint()
         for count in range(10):
             self.makeSpec(sprint)
-        self.assertEqual(10, sprint.specifications().count())
-        self.assertEqual(10, sprint.specifications(quantity=None).count())
-        self.assertEqual(8, sprint.specifications(quantity=8).count())
-        self.assertEqual(10, sprint.specifications(quantity=11).count())
+        self.assertEqual(10, sprint.specifications(None).count())
+        result = sprint.specifications(None, quantity=None).count()
+        self.assertEqual(10, result)
+        self.assertEqual(8, sprint.specifications(None, quantity=8).count())
+        self.assertEqual(10, sprint.specifications(None, quantity=11).count())
 
     def test_specifications_date_sort_accepted_decided(self):
         # If only accepted proposals are requested, date-sorting uses
@@ -133,9 +138,9 @@
         blueprint3 = self.makeSpec(
             sprint, priority=SpecificationPriority.LOW,
             status=SpecificationDefinitionStatus.OBSOLETE)
-        result = sprint.specifications()
+        result = sprint.specifications(None)
         self.assertEqual([blueprint3, blueprint1, blueprint2], list(result))
-        result = sprint.specifications(sort=SpecificationSort.PRIORITY)
+        result = sprint.specifications(None, sort=SpecificationSort.PRIORITY)
         self.assertEqual([blueprint3, blueprint1, blueprint2], list(result))
 
     def test_priority_sort_fallback_status(self):
@@ -148,9 +153,9 @@
             sprint, status=SpecificationDefinitionStatus.APPROVED, name='c')
         blueprint3 = self.makeSpec(
             sprint, status=SpecificationDefinitionStatus.NEW, name='b')
-        result = sprint.specifications()
+        result = sprint.specifications(None)
         self.assertEqual([blueprint2, blueprint3, blueprint1], list(result))
-        result = sprint.specifications(sort=SpecificationSort.PRIORITY)
+        result = sprint.specifications(None, sort=SpecificationSort.PRIORITY)
         self.assertEqual([blueprint2, blueprint3, blueprint1], list(result))
 
     def test_priority_sort_fallback_name(self):
@@ -159,9 +164,9 @@
         sprint = blueprint1.sprints[0]
         blueprint2 = self.makeSpec(sprint, name='c')
         blueprint3 = self.makeSpec(sprint, name='a')
-        result = sprint.specifications()
+        result = sprint.specifications(None)
         self.assertEqual([blueprint3, blueprint1, blueprint2], list(result))
-        result = sprint.specifications(sort=SpecificationSort.PRIORITY)
+        result = sprint.specifications(None, sort=SpecificationSort.PRIORITY)
         self.assertEqual([blueprint3, blueprint1, blueprint2], list(result))
 
     def test_text_search(self):
@@ -182,6 +187,34 @@
         result = list_result(sprint, [SpecificationFilter.DECLINED])
         self.assertEqual([blueprint2], result)
 
+    def test_proprietary_not_listed(self):
+        # Proprietary blueprints are not listed for random users
+        blueprint1 = self.makeSpec(
+            information_type=InformationType.PROPRIETARY)
+        self.assertEqual([], list_result(blueprint1.sprints[0]))
+
+    def test_proprietary_listed_for_artifact_grant(self):
+        # Proprietary blueprints are listed for users with an artifact grant.
+        blueprint1 = self.makeSpec(
+            information_type=InformationType.PROPRIETARY)
+        grant = self.factory.makeAccessArtifactGrant(
+            concrete_artifact=blueprint1)
+        self.assertEqual(
+            [blueprint1],
+            list_result(blueprint1.sprints[0], user=grant.grantee))
+
+    def test_proprietary_listed_for_policy_grant(self):
+        # Proprietary blueprints are listed for users with a policy grant.
+        blueprint1 = self.makeSpec(
+            information_type=InformationType.PROPRIETARY)
+        policy_source = getUtility(IAccessPolicySource)
+        (policy,) = policy_source.find(
+            [(blueprint1.product, InformationType.PROPRIETARY)])
+        grant = self.factory.makeAccessPolicyGrant(policy)
+        self.assertEqual(
+            [blueprint1],
+            list_result(blueprint1.sprints[0], user=grant.grantee))
+
 
 class TestSprintAttendancesSort(TestCaseWithFactory):
 

=== modified file 'lib/lp/blueprints/templates/person-specworkload.pt'
--- lib/lp/blueprints/templates/person-specworkload.pt	2011-12-08 22:41:00 +0000
+++ lib/lp/blueprints/templates/person-specworkload.pt	2012-09-27 20:24:22 +0000
@@ -44,7 +44,7 @@
 
       <tal:participants repeat="member members_batch">
 
-        <tal:specs define="specifications member/specifications">
+        <tal:specs define="specifications member/@@+specworkload/specifications">
 
           <div style="margin-bottom: 1em;" tal:condition="specifications">
             <p>

=== modified file 'lib/lp/registry/browser/person.py'
--- lib/lp/registry/browser/person.py	2012-09-11 13:25:48 +0000
+++ lib/lp/registry/browser/person.py	2012-09-27 20:24:22 +0000
@@ -1303,6 +1303,9 @@
         batch_nav = BatchNavigator(members, self.request, size=20)
         return batch_nav
 
+    def specifications(self):
+        return self.context.specifications(self.user)
+
 
 class PersonSpecWorkloadTableView(LaunchpadView):
     """View to render the specification workload table for a person.
@@ -1331,7 +1334,7 @@
         approver, the assignee or the drafter.
         """
         return [PersonSpecWorkloadTableView.PersonSpec(spec, self.context)
-                for spec in self.context.specifications()]
+                for spec in self.context.specifications(self.user)]
 
 
 class PersonVouchersView(LaunchpadFormView):

=== modified file 'lib/lp/registry/doc/distribution.txt'
--- lib/lp/registry/doc/distribution.txt	2012-09-27 20:24:22 +0000
+++ lib/lp/registry/doc/distribution.txt	2012-09-27 20:24:22 +0000
@@ -490,18 +490,18 @@
 complete so it will not show up unless we explicitly ask for complete specs:
 
     >>> filter = [SpecificationFilter.INFORMATIONAL]
-    >>> kubuntu.specifications(filter=filter).count()
+    >>> kubuntu.specifications(None, filter=filter).count()
     0
     >>> filter = [SpecificationFilter.INFORMATIONAL,
     ...           SpecificationFilter.COMPLETE]
-    >>> kubuntu.specifications(filter=filter).count()
+    >>> kubuntu.specifications(None, filter=filter).count()
     1
 
 
 There are 2 completed specs for Kubuntu:
 
     >>> filter = [SpecificationFilter.COMPLETE]
-    >>> for spec in kubuntu.specifications(filter=filter):
+    >>> for spec in kubuntu.specifications(None, filter=filter):
     ...    print spec.name, spec.is_complete
     thinclient-local-devices True
     usplash-on-hibernation True
@@ -510,7 +510,7 @@
 And there are four incomplete specs:
 
     >>> filter = [SpecificationFilter.INCOMPLETE]
-    >>> for spec in kubuntu.specifications(filter=filter):
+    >>> for spec in kubuntu.specifications(None, filter=filter):
     ...    print spec.name, spec.is_complete
     cluster-installation False
     revu False
@@ -521,7 +521,7 @@
 If we ask for all specs, we get them in the order of priority.
 
     >>> filter = [SpecificationFilter.ALL]
-    >>> for spec in kubuntu.specifications(filter=filter):
+    >>> for spec in kubuntu.specifications(None, filter=filter):
     ...    print spec.priority.title, spec.name
     Essential cluster-installation
     High revu
@@ -533,7 +533,7 @@
 
 And if we ask just for specs, we get the incomplete ones.
 
-    >>> for spec in kubuntu.specifications():
+    >>> for spec in kubuntu.specifications(None):
     ...     print spec.name, spec.is_complete
     cluster-installation False
     revu False
@@ -542,7 +542,7 @@
 
 We can filter for specifications that contain specific text:
 
-    >>> for spec in kubuntu.specifications(filter=['package']):
+    >>> for spec in kubuntu.specifications(None, filter=['package']):
     ...     print spec.name
     revu
 
@@ -550,7 +550,7 @@
 
     >>> from lp.blueprints.enums import SpecificationDefinitionStatus
     >>> login('mark@xxxxxxxxxxx')
-    >>> for spec in kubuntu.specifications():
+    >>> for spec in kubuntu.specifications(None):
     ...     # Do this here, otherwise, the change will be flush before
     ...     # updateLifecycleStatus() acts and an IntegrityError will be
     ...     # raised.

=== modified file 'lib/lp/registry/doc/projectgroup.txt'
--- lib/lp/registry/doc/projectgroup.txt	2012-09-27 20:24:22 +0000
+++ lib/lp/registry/doc/projectgroup.txt	2012-09-27 20:24:22 +0000
@@ -150,7 +150,7 @@
 First, there should be only one informational spec for mozilla:
 
     >>> filter = [SpecificationFilter.INFORMATIONAL]
-    >>> for spec in mozilla.specifications(filter=filter):
+    >>> for spec in mozilla.specifications(None, filter=filter):
     ...    print spec.name
     extension-manager-upgrades
 
@@ -158,19 +158,19 @@
 There are no completed specs for mozilla:
 
     >>> filter = [SpecificationFilter.COMPLETE]
-    >>> for spec in mozilla.specifications(filter=filter):
+    >>> for spec in mozilla.specifications(None, filter=filter):
     ...    print spec.name
 
 
 And there are five incomplete specs:
 
     >>> filter = [SpecificationFilter.INCOMPLETE]
-    >>> mozilla.specifications(filter=filter).count()
+    >>> mozilla.specifications(None, filter=filter).count()
     5
 
 We can filter for specifications that contain specific text:
 
-    >>> for spec in mozilla.specifications(filter=['install']):
+    >>> for spec in mozilla.specifications(None, filter=['install']):
     ...     print spec.name
     extension-manager-upgrades
 
@@ -178,7 +178,7 @@
 Inactive products are excluded from the listings.
 
     >>> filter = [SpecificationFilter.INCOMPLETE]
-    >>> mozilla.specifications(filter=filter).count()
+    >>> mozilla.specifications(None, filter=filter).count()
     5
 
     >>> from lp.registry.interfaces.product import IProductSet
@@ -190,7 +190,7 @@
     >>> firefox.active = False
     >>> flush_database_updates()
     >>> filter = [SpecificationFilter.INCOMPLETE]
-    >>> mozilla.specifications(filter=filter).count()
+    >>> mozilla.specifications(None, filter=filter).count()
     0
 
 Reset firefox so we don't mess up later tests.
@@ -248,7 +248,8 @@
 mozilla_1_0_series._all_specifications.
 
     >>> filter = [SpecificationFilter.INFORMATIONAL]
-    >>> extension_manager_upgrades = mozilla.specifications(filter=filter)[0]
+    >>> extension_manager_upgrades = mozilla.specifications(
+    ...     None, filter=filter)[0]
     >>> series_1_0 = firefox.getSeries('1.0')
     >>> extension_manager_upgrades.proposeGoal(series_1_0, no_priv)
     >>> for spec in mozilla_series_1_0._all_specifications:
@@ -264,7 +265,7 @@
 Filtered lists of project series related specifications are generated
 the same way as for project related specifications.
 
-    >>> for spec in mozilla_series_1_0.specifications(filter=filter):
+    >>> for spec in mozilla_series_1_0.specifications(None, filter=filter):
     ...     print spec.name
     extension-manager-upgrades
 
@@ -277,7 +278,7 @@
 project itself.
 
     >>> filter = [SpecificationFilter.INCOMPLETE]
-    >>> for spec in mozilla_series_1_0.specifications(filter=filter):
+    >>> for spec in mozilla_series_1_0.specifications(None, filter=filter):
     ...     print spec.name
     svg-support
     canvas
@@ -287,21 +288,22 @@
 
  Searching for text is also possible.
 
-    >>> for spec in mozilla_series_1_0.specifications(filter=['install']):
+    >>> for spec in mozilla_series_1_0.specifications(
+    ...     None, filter=['install']):
     ...     print spec.name
     extension-manager-upgrades
 
 Inactive products are excluded from the series listings.
 
     >>> filter = [SpecificationFilter.INCOMPLETE]
-    >>> specs = mozilla_series_1_0.specifications(filter=filter)
+    >>> specs = mozilla_series_1_0.specifications(None, filter=filter)
     >>> print specs.count()
     5
 
     >>> firefox = getUtility(IProductSet).getByName('firefox')
     >>> firefox.active = False
     >>> filter = [SpecificationFilter.INCOMPLETE]
-    >>> mozilla_series_1_0.specifications(filter=filter).count()
+    >>> mozilla_series_1_0.specifications(None, filter=filter).count()
     0
 
 Reset firefox so we don't mess up later tests.

=== modified file 'lib/lp/registry/model/distribution.py'
--- lib/lp/registry/model/distribution.py	2012-09-27 20:24:22 +0000
+++ lib/lp/registry/model/distribution.py	2012-09-27 20:24:22 +0000
@@ -878,7 +878,7 @@
         return getUtility(IDistributionSet).getCurrentSourceReleases(
             {self: source_package_names})
 
-    def specifications(self, sort=None, quantity=None, filter=None,
+    def specifications(self, user, sort=None, quantity=None, filter=None,
                        prejoin_people=True):
         """See `IHasSpecifications`.
 

=== modified file 'lib/lp/registry/model/distroseries.py'
--- lib/lp/registry/model/distroseries.py	2012-09-27 20:24:22 +0000
+++ lib/lp/registry/model/distroseries.py	2012-09-27 20:24:22 +0000
@@ -777,7 +777,7 @@
         """See `IHasBugs`."""
         return self.distribution.official_bug_tags
 
-    def specifications(self, sort=None, quantity=None, filter=None,
+    def specifications(self, user, sort=None, quantity=None, filter=None,
                        prejoin_people=True):
         """See IHasSpecifications.
 

=== modified file 'lib/lp/registry/model/person.py'
--- lib/lp/registry/model/person.py	2012-09-27 20:24:22 +0000
+++ lib/lp/registry/model/person.py	2012-09-27 20:24:22 +0000
@@ -821,7 +821,7 @@
         """See `IPerson`."""
         return "%s (%s)" % (self.displayname, self.name)
 
-    def specifications(self, sort=None, quantity=None, filter=None,
+    def specifications(self, user, sort=None, quantity=None, filter=None,
                        prejoin_people=True):
         """See `IHasSpecifications`."""
 

=== modified file 'lib/lp/registry/model/product.py'
--- lib/lp/registry/model/product.py	2012-09-27 20:24:22 +0000
+++ lib/lp/registry/model/product.py	2012-09-27 20:24:22 +0000
@@ -1303,7 +1303,7 @@
         # automatically shared.
         return True
 
-    def specifications(self, sort=None, quantity=None, filter=None,
+    def specifications(self, user, sort=None, quantity=None, filter=None,
                        prejoin_people=True):
         """See `IHasSpecifications`."""
 

=== modified file 'lib/lp/registry/model/productseries.py'
--- lib/lp/registry/model/productseries.py	2012-09-27 20:24:22 +0000
+++ lib/lp/registry/model/productseries.py	2012-09-27 20:24:22 +0000
@@ -301,7 +301,7 @@
         """See `IProductSeries`."""
         return self == self.product.development_focus
 
-    def specifications(self, sort=None, quantity=None, filter=None,
+    def specifications(self, user, sort=None, quantity=None, filter=None,
                        prejoin_people=True):
         """See IHasSpecifications.
 

=== modified file 'lib/lp/registry/model/projectgroup.py'
--- lib/lp/registry/model/projectgroup.py	2012-09-27 20:24:22 +0000
+++ lib/lp/registry/model/projectgroup.py	2012-09-27 20:24:22 +0000
@@ -231,7 +231,7 @@
             """ % sqlvalues(self, SprintSpecificationStatus.ACCEPTED)
         return query, ['Product', 'Specification', 'SprintSpecification']
 
-    def specifications(self, sort=None, quantity=None, filter=None,
+    def specifications(self, user, sort=None, quantity=None, filter=None,
                        series=None, prejoin_people=True):
         """See `IHasSpecifications`."""
 
@@ -631,10 +631,11 @@
         self.project = project
         self.name = name
 
-    def specifications(self, sort=None, quantity=None, filter=None,
+    def specifications(self, user, sort=None, quantity=None, filter=None,
                        prejoin_people=True):
         return self.project.specifications(
-            sort, quantity, filter, self.name, prejoin_people=prejoin_people)
+            user, sort, quantity, filter, self.name,
+            prejoin_people=prejoin_people)
 
     @property
     def title(self):

=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py	2012-09-22 23:58:54 +0000
+++ lib/lp/testing/factory.py	2012-09-27 20:24:22 +0000
@@ -2091,8 +2091,16 @@
         :param product: The product to make the blueprint on.  If one is
             not specified, an arbitrary product is created.
         """
+        proprietary = (information_type not in PUBLIC_INFORMATION_TYPES and
+            information_type is not None)
         if distribution is None and product is None:
-            product = self.makeProduct()
+            if proprietary:
+                specification_sharing_policy = (
+                SpecificationSharingPolicy.EMBARGOED_OR_PROPRIETARY)
+            else:
+                specification_sharing_policy = None
+            product = self.makeProduct(
+                specification_sharing_policy=specification_sharing_policy)
         if name is None:
             name = self.getUniqueString('name')
         if summary is None:
@@ -2125,7 +2133,7 @@
             priority=priority)
         naked_spec = removeSecurityProxy(spec)
         if information_type is not None:
-            if information_type not in PUBLIC_INFORMATION_TYPES:
+            if proprietary:
                 naked_spec.target._ensurePolicies([information_type])
             naked_spec.transitionToInformationType(
                 information_type, spec.target.owner)
@@ -4340,9 +4348,9 @@
         return link
 
     def makeAccessArtifactGrant(self, artifact=None, grantee=None,
-                                grantor=None):
+                                grantor=None, concrete_artifact=None):
         if artifact is None:
-            artifact = self.makeAccessArtifact()
+            artifact = self.makeAccessArtifact(concrete_artifact)
         if grantee is None:
             grantee = self.makePerson()
         if grantor is None:


Follow ups