Merge lp:~jcsackett/launchpad/private-bugs-without-supervision into lp:launchpad

Proposed by j.c.sackett
Status: Merged
Approved by: Curtis Hovey
Approved revision: no longer in the source branch.
Merged at revision: 15113
Proposed branch: lp:~jcsackett/launchpad/private-bugs-without-supervision
Merge into: lp:launchpad
Diff against target: 725 lines (+103/-296)
9 files modified
lib/lp/bugs/doc/bugnotification-sending.txt (+6/-1)
lib/lp/bugs/doc/bugsubscription.txt (+11/-27)
lib/lp/bugs/doc/initial-bug-contacts.txt (+1/-15)
lib/lp/bugs/doc/security-teams.txt (+1/-38)
lib/lp/bugs/model/bug.py (+31/-97)
lib/lp/bugs/model/tests/test_bug.py (+40/-108)
lib/lp/bugs/tests/test_bug_mirror_access_triggers.py (+6/-2)
lib/lp/bugs/tests/test_bugvisibility.py (+7/-1)
lib/lp/services/features/flags.py (+0/-7)
To merge this branch: bzr merge lp:~jcsackett/launchpad/private-bugs-without-supervision
Reviewer Review Type Date Requested Status
Curtis Hovey (community) code Approve
Review via email: mp+102366@code.launchpad.net

This proposal supersedes a proposal from 2012-04-11.

Commit message

Fixes subscription problems when transitioning a bug to non-public information types. Also removes the unused reconcileSubscribers code involved in the transition code.

Description of the change

Summary
=======
The constantly changing setup of subscription and privacy rules led to a
regression, wherein a person marking a bug private would lose access to the
bug, preventing privacy from happening. Because we put the subscriber
reconcilation behind a feature flag and didn't activate it, the absence of a
bug supervisor prevents marking bugs private.

This branch ensures that:
* The bug reporter stays attached
* The bug assignee stays attached
* The bug supervisor is attached if it exists
* The pillar maintainer is attached if the supervisor doesn't exist

Preimp
======
Spoke with Curtis Hovey

Implementation
==============
The reconcileSubscribers, method, which is put behind a feature flag, is
removed as the implementation is now out of date and is unlikely to ever be
enabled. The flag and flag related checks are also removed.

The no f_flag fallback has been cleaned up; it now ensures the bug
supervisor/maintainer fallback works, and ensure the other expected
subscriptions.

A *number* of tests have been updated; they relied on the feature flag being
set, and weren't valid as they were. In some cases entire tests have been
removed, as they were testing for behavior that never existed without the flag,
which was never enabled anywhere.

Tests
=====
bin/test -vvct test_bug.*PrivateAndSecurityRelated

QA
==
Look at the setup in the bug; that should work.

Lint
====

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/bugs/tests/test_bugvisibility.py
  lib/lp/bugs/model/bug.py
  lib/lp/services/features/flags.py

To post a comment you must log in.
Revision history for this message
Curtis Hovey (sinzui) wrote : Posted in a previous version of this proposal

This branch reproduces the situation that drove William to put it behind the feature flag to kill it. Let's not repeat this mistake again.

These rules cannot apply to Ubuntu. it's bug supervisor does not get automatic access to private bugs, this is why we spent an extra three months to create 3 sharing policies instead of one, Ubuntu is not like any other project in Lp. Ubuntu's maintainer does not get access to private or security bugs. recall the success criteria for sharing is that that maintainer (or us) can setup Ubuntu so that ubuntu-security is the only party that can see embargoed security bugs, and apport is the only party that can see user data bugs.

There is, or was, code that checked that the project has .private_bugs or .hasCurrentCommercialSubscription() which we know will be working with private bugs. I am not certain we want check these attributes. Maybe we should just check if the pillar is not Ubuntu -- I do not think any other project took advantage of Lp defects to create policies different from what is documented.

review: Needs Fixing (code)
Revision history for this message
j.c.sackett (jcsackett) wrote : Posted in a previous version of this proposal

> This branch reproduces the situation that drove William to put it behind the
> feature flag to kill it. Let's not repeat this mistake again.
>
> These rules cannot apply to Ubuntu. it's bug supervisor does not get automatic
> access to private bugs, this is why we spent an extra three months to create 3
> sharing policies instead of one, Ubuntu is not like any other project in Lp.
> Ubuntu's maintainer does not get access to private or security bugs. recall
> the success criteria for sharing is that that maintainer (or us) can setup
> Ubuntu so that ubuntu-security is the only party that can see embargoed
> security bugs, and apport is the only party that can see user data bugs.

That's a little perplexing; if look at the deleted clause starting on line 19, you can see that the supervisor is already being given access to USERDATA bugs. So if that was the mistake, it was not resolved by the feature flag. I can certainly make the check for Ubuntu, but I want to be clear that it's an additional change from the current behavior (not that there's anything wrong with that).

> There is, or was, code that checked that the project has .private_bugs or
> .hasCurrentCommercialSubscription() which we know will be working with private
> bugs. I am not certain we want check these attributes. Maybe we should just
> check if the pillar is not Ubuntu -- I do not think any other project took
> advantage of Lp defects to create policies different from what is documented.

I'm going to vote for "was". Or, possibly, that check is only being done on bug creation. I think just adding a check for the pillar being Ubuntu is sufficient--we already special case that, so it doesn't feel too wrong to make that check.

I will update the code so that if the pillar is Ubuntu, the bug supervisor is not given access to USERDATA bugs.

Revision history for this message
Curtis Hovey (sinzui) wrote : Posted in a previous version of this proposal

We discusses this on IRC. The issue is that while we think the rules for Ubuntu are wrong at this moment, Ubuntu has not reported any issue with changing bugs to private, only projects have this issue. Maybe this is not an issue because apport does not using this path through the code. We will talk with the purple squad to resolve this.

review: Needs Information (code)
Revision history for this message
Curtis Hovey (sinzui) wrote : Posted in a previous version of this proposal

We discussed this and believe your change is correct. This is good to land.

review: Approve (code)
Revision history for this message
j.c.sackett (jcsackett) wrote :

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Here's a clean diff of just what's changed since the previously
approved MP.
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.11 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iQEcBAEBAgAGBQJPjbI4AAoJEJvCBZ3E2NUPRvkH/iKGm03AAwiU4ftu2Crgb872
sBgMsDUKeDKcrvZqZC7U14OtMLeQew6Fuha6rUZghjHW+m+kQinyclKl6zdtZZAj
7zlcok4eNYQP2UMXp19OJIQkW1RUQPLqzA8x1og7Lz+KggiaSxtD9fG/QE4msGoE
vqGy59tJMZx3FwBmlsQzowNFHcQlFYNeUYGa80r7zx0ZfXJZoBnxc6RMjP5FGQwL
wk1aq20sRzfhKZxCz9aR/fP4JEAxjpqFN5/jaVgSM8TL8yU7UhHalofDwEyfm+iA
hvjjds4/gv+sjkfO/ckMQIx9hSkNqjekeKnzfLwQs4GCKPdrJ8jCuKwoAzl923k=
=RZ+X
-----END PGP SIGNATURE-----

1=== modified file 'lib/lp/bugs/doc/bugnotification-sending.txt'
2--- lib/lp/bugs/doc/bugnotification-sending.txt 2012-04-17 17:59:54 +0000
3+++ lib/lp/bugs/doc/bugnotification-sending.txt 2012-04-17 18:01:41 +0000
4@@ -756,9 +756,10 @@
5
6 >>> for message in trigger_and_get_email_messages(bug_three):
7 ... print message['To'], message.get_all('X-Launchpad-Bug-Private')
8+ foo.bar@canonical.com ['yes']
9+ mark@example.com ['yes']
10 test@canonical.com ['yes']
11
12-
13 The X-Launchpad-Bug-Security-Vulnerability header
14 -------------------------------------------------
15
16@@ -772,6 +773,8 @@
17 >>> for message in trigger_and_get_email_messages(bug_three):
18 ... print message['To'], (
19 ... message.get_all('X-Launchpad-Bug-Security-Vulnerability'))
20+ foo.bar@canonical.com ['no']
21+ mark@example.com ['no']
22 test@canonical.com ['no']
23
24 The presence of the security flag on a bug is, surprise, denoted by a
25@@ -786,6 +789,8 @@
26 >>> for message in trigger_and_get_email_messages(bug_three):
27 ... print message['To'], (
28 ... message.get_all('X-Launchpad-Bug-Security-Vulnerability'))
29+ foo.bar@canonical.com ['yes']
30+ mark@example.com ['yes']
31 test@canonical.com ['yes']
32
33
34
35=== modified file 'lib/lp/bugs/doc/bugsubscription.txt'
36--- lib/lp/bugs/doc/bugsubscription.txt 2012-04-17 17:58:37 +0000
37+++ lib/lp/bugs/doc/bugsubscription.txt 2012-04-17 18:01:41 +0000
38@@ -380,17 +380,7 @@
39 ()
40
41 When a bug is marked private, specific people like the bugtask bug supervisors
42-will be automatically subscribed, and only specifically allowed existing
43-direct subscribers (eg bugtask pillar maintainers) will remain subscribed.
44-
45-We currently use a feature flag to control who is subscribed when a bug is
46-made private.
47-
48- >>> from lp.services.features.testing import FeatureFixture
49- >>> feature_flag = {
50- ... 'disclosure.enhanced_private_bug_subscriptions.enabled': 'on'}
51- >>> flags = FeatureFixture(feature_flag)
52- >>> flags.setUp()
53+will be automatically subscribed.
54
55 >>> from zope.event import notify
56
57@@ -438,26 +428,20 @@
58 >>> print_displayname(linux_source_bug.getDirectSubscribers())
59 Foo Bar
60 Mark Shuttleworth
61+ Robert Collins
62+ Ubuntu Team
63
64 >>> print_displayname(linux_source_bug.getIndirectSubscribers())
65 Martin Pitt
66 No Privileges Person
67- Robert Collins
68 Sample Person
69 Scott James Remnant
70- Ubuntu Team
71
72 >>> print_displayname(linux_source_bug.getAlsoNotifiedSubscribers())
73 Martin Pitt
74 No Privileges Person
75- Robert Collins
76 Sample Person
77 Scott James Remnant
78- Ubuntu Team
79-
80-Clean up the feature flag.
81-
82- >>> flags.cleanUp()
83
84 To find out which email addresses should receive a notification email on
85 a bug, and why, IBug.getBugNotificationRecipients() assembles an
86@@ -472,8 +456,8 @@
87 [('foo.bar@canonical.com', 'Subscriber'),
88 ('mark@example.com', 'Subscriber'),
89 ('no-priv@canonical.com', u'Subscriber (linux-source-2.6.15 in Ubuntu)'),
90- ('robertc@robertcollins.net', u'Subscriber (Mozilla Firefox)'),
91- ('support@ubuntu.com', u'Subscriber (Ubuntu) @ubuntu-team'),
92+ ('robertc@robertcollins.net', 'Subscriber'),
93+ ('support@ubuntu.com', u'Subscriber @ubuntu-team'),
94 ('test@canonical.com', 'Assignee')]
95
96 If IBug.getBugNotificationRecipients() is passed a BugNotificationLevel
97@@ -486,8 +470,8 @@
98 >>> [(address, recipients.getReason(address)[1]) for address in addresses]
99 [('foo.bar@canonical.com', 'Subscriber'),
100 ('mark@example.com', 'Subscriber'),
101- ('robertc@robertcollins.net', u'Subscriber (Mozilla Firefox)'),
102- ('support@ubuntu.com', u'Subscriber (Ubuntu) @ubuntu-team'),
103+ ('robertc@robertcollins.net', 'Subscriber'),
104+ ('support@ubuntu.com', u'Subscriber @ubuntu-team'),
105 ('test@canonical.com', 'Assignee')]
106
107 When Sample Person is unsubscribed from linux_source_bug, he is no
108@@ -501,8 +485,8 @@
109 >>> [(address, recipients.getReason(address)[1]) for address in addresses]
110 [('foo.bar@canonical.com', 'Subscriber'),
111 ('mark@example.com', 'Subscriber'),
112- ('robertc@robertcollins.net', u'Subscriber (Mozilla Firefox)'),
113- ('support@ubuntu.com', u'Subscriber (Ubuntu) @ubuntu-team'),
114+ ('robertc@robertcollins.net', 'Subscriber'),
115+ ('support@ubuntu.com', u'Subscriber @ubuntu-team'),
116 ('test@canonical.com', 'Assignee')]
117
118 ...but remains included for the level LIFECYCLE.
119@@ -515,8 +499,8 @@
120 [('foo.bar@canonical.com', 'Subscriber'),
121 ('mark@example.com', 'Subscriber'),
122 ('no-priv@canonical.com', u'Subscriber (linux-source-2.6.15 in Ubuntu)'),
123- ('robertc@robertcollins.net', u'Subscriber (Mozilla Firefox)'),
124- ('support@ubuntu.com', u'Subscriber (Ubuntu) @ubuntu-team'),
125+ ('robertc@robertcollins.net', 'Subscriber'),
126+ ('support@ubuntu.com', u'Subscriber @ubuntu-team'),
127 ('test@canonical.com', 'Assignee')]
128
129 To find out if someone is already directly subscribed to a bug, call
130
131=== modified file 'lib/lp/bugs/doc/initial-bug-contacts.txt'
132--- lib/lp/bugs/doc/initial-bug-contacts.txt 2012-04-17 17:58:37 +0000
133+++ lib/lp/bugs/doc/initial-bug-contacts.txt 2012-04-17 18:01:41 +0000
134@@ -288,16 +288,6 @@
135 u'Sample Person', u'Steve Alexander', u'Ubuntu Gnome Team',
136 u'Ubuntu Team']
137
138-
139-We currently use a feature flag to control who is subscribed when a bug is
140-made private.
141-
142- >>> from lp.services.features.testing import FeatureFixture
143- >>> feature_flag = {
144- ... 'disclosure.enhanced_private_bug_subscriptions.enabled': 'on'}
145- >>> flags = FeatureFixture(feature_flag)
146- >>> flags.setUp()
147-
148 If a bug is private, no changes are made to the subscriber list when a
149 bug is reassigned to a different package.
150
151@@ -329,13 +319,9 @@
152 the unallowed subscribers are removed:
153
154 >>> subscriber_names(bug_one_in_ubuntu_firefox.bug)
155- [u'Foo Bar', u'Mark Shuttleworth', u'Sample Person',
156+ [u'Foo Bar', u'Sample Person',
157 u'Steve Alexander', u'Ubuntu Team']
158
159-Clean up the feature flag.
160-
161- >>> flags.cleanUp()
162-
163
164 Product Bug Supervisors and Bug Tasks
165 -------------------------------------
166
167=== modified file 'lib/lp/bugs/doc/security-teams.txt'
168--- lib/lp/bugs/doc/security-teams.txt 2012-04-17 17:58:37 +0000
169+++ lib/lp/bugs/doc/security-teams.txt 2012-04-17 18:01:41 +0000
170@@ -280,17 +280,7 @@
171
172
173 When a bug becomes security-related, the security contacts for the pillars it
174-affects are subscribed to it. This happens regardless of whether the feature
175-flag is set.
176-
177-We currently use a feature flag to control who is subscribed when a bug is
178-made security related.
179-
180- >>> from lp.services.features.testing import FeatureFixture
181- >>> feature_flag = {
182- ... 'disclosure.enhanced_private_bug_subscriptions.enabled': 'on'}
183- >>> security_flags = FeatureFixture(feature_flag)
184- >>> security_flags.setUp()
185+affects are subscribed to it.
186
187 >>> from zope.event import notify
188 >>> from lazr.lifecycle.event import ObjectModifiedEvent
189@@ -317,30 +307,3 @@
190 Bug Reporter
191 Distribution Security Contact
192 Product Security Contact
193-
194-Clean up the feature flag.
195-
196- >>> security_flags.cleanUp()
197-
198-And once more without the feature flag.
199-
200- >>> product = factory.makeProduct()
201- >>> product.security_contact = factory.makePerson(
202- ... displayname='Product Security Contact')
203- >>> distribution = factory.makeDistribution()
204- >>> distribution.security_contact = factory.makePerson(
205- ... displayname='Distribution Security Contact')
206- >>> reporter = factory.makePerson(displayname=u'Bug Reporter')
207- >>> bug = factory.makeBug(product=product, owner=reporter)
208- >>> bug.addTask(owner=reporter, target=distribution)
209- <BugTask ...>
210- >>> old_state = Snapshot(bug, providing=IBug)
211- >>> bug.setSecurityRelated(True, getUtility(ILaunchBag).user)
212- True
213- >>> notify(ObjectModifiedEvent(bug, old_state, ['security_related']))
214- >>> for subscriber_name in sorted(
215- ... s.displayname for s in bug.getDirectSubscribers()):
216- ... print subscriber_name
217- Bug Reporter
218- Distribution Security Contact
219- Product Security Contact
220
221=== modified file 'lib/lp/bugs/model/bug.py'
222--- lib/lp/bugs/model/bug.py 2012-04-17 18:00:24 +0000
223+++ lib/lp/bugs/model/bug.py 2012-04-17 18:01:41 +0000
224@@ -1740,9 +1740,11 @@
225 if information_type in PRIVATE_INFORMATION_TYPES:
226 self.who_made_private = who
227 self.date_made_private = UTC_NOW
228+ missing_subscribers = set([who, self.owner])
229 else:
230 self.who_made_private = None
231 self.date_made_private = None
232+ missing_subscribers = set()
233 # XXX: This should be a bulk update. RBC 20100827
234 # bug=https://bugs.launchpad.net/storm/+bug/625071
235 for attachment in self.attachments_unpopulated:
236@@ -1751,7 +1753,6 @@
237 self.updateHeat()
238
239 # There are several people we need to ensure are subscribed.
240- missing_subscribers = set([who, self.owner])
241 # If the information type is userdata, we need to check for bug
242 # supervisors who aren't subscribed and should be. If there is no
243 # bug supervisor, we need to subscribe the maintainer.
244@@ -1774,8 +1775,10 @@
245 missing_subscribers.add(pillar.owner)
246
247 for s in missing_subscribers:
248- subscriptions = get_structural_subscriptions_for_bug(self, s)
249- if subscriptions.is_empty():
250+ # Don't subscribe someone if they're already subscribed via a
251+ # team.
252+ already_subscribed_teams = self.getSubscribersForPerson(s)
253+ if already_subscribed_teams.is_empty():
254 self.subscribe(s, who)
255
256 self.information_type = information_type
257
258=== modified file 'lib/lp/bugs/model/tests/test_bug.py'
259--- lib/lp/bugs/model/tests/test_bug.py 2012-04-17 18:00:24 +0000
260+++ lib/lp/bugs/model/tests/test_bug.py 2012-04-17 18:01:41 +0000
261@@ -567,14 +567,6 @@
262
263 layer = DatabaseFunctionalLayer
264
265- def setUp(self):
266- super(TestBugPrivateAndSecurityRelatedUpdatesMixin, self).setUp()
267- f_flag_str = 'disclosure.enhanced_private_bug_subscriptions.enabled'
268- feature_flag = {f_flag_str: 'on'}
269- flags = FeatureFixture(feature_flag)
270- flags.setUp()
271- self.addCleanup(flags.cleanUp)
272-
273 def test_setPrivate_subscribes_person_who_makes_bug_private(self):
274 # When setPrivate(True) is called on a bug, the person who is
275 # marking the bug private is subscribed to the bug.
276@@ -589,8 +581,8 @@
277 # marking the bug private will not be subscribed if they're
278 # already a member of a team which is a direct subscriber.
279 bug = self.factory.makeBug()
280- team = self.factory.makeTeam()
281- person = team.teamowner
282+ person = self.factory.makePerson(name='teamowner')
283+ team = self.factory.makeTeam(owner=person, name='team')
284 with person_logged_in(person):
285 bug.subscribe(team, person)
286 bug.setPrivate(True, person)
287@@ -632,78 +624,71 @@
288 return (bug, bug_owner, naked_bugtask_a, naked_bugtask_b,
289 naked_default_bugtask)
290
291- def test_setPrivateTrueAndSecurityRelatedTrue(self):
292- # When a bug is marked as private=true and security_related=true, the
293- # direct subscribers should include:
294+ def test_transition_to_EMBARGOEDSECURITY_information_type(self):
295+ # When a bug is marked as EMBARGOEDSECURITY, the direct subscribers
296+ # should include:
297 # - the bug reporter
298 # - the bugtask pillar security contacts (if set)
299 # - the person changing the state
300 # - and bug/pillar owners, drivers if they are already subscribed
301- # If the bug is for a private project, then other direct subscribers
302- # should be unsubscribed.
303
304 (bug, bug_owner, bugtask_a, bugtask_b, default_bugtask) = (
305 self.createBugTasksAndSubscribers())
306 initial_subscribers = set((
307- self.factory.makePerson(), bugtask_a.owner, bug_owner,
308- bugtask_a.pillar.security_contact, bugtask_a.pillar.driver))
309+ self.factory.makePerson(name='subscriber'), bugtask_a.owner,
310+ bug_owner, bugtask_a.pillar.security_contact,
311+ bugtask_a.pillar.driver))
312+ initial_subscribers.update(bug.getDirectSubscribers())
313
314 with person_logged_in(bug_owner):
315 for subscriber in initial_subscribers:
316 bug.subscribe(subscriber, bug_owner)
317- who = self.factory.makePerson()
318+ who = self.factory.makePerson(name='who')
319 bug.transitionToInformationType(
320 InformationType.EMBARGOEDSECURITY, who=who)
321 subscribers = bug.getDirectSubscribers()
322+ initial_subscribers.update(bug.getDirectSubscribers())
323 expected_subscribers = set((
324 bugtask_a.owner,
325- default_bugtask.pillar.bug_supervisor,
326 default_bugtask.pillar.driver,
327 default_bugtask.pillar.security_contact,
328 bug_owner, who))
329- if not self.private_project:
330- expected_subscribers.update(initial_subscribers)
331+ expected_subscribers.update(initial_subscribers)
332 self.assertContentEqual(expected_subscribers, subscribers)
333
334- def test_setPrivateTrueAndSecurityRelatedFalse(self):
335- # When a bug is marked as private=true and security_related=false, the
336- # direct subscribers should include:
337+ def test_transition_to_USERDATA_information_type(self):
338+ # When a bug is marked as USERDATA, the direct subscribers should
339+ # include:
340 # - the bug reporter
341 # - the bugtask pillar bug supervisors (if set)
342 # - the person changing the state
343 # - and bug/pillar owners, drivers if they are already subscribed
344- # If the bug is for a private project, then other direct subscribers
345- # should be unsubscribed.
346
347 (bug, bug_owner, bugtask_a, bugtask_b, default_bugtask) = (
348 self.createBugTasksAndSubscribers(private_security_related=True))
349 initial_subscribers = set((
350- self.factory.makePerson(), bug_owner,
351+ self.factory.makePerson(name='subscriber'), bug_owner,
352 bugtask_a.pillar.security_contact, bugtask_a.pillar.driver))
353
354 with person_logged_in(bug_owner):
355 for subscriber in initial_subscribers:
356 bug.subscribe(subscriber, bug_owner)
357- who = self.factory.makePerson()
358+ who = self.factory.makePerson(name='who')
359 bug.transitionToInformationType(InformationType.USERDATA, who)
360 subscribers = bug.getDirectSubscribers()
361 expected_subscribers = set((
362 default_bugtask.pillar.bug_supervisor,
363 default_bugtask.pillar.driver,
364 bug_owner, who))
365- if not self.private_project:
366- expected_subscribers.update(initial_subscribers)
367- expected_subscribers.remove(bugtask_a.pillar.security_contact)
368+ expected_subscribers.update(initial_subscribers)
369 self.assertContentEqual(expected_subscribers, subscribers)
370
371- def test_setPrivateFalseAndSecurityRelatedTrue(self):
372- # When a bug is marked as private=false and security_related=true, the
373- # direct subscribers should include:
374+ def test_transition_to_UNEMBARGOEDSECURITY_information_type(self):
375+ # When a security bug is unembargoed, direct subscribers should
376+ # include:
377 # - the bug reporter
378 # - the bugtask pillar security contacts (if set)
379 # - and bug/pillar owners, drivers if they are already subscribed
380- # If the bug is for a private project, then other direct subscribers
381- # should be unsubscribed.
382
383 (bug, bug_owner, bugtask_a, bugtask_b, default_bugtask) = (
384 self.createBugTasksAndSubscribers(private_security_related=True))
385@@ -715,7 +700,7 @@
386 with person_logged_in(bug_owner):
387 for subscriber in initial_subscribers:
388 bug.subscribe(subscriber, bug_owner)
389- who = self.factory.makePerson()
390+ who = self.factory.makePerson(name='who')
391 bug.transitionToInformationType(
392 InformationType.UNEMBARGOEDSECURITY, who)
393 subscribers = bug.getDirectSubscribers()
394@@ -723,36 +708,33 @@
395 default_bugtask.pillar.driver,
396 default_bugtask.pillar.security_contact,
397 bug_owner))
398- if not self.private_project:
399- expected_subscribers.update(initial_subscribers)
400- expected_subscribers.remove(default_bugtask.pillar.bug_supervisor)
401+ expected_subscribers.update(initial_subscribers)
402 self.assertContentEqual(expected_subscribers, subscribers)
403
404- def test_setPrivateFalseAndSecurityRelatedFalse(self):
405- # When a bug is marked as private=false and security_related=false,
406- # any existing subscriptions are left alone.
407+ def test_transition_to_PUBLIC_information_type(self):
408+ # Subscriptions aren't altered when a bug is transitioned to the
409+ # PUBLIC information type.
410
411 (bug, bug_owner, bugtask_a, bugtask_b, default_bugtask) = (
412 self.createBugTasksAndSubscribers(private_security_related=True))
413 initial_subscribers = set((
414- self.factory.makePerson(), bug_owner,
415+ self.factory.makePerson(name='subscriber'), bug_owner,
416 bugtask_a.pillar.security_contact, bugtask_a.pillar.driver))
417
418 with person_logged_in(bug_owner):
419 for subscriber in initial_subscribers:
420 bug.subscribe(subscriber, bug_owner)
421- who = self.factory.makePerson()
422- expected_direct_subscribers = set(bug.getDirectSubscribers())
423+ who = self.factory.makePerson(name='who')
424+ subscribers_before_public = set(bug.getDirectSubscribers())
425 bug.transitionToInformationType(InformationType.PUBLIC, who)
426- subscribers = set(bug.getDirectSubscribers())
427- expected_direct_subscribers.difference_update(
428- (default_bugtask.pillar.security_contact,
429- default_bugtask.pillar.bug_supervisor,
430- bugtask_a.pillar.security_contact))
431- self.assertContentEqual(expected_direct_subscribers, subscribers)
432+ subscribers_after_public = set(bug.getDirectSubscribers())
433+ self.assertContentEqual(
434+ subscribers_before_public,
435+ subscribers_after_public)
436
437 def test_setPillarOwnerSubscribedIfNoBugSupervisor(self):
438- # The pillar owner is subscribed if the bug supervisor is not set.
439+ # The pillar owner is subscribed if the bug supervisor is not set and
440+ # the bug is marked as USERDATA.
441
442 bug_owner = self.factory.makePerson(name='bugowner')
443 bug = self.factory.makeBug(owner=bug_owner)
444@@ -766,18 +748,19 @@
445 subscribers)
446
447 def test_setPillarOwnerSubscribedIfNoSecurityContact(self):
448- # The pillar owner is subscribed if the security contact is not set.
449+ # The pillar owner is subscribed if the security contact is not set
450+ # and the bug is marked as EMBARGOEDSECURITY.
451
452 bug_owner = self.factory.makePerson(name='bugowner')
453 bug = self.factory.makeBug(owner=bug_owner)
454 with person_logged_in(bug_owner):
455- who = self.factory.makePerson()
456+ who = self.factory.makePerson(name='who')
457 bug.transitionToInformationType(
458- InformationType.UNEMBARGOEDSECURITY, who)
459+ InformationType.EMBARGOEDSECURITY, who)
460 subscribers = bug.getDirectSubscribers()
461 naked_bugtask = removeSecurityProxy(bug).default_bugtask
462 self.assertContentEqual(
463- set((naked_bugtask.pillar.owner, bug_owner)),
464+ set((naked_bugtask.pillar.owner, bug_owner, who)),
465 subscribers)
466
467 def _fetch_notifications(self, bug, reason_header):
468@@ -833,32 +816,6 @@
469 actual_recipients.append(recipient.person)
470 self.assertContentEqual(expected_recipients, actual_recipients)
471
472- def test_bugSupervisorUnsubscribedIfBugMadePublic(self):
473- # The bug supervisors are unsubscribed if a bug is made public and an
474- # email is sent telling them they have been unsubscribed.
475-
476- (bug, bug_owner, bugtask_a, bugtask_b, default_bugtask) = (
477- self.createBugTasksAndSubscribers(private_security_related=True))
478-
479- with person_logged_in(bug_owner):
480- bug.subscribe(default_bugtask.pillar.bug_supervisor, bug_owner)
481- who = self.factory.makePerson(name="who")
482- bug.transitionToInformationType(
483- InformationType.UNEMBARGOEDSECURITY, who)
484- subscribers = bug.getDirectSubscribers()
485- self.assertNotIn(
486- default_bugtask.pillar.bug_supervisor, subscribers)
487-
488- expected_recipients = [
489- default_bugtask.pillar.bug_supervisor,
490- ]
491- expected_body_text = '** This bug is no longer private'
492- expected_reason_body = ('You received this bug notification '
493- 'because you are a bug supervisor.')
494- self._check_notifications(
495- bug, expected_recipients, expected_body_text,
496- expected_reason_body, False, True, 'Bug Supervisor')
497-
498 def test_structural_bug_supervisor_becomes_direct_on_private(self):
499 # If a bug supervisor has a structural subscription to the bug, and
500 # the bug is marked as private, the supervisor should get a direct
501@@ -876,31 +833,6 @@
502 bug.transitionToInformationType(InformationType.USERDATA, who)
503 self.assertTrue(bug_supervisor in bug.getDirectSubscribers())
504
505- def test_securityContactUnsubscribedIfBugNotSecurityRelated(self):
506- # The security contacts are unsubscribed if a bug has security_related
507- # set to false and an email is sent telling them they have been
508- # unsubscribed.
509-
510- (bug, bug_owner, bugtask_a, bugtask_b, default_bugtask) = (
511- self.createBugTasksAndSubscribers(private_security_related=True))
512-
513- with person_logged_in(bug_owner):
514- bug.subscribe(bugtask_a.pillar.security_contact, bug_owner)
515- who = self.factory.makePerson(name="who")
516- bug.transitionToInformationType(InformationType.USERDATA, who)
517- subscribers = bug.getDirectSubscribers()
518- self.assertFalse(bugtask_a.pillar.security_contact in subscribers)
519-
520- expected_recipients = [
521- bugtask_a.pillar.security_contact,
522- ]
523- expected_body_text = '** This bug is no longer security related'
524- expected_reason_body = ('You received this bug notification '
525- 'because you are a security contact.')
526- self._check_notifications(
527- bug, expected_recipients, expected_body_text,
528- expected_reason_body, True, False, 'Security Contact')
529-
530
531 class TestBugPrivacy(TestCaseWithFactory):
532
533
534=== modified file 'lib/lp/bugs/tests/test_bug_mirror_access_triggers.py'
535--- lib/lp/bugs/tests/test_bug_mirror_access_triggers.py 2012-04-17 17:58:37 +0000
536+++ lib/lp/bugs/tests/test_bug_mirror_access_triggers.py 2012-04-17 18:01:41 +0000
537@@ -141,7 +141,9 @@
538 bug.setPrivate(True, bug.owner)
539 self.assertIsNot(
540 None, getUtility(IAccessArtifactSource).find([bug]).one())
541- self.assertEqual((1, 1), self.assertMirrored(bug))
542+ # There are two grants--one for the reporter, one for the product
543+ # owner or supervisor (if set). There is only one policy, USERDATA.
544+ self.assertEqual((2, 1), self.assertMirrored(bug))
545
546 def test_security_related(self):
547 # Setting the security_related flag uses EMBARGOEDSECURITY
548@@ -153,7 +155,9 @@
549 [InformationType.USERDATA],
550 self.getPolicyTypesForArtifact(artifact))
551 bug.setSecurityRelated(True, bug.owner)
552- self.assertEqual((1, 1), self.assertMirrored(bug))
553+ # Both the reporter and either the product owner or the product's
554+ # security contact have grants.
555+ self.assertEqual((2, 1), self.assertMirrored(bug))
556 self.assertContentEqual(
557 [InformationType.EMBARGOEDSECURITY],
558 self.getPolicyTypesForArtifact(artifact))
559
560=== modified file 'lib/lp/bugs/tests/test_bugvisibility.py'
561--- lib/lp/bugs/tests/test_bugvisibility.py 2012-04-17 17:59:54 +0000
562+++ lib/lp/bugs/tests/test_bugvisibility.py 2012-04-17 18:01:41 +0000
563@@ -88,72 +88,3 @@
564 def test_publicBugAnonUser(self):
565 # Since the bug is private, the anonymous user cannot see it.
566 self.assertFalse(self.bug.userCanView(None))
567-
568-
569-class TestPrivateBugVisibilityAfterTransition(TestCaseWithFactory):
570- """Test visibility for a public bug, set to private."""
571-
572- layer = DatabaseFunctionalLayer
573-
574- def setUp(self):
575- super(TestPrivateBugVisibilityAfterTransition, self).setUp()
576- self.product_owner = self.factory.makePerson(name="productowner")
577- self.maintainer = self.factory.makeTeam(
578- name="maintainer",
579- owner=self.product_owner,
580- subscription_policy=TeamSubscriptionPolicy.RESTRICTED)
581- self.maintainer_member = self.factory.makePerson(
582- name="maintainermember")
583- self.owner = self.factory.makePerson(name="bugowner")
584- self.product = self.factory.makeProduct(
585- name="regular-product", owner=self.maintainer)
586- self.bug = self.factory.makeBug(
587- owner=self.owner, product=self.product)
588-
589- self.bug_team_member = self.factory.makePerson(name="bugteammember")
590- self.bug_team = self.factory.makeTeam(
591- name="bugteam",
592- owner=self.product_owner)
593-
594- with celebrity_logged_in('admin'):
595- self.maintainer.addMember(
596- self.maintainer_member,
597- self.product_owner)
598- self.bug_team.addMember(self.bug_team_member, self.product_owner)
599-
600- def _makePrivate(self):
601- with celebrity_logged_in('admin'):
602- self.bug.setPrivate(private=True, who=self.product_owner)
603-
604- @contextmanager
605- def _setupSupervisor(self):
606- with celebrity_logged_in('admin'):
607- self.product.setBugSupervisor(
608- bug_supervisor=self.bug_team,
609- user=self.product_owner)
610- yield
611- with celebrity_logged_in('admin'):
612- self.product.setBugSupervisor(
613- bug_supervisor=None,
614- user=self.product_owner)
615-
616- def test_bug_supervisor_can_see_bug(self):
617- with self._setupSupervisor():
618- self._makePrivate()
619- self.assertTrue(self.bug.userCanView(self.bug_team_member))
620-
621- def test_reporter_can_see(self):
622- self._makePrivate()
623- self.assertTrue(self.bug.userCanView(self.owner))
624-
625- def test_maintainer_can_see_without_supervisor(self):
626- # If no bug supervisor is set, the maintainer is given access.
627- self._makePrivate()
628- self.assertTrue(self.bug.userCanView(self.maintainer_member))
629-
630- def test_assignee_can_see(self):
631- bug_assignee = self.factory.makePerson(name="bugassignee")
632- with celebrity_logged_in('admin'):
633- self.bug.default_bugtask.transitionToAssignee(bug_assignee)
634- self._makePrivate()
635- self.assertTrue(self.bug.userCanView(bug_assignee))
Revision history for this message
Curtis Hovey (sinzui) wrote :

