← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~wgrant/launchpad/multipolicy-4 into lp:launchpad

 

William Grant has proposed merging lp:~wgrant/launchpad/multipolicy-4 into lp:launchpad.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~wgrant/launchpad/multipolicy-4/+merge/94721

Introduce AccessPolicyArtifact model and interface and an example of querying AccessPolicyGrantFlat. More APGF stuff is to come once we have defined the needed APIs.

I also fixed AccessArtifactGrant and AccessPolicyGrant's date_created fields to be UTC in Python.
-- 
https://code.launchpad.net/~wgrant/launchpad/multipolicy-4/+merge/94721
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~wgrant/launchpad/multipolicy-4 into lp:launchpad.
=== modified file 'lib/lp/registry/configure.zcml'
--- lib/lp/registry/configure.zcml	2012-02-24 04:29:13 +0000
+++ lib/lp/registry/configure.zcml	2012-02-27 06:22:21 +0000
@@ -1930,6 +1930,17 @@
             interface="lp.registry.interfaces.accesspolicy.IAccessArtifactSource"/>
     </securedutility>
     <class
+        class="lp.registry.model.accesspolicy.AccessPolicyArtifact">
+        <allow
+            interface="lp.registry.interfaces.accesspolicy.IAccessPolicyArtifact"/>
+    </class>
+    <securedutility
+        component="lp.registry.model.accesspolicy.AccessPolicyArtifact"
+        provides="lp.registry.interfaces.accesspolicy.IAccessPolicyArtifactSource">
+        <allow
+            interface="lp.registry.interfaces.accesspolicy.IAccessPolicyArtifactSource"/>
+    </securedutility>
+    <class
         class="lp.registry.model.accesspolicy.AccessArtifactGrant">
         <allow
             interface="lp.registry.interfaces.accesspolicy.IAccessArtifactGrant"/>
@@ -1951,4 +1962,10 @@
         <allow
             interface="lp.registry.interfaces.accesspolicy.IAccessPolicyGrantSource"/>
     </securedutility>
+    <securedutility
+        component="lp.registry.model.accesspolicy.AccessPolicyGrantFlat"
+        provides="lp.registry.interfaces.accesspolicy.IAccessPolicyGrantFlatSource">
+        <allow
+            interface="lp.registry.interfaces.accesspolicy.IAccessPolicyGrantFlatSource"/>
+    </securedutility>
 </configure>

=== modified file 'lib/lp/registry/interfaces/accesspolicy.py'
--- lib/lp/registry/interfaces/accesspolicy.py	2012-02-26 23:55:59 +0000
+++ lib/lp/registry/interfaces/accesspolicy.py	2012-02-27 06:22:21 +0000
@@ -11,7 +11,10 @@
     'IAccessArtifactGrantSource',
     'IAccessArtifactSource',
     'IAccessPolicy',
+    'IAccessPolicyArtifact',
+    'IAccessPolicyArtifactSource',
     'IAccessPolicyGrant',
+    'IAccessPolicyGrantFlatSource',
     'IAccessPolicyGrantSource',
     'IAccessPolicySource',
     ]
@@ -59,6 +62,17 @@
     type = Attribute("Type")
 
 
+class IAccessPolicyArtifact(Interface):
+    """An association between an artifact and a policy.
+
+    For example, an security bug in Ubuntu associated with the Ubuntu
+    security policy so people with a grant for that policy can see it.
+    """
+
+    abstract_artifact = Attribute("Abstract artifact")
+    policy = Attribute("Access policy")
+
+
 class IAccessPolicyGrant(Interface):
     """A grant for a person or team to access all of a policy's artifacts.
 
@@ -117,6 +131,29 @@
         """Delete all `IAccessArtifactGrant` objects for the artifacts."""
 
 
+class IAccessPolicyArtifactSource(Interface):
+
+    def create(links):
+        """Create `IAccessPolicyArtifacts`s.
+
+        :param links: a collection of (`IAccessArtifact`, `IAccessPolicy`)
+            pairs to link.
+        """
+
+    def find(links):
+        """Return the specified `IAccessPolicyArtifacts`s if they exist.
+
+        :param links: a collection of (`IAccessArtifact`, `IAccessPolicy`)
+            pairs.
+        """
+
+    def findByArtifact(artifacts):
+        """Return all `IAccessPolicyArtifact` objects for the artifacts."""
+
+    def findByPolicy(policies):
+        """Return all `IAccessPolicyArtifact` objects for the policies."""
+
+
 class IAccessPolicySource(Interface):
 
     def create(pillars_and_types):
@@ -161,4 +198,16 @@
         """
 
     def findByPolicy(policies):
-        """Return all `IAccessPolicyGrant` objects for the artifacts."""
+        """Return all `IAccessPolicyGrant` objects for the policies."""
+
+
+class IAccessPolicyGrantFlatSource(Interface):
+    """Experimental query utility to search through the flattened schema."""
+
+    def findGranteesByPolicy(policies):
+        """Find the `IPerson`s with access grants for the policies.
+
+        This includes grants for artifacts in the policies.
+
+        :param policies: a collection of `IAccesPolicy`s.
+        """

=== modified file 'lib/lp/registry/model/accesspolicy.py'
--- lib/lp/registry/model/accesspolicy.py	2012-02-27 00:01:06 +0000
+++ lib/lp/registry/model/accesspolicy.py	2012-02-27 06:22:21 +0000
@@ -7,9 +7,11 @@
 __all__ = [
     'AccessArtifact',
     'AccessPolicy',
+    'AccessPolicyArtifact',
     'AccessPolicyGrant',
     ]
 
+import pytz
 from storm.expr import (
     And,
     Or,
@@ -28,8 +30,10 @@
     IAccessArtifactGrant,
     IAccessArtifactGrantSource,
     IAccessPolicy,
+    IAccessPolicyArtifact,
     IAccessPolicyGrant,
     )
+from lp.registry.model.person import Person
 from lp.services.database.bulk import create
 from lp.services.database.enumcol import DBEnum
 from lp.services.database.lpstorm import IStore
@@ -178,6 +182,47 @@
             Or(*(cls._constraintForPillar(pillar) for pillar in pillars)))
 
 
+class AccessPolicyArtifact(StormBase):
+    implements(IAccessPolicyArtifact)
+
+    __storm_table__ = 'AccessPolicyArtifact'
+    __storm_primary__ = 'abstract_artifact_id', 'policy_id'
+
+    abstract_artifact_id = Int(name='artifact')
+    abstract_artifact = Reference(
+        abstract_artifact_id, 'AccessArtifact.id')
+    policy_id = Int(name='policy')
+    policy = Reference(policy_id, 'AccessPolicy.id')
+
+    @classmethod
+    def create(cls, links):
+        """See `IAccessPolicyArtifactSource`."""
+        return create(
+            (cls.abstract_artifact, cls.policy), links,
+            get_objects=True)
+
+    @classmethod
+    def find(cls, links):
+        """See `IAccessArtifactGrantSource`."""
+        return IStore(cls).find(
+            cls,
+            Or(*(
+                And(cls.abstract_artifact == artifact, cls.policy == policy)
+                for (artifact, policy) in links)))
+
+    @classmethod
+    def findByArtifact(cls, artifacts):
+        """See `IAccessPolicyArtifactSource`."""
+        ids = [artifact.id for artifact in artifacts]
+        return IStore(cls).find(cls, cls.abstract_artifact_id.is_in(ids))
+
+    @classmethod
+    def findByPolicy(cls, policies):
+        """See `IAccessPolicyArtifactSource`."""
+        ids = [policy.id for policy in policies]
+        return IStore(cls).find(cls, cls.policy_id.is_in(ids))
+
+
 class AccessArtifactGrant(StormBase):
     implements(IAccessArtifactGrant)
 
@@ -191,7 +236,7 @@
     grantee = Reference(grantee_id, 'Person.id')
     grantor_id = Int(name='grantor')
     grantor = Reference(grantor_id, 'Person.id')
-    date_created = DateTime()
+    date_created = DateTime(tzinfo=pytz.UTC)
 
     @property
     def concrete_artifact(self):
@@ -238,7 +283,7 @@
     grantee = Reference(grantee_id, 'Person.id')
     grantor_id = Int(name='grantor')
     grantor = Reference(grantor_id, 'Person.id')
-    date_created = DateTime()
+    date_created = DateTime(tzinfo=pytz.UTC)
 
     @classmethod
     def grant(cls, grants):
@@ -260,3 +305,23 @@
         """See `IAccessPolicyGrantSource`."""
         ids = [policy.id for policy in policies]
         return IStore(cls).find(cls, cls.policy_id.is_in(ids))
+
+
+class AccessPolicyGrantFlat(StormBase):
+    __storm_table__ = 'AccessPolicyGrantFlat'
+
+    id = Int(primary=True)
+    policy_id = Int(name='policy')
+    policy = Reference(policy_id, 'AccessPolicy.id')
+    abstract_artifact_id = Int(name='artifact')
+    abstract_artifact = Reference(
+        abstract_artifact_id, 'AccessArtifact.id')
+    grantee_id = Int(name='grantee')
+    grantee = Reference(grantee_id, 'Person.id')
+
+    @classmethod
+    def findGranteesByPolicy(cls, policies):
+        """See `IAccessPolicyGrantFlatSource`."""
+        ids = [policy.id for policy in policies]
+        return IStore(cls).find(
+            Person, Person.id == cls.grantee_id, cls.policy_id.is_in(ids))

=== modified file 'lib/lp/registry/tests/test_accesspolicy.py'
--- lib/lp/registry/tests/test_accesspolicy.py	2012-02-26 23:46:11 +0000
+++ lib/lp/registry/tests/test_accesspolicy.py	2012-02-27 06:22:21 +0000
@@ -4,19 +4,20 @@
 __metaclass__ = type
 
 from storm.exceptions import LostObjectError
-from testtools.matchers import (
-    AllMatch,
-    )
+from testtools.matchers import AllMatch
 from zope.component import getUtility
 
 from lp.registry.enums import AccessPolicyType
 from lp.registry.interfaces.accesspolicy import (
-    IAccessPolicy,
     IAccessArtifact,
     IAccessArtifactGrant,
     IAccessArtifactGrantSource,
     IAccessArtifactSource,
+    IAccessPolicy,
+    IAccessPolicyArtifact,
+    IAccessPolicyArtifactSource,
     IAccessPolicyGrant,
+    IAccessPolicyGrantFlatSource,
     IAccessPolicyGrantSource,
     IAccessPolicySource,
     )
@@ -264,6 +265,61 @@
             getUtility(IAccessArtifactGrantSource).findByArtifact([artifact]))
 
 
+class TestAccessPolicyArtifact(TestCaseWithFactory):
+    layer = DatabaseFunctionalLayer
+
+    def test_provides_interface(self):
+        self.assertThat(
+            self.factory.makeAccessPolicyArtifact(),
+            Provides(IAccessPolicyArtifact))
+
+
+class TestAccessPolicyArtifactSource(TestCaseWithFactory):
+    layer = DatabaseFunctionalLayer
+
+    def test_create(self):
+        wanted = [
+            (self.factory.makeAccessArtifact(),
+             self.factory.makeAccessPolicy()),
+            (self.factory.makeAccessArtifact(),
+             self.factory.makeAccessPolicy()),
+            ]
+        links = getUtility(IAccessPolicyArtifactSource).create(wanted)
+        self.assertContentEqual(
+            wanted,
+            [(link.abstract_artifact, link.policy) for link in links])
+
+    def test_find(self):
+        links = [self.factory.makeAccessPolicyArtifact() for i in range(3)]
+        self.assertContentEqual(
+            links,
+            getUtility(IAccessPolicyArtifactSource).find(
+                [(link.abstract_artifact, link.policy) for link in links]))
+
+    def test_findByArtifact(self):
+        # findByArtifact() finds only the relevant links.
+        artifact = self.factory.makeAccessArtifact()
+        links = [
+            self.factory.makeAccessPolicyArtifact(artifact=artifact)
+            for i in range(3)]
+        self.factory.makeAccessPolicyArtifact()
+        self.assertContentEqual(
+            links,
+            getUtility(IAccessPolicyArtifactSource).findByArtifact(
+                [artifact]))
+
+    def test_findByPolicy(self):
+        # findByPolicy() finds only the relevant links.
+        policy = self.factory.makeAccessPolicy()
+        links = [
+            self.factory.makeAccessPolicyArtifact(policy=policy)
+            for i in range(3)]
+        self.factory.makeAccessPolicyArtifact()
+        self.assertContentEqual(
+            links,
+            getUtility(IAccessPolicyArtifactSource).findByPolicy([policy]))
+
+
 class TestAccessPolicyGrant(TestCaseWithFactory):
     layer = DatabaseFunctionalLayer
 
@@ -305,3 +361,30 @@
         self.assertContentEqual(
             grants,
             getUtility(IAccessPolicyGrantSource).findByPolicy([policy]))
+
+
+class TestAccessPolicyGrantFlatSource(TestCaseWithFactory):
+    layer = DatabaseFunctionalLayer
+
+    def test_findGranteesByPolicy(self):
+        # findGranteesByPolicy() returns anyone with a grant for any of
+        # the policies or the policies' artifacts.
+        apgfs = getUtility(IAccessPolicyGrantFlatSource)
+
+        # People with grants on the policy show up.
+        policy = self.factory.makeAccessPolicy()
+        policy_grant = self.factory.makeAccessPolicyGrant(policy=policy)
+        self.assertContentEqual(
+            [policy_grant.grantee], apgfs.findGranteesByPolicy([policy]))
+
+        # But not people with grants on artifacts.
+        artifact_grant = self.factory.makeAccessArtifactGrant()
+        self.assertContentEqual(
+            [policy_grant.grantee], apgfs.findGranteesByPolicy([policy]))
+
+        # Unless the artifacts are linked to the policy.
+        self.factory.makeAccessPolicyArtifact(
+            artifact=artifact_grant.abstract_artifact, policy=policy)
+        self.assertContentEqual(
+            [policy_grant.grantee, artifact_grant.grantee],
+            apgfs.findGranteesByPolicy([policy]))

=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py	2012-02-27 05:16:39 +0000
+++ lib/lp/testing/factory.py	2012-02-27 06:22:21 +0000
@@ -146,6 +146,7 @@
 from lp.registry.interfaces.accesspolicy import (
     IAccessArtifactGrantSource,
     IAccessArtifactSource,
+    IAccessPolicyArtifactSource,
     IAccessPolicyGrantSource,
     IAccessPolicySource,
     )
@@ -4390,6 +4391,15 @@
         artifacts = getUtility(IAccessArtifactSource).ensure([concrete])
         return artifacts[0]
 
+    def makeAccessPolicyArtifact(self, artifact=None, policy=None):
+        if artifact is None:
+            artifact = self.makeAccessArtifact()
+        if policy is None:
+            policy = self.makeAccessPolicy()
+        [link] = getUtility(IAccessPolicyArtifactSource).create(
+            [(artifact, policy)])
+        return link
+
     def makeAccessArtifactGrant(self, artifact=None, grantee=None,
                                 grantor=None):
         if artifact is None: