Merge lp:~wallyworld/launchpad/pillar-owners-private-bug-visibility-702429 into lp:launchpad

Proposed by Ian Booth
Status: Merged
Approved by: Curtis Hovey
Approved revision: no longer in the source branch.
Merged at revision: 14038
Proposed branch: lp:~wallyworld/launchpad/pillar-owners-private-bug-visibility-702429
Merge into: lp:launchpad
Diff against target: 272 lines (+101/-41)
4 files modified
lib/lp/bugs/model/bug.py (+4/-29)
lib/lp/bugs/model/bugtask.py (+44/-3)
lib/lp/bugs/tests/test_bugtask_search.py (+48/-9)
lib/lp/services/features/flags.py (+5/-0)
To merge this branch: bzr merge lp:~wallyworld/launchpad/pillar-owners-private-bug-visibility-702429
Reviewer Review Type Date Requested Status
Curtis Hovey (community) code Approve
Robert Collins (community) Needs Fixing
Review via email: mp+75961@code.launchpad.net

Commit message

[r=sinzui][bug=702429] Bugtask pillar owners can see private bugs.

Description of the change

Add extra search terms to get_bug_privacy_filter_with_decorator() so that pillar owners can see private bugs.

== Implementation ==

The bug report is a little out of date. BugTaskResultset doesn't exist any more and Bug.userCanView has already been updated to do the right thing. What remained was to update the construction of the filter terms used in bug queries.

BugTask.get_bug_privacy_filter_with_decorator() was updated. Then Bug.searchAsUser was inspected and instead of just copy and paste of the SQL, there was no reason I could see why I couldn't just call the existing BigTask.get_bug_privacy_filter() method. This allows the required SQL to be set up in only one place.

Also did some drive by lint fixes.

== Tests ==

Add a new test to test_bugtask_search:
  - test_private_bug_in_search_result_pillar_owners()

Also, I broke up the existing large test_private_bug_in_search_result test into a number of individual tests.

bin/test -vvct test_bugtask -t bug.txt

== Lint ==

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/bugs/model/bug.py
  lib/lp/bugs/model/bugtask.py
  lib/lp/bugs/tests/test_bugtask_search.py

To post a comment you must log in.
Revision history for this message
Robert Collins (lifeless) wrote :

looks broadly fine but:
 - needs a feature flag to toggle on the new behaviour - we are likely to have perf issues and this lets us react quickly without panicking.
 - you need to update the security rules as well; bug *searches* inject visibility, but direct landing on such a bug won't, the user will still get a 404.

review: Needs Fixing
Revision history for this message
Ian Booth (wallyworld) wrote :

> looks broadly fine but:
> - needs a feature flag to toggle on the new behaviour - we are likely to have
> perf issues and this lets us react quickly without panicking.
> - you need to update the security rules as well; bug *searches* inject
> visibility, but direct landing on such a bug won't, the user will still get a
> 404.

I've introduced a new feature flag: disclosure.private_bug_visibility_rules.enabled
This will be used also for the work to allow defined project roles to see private bugs as per bug 696954.

The Bug.userCanView method had already been updated to include pillar owners so I believe direct landings already work as they should.

Revision history for this message
Curtis Hovey (sinzui) wrote :

I do not think the privacy access rules are correct. They do not take security_related into account per the PDF and the email I sent about giving maintainers access to private non-security items. I imagined that we would deal with the security contact role first to establish the exceptional behaviour of private and security_related that no other changes can break. I am fine with landing this first, but we must be certain that if the maintainer delegates security issues to another person, he does not have access to the private security_related bugs.

review: Needs Fixing (curtis)
Revision history for this message
Ian Booth (wallyworld) wrote :

I have updated the privacy filter to exclude security related bugs for maintainers.
The test_private_bug_in_search_result_pillar_owners test has also been updated.

Revision history for this message
Curtis Hovey (sinzui) wrote :