Thank you very very much.

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/doc/bugnotification-sending.txt'
2--- lib/lp/bugs/doc/bugnotification-sending.txt 2012-03-23 06:10:28 +0000
3+++ lib/lp/bugs/doc/bugnotification-sending.txt 2012-04-17 18:05:27 +0000
4@@ -756,9 +756,10 @@
5
6 >>> for message in trigger_and_get_email_messages(bug_three):
7 ... print message['To'], message.get_all('X-Launchpad-Bug-Private')
8+ foo.bar@canonical.com ['yes']
9+ mark@example.com ['yes']
10 test@canonical.com ['yes']
11
12-
13 The X-Launchpad-Bug-Security-Vulnerability header
14 -------------------------------------------------
15
16@@ -772,6 +773,8 @@
17 >>> for message in trigger_and_get_email_messages(bug_three):
18 ... print message['To'], (
19 ... message.get_all('X-Launchpad-Bug-Security-Vulnerability'))
20+ foo.bar@canonical.com ['no']
21+ mark@example.com ['no']
22 test@canonical.com ['no']
23
24 The presence of the security flag on a bug is, surprise, denoted by a
25@@ -786,6 +789,8 @@
26 >>> for message in trigger_and_get_email_messages(bug_three):
27 ... print message['To'], (
28 ... message.get_all('X-Launchpad-Bug-Security-Vulnerability'))
29+ foo.bar@canonical.com ['yes']
30+ mark@example.com ['yes']
31 test@canonical.com ['yes']
32
33
34
35=== modified file 'lib/lp/bugs/doc/bugsubscription.txt'
36--- lib/lp/bugs/doc/bugsubscription.txt 2012-04-03 06:14:09 +0000
37+++ lib/lp/bugs/doc/bugsubscription.txt 2012-04-17 18:05:27 +0000
38@@ -380,17 +380,7 @@
39 ()
40
41 When a bug is marked private, specific people like the bugtask bug supervisors
42-will be automatically subscribed, and only specifically allowed existing
43-direct subscribers (eg bugtask pillar maintainers) will remain subscribed.
44-
45-We currently use a feature flag to control who is subscribed when a bug is
46-made private.
47-
48- >>> from lp.services.features.testing import FeatureFixture
49- >>> feature_flag = {
50- ... 'disclosure.enhanced_private_bug_subscriptions.enabled': 'on'}
51- >>> flags = FeatureFixture(feature_flag)
52- >>> flags.setUp()
53+will be automatically subscribed.
54
55 >>> from zope.event import notify
56
57@@ -438,26 +428,20 @@
58 >>> print_displayname(linux_source_bug.getDirectSubscribers())
59 Foo Bar
60 Mark Shuttleworth
61+ Robert Collins
62+ Ubuntu Team
63
64 >>> print_displayname(linux_source_bug.getIndirectSubscribers())
65 Martin Pitt
66 No Privileges Person
67- Robert Collins
68 Sample Person
69 Scott James Remnant
70- Ubuntu Team
71
72 >>> print_displayname(linux_source_bug.getAlsoNotifiedSubscribers())
73 Martin Pitt
74 No Privileges Person
75- Robert Collins
76 Sample Person
77 Scott James Remnant
78- Ubuntu Team
79-
80-Clean up the feature flag.
81-
82- >>> flags.cleanUp()
83
84 To find out which email addresses should receive a notification email on
85 a bug, and why, IBug.getBugNotificationRecipients() assembles an
86@@ -472,8 +456,8 @@
87 [('foo.bar@canonical.com', 'Subscriber'),
88 ('mark@example.com', 'Subscriber'),
89 ('no-priv@canonical.com', u'Subscriber (linux-source-2.6.15 in Ubuntu)'),
90- ('robertc@robertcollins.net', u'Subscriber (Mozilla Firefox)'),
91- ('support@ubuntu.com', u'Subscriber (Ubuntu) @ubuntu-team'),
92+ ('robertc@robertcollins.net', 'Subscriber'),
93+ ('support@ubuntu.com', u'Subscriber @ubuntu-team'),
94 ('test@canonical.com', 'Assignee')]
95
96 If IBug.getBugNotificationRecipients() is passed a BugNotificationLevel
97@@ -486,8 +470,8 @@
98 >>> [(address, recipients.getReason(address)[1]) for address in addresses]
99 [('foo.bar@canonical.com', 'Subscriber'),
100 ('mark@example.com', 'Subscriber'),
101- ('robertc@robertcollins.net', u'Subscriber (Mozilla Firefox)'),
102- ('support@ubuntu.com', u'Subscriber (Ubuntu) @ubuntu-team'),
103+ ('robertc@robertcollins.net', 'Subscriber'),
104+ ('support@ubuntu.com', u'Subscriber @ubuntu-team'),
105 ('test@canonical.com', 'Assignee')]
106
107 When Sample Person is unsubscribed from linux_source_bug, he is no
108@@ -501,8 +485,8 @@
109 >>> [(address, recipients.getReason(address)[1]) for address in addresses]
110 [('foo.bar@canonical.com', 'Subscriber'),
111 ('mark@example.com', 'Subscriber'),
112- ('robertc@robertcollins.net', u'Subscriber (Mozilla Firefox)'),
113- ('support@ubuntu.com', u'Subscriber (Ubuntu) @ubuntu-team'),
114+ ('robertc@robertcollins.net', 'Subscriber'),
115+ ('support@ubuntu.com', u'Subscriber @ubuntu-team'),
116 ('test@canonical.com', 'Assignee')]
117
118 ...but remains included for the level LIFECYCLE.
119@@ -515,8 +499,8 @@
120 [('foo.bar@canonical.com', 'Subscriber'),
121 ('mark@example.com', 'Subscriber'),
122 ('no-priv@canonical.com', u'Subscriber (linux-source-2.6.15 in Ubuntu)'),
123- ('robertc@robertcollins.net', u'Subscriber (Mozilla Firefox)'),
124- ('support@ubuntu.com', u'Subscriber (Ubuntu) @ubuntu-team'),
125+ ('robertc@robertcollins.net', 'Subscriber'),
126+ ('support@ubuntu.com', u'Subscriber @ubuntu-team'),
127 ('test@canonical.com', 'Assignee')]
128
129 To find out if someone is already directly subscribed to a bug, call
130
131=== modified file 'lib/lp/bugs/doc/initial-bug-contacts.txt'
132--- lib/lp/bugs/doc/initial-bug-contacts.txt 2012-04-03 06:14:09 +0000
133+++ lib/lp/bugs/doc/initial-bug-contacts.txt 2012-04-17 18:05:27 +0000
134@@ -288,16 +288,6 @@
135 u'Sample Person', u'Steve Alexander', u'Ubuntu Gnome Team',
136 u'Ubuntu Team']
137
138-
139-We currently use a feature flag to control who is subscribed when a bug is
140-made private.
141-
142- >>> from lp.services.features.testing import FeatureFixture
143- >>> feature_flag = {
144- ... 'disclosure.enhanced_private_bug_subscriptions.enabled': 'on'}
145- >>> flags = FeatureFixture(feature_flag)
146- >>> flags.setUp()
147-
148 If a bug is private, no changes are made to the subscriber list when a
149 bug is reassigned to a different package.
150
151@@ -329,13 +319,9 @@
152 the unallowed subscribers are removed:
153
154 >>> subscriber_names(bug_one_in_ubuntu_firefox.bug)
155- [u'Foo Bar', u'Mark Shuttleworth', u'Sample Person',
156+ [u'Foo Bar', u'Sample Person',
157 u'Steve Alexander', u'Ubuntu Team']
158
159-Clean up the feature flag.
160-
161- >>> flags.cleanUp()
162-
163
164 Product Bug Supervisors and Bug Tasks
165 -------------------------------------
166
167=== modified file 'lib/lp/bugs/doc/security-teams.txt'
168--- lib/lp/bugs/doc/security-teams.txt 2012-04-04 05:46:26 +0000
169+++ lib/lp/bugs/doc/security-teams.txt 2012-04-17 18:05:27 +0000
170@@ -280,17 +280,7 @@
171
172
173 When a bug becomes security-related, the security contacts for the pillars it
174-affects are subscribed to it. This happens regardless of whether the feature
175-flag is set.
176-
177-We currently use a feature flag to control who is subscribed when a bug is
178-made security related.
179-
180- >>> from lp.services.features.testing import FeatureFixture
181- >>> feature_flag = {
182- ... 'disclosure.enhanced_private_bug_subscriptions.enabled': 'on'}
183- >>> security_flags = FeatureFixture(feature_flag)
184- >>> security_flags.setUp()
185+affects are subscribed to it.
186
187 >>> from zope.event import notify
188 >>> from lazr.lifecycle.event import ObjectModifiedEvent
189@@ -317,30 +307,3 @@
190 Bug Reporter
191 Distribution Security Contact
192 Product Security Contact
193-
194-Clean up the feature flag.
195-
196- >>> security_flags.cleanUp()
197-
198-And once more without the feature flag.
199-
200- >>> product = factory.makeProduct()
201- >>> product.security_contact = factory.makePerson(
202- ... displayname='Product Security Contact')
203- >>> distribution = factory.makeDistribution()
204- >>> distribution.security_contact = factory.makePerson(
205- ... displayname='Distribution Security Contact')
206- >>> reporter = factory.makePerson(displayname=u'Bug Reporter')
207- >>> bug = factory.makeBug(product=product, owner=reporter)
208- >>> bug.addTask(owner=reporter, target=distribution)
209- <BugTask ...>
210- >>> old_state = Snapshot(bug, providing=IBug)
211- >>> bug.setSecurityRelated(True, getUtility(ILaunchBag).user)
212- True
213- >>> notify(ObjectModifiedEvent(bug, old_state, ['security_related']))
214- >>> for subscriber_name in sorted(
215- ... s.displayname for s in bug.getDirectSubscribers()):
216- ... print subscriber_name
217- Bug Reporter
218- Distribution Security Contact
219- Product Security Contact
220
221=== modified file 'lib/lp/bugs/model/bug.py'
222--- lib/lp/bugs/model/bug.py 2012-04-17 01:11:47 +0000
223+++ lib/lp/bugs/model/bug.py 2012-04-17 18:05:27 +0000
224@@ -1733,10 +1733,6 @@
225 "Cannot transition the information type to proprietary.")
226 if self.information_type == information_type:
227 return False
228- f_flag_str = 'disclosure.enhanced_private_bug_subscriptions.enabled'
229- f_flag = bool(getFeatureFlag(f_flag_str))
230- if f_flag:
231- self.reconcileSubscribers(information_type, who)
232 if (information_type == InformationType.PROPRIETARY and
233 len(self.affected_pillars) > 1):
234 raise BugCannotBePrivate(
235@@ -1744,34 +1740,47 @@
236 if information_type in PRIVATE_INFORMATION_TYPES:
237 self.who_made_private = who
238 self.date_made_private = UTC_NOW
239+ missing_subscribers = set([who, self.owner])
240 else:
241 self.who_made_private = None
242 self.date_made_private = None
243+ missing_subscribers = set()
244 # XXX: This should be a bulk update. RBC 20100827
245 # bug=https://bugs.launchpad.net/storm/+bug/625071
246 for attachment in self.attachments_unpopulated:
247 attachment.libraryfile.restricted = (
248 information_type in PRIVATE_INFORMATION_TYPES)
249 self.updateHeat()
250- if not f_flag and information_type == InformationType.USERDATA:
251- # If we didn't call reconcileSubscribers(), we may have
252- # bug supervisors who should be on this bug, but aren't.
253- supervisors = set()
254- for bugtask in self.bugtasks:
255- supervisors.add(bugtask.pillar.bug_supervisor)
256- if None in supervisors:
257- supervisors.remove(None)
258- for s in supervisors:
259- if not get_structural_subscriptions_for_bug(self, s):
260- self.subscribe(s, who)
261- if not f_flag and information_type in SECURITY_INFORMATION_TYPES:
262- # The bug turned out to be security-related, subscribe the
263- # security contact. We do it here only if the feature flag
264- # is not set, otherwise it's done in
265- # reconcileSubscribers().
266- for pillar in self.affected_pillars:
267+
268+ # There are several people we need to ensure are subscribed.
269+ # If the information type is userdata, we need to check for bug
270+ # supervisors who aren't subscribed and should be. If there is no
271+ # bug supervisor, we need to subscribe the maintainer.
272+ pillars = self.affected_pillars
273+ if information_type == InformationType.USERDATA:
274+ for pillar in pillars:
275+ if pillar.bug_supervisor is not None:
276+ missing_subscribers.add(pillar.bug_supervisor)
277+ else:
278+ missing_subscribers.add(pillar.owner)
279+
280+ # If the information type is security related, we need to ensure
281+ # the security contacts are subscribed. If there is no security
282+ # contact, we need to subscribe the maintainer.
283+ if information_type in SECURITY_INFORMATION_TYPES:
284+ for pillar in pillars:
285 if pillar.security_contact is not None:
286- self.subscribe(pillar.security_contact, who)
287+ missing_subscribers.add(pillar.security_contact)
288+ else:
289+ missing_subscribers.add(pillar.owner)
290+
291+ for s in missing_subscribers:
292+ # Don't subscribe someone if they're already subscribed via a
293+ # team.
294+ already_subscribed_teams = self.getSubscribersForPerson(s)
295+ if already_subscribed_teams.is_empty():
296+ self.subscribe(s, who)
297+
298 self.information_type = information_type
299 # Set the legacy attributes for now.
300 self._private = information_type in PRIVATE_INFORMATION_TYPES
301@@ -1838,81 +1847,6 @@
302 bug_supervisors.append(pillar.bug_supervisor)
303 return bug_supervisors, security_contacts
304
305- def reconcileSubscribers(self, information_type, who):
306- """ Ensure only appropriate people are subscribed to private bugs.
307-
308- When a bug is marked as either private = True or security_related =
309- True, we need to ensure that only people who are authorised to know
310- about the privileged contents of the bug remain directly subscribed
311- to it. So we:
312- 1. Get the required subscribers depending on the bug status.
313- 2. Get the auto removed subscribers depending on the bug status.
314- eg security contacts when a bug is updated to security related =
315- false.
316- 3. Get the allowed subscribers = required subscribers
317- + bugtask owners
318- 4. Remove any current direct subscribers who are not allowed or are
319- to be auto removed.
320- 5. Add any subscribers who are required.
321- """
322- current_direct_subscribers = (
323- self.getSubscriptionInfo().direct_subscribers)
324- required_subscribers = self.getRequiredSubscribers(
325- information_type, who)
326- removed_bug_supervisors, removed_security_contacts = (
327- self.getAutoRemovedSubscribers(information_type))
328- for subscriber in removed_bug_supervisors:
329- recipients = BugNotificationRecipients()
330- recipients.addBugSupervisor(subscriber)
331- notification_text = ("This bug is no longer private so the bug "
332- "supervisor was unsubscribed. They will no longer be "
333- "notified of changes to this bug for privacy related "
334- "reasons, but may receive notifications about this bug from "
335- "other subscriptions.")
336- self.unsubscribe(
337- subscriber, who, ignore_permissions=True,
338- send_notification=True,
339- notification_text=notification_text,
340- recipients=recipients)
341- for subscriber in removed_security_contacts:
342- recipients = BugNotificationRecipients()
343- recipients.addSecurityContact(subscriber)
344- notification_text = ("This bug is no longer security related so "
345- "the security contact was unsubscribed. They will no longer "
346- "be notified of changes to this bug for security related "
347- "reasons, but may receive notifications about this bug "
348- "from other subscriptions.")
349- self.unsubscribe(
350- subscriber, who, ignore_permissions=True,
351- send_notification=True,
352- notification_text=notification_text,
353- recipients=recipients)
354-
355- # If this bug is for a project that is marked as having private bugs
356- # by default, and the bug is private or security related, we will
357- # unsubscribe any unauthorised direct subscribers.
358- pillar = self.default_bugtask.pillar
359- private_project = IProduct.providedBy(pillar) and pillar.private_bugs
360- privileged_info = information_type != InformationType.PUBLIC
361- if private_project and privileged_info:
362- allowed_subscribers = set()
363- allowed_subscribers.add(self.owner)
364- for bugtask in self.bugtasks:
365- allowed_subscribers.add(bugtask.owner)
366- allowed_subscribers.add(bugtask.pillar.owner)
367- allowed_subscribers.update(set(bugtask.pillar.drivers))
368- allowed_subscribers = required_subscribers.union(
369- allowed_subscribers)
370- subscribers_to_remove = (
371- current_direct_subscribers.difference(allowed_subscribers))
372- for subscriber in subscribers_to_remove:
373- self.unsubscribe(subscriber, who, ignore_permissions=True)
374-
375- subscribers_to_add = (
376- required_subscribers.difference(current_direct_subscribers))
377- for subscriber in subscribers_to_add:
378- self.subscribe(subscriber, who)
379-
380 def getBugTask(self, target):
381 """See `IBug`."""
382 for bugtask in self.bugtasks:
383
384=== modified file 'lib/lp/bugs/model/tests/test_bug.py'
385--- lib/lp/bugs/model/tests/test_bug.py 2012-04-17 01:11:47 +0000
386+++ lib/lp/bugs/model/tests/test_bug.py 2012-04-17 18:05:27 +0000
387@@ -567,14 +567,6 @@
388
389 layer = DatabaseFunctionalLayer
390
391- def setUp(self):
392- super(TestBugPrivateAndSecurityRelatedUpdatesMixin, self).setUp()
393- f_flag_str = 'disclosure.enhanced_private_bug_subscriptions.enabled'
394- feature_flag = {f_flag_str: 'on'}
395- flags = FeatureFixture(feature_flag)
396- flags.setUp()
397- self.addCleanup(flags.cleanUp)
398-
399 def test_setPrivate_subscribes_person_who_makes_bug_private(self):
400 # When setPrivate(True) is called on a bug, the person who is
401 # marking the bug private is subscribed to the bug.
402@@ -589,8 +581,8 @@
403 # marking the bug private will not be subscribed if they're
404 # already a member of a team which is a direct subscriber.
405 bug = self.factory.makeBug()
406- team = self.factory.makeTeam()
407- person = team.teamowner
408+ person = self.factory.makePerson(name='teamowner')
409+ team = self.factory.makeTeam(owner=person, name='team')
410 with person_logged_in(person):
411 bug.subscribe(team, person)
412 bug.setPrivate(True, person)
413@@ -632,78 +624,71 @@
414 return (bug, bug_owner, naked_bugtask_a, naked_bugtask_b,
415 naked_default_bugtask)
416
417- def test_setPrivateTrueAndSecurityRelatedTrue(self):
418- # When a bug is marked as private=true and security_related=true, the
419- # direct subscribers should include:
420+ def test_transition_to_EMBARGOEDSECURITY_information_type(self):
421+ # When a bug is marked as EMBARGOEDSECURITY, the direct subscribers
422+ # should include:
423 # - the bug reporter
424 # - the bugtask pillar security contacts (if set)
425 # - the person changing the state
426 # - and bug/pillar owners, drivers if they are already subscribed
427- # If the bug is for a private project, then other direct subscribers
428- # should be unsubscribed.
429
430 (bug, bug_owner, bugtask_a, bugtask_b, default_bugtask) = (
431 self.createBugTasksAndSubscribers())
432 initial_subscribers = set((
433- self.factory.makePerson(), bugtask_a.owner, bug_owner,
434- bugtask_a.pillar.security_contact, bugtask_a.pillar.driver))
435+ self.factory.makePerson(name='subscriber'), bugtask_a.owner,
436+ bug_owner, bugtask_a.pillar.security_contact,
437+ bugtask_a.pillar.driver))
438+ initial_subscribers.update(bug.getDirectSubscribers())
439
440 with person_logged_in(bug_owner):
441 for subscriber in initial_subscribers:
442 bug.subscribe(subscriber, bug_owner)
443- who = self.factory.makePerson()
444+ who = self.factory.makePerson(name='who')
445 bug.transitionToInformationType(
446 InformationType.EMBARGOEDSECURITY, who=who)
447 subscribers = bug.getDirectSubscribers()
448+ initial_subscribers.update(bug.getDirectSubscribers())
449 expected_subscribers = set((
450 bugtask_a.owner,
451- default_bugtask.pillar.bug_supervisor,
452 default_bugtask.pillar.driver,
453 default_bugtask.pillar.security_contact,
454 bug_owner, who))
455- if not self.private_project:
456- expected_subscribers.update(initial_subscribers)
457+ expected_subscribers.update(initial_subscribers)
458 self.assertContentEqual(expected_subscribers, subscribers)
459
460- def test_setPrivateTrueAndSecurityRelatedFalse(self):
461- # When a bug is marked as private=true and security_related=false, the
462- # direct subscribers should include:
463+ def test_transition_to_USERDATA_information_type(self):
464+ # When a bug is marked as USERDATA, the direct subscribers should
465+ # include:
466 # - the bug reporter
467 # - the bugtask pillar bug supervisors (if set)
468 # - the person changing the state
469 # - and bug/pillar owners, drivers if they are already subscribed
470- # If the bug is for a private project, then other direct subscribers
471- # should be unsubscribed.
472
473 (bug, bug_owner, bugtask_a, bugtask_b, default_bugtask) = (
474 self.createBugTasksAndSubscribers(private_security_related=True))
475 initial_subscribers = set((
476- self.factory.makePerson(), bug_owner,
477+ self.factory.makePerson(name='subscriber'), bug_owner,
478 bugtask_a.pillar.security_contact, bugtask_a.pillar.driver))
479
480 with person_logged_in(bug_owner):
481 for subscriber in initial_subscribers:
482 bug.subscribe(subscriber, bug_owner)
483- who = self.factory.makePerson()
484+ who = self.factory.makePerson(name='who')
485 bug.transitionToInformationType(InformationType.USERDATA, who)
486 subscribers = bug.getDirectSubscribers()
487 expected_subscribers = set((
488 default_bugtask.pillar.bug_supervisor,
489 default_bugtask.pillar.driver,
490 bug_owner, who))
491- if not self.private_project:
492- expected_subscribers.update(initial_subscribers)
493- expected_subscribers.remove(bugtask_a.pillar.security_contact)
494+ expected_subscribers.update(initial_subscribers)
495 self.assertContentEqual(expected_subscribers, subscribers)
496
497- def test_setPrivateFalseAndSecurityRelatedTrue(self):
498- # When a bug is marked as private=false and security_related=true, the
499- # direct subscribers should include:
500+ def test_transition_to_UNEMBARGOEDSECURITY_information_type(self):
501+ # When a security bug is unembargoed, direct subscribers should
502+ # include:
503 # - the bug reporter
504 # - the bugtask pillar security contacts (if set)
505 # - and bug/pillar owners, drivers if they are already subscribed
506- # If the bug is for a private project, then other direct subscribers
507- # should be unsubscribed.
508
509 (bug, bug_owner, bugtask_a, bugtask_b, default_bugtask) = (
510 self.createBugTasksAndSubscribers(private_security_related=True))
511@@ -715,7 +700,7 @@
512 with person_logged_in(bug_owner):
513 for subscriber in initial_subscribers:
514 bug.subscribe(subscriber, bug_owner)
515- who = self.factory.makePerson()
516+ who = self.factory.makePerson(name='who')
517 bug.transitionToInformationType(
518 InformationType.UNEMBARGOEDSECURITY, who)
519 subscribers = bug.getDirectSubscribers()
520@@ -723,36 +708,33 @@
521 default_bugtask.pillar.driver,
522 default_bugtask.pillar.security_contact,
523 bug_owner))
524- if not self.private_project:
525- expected_subscribers.update(initial_subscribers)
526- expected_subscribers.remove(default_bugtask.pillar.bug_supervisor)
527+ expected_subscribers.update(initial_subscribers)
528 self.assertContentEqual(expected_subscribers, subscribers)
529
530- def test_setPrivateFalseAndSecurityRelatedFalse(self):
531- # When a bug is marked as private=false and security_related=false,
532- # any existing subscriptions are left alone.
533+ def test_transition_to_PUBLIC_information_type(self):
534+ # Subscriptions aren't altered when a bug is transitioned to the
535+ # PUBLIC information type.
536
537 (bug, bug_owner, bugtask_a, bugtask_b, default_bugtask) = (
538 self.createBugTasksAndSubscribers(private_security_related=True))
539 initial_subscribers = set((
540- self.factory.makePerson(), bug_owner,
541+ self.factory.makePerson(name='subscriber'), bug_owner,
542 bugtask_a.pillar.security_contact, bugtask_a.pillar.driver))
543
544 with person_logged_in(bug_owner):
545 for subscriber in initial_subscribers:
546 bug.subscribe(subscriber, bug_owner)
547- who = self.factory.makePerson()
548- expected_direct_subscribers = set(bug.getDirectSubscribers())
549+ who = self.factory.makePerson(name='who')
550+ subscribers_before_public = set(bug.getDirectSubscribers())
551 bug.transitionToInformationType(InformationType.PUBLIC, who)
552- subscribers = set(bug.getDirectSubscribers())
553- expected_direct_subscribers.difference_update(
554- (default_bugtask.pillar.security_contact,
555- default_bugtask.pillar.bug_supervisor,
556- bugtask_a.pillar.security_contact))
557- self.assertContentEqual(expected_direct_subscribers, subscribers)
558+ subscribers_after_public = set(bug.getDirectSubscribers())
559+ self.assertContentEqual(
560+ subscribers_before_public,
561+ subscribers_after_public)
562
563 def test_setPillarOwnerSubscribedIfNoBugSupervisor(self):
564- # The pillar owner is subscribed if the bug supervisor is not set.
565+ # The pillar owner is subscribed if the bug supervisor is not set and
566+ # the bug is marked as USERDATA.
567
568 bug_owner = self.factory.makePerson(name='bugowner')
569 bug = self.factory.makeBug(owner=bug_owner)
570@@ -766,18 +748,19 @@
571 subscribers)
572
573 def test_setPillarOwnerSubscribedIfNoSecurityContact(self):
574- # The pillar owner is subscribed if the security contact is not set.
575+ # The pillar owner is subscribed if the security contact is not set
576+ # and the bug is marked as EMBARGOEDSECURITY.
577
578 bug_owner = self.factory.makePerson(name='bugowner')
579 bug = self.factory.makeBug(owner=bug_owner)
580 with person_logged_in(bug_owner):
581- who = self.factory.makePerson()
582+ who = self.factory.makePerson(name='who')
583 bug.transitionToInformationType(
584- InformationType.UNEMBARGOEDSECURITY, who)
585+ InformationType.EMBARGOEDSECURITY, who)
586 subscribers = bug.getDirectSubscribers()
587 naked_bugtask = removeSecurityProxy(bug).default_bugtask
588 self.assertContentEqual(
589- set((naked_bugtask.pillar.owner, bug_owner)),
590+ set((naked_bugtask.pillar.owner, bug_owner, who)),
591 subscribers)
592
593 def _fetch_notifications(self, bug, reason_header):
594@@ -833,32 +816,6 @@
595 actual_recipients.append(recipient.person)
596 self.assertContentEqual(expected_recipients, actual_recipients)
597
598- def test_bugSupervisorUnsubscribedIfBugMadePublic(self):
599- # The bug supervisors are unsubscribed if a bug is made public and an
600- # email is sent telling them they have been unsubscribed.
601-
602- (bug, bug_owner, bugtask_a, bugtask_b, default_bugtask) = (
603- self.createBugTasksAndSubscribers(private_security_related=True))
604-
605- with person_logged_in(bug_owner):
606- bug.subscribe(default_bugtask.pillar.bug_supervisor, bug_owner)
607- who = self.factory.makePerson(name="who")
608- bug.transitionToInformationType(
609- InformationType.UNEMBARGOEDSECURITY, who)
610- subscribers = bug.getDirectSubscribers()
611- self.assertNotIn(
612- default_bugtask.pillar.bug_supervisor, subscribers)
613-
614- expected_recipients = [
615- default_bugtask.pillar.bug_supervisor,
616- ]
617- expected_body_text = '** This bug is no longer private'
618- expected_reason_body = ('You received this bug notification '
619- 'because you are a bug supervisor.')
620- self._check_notifications(
621- bug, expected_recipients, expected_body_text,
622- expected_reason_body, False, True, 'Bug Supervisor')
623-
624 def test_structural_bug_supervisor_becomes_direct_on_private(self):
625 # If a bug supervisor has a structural subscription to the bug, and
626 # the bug is marked as private, the supervisor should get a direct
627@@ -876,31 +833,6 @@
628 bug.transitionToInformationType(InformationType.USERDATA, who)
629 self.assertTrue(bug_supervisor in bug.getDirectSubscribers())
630
631- def test_securityContactUnsubscribedIfBugNotSecurityRelated(self):
632- # The security contacts are unsubscribed if a bug has security_related
633- # set to false and an email is sent telling them they have been
634- # unsubscribed.
635-
636- (bug, bug_owner, bugtask_a, bugtask_b, default_bugtask) = (
637- self.createBugTasksAndSubscribers(private_security_related=True))
638-
639- with person_logged_in(bug_owner):
640- bug.subscribe(bugtask_a.pillar.security_contact, bug_owner)
641- who = self.factory.makePerson(name="who")
642- bug.transitionToInformationType(InformationType.USERDATA, who)
643- subscribers = bug.getDirectSubscribers()
644- self.assertFalse(bugtask_a.pillar.security_contact in subscribers)
645-
646- expected_recipients = [
647- bugtask_a.pillar.security_contact,
648- ]
649- expected_body_text = '** This bug is no longer security related'
650- expected_reason_body = ('You received this bug notification '
651- 'because you are a security contact.')
652- self._check_notifications(
653- bug, expected_recipients, expected_body_text,
654- expected_reason_body, True, False, 'Security Contact')
655-
656
657 class TestBugPrivacy(TestCaseWithFactory):
658
659
660=== modified file 'lib/lp/bugs/tests/test_bug_mirror_access_triggers.py'
661--- lib/lp/bugs/tests/test_bug_mirror_access_triggers.py 2012-04-03 06:14:09 +0000
662+++ lib/lp/bugs/tests/test_bug_mirror_access_triggers.py 2012-04-17 18:05:27 +0000
663@@ -141,7 +141,9 @@
664 bug.setPrivate(True, bug.owner)
665 self.assertIsNot(
666 None, getUtility(IAccessArtifactSource).find([bug]).one())
667- self.assertEqual((1, 1), self.assertMirrored(bug))
668+ # There are two grants--one for the reporter, one for the product
669+ # owner or supervisor (if set). There is only one policy, USERDATA.
670+ self.assertEqual((2, 1), self.assertMirrored(bug))
671
672 def test_security_related(self):
673 # Setting the security_related flag uses EMBARGOEDSECURITY
674@@ -153,7 +155,9 @@
675 [InformationType.USERDATA],
676 self.getPolicyTypesForArtifact(artifact))
677 bug.setSecurityRelated(True, bug.owner)
678- self.assertEqual((1, 1), self.assertMirrored(bug))
679+ # Both the reporter and either the product owner or the product's
680+ # security contact have grants.
681+ self.assertEqual((2, 1), self.assertMirrored(bug))
682 self.assertContentEqual(
683 [InformationType.EMBARGOEDSECURITY],
684 self.getPolicyTypesForArtifact(artifact))
685
686=== modified file 'lib/lp/bugs/tests/test_bugvisibility.py'
687--- lib/lp/bugs/tests/test_bugvisibility.py 2012-02-15 02:01:14 +0000
688+++ lib/lp/bugs/tests/test_bugvisibility.py 2012-04-17 18:05:27 +0000
689@@ -3,11 +3,17 @@
690
691 """Tests for visibility of a bug."""
692
693+from contextlib import contextmanager
694+
695+from lp.registry.interfaces.person import TeamSubscriptionPolicy
696 from lp.testing import (
697 celebrity_logged_in,
698 TestCaseWithFactory,
699 )
700-from lp.testing.layers import LaunchpadFunctionalLayer
701+from lp.testing.layers import (
702+ DatabaseFunctionalLayer,
703+ LaunchpadFunctionalLayer,
704+ )
705
706
707 class TestPublicBugVisibility(TestCaseWithFactory):
708
709=== modified file 'lib/lp/services/features/flags.py'
710--- lib/lp/services/features/flags.py 2012-04-11 05:21:33 +0000
711+++ lib/lp/services/features/flags.py 2012-04-17 18:05:27 +0000
712@@ -216,13 +216,6 @@
713 '',
714 '',
715 ''),
716- ('disclosure.enhanced_private_bug_subscriptions.enabled',
717- 'boolean',
718- ('Enables the auto subscribing and unsubscribing of users as a bug '
719- 'transitions between public, private and security related states.'),
720- '',
721- '',
722- ''),
723 ('disclosure.users_hide_own_bug_comments.enabled',
724 'boolean',
725 'Allows users in project roles and comment owners to hide bug comments.',