Merge lp:~abentley/launchpad/workitems-for-private-blueprints into lp:launchpad

Proposed by Aaron Bentley
Status: Merged
Approved by: Aaron Bentley
Approved revision: no longer in the source branch.
Merged at revision: 16154
Proposed branch: lp:~abentley/launchpad/workitems-for-private-blueprints
Merge into: lp:launchpad
Diff against target: 219 lines (+66/-12)
6 files modified
lib/lp/blueprints/browser/person_upcomingwork.py (+2/-1)
lib/lp/blueprints/browser/tests/test_person_upcomingwork.py (+24/-0)
lib/lp/registry/interfaces/person.py (+5/-1)
lib/lp/registry/model/person.py (+4/-2)
lib/lp/registry/tests/test_person.py (+27/-7)
lib/lp/testing/factory.py (+4/-1)
To merge this branch: bzr merge lp:~abentley/launchpad/workitems-for-private-blueprints
Reviewer Review Type Date Requested Status
Benji York (community) code Approve
Review via email: mp+129736@code.launchpad.net

Commit message

Filter non-Public blueprints for +upcomingwork.

Description of the change

= Summary =
Fix bug #1056891: Specification privacy: upcoming work won't load for anyone if there's any private data on it

== Proposed fix ==
Use standard filter to ensure that only specifications visible to the viewer are listed.

== Pre-implementation notes ==
None

== LOC Rationale ==
Part of private products

== Implementation details ==
Add user parameter to getAssignedSpecificationWorkItemsDueBefore, supply view.user.

Update LaunchpadObjectFactory.makeSpecification to use milestone.product as needed.

== Tests ==
bin/test -t test_non_public_specifications -t Test_getAssignedSpecificationWorkItemsDueBefore

== Demo and Q/A ==
 1. create a project with other/proprietary license as a regular user
 2. create a milestone targetted to a date in the future (in the next 180 days for it to show up on +upcomingwork page)
 3. create a private blueprint, assign it to yourself and milestone from 2
 4. add a workitem to the blueprint
 5. check /people/+me/+upcomingwork to ensure it displays correctly for yourself
 6. log in as a different user and load that same page

It should omit the proprietary spec's workitem, but otherwise work normally.

= Launchpad lint =

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/blueprints/browser/tests/test_person_upcomingwork.py
  lib/lp/testing/factory.py
  lib/lp/blueprints/browser/person_upcomingwork.py
  lib/lp/registry/model/person.py
  lib/lp/registry/tests/test_person.py

To post a comment you must log in.
Revision history for this message
Benji York (benji) wrote :