Thank you for making the revisions. I think the branch established some good rules to extend to other project roles in the future.

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/bugs/model/bug.py'
2--- lib/lp/bugs/model/bug.py 2011-09-23 13:14:36 +0000
3+++ lib/lp/bugs/model/bug.py 2011-09-26 02:10:30 +0000
4@@ -170,6 +170,7 @@
5 from lp.bugs.model.bugtask import (
6 BugTask,
7 bugtask_sort_key,
8+ get_bug_privacy_filter
9 )
10 from lp.bugs.model.bugwatch import BugWatch
11 from lp.bugs.model.structuralsubscription import (
12@@ -2602,35 +2603,9 @@
13 if duplicateof:
14 where_clauses.append("Bug.duplicateof = %d" % duplicateof.id)
15
16- admins = getUtility(ILaunchpadCelebrities).admin
17- if user:
18- if not user.inTeam(admins):
19- # Enforce privacy-awareness for logged-in, non-admin users,
20- # so that they can only see the private bugs that they're
21- # allowed to see.
22- where_clauses.append("""
23- (Bug.private = FALSE OR
24- Bug.id in (
25- -- Users who have a subscription to this bug.
26- SELECT BugSubscription.bug
27- FROM BugSubscription, TeamParticipation
28- WHERE
29- TeamParticipation.person = %(personid)s AND
30- BugSubscription.person = TeamParticipation.team
31- UNION
32- -- Users who are the assignee for one of the bug's
33- -- bugtasks.
34- SELECT BugTask.bug
35- FROM BugTask, TeamParticipation
36- WHERE
37- TeamParticipation.person = %(personid)s AND
38- TeamParticipation.team = BugTask.assignee
39- )
40- )""" % sqlvalues(personid=user.id))
41- else:
42- # Anonymous user; filter to include only public bugs in
43- # the search results.
44- where_clauses.append("Bug.private = FALSE")
45+ privacy_filter = get_bug_privacy_filter(user)
46+ if privacy_filter:
47+ where_clauses.append(privacy_filter)
48
49 other_params = {}
50 if orderBy:
51
52=== modified file 'lib/lp/bugs/model/bugtask.py'
53--- lib/lp/bugs/model/bugtask.py 2011-09-12 20:27:32 +0000
54+++ lib/lp/bugs/model/bugtask.py 2011-09-26 02:10:30 +0000
55@@ -1403,7 +1403,45 @@
56 # part of the WHERE condition (i.e. the bit below.) The
57 # other half of this condition (see code above) does not
58 # use TeamParticipation at all.
59- return ("""
60+ pillar_privacy_filters = ''
61+ if features.getFeatureFlag(
62+ 'disclosure.private_bug_visibility_rules.enabled'):
63+ pillar_privacy_filters = """
64+ UNION
65+ SELECT BugTask.bug
66+ FROM BugTask, TeamParticipation, Product
67+ WHERE TeamParticipation.person = %(personid)s AND
68+ TeamParticipation.team = Product.owner AND
69+ BugTask.product = Product.id AND
70+ BugTask.bug = Bug.id AND
71+ Bug.security_related IS False
72+ UNION
73+ SELECT BugTask.bug
74+ FROM BugTask, TeamParticipation, ProductSeries
75+ WHERE TeamParticipation.person = %(personid)s AND
76+ TeamParticipation.team = ProductSeries.owner AND
77+ BugTask.productseries = ProductSeries.id AND
78+ BugTask.bug = Bug.id AND
79+ Bug.security_related IS False
80+ UNION
81+ SELECT BugTask.bug
82+ FROM BugTask, TeamParticipation, Distribution
83+ WHERE TeamParticipation.person = %(personid)s AND
84+ TeamParticipation.team = Distribution.owner AND
85+ BugTask.distribution = Distribution.id AND
86+ BugTask.bug = Bug.id AND
87+ Bug.security_related IS False
88+ UNION
89+ SELECT BugTask.bug
90+ FROM BugTask, TeamParticipation, DistroSeries, Distribution
91+ WHERE TeamParticipation.person = %(personid)s AND
92+ TeamParticipation.team = Distribution.owner AND
93+ DistroSeries.distribution = Distribution.id AND
94+ BugTask.distroseries = DistroSeries.id AND
95+ BugTask.bug = Bug.id AND
96+ Bug.security_related IS False
97+ """ % sqlvalues(personid=user.id)
98+ query = """
99 (Bug.private = FALSE OR EXISTS (
100 SELECT BugSubscription.bug
101 FROM BugSubscription, TeamParticipation
102@@ -1416,9 +1454,12 @@
103 WHERE TeamParticipation.person = %(personid)s AND
104 TeamParticipation.team = BugTask.assignee AND
105 BugTask.bug = Bug.id
106+ %(extra_filters)s
107 ))
108- """ % sqlvalues(personid=user.id),
109- _make_cache_user_can_view_bug(user))
110+ """ % dict(
111+ personid=quote(user.id),
112+ extra_filters=pillar_privacy_filters)
113+ return query, _make_cache_user_can_view_bug(user)
114
115
116 def build_tag_set_query(joiner, tags):
117
118=== modified file 'lib/lp/bugs/tests/test_bugtask_search.py'
119--- lib/lp/bugs/tests/test_bugtask_search.py 2011-06-20 07:03:18 +0000
120+++ lib/lp/bugs/tests/test_bugtask_search.py 2011-09-26 02:10:30 +0000
121@@ -54,6 +54,9 @@
122 )
123 from lp.testing.matchers import HasQueryCount
124
125+PRIVATE_BUG_VISIBILITY_FLAG = {
126+ 'disclosure.private_bug_visibility_rules.enabled': 'on'}
127+
128
129 class SearchTestBase:
130 """A mixin class with tests useful for all targets and search variants."""
131@@ -92,20 +95,26 @@
132 params = self.getBugTaskSearchParams(user=None)
133 self.assertSearchFinds(params, self.bugtasks)
134
135- def test_private_bug_in_search_result(self):
136+ def test_private_bug_in_search_result_anonymous_users(self):
137 # Private bugs are not included in search results for anonymous users.
138 with person_logged_in(self.owner):
139 self.bugtasks[-1].bug.setPrivate(True, self.owner)
140 params = self.getBugTaskSearchParams(user=None)
141 self.assertSearchFinds(params, self.bugtasks[:-1])
142
143+ def test_private_bug_in_search_result_unauthorised_users(self):
144 # Private bugs are not included in search results for ordinary users.
145+ with person_logged_in(self.owner):
146+ self.bugtasks[-1].bug.setPrivate(True, self.owner)
147 user = self.factory.makePerson()
148 params = self.getBugTaskSearchParams(user=user)
149 self.assertSearchFinds(params, self.bugtasks[:-1])
150
151+ def test_private_bug_in_search_result_subscribers(self):
152 # If the user is subscribed to the bug, it is included in the
153 # search result.
154+ with person_logged_in(self.owner):
155+ self.bugtasks[-1].bug.setPrivate(True, self.owner)
156 user = self.factory.makePerson()
157 admin = getUtility(IPersonSet).getByEmail('foo.bar@canonical.com')
158 with person_logged_in(admin):
159@@ -114,18 +123,49 @@
160 params = self.getBugTaskSearchParams(user=user)
161 self.assertSearchFinds(params, self.bugtasks)
162
163+ def test_private_bug_in_search_result_admins(self):
164 # Private bugs are included in search results for admins.
165+ with person_logged_in(self.owner):
166+ self.bugtasks[-1].bug.setPrivate(True, self.owner)
167+ admin = getUtility(IPersonSet).getByEmail('foo.bar@canonical.com')
168 params = self.getBugTaskSearchParams(user=admin)
169 self.assertSearchFinds(params, self.bugtasks)
170
171+ def test_private_bug_in_search_result_assignees(self):
172 # Private bugs are included in search results for the assignee.
173+ with person_logged_in(self.owner):
174+ self.bugtasks[-1].bug.setPrivate(True, self.owner)
175+ bugtask = self.bugtasks[-1]
176 user = self.factory.makePerson()
177- bugtask = self.bugtasks[-1]
178+ admin = getUtility(IPersonSet).getByEmail('foo.bar@canonical.com')
179 with person_logged_in(admin):
180 bugtask.transitionToAssignee(user)
181 params = self.getBugTaskSearchParams(user=user)
182 self.assertSearchFinds(params, self.bugtasks)
183
184+ def test_private_bug_in_search_result_pillar_owners(self):
185+ # Private, non-security bugs are included in search results for the
186+ # pillar owners if the correct feature flag is enabled.
187+ bugtask = self.bugtasks[-1]
188+ pillar_owner = bugtask.pillar.owner
189+ with person_logged_in(self.owner):
190+ bugtask.bug.setPrivate(True, self.owner)
191+ bugtask.bug.unsubscribe(pillar_owner, self.owner)
192+ params = self.getBugTaskSearchParams(user=pillar_owner)
193+ # Check the results with the feature flag.
194+ with FeatureFixture(PRIVATE_BUG_VISIBILITY_FLAG):
195+ self.assertSearchFinds(params, self.bugtasks)
196+ # Check the results without the feature flag.
197+ self.assertSearchFinds(params, self.bugtasks[:-1])
198+
199+ # Make the bugtask security related.
200+ with person_logged_in(self.owner):
201+ bugtask.bug.setSecurityRelated(True, self.owner)
202+ bugtask.bug.unsubscribe(pillar_owner, self.owner)
203+ # It should now be excluded from the results.
204+ with FeatureFixture(PRIVATE_BUG_VISIBILITY_FLAG):
205+ self.assertSearchFinds(params, self.bugtasks[:-1])
206+
207 def test_search_by_bug_reporter(self):
208 # Search results can be limited to bugs filed by a given person.
209 bugtask = self.bugtasks[0]
210@@ -415,10 +455,10 @@
211 utc_now = datetime.now(pytz.timezone('UTC'))
212 self.assertTrue(utc_now >= self.bugtasks[2].date_closed)
213 params = self.getBugTaskSearchParams(
214- user=None, date_closed=greater_than(utc_now-timedelta(days=1)))
215+ user=None, date_closed=greater_than(utc_now - timedelta(days=1)))
216 self.assertSearchFinds(params, self.bugtasks[2:])
217 params = self.getBugTaskSearchParams(
218- user=None, date_closed=greater_than(utc_now+timedelta(days=1)))
219+ user=None, date_closed=greater_than(utc_now + timedelta(days=1)))
220 self.assertSearchFinds(params, [])
221
222 def test_created_since(self):
223@@ -561,7 +601,6 @@
224 def test_deactivated_listings_not_seen(self):
225 # Someone without permission to see deactiveated projects does
226 # not see bugtasks for deactivated projects.
227- nopriv = getUtility(IPersonSet).getByEmail('no-priv@canonical.com')
228 bugtask_set = getUtility(IBugTaskSet)
229 param = BugTaskSearchParams(user=None, fast_searchtext='Monkeys')
230 results = bugtask_set.search(param, _noprejoins=True)
231@@ -607,13 +646,13 @@
232
233 class BugTargetTestBase:
234 """A base class for the bug target mixin classes.
235-
236+
237 :ivar searchtarget: A bug context to search within.
238 :ivar searchtarget2: A sibling bug context for testing cross-context
239 searches. Created on demand when
240 getBugTaskSearchParams(multitarget=True) is called.
241- :ivar bugtasks2: Bugtasks created for searchtarget2. Twice as many are made
242- as for searchtarget.
243+ :ivar bugtasks2: Bugtasks created for searchtarget2. Twice as many are
244+ made as for searchtarget.
245 :ivar group_on: The columns to group on when calling countBugs. None
246 if the target being testing is not sensible/implemented for counting
247 bugs. For instance, grouping by project group may be interesting but
248@@ -872,7 +911,7 @@
249 owner=self.owner, project=self.searchtarget)
250 bug1 = self.factory.makeBug(product=product1)
251 bug1.default_bugtask.updateTargetNameCache()
252- bug2 = self.factory.makeBug(product=product2)
253+ self.factory.makeBug(product=product2)
254 params = self.getBugTaskSearchParams(user=None, searchtext='uct-fo')
255 # With no flag, we find the first bug.
256 self.assertSearchFinds(params, [bug1.default_bugtask])
257
258=== modified file 'lib/lp/services/features/flags.py'
259--- lib/lp/services/features/flags.py 2011-09-16 16:19:22 +0000
260+++ lib/lp/services/features/flags.py 2011-09-26 02:10:30 +0000
261@@ -133,6 +133,11 @@
262 'boolean',
263 ('Enables the display and use of the enhanced target pickers.'),
264 ''),
265+ ('disclosure.private_bug_visibility_rules.enabled',
266+ 'boolean',
267+ ('Enables the application of additional privacy filter terms in bug '
268+ 'queries to allow defined project roles to see private bugs.'),
269+ ''),
270 ('bugs.autoconfirm.enabled_distribution_names',
271 'space delimited',
272 ('Enables auto-confirming bugtasks for distributions (and their '