Merge lp:~brian-murray/launchpad/bug-556489-distro-struct-sub into lp:launchpad

Proposed by Brian Murray
Status: Merged
Approved by: Deryck Hodge
Approved revision: no longer in the source branch.
Merged at revision: 11385
Proposed branch: lp:~brian-murray/launchpad/bug-556489-distro-struct-sub
Merge into: lp:launchpad
Diff against target: 673 lines (+295/-148)
10 files modified
lib/lp/bugs/browser/bug.py (+1/-1)
lib/lp/bugs/browser/bugtask.py (+3/-1)
lib/lp/bugs/templates/bugtarget-portlet-bugfilters.pt (+1/-1)
lib/lp/registry/browser/structuralsubscription.py (+5/-1)
lib/lp/registry/configure.zcml (+14/-7)
lib/lp/registry/interfaces/distribution.py (+1/-1)
lib/lp/registry/interfaces/structuralsubscription.py (+3/-0)
lib/lp/registry/model/structuralsubscription.py (+24/-1)
lib/lp/registry/tests/structural-subscription-target.txt (+5/-134)
lib/lp/registry/tests/test_structuralsubscriptiontarget.py (+238/-1)
To merge this branch: bzr merge lp:~brian-murray/launchpad/bug-556489-distro-struct-sub
Reviewer Review Type Date Requested Status
Deryck Hodge (community) code Approve
Review via email: mp+31090@code.launchpad.net

Commit message

If a distribution has a bug supervisor set, only allow the bug supervisor or members of that team to subscribe to all of the distribution's bug reports.

Description of the change

= Summary =

The Ubuntu distribution has a large number of subscribers whose accounts are
deactivated, https://bugs.edge.launchpad.net/ubuntu/+subscribe, probably due
to the volume of the bug mail they ended up receiving. This is problematic to
some degree as all the subscribers show up on every bug report about Ubuntu.
Subscribing to all of Ubuntu bugs can also cause an unpleasant user experience
due to the volume of email they end up receiving and it not being clear how to
stop all of it. Additionally, if someone really wants to subscribe to every
bug report about Ubuntu there is a mailing list
(https://lists.ubuntu.com/mailman/listinfo/ubuntu-bugs) for that.

== Proposed fix ==

To prevent people from over subscribing themselves a permissions check should
be added to distribution stuctural subscriptions to ensure that only admins or
bug supervisors, if one exists, can subscribe to the distribution.

== Pre-implementation notes ==

This was discussed with Deryck who reported bug 556489 regarding this issue
and we discussed the process for removing existing subscribers to Ubuntu.

== Implementation details ==

While writing a test for the special case of distribution structural
subscriptions, I discovered that test_structuralsubscriptiontarget.py really
used structural-subscription-target.txt which needed some rewriting as a unit
test.

lib/lp/registry/model/structuralsubscription.py:
 * Added distribution check to addBugSubscription

lib/lp/registry/tests/test_structuralsubscriptiontarget.py:
 * Refactored most of structural-subscription-target.txt as a unit test

lib/lp/registry/tests/structural-subscription-target.txt:
 * Removed portions implemented in test_structuralsubscriptiontarget.py

== Tests ==

bin/test -vv -t test_structuralsubscriptiontarget -t structural-subscription-target.txt

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

I'm not clear why this makes sense.

 - users can subscribe to the bugs list [same amount of mail]
 - there are uses to being notified of every bug, same as things like
gmane are subscribed to lists
 - this makes lp less flexible not more.

I suspect you're doing a bandaid to the real issues:
 - subscriber lists take up a lot of space and are rarely important
 - we don't prune deactivated accounts effectively
 - the implications of structural subscription are not clear to users

Could you please enlarge on why the approach you've put forward makes
sense long term? If we do need a bandaid, I'm positive doing that, but
I get the impression you don't consider this a bandaid, and given the
downsides: more interrupts from valid subscribers, more code to handle
special-only-in-scale problems, more ui complexity that is hard to
explain, I'd really prefer to see a path to a long term unified fix!

-Rob

Revision history for this message
Brian Murray (brian-murray) wrote :

On Wed, Jul 28, 2010 at 05:13:42AM -0000, Robert Collins wrote:
> I'm not clear why this makes sense.
>
> - users can subscribe to the bugs list [same amount of mail]

However, it is harder to subscribe to the mailing list than structurally
subscribe to Ubuntu.

> - there are uses to being notified of every bug, same as things like
> gmane are subscribed to lists
> - this makes lp less flexible not more.

Yes, but this only applies to distribution's using Launchpad that have a
bug supervisor. Additionally, it just so happens that Ubuntu has a team
set as the bug supervisor which has the email address set to a mailing
list. Other distributions do not, or should not, have to have a bug
supervisor set and in the event that they do that could be a team
without a mailing list set for the contact address.

> I suspect you're doing a bandaid to the real issues:
> - subscriber lists take up a lot of space and are rarely important
> - we don't prune deactivated accounts effectively
> - the implications of structural subscription are not clear to users
>
> Could you please enlarge on why the approach you've put forward makes
> sense long term? If we do need a bandaid, I'm positive doing that, but
> I get the impression you don't consider this a bandaid, and given the
> downsides: more interrupts from valid subscribers, more code to handle
> special-only-in-scale problems, more ui complexity that is hard to
> explain, I'd really prefer to see a path to a long term unified fix!

I do agree that is a band-aid in that we are preventing people from
doing something they don't really understand the consequences of.
Looking at my personal email archive I have some emails from people who
mistakenly subscribed to all bug reports about Ubuntu, did not know how
to unsubscribe and were receiving hundreds of emails a day. While
emails provide an unsubscribe link for individual bug reports there is
no link for how to remove a structural subscription.

I'd guess that we will get less requests from people wanting to
subscribe to all bug reports about Ubuntu than we will about people
wanting to be unsubscribed. Perhaps we should track this somehow?

--
Brian Murray

Revision history for this message
Brian Murray (brian-murray) wrote :

A structural subscription unsubscribe link is covered in bug 484319.

Revision history for this message
Abel Deuring (adeuring) wrote :

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

On 28.07.2010 19:41, Brian Murray wrote:
> I do agree that is a band-aid in that we are preventing people from
> doing something they don't really understand the consequences of.
> Looking at my personal email archive I have some emails from people who
> mistakenly subscribed to all bug reports about Ubuntu, did not know how
> to unsubscribe and were receiving hundreds of emails a day. While
> emails provide an unsubscribe link for individual bug reports there is
> no link for how to remove a structural subscription.

Another example: <email address hidden> receives more or less regulary
complaints from people who feel "drowning" by the amount of mails they
get from bug subscriptions.
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.10 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iD8DBQFMUIF/ekBPhm8NrtARAtQqAKCQ2872DAsH0hByPaK8dzTlLk/jiwCeOjwE
LI/vZ8xrFAShGAD7DE6MBjY=
=50CY
-----END PGP SIGNATURE-----

Revision history for this message
Deryck Hodge (deryck) wrote :

While I understand Rob's concerns, I think in this case we do want to be opinionated and keep people from getting themselves into trouble. "less flexible" is a feature not a bug in this case. :-)

Yes, we should fix the surrounding issues, some of which will be fixed by our current work to improve subscriptions. But there is rarely a case where subscribing to all of a distribution's bugs is what people actually want to do. For the times where there is -- e.g., someone wants to archive all bug activity for a distribution -- there is an easy work around by subscribing to the mailing list or join the bug supervisor team. Perhaps this adds some UI complexity to communicate how one would go about this, but I think this risk is acceptable given the alternatives and the few number of people who desire this.

I'll also take a look at the code now to review this for Brian, to get him unblocked on this work.

Cheers,
deryck

Revision history for this message
Deryck Hodge (deryck) wrote :

Hi, Brian.

I've had a chance to review the code itself now. Thanks for the great job here you did refactoring the tests! Well done!

I do, however, see a couple problems that need to be addressed before this is ready to land.

First, you do a great job of avoiding sample data in the test except for your admin test, which uses Foo Bar. Your commented out attempt at using a with block and logging in the admin celebrity is the right way to go. You can reference the currently logged in user inside that block. Look in lp.testing.tests.test_login and look at test_with_celebrity_logged_in and getLoggedInPerson for ideas of how to do this. If you need more help working this out, ping me on IRC.

Second, this work ensures that addBugSubscription won't succeed, but the user will still have a subscribe to bug mail link, which will then OOPS if that click-path is followed and the user tries to subscribe. The code that checks that the user can subscribe should be refactored into a helper method which can be called from the model and the view so that the link is never presented in the first place. See lp.bugs.browser.bugtask.BugTaskSearchListingMenu.subscribe for where to do this check in the view.

Again, feel free to ping me if you need to talk more about these changes.

Cheers,
deryck

review: Needs Fixing (code)
Revision history for this message
Deryck Hodge (deryck) wrote :

The code itself looks fine now. But I led you wrong on where to check to display the link, I think. This didn't work for me testing locally. Did it work for you?

I didn't realize there is a mix at lp.registry.browser.structuralsubscription.StructuralSubscriptionMenuMixin where the link gets setup now. Sorry. The check is good, I believe, but needs to be done inside the mixin.

Revision history for this message
Robert Collins (lifeless) wrote :

So, to make sure I understand:
 - a distro without a bug supervisor does not permit structural subscription?

We are expecting many more distros though - derived distros are being worked on.

I have no problem with being opinionated; I'd -much- rather we warned
than stopped people - simply because if we prohibit something we
*will* get bug reports that its prohibited (the same as we get users
who were not warned about the firehose - who were *invited* into doing
something silly).

-Rob

Revision history for this message
Brian Murray (brian-murray) wrote :

On Tue, Aug 03, 2010 at 10:11:04PM -0000, Robert Collins wrote:
> So, to make sure I understand:
> - a distro without a bug supervisor does not permit structural subscription?

No, this is backwards.

A distro without a bug supervisor allows structural subscription.

A distro with a bug supervisor only allows the bug supervisor, or
members of that team, to structurally subscribe.

Revision history for this message
Deryck Hodge (deryck) wrote :

On Tue, Aug 3, 2010 at 5:11 PM, Robert Collins
<email address hidden> wrote:
> So, to make sure I understand:
>  - a distro without a bug supervisor does not permit structural subscription?
>
> We are expecting many more distros though - derived distros are being worked on.
>
> I have no problem with being opinionated; I'd -much- rather we warned
> than stopped people - simply because if we prohibit something we
> *will* get bug reports that its prohibited (the same as we get users
> who were not warned about the firehose - who were *invited* into doing
> something silly).
>

Right, but we get bug reports regardless of what we do. :-) The
threat of bug reports makes me neither fear nor regret the change. ;)

The real question is "should we allow users to subscribe to all of a
distributions bugs?" I think for distributions with a large number of
users and bugs this makes no sense. Brian's choice to limit this to
distros that have defined a bug supervisor seems about the best check
we have for "is this a large distro?" Not perfect, sure, but not bad
either.

The cases of someone wanting to subscribe to every bug email for
everything in a distro are few -- people like Brian and the Debian PTS
-- and all of these cases have an option to get at the notifications
(subscribe to mailing list or join bug supervisor team), and in the
common case, we prevent people from getting themselves into trouble
and giving Launchpad a bad reputation for spamming people. Also,
we're not changing anyone's current subscription, just preventing them
in the future. We're only going to clean up deactivated accounts once
this work lands.

People don't read warnings. There is already a warning.

Finally, I really don't understand why this change is a big deal. In
the world of Launchpad, this is a very minor area of potential
complexity and confusion, and it brings great benefit in terms of
sparing less-experienced users getting spammed by the bug tracker.
It's well known and well documented that, despite our best efforts,
people end up subscribing to all of Ubuntu's bugs. Why not prevent
that and be done with it? :-)

Cheers,
deryck

--
Deryck Hodge
https://launchpad.net/~deryck
http://www.devurandom.org/

Revision history for this message
Robert Collins (lifeless) wrote :

>> Finally, I really don't understand why this change is a big deal.

Its not a big deal :- I'm still getting a handle on a couple of things.

One of them is how to be clear what hat I'm when I say something; in
this conversation its a bit unclear - sorry. I wasn't trying to swing
the TA bat at this particular mp - rather I trying to really
understand the tradeoffs we're making, and why - and expressing my
personal opinions freely as I learn.

The other thing *is* architectural, which added to the confusion i
generated with the first thing : I'm still refining my understanding /
story around what bits of our UI are for machines, and what aren't :
the email interfaces are kindof-both and as such there are tensions
around making it really excellent. (Contrast with the web UI - humans
only, and the rest UI - machines only).

-Rob

Revision history for this message
Brian Murray (brian-murray) wrote :

It should be ready for re-review now. I tested it locally by setting a bug supervisor (guadamen) for Ubuntu and then confirming that the link did not appear for someone (sample person) who is not a member of that bug supervisor team.

Revision history for this message
Deryck Hodge (deryck) wrote :

Looks good. Thanks for all your work getting this ready.