Looks good.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/blueprints/browser/person_upcomingwork.py'
2--- lib/lp/blueprints/browser/person_upcomingwork.py 2012-07-05 11:47:25 +0000
3+++ lib/lp/blueprints/browser/person_upcomingwork.py 2012-10-16 19:14:21 +0000
4@@ -277,7 +277,8 @@
5 Only work items whose milestone have a due date between today and the
6 given cut-off date are included in the results.
7 """
8- workitems = person.getAssignedSpecificationWorkItemsDueBefore(cutoff_date)
9+ workitems = person.getAssignedSpecificationWorkItemsDueBefore(cutoff_date,
10+ user)
11 # For every specification that has work items in the list above, create
12 # one SpecWorkItemContainer holding the work items from that spec that are
13 # targeted to the same milestone and assigned to this person (or its
14
15=== modified file 'lib/lp/blueprints/browser/tests/test_person_upcomingwork.py'
16--- lib/lp/blueprints/browser/tests/test_person_upcomingwork.py 2012-07-24 06:39:54 +0000
17+++ lib/lp/blueprints/browser/tests/test_person_upcomingwork.py 2012-10-16 19:14:21 +0000
18@@ -11,6 +11,7 @@
19
20 from zope.security.proxy import removeSecurityProxy
21
22+from lp.app.enums import InformationType
23 from lp.blueprints.browser.person_upcomingwork import (
24 GenericWorkItem,
25 getWorkItemsDueBefore,
26@@ -391,6 +392,29 @@
27 with anonymous_logged_in():
28 self.assertIn(bugtask.bug.title, tomorrows_group)
29
30+ def test_non_public_specifications(self):
31+ """Work items for non-public specs are filtered correctly."""
32+ person = self.factory.makePerson()
33+ proprietary_spec = self.factory.makeSpecification(
34+ information_type=InformationType.PROPRIETARY)
35+ today_milestone = self.factory.makeMilestone(
36+ dateexpected=self.today, product=proprietary_spec.product)
37+ public_workitem = self.factory.makeSpecificationWorkItem(
38+ assignee=person, milestone=today_milestone)
39+ proprietary_workitem = self.factory.makeSpecificationWorkItem(
40+ assignee=person, milestone=today_milestone,
41+ specification=proprietary_spec)
42+ browser = self.getViewBrowser(
43+ person, view_name='+upcomingwork')
44+ self.assertIn(public_workitem.specification.name, browser.contents)
45+ self.assertNotIn(proprietary_workitem.specification.name,
46+ browser.contents)
47+ browser = self.getViewBrowser(
48+ person, view_name='+upcomingwork',
49+ user=proprietary_workitem.specification.product.owner)
50+ self.assertIn(proprietary_workitem.specification.name,
51+ browser.contents)
52+
53
54 class TestPersonUpcomingWorkView(TestCaseWithFactory):
55
56
57=== modified file 'lib/lp/registry/interfaces/person.py'
58--- lib/lp/registry/interfaces/person.py 2012-09-08 13:47:12 +0000
59+++ lib/lp/registry/interfaces/person.py 2012-10-16 19:14:21 +0000
60@@ -1390,10 +1390,14 @@
61 :return: a boolean.
62 """
63
64- def getAssignedSpecificationWorkItemsDueBefore(date):
65+ def getAssignedSpecificationWorkItemsDueBefore(date, user):
66 """Return SpecificationWorkItems assigned to this person (or members
67 of this team) and whose milestone is due between today and the given
68 date (inclusive).
69+
70+ user specifies the user who is viewing the list; items they cannot see
71+ are filtered out. None indicates the anonymous user who can only see
72+ public items.
73 """
74
75 def getAssignedBugTasksDueBefore(date, user):
76
77=== modified file 'lib/lp/registry/model/person.py'
78--- lib/lp/registry/model/person.py 2012-10-03 07:25:14 +0000
79+++ lib/lp/registry/model/person.py 2012-10-16 19:14:21 +0000
80@@ -128,6 +128,7 @@
81 SpecificationSort,
82 )
83 from lp.blueprints.model.specification import (
84+ get_specification_privacy_filter,
85 HasSpecificationsMixin,
86 Specification,
87 )
88@@ -1462,7 +1463,7 @@
89 return list(Store.of(self).find(
90 TeamParticipation.personID, TeamParticipation.teamID == self.id))
91
92- def getAssignedSpecificationWorkItemsDueBefore(self, date):
93+ def getAssignedSpecificationWorkItemsDueBefore(self, date, user):
94 """See `IPerson`."""
95 from lp.registry.model.person import Person
96 from lp.registry.model.product import Product
97@@ -1479,7 +1480,8 @@
98 Specification.milestoneID) == Milestone.id),
99 ]
100 today = datetime.today().date()
101- query = AND(
102+ query = And(
103+ get_specification_privacy_filter(user),
104 Milestone.dateexpected <= date, Milestone.dateexpected >= today,
105 WorkItem.deleted == False,
106 OR(WorkItem.assignee_id.is_in(self.participant_ids),
107
108=== modified file 'lib/lp/registry/tests/test_person.py'
109--- lib/lp/registry/tests/test_person.py 2012-09-28 06:15:58 +0000
110+++ lib/lp/registry/tests/test_person.py 2012-10-16 19:14:21 +0000
111@@ -1225,7 +1225,7 @@
112 milestone=self.future_milestone)
113
114 workitems = self.team.getAssignedSpecificationWorkItemsDueBefore(
115- self.current_milestone.dateexpected)
116+ self.current_milestone.dateexpected, self.team)
117
118 self.assertEqual([workitem], list(workitems))
119
120@@ -1238,7 +1238,7 @@
121 title=u'workitem', specification=assigned_spec, deleted=True)
122
123 workitems = self.team.getAssignedSpecificationWorkItemsDueBefore(
124- self.current_milestone.dateexpected)
125+ self.current_milestone.dateexpected, self.team)
126 self.assertEqual([], list(workitems))
127
128 def test_workitems_assigned_to_others_working_on_blueprint(self):
129@@ -1258,7 +1258,7 @@
130 assignee=self.factory.makePerson())
131
132 workitems = self.team.getAssignedSpecificationWorkItemsDueBefore(
133- self.current_milestone.dateexpected)
134+ self.current_milestone.dateexpected, self.team)
135
136 self.assertContentEqual([workitem, workitem_for_other_person],
137 list(workitems))
138@@ -1273,7 +1273,8 @@
139 self.factory.makeSpecificationWorkItem(
140 title=u'workitem 1', specification=spec)
141
142- workitems = self.team.getAssignedSpecificationWorkItemsDueBefore(today)
143+ workitems = self.team.getAssignedSpecificationWorkItemsDueBefore(
144+ today, self.team)
145
146 self.assertEqual([], list(workitems))
147
148@@ -1293,7 +1294,7 @@
149 milestone=self.current_milestone)
150
151 workitems = self.team.getAssignedSpecificationWorkItemsDueBefore(
152- self.current_milestone.dateexpected)
153+ self.current_milestone.dateexpected, self.team)
154
155 self.assertEqual([workitem], list(workitems))
156
157@@ -1317,10 +1318,29 @@
158 assignee=self.team.teamowner)
159
160 workitems = self.team.getAssignedSpecificationWorkItemsDueBefore(
161- self.current_milestone.dateexpected)
162+ self.current_milestone.dateexpected, self.team)
163
164 self.assertEqual([workitem], list(workitems))
165
166+ def test_listings_consider_spec_visibility(self):
167+ # This spec is visible only to the product owner, even though it is
168+ # assigned to self.team.teamowner. Therefore, it is listed only for
169+ # product.owner, not the team.
170+ product = self.factory.makeProduct(
171+ information_type=InformationType.PROPRIETARY)
172+ milestone = self.factory.makeMilestone(
173+ dateexpected=self.current_milestone.dateexpected, product=product)
174+ spec = self.factory.makeSpecification(
175+ milestone=milestone, information_type=InformationType.PROPRIETARY)
176+ workitem = self.factory.makeSpecificationWorkItem(
177+ specification=spec, assignee=self.team.teamowner)
178+ workitems = self.team.getAssignedSpecificationWorkItemsDueBefore(
179+ milestone.dateexpected, self.team)
180+ self.assertNotIn(workitem, workitems)
181+ workitems = self.team.getAssignedSpecificationWorkItemsDueBefore(
182+ milestone.dateexpected, removeSecurityProxy(product).owner)
183+ self.assertIn(workitem, workitems)
184+
185 def _makeProductSpec(self, milestone_dateexpected):
186 assignee = self.factory.makePerson()
187 with person_logged_in(self.team.teamowner):
188@@ -1362,7 +1382,7 @@
189 with StormStatementRecorder() as recorder:
190 workitems = list(
191 self.team.getAssignedSpecificationWorkItemsDueBefore(
192- dateexpected))
193+ dateexpected, self.team))
194 for workitem in workitems:
195 workitem.assignee
196 workitem.milestone
197
198=== modified file 'lib/lp/testing/factory.py'
199--- lib/lp/testing/factory.py 2012-10-15 23:20:25 +0000
200+++ lib/lp/testing/factory.py 2012-10-16 19:14:21 +0000
201@@ -2082,6 +2082,9 @@
202 """
203 proprietary = (information_type not in PUBLIC_INFORMATION_TYPES and
204 information_type is not None)
205+ if (product is None and milestone is not None and
206+ milestone.productseries is not None):
207+ product = milestone.productseries.product
208 if distribution is None and product is None:
209 if proprietary:
210 specification_sharing_policy = (
211@@ -2125,7 +2128,7 @@
212 if proprietary:
213 naked_spec.target._ensurePolicies([information_type])
214 naked_spec.transitionToInformationType(
215- information_type, spec.target.owner)
216+ information_type, removeSecurityProxy(spec.target).owner)
217 if status.name not in status_names:
218 # Set the closed status after the status has a sane initial state.
219 naked_spec.definition_status = status