← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~adeuring/launchpad/sec-adapter-projectgroup-milestone into lp:launchpad

 

Abel Deuring has proposed merging lp:~adeuring/launchpad/sec-adapter-projectgroup-milestone into lp:launchpad.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~adeuring/launchpad/sec-adapter-projectgroup-milestone/+merge/130800

This branch adds a security adapter for project milestones.

We want to keep data from private products completely private,
this includes also class ProjectMilestone, the "project
representation" of milestones for products.

The changes are simple:

- require the permission launchpad.View for all properties of
  IProjectGroupMilestone
- delegate the authorization to the parent product.

tests:

./bin/test -vvt lp.registry.tests.test_milestone.ProjectMilestoneSecurityAdaperTestCase


no lint

-- 
https://code.launchpad.net/~adeuring/launchpad/sec-adapter-projectgroup-milestone/+merge/130800
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~adeuring/launchpad/sec-adapter-projectgroup-milestone into lp:launchpad.
=== modified file 'lib/lp/registry/configure.zcml'
--- lib/lp/registry/configure.zcml	2012-10-19 19:09:35 +0000
+++ lib/lp/registry/configure.zcml	2012-10-22 13:56:22 +0000
@@ -1102,7 +1102,8 @@
 
     <class
         class="lp.registry.model.milestone.ProjectMilestone">
-        <allow
+        <require
+            permission="launchpad.View"
             interface="lp.registry.interfaces.milestone.IProjectGroupMilestone"/>
     </class>
     <adapter

=== modified file 'lib/lp/registry/tests/test_milestone.py'
--- lib/lp/registry/tests/test_milestone.py	2012-10-19 14:51:41 +0000
+++ lib/lp/registry/tests/test_milestone.py	2012-10-22 13:56:22 +0000
@@ -220,7 +220,7 @@
             self.assertRaises(Unauthorized, setattr, obj, name, None)
 
     def test_access_for_anonymous(self):
-        # Anonymous users have access to to public attributes of
+        # Anonymous users have access to public attributes of
         # milestones for private and public products.
         with person_logged_in(ANONYMOUS):
             self.assertAccessAuthorized(
@@ -549,3 +549,128 @@
             self.assertEqual(
                 IInformationType(milestone).information_type,
                 information_type)
+
+
+class ProjectMilestoneSecurityAdaperTestCase(TestCaseWithFactory):
+    """A TestCase for the security adapter of IProjectGroupMilestone."""
+
+    layer = DatabaseFunctionalLayer
+
+    def setUp(self):
+        super(ProjectMilestoneSecurityAdaperTestCase, self).setUp()
+        project_group = self.factory.makeProject()
+        public_product = self.factory.makeProduct(project=project_group)
+        self.factory.makeMilestone(
+            product=public_product, name='public-milestone')
+        self.proprietary_product_owner = self.factory.makePerson()
+        self.proprietary_product = self.factory.makeProduct(
+            project=project_group,
+            owner=self.proprietary_product_owner,
+            information_type=InformationType.PROPRIETARY)
+        self.factory.makeMilestone(
+            product=self.proprietary_product, name='proprietary-milestone')
+        with person_logged_in(self.proprietary_product_owner):
+            milestone_1, milestone_2 = project_group.milestones
+            if milestone_1.name == 'public-milestone':
+                self.public_projectgroup_milestone = milestone_1
+                self.proprietary_projectgroup_milestone = milestone_2
+            else:
+                self.public_projectgroup_milestone = milestone_2
+                self.proprietary_projectgroup_milestone = milestone_1
+
+    expected_get_permissions = {
+        'launchpad.View': set((
+            '_getOfficialTagClause', 'active', 'addBugSubscription',
+            'addBugSubscriptionFilter', 'addSubscription',
+            'bug_subscriptions', 'bugtasks', 'closeBugsAndBlueprints',
+            'code_name', 'createProductRelease', 'dateexpected',
+            'destroySelf', 'displayname', 'distribution', 'distroseries',
+            'getBugTaskWeightFunction', 'getSpecifications',
+            'getSubscription', 'getSubscriptions',
+            'getUsedBugTagsWithOpenCounts', 'id', 'name',
+            'official_bug_tags', 'parent_subscription_target', 'product',
+            'product_release', 'productseries', 'removeBugSubscription',
+            'searchTasks', 'series_target', 'summary', 'target',
+            'target_type_display', 'title', 'userCanAlterBugSubscription',
+            'userCanAlterSubscription', 'userHasBugSubscriptions')),
+        }
+
+    def test_get_permissions(self):
+        checker = getChecker(self.public_projectgroup_milestone)
+        self.checkPermissions(
+            self.expected_get_permissions, checker.get_permissions, 'get')
+
+    # Project milestones are read-only objects, so no set permissions.
+    expected_set_permissions = {
+        }
+
+    def test_set_permissions(self):
+        checker = getChecker(self.public_projectgroup_milestone)
+        self.checkPermissions(
+            self.expected_set_permissions, checker.set_permissions, 'set')
+
+    def assertAccessAuthorized(self, attribute_names, obj):
+        # Try to access the given attributes of obj. No exception
+        # should be raised.
+        for name in attribute_names:
+            # class Milestone does not implement all attributes defined by
+            # class IMilestone. AttributeErrors caused by attempts to
+            # access these attribues are not relevant here: We simply
+            # want to be sure that no Unauthorized error is raised.
+            try:
+                getattr(obj, name)
+            except AttributeError:
+                pass
+
+    def assertAccessUnauthorized(self, attribute_names, obj):
+        # Try to access the given attributes of obj. Unauthorized
+        # should be raised.
+        for name in attribute_names:
+            self.assertRaises(Unauthorized, getattr, obj, name)
+
+    def test_access_for_anonymous(self):
+        # Anonymous users have access to public project group milestones.
+        with person_logged_in(ANONYMOUS):
+            self.assertAccessAuthorized(
+                self.expected_get_permissions['launchpad.View'],
+                self.public_projectgroup_milestone)
+
+            # ...but not to private project group milestones.
+            self.assertAccessUnauthorized(
+                self.expected_get_permissions['launchpad.View'],
+                self.proprietary_projectgroup_milestone)
+
+    def test_access_for_ordinary_user(self):
+        # Regular users have to public project group milestones.
+        user = self.factory.makePerson()
+        with person_logged_in(user):
+            self.assertAccessAuthorized(
+                self.expected_get_permissions['launchpad.View'],
+                self.public_projectgroup_milestone)
+
+            # ...but not to private project group milestones.
+            self.assertAccessUnauthorized(
+                self.expected_get_permissions['launchpad.View'],
+                self.proprietary_projectgroup_milestone)
+
+    def test_access_for_user_with_grant_for_private_product(self):
+        # Users with a policy grant for a private product have access
+        # to private project group milestones.
+        user = self.factory.makePerson()
+        with person_logged_in(self.proprietary_product_owner):
+            getUtility(IService, 'sharing').sharePillarInformation(
+                self.proprietary_product, user, self.proprietary_product_owner,
+                {InformationType.PROPRIETARY: SharingPermission.ALL})
+
+        with person_logged_in(user):
+            self.assertAccessAuthorized(
+                self.expected_get_permissions['launchpad.View'],
+                self.proprietary_projectgroup_milestone)
+
+    def test_access_for_product_owner(self):
+        # The owner of a private product can access a rpivate project group
+        # milestone.
+        with person_logged_in(self.proprietary_product_owner):
+            self.assertAccessAuthorized(
+                self.expected_get_permissions['launchpad.View'],
+                self.proprietary_projectgroup_milestone)

=== modified file 'lib/lp/security.py'
--- lib/lp/security.py	2012-10-19 12:58:33 +0000
+++ lib/lp/security.py	2012-10-22 13:56:22 +0000
@@ -718,6 +718,15 @@
                 user.in_admin)
 
 
+class ViewProjectMilestone(DelegatedAuthorization):
+    permission = 'launchpad.View'
+    usedfor = IProjectGroupMilestone
+
+    def __init__(self, obj):
+        super(ViewProjectMilestone, self).__init__(
+            obj, obj.product, 'launchpad.View')
+
+
 class EditProjectMilestoneNever(AuthorizationBase):
     permission = 'launchpad.Edit'
     usedfor = IProjectGroupMilestone


Follow ups