Cheers,
deryck

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/browser/bug.py'
2--- lib/lp/bugs/browser/bug.py 2010-08-07 14:54:40 +0000
3+++ lib/lp/bugs/browser/bug.py 2010-08-17 14:18:48 +0000
4@@ -251,7 +251,7 @@
5 return Link('+addbranch', text, icon='add')
6
7 def linktocve(self):
8- """Return the 'Link tp CVE' Link."""
9+ """Return the 'Link to CVE' Link."""
10 text = structured(
11 'Link to '
12 '<abbr title="Common Vulnerabilities and Exposures Index">'
13
14=== modified file 'lib/lp/bugs/browser/bugtask.py'
15--- lib/lp/bugs/browser/bugtask.py 2010-08-03 08:49:19 +0000
16+++ lib/lp/bugs/browser/bugtask.py 2010-08-17 14:18:48 +0000
17@@ -2320,7 +2320,9 @@
18 '+securitycontact', 'Change security contact', icon='edit')
19
20 def subscribe(self):
21- return Link('+subscribe', 'Subscribe to bug mail', icon='edit')
22+ user = getUtility(ILaunchBag).user
23+ if self.context.userCanAlterBugSubscription(user):
24+ return Link('+subscribe', 'Subscribe to bug mail', icon='edit')
25
26 def nominations(self):
27 return Link('+nominations', 'Review nominations', icon='bug')
28
29=== modified file 'lib/lp/bugs/templates/bugtarget-portlet-bugfilters.pt'
30--- lib/lp/bugs/templates/bugtarget-portlet-bugfilters.pt 2009-12-07 15:00:59 +0000
31+++ lib/lp/bugs/templates/bugtarget-portlet-bugfilters.pt 2010-08-17 14:18:48 +0000
32@@ -11,7 +11,7 @@
33 tal:content="structure context/@@+bugtarget-portlet-bugfilters-info" />
34 <tbody tal:define="menu context/menu:bugs">
35 <tr tal:define="subscribe_link menu/subscribe|nothing"
36- tal:condition="subscribe_link">
37+ tal:condition="python: subscribe_link and subscribe_link.enabled">
38 <td class="bugs-count" style="padding-top: 1em">
39 <a tal:attributes="href subscribe_link/url">
40 <img tal:attributes="src subscribe_link/icon_url" />
41
42=== modified file 'lib/lp/registry/browser/structuralsubscription.py'
43--- lib/lp/registry/browser/structuralsubscription.py 2010-04-16 15:06:55 +0000
44+++ lib/lp/registry/browser/structuralsubscription.py 2010-08-17 14:18:48 +0000
45@@ -313,4 +313,8 @@
46 else:
47 text = 'Subscribe to bug mail'
48 icon = 'add'
49- return Link('+subscribe', text, icon=icon, enabled=enabled)
50+ if enabled == False or (
51+ not sst.userCanAlterBugSubscription(self.user, self.user)):
52+ return Link('+subscribe', text, icon=icon, enabled=False)
53+ else:
54+ return Link('+subscribe', text, icon=icon, enabled=enabled)
55
56=== modified file 'lib/lp/registry/configure.zcml'
57--- lib/lp/registry/configure.zcml 2010-08-06 14:49:35 +0000
58+++ lib/lp/registry/configure.zcml 2010-08-17 14:18:48 +0000
59@@ -150,7 +150,8 @@
60 getSubscriptions
61 parent_subscription_target
62 target_type_display
63- userHasBugSubscriptions"/>
64+ userHasBugSubscriptions
65+ userCanAlterBugSubscription"/>
66 <require
67 permission="launchpad.AnyPerson"
68 attributes="
69@@ -294,7 +295,8 @@
70 getBugNotificationsRecipients
71 parent_subscription_target
72 target_type_display
73- userHasBugSubscriptions"/>
74+ userHasBugSubscriptions
75+ userCanAlterBugSubscription"/>
76 <require
77 permission="launchpad.AnyPerson"
78 attributes="
79@@ -417,7 +419,8 @@
80 official_bug_tags
81 findRelatedArchives
82 findRelatedArchivePublications
83- userHasBugSubscriptions"/>
84+ userHasBugSubscriptions
85+ userCanAlterBugSubscription"/>
86 <require
87 permission="launchpad.AnyPerson"
88 attributes="
89@@ -924,7 +927,8 @@
90 getBugNotificationsRecipients
91 parent_subscription_target
92 target_type_display
93- userHasBugSubscriptions"/>
94+ userHasBugSubscriptions
95+ userCanAlterBugSubscription"/>
96 <require
97 permission="launchpad.AnyPerson"
98 attributes="
99@@ -1195,7 +1199,8 @@
100 getBugNotificationsRecipients
101 parent_subscription_target
102 target_type_display
103- userHasBugSubscriptions"/>
104+ userHasBugSubscriptions
105+ userCanAlterBugSubscription"/>
106 <require
107 permission="launchpad.AnyPerson"
108 attributes="
109@@ -1344,7 +1349,8 @@
110 getSubscriptions
111 parent_subscription_target
112 target_type_display
113- userHasBugSubscriptions"/>
114+ userHasBugSubscriptions
115+ userCanAlterBugSubscription"/>
116 <require
117 permission="launchpad.AnyPerson"
118 attributes="
119@@ -1467,7 +1473,8 @@
120 getBugNotificationsRecipients
121 parent_subscription_target
122 target_type_display
123- userHasBugSubscriptions"/>
124+ userHasBugSubscriptions
125+ userCanAlterBugSubscription"/>
126 <require
127 permission="launchpad.AnyPerson"
128 attributes="
129
130=== modified file 'lib/lp/registry/interfaces/distribution.py'
131--- lib/lp/registry/interfaces/distribution.py 2010-08-12 19:35:11 +0000
132+++ lib/lp/registry/interfaces/distribution.py 2010-08-17 14:18:48 +0000
133@@ -183,7 +183,7 @@
134 "The person or team responsible for decisions about features "
135 "and bugs that will be targeted for any series in this "
136 "distribution. Note that you can also specify a driver "
137- "on each series who's permissions will be limited to that "
138+ "on each series whose permissions will be limited to that "
139 "specific series."),
140 required=False, vocabulary='ValidPersonOrTeam'))
141 drivers = Attribute(
142
143=== modified file 'lib/lp/registry/interfaces/structuralsubscription.py'
144--- lib/lp/registry/interfaces/structuralsubscription.py 2010-07-28 20:44:25 +0000
145+++ lib/lp/registry/interfaces/structuralsubscription.py 2010-08-17 14:18:48 +0000
146@@ -184,6 +184,9 @@
147 :return: The new subscription.
148 """
149
150+ def userCanAlterBugSubscription(subscriber, subscribed_by):
151+ """Check if a user can change a bug subscription for a person."""
152+
153 @operation_parameters(
154 subscriber=Reference(
155 schema=IPerson,
156
157=== modified file 'lib/lp/registry/model/structuralsubscription.py'
158--- lib/lp/registry/model/structuralsubscription.py 2010-07-29 20:16:23 +0000
159+++ lib/lp/registry/model/structuralsubscription.py 2010-08-17 14:18:48 +0000
160@@ -179,12 +179,35 @@
161 subscribed_by=subscribed_by,
162 **self._target_args)
163
164+ def userCanAlterBugSubscription(self, subscriber, subscribed_by):
165+ """See `IStructuralSubscriptionTarget`."""
166+
167+ admins = getUtility(ILaunchpadCelebrities).admin
168+ # If the object to be structurally subscribed to for bug
169+ # notifications is a distribution and that distribution has a
170+ # bug supervisor then only the bug supervisor or a member of
171+ # that team or, of course, admins, can subscribe someone to it.
172+ if IDistribution.providedBy(self) and self.bug_supervisor is not None:
173+ if subscriber is None or subscribed_by is None:
174+ return False
175+ elif (subscriber != self.bug_supervisor
176+ and not subscriber.inTeam(self.bug_supervisor)
177+ and not subscribed_by.inTeam(admins)):
178+ return False
179+ return True
180+
181 def addBugSubscription(self, subscriber, subscribed_by):
182 """See `IStructuralSubscriptionTarget`."""
183 # This is a helper method for creating a structural
184 # subscription and immediately giving it a full
185 # bug notification level. It is useful so long as
186 # subscriptions are mainly used to implement bug contacts.
187+
188+ if not self.userCanAlterBugSubscription(subscriber, subscribed_by):
189+ raise UserCannotSubscribePerson(
190+ '%s does not have permission to subscribe %s' % (
191+ subscribed_by.name, subscriber.name))
192+
193 sub = self.addSubscription(subscriber, subscribed_by)
194 sub.bug_notification_level = BugNotificationLevel.COMMENTS
195 return sub
196@@ -239,7 +262,7 @@
197 for key, value in self._target_args.items():
198 if value is None:
199 target_clause_parts.append(
200- "StructuralSubscription.%s IS NULL " % (key,))
201+ "StructuralSubscription.%s IS NULL " % (key, ))
202 else:
203 target_clause_parts.append(
204 "StructuralSubscription.%s = %s " % (key, quote(value)))
205
206=== modified file 'lib/lp/registry/tests/structural-subscription-target.txt'
207--- lib/lp/registry/tests/structural-subscription-target.txt 2010-04-19 08:11:52 +0000
208+++ lib/lp/registry/tests/structural-subscription-target.txt 2010-08-17 14:18:48 +0000
209@@ -1,144 +1,15 @@
210 = IStructuralSubscriptionTarget =
211
212-Structural subscriptions allow users to subscribe to notifications about
213-new items and changes to existing items for a Launchpad structure such as
214-Product, ProjectGroup, Distribution, Milestone and series.
215-
216- >>> from canonical.launchpad.interfaces import (
217- ... IStructuralSubscriptionTarget)
218- >>> from canonical.launchpad.webapp.testing import verifyObject
219- >>> verifyObject(IStructuralSubscriptionTarget, target)
220- True
221-
222-== Bug Subscriptions ==
223-
224-Bug subscriptions are structural subscriptions to notifications about
225-bug activity.
226-
227- >>> target.bug_subscriptions
228- []
229-
230-First, we try to create a new subscription.
231+
232+Let's subscribe ubuntu-team.
233
234 >>> from canonical.launchpad.interfaces import IPersonSet
235 >>> personset = getUtility(IPersonSet)
236+ >>> ubuntu_team = personset.getByName("ubuntu-team")
237 >>> no_priv = personset.getByName("no-priv")
238-
239-Only authenticated users can create subscriptions.
240-
241- >>> target.addBugSubscription(no_priv, no_priv)
242- Traceback (most recent call last):
243- ...
244- Unauthorized: ...
245-
246-Let's login then to add a subscription:
247-
248+ >>> foobar = personset.getByName("name16")
249 >>> from canonical.launchpad.ftests import login
250 >>> login("foo.bar@canonical.com")
251-
252- >>> target.addBugSubscription(no_priv, no_priv)
253- <StructuralSubscription ...>
254- >>> [sub.subscriber.name for sub in target.bug_subscriptions]
255- [u'no-priv']
256-
257-Trying to add a subscription to a target when that person
258-or team is already subscribed to that target will return
259-the existing subscription.
260-
261- >>> target.addBugSubscription(no_priv, no_priv)
262- <StructuralSubscription ...>
263-
264-People can only be subscribed by themselves, and only the team admins may
265-subscribe a team.
266-
267-no-priv, who has no relationship to ubuntu-team, cannot subscribe it.
268-
269- >>> ubuntu_team = personset.getByName("ubuntu-team")
270- >>> target.addBugSubscription(ubuntu_team, no_priv)
271- Traceback (most recent call last):
272- ...
273- UserCannotSubscribePerson: no-priv does not have permission to subscribe ubuntu-team.
274-
275-But kamion, an admin of the team, can.
276-
277- >>> kamion = personset.getByName("kamion")
278- >>> target.addBugSubscription(ubuntu_team, kamion)
279- <StructuralSubscription ...>
280-
281- >>> sorted([sub.subscriber.name for sub in target.bug_subscriptions])
282- [u'no-priv', u'ubuntu-team']
283-
284-foobar, a Launchpad administrator, can as well.
285-
286- >>> foobar = personset.getByName("name16")
287- >>> target.addBugSubscription(ubuntu_team, foobar)
288- <StructuralSubscription ...>
289-
290-A non-admin cannot subscribe a person other than themselves.
291-
292- >>> target.addBugSubscription(kamion, no_priv)
293- Traceback (most recent call last):
294- ...
295- UserCannotSubscribePerson: no-priv does not have permission to subscribe kamion.
296- >>> sorted([sub.subscriber.name for sub in target.bug_subscriptions])
297- [u'no-priv', u'ubuntu-team']
298-
299-But again, an admin can.
300-
301- >>> target.addBugSubscription(kamion, foobar)
302- <StructuralSubscription ...>
303- >>> sorted([sub.subscriber.name for sub in target.bug_subscriptions])
304- [u'kamion', u'no-priv', u'ubuntu-team']
305-
306-To remove a bug subscription, use
307-IStructuralSubscriptionTarget.removeBugSubscription:
308-
309- >>> target.removeBugSubscription(no_priv, no_priv)
310- >>> sorted([sub.subscriber.name for sub in target.bug_subscriptions])
311- [u'kamion', u'ubuntu-team']
312-
313-The subscription rules apply to unsubscription as well.
314-
315-An unprivileged user cannot unsubscribe a team.
316-
317- >>> target.removeBugSubscription(ubuntu_team, no_priv)
318- Traceback (most recent call last):
319- ...
320- UserCannotSubscribePerson: no-priv does not have permission to unsubscribe ubuntu-team.
321- >>> sorted([sub.subscriber.name for sub in target.bug_subscriptions])
322- [u'kamion', u'ubuntu-team']
323-
324-But a team admin can.
325-
326- >>> target.removeBugSubscription(ubuntu_team, kamion)
327- >>> sorted([sub.subscriber.name for sub in target.bug_subscriptions])
328- [u'kamion']
329-
330-An unprivileged user also cannot unsubscribe another user.
331-
332- >>> target.removeBugSubscription(kamion, no_priv)
333- Traceback (most recent call last):
334- ...
335- UserCannotSubscribePerson: no-priv does not have permission to unsubscribe kamion.
336- >>> sorted([sub.subscriber.name for sub in target.bug_subscriptions])
337- [u'kamion']
338-
339-But the user themselves can.
340-
341- >>> target.removeBugSubscription(kamion, kamion)
342- >>> sorted([sub.subscriber.name for sub in target.bug_subscriptions])
343- []
344-
345-Trying to remove a subscription that doesn't exist on a target raises a
346-DeleteSubscriptionError.
347-
348- >>> target.removeBugSubscription(foobar, foobar)
349- Traceback (most recent call last):
350- ...
351- DeleteSubscriptionError: ...
352-
353-Let's subscribe ubuntu-team again.
354-
355 >>> target.addBugSubscription(ubuntu_team, foobar)
356 <StructuralSubscription ...>
357
358@@ -191,7 +62,7 @@
359
360
361 === Structural subscriptions and indirect bug subscriptions ===
362-
363+
364 >>> bug = filebug(target, 'test bug one')
365 >>> indirect_subscribers = set(
366 ... subscriber.name for subscriber in bug.getIndirectSubscribers())
367
368=== modified file 'lib/lp/registry/tests/test_structuralsubscriptiontarget.py'
369--- lib/lp/registry/tests/test_structuralsubscriptiontarget.py 2009-12-05 18:37:28 +0000
370+++ lib/lp/registry/tests/test_structuralsubscriptiontarget.py 2010-08-17 14:18:48 +0000
371@@ -4,12 +4,14 @@
372 """Test harness for running tests against IStructuralsubscriptionTarget
373 implementations.
374 """
375+__metaclass__ = type
376
377 import unittest
378
379 from zope.component import getUtility
380+from zope.security.interfaces import Unauthorized
381+from zope.security.proxy import ProxyFactory, removeSecurityProxy
382
383-from canonical.launchpad.webapp.interfaces import ILaunchBag
384 from lp.bugs.interfaces.bug import CreateBugParams
385 from lp.registry.interfaces.distribution import IDistributionSet
386 from lp.registry.interfaces.product import IProductSet
387@@ -18,7 +20,234 @@
388 bugtarget_filebug)
389 from canonical.launchpad.testing.systemdocs import (
390 LayeredDocFileSuite, setUp, tearDown)
391+from canonical.launchpad.webapp.interfaces import ILaunchBag
392+from canonical.launchpad.webapp.testing import verifyObject
393 from canonical.testing import LaunchpadFunctionalLayer
394+from lp.testing import (
395+ ANONYMOUS, login, login_person, TestCaseWithFactory)
396+from lp.testing._login import login_celebrity
397+from lp.registry.interfaces.structuralsubscription import (
398+ DeleteSubscriptionError, IStructuralSubscriptionTarget,
399+ UserCannotSubscribePerson)
400+from lp.registry.model.structuralsubscription import StructuralSubscription
401+
402+
403+class StructuralSubscriptionTestBase:
404+
405+ def setUp(self):
406+ super(StructuralSubscriptionTestBase, self).setUp()
407+ self.ordinary_subscriber = self.factory.makePerson()
408+ self.bug_supervisor_subscriber = self.factory.makePerson()
409+ self.team_owner = self.factory.makePerson()
410+ self.team = self.factory.makeTeam(owner=self.team_owner)
411+
412+ def test_target_implements_structural_subscription_target(self):
413+ self.assertTrue(verifyObject(IStructuralSubscriptionTarget,
414+ self.target))
415+
416+ def test_anonymous_cannot_subscribe_anyone(self):
417+ # only authenticated users can create structural subscriptions
418+ login(ANONYMOUS)
419+ self.assertRaises(Unauthorized, getattr, self.target,
420+ 'addBugSubscription')
421+
422+ def test_person_structural_subscription_by_other_person(self):
423+ # a person can not subscribe someone else willy nilly
424+ login_person(self.ordinary_subscriber)
425+ self.assertRaises(UserCannotSubscribePerson,
426+ self.target.addBugSubscription,
427+ self.team_owner, self.ordinary_subscriber)
428+
429+ def test_team_structural_subscription_by_non_team_member(self):
430+ # a person not related to a team cannot subscribe it
431+ login_person(self.ordinary_subscriber)
432+ self.assertRaises(UserCannotSubscribePerson,
433+ self.target.addBugSubscription,
434+ self.team, self.ordinary_subscriber)
435+
436+ def test_admin_can_subscribe_anyone(self):
437+ # a launchpad admin can create a structural subscription for
438+ # anyone
439+ admin = login_celebrity('admin')
440+ self.assertIsInstance(
441+ self.target.addBugSubscription(self.ordinary_subscriber, admin),
442+ StructuralSubscription)
443+
444+ def test_secondary_structural_subscription(self):
445+ # creating a structural subscription a 2nd time returns the
446+ # first structural subscription
447+ login_person(self.bug_supervisor_subscriber)
448+ subscription1 = self.target.addBugSubscription(
449+ self.bug_supervisor_subscriber, self.bug_supervisor_subscriber)
450+ subscription2 = self.target.addBugSubscription(
451+ self.bug_supervisor_subscriber, self.bug_supervisor_subscriber)
452+ self.assertIs(subscription1.id, subscription2.id)
453+
454+ def test_remove_structural_subscription(self):
455+ # an unprivileged user cannot unsubscribe a team
456+ login_person(self.ordinary_subscriber)
457+ self.assertRaises(UserCannotSubscribePerson,
458+ self.target.removeBugSubscription,
459+ self.team, self.ordinary_subscriber)
460+
461+ def test_remove_nonexistant_structural_subscription(self):
462+ # removing a nonexistant subscription raises a
463+ # DeleteSubscriptionError
464+ login_person(self.ordinary_subscriber)
465+ self.assertRaises(DeleteSubscriptionError,
466+ self.target.removeBugSubscription,
467+ self.ordinary_subscriber, self.ordinary_subscriber)
468+
469+
470+class UnrestrictedStructuralSubscription(StructuralSubscriptionTestBase):
471+
472+ def test_structural_subscription_by_ordinary_user(self):
473+ # ordinary users can subscribe themselves
474+ login_person(self.ordinary_subscriber)
475+ self.assertIsInstance(
476+ self.target.addBugSubscription(
477+ self.ordinary_subscriber, self.ordinary_subscriber),
478+ StructuralSubscription)
479+
480+ def test_remove_structural_subscription_by_ordinary_user(self):
481+ # ordinary users can unsubscribe themselves
482+ login_person(self.ordinary_subscriber)
483+ self.assertIsInstance(
484+ self.target.addBugSubscription(
485+ self.ordinary_subscriber, self.ordinary_subscriber),
486+ StructuralSubscription)
487+ self.assertEqual(
488+ self.target.removeBugSubscription(
489+ self.ordinary_subscriber, self.ordinary_subscriber),
490+ None)
491+
492+ def test_team_structural_subscription_by_team_owner(self):
493+ # team owners can subscribe their team
494+ login_person(self.team_owner)
495+ self.assertIsInstance(
496+ self.target.addBugSubscription(
497+ self.team, self.team_owner),
498+ StructuralSubscription)
499+
500+ def test_remove_team_structural_subscription_by_team_owner(self):
501+ # team owners can unsubscribe their team
502+ login_person(self.team_owner)
503+ self.assertIsInstance(
504+ self.target.addBugSubscription(
505+ self.team, self.team_owner),
506+ StructuralSubscription)
507+ self.assertEqual(
508+ self.target.removeBugSubscription(
509+ self.team, self.team_owner),
510+ None)
511+
512+
513+class TestStructuralSubscriptionForDistro(StructuralSubscriptionTestBase,
514+ TestCaseWithFactory):
515+
516+ layer = LaunchpadFunctionalLayer
517+
518+ def setUp(self):
519+ super(TestStructuralSubscriptionForDistro, self).setUp()
520+ self.target = self.factory.makeDistribution()
521+ naked_distro = removeSecurityProxy(self.target)
522+ naked_distro.bug_supervisor = self.bug_supervisor_subscriber
523+
524+ def test_distribution_subscription_by_ordinary_user(self):
525+ # ordinary users can not subscribe themselves to a distribution
526+ login_person(self.ordinary_subscriber)
527+ self.assertRaises(UserCannotSubscribePerson,
528+ self.target.addBugSubscription,
529+ self.ordinary_subscriber, self.ordinary_subscriber)
530+
531+ def test_team_distribution_structural_subscription_by_team_owner(self):
532+ # team owners cannot subscribe their team to a distribution
533+ login_person(self.team_owner)
534+ self.assertRaises(UserCannotSubscribePerson,
535+ self.target.addBugSubscription,
536+ self.team, self.team_owner)
537+
538+ def test_distribution_subscription_by_bug_supervisor(self):
539+ # bug supervisor can subscribe themselves
540+ login_person(self.bug_supervisor_subscriber)
541+ self.assertIsInstance(
542+ self.target.addBugSubscription(
543+ self.bug_supervisor_subscriber,
544+ self.bug_supervisor_subscriber),
545+ StructuralSubscription)
546+
547+ def test_distribution_subscription_by_bug_supervisor_team(self):
548+ # team admins can subscribe team if team is bug supervisor
549+ removeSecurityProxy(self.target).bug_supervisor = self.team
550+ login_person(self.team_owner)
551+ self.assertIsInstance(
552+ self.target.addBugSubscription(self.team, self.team_owner),
553+ StructuralSubscription)
554+
555+ def test_distribution_unsubscription_by_bug_supervisor_team(self):
556+ # team admins can unsubscribe team if team is bug supervisor
557+ removeSecurityProxy(self.target).bug_supervisor = self.team
558+ login_person(self.team_owner)
559+ self.assertIsInstance(
560+ self.target.addBugSubscription(self.team, self.team_owner),
561+ StructuralSubscription)
562+ self.assertEqual(
563+ self.target.removeBugSubscription(self.team, self.team_owner),
564+ None)
565+
566+ def test_distribution_subscription_without_bug_supervisor(self):
567+ # for a distribution without a bug supervisor anyone can
568+ # subscribe
569+ removeSecurityProxy(self.target).bug_supervisor = None
570+ login_person(self.ordinary_subscriber)
571+ self.assertIsInstance(
572+ self.target.addBugSubscription(
573+ self.ordinary_subscriber, self.ordinary_subscriber),
574+ StructuralSubscription)
575+
576+
577+class TestStructuralSubscriptionForProduct(
578+ UnrestrictedStructuralSubscription, TestCaseWithFactory):
579+
580+ layer = LaunchpadFunctionalLayer
581+
582+ def setUp(self):
583+ super(TestStructuralSubscriptionForProduct, self).setUp()
584+ self.target = self.factory.makeProduct()
585+
586+
587+class TestStructuralSubscriptionForDistroSourcePackage(
588+ UnrestrictedStructuralSubscription, TestCaseWithFactory):
589+
590+ layer = LaunchpadFunctionalLayer
591+
592+ def setUp(self):
593+ super(TestStructuralSubscriptionForDistroSourcePackage, self).setUp()
594+ self.target = self.factory.makeDistributionSourcePackage()
595+ self.target = ProxyFactory(self.target)
596+
597+
598+class TestStructuralSubscriptionForMilestone(
599+ UnrestrictedStructuralSubscription, TestCaseWithFactory):
600+
601+ layer = LaunchpadFunctionalLayer
602+
603+ def setUp(self):
604+ super(TestStructuralSubscriptionForMilestone, self).setUp()
605+ self.target = self.factory.makeMilestone()
606+ self.target = ProxyFactory(self.target)
607+
608+
609+class TestStructuralSubscriptionForDistroSeries(
610+ UnrestrictedStructuralSubscription, TestCaseWithFactory):
611+
612+ layer = LaunchpadFunctionalLayer
613+
614+ def setUp(self):
615+ super(TestStructuralSubscriptionForDistroSeries, self).setUp()
616+ self.target = self.factory.makeDistroSeries()
617+ self.target = ProxyFactory(self.target)
618+
619
620 def distributionSourcePackageSetUp(test):
621 setUp(test)
622@@ -27,27 +256,32 @@
623 test.globs['other_target'] = ubuntu.getSourcePackage('pmount')
624 test.globs['filebug'] = bugtarget_filebug
625
626+
627 def productSetUp(test):
628 setUp(test)
629 test.globs['target'] = getUtility(IProductSet).getByName('firefox')
630 test.globs['filebug'] = bugtarget_filebug
631
632+
633 def distributionSetUp(test):
634 setUp(test)
635 test.globs['target'] = getUtility(IDistributionSet).getByName('ubuntu')
636 test.globs['filebug'] = bugtarget_filebug
637
638+
639 def milestone_filebug(milestone, summary, status=None):
640 bug = bugtarget_filebug(milestone.target, summary, status=status)
641 bug.bugtasks[0].milestone = milestone
642 return bug
643
644+
645 def milestoneSetUp(test):
646 setUp(test)
647 firefox = getUtility(IProductSet).getByName('firefox')
648 test.globs['target'] = firefox.getMilestone('1.0')
649 test.globs['filebug'] = milestone_filebug
650
651+
652 def distroseries_sourcepackage_filebug(distroseries, summary, status=None):
653 params = CreateBugParams(
654 getUtility(ILaunchBag).user, summary, comment=summary, status=status)
655@@ -60,15 +294,18 @@
656 nomination.approve(distroseries.distribution.owner)
657 return bug
658
659+
660 def distroSeriesSourcePackageSetUp(test):
661 setUp(test)
662 test.globs['target'] = (
663 getUtility(IDistributionSet).getByName('ubuntu').getSeries('hoary'))
664 test.globs['filebug'] = distroseries_sourcepackage_filebug
665
666+
667 def test_suite():
668 """Return the `IStructuralSubscriptionTarget` TestSuite."""
669 suite = unittest.TestSuite()
670+ suite.addTest(unittest.TestLoader().loadTestsFromName(__name__))
671
672 setUpMethods = [
673 distributionSourcePackageSetUp,