← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~adeuring/launchpad/specification-grants into lp:launchpad

 

Abel Deuring has proposed merging lp:~adeuring/launchpad/specification-grants into lp:launchpad.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~adeuring/launchpad/specification-grants/+merge/123062

This branch makes it possible to grant access to a single specification that
is not publicly visible. (The security adapter for ISpecification does not
use yet these grants -- the MP would become overly long with this change...)

= Details =

lib/lp/blueprints/configure.zcml,
lib/lp/blueprints/model/specification.py,
lib/lp/blueprints/tests/test_specification.py:

The property Specification.information_type should not be set directly
because instances that are not publicly visible need a record in
the table AccessPolicyArtifact that links the specification to an
AccessPolicy. These links are maintained by calling
reconcile_access_for_artifact() in Specifcation.transitionToInformationType().

This change leads to a number of changes in
lib/lp/blueprints/tests/test_specification.py, where severeal tests
changed information_type with a simple assignment.

Furthermore, reconcile_access_for_artifact() obviously fails if no
matching AccessPolicy record exists; these records are created by
calling speicifcaton.target._ensurePolicies(). This is a bit hackish,
but the class Product does not yet have a method
setSpecifcationSharingPolicy() which would manage AccessPolicy records
"officially".

lib/lp/registry/interfaces/accesspolicy.py:

The attribute specification_id was simply missing in the interface class;
it is already used in the model.

lib/lp/registry/model/accesspolicy.py,
lib/lp/registry/services/sharingservice.py,
lib/lp/registry/services/tests/test_sharingservice.py:

The core of the change: AccessArtifact.ensure() and
SharingService.ensureAccessGrants() must be able to deal
not only with bugs and branches, but also with specifications.

= tests =

./bin/test -vvt lp.registry.services.tests.test_sharingservice.*test_ensureAccessGrants
./bin/test -vvt lp.blueprints.tests.test_specification.SpecificationTests.test.*access
./bin/test -vvt lp.registry.tests.test_accesspolicy.TestAccessArtifactSpecification


no lint


-- 
https://code.launchpad.net/~adeuring/launchpad/specification-grants/+merge/123062
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~adeuring/launchpad/specification-grants into lp:launchpad.
=== modified file 'lib/lp/blueprints/configure.zcml'
--- lib/lp/blueprints/configure.zcml	2012-08-20 16:38:10 +0000
+++ lib/lp/blueprints/configure.zcml	2012-09-06 12:10:25 +0000
@@ -150,10 +150,6 @@
   <class class="lp.blueprints.model.specification.Specification">
     <allow interface="lp.blueprints.interfaces.specification.ISpecificationPublic"/>
     <require
-        permission="launchpad.Edit"
-        set_attributes="information_type" />
-
-    <require
         permission="launchpad.LimitedView"
         interface="lp.blueprints.interfaces.specification.ISpecificationView" />
 

=== modified file 'lib/lp/blueprints/model/specification.py'
--- lib/lp/blueprints/model/specification.py	2012-08-31 18:54:29 +0000
+++ lib/lp/blueprints/model/specification.py	2012-09-06 12:10:25 +0000
@@ -820,12 +820,17 @@
         return set(InformationType.items)
 
     def transitionToInformationType(self, information_type, who):
-        """See `IBug`."""
+        """See ISpecification."""
+        # avoid circular imports.
+        from lp.registry.model.accesspolicy import (
+            reconcile_access_for_artifact,
+            )
         if self.information_type == information_type:
             return False
         if information_type not in self.getAllowedInformationTypes(who):
             raise CannotChangeInformationType("Forbidden by project policy.")
         self.information_type = information_type
+        reconcile_access_for_artifact(self, information_type, [self.target])
         return True
 
     @property

=== modified file 'lib/lp/blueprints/tests/test_specification.py'
--- lib/lp/blueprints/tests/test_specification.py	2012-09-06 00:01:38 +0000
+++ lib/lp/blueprints/tests/test_specification.py	2012-09-06 12:10:25 +0000
@@ -193,9 +193,9 @@
             'launchpad.AnyAllowedPerson': set(('whiteboard', )),
             'launchpad.Edit': set((
                 'approver', 'assignee', 'definition_status', 'distribution',
-                'drafter', 'implementation_status', 'information_type',
-                'man_days', 'milestone', 'name', 'product', 'specurl',
-                'summary', 'superseded_by', 'title')),
+                'drafter', 'implementation_status', 'man_days', 'milestone',
+                'name', 'product', 'specurl', 'summary', 'superseded_by',
+                'title')),
             }
         specification = self.factory.makeSpecification()
         checker = getChecker(specification)
@@ -253,23 +253,30 @@
         specification = self.factory.makeSpecification()
         for information_type in PUBLIC_INFORMATION_TYPES:
             with person_logged_in(specification.owner):
-                specification.information_type = information_type
+                specification.transitionToInformationType(
+                    information_type, specification.owner)
             self.read_access_to_ISpecificationView(
                 ANONYMOUS, specification, error_expected=False)
         # ...but not to private specifications.
+        removeSecurityProxy(specification.target)._ensurePolicies(
+            PRIVATE_INFORMATION_TYPES)
         for information_type in PRIVATE_INFORMATION_TYPES:
             with person_logged_in(specification.owner):
-                specification.information_type = information_type
+                specification.transitionToInformationType(
+                    information_type, specification.owner)
             self.read_access_to_ISpecificationView(
                 ANONYMOUS, specification, error_expected=True)
 
     def test_anon_write_access(self):
         # Anonymous users do not have write access to specifications.
         specification = self.factory.makeSpecification()
+        removeSecurityProxy(specification.target)._ensurePolicies(
+            PRIVATE_INFORMATION_TYPES)
         for information_type in (PUBLIC_INFORMATION_TYPES +
                                  PRIVATE_INFORMATION_TYPES):
             with person_logged_in(specification.owner):
-                specification.information_type = information_type
+                specification.transitionToInformationType(
+                    information_type, specification.owner)
             self.write_access_to_ISpecificationView(
                 ANONYMOUS, specification, error_expected=True,
                 attribute='whiteboard', value='foo')
@@ -283,13 +290,17 @@
         user = self.factory.makePerson()
         for information_type in PUBLIC_INFORMATION_TYPES:
             with person_logged_in(specification.owner):
-                specification.information_type = information_type
+                specification.transitionToInformationType(
+                    information_type, specification.owner)
             self.read_access_to_ISpecificationView(
                 user, specification, error_expected=False)
         # ...but not to private specifications.
+        removeSecurityProxy(specification.target)._ensurePolicies(
+            PRIVATE_INFORMATION_TYPES)
         for information_type in PRIVATE_INFORMATION_TYPES:
             with person_logged_in(specification.owner):
-                specification.information_type = information_type
+                specification.transitionToInformationType(
+                    information_type, specification.owner)
             self.read_access_to_ISpecificationView(
                 user, specification, error_expected=True)
 
@@ -300,7 +311,8 @@
         user = self.factory.makePerson()
         for information_type in PUBLIC_INFORMATION_TYPES:
             with person_logged_in(specification.owner):
-                specification.information_type = information_type
+                specification.transitionToInformationType(
+                    information_type, specification.owner)
             self.write_access_to_ISpecificationView(
                 user, specification, error_expected=False,
                 attribute='whiteboard', value='foo')
@@ -308,9 +320,12 @@
                 user, specification, error_expected=True,
                 attribute='name', value='foo')
         # The cannot change any attribute of private specifcations.
+        removeSecurityProxy(specification.target)._ensurePolicies(
+            PRIVATE_INFORMATION_TYPES)
         for information_type in PRIVATE_INFORMATION_TYPES:
             with person_logged_in(specification.owner):
-                specification.information_type = information_type
+                specification.transitionToInformationType(
+                    information_type, specification.owner)
             self.write_access_to_ISpecificationView(
                 user, specification, error_expected=True,
                 attribute='whiteboard', value='foo')
@@ -322,10 +337,13 @@
         # Users with special privileges can aceess the attributes
         # of public and private specifcations.
         specification = self.factory.makeSpecification()
+        removeSecurityProxy(specification.target)._ensurePolicies(
+            PRIVATE_INFORMATION_TYPES)
         for information_type in (PUBLIC_INFORMATION_TYPES +
                                  PRIVATE_INFORMATION_TYPES):
             with person_logged_in(specification.owner):
-                specification.information_type = information_type
+                specification.transitionToInformationType(
+                    information_type, specification.owner)
             self.read_access_to_ISpecificationView(
                 specification.owner, specification, error_expected=False)
 
@@ -333,10 +351,13 @@
         # Users with special privileges can change the attributes
         # of public and private specifcations.
         specification = self.factory.makeSpecification()
+        removeSecurityProxy(specification.target)._ensurePolicies(
+            PRIVATE_INFORMATION_TYPES)
         for information_type in (PUBLIC_INFORMATION_TYPES +
                                  PRIVATE_INFORMATION_TYPES):
             with person_logged_in(specification.owner):
-                specification.information_type = information_type
+                specification.transitionToInformationType(
+                    information_type, specification.owner)
             self.write_access_to_ISpecificationView(
                 specification.owner, specification, error_expected=False,
                 attribute='whiteboard', value='foo')

=== modified file 'lib/lp/registry/interfaces/accesspolicy.py'
--- lib/lp/registry/interfaces/accesspolicy.py	2012-09-03 11:22:25 +0000
+++ lib/lp/registry/interfaces/accesspolicy.py	2012-09-06 12:10:25 +0000
@@ -35,6 +35,7 @@
     concrete_artifact = Attribute("Concrete artifact")
     bug_id = Attribute("bug_id")
     branch_id = Attribute("branch_id")
+    specification_id = Attribute("specification_id")
 
 
 class IAccessArtifactGrant(Interface):

=== modified file 'lib/lp/registry/interfaces/sharingservice.py'
--- lib/lp/registry/interfaces/sharingservice.py	2012-09-05 03:39:50 +0000
+++ lib/lp/registry/interfaces/sharingservice.py	2012-09-06 12:10:25 +0000
@@ -247,13 +247,15 @@
         branches=List(
             Reference(schema=IBranch), title=_('Branches'), required=False))
     @operation_for_version('devel')
-    def ensureAccessGrants(grantees, user, branches=None, bugs=None):
+    def ensureAccessGrants(grantees, user, branches=None, bugs=None,
+                           specifications=None):
         """Ensure a grantee has an access grant to the specified artifacts.
 
         :param grantees: the people or teams for whom to grant access
         :param user: the user making the request
         :param bugs: the bugs for which to grant access
         :param branches: the branches for which to grant access
+        :param specifications: the specifications for which to grant access
         """
 
     @export_write_operation()

=== modified file 'lib/lp/registry/model/accesspolicy.py'
--- lib/lp/registry/model/accesspolicy.py	2012-09-03 11:22:25 +0000
+++ lib/lp/registry/model/accesspolicy.py	2012-09-06 12:10:25 +0000
@@ -70,7 +70,7 @@
         (pillar, information_type) for pillar in pillars)
     missing_pillars = set(pillars) - set([ap.pillar for ap in aps])
     if len(missing_pillars):
-        pillar_str =  ', '.join([p.name for p in missing_pillars])
+        pillar_str = ', '.join([p.name for p in missing_pillars])
         raise AssertionError(
             "Pillar(s) %s require an access policy for information type "
             "%s." % (pillar_str, information_type.title))
@@ -97,20 +97,25 @@
     bug = Reference(bug_id, 'Bug.id')
     branch_id = Int(name='branch')
     branch = Reference(branch_id, 'Branch.id')
+    specification_id = Int(name='specification')
+    specification = Reference(specification_id, 'Specification.id')
 
     @property
     def concrete_artifact(self):
-        artifact = self.bug or self.branch
+        artifact = self.bug or self.branch or self.specification
         return artifact
 
     @classmethod
     def _constraintForConcrete(cls, concrete_artifact):
+        from lp.blueprints.interfaces.specification import ISpecification
         from lp.bugs.interfaces.bug import IBug
         from lp.code.interfaces.branch import IBranch
         if IBug.providedBy(concrete_artifact):
             col = cls.bug
         elif IBranch.providedBy(concrete_artifact):
             col = cls.branch
+        elif ISpecification.providedBy(concrete_artifact):
+            col = cls.specification
         else:
             raise ValueError(
                 "%r is not a valid artifact" % concrete_artifact)
@@ -128,6 +133,7 @@
     @classmethod
     def ensure(cls, concrete_artifacts):
         """See `IAccessArtifactSource`."""
+        from lp.blueprints.interfaces.specification import ISpecification
         from lp.bugs.interfaces.bug import IBug
         from lp.code.interfaces.branch import IBranch
 
@@ -143,12 +149,16 @@
         insert_values = []
         for concrete in needed:
             if IBug.providedBy(concrete):
-                insert_values.append((concrete, None))
+                insert_values.append((concrete, None, None))
             elif IBranch.providedBy(concrete):
-                insert_values.append((None, concrete))
+                insert_values.append((None, concrete, None))
+            elif ISpecification.providedBy(concrete):
+                insert_values.append((None, None, concrete))
             else:
                 raise ValueError("%r is not a supported artifact" % concrete)
-        new = create((cls.bug, cls.branch), insert_values, get_objects=True)
+        new = create(
+            (cls.bug, cls.branch, cls.specification),
+            insert_values, get_objects=True)
         return list(existing) + new
 
     @classmethod

=== modified file 'lib/lp/registry/services/sharingservice.py'
--- lib/lp/registry/services/sharingservice.py	2012-09-05 22:02:39 +0000
+++ lib/lp/registry/services/sharingservice.py	2012-09-06 12:10:25 +0000
@@ -523,7 +523,7 @@
             user, artifacts, grantee=grantee, pillar=pillar)
 
     def ensureAccessGrants(self, grantees, user, branches=None, bugs=None,
-                           ignore_permissions=False):
+                           specifications=None, ignore_permissions=False):
         """See `ISharingService`."""
 
         artifacts = []
@@ -531,6 +531,8 @@
             artifacts.extend(branches)
         if bugs:
             artifacts.extend(bugs)
+        if specifications:
+            artifacts.extend(specifications)
         if not ignore_permissions:
             # The user needs to have launchpad.Edit permission on all supplied
             # bugs and branches or else we raise an Unauthorized exception.

=== modified file 'lib/lp/registry/services/tests/test_sharingservice.py'
--- lib/lp/registry/services/tests/test_sharingservice.py	2012-09-05 22:02:39 +0000
+++ lib/lp/registry/services/tests/test_sharingservice.py	2012-09-06 12:10:25 +0000
@@ -13,6 +13,7 @@
 from zope.traversing.browser.absoluteurl import absoluteURL
 
 from lp.app.interfaces.services import IService
+from lp.blueprints.interfaces.specification import ISpecification
 from lp.bugs.interfaces.bug import IBug
 from lp.code.enums import (
     BranchSubscriptionNotificationLevel,
@@ -43,6 +44,7 @@
     admin_logged_in,
     login,
     login_person,
+    person_logged_in,
     StormStatementRecorder,
     TestCaseWithFactory,
     WebServiceTestCase,
@@ -1011,22 +1013,26 @@
             ValueError, self.service.revokeAccessGrants,
             product, grantee, product.owner)
 
-    def _assert_ensureAccessGrants(self, user, bugs, branches,
+    def _assert_ensureAccessGrants(self, user, bugs, branches, specifications,
                                    grantee=None):
         # Creating access grants works as expected.
         if not grantee:
             grantee = self.factory.makePerson()
         self.service.ensureAccessGrants(
-            [grantee], user, bugs=bugs, branches=branches)
+            [grantee], user, bugs=bugs, branches=branches,
+            specifications=specifications)
 
         # Check that grantee has expected access grants.
         shared_bugs = []
         shared_branches = []
+        shared_specifications = []
         all_pillars = []
         for bug in bugs or []:
             all_pillars.extend(bug.affected_pillars)
         for branch in branches or []:
             all_pillars.append(branch.target.context)
+        for specification in specifications or []:
+            all_pillars.append(specification.target)
         policies = getUtility(IAccessPolicySource).findByPillar(all_pillars)
 
         apgfs = getUtility(IAccessPolicyGrantFlatSource)
@@ -1036,8 +1042,11 @@
                 shared_bugs.append(a.concrete_artifact)
             elif IBranch.providedBy(a.concrete_artifact):
                 shared_branches.append(a.concrete_artifact)
+            elif ISpecification.providedBy(a.concrete_artifact):
+                shared_specifications.append(a.concrete_artifact)
         self.assertContentEqual(bugs or [], shared_bugs)
         self.assertContentEqual(branches or [], shared_branches)
+        self.assertContentEqual(specifications or [], shared_specifications)
 
     def test_ensureAccessGrantsBugs(self):
         # Access grants can be created for bugs.
@@ -1047,7 +1056,7 @@
         bug = self.factory.makeBug(
             target=distro, owner=owner,
             information_type=InformationType.USERDATA)
-        self._assert_ensureAccessGrants(owner, [bug], None)
+        self._assert_ensureAccessGrants(owner, [bug], None, None)
 
     def test_ensureAccessGrantsBranches(self):
         # Access grants can be created for branches.
@@ -1057,7 +1066,19 @@
         branch = self.factory.makeBranch(
             product=product, owner=owner,
             information_type=InformationType.USERDATA)
-        self._assert_ensureAccessGrants(owner, None, [branch])
+        self._assert_ensureAccessGrants(owner, None, [branch], None)
+
+    def test_ensureAccessGrantsSpecifications(self):
+        # Access grants can be created for branches.
+        owner = self.factory.makePerson()
+        product = self.factory.makeProduct(owner=owner)
+        login_person(owner)
+        specification = self.factory.makeSpecification(
+            product=product, owner=owner)
+        with person_logged_in(owner):
+            specification.transitionToInformationType(
+                InformationType.USERDATA, owner)
+        self._assert_ensureAccessGrants(owner, None, None, [specification])
 
     def test_ensureAccessGrantsExisting(self):
         # Any existing access grants are retained and new ones created.
@@ -1075,7 +1096,8 @@
         self.service.ensureAccessGrants([grantee], owner, bugs=[bug])
         # Test with a new bug as well as the one for which access is already
         # granted.
-        self._assert_ensureAccessGrants(owner, [bug, bug2], None, grantee)
+        self._assert_ensureAccessGrants(
+            owner, [bug, bug2], None, None, grantee)
 
     def _assert_ensureAccessGrantsUnauthorized(self, user):
         # ensureAccessGrants raises an Unauthorized exception if the user

=== modified file 'lib/lp/registry/tests/test_accesspolicy.py'
--- lib/lp/registry/tests/test_accesspolicy.py	2012-09-03 11:22:25 +0000
+++ lib/lp/registry/tests/test_accesspolicy.py	2012-09-06 12:10:25 +0000
@@ -289,6 +289,13 @@
         return self.factory.makeBug()
 
 
+class TestAccessArtifactSpecification(BaseAccessArtifactTests,
+                            TestCaseWithFactory):
+
+    def getConcreteArtifact(self):
+        return self.factory.makeSpecification()
+
+
 class TestAccessArtifactGrant(TestCaseWithFactory):
     layer = DatabaseFunctionalLayer
 


Follow ups