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-----

=== modified file 'lib/lp/bugs/doc/bugnotification-sending.txt'
--- lib/lp/bugs/doc/bugnotification-sending.txt 2012-04-17 17:59:54 +0000
+++ lib/lp/bugs/doc/bugnotification-sending.txt 2012-04-17 18:01:41 +0000
@@ -756,9 +756,10 @@
756756
757 >>> for message in trigger_and_get_email_messages(bug_three):757 >>> for message in trigger_and_get_email_messages(bug_three):
758 ... print message['To'], message.get_all('X-Launchpad-Bug-Private')758 ... print message['To'], message.get_all('X-Launchpad-Bug-Private')
759 foo.bar@canonical.com ['yes']
760 mark@example.com ['yes']
759 test@canonical.com ['yes']761 test@canonical.com ['yes']
760762
761
762The X-Launchpad-Bug-Security-Vulnerability header763The X-Launchpad-Bug-Security-Vulnerability header
763-------------------------------------------------764-------------------------------------------------
764765
@@ -772,6 +773,8 @@
772 >>> for message in trigger_and_get_email_messages(bug_three):773 >>> for message in trigger_and_get_email_messages(bug_three):
773 ... print message['To'], (774 ... print message['To'], (
774 ... message.get_all('X-Launchpad-Bug-Security-Vulnerability'))775 ... message.get_all('X-Launchpad-Bug-Security-Vulnerability'))
776 foo.bar@canonical.com ['no']
777 mark@example.com ['no']
775 test@canonical.com ['no']778 test@canonical.com ['no']
776779
777The presence of the security flag on a bug is, surprise, denoted by a780The presence of the security flag on a bug is, surprise, denoted by a
@@ -786,6 +789,8 @@
786 >>> for message in trigger_and_get_email_messages(bug_three):789 >>> for message in trigger_and_get_email_messages(bug_three):
787 ... print message['To'], (790 ... print message['To'], (
788 ... message.get_all('X-Launchpad-Bug-Security-Vulnerability'))791 ... message.get_all('X-Launchpad-Bug-Security-Vulnerability'))
792 foo.bar@canonical.com ['yes']
793 mark@example.com ['yes']
789 test@canonical.com ['yes']794 test@canonical.com ['yes']
790795
791796
792797
=== modified file 'lib/lp/bugs/doc/bugsubscription.txt'
--- lib/lp/bugs/doc/bugsubscription.txt 2012-04-17 17:58:37 +0000
+++ lib/lp/bugs/doc/bugsubscription.txt 2012-04-17 18:01:41 +0000
@@ -380,17 +380,7 @@
380 ()380 ()
381381
382When a bug is marked private, specific people like the bugtask bug supervisors382When a bug is marked private, specific people like the bugtask bug supervisors
383will be automatically subscribed, and only specifically allowed existing383will be automatically subscribed.
384direct subscribers (eg bugtask pillar maintainers) will remain subscribed.
385
386We currently use a feature flag to control who is subscribed when a bug is
387made private.
388
389 >>> from lp.services.features.testing import FeatureFixture
390 >>> feature_flag = {
391 ... 'disclosure.enhanced_private_bug_subscriptions.enabled': 'on'}
392 >>> flags = FeatureFixture(feature_flag)
393 >>> flags.setUp()
394384
395 >>> from zope.event import notify385 >>> from zope.event import notify
396386
@@ -438,26 +428,20 @@
438 >>> print_displayname(linux_source_bug.getDirectSubscribers())428 >>> print_displayname(linux_source_bug.getDirectSubscribers())
439 Foo Bar429 Foo Bar
440 Mark Shuttleworth430 Mark Shuttleworth
431 Robert Collins
432 Ubuntu Team
441433
442 >>> print_displayname(linux_source_bug.getIndirectSubscribers())434 >>> print_displayname(linux_source_bug.getIndirectSubscribers())
443 Martin Pitt435 Martin Pitt
444 No Privileges Person436 No Privileges Person
445 Robert Collins
446 Sample Person437 Sample Person
447 Scott James Remnant438 Scott James Remnant
448 Ubuntu Team
449439
450 >>> print_displayname(linux_source_bug.getAlsoNotifiedSubscribers())440 >>> print_displayname(linux_source_bug.getAlsoNotifiedSubscribers())
451 Martin Pitt441 Martin Pitt
452 No Privileges Person442 No Privileges Person
453 Robert Collins
454 Sample Person443 Sample Person
455 Scott James Remnant444 Scott James Remnant
456 Ubuntu Team
457
458Clean up the feature flag.
459
460 >>> flags.cleanUp()
461445
462To find out which email addresses should receive a notification email on446To find out which email addresses should receive a notification email on
463a bug, and why, IBug.getBugNotificationRecipients() assembles an447a bug, and why, IBug.getBugNotificationRecipients() assembles an
@@ -472,8 +456,8 @@
472 [('foo.bar@canonical.com', 'Subscriber'),456 [('foo.bar@canonical.com', 'Subscriber'),
473 ('mark@example.com', 'Subscriber'),457 ('mark@example.com', 'Subscriber'),
474 ('no-priv@canonical.com', u'Subscriber (linux-source-2.6.15 in Ubuntu)'),458 ('no-priv@canonical.com', u'Subscriber (linux-source-2.6.15 in Ubuntu)'),
475 ('robertc@robertcollins.net', u'Subscriber (Mozilla Firefox)'),459 ('robertc@robertcollins.net', 'Subscriber'),
476 ('support@ubuntu.com', u'Subscriber (Ubuntu) @ubuntu-team'),460 ('support@ubuntu.com', u'Subscriber @ubuntu-team'),
477 ('test@canonical.com', 'Assignee')]461 ('test@canonical.com', 'Assignee')]
478462
479If IBug.getBugNotificationRecipients() is passed a BugNotificationLevel463If IBug.getBugNotificationRecipients() is passed a BugNotificationLevel
@@ -486,8 +470,8 @@
486 >>> [(address, recipients.getReason(address)[1]) for address in addresses]470 >>> [(address, recipients.getReason(address)[1]) for address in addresses]
487 [('foo.bar@canonical.com', 'Subscriber'),471 [('foo.bar@canonical.com', 'Subscriber'),
488 ('mark@example.com', 'Subscriber'),472 ('mark@example.com', 'Subscriber'),
489 ('robertc@robertcollins.net', u'Subscriber (Mozilla Firefox)'),473 ('robertc@robertcollins.net', 'Subscriber'),
490 ('support@ubuntu.com', u'Subscriber (Ubuntu) @ubuntu-team'),474 ('support@ubuntu.com', u'Subscriber @ubuntu-team'),
491 ('test@canonical.com', 'Assignee')]475 ('test@canonical.com', 'Assignee')]
492476
493When Sample Person is unsubscribed from linux_source_bug, he is no477When Sample Person is unsubscribed from linux_source_bug, he is no
@@ -501,8 +485,8 @@
501 >>> [(address, recipients.getReason(address)[1]) for address in addresses]485 >>> [(address, recipients.getReason(address)[1]) for address in addresses]
502 [('foo.bar@canonical.com', 'Subscriber'),486 [('foo.bar@canonical.com', 'Subscriber'),
503 ('mark@example.com', 'Subscriber'),487 ('mark@example.com', 'Subscriber'),
504 ('robertc@robertcollins.net', u'Subscriber (Mozilla Firefox)'),488 ('robertc@robertcollins.net', 'Subscriber'),
505 ('support@ubuntu.com', u'Subscriber (Ubuntu) @ubuntu-team'),489 ('support@ubuntu.com', u'Subscriber @ubuntu-team'),
506 ('test@canonical.com', 'Assignee')]490 ('test@canonical.com', 'Assignee')]
507491
508...but remains included for the level LIFECYCLE.492...but remains included for the level LIFECYCLE.
@@ -515,8 +499,8 @@
515 [('foo.bar@canonical.com', 'Subscriber'),499 [('foo.bar@canonical.com', 'Subscriber'),
516 ('mark@example.com', 'Subscriber'),500 ('mark@example.com', 'Subscriber'),
517 ('no-priv@canonical.com', u'Subscriber (linux-source-2.6.15 in Ubuntu)'),501 ('no-priv@canonical.com', u'Subscriber (linux-source-2.6.15 in Ubuntu)'),
518 ('robertc@robertcollins.net', u'Subscriber (Mozilla Firefox)'),502 ('robertc@robertcollins.net', 'Subscriber'),
519 ('support@ubuntu.com', u'Subscriber (Ubuntu) @ubuntu-team'),503 ('support@ubuntu.com', u'Subscriber @ubuntu-team'),
520 ('test@canonical.com', 'Assignee')]504 ('test@canonical.com', 'Assignee')]
521505
522To find out if someone is already directly subscribed to a bug, call506To find out if someone is already directly subscribed to a bug, call
523507
=== modified file 'lib/lp/bugs/doc/initial-bug-contacts.txt'
--- lib/lp/bugs/doc/initial-bug-contacts.txt 2012-04-17 17:58:37 +0000
+++ lib/lp/bugs/doc/initial-bug-contacts.txt 2012-04-17 18:01:41 +0000
@@ -288,16 +288,6 @@
288 u'Sample Person', u'Steve Alexander', u'Ubuntu Gnome Team',288 u'Sample Person', u'Steve Alexander', u'Ubuntu Gnome Team',
289 u'Ubuntu Team']289 u'Ubuntu Team']
290290
291
292We currently use a feature flag to control who is subscribed when a bug is
293made private.
294
295 >>> from lp.services.features.testing import FeatureFixture
296 >>> feature_flag = {
297 ... 'disclosure.enhanced_private_bug_subscriptions.enabled': 'on'}
298 >>> flags = FeatureFixture(feature_flag)
299 >>> flags.setUp()
300
301If a bug is private, no changes are made to the subscriber list when a291If a bug is private, no changes are made to the subscriber list when a
302bug is reassigned to a different package.292bug is reassigned to a different package.
303293
@@ -329,13 +319,9 @@
329the unallowed subscribers are removed:319the unallowed subscribers are removed:
330320
331 >>> subscriber_names(bug_one_in_ubuntu_firefox.bug)321 >>> subscriber_names(bug_one_in_ubuntu_firefox.bug)
332 [u'Foo Bar', u'Mark Shuttleworth', u'Sample Person',322 [u'Foo Bar', u'Sample Person',
333 u'Steve Alexander', u'Ubuntu Team']323 u'Steve Alexander', u'Ubuntu Team']
334324
335Clean up the feature flag.
336
337 >>> flags.cleanUp()
338
339325
340Product Bug Supervisors and Bug Tasks326Product Bug Supervisors and Bug Tasks
341-------------------------------------327-------------------------------------
342328
=== modified file 'lib/lp/bugs/doc/security-teams.txt'
--- lib/lp/bugs/doc/security-teams.txt 2012-04-17 17:58:37 +0000
+++ lib/lp/bugs/doc/security-teams.txt 2012-04-17 18:01:41 +0000
@@ -280,17 +280,7 @@
280280
281281
282When a bug becomes security-related, the security contacts for the pillars it282When a bug becomes security-related, the security contacts for the pillars it
283affects are subscribed to it. This happens regardless of whether the feature283affects are subscribed to it.
284flag is set.
285
286We currently use a feature flag to control who is subscribed when a bug is
287made security related.
288
289 >>> from lp.services.features.testing import FeatureFixture
290 >>> feature_flag = {
291 ... 'disclosure.enhanced_private_bug_subscriptions.enabled': 'on'}
292 >>> security_flags = FeatureFixture(feature_flag)
293 >>> security_flags.setUp()
294284
295 >>> from zope.event import notify285 >>> from zope.event import notify
296 >>> from lazr.lifecycle.event import ObjectModifiedEvent286 >>> from lazr.lifecycle.event import ObjectModifiedEvent
@@ -317,30 +307,3 @@
317 Bug Reporter307 Bug Reporter
318 Distribution Security Contact308 Distribution Security Contact
319 Product Security Contact309 Product Security Contact
320
321Clean up the feature flag.
322
323 >>> security_flags.cleanUp()
324
325And once more without the feature flag.
326
327 >>> product = factory.makeProduct()
328 >>> product.security_contact = factory.makePerson(
329 ... displayname='Product Security Contact')
330 >>> distribution = factory.makeDistribution()
331 >>> distribution.security_contact = factory.makePerson(
332 ... displayname='Distribution Security Contact')
333 >>> reporter = factory.makePerson(displayname=u'Bug Reporter')
334 >>> bug = factory.makeBug(product=product, owner=reporter)
335 >>> bug.addTask(owner=reporter, target=distribution)
336 <BugTask ...>
337 >>> old_state = Snapshot(bug, providing=IBug)
338 >>> bug.setSecurityRelated(True, getUtility(ILaunchBag).user)
339 True
340 >>> notify(ObjectModifiedEvent(bug, old_state, ['security_related']))
341 >>> for subscriber_name in sorted(
342 ... s.displayname for s in bug.getDirectSubscribers()):
343 ... print subscriber_name
344 Bug Reporter
345 Distribution Security Contact
346 Product Security Contact
347310
=== modified file 'lib/lp/bugs/model/bug.py'
--- lib/lp/bugs/model/bug.py 2012-04-17 18:00:24 +0000
+++ lib/lp/bugs/model/bug.py 2012-04-17 18:01:41 +0000
@@ -1740,9 +1740,11 @@
1740 if information_type in PRIVATE_INFORMATION_TYPES:1740 if information_type in PRIVATE_INFORMATION_TYPES:
1741 self.who_made_private = who1741 self.who_made_private = who
1742 self.date_made_private = UTC_NOW1742 self.date_made_private = UTC_NOW
1743 missing_subscribers = set([who, self.owner])
1743 else:1744 else:
1744 self.who_made_private = None1745 self.who_made_private = None
1745 self.date_made_private = None1746 self.date_made_private = None
1747 missing_subscribers = set()
1746 # XXX: This should be a bulk update. RBC 201008271748 # XXX: This should be a bulk update. RBC 20100827
1747 # bug=https://bugs.launchpad.net/storm/+bug/6250711749 # bug=https://bugs.launchpad.net/storm/+bug/625071
1748 for attachment in self.attachments_unpopulated:1750 for attachment in self.attachments_unpopulated:
@@ -1751,7 +1753,6 @@
1751 self.updateHeat()1753 self.updateHeat()
17521754
1753 # There are several people we need to ensure are subscribed.1755 # There are several people we need to ensure are subscribed.
1754 missing_subscribers = set([who, self.owner])
1755 # If the information type is userdata, we need to check for bug1756 # If the information type is userdata, we need to check for bug
1756 # supervisors who aren't subscribed and should be. If there is no1757 # supervisors who aren't subscribed and should be. If there is no
1757 # bug supervisor, we need to subscribe the maintainer.1758 # bug supervisor, we need to subscribe the maintainer.
@@ -1774,8 +1775,10 @@
1774 missing_subscribers.add(pillar.owner)1775 missing_subscribers.add(pillar.owner)
17751776
1776 for s in missing_subscribers:1777 for s in missing_subscribers:
1777 subscriptions = get_structural_subscriptions_for_bug(self, s)1778 # Don't subscribe someone if they're already subscribed via a
1778 if subscriptions.is_empty():1779 # team.
1780 already_subscribed_teams = self.getSubscribersForPerson(s)
1781 if already_subscribed_teams.is_empty():
1779 self.subscribe(s, who)1782 self.subscribe(s, who)
17801783
1781 self.information_type = information_type1784 self.information_type = information_type
17821785
=== modified file 'lib/lp/bugs/model/tests/test_bug.py'
--- lib/lp/bugs/model/tests/test_bug.py 2012-04-17 18:00:24 +0000
+++ lib/lp/bugs/model/tests/test_bug.py 2012-04-17 18:01:41 +0000
@@ -567,14 +567,6 @@
567567
568 layer = DatabaseFunctionalLayer568 layer = DatabaseFunctionalLayer
569569
570 def setUp(self):
571 super(TestBugPrivateAndSecurityRelatedUpdatesMixin, self).setUp()
572 f_flag_str = 'disclosure.enhanced_private_bug_subscriptions.enabled'
573 feature_flag = {f_flag_str: 'on'}
574 flags = FeatureFixture(feature_flag)
575 flags.setUp()
576 self.addCleanup(flags.cleanUp)
577
578 def test_setPrivate_subscribes_person_who_makes_bug_private(self):570 def test_setPrivate_subscribes_person_who_makes_bug_private(self):
579 # When setPrivate(True) is called on a bug, the person who is571 # When setPrivate(True) is called on a bug, the person who is
580 # marking the bug private is subscribed to the bug.572 # marking the bug private is subscribed to the bug.
@@ -589,8 +581,8 @@
589 # marking the bug private will not be subscribed if they're581 # marking the bug private will not be subscribed if they're
590 # already a member of a team which is a direct subscriber.582 # already a member of a team which is a direct subscriber.
591 bug = self.factory.makeBug()583 bug = self.factory.makeBug()
592 team = self.factory.makeTeam()584 person = self.factory.makePerson(name='teamowner')
593 person = team.teamowner585 team = self.factory.makeTeam(owner=person, name='team')
594 with person_logged_in(person):586 with person_logged_in(person):
595 bug.subscribe(team, person)587 bug.subscribe(team, person)
596 bug.setPrivate(True, person)588 bug.setPrivate(True, person)
@@ -632,78 +624,71 @@
632 return (bug, bug_owner, naked_bugtask_a, naked_bugtask_b,624 return (bug, bug_owner, naked_bugtask_a, naked_bugtask_b,
633 naked_default_bugtask)625 naked_default_bugtask)
634626
635 def test_setPrivateTrueAndSecurityRelatedTrue(self):627 def test_transition_to_EMBARGOEDSECURITY_information_type(self):
636 # When a bug is marked as private=true and security_related=true, the628 # When a bug is marked as EMBARGOEDSECURITY, the direct subscribers
637 # direct subscribers should include:629 # should include:
638 # - the bug reporter630 # - the bug reporter
639 # - the bugtask pillar security contacts (if set)631 # - the bugtask pillar security contacts (if set)
640 # - the person changing the state632 # - the person changing the state
641 # - and bug/pillar owners, drivers if they are already subscribed633 # - and bug/pillar owners, drivers if they are already subscribed
642 # If the bug is for a private project, then other direct subscribers
643 # should be unsubscribed.
644634
645 (bug, bug_owner, bugtask_a, bugtask_b, default_bugtask) = (635 (bug, bug_owner, bugtask_a, bugtask_b, default_bugtask) = (
646 self.createBugTasksAndSubscribers())636 self.createBugTasksAndSubscribers())
647 initial_subscribers = set((637 initial_subscribers = set((
648 self.factory.makePerson(), bugtask_a.owner, bug_owner,638 self.factory.makePerson(name='subscriber'), bugtask_a.owner,
649 bugtask_a.pillar.security_contact, bugtask_a.pillar.driver))639 bug_owner, bugtask_a.pillar.security_contact,
640 bugtask_a.pillar.driver))
641 initial_subscribers.update(bug.getDirectSubscribers())
650642
651 with person_logged_in(bug_owner):643 with person_logged_in(bug_owner):
652 for subscriber in initial_subscribers:644 for subscriber in initial_subscribers:
653 bug.subscribe(subscriber, bug_owner)645 bug.subscribe(subscriber, bug_owner)
654 who = self.factory.makePerson()646 who = self.factory.makePerson(name='who')
655 bug.transitionToInformationType(647 bug.transitionToInformationType(
656 InformationType.EMBARGOEDSECURITY, who=who)648 InformationType.EMBARGOEDSECURITY, who=who)
657 subscribers = bug.getDirectSubscribers()649 subscribers = bug.getDirectSubscribers()
650 initial_subscribers.update(bug.getDirectSubscribers())
658 expected_subscribers = set((651 expected_subscribers = set((
659 bugtask_a.owner,652 bugtask_a.owner,
660 default_bugtask.pillar.bug_supervisor,
661 default_bugtask.pillar.driver,653 default_bugtask.pillar.driver,
662 default_bugtask.pillar.security_contact,654 default_bugtask.pillar.security_contact,
663 bug_owner, who))655 bug_owner, who))
664 if not self.private_project:656 expected_subscribers.update(initial_subscribers)
665 expected_subscribers.update(initial_subscribers)
666 self.assertContentEqual(expected_subscribers, subscribers)657 self.assertContentEqual(expected_subscribers, subscribers)
667658
668 def test_setPrivateTrueAndSecurityRelatedFalse(self):659 def test_transition_to_USERDATA_information_type(self):
669 # When a bug is marked as private=true and security_related=false, the660 # When a bug is marked as USERDATA, the direct subscribers should
670 # direct subscribers should include:661 # include:
671 # - the bug reporter662 # - the bug reporter
672 # - the bugtask pillar bug supervisors (if set)663 # - the bugtask pillar bug supervisors (if set)
673 # - the person changing the state664 # - the person changing the state
674 # - and bug/pillar owners, drivers if they are already subscribed665 # - and bug/pillar owners, drivers if they are already subscribed
675 # If the bug is for a private project, then other direct subscribers
676 # should be unsubscribed.
677666
678 (bug, bug_owner, bugtask_a, bugtask_b, default_bugtask) = (667 (bug, bug_owner, bugtask_a, bugtask_b, default_bugtask) = (
679 self.createBugTasksAndSubscribers(private_security_related=True))668 self.createBugTasksAndSubscribers(private_security_related=True))
680 initial_subscribers = set((669 initial_subscribers = set((
681 self.factory.makePerson(), bug_owner,670 self.factory.makePerson(name='subscriber'), bug_owner,
682 bugtask_a.pillar.security_contact, bugtask_a.pillar.driver))671 bugtask_a.pillar.security_contact, bugtask_a.pillar.driver))
683672
684 with person_logged_in(bug_owner):673 with person_logged_in(bug_owner):
685 for subscriber in initial_subscribers:674 for subscriber in initial_subscribers:
686 bug.subscribe(subscriber, bug_owner)675 bug.subscribe(subscriber, bug_owner)
687 who = self.factory.makePerson()676 who = self.factory.makePerson(name='who')
688 bug.transitionToInformationType(InformationType.USERDATA, who)677 bug.transitionToInformationType(InformationType.USERDATA, who)
689 subscribers = bug.getDirectSubscribers()678 subscribers = bug.getDirectSubscribers()
690 expected_subscribers = set((679 expected_subscribers = set((
691 default_bugtask.pillar.bug_supervisor,680 default_bugtask.pillar.bug_supervisor,
692 default_bugtask.pillar.driver,681 default_bugtask.pillar.driver,
693 bug_owner, who))682 bug_owner, who))
694 if not self.private_project:683 expected_subscribers.update(initial_subscribers)
695 expected_subscribers.update(initial_subscribers)
696 expected_subscribers.remove(bugtask_a.pillar.security_contact)
697 self.assertContentEqual(expected_subscribers, subscribers)684 self.assertContentEqual(expected_subscribers, subscribers)
698685
699 def test_setPrivateFalseAndSecurityRelatedTrue(self):686 def test_transition_to_UNEMBARGOEDSECURITY_information_type(self):
700 # When a bug is marked as private=false and security_related=true, the687 # When a security bug is unembargoed, direct subscribers should
701 # direct subscribers should include:688 # include:
702 # - the bug reporter689 # - the bug reporter
703 # - the bugtask pillar security contacts (if set)690 # - the bugtask pillar security contacts (if set)
704 # - and bug/pillar owners, drivers if they are already subscribed691 # - and bug/pillar owners, drivers if they are already subscribed
705 # If the bug is for a private project, then other direct subscribers
706 # should be unsubscribed.
707692
708 (bug, bug_owner, bugtask_a, bugtask_b, default_bugtask) = (693 (bug, bug_owner, bugtask_a, bugtask_b, default_bugtask) = (
709 self.createBugTasksAndSubscribers(private_security_related=True))694 self.createBugTasksAndSubscribers(private_security_related=True))
@@ -715,7 +700,7 @@
715 with person_logged_in(bug_owner):700 with person_logged_in(bug_owner):
716 for subscriber in initial_subscribers:701 for subscriber in initial_subscribers:
717 bug.subscribe(subscriber, bug_owner)702 bug.subscribe(subscriber, bug_owner)
718 who = self.factory.makePerson()703 who = self.factory.makePerson(name='who')
719 bug.transitionToInformationType(704 bug.transitionToInformationType(
720 InformationType.UNEMBARGOEDSECURITY, who)705 InformationType.UNEMBARGOEDSECURITY, who)
721 subscribers = bug.getDirectSubscribers()706 subscribers = bug.getDirectSubscribers()
@@ -723,36 +708,33 @@
723 default_bugtask.pillar.driver,708 default_bugtask.pillar.driver,
724 default_bugtask.pillar.security_contact,709 default_bugtask.pillar.security_contact,
725 bug_owner))710 bug_owner))
726 if not self.private_project:711 expected_subscribers.update(initial_subscribers)
727 expected_subscribers.update(initial_subscribers)
728 expected_subscribers.remove(default_bugtask.pillar.bug_supervisor)
729 self.assertContentEqual(expected_subscribers, subscribers)712 self.assertContentEqual(expected_subscribers, subscribers)
730713
731 def test_setPrivateFalseAndSecurityRelatedFalse(self):714 def test_transition_to_PUBLIC_information_type(self):
732 # When a bug is marked as private=false and security_related=false,715 # Subscriptions aren't altered when a bug is transitioned to the
733 # any existing subscriptions are left alone.716 # PUBLIC information type.
734717
735 (bug, bug_owner, bugtask_a, bugtask_b, default_bugtask) = (718 (bug, bug_owner, bugtask_a, bugtask_b, default_bugtask) = (
736 self.createBugTasksAndSubscribers(private_security_related=True))719 self.createBugTasksAndSubscribers(private_security_related=True))
737 initial_subscribers = set((720 initial_subscribers = set((
738 self.factory.makePerson(), bug_owner,721 self.factory.makePerson(name='subscriber'), bug_owner,
739 bugtask_a.pillar.security_contact, bugtask_a.pillar.driver))722 bugtask_a.pillar.security_contact, bugtask_a.pillar.driver))
740723
741 with person_logged_in(bug_owner):724 with person_logged_in(bug_owner):
742 for subscriber in initial_subscribers:725 for subscriber in initial_subscribers:
743 bug.subscribe(subscriber, bug_owner)726 bug.subscribe(subscriber, bug_owner)
744 who = self.factory.makePerson()727 who = self.factory.makePerson(name='who')
745 expected_direct_subscribers = set(bug.getDirectSubscribers())728 subscribers_before_public = set(bug.getDirectSubscribers())
746 bug.transitionToInformationType(InformationType.PUBLIC, who)729 bug.transitionToInformationType(InformationType.PUBLIC, who)
747 subscribers = set(bug.getDirectSubscribers())730 subscribers_after_public = set(bug.getDirectSubscribers())
748 expected_direct_subscribers.difference_update(731 self.assertContentEqual(
749 (default_bugtask.pillar.security_contact,732 subscribers_before_public,
750 default_bugtask.pillar.bug_supervisor,733 subscribers_after_public)
751 bugtask_a.pillar.security_contact))
752 self.assertContentEqual(expected_direct_subscribers, subscribers)
753734
754 def test_setPillarOwnerSubscribedIfNoBugSupervisor(self):735 def test_setPillarOwnerSubscribedIfNoBugSupervisor(self):
755 # The pillar owner is subscribed if the bug supervisor is not set.736 # The pillar owner is subscribed if the bug supervisor is not set and
737 # the bug is marked as USERDATA.
756738
757 bug_owner = self.factory.makePerson(name='bugowner')739 bug_owner = self.factory.makePerson(name='bugowner')
758 bug = self.factory.makeBug(owner=bug_owner)740 bug = self.factory.makeBug(owner=bug_owner)
@@ -766,18 +748,19 @@
766 subscribers)748 subscribers)
767749
768 def test_setPillarOwnerSubscribedIfNoSecurityContact(self):750 def test_setPillarOwnerSubscribedIfNoSecurityContact(self):
769 # The pillar owner is subscribed if the security contact is not set.751 # The pillar owner is subscribed if the security contact is not set
752 # and the bug is marked as EMBARGOEDSECURITY.
770753
771 bug_owner = self.factory.makePerson(name='bugowner')754 bug_owner = self.factory.makePerson(name='bugowner')
772 bug = self.factory.makeBug(owner=bug_owner)755 bug = self.factory.makeBug(owner=bug_owner)
773 with person_logged_in(bug_owner):756 with person_logged_in(bug_owner):
774 who = self.factory.makePerson()757 who = self.factory.makePerson(name='who')
775 bug.transitionToInformationType(758 bug.transitionToInformationType(
776 InformationType.UNEMBARGOEDSECURITY, who)759 InformationType.EMBARGOEDSECURITY, who)
777 subscribers = bug.getDirectSubscribers()760 subscribers = bug.getDirectSubscribers()
778 naked_bugtask = removeSecurityProxy(bug).default_bugtask761 naked_bugtask = removeSecurityProxy(bug).default_bugtask
779 self.assertContentEqual(762 self.assertContentEqual(
780 set((naked_bugtask.pillar.owner, bug_owner)),763 set((naked_bugtask.pillar.owner, bug_owner, who)),
781 subscribers)764 subscribers)
782765
783 def _fetch_notifications(self, bug, reason_header):766 def _fetch_notifications(self, bug, reason_header):
@@ -833,32 +816,6 @@
833 actual_recipients.append(recipient.person)816 actual_recipients.append(recipient.person)
834 self.assertContentEqual(expected_recipients, actual_recipients)817 self.assertContentEqual(expected_recipients, actual_recipients)
835818
836 def test_bugSupervisorUnsubscribedIfBugMadePublic(self):
837 # The bug supervisors are unsubscribed if a bug is made public and an
838 # email is sent telling them they have been unsubscribed.
839
840 (bug, bug_owner, bugtask_a, bugtask_b, default_bugtask) = (
841 self.createBugTasksAndSubscribers(private_security_related=True))
842
843 with person_logged_in(bug_owner):
844 bug.subscribe(default_bugtask.pillar.bug_supervisor, bug_owner)
845 who = self.factory.makePerson(name="who")
846 bug.transitionToInformationType(
847 InformationType.UNEMBARGOEDSECURITY, who)
848 subscribers = bug.getDirectSubscribers()
849 self.assertNotIn(
850 default_bugtask.pillar.bug_supervisor, subscribers)
851
852 expected_recipients = [
853 default_bugtask.pillar.bug_supervisor,
854 ]
855 expected_body_text = '** This bug is no longer private'
856 expected_reason_body = ('You received this bug notification '
857 'because you are a bug supervisor.')
858 self._check_notifications(
859 bug, expected_recipients, expected_body_text,
860 expected_reason_body, False, True, 'Bug Supervisor')
861
862 def test_structural_bug_supervisor_becomes_direct_on_private(self):819 def test_structural_bug_supervisor_becomes_direct_on_private(self):
863 # If a bug supervisor has a structural subscription to the bug, and820 # If a bug supervisor has a structural subscription to the bug, and
864 # the bug is marked as private, the supervisor should get a direct821 # the bug is marked as private, the supervisor should get a direct
@@ -876,31 +833,6 @@
876 bug.transitionToInformationType(InformationType.USERDATA, who)833 bug.transitionToInformationType(InformationType.USERDATA, who)
877 self.assertTrue(bug_supervisor in bug.getDirectSubscribers())834 self.assertTrue(bug_supervisor in bug.getDirectSubscribers())
878835
879 def test_securityContactUnsubscribedIfBugNotSecurityRelated(self):
880 # The security contacts are unsubscribed if a bug has security_related
881 # set to false and an email is sent telling them they have been
882 # unsubscribed.
883
884 (bug, bug_owner, bugtask_a, bugtask_b, default_bugtask) = (
885 self.createBugTasksAndSubscribers(private_security_related=True))
886
887 with person_logged_in(bug_owner):
888 bug.subscribe(bugtask_a.pillar.security_contact, bug_owner)
889 who = self.factory.makePerson(name="who")
890 bug.transitionToInformationType(InformationType.USERDATA, who)
891 subscribers = bug.getDirectSubscribers()
892 self.assertFalse(bugtask_a.pillar.security_contact in subscribers)
893
894 expected_recipients = [
895 bugtask_a.pillar.security_contact,
896 ]
897 expected_body_text = '** This bug is no longer security related'
898 expected_reason_body = ('You received this bug notification '
899 'because you are a security contact.')
900 self._check_notifications(
901 bug, expected_recipients, expected_body_text,
902 expected_reason_body, True, False, 'Security Contact')
903
904836
905class TestBugPrivacy(TestCaseWithFactory):837class TestBugPrivacy(TestCaseWithFactory):
906838
907839
=== modified file 'lib/lp/bugs/tests/test_bug_mirror_access_triggers.py'
--- lib/lp/bugs/tests/test_bug_mirror_access_triggers.py 2012-04-17 17:58:37 +0000
+++ lib/lp/bugs/tests/test_bug_mirror_access_triggers.py 2012-04-17 18:01:41 +0000
@@ -141,7 +141,9 @@
141 bug.setPrivate(True, bug.owner)141 bug.setPrivate(True, bug.owner)
142 self.assertIsNot(142 self.assertIsNot(
143 None, getUtility(IAccessArtifactSource).find([bug]).one())143 None, getUtility(IAccessArtifactSource).find([bug]).one())
144 self.assertEqual((1, 1), self.assertMirrored(bug))144 # There are two grants--one for the reporter, one for the product
145 # owner or supervisor (if set). There is only one policy, USERDATA.
146 self.assertEqual((2, 1), self.assertMirrored(bug))
145147
146 def test_security_related(self):148 def test_security_related(self):
147 # Setting the security_related flag uses EMBARGOEDSECURITY149 # Setting the security_related flag uses EMBARGOEDSECURITY
@@ -153,7 +155,9 @@
153 [InformationType.USERDATA],155 [InformationType.USERDATA],
154 self.getPolicyTypesForArtifact(artifact))156 self.getPolicyTypesForArtifact(artifact))
155 bug.setSecurityRelated(True, bug.owner)157 bug.setSecurityRelated(True, bug.owner)
156 self.assertEqual((1, 1), self.assertMirrored(bug))158 # Both the reporter and either the product owner or the product's
159 # security contact have grants.
160 self.assertEqual((2, 1), self.assertMirrored(bug))
157 self.assertContentEqual(161 self.assertContentEqual(
158 [InformationType.EMBARGOEDSECURITY],162 [InformationType.EMBARGOEDSECURITY],
159 self.getPolicyTypesForArtifact(artifact))163 self.getPolicyTypesForArtifact(artifact))
160164
=== modified file 'lib/lp/bugs/tests/test_bugvisibility.py'
--- lib/lp/bugs/tests/test_bugvisibility.py 2012-04-17 17:59:54 +0000
+++ lib/lp/bugs/tests/test_bugvisibility.py 2012-04-17 18:01:41 +0000
@@ -88,72 +88,3 @@
88 def test_publicBugAnonUser(self):88 def test_publicBugAnonUser(self):
89 # Since the bug is private, the anonymous user cannot see it.89 # Since the bug is private, the anonymous user cannot see it.
90 self.assertFalse(self.bug.userCanView(None))90 self.assertFalse(self.bug.userCanView(None))
91
92
93class TestPrivateBugVisibilityAfterTransition(TestCaseWithFactory):
94 """Test visibility for a public bug, set to private."""
95
96 layer = DatabaseFunctionalLayer
97
98 def setUp(self):
99 super(TestPrivateBugVisibilityAfterTransition, self).setUp()
100 self.product_owner = self.factory.makePerson(name="productowner")
101 self.maintainer = self.factory.makeTeam(
102 name="maintainer",
103 owner=self.product_owner,
104 subscription_policy=TeamSubscriptionPolicy.RESTRICTED)
105 self.maintainer_member = self.factory.makePerson(
106 name="maintainermember")
107 self.owner = self.factory.makePerson(name="bugowner")
108 self.product = self.factory.makeProduct(
109 name="regular-product", owner=self.maintainer)
110 self.bug = self.factory.makeBug(
111 owner=self.owner, product=self.product)
112
113 self.bug_team_member = self.factory.makePerson(name="bugteammember")
114 self.bug_team = self.factory.makeTeam(
115 name="bugteam",
116 owner=self.product_owner)
117
118 with celebrity_logged_in('admin'):
119 self.maintainer.addMember(
120 self.maintainer_member,
121 self.product_owner)
122 self.bug_team.addMember(self.bug_team_member, self.product_owner)
123
124 def _makePrivate(self):
125 with celebrity_logged_in('admin'):
126 self.bug.setPrivate(private=True, who=self.product_owner)
127
128 @contextmanager
129 def _setupSupervisor(self):
130 with celebrity_logged_in('admin'):
131 self.product.setBugSupervisor(
132 bug_supervisor=self.bug_team,
133 user=self.product_owner)
134 yield
135 with celebrity_logged_in('admin'):
136 self.product.setBugSupervisor(
137 bug_supervisor=None,
138 user=self.product_owner)
139
140 def test_bug_supervisor_can_see_bug(self):
141 with self._setupSupervisor():
142 self._makePrivate()
143 self.assertTrue(self.bug.userCanView(self.bug_team_member))
144
145 def test_reporter_can_see(self):
146 self._makePrivate()
147 self.assertTrue(self.bug.userCanView(self.owner))
148
149 def test_maintainer_can_see_without_supervisor(self):
150 # If no bug supervisor is set, the maintainer is given access.
151 self._makePrivate()
152 self.assertTrue(self.bug.userCanView(self.maintainer_member))
153
154 def test_assignee_can_see(self):
155 bug_assignee = self.factory.makePerson(name="bugassignee")
156 with celebrity_logged_in('admin'):
157 self.bug.default_bugtask.transitionToAssignee(bug_assignee)
158 self._makePrivate()
159 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
=== modified file 'lib/lp/bugs/doc/bugnotification-sending.txt'
--- lib/lp/bugs/doc/bugnotification-sending.txt 2012-03-23 06:10:28 +0000
+++ lib/lp/bugs/doc/bugnotification-sending.txt 2012-04-17 18:05:27 +0000
@@ -756,9 +756,10 @@
756756
757 >>> for message in trigger_and_get_email_messages(bug_three):757 >>> for message in trigger_and_get_email_messages(bug_three):
758 ... print message['To'], message.get_all('X-Launchpad-Bug-Private')758 ... print message['To'], message.get_all('X-Launchpad-Bug-Private')
759 foo.bar@canonical.com ['yes']
760 mark@example.com ['yes']
759 test@canonical.com ['yes']761 test@canonical.com ['yes']
760762
761
762The X-Launchpad-Bug-Security-Vulnerability header763The X-Launchpad-Bug-Security-Vulnerability header
763-------------------------------------------------764-------------------------------------------------
764765
@@ -772,6 +773,8 @@
772 >>> for message in trigger_and_get_email_messages(bug_three):773 >>> for message in trigger_and_get_email_messages(bug_three):
773 ... print message['To'], (774 ... print message['To'], (
774 ... message.get_all('X-Launchpad-Bug-Security-Vulnerability'))775 ... message.get_all('X-Launchpad-Bug-Security-Vulnerability'))
776 foo.bar@canonical.com ['no']
777 mark@example.com ['no']
775 test@canonical.com ['no']778 test@canonical.com ['no']
776779
777The presence of the security flag on a bug is, surprise, denoted by a780The presence of the security flag on a bug is, surprise, denoted by a
@@ -786,6 +789,8 @@
786 >>> for message in trigger_and_get_email_messages(bug_three):789 >>> for message in trigger_and_get_email_messages(bug_three):
787 ... print message['To'], (790 ... print message['To'], (
788 ... message.get_all('X-Launchpad-Bug-Security-Vulnerability'))791 ... message.get_all('X-Launchpad-Bug-Security-Vulnerability'))
792 foo.bar@canonical.com ['yes']
793 mark@example.com ['yes']
789 test@canonical.com ['yes']794 test@canonical.com ['yes']
790795
791796
792797
=== modified file 'lib/lp/bugs/doc/bugsubscription.txt'
--- lib/lp/bugs/doc/bugsubscription.txt 2012-04-03 06:14:09 +0000
+++ lib/lp/bugs/doc/bugsubscription.txt 2012-04-17 18:05:27 +0000
@@ -380,17 +380,7 @@
380 ()380 ()
381381
382When a bug is marked private, specific people like the bugtask bug supervisors382When a bug is marked private, specific people like the bugtask bug supervisors
383will be automatically subscribed, and only specifically allowed existing383will be automatically subscribed.
384direct subscribers (eg bugtask pillar maintainers) will remain subscribed.
385
386We currently use a feature flag to control who is subscribed when a bug is
387made private.
388
389 >>> from lp.services.features.testing import FeatureFixture
390 >>> feature_flag = {
391 ... 'disclosure.enhanced_private_bug_subscriptions.enabled': 'on'}
392 >>> flags = FeatureFixture(feature_flag)
393 >>> flags.setUp()
394384
395 >>> from zope.event import notify385 >>> from zope.event import notify
396386
@@ -438,26 +428,20 @@
438 >>> print_displayname(linux_source_bug.getDirectSubscribers())428 >>> print_displayname(linux_source_bug.getDirectSubscribers())
439 Foo Bar429 Foo Bar
440 Mark Shuttleworth430 Mark Shuttleworth
431 Robert Collins
432 Ubuntu Team
441433
442 >>> print_displayname(linux_source_bug.getIndirectSubscribers())434 >>> print_displayname(linux_source_bug.getIndirectSubscribers())
443 Martin Pitt435 Martin Pitt
444 No Privileges Person436 No Privileges Person
445 Robert Collins
446 Sample Person437 Sample Person
447 Scott James Remnant438 Scott James Remnant
448 Ubuntu Team
449439
450 >>> print_displayname(linux_source_bug.getAlsoNotifiedSubscribers())440 >>> print_displayname(linux_source_bug.getAlsoNotifiedSubscribers())
451 Martin Pitt441 Martin Pitt
452 No Privileges Person442 No Privileges Person
453 Robert Collins
454 Sample Person443 Sample Person
455 Scott James Remnant444 Scott James Remnant
456 Ubuntu Team
457
458Clean up the feature flag.
459
460 >>> flags.cleanUp()
461445
462To find out which email addresses should receive a notification email on446To find out which email addresses should receive a notification email on
463a bug, and why, IBug.getBugNotificationRecipients() assembles an447a bug, and why, IBug.getBugNotificationRecipients() assembles an
@@ -472,8 +456,8 @@
472 [('foo.bar@canonical.com', 'Subscriber'),456 [('foo.bar@canonical.com', 'Subscriber'),
473 ('mark@example.com', 'Subscriber'),457 ('mark@example.com', 'Subscriber'),
474 ('no-priv@canonical.com', u'Subscriber (linux-source-2.6.15 in Ubuntu)'),458 ('no-priv@canonical.com', u'Subscriber (linux-source-2.6.15 in Ubuntu)'),
475 ('robertc@robertcollins.net', u'Subscriber (Mozilla Firefox)'),459 ('robertc@robertcollins.net', 'Subscriber'),
476 ('support@ubuntu.com', u'Subscriber (Ubuntu) @ubuntu-team'),460 ('support@ubuntu.com', u'Subscriber @ubuntu-team'),
477 ('test@canonical.com', 'Assignee')]461 ('test@canonical.com', 'Assignee')]
478462
479If IBug.getBugNotificationRecipients() is passed a BugNotificationLevel463If IBug.getBugNotificationRecipients() is passed a BugNotificationLevel
@@ -486,8 +470,8 @@
486 >>> [(address, recipients.getReason(address)[1]) for address in addresses]470 >>> [(address, recipients.getReason(address)[1]) for address in addresses]
487 [('foo.bar@canonical.com', 'Subscriber'),471 [('foo.bar@canonical.com', 'Subscriber'),
488 ('mark@example.com', 'Subscriber'),472 ('mark@example.com', 'Subscriber'),
489 ('robertc@robertcollins.net', u'Subscriber (Mozilla Firefox)'),473 ('robertc@robertcollins.net', 'Subscriber'),
490 ('support@ubuntu.com', u'Subscriber (Ubuntu) @ubuntu-team'),474 ('support@ubuntu.com', u'Subscriber @ubuntu-team'),
491 ('test@canonical.com', 'Assignee')]475 ('test@canonical.com', 'Assignee')]
492476
493When Sample Person is unsubscribed from linux_source_bug, he is no477When Sample Person is unsubscribed from linux_source_bug, he is no
@@ -501,8 +485,8 @@
501 >>> [(address, recipients.getReason(address)[1]) for address in addresses]485 >>> [(address, recipients.getReason(address)[1]) for address in addresses]
502 [('foo.bar@canonical.com', 'Subscriber'),486 [('foo.bar@canonical.com', 'Subscriber'),
503 ('mark@example.com', 'Subscriber'),487 ('mark@example.com', 'Subscriber'),
504 ('robertc@robertcollins.net', u'Subscriber (Mozilla Firefox)'),488 ('robertc@robertcollins.net', 'Subscriber'),
505 ('support@ubuntu.com', u'Subscriber (Ubuntu) @ubuntu-team'),489 ('support@ubuntu.com', u'Subscriber @ubuntu-team'),
506 ('test@canonical.com', 'Assignee')]490 ('test@canonical.com', 'Assignee')]
507491
508...but remains included for the level LIFECYCLE.492...but remains included for the level LIFECYCLE.
@@ -515,8 +499,8 @@
515 [('foo.bar@canonical.com', 'Subscriber'),499 [('foo.bar@canonical.com', 'Subscriber'),
516 ('mark@example.com', 'Subscriber'),500 ('mark@example.com', 'Subscriber'),
517 ('no-priv@canonical.com', u'Subscriber (linux-source-2.6.15 in Ubuntu)'),501 ('no-priv@canonical.com', u'Subscriber (linux-source-2.6.15 in Ubuntu)'),
518 ('robertc@robertcollins.net', u'Subscriber (Mozilla Firefox)'),502 ('robertc@robertcollins.net', 'Subscriber'),
519 ('support@ubuntu.com', u'Subscriber (Ubuntu) @ubuntu-team'),503 ('support@ubuntu.com', u'Subscriber @ubuntu-team'),
520 ('test@canonical.com', 'Assignee')]504 ('test@canonical.com', 'Assignee')]
521505
522To find out if someone is already directly subscribed to a bug, call506To find out if someone is already directly subscribed to a bug, call
523507
=== modified file 'lib/lp/bugs/doc/initial-bug-contacts.txt'
--- lib/lp/bugs/doc/initial-bug-contacts.txt 2012-04-03 06:14:09 +0000
+++ lib/lp/bugs/doc/initial-bug-contacts.txt 2012-04-17 18:05:27 +0000
@@ -288,16 +288,6 @@
288 u'Sample Person', u'Steve Alexander', u'Ubuntu Gnome Team',288 u'Sample Person', u'Steve Alexander', u'Ubuntu Gnome Team',
289 u'Ubuntu Team']289 u'Ubuntu Team']
290290
291
292We currently use a feature flag to control who is subscribed when a bug is
293made private.
294
295 >>> from lp.services.features.testing import FeatureFixture
296 >>> feature_flag = {
297 ... 'disclosure.enhanced_private_bug_subscriptions.enabled': 'on'}
298 >>> flags = FeatureFixture(feature_flag)
299 >>> flags.setUp()
300
301If a bug is private, no changes are made to the subscriber list when a291If a bug is private, no changes are made to the subscriber list when a
302bug is reassigned to a different package.292bug is reassigned to a different package.
303293
@@ -329,13 +319,9 @@
329the unallowed subscribers are removed:319the unallowed subscribers are removed:
330320
331 >>> subscriber_names(bug_one_in_ubuntu_firefox.bug)321 >>> subscriber_names(bug_one_in_ubuntu_firefox.bug)
332 [u'Foo Bar', u'Mark Shuttleworth', u'Sample Person',322 [u'Foo Bar', u'Sample Person',
333 u'Steve Alexander', u'Ubuntu Team']323 u'Steve Alexander', u'Ubuntu Team']
334324
335Clean up the feature flag.
336
337 >>> flags.cleanUp()
338
339325
340Product Bug Supervisors and Bug Tasks326Product Bug Supervisors and Bug Tasks
341-------------------------------------327-------------------------------------
342328
=== modified file 'lib/lp/bugs/doc/security-teams.txt'
--- lib/lp/bugs/doc/security-teams.txt 2012-04-04 05:46:26 +0000
+++ lib/lp/bugs/doc/security-teams.txt 2012-04-17 18:05:27 +0000
@@ -280,17 +280,7 @@
280280
281281
282When a bug becomes security-related, the security contacts for the pillars it282When a bug becomes security-related, the security contacts for the pillars it
283affects are subscribed to it. This happens regardless of whether the feature283affects are subscribed to it.
284flag is set.
285
286We currently use a feature flag to control who is subscribed when a bug is
287made security related.
288
289 >>> from lp.services.features.testing import FeatureFixture
290 >>> feature_flag = {
291 ... 'disclosure.enhanced_private_bug_subscriptions.enabled': 'on'}
292 >>> security_flags = FeatureFixture(feature_flag)
293 >>> security_flags.setUp()
294284
295 >>> from zope.event import notify285 >>> from zope.event import notify
296 >>> from lazr.lifecycle.event import ObjectModifiedEvent286 >>> from lazr.lifecycle.event import ObjectModifiedEvent
@@ -317,30 +307,3 @@
317 Bug Reporter307 Bug Reporter
318 Distribution Security Contact308 Distribution Security Contact
319 Product Security Contact309 Product Security Contact
320
321Clean up the feature flag.
322
323 >>> security_flags.cleanUp()
324
325And once more without the feature flag.
326
327 >>> product = factory.makeProduct()
328 >>> product.security_contact = factory.makePerson(
329 ... displayname='Product Security Contact')
330 >>> distribution = factory.makeDistribution()
331 >>> distribution.security_contact = factory.makePerson(
332 ... displayname='Distribution Security Contact')
333 >>> reporter = factory.makePerson(displayname=u'Bug Reporter')
334 >>> bug = factory.makeBug(product=product, owner=reporter)
335 >>> bug.addTask(owner=reporter, target=distribution)
336 <BugTask ...>
337 >>> old_state = Snapshot(bug, providing=IBug)
338 >>> bug.setSecurityRelated(True, getUtility(ILaunchBag).user)
339 True
340 >>> notify(ObjectModifiedEvent(bug, old_state, ['security_related']))
341 >>> for subscriber_name in sorted(
342 ... s.displayname for s in bug.getDirectSubscribers()):
343 ... print subscriber_name
344 Bug Reporter
345 Distribution Security Contact
346 Product Security Contact
347310
=== modified file 'lib/lp/bugs/model/bug.py'
--- lib/lp/bugs/model/bug.py 2012-04-17 01:11:47 +0000
+++ lib/lp/bugs/model/bug.py 2012-04-17 18:05:27 +0000
@@ -1733,10 +1733,6 @@
1733 "Cannot transition the information type to proprietary.")1733 "Cannot transition the information type to proprietary.")
1734 if self.information_type == information_type:1734 if self.information_type == information_type:
1735 return False1735 return False
1736 f_flag_str = 'disclosure.enhanced_private_bug_subscriptions.enabled'
1737 f_flag = bool(getFeatureFlag(f_flag_str))
1738 if f_flag:
1739 self.reconcileSubscribers(information_type, who)
1740 if (information_type == InformationType.PROPRIETARY and1736 if (information_type == InformationType.PROPRIETARY and
1741 len(self.affected_pillars) > 1):1737 len(self.affected_pillars) > 1):
1742 raise BugCannotBePrivate(1738 raise BugCannotBePrivate(
@@ -1744,34 +1740,47 @@
1744 if information_type in PRIVATE_INFORMATION_TYPES:1740 if information_type in PRIVATE_INFORMATION_TYPES:
1745 self.who_made_private = who1741 self.who_made_private = who
1746 self.date_made_private = UTC_NOW1742 self.date_made_private = UTC_NOW
1743 missing_subscribers = set([who, self.owner])
1747 else:1744 else:
1748 self.who_made_private = None1745 self.who_made_private = None
1749 self.date_made_private = None1746 self.date_made_private = None
1747 missing_subscribers = set()
1750 # XXX: This should be a bulk update. RBC 201008271748 # XXX: This should be a bulk update. RBC 20100827
1751 # bug=https://bugs.launchpad.net/storm/+bug/6250711749 # bug=https://bugs.launchpad.net/storm/+bug/625071
1752 for attachment in self.attachments_unpopulated:1750 for attachment in self.attachments_unpopulated:
1753 attachment.libraryfile.restricted = (1751 attachment.libraryfile.restricted = (
1754 information_type in PRIVATE_INFORMATION_TYPES)1752 information_type in PRIVATE_INFORMATION_TYPES)
1755 self.updateHeat()1753 self.updateHeat()
1756 if not f_flag and information_type == InformationType.USERDATA:1754
1757 # If we didn't call reconcileSubscribers(), we may have1755 # There are several people we need to ensure are subscribed.
1758 # bug supervisors who should be on this bug, but aren't.1756 # If the information type is userdata, we need to check for bug
1759 supervisors = set()1757 # supervisors who aren't subscribed and should be. If there is no
1760 for bugtask in self.bugtasks:1758 # bug supervisor, we need to subscribe the maintainer.
1761 supervisors.add(bugtask.pillar.bug_supervisor)1759 pillars = self.affected_pillars
1762 if None in supervisors:1760 if information_type == InformationType.USERDATA:
1763 supervisors.remove(None)1761 for pillar in pillars:
1764 for s in supervisors:1762 if pillar.bug_supervisor is not None:
1765 if not get_structural_subscriptions_for_bug(self, s):1763 missing_subscribers.add(pillar.bug_supervisor)
1766 self.subscribe(s, who)1764 else:
1767 if not f_flag and information_type in SECURITY_INFORMATION_TYPES:1765 missing_subscribers.add(pillar.owner)
1768 # The bug turned out to be security-related, subscribe the1766
1769 # security contact. We do it here only if the feature flag1767 # If the information type is security related, we need to ensure
1770 # is not set, otherwise it's done in1768 # the security contacts are subscribed. If there is no security
1771 # reconcileSubscribers().1769 # contact, we need to subscribe the maintainer.
1772 for pillar in self.affected_pillars:1770 if information_type in SECURITY_INFORMATION_TYPES:
1771 for pillar in pillars:
1773 if pillar.security_contact is not None:1772 if pillar.security_contact is not None:
1774 self.subscribe(pillar.security_contact, who)1773 missing_subscribers.add(pillar.security_contact)
1774 else:
1775 missing_subscribers.add(pillar.owner)
1776
1777 for s in missing_subscribers:
1778 # Don't subscribe someone if they're already subscribed via a
1779 # team.
1780 already_subscribed_teams = self.getSubscribersForPerson(s)
1781 if already_subscribed_teams.is_empty():
1782 self.subscribe(s, who)
1783
1775 self.information_type = information_type1784 self.information_type = information_type
1776 # Set the legacy attributes for now.1785 # Set the legacy attributes for now.
1777 self._private = information_type in PRIVATE_INFORMATION_TYPES1786 self._private = information_type in PRIVATE_INFORMATION_TYPES
@@ -1838,81 +1847,6 @@
1838 bug_supervisors.append(pillar.bug_supervisor)1847 bug_supervisors.append(pillar.bug_supervisor)
1839 return bug_supervisors, security_contacts1848 return bug_supervisors, security_contacts
18401849
1841 def reconcileSubscribers(self, information_type, who):
1842 """ Ensure only appropriate people are subscribed to private bugs.
1843
1844 When a bug is marked as either private = True or security_related =
1845 True, we need to ensure that only people who are authorised to know
1846 about the privileged contents of the bug remain directly subscribed
1847 to it. So we:
1848 1. Get the required subscribers depending on the bug status.
1849 2. Get the auto removed subscribers depending on the bug status.
1850 eg security contacts when a bug is updated to security related =
1851 false.
1852 3. Get the allowed subscribers = required subscribers
1853 + bugtask owners
1854 4. Remove any current direct subscribers who are not allowed or are
1855 to be auto removed.
1856 5. Add any subscribers who are required.
1857 """
1858 current_direct_subscribers = (
1859 self.getSubscriptionInfo().direct_subscribers)
1860 required_subscribers = self.getRequiredSubscribers(
1861 information_type, who)
1862 removed_bug_supervisors, removed_security_contacts = (
1863 self.getAutoRemovedSubscribers(information_type))
1864 for subscriber in removed_bug_supervisors:
1865 recipients = BugNotificationRecipients()
1866 recipients.addBugSupervisor(subscriber)
1867 notification_text = ("This bug is no longer private so the bug "
1868 "supervisor was unsubscribed. They will no longer be "
1869 "notified of changes to this bug for privacy related "
1870 "reasons, but may receive notifications about this bug from "
1871 "other subscriptions.")
1872 self.unsubscribe(
1873 subscriber, who, ignore_permissions=True,
1874 send_notification=True,
1875 notification_text=notification_text,
1876 recipients=recipients)
1877 for subscriber in removed_security_contacts:
1878 recipients = BugNotificationRecipients()
1879 recipients.addSecurityContact(subscriber)
1880 notification_text = ("This bug is no longer security related so "
1881 "the security contact was unsubscribed. They will no longer "
1882 "be notified of changes to this bug for security related "
1883 "reasons, but may receive notifications about this bug "
1884 "from other subscriptions.")
1885 self.unsubscribe(
1886 subscriber, who, ignore_permissions=True,
1887 send_notification=True,
1888 notification_text=notification_text,
1889 recipients=recipients)
1890
1891 # If this bug is for a project that is marked as having private bugs
1892 # by default, and the bug is private or security related, we will
1893 # unsubscribe any unauthorised direct subscribers.
1894 pillar = self.default_bugtask.pillar
1895 private_project = IProduct.providedBy(pillar) and pillar.private_bugs
1896 privileged_info = information_type != InformationType.PUBLIC
1897 if private_project and privileged_info:
1898 allowed_subscribers = set()
1899 allowed_subscribers.add(self.owner)
1900 for bugtask in self.bugtasks:
1901 allowed_subscribers.add(bugtask.owner)
1902 allowed_subscribers.add(bugtask.pillar.owner)
1903 allowed_subscribers.update(set(bugtask.pillar.drivers))
1904 allowed_subscribers = required_subscribers.union(
1905 allowed_subscribers)
1906 subscribers_to_remove = (
1907 current_direct_subscribers.difference(allowed_subscribers))
1908 for subscriber in subscribers_to_remove:
1909 self.unsubscribe(subscriber, who, ignore_permissions=True)
1910
1911 subscribers_to_add = (
1912 required_subscribers.difference(current_direct_subscribers))
1913 for subscriber in subscribers_to_add:
1914 self.subscribe(subscriber, who)
1915
1916 def getBugTask(self, target):1850 def getBugTask(self, target):
1917 """See `IBug`."""1851 """See `IBug`."""
1918 for bugtask in self.bugtasks:1852 for bugtask in self.bugtasks:
19191853
=== modified file 'lib/lp/bugs/model/tests/test_bug.py'
--- lib/lp/bugs/model/tests/test_bug.py 2012-04-17 01:11:47 +0000
+++ lib/lp/bugs/model/tests/test_bug.py 2012-04-17 18:05:27 +0000
@@ -567,14 +567,6 @@
567567
568 layer = DatabaseFunctionalLayer568 layer = DatabaseFunctionalLayer
569569
570 def setUp(self):
571 super(TestBugPrivateAndSecurityRelatedUpdatesMixin, self).setUp()
572 f_flag_str = 'disclosure.enhanced_private_bug_subscriptions.enabled'
573 feature_flag = {f_flag_str: 'on'}
574 flags = FeatureFixture(feature_flag)
575 flags.setUp()
576 self.addCleanup(flags.cleanUp)
577
578 def test_setPrivate_subscribes_person_who_makes_bug_private(self):570 def test_setPrivate_subscribes_person_who_makes_bug_private(self):
579 # When setPrivate(True) is called on a bug, the person who is571 # When setPrivate(True) is called on a bug, the person who is
580 # marking the bug private is subscribed to the bug.572 # marking the bug private is subscribed to the bug.
@@ -589,8 +581,8 @@
589 # marking the bug private will not be subscribed if they're581 # marking the bug private will not be subscribed if they're
590 # already a member of a team which is a direct subscriber.582 # already a member of a team which is a direct subscriber.
591 bug = self.factory.makeBug()583 bug = self.factory.makeBug()
592 team = self.factory.makeTeam()584 person = self.factory.makePerson(name='teamowner')
593 person = team.teamowner585 team = self.factory.makeTeam(owner=person, name='team')
594 with person_logged_in(person):586 with person_logged_in(person):
595 bug.subscribe(team, person)587 bug.subscribe(team, person)
596 bug.setPrivate(True, person)588 bug.setPrivate(True, person)
@@ -632,78 +624,71 @@
632 return (bug, bug_owner, naked_bugtask_a, naked_bugtask_b,624 return (bug, bug_owner, naked_bugtask_a, naked_bugtask_b,
633 naked_default_bugtask)625 naked_default_bugtask)
634626
635 def test_setPrivateTrueAndSecurityRelatedTrue(self):627 def test_transition_to_EMBARGOEDSECURITY_information_type(self):
636 # When a bug is marked as private=true and security_related=true, the628 # When a bug is marked as EMBARGOEDSECURITY, the direct subscribers
637 # direct subscribers should include:629 # should include:
638 # - the bug reporter630 # - the bug reporter
639 # - the bugtask pillar security contacts (if set)631 # - the bugtask pillar security contacts (if set)
640 # - the person changing the state632 # - the person changing the state
641 # - and bug/pillar owners, drivers if they are already subscribed633 # - and bug/pillar owners, drivers if they are already subscribed
642 # If the bug is for a private project, then other direct subscribers
643 # should be unsubscribed.
644634
645 (bug, bug_owner, bugtask_a, bugtask_b, default_bugtask) = (635 (bug, bug_owner, bugtask_a, bugtask_b, default_bugtask) = (
646 self.createBugTasksAndSubscribers())636 self.createBugTasksAndSubscribers())
647 initial_subscribers = set((637 initial_subscribers = set((
648 self.factory.makePerson(), bugtask_a.owner, bug_owner,638 self.factory.makePerson(name='subscriber'), bugtask_a.owner,
649 bugtask_a.pillar.security_contact, bugtask_a.pillar.driver))639 bug_owner, bugtask_a.pillar.security_contact,
640 bugtask_a.pillar.driver))
641 initial_subscribers.update(bug.getDirectSubscribers())
650642
651 with person_logged_in(bug_owner):643 with person_logged_in(bug_owner):
652 for subscriber in initial_subscribers:644 for subscriber in initial_subscribers:
653 bug.subscribe(subscriber, bug_owner)645 bug.subscribe(subscriber, bug_owner)
654 who = self.factory.makePerson()646 who = self.factory.makePerson(name='who')
655 bug.transitionToInformationType(647 bug.transitionToInformationType(
656 InformationType.EMBARGOEDSECURITY, who=who)648 InformationType.EMBARGOEDSECURITY, who=who)
657 subscribers = bug.getDirectSubscribers()649 subscribers = bug.getDirectSubscribers()
650 initial_subscribers.update(bug.getDirectSubscribers())
658 expected_subscribers = set((651 expected_subscribers = set((
659 bugtask_a.owner,652 bugtask_a.owner,
660 default_bugtask.pillar.bug_supervisor,
661 default_bugtask.pillar.driver,653 default_bugtask.pillar.driver,
662 default_bugtask.pillar.security_contact,654 default_bugtask.pillar.security_contact,
663 bug_owner, who))655 bug_owner, who))
664 if not self.private_project:656 expected_subscribers.update(initial_subscribers)
665 expected_subscribers.update(initial_subscribers)
666 self.assertContentEqual(expected_subscribers, subscribers)657 self.assertContentEqual(expected_subscribers, subscribers)
667658
668 def test_setPrivateTrueAndSecurityRelatedFalse(self):659 def test_transition_to_USERDATA_information_type(self):
669 # When a bug is marked as private=true and security_related=false, the660 # When a bug is marked as USERDATA, the direct subscribers should
670 # direct subscribers should include:661 # include:
671 # - the bug reporter662 # - the bug reporter
672 # - the bugtask pillar bug supervisors (if set)663 # - the bugtask pillar bug supervisors (if set)
673 # - the person changing the state664 # - the person changing the state
674 # - and bug/pillar owners, drivers if they are already subscribed665 # - and bug/pillar owners, drivers if they are already subscribed
675 # If the bug is for a private project, then other direct subscribers
676 # should be unsubscribed.
677666
678 (bug, bug_owner, bugtask_a, bugtask_b, default_bugtask) = (667 (bug, bug_owner, bugtask_a, bugtask_b, default_bugtask) = (
679 self.createBugTasksAndSubscribers(private_security_related=True))668 self.createBugTasksAndSubscribers(private_security_related=True))
680 initial_subscribers = set((669 initial_subscribers = set((
681 self.factory.makePerson(), bug_owner,670 self.factory.makePerson(name='subscriber'), bug_owner,
682 bugtask_a.pillar.security_contact, bugtask_a.pillar.driver))671 bugtask_a.pillar.security_contact, bugtask_a.pillar.driver))
683672
684 with person_logged_in(bug_owner):673 with person_logged_in(bug_owner):
685 for subscriber in initial_subscribers:674 for subscriber in initial_subscribers:
686 bug.subscribe(subscriber, bug_owner)675 bug.subscribe(subscriber, bug_owner)
687 who = self.factory.makePerson()676 who = self.factory.makePerson(name='who')
688 bug.transitionToInformationType(InformationType.USERDATA, who)677 bug.transitionToInformationType(InformationType.USERDATA, who)
689 subscribers = bug.getDirectSubscribers()678 subscribers = bug.getDirectSubscribers()
690 expected_subscribers = set((679 expected_subscribers = set((
691 default_bugtask.pillar.bug_supervisor,680 default_bugtask.pillar.bug_supervisor,
692 default_bugtask.pillar.driver,681 default_bugtask.pillar.driver,
693 bug_owner, who))682 bug_owner, who))
694 if not self.private_project:683 expected_subscribers.update(initial_subscribers)
695 expected_subscribers.update(initial_subscribers)
696 expected_subscribers.remove(bugtask_a.pillar.security_contact)
697 self.assertContentEqual(expected_subscribers, subscribers)684 self.assertContentEqual(expected_subscribers, subscribers)
698685
699 def test_setPrivateFalseAndSecurityRelatedTrue(self):686 def test_transition_to_UNEMBARGOEDSECURITY_information_type(self):
700 # When a bug is marked as private=false and security_related=true, the687 # When a security bug is unembargoed, direct subscribers should
701 # direct subscribers should include:688 # include:
702 # - the bug reporter689 # - the bug reporter
703 # - the bugtask pillar security contacts (if set)690 # - the bugtask pillar security contacts (if set)
704 # - and bug/pillar owners, drivers if they are already subscribed691 # - and bug/pillar owners, drivers if they are already subscribed
705 # If the bug is for a private project, then other direct subscribers
706 # should be unsubscribed.
707692
708 (bug, bug_owner, bugtask_a, bugtask_b, default_bugtask) = (693 (bug, bug_owner, bugtask_a, bugtask_b, default_bugtask) = (
709 self.createBugTasksAndSubscribers(private_security_related=True))694 self.createBugTasksAndSubscribers(private_security_related=True))
@@ -715,7 +700,7 @@
715 with person_logged_in(bug_owner):700 with person_logged_in(bug_owner):
716 for subscriber in initial_subscribers:701 for subscriber in initial_subscribers:
717 bug.subscribe(subscriber, bug_owner)702 bug.subscribe(subscriber, bug_owner)
718 who = self.factory.makePerson()703 who = self.factory.makePerson(name='who')
719 bug.transitionToInformationType(704 bug.transitionToInformationType(
720 InformationType.UNEMBARGOEDSECURITY, who)705 InformationType.UNEMBARGOEDSECURITY, who)
721 subscribers = bug.getDirectSubscribers()706 subscribers = bug.getDirectSubscribers()
@@ -723,36 +708,33 @@
723 default_bugtask.pillar.driver,708 default_bugtask.pillar.driver,
724 default_bugtask.pillar.security_contact,709 default_bugtask.pillar.security_contact,
725 bug_owner))710 bug_owner))
726 if not self.private_project:711 expected_subscribers.update(initial_subscribers)
727 expected_subscribers.update(initial_subscribers)
728 expected_subscribers.remove(default_bugtask.pillar.bug_supervisor)
729 self.assertContentEqual(expected_subscribers, subscribers)712 self.assertContentEqual(expected_subscribers, subscribers)
730713
731 def test_setPrivateFalseAndSecurityRelatedFalse(self):714 def test_transition_to_PUBLIC_information_type(self):
732 # When a bug is marked as private=false and security_related=false,715 # Subscriptions aren't altered when a bug is transitioned to the
733 # any existing subscriptions are left alone.716 # PUBLIC information type.
734717
735 (bug, bug_owner, bugtask_a, bugtask_b, default_bugtask) = (718 (bug, bug_owner, bugtask_a, bugtask_b, default_bugtask) = (
736 self.createBugTasksAndSubscribers(private_security_related=True))719 self.createBugTasksAndSubscribers(private_security_related=True))
737 initial_subscribers = set((720 initial_subscribers = set((
738 self.factory.makePerson(), bug_owner,721 self.factory.makePerson(name='subscriber'), bug_owner,
739 bugtask_a.pillar.security_contact, bugtask_a.pillar.driver))722 bugtask_a.pillar.security_contact, bugtask_a.pillar.driver))
740723
741 with person_logged_in(bug_owner):724 with person_logged_in(bug_owner):
742 for subscriber in initial_subscribers:725 for subscriber in initial_subscribers:
743 bug.subscribe(subscriber, bug_owner)726 bug.subscribe(subscriber, bug_owner)
744 who = self.factory.makePerson()727 who = self.factory.makePerson(name='who')
745 expected_direct_subscribers = set(bug.getDirectSubscribers())728 subscribers_before_public = set(bug.getDirectSubscribers())
746 bug.transitionToInformationType(InformationType.PUBLIC, who)729 bug.transitionToInformationType(InformationType.PUBLIC, who)
747 subscribers = set(bug.getDirectSubscribers())730 subscribers_after_public = set(bug.getDirectSubscribers())
748 expected_direct_subscribers.difference_update(731 self.assertContentEqual(
749 (default_bugtask.pillar.security_contact,732 subscribers_before_public,
750 default_bugtask.pillar.bug_supervisor,733 subscribers_after_public)
751 bugtask_a.pillar.security_contact))
752 self.assertContentEqual(expected_direct_subscribers, subscribers)
753734
754 def test_setPillarOwnerSubscribedIfNoBugSupervisor(self):735 def test_setPillarOwnerSubscribedIfNoBugSupervisor(self):
755 # The pillar owner is subscribed if the bug supervisor is not set.736 # The pillar owner is subscribed if the bug supervisor is not set and
737 # the bug is marked as USERDATA.
756738
757 bug_owner = self.factory.makePerson(name='bugowner')739 bug_owner = self.factory.makePerson(name='bugowner')
758 bug = self.factory.makeBug(owner=bug_owner)740 bug = self.factory.makeBug(owner=bug_owner)
@@ -766,18 +748,19 @@
766 subscribers)748 subscribers)
767749
768 def test_setPillarOwnerSubscribedIfNoSecurityContact(self):750 def test_setPillarOwnerSubscribedIfNoSecurityContact(self):
769 # The pillar owner is subscribed if the security contact is not set.751 # The pillar owner is subscribed if the security contact is not set
752 # and the bug is marked as EMBARGOEDSECURITY.
770753
771 bug_owner = self.factory.makePerson(name='bugowner')754 bug_owner = self.factory.makePerson(name='bugowner')
772 bug = self.factory.makeBug(owner=bug_owner)755 bug = self.factory.makeBug(owner=bug_owner)
773 with person_logged_in(bug_owner):756 with person_logged_in(bug_owner):
774 who = self.factory.makePerson()757 who = self.factory.makePerson(name='who')
775 bug.transitionToInformationType(758 bug.transitionToInformationType(
776 InformationType.UNEMBARGOEDSECURITY, who)759 InformationType.EMBARGOEDSECURITY, who)
777 subscribers = bug.getDirectSubscribers()760 subscribers = bug.getDirectSubscribers()
778 naked_bugtask = removeSecurityProxy(bug).default_bugtask761 naked_bugtask = removeSecurityProxy(bug).default_bugtask
779 self.assertContentEqual(762 self.assertContentEqual(
780 set((naked_bugtask.pillar.owner, bug_owner)),763 set((naked_bugtask.pillar.owner, bug_owner, who)),
781 subscribers)764 subscribers)
782765
783 def _fetch_notifications(self, bug, reason_header):766 def _fetch_notifications(self, bug, reason_header):
@@ -833,32 +816,6 @@
833 actual_recipients.append(recipient.person)816 actual_recipients.append(recipient.person)
834 self.assertContentEqual(expected_recipients, actual_recipients)817 self.assertContentEqual(expected_recipients, actual_recipients)
835818
836 def test_bugSupervisorUnsubscribedIfBugMadePublic(self):
837 # The bug supervisors are unsubscribed if a bug is made public and an
838 # email is sent telling them they have been unsubscribed.
839
840 (bug, bug_owner, bugtask_a, bugtask_b, default_bugtask) = (
841 self.createBugTasksAndSubscribers(private_security_related=True))
842
843 with person_logged_in(bug_owner):
844 bug.subscribe(default_bugtask.pillar.bug_supervisor, bug_owner)
845 who = self.factory.makePerson(name="who")
846 bug.transitionToInformationType(
847 InformationType.UNEMBARGOEDSECURITY, who)
848 subscribers = bug.getDirectSubscribers()
849 self.assertNotIn(
850 default_bugtask.pillar.bug_supervisor, subscribers)
851
852 expected_recipients = [
853 default_bugtask.pillar.bug_supervisor,
854 ]
855 expected_body_text = '** This bug is no longer private'
856 expected_reason_body = ('You received this bug notification '
857 'because you are a bug supervisor.')
858 self._check_notifications(
859 bug, expected_recipients, expected_body_text,
860 expected_reason_body, False, True, 'Bug Supervisor')
861
862 def test_structural_bug_supervisor_becomes_direct_on_private(self):819 def test_structural_bug_supervisor_becomes_direct_on_private(self):
863 # If a bug supervisor has a structural subscription to the bug, and820 # If a bug supervisor has a structural subscription to the bug, and
864 # the bug is marked as private, the supervisor should get a direct821 # the bug is marked as private, the supervisor should get a direct
@@ -876,31 +833,6 @@
876 bug.transitionToInformationType(InformationType.USERDATA, who)833 bug.transitionToInformationType(InformationType.USERDATA, who)
877 self.assertTrue(bug_supervisor in bug.getDirectSubscribers())834 self.assertTrue(bug_supervisor in bug.getDirectSubscribers())
878835
879 def test_securityContactUnsubscribedIfBugNotSecurityRelated(self):
880 # The security contacts are unsubscribed if a bug has security_related
881 # set to false and an email is sent telling them they have been
882 # unsubscribed.
883
884 (bug, bug_owner, bugtask_a, bugtask_b, default_bugtask) = (
885 self.createBugTasksAndSubscribers(private_security_related=True))
886
887 with person_logged_in(bug_owner):
888 bug.subscribe(bugtask_a.pillar.security_contact, bug_owner)
889 who = self.factory.makePerson(name="who")
890 bug.transitionToInformationType(InformationType.USERDATA, who)
891 subscribers = bug.getDirectSubscribers()
892 self.assertFalse(bugtask_a.pillar.security_contact in subscribers)
893
894 expected_recipients = [
895 bugtask_a.pillar.security_contact,
896 ]
897 expected_body_text = '** This bug is no longer security related'
898 expected_reason_body = ('You received this bug notification '
899 'because you are a security contact.')
900 self._check_notifications(
901 bug, expected_recipients, expected_body_text,
902 expected_reason_body, True, False, 'Security Contact')
903
904836
905class TestBugPrivacy(TestCaseWithFactory):837class TestBugPrivacy(TestCaseWithFactory):
906838
907839
=== modified file 'lib/lp/bugs/tests/test_bug_mirror_access_triggers.py'
--- lib/lp/bugs/tests/test_bug_mirror_access_triggers.py 2012-04-03 06:14:09 +0000
+++ lib/lp/bugs/tests/test_bug_mirror_access_triggers.py 2012-04-17 18:05:27 +0000
@@ -141,7 +141,9 @@
141 bug.setPrivate(True, bug.owner)141 bug.setPrivate(True, bug.owner)
142 self.assertIsNot(142 self.assertIsNot(
143 None, getUtility(IAccessArtifactSource).find([bug]).one())143 None, getUtility(IAccessArtifactSource).find([bug]).one())
144 self.assertEqual((1, 1), self.assertMirrored(bug))144 # There are two grants--one for the reporter, one for the product
145 # owner or supervisor (if set). There is only one policy, USERDATA.
146 self.assertEqual((2, 1), self.assertMirrored(bug))
145147
146 def test_security_related(self):148 def test_security_related(self):
147 # Setting the security_related flag uses EMBARGOEDSECURITY149 # Setting the security_related flag uses EMBARGOEDSECURITY
@@ -153,7 +155,9 @@
153 [InformationType.USERDATA],155 [InformationType.USERDATA],
154 self.getPolicyTypesForArtifact(artifact))156 self.getPolicyTypesForArtifact(artifact))
155 bug.setSecurityRelated(True, bug.owner)157 bug.setSecurityRelated(True, bug.owner)
156 self.assertEqual((1, 1), self.assertMirrored(bug))158 # Both the reporter and either the product owner or the product's
159 # security contact have grants.
160 self.assertEqual((2, 1), self.assertMirrored(bug))
157 self.assertContentEqual(161 self.assertContentEqual(
158 [InformationType.EMBARGOEDSECURITY],162 [InformationType.EMBARGOEDSECURITY],
159 self.getPolicyTypesForArtifact(artifact))163 self.getPolicyTypesForArtifact(artifact))
160164
=== modified file 'lib/lp/bugs/tests/test_bugvisibility.py'
--- lib/lp/bugs/tests/test_bugvisibility.py 2012-02-15 02:01:14 +0000
+++ lib/lp/bugs/tests/test_bugvisibility.py 2012-04-17 18:05:27 +0000
@@ -3,11 +3,17 @@
33
4"""Tests for visibility of a bug."""4"""Tests for visibility of a bug."""
55
6from contextlib import contextmanager
7
8from lp.registry.interfaces.person import TeamSubscriptionPolicy
6from lp.testing import (9from lp.testing import (
7 celebrity_logged_in,10 celebrity_logged_in,
8 TestCaseWithFactory,11 TestCaseWithFactory,
9 )12 )
10from lp.testing.layers import LaunchpadFunctionalLayer13from lp.testing.layers import (
14 DatabaseFunctionalLayer,
15 LaunchpadFunctionalLayer,
16 )
1117
1218
13class TestPublicBugVisibility(TestCaseWithFactory):19class TestPublicBugVisibility(TestCaseWithFactory):
1420
=== modified file 'lib/lp/services/features/flags.py'
--- lib/lp/services/features/flags.py 2012-04-11 05:21:33 +0000
+++ lib/lp/services/features/flags.py 2012-04-17 18:05:27 +0000
@@ -216,13 +216,6 @@
216 '',216 '',
217 '',217 '',
218 ''),218 ''),
219 ('disclosure.enhanced_private_bug_subscriptions.enabled',
220 'boolean',
221 ('Enables the auto subscribing and unsubscribing of users as a bug '
222 'transitions between public, private and security related states.'),
223 '',
224 '',
225 ''),
226 ('disclosure.users_hide_own_bug_comments.enabled',219 ('disclosure.users_hide_own_bug_comments.enabled',
227 'boolean',220 'boolean',
228 'Allows users in project roles and comment owners to hide bug comments.',221 'Allows users in project roles and comment owners to hide bug comments.',