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

Proposed by Abel Deuring
Status: Merged
Approved by: Aaron Bentley
Approved revision: no longer in the source branch.
Merged at revision: 16180
Proposed branch: lp:~adeuring/launchpad/sec-adapter-projectgroup-milestone
Merge into: lp:launchpad
Diff against target: 175 lines (+137/-2)
3 files modified
lib/lp/registry/configure.zcml (+2/-1)
lib/lp/registry/tests/test_milestone.py (+126/-1)
lib/lp/security.py (+9/-0)
To merge this branch: bzr merge lp:~adeuring/launchpad/sec-adapter-projectgroup-milestone
Reviewer Review Type Date Requested Status
Aaron Bentley (community) Approve
Review via email: mp+130800@code.launchpad.net

Commit message

privacy aware security adapter for IProjectGroupMilestone

Description of the change

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

To post a comment you must log in.
Revision history for this message
Aaron Bentley (abentley) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/registry/configure.zcml'
2--- lib/lp/registry/configure.zcml 2012-10-19 19:09:35 +0000
3+++ lib/lp/registry/configure.zcml 2012-10-22 14:01:21 +0000
4@@ -1102,7 +1102,8 @@
5
6 <class
7 class="lp.registry.model.milestone.ProjectMilestone">
8- <allow
9+ <require
10+ permission="launchpad.View"
11 interface="lp.registry.interfaces.milestone.IProjectGroupMilestone"/>
12 </class>
13 <adapter
14
15=== modified file 'lib/lp/registry/tests/test_milestone.py'
16--- lib/lp/registry/tests/test_milestone.py 2012-10-19 14:51:41 +0000
17+++ lib/lp/registry/tests/test_milestone.py 2012-10-22 14:01:21 +0000
18@@ -220,7 +220,7 @@
19 self.assertRaises(Unauthorized, setattr, obj, name, None)
20
21 def test_access_for_anonymous(self):
22- # Anonymous users have access to to public attributes of
23+ # Anonymous users have access to public attributes of
24 # milestones for private and public products.
25 with person_logged_in(ANONYMOUS):
26 self.assertAccessAuthorized(
27@@ -549,3 +549,128 @@
28 self.assertEqual(
29 IInformationType(milestone).information_type,
30 information_type)
31+
32+
33+class ProjectMilestoneSecurityAdaperTestCase(TestCaseWithFactory):
34+ """A TestCase for the security adapter of IProjectGroupMilestone."""
35+
36+ layer = DatabaseFunctionalLayer
37+
38+ def setUp(self):
39+ super(ProjectMilestoneSecurityAdaperTestCase, self).setUp()
40+ project_group = self.factory.makeProject()
41+ public_product = self.factory.makeProduct(project=project_group)
42+ self.factory.makeMilestone(
43+ product=public_product, name='public-milestone')
44+ self.proprietary_product_owner = self.factory.makePerson()
45+ self.proprietary_product = self.factory.makeProduct(
46+ project=project_group,
47+ owner=self.proprietary_product_owner,
48+ information_type=InformationType.PROPRIETARY)
49+ self.factory.makeMilestone(
50+ product=self.proprietary_product, name='proprietary-milestone')
51+ with person_logged_in(self.proprietary_product_owner):
52+ milestone_1, milestone_2 = project_group.milestones
53+ if milestone_1.name == 'public-milestone':
54+ self.public_projectgroup_milestone = milestone_1
55+ self.proprietary_projectgroup_milestone = milestone_2
56+ else:
57+ self.public_projectgroup_milestone = milestone_2
58+ self.proprietary_projectgroup_milestone = milestone_1
59+
60+ expected_get_permissions = {
61+ 'launchpad.View': set((
62+ '_getOfficialTagClause', 'active', 'addBugSubscription',
63+ 'addBugSubscriptionFilter', 'addSubscription',
64+ 'bug_subscriptions', 'bugtasks', 'closeBugsAndBlueprints',
65+ 'code_name', 'createProductRelease', 'dateexpected',
66+ 'destroySelf', 'displayname', 'distribution', 'distroseries',
67+ 'getBugTaskWeightFunction', 'getSpecifications',
68+ 'getSubscription', 'getSubscriptions',
69+ 'getUsedBugTagsWithOpenCounts', 'id', 'name',
70+ 'official_bug_tags', 'parent_subscription_target', 'product',
71+ 'product_release', 'productseries', 'removeBugSubscription',
72+ 'searchTasks', 'series_target', 'summary', 'target',
73+ 'target_type_display', 'title', 'userCanAlterBugSubscription',
74+ 'userCanAlterSubscription', 'userHasBugSubscriptions')),
75+ }
76+
77+ def test_get_permissions(self):
78+ checker = getChecker(self.public_projectgroup_milestone)
79+ self.checkPermissions(
80+ self.expected_get_permissions, checker.get_permissions, 'get')
81+
82+ # Project milestones are read-only objects, so no set permissions.
83+ expected_set_permissions = {
84+ }
85+
86+ def test_set_permissions(self):
87+ checker = getChecker(self.public_projectgroup_milestone)
88+ self.checkPermissions(
89+ self.expected_set_permissions, checker.set_permissions, 'set')
90+
91+ def assertAccessAuthorized(self, attribute_names, obj):
92+ # Try to access the given attributes of obj. No exception
93+ # should be raised.
94+ for name in attribute_names:
95+ # class Milestone does not implement all attributes defined by
96+ # class IMilestone. AttributeErrors caused by attempts to
97+ # access these attribues are not relevant here: We simply
98+ # want to be sure that no Unauthorized error is raised.
99+ try:
100+ getattr(obj, name)
101+ except AttributeError:
102+ pass
103+
104+ def assertAccessUnauthorized(self, attribute_names, obj):
105+ # Try to access the given attributes of obj. Unauthorized
106+ # should be raised.
107+ for name in attribute_names:
108+ self.assertRaises(Unauthorized, getattr, obj, name)
109+
110+ def test_access_for_anonymous(self):
111+ # Anonymous users have access to public project group milestones.
112+ with person_logged_in(ANONYMOUS):
113+ self.assertAccessAuthorized(
114+ self.expected_get_permissions['launchpad.View'],
115+ self.public_projectgroup_milestone)
116+
117+ # ...but not to private project group milestones.
118+ self.assertAccessUnauthorized(
119+ self.expected_get_permissions['launchpad.View'],
120+ self.proprietary_projectgroup_milestone)
121+
122+ def test_access_for_ordinary_user(self):
123+ # Regular users have to public project group milestones.
124+ user = self.factory.makePerson()
125+ with person_logged_in(user):
126+ self.assertAccessAuthorized(
127+ self.expected_get_permissions['launchpad.View'],
128+ self.public_projectgroup_milestone)
129+
130+ # ...but not to private project group milestones.
131+ self.assertAccessUnauthorized(
132+ self.expected_get_permissions['launchpad.View'],
133+ self.proprietary_projectgroup_milestone)
134+
135+ def test_access_for_user_with_grant_for_private_product(self):
136+ # Users with a policy grant for a private product have access
137+ # to private project group milestones.
138+ user = self.factory.makePerson()
139+ with person_logged_in(self.proprietary_product_owner):
140+ getUtility(IService, 'sharing').sharePillarInformation(
141+ self.proprietary_product, user, self.proprietary_product_owner,
142+ {InformationType.PROPRIETARY: SharingPermission.ALL})
143+
144+ with person_logged_in(user):
145+ self.assertAccessAuthorized(
146+ self.expected_get_permissions['launchpad.View'],
147+ self.proprietary_projectgroup_milestone)
148+
149+ def test_access_for_product_owner(self):
150+ # The owner of a private product can access a rpivate project group
151+ # milestone.
152+ with person_logged_in(self.proprietary_product_owner):
153+ self.assertAccessAuthorized(
154+ self.expected_get_permissions['launchpad.View'],
155+ self.proprietary_projectgroup_milestone)
156
157=== modified file 'lib/lp/security.py'
158--- lib/lp/security.py 2012-10-19 12:58:33 +0000
159+++ lib/lp/security.py 2012-10-22 14:01:21 +0000
160@@ -718,6 +718,15 @@
161 user.in_admin)
162
163
164+class ViewProjectMilestone(DelegatedAuthorization):
165+ permission = 'launchpad.View'
166+ usedfor = IProjectGroupMilestone
167+
168+ def __init__(self, obj):
169+ super(ViewProjectMilestone, self).__init__(
170+ obj, obj.product, 'launchpad.View')
171+
172+
173 class EditProjectMilestoneNever(AuthorizationBase):
174 permission = 'launchpad.Edit'
175 usedfor = IProjectGroupMilestone