Merge lp:~stevenk/launchpad/branch-subscribe-aag into lp:launchpad

Proposed by Steve Kowalik
Status: Merged
Approved by: Steve Kowalik
Approved revision: no longer in the source branch.
Merged at revision: 15378
Proposed branch: lp:~stevenk/launchpad/branch-subscribe-aag
Merge into: lp:launchpad
Diff against target: 777 lines (+243/-71)
21 files modified
lib/lp/app/errors.py (+7/-1)
lib/lp/app/tests/test_errors.py (+33/-0)
lib/lp/bugs/browser/bugsubscription.py (+1/-1)
lib/lp/bugs/errors.py (+1/-7)
lib/lp/bugs/model/bug.py (+2/-2)
lib/lp/bugs/tests/test_errors.py (+1/-7)
lib/lp/code/browser/branchsubscription.py (+13/-9)
lib/lp/code/browser/tests/test_branch.py (+7/-4)
lib/lp/code/browser/tests/test_branchlisting.py (+1/-1)
lib/lp/code/browser/tests/test_branchmergeproposal.py (+6/-2)
lib/lp/code/browser/tests/test_branchsubscription.py (+30/-2)
lib/lp/code/model/branch.py (+20/-5)
lib/lp/code/model/branchmergeproposal.py (+16/-3)
lib/lp/code/model/tests/test_branch.py (+7/-2)
lib/lp/code/model/tests/test_branchcollection.py (+4/-1)
lib/lp/code/model/tests/test_branchmergeproposal.py (+40/-2)
lib/lp/code/model/tests/test_branchnamespace.py (+15/-17)
lib/lp/code/model/tests/test_branchsubscription.py (+30/-2)
lib/lp/code/model/tests/test_branchvisibility.py (+4/-1)
lib/lp/registry/tests/test_distro_webservice.py (+4/-1)
lib/lp/testing/factory.py (+1/-1)
To merge this branch: bzr merge lp:~stevenk/launchpad/branch-subscribe-aag
Reviewer Review Type Date Requested Status
Ian Booth (community) Approve
Review via email: mp+108881@code.launchpad.net

Commit message

IBranch.subscribe() now forbids open and delegated teams from being subscribed to private branches, and creates an AccessArtifactGrant if the subscriber requires it for access. Secondly, if the reviewer is an open team, they are not subscribed to private branches for merge proposals.

Description of the change

IBranch.subscribe() now forbids open and delegated teams from being subscribed to private branches. This necessitated moving the exception into lp.app from bugs. I have also carried over the test for the webservice, and have written a few tests for the branch case.

I have also added the ability to IBranch.subscribe() to add a AccessArtifactGrant if required when a person is subscribed to a branch and added a test for that behaviour too.

To post a comment you must log in.
Revision history for this message
Ian Booth (wallyworld) wrote :

<wallyworld_> StevenK: TestBranchSubscriptionAddOtherView could iterate over all types of open team and test with each one
<wallyworld_> StevenK: also getVisibleArtifacts - you have the return tuple mixed up
<wallyworld_> StevenK: also, there are no legacy branch triggers so that check isn't needed
<wallyworld_> StevenK: and so the reason the test_subscribe_gives_access test passes is that the branch collection assumes subscribed = visible
<wallyworld_> so that bit of code is sort of pointless atm

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

> <wallyworld_> StevenK: TestBranchSubscriptionAddOtherView could iterate over
> all types of open team and test with each one
> <wallyworld_> StevenK: also getVisibleArtifacts - you have the return tuple
> mixed up
> <wallyworld_> StevenK: also, there are no legacy branch triggers so that check
> isn't needed
> <wallyworld_> StevenK: and so the reason the test_subscribe_gives_access test
> passes is that the branch collection assumes subscribed = visible
> <wallyworld_> so that bit of code is sort of pointless atm

I should clarify - the test will always pass right now, but will be needed for when we uncouple subscription and visibility for branches. So no harm in keeping it.

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

320 + # Grant the subscriber access if they can't see the branch (if the
321 + # database triggers aren't going to do it for us).

Fix the above comment and you're good to go.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/app/errors.py'
2--- lib/lp/app/errors.py 2011-06-16 20:12:00 +0000
3+++ lib/lp/app/errors.py 2012-06-08 06:05:25 +0000
4@@ -1,4 +1,4 @@
5-# Copyright 2010 Canonical Ltd. This software is licensed under the
6+# Copyright 2010-2012 Canonical Ltd. This software is licensed under the
7 # GNU Affero General Public License version 3 (see the file LICENSE).
8
9 """Cross application type errors for launchpad."""
10@@ -9,6 +9,7 @@
11 'NameLookupFailed',
12 'NotFoundError',
13 'POSTToNonCanonicalURL',
14+ 'SubscriptionPrivacyViolation',
15 'TranslationUnavailable',
16 'UnexpectedFormData',
17 'UserCannotUnsubscribePerson',
18@@ -75,5 +76,10 @@
19 """User does not have permission to unsubscribe person or team."""
20
21
22+@error_status(httplib.BAD_REQUEST)
23+class SubscriptionPrivacyViolation(Exception):
24+ """The subscription would violate privacy policies."""
25+
26+
27 # Slam a 401 response code onto all ForbiddenAttribute errors.
28 error_status(httplib.UNAUTHORIZED)(ForbiddenAttribute)
29
30=== added file 'lib/lp/app/tests/test_errors.py'
31--- lib/lp/app/tests/test_errors.py 1970-01-01 00:00:00 +0000
32+++ lib/lp/app/tests/test_errors.py 2012-06-08 06:05:25 +0000
33@@ -0,0 +1,33 @@
34+# Copyright 2012 Canonical Ltd. This software is licensed under the
35+# GNU Affero General Public License version 3 (see the file LICENSE).
36+
37+__metaclass__ = type
38+
39+from httplib import (
40+ BAD_REQUEST,
41+ UNAUTHORIZED,
42+ )
43+
44+from lp.app.errors import (
45+ SubscriptionPrivacyViolation,
46+ UserCannotUnsubscribePerson,
47+ )
48+from lp.testing import TestCase
49+from lp.testing.layers import FunctionalLayer
50+from lp.testing.views import create_webservice_error_view
51+
52+
53+class TestWebServiceErrors(TestCase):
54+ """ Test that errors are correctly mapped to HTTP status codes."""
55+
56+ layer = FunctionalLayer
57+
58+ def test_UserCannotUnsubscribePerson_unauthorized(self):
59+ error_view = create_webservice_error_view(
60+ UserCannotUnsubscribePerson())
61+ self.assertEqual(UNAUTHORIZED, error_view.status)
62+
63+ def test_SubscriptionPrivacyViolation_bad_request(self):
64+ error_view = create_webservice_error_view(
65+ SubscriptionPrivacyViolation())
66+ self.assertEqual(BAD_REQUEST, error_view.status)
67
68=== modified file 'lib/lp/bugs/browser/bugsubscription.py'
69--- lib/lp/bugs/browser/bugsubscription.py 2012-03-13 00:45:33 +0000
70+++ lib/lp/bugs/browser/bugsubscription.py 2012-06-08 06:05:25 +0000
71@@ -37,11 +37,11 @@
72 LaunchpadFormView,
73 ReturnToReferrerMixin,
74 )
75+from lp.app.errors import SubscriptionPrivacyViolation
76 from lp.bugs.browser.structuralsubscription import (
77 expose_structural_subscription_data_to_js,
78 )
79 from lp.bugs.enums import BugNotificationLevel
80-from lp.bugs.errors import SubscriptionPrivacyViolation
81 from lp.bugs.interfaces.bug import IBug
82 from lp.bugs.interfaces.bugsubscription import IBugSubscription
83 from lp.bugs.model.personsubscriptioninfo import PersonSubscriptions
84
85=== modified file 'lib/lp/bugs/errors.py'
86--- lib/lp/bugs/errors.py 2011-11-25 04:25:00 +0000
87+++ lib/lp/bugs/errors.py 2012-06-08 06:05:25 +0000
88@@ -1,4 +1,4 @@
89-# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
90+# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
91 # GNU Affero General Public License version 3 (see the file LICENSE).
92
93 """Errors used in the lp/bugs modules."""
94@@ -8,7 +8,6 @@
95 'BugCannotBePrivate',
96 'InvalidBugTargetType',
97 'InvalidDuplicateValue',
98- 'SubscriptionPrivacyViolation',
99 ]
100
101 import httplib
102@@ -29,10 +28,5 @@
103
104
105 @error_status(httplib.BAD_REQUEST)
106-class SubscriptionPrivacyViolation(Exception):
107- """The subscription would violate privacy policies."""
108-
109-
110-@error_status(httplib.BAD_REQUEST)
111 class BugCannotBePrivate(Exception):
112 """The bug is not allowed to be private."""
113
114=== modified file 'lib/lp/bugs/model/bug.py'
115--- lib/lp/bugs/model/bug.py 2012-06-07 06:04:46 +0000
116+++ lib/lp/bugs/model/bug.py 2012-06-08 06:05:25 +0000
117@@ -85,6 +85,7 @@
118 from lp.app.enums import ServiceUsage
119 from lp.app.errors import (
120 NotFoundError,
121+ SubscriptionPrivacyViolation,
122 UserCannotUnsubscribePerson,
123 )
124 from lp.app.interfaces.launchpad import ILaunchpadCelebrities
125@@ -105,7 +106,6 @@
126 from lp.bugs.errors import (
127 BugCannotBePrivate,
128 InvalidDuplicateValue,
129- SubscriptionPrivacyViolation,
130 )
131 from lp.bugs.interfaces.bug import (
132 IBug,
133@@ -177,8 +177,8 @@
134 from lp.registry.interfaces.product import IProduct
135 from lp.registry.interfaces.productseries import IProductSeries
136 from lp.registry.interfaces.role import IPersonRoles
137+from lp.registry.interfaces.series import SeriesStatus
138 from lp.registry.interfaces.sharingjob import IRemoveBugSubscriptionsJobSource
139-from lp.registry.interfaces.series import SeriesStatus
140 from lp.registry.interfaces.sourcepackage import ISourcePackage
141 from lp.registry.model.accesspolicy import reconcile_access_for_artifact
142 from lp.registry.model.person import (
143
144=== modified file 'lib/lp/bugs/tests/test_errors.py'
145--- lib/lp/bugs/tests/test_errors.py 2012-01-01 02:58:52 +0000
146+++ lib/lp/bugs/tests/test_errors.py 2012-06-08 06:05:25 +0000
147@@ -1,4 +1,4 @@
148-# Copyright 2011 Canonical Ltd. This software is licensed under the
149+# Copyright 2011-2012 Canonical Ltd. This software is licensed under the
150 # GNU Affero General Public License version 3 (see the file LICENSE).
151
152 """Tests for bugs errors."""
153@@ -15,7 +15,6 @@
154 from lp.bugs.errors import (
155 InvalidBugTargetType,
156 InvalidDuplicateValue,
157- SubscriptionPrivacyViolation,
158 )
159 from lp.testing import TestCase
160 from lp.testing.layers import FunctionalLayer
161@@ -35,8 +34,3 @@
162 error_view = create_webservice_error_view(
163 InvalidDuplicateValue("Dup"))
164 self.assertEqual(EXPECTATION_FAILED, error_view.status)
165-
166- def test_SubscriptionPrivacyViolation_bad_request(self):
167- error_view = create_webservice_error_view(
168- SubscriptionPrivacyViolation())
169- self.assertEqual(BAD_REQUEST, error_view.status)
170
171=== modified file 'lib/lp/code/browser/branchsubscription.py'
172--- lib/lp/code/browser/branchsubscription.py 2012-01-01 02:58:52 +0000
173+++ lib/lp/code/browser/branchsubscription.py 2012-06-08 06:05:25 +0000
174@@ -1,4 +1,4 @@
175-# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
176+# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
177 # GNU Affero General Public License version 3 (see the file LICENSE).
178
179 __metaclass__ = type
180@@ -20,6 +20,7 @@
181 LaunchpadEditFormView,
182 LaunchpadFormView,
183 )
184+from lp.app.errors import SubscriptionPrivacyViolation
185 from lp.code.enums import BranchSubscriptionNotificationLevel
186 from lp.code.interfaces.branchsubscription import IBranchSubscription
187 from lp.services.webapp import (
188@@ -226,14 +227,17 @@
189 person = data['person']
190 subscription = self.context.getSubscription(person)
191 if subscription is None:
192- self.context.subscribe(
193- person, notification_level, max_diff_lines, review_level,
194- self.user)
195-
196- self.add_notification_message(
197- '%s has been subscribed to this branch with: '
198- % person.displayname, notification_level, max_diff_lines,
199- review_level)
200+ try:
201+ self.context.subscribe(
202+ person, notification_level, max_diff_lines, review_level,
203+ self.user)
204+ except SubscriptionPrivacyViolation as error:
205+ self.setFieldError('person', unicode(error))
206+ else:
207+ self.add_notification_message(
208+ '%s has been subscribed to this branch with: '
209+ % person.displayname, notification_level, max_diff_lines,
210+ review_level)
211 else:
212 self.add_notification_message(
213 '%s was already subscribed to this branch with: '
214
215=== modified file 'lib/lp/code/browser/tests/test_branch.py'
216--- lib/lp/code/browser/tests/test_branch.py 2012-06-02 13:55:16 +0000
217+++ lib/lp/code/browser/tests/test_branch.py 2012-06-08 06:05:25 +0000
218@@ -547,7 +547,8 @@
219 # A branch with a private owner is rendered.
220 private_owner = self.factory.makeTeam(
221 displayname="PrivateTeam", visibility=PersonVisibility.PRIVATE)
222- branch = self.factory.makeAnyBranch(owner=private_owner)
223+ with person_logged_in(private_owner):
224+ branch = self.factory.makeAnyBranch(owner=private_owner)
225 # Ensure the branch owner is rendered.
226 url = canonical_url(branch, rootsite='code')
227 user = self.factory.makePerson()
228@@ -560,7 +561,8 @@
229 # A private branch with a private owner is rendered.
230 private_owner = self.factory.makeTeam(
231 displayname="PrivateTeam", visibility=PersonVisibility.PRIVATE)
232- branch = self.factory.makeAnyBranch(owner=private_owner)
233+ with person_logged_in(private_owner):
234+ branch = self.factory.makeAnyBranch(owner=private_owner)
235 # Ensure the branch owner is rendered.
236 url = canonical_url(branch, rootsite='code')
237 user = self.factory.makePerson()
238@@ -576,7 +578,8 @@
239 # A branch with a private owner is not rendered for anon users.
240 private_owner = self.factory.makeTeam(
241 visibility=PersonVisibility.PRIVATE)
242- branch = self.factory.makeAnyBranch(owner=private_owner)
243+ with person_logged_in(private_owner):
244+ branch = self.factory.makeAnyBranch(owner=private_owner)
245 # Viewing the branch results in an error.
246 url = canonical_url(branch, rootsite='code')
247 browser = self._getBrowser()
248@@ -604,7 +607,7 @@
249 private_subscriber = self.factory.makeTeam(
250 name="privateteam", visibility=PersonVisibility.PRIVATE)
251 branch = self.factory.makeAnyBranch()
252- with person_logged_in(branch.owner):
253+ with person_logged_in(private_subscriber):
254 self.factory.makeBranchSubscription(
255 branch, private_subscriber, branch.owner)
256 # Viewing the branch doesn't show the private subscriber.
257
258=== modified file 'lib/lp/code/browser/tests/test_branchlisting.py'
259--- lib/lp/code/browser/tests/test_branchlisting.py 2012-02-21 12:18:30 +0000
260+++ lib/lp/code/browser/tests/test_branchlisting.py 2012-06-08 06:05:25 +0000
261@@ -657,7 +657,7 @@
262 member = self.factory.makePerson(email='member@example.com')
263 with person_logged_in(owner):
264 private_team.addMember(member, owner)
265- branch = self.factory.makeProductBranch(owner=private_team)
266+ branch = self.factory.makeProductBranch(owner=private_team)
267 return private_team, member, branch
268
269 def test_private_team_membership_for_team_member(self):
270
271=== modified file 'lib/lp/code/browser/tests/test_branchmergeproposal.py'
272--- lib/lp/code/browser/tests/test_branchmergeproposal.py 2012-05-31 03:54:13 +0000
273+++ lib/lp/code/browser/tests/test_branchmergeproposal.py 2012-06-08 06:05:25 +0000
274@@ -54,7 +54,10 @@
275 make_merge_proposal_without_reviewers,
276 )
277 from lp.registry.enums import InformationType
278-from lp.registry.interfaces.person import PersonVisibility
279+from lp.registry.interfaces.person import (
280+ PersonVisibility,
281+ TeamSubscriptionPolicy,
282+ )
283 from lp.services.messages.model.message import MessageSet
284 from lp.services.webapp import canonical_url
285 from lp.services.webapp.interfaces import (
286@@ -217,7 +220,8 @@
287 # Set up some review requests.
288 public_person1 = self.factory.makePerson()
289 private_team1 = self.factory.makeTeam(
290- visibility=PersonVisibility.PRIVATE)
291+ visibility=PersonVisibility.PRIVATE,
292+ subscription_policy=TeamSubscriptionPolicy.MODERATED)
293 self._nominateReviewer(public_person1, owner)
294 self._nominateReviewer(private_team1, owner)
295
296
297=== modified file 'lib/lp/code/browser/tests/test_branchsubscription.py'
298--- lib/lp/code/browser/tests/test_branchsubscription.py 2012-01-01 02:58:52 +0000
299+++ lib/lp/code/browser/tests/test_branchsubscription.py 2012-06-08 06:05:25 +0000
300@@ -1,13 +1,18 @@
301-# Copyright 2009 Canonical Ltd. This software is licensed under the
302+# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
303 # GNU Affero General Public License version 3 (see the file LICENSE).
304
305 """Unit tests for BranchSubscriptions."""
306
307 __metaclass__ = type
308
309+from lp.registry.enums import InformationType
310 from lp.services.webapp.interfaces import IPrimaryContext
311-from lp.testing import TestCaseWithFactory
312+from lp.testing import (
313+ person_logged_in,
314+ TestCaseWithFactory,
315+ )
316 from lp.testing.layers import DatabaseFunctionalLayer
317+from lp.testing.views import create_initialized_view
318
319
320 class TestBranchSubscriptionPrimaryContext(TestCaseWithFactory):
321@@ -22,3 +27,26 @@
322 self.assertEqual(
323 IPrimaryContext(subscription).context,
324 IPrimaryContext(subscription.branch).context)
325+
326+
327+class TestBranchSubscriptionAddOtherView(TestCaseWithFactory):
328+
329+ layer = DatabaseFunctionalLayer
330+
331+ def test_cannot_subscribe_open_team_to_private_branch(self):
332+ owner = self.factory.makePerson()
333+ branch = self.factory.makeBranch(
334+ information_type=InformationType.USERDATA, owner=owner)
335+ team = self.factory.makeTeam()
336+ form = {
337+ 'field.person': team.name,
338+ 'field.notification_level': 'NOEMAIL',
339+ 'field.max_diff_lines': 'NODIFF',
340+ 'field.review_level': 'NOEMAIL',
341+ 'field.actions.subscribe_action': 'Subscribe'}
342+ with person_logged_in(owner):
343+ view = create_initialized_view(
344+ branch, '+addsubscriber', pricipal=owner, form=form)
345+ self.assertContentEqual(
346+ ['Open and delegated teams cannot be subscribed to private '
347+ 'branches.'], view.errors)
348
349=== modified file 'lib/lp/code/model/branch.py'
350--- lib/lp/code/model/branch.py 2012-06-05 02:03:44 +0000
351+++ lib/lp/code/model/branch.py 2012-06-08 06:05:25 +0000
352@@ -2,8 +2,6 @@
353 # GNU Affero General Public License version 3 (see the file LICENSE).
354
355 # pylint: disable-msg=E0611,W0212,W0141,F0401
356-from lp.bugs.interfaces.bugtarget import IBugTarget
357-from lp.registry.interfaces.accesspolicy import IAccessPolicyArtifactSource, IAccessPolicySource, IAccessArtifactSource
358
359 __metaclass__ = type
360 __all__ = [
361@@ -52,11 +50,15 @@
362 )
363
364 from lp import _
365-from lp.app.errors import UserCannotUnsubscribePerson
366+from lp.app.errors import (
367+ SubscriptionPrivacyViolation,
368+ UserCannotUnsubscribePerson,
369+ )
370 from lp.app.interfaces.launchpad import (
371 ILaunchpadCelebrities,
372 IPrivacy,
373 )
374+from lp.app.interfaces.services import IService
375 from lp.bugs.interfaces.bugtask import (
376 BugTaskSearchParams,
377 IBugTaskSet,
378@@ -155,6 +157,7 @@
379 SQLBase,
380 sqlvalues,
381 )
382+from lp.services.features import getFeatureFlag
383 from lp.services.helpers import shortlist
384 from lp.services.job.interfaces.job import JobStatus
385 from lp.services.job.model.job import Job
386@@ -817,6 +820,11 @@
387 def subscribe(self, person, notification_level, max_diff_lines,
388 code_review_level, subscribed_by):
389 """See `IBranch`."""
390+ if (person.is_team and self.information_type in
391+ PRIVATE_INFORMATION_TYPES and person.anyone_can_join()):
392+ raise SubscriptionPrivacyViolation(
393+ "Open and delegated teams cannot be subscribed to private "
394+ "branches.")
395 # If the person is already subscribed, update the subscription with
396 # the specified notification details.
397 subscription = self.getSubscription(person)
398@@ -831,6 +839,14 @@
399 subscription.notification_level = notification_level
400 subscription.max_diff_lines = max_diff_lines
401 subscription.review_level = code_review_level
402+ # Grant the subscriber access if they can't see the branch.
403+ service = getUtility(IService, 'sharing')
404+ ignored, branches = service.getVisibleArtifacts(
405+ person, branches=[self])
406+ if not branches:
407+ service.ensureAccessGrants(
408+ subscribed_by, person, branches=[self],
409+ ignore_permissions=True)
410 return subscription
411
412 def getSubscription(self, person):
413@@ -858,13 +874,12 @@
414 """See `IBranch`."""
415 return self.getSubscription(person) is not None
416
417- def unsubscribe(self, person, unsubscribed_by, **kwargs):
418+ def unsubscribe(self, person, unsubscribed_by, ignore_permissions=False):
419 """See `IBranch`."""
420 subscription = self.getSubscription(person)
421 if subscription is None:
422 # Silent success seems order of the day (like bugs).
423 return
424- ignore_permissions = kwargs.get('ignore_permissions', False)
425 if (not ignore_permissions
426 and not subscription.canBeUnsubscribedByUser(unsubscribed_by)):
427 raise UserCannotUnsubscribePerson(
428
429=== modified file 'lib/lp/code/model/branchmergeproposal.py'
430--- lib/lp/code/model/branchmergeproposal.py 2012-04-12 15:16:12 +0000
431+++ lib/lp/code/model/branchmergeproposal.py 2012-06-08 06:05:25 +0000
432@@ -76,6 +76,7 @@
433 IncrementalDiff,
434 PreviewDiff,
435 )
436+from lp.registry.enums import PRIVATE_INFORMATION_TYPES
437 from lp.registry.interfaces.person import (
438 IPerson,
439 IPersonSet,
440@@ -634,17 +635,29 @@
441 self._subscribeUserToStackedBranch(
442 branch.stacked_on, user, checked_branches)
443
444+ def _acceptable_to_give_visibility(self, branch, reviewer):
445+ # If the branch is private, only closed teams can be subscribed to
446+ # prevent leaks.
447+ if (branch.information_type in PRIVATE_INFORMATION_TYPES and
448+ reviewer.is_team and reviewer.anyone_can_join()):
449+ return False
450+ return True
451+
452 def _ensureAssociatedBranchesVisibleToReviewer(self, reviewer):
453 """ A reviewer must be able to see the source and target branches.
454
455 Currently, we ensure the required visibility by subscribing the user
456- to the branch and those on which it is stacked.
457+ to the branch and those on which it is stacked. We do not subscribe
458+ the reviewer if the branch is private and the reviewer is an open
459+ team.
460 """
461 source = self.source_branch
462- if not source.visibleByUser(reviewer):
463+ if (not source.visibleByUser(reviewer) and
464+ self._acceptable_to_give_visibility(source, reviewer)):
465 self._subscribeUserToStackedBranch(source, reviewer)
466 target = self.target_branch
467- if not target.visibleByUser(reviewer):
468+ if (not target.visibleByUser(reviewer) and
469+ self._acceptable_to_give_visibility(source, reviewer)):
470 self._subscribeUserToStackedBranch(target, reviewer)
471
472 def nominateReviewer(self, reviewer, registrant, review_type=None,
473
474=== modified file 'lib/lp/code/model/tests/test_branch.py'
475--- lib/lp/code/model/tests/test_branch.py 2012-06-04 09:41:48 +0000
476+++ lib/lp/code/model/tests/test_branch.py 2012-06-08 06:05:25 +0000
477@@ -116,7 +116,10 @@
478 IAccessPolicyArtifactSource,
479 IAccessPolicySource,
480 )
481-from lp.registry.interfaces.person import PersonVisibility
482+from lp.registry.interfaces.person import (
483+ PersonVisibility,
484+ TeamSubscriptionPolicy,
485+ )
486 from lp.registry.interfaces.pocket import PackagePublishingPocket
487 from lp.registry.model.sourcepackage import SourcePackage
488 from lp.registry.tests.test_accesspolicy import get_policies_for_artifact
489@@ -2365,7 +2368,9 @@
490 self.assertTrue(branch.explicitly_private)
491
492 def test_personal_branches_for_private_teams_are_private(self):
493- team = self.factory.makeTeam(visibility=PersonVisibility.PRIVATE)
494+ team = self.factory.makeTeam(
495+ subscription_policy=TeamSubscriptionPolicy.MODERATED,
496+ visibility=PersonVisibility.PRIVATE)
497 branch = self.factory.makePersonalBranch(owner=team)
498 self.assertTrue(branch.private)
499 self.assertEqual(InformationType.USERDATA, branch.information_type)
500
501=== modified file 'lib/lp/code/model/tests/test_branchcollection.py'
502--- lib/lp/code/model/tests/test_branchcollection.py 2012-05-31 03:54:13 +0000
503+++ lib/lp/code/model/tests/test_branchcollection.py 2012-06-08 06:05:25 +0000
504@@ -31,6 +31,7 @@
505 from lp.code.model.branchcollection import GenericBranchCollection
506 from lp.code.tests.helpers import remove_all_sample_data_branches
507 from lp.registry.enums import InformationType
508+from lp.registry.interfaces.person import TeamSubscriptionPolicy
509 from lp.registry.interfaces.pocket import PackagePublishingPocket
510 from lp.services.webapp.interfaces import (
511 DEFAULT_FLAVOR,
512@@ -684,7 +685,9 @@
513 # A person in a team that is subscribed to a branch can see that
514 # branch, even if it's private.
515 team_owner = self.factory.makePerson()
516- team = self.factory.makeTeam(team_owner)
517+ team = self.factory.makeTeam(
518+ subscription_policy=TeamSubscriptionPolicy.MODERATED,
519+ owner=team_owner)
520 private_branch = self.factory.makeAnyBranch(
521 information_type=InformationType.USERDATA)
522 # Subscribe the team.
523
524=== modified file 'lib/lp/code/model/tests/test_branchmergeproposal.py'
525--- lib/lp/code/model/tests/test_branchmergeproposal.py 2012-05-31 03:54:13 +0000
526+++ lib/lp/code/model/tests/test_branchmergeproposal.py 2012-06-08 06:05:25 +0000
527@@ -63,7 +63,10 @@
528 make_merge_proposal_without_reviewers,
529 )
530 from lp.registry.enums import InformationType
531-from lp.registry.interfaces.person import IPersonSet
532+from lp.registry.interfaces.person import (
533+ IPersonSet,
534+ TeamSubscriptionPolicy,
535+ )
536 from lp.registry.interfaces.product import IProductSet
537 from lp.services.database.constants import UTC_NOW
538 from lp.services.webapp import canonical_url
539@@ -151,6 +154,40 @@
540 self.setPrivate(bmp.prerequisite_branch)
541 self.assertTrue(bmp.private)
542
543+ def test_open_reviewer_with_private_branch(self):
544+ """If the reviewer is an open team, and either of the branches are
545+ private, they are not subscribed."""
546+ owner = self.factory.makePerson()
547+ product = self.factory.makeProduct()
548+ trunk = self.factory.makeBranch(product=product, owner=owner)
549+ team = self.factory.makeTeam()
550+ branch = self.factory.makeBranch(
551+ information_type=InformationType.USERDATA, owner=owner,
552+ product=product)
553+ with person_logged_in(owner):
554+ trunk.reviewer = team
555+ bmp = self.factory.makeBranchMergeProposal(
556+ source_branch=branch, target_branch=trunk)
557+ subscriptions = [bsub.person for bsub in branch.subscriptions]
558+ self.assertEqual([owner], subscriptions)
559+
560+ def test_closed_reviewer_with_private_branch(self):
561+ """If the reviewer is a closed team, they are subscribed."""
562+ owner = self.factory.makePerson()
563+ product = self.factory.makeProduct()
564+ trunk = self.factory.makeBranch(product=product, owner=owner)
565+ team = self.factory.makeTeam(
566+ subscription_policy=TeamSubscriptionPolicy.MODERATED)
567+ branch = self.factory.makeBranch(
568+ information_type=InformationType.USERDATA, owner=owner,
569+ product=product)
570+ with person_logged_in(owner):
571+ trunk.reviewer = team
572+ bmp = self.factory.makeBranchMergeProposal(
573+ source_branch=branch, target_branch=trunk)
574+ subscriptions = [bsub.person for bsub in branch.subscriptions]
575+ self.assertContentEqual([owner, team], subscriptions)
576+
577
578 class TestBranchMergeProposalTransitions(TestCaseWithFactory):
579 """Test the state transitions of branch merge proposals."""
580@@ -1530,7 +1567,8 @@
581 self._test_nominate_grants_visibility(reviewer)
582
583 def test_nominate_team_grants_visibility(self):
584- reviewer = self.factory.makeTeam()
585+ reviewer = self.factory.makeTeam(
586+ subscription_policy=TeamSubscriptionPolicy.MODERATED)
587 self._test_nominate_grants_visibility(reviewer)
588
589 def test_comment_with_vote_creates_reference(self):
590
591=== modified file 'lib/lp/code/model/tests/test_branchnamespace.py'
592--- lib/lp/code/model/tests/test_branchnamespace.py 2012-05-04 04:52:24 +0000
593+++ lib/lp/code/model/tests/test_branchnamespace.py 2012-06-08 06:05:25 +0000
594@@ -44,6 +44,7 @@
595 from lp.registry.interfaces.person import (
596 NoSuchPerson,
597 PersonVisibility,
598+ TeamSubscriptionPolicy,
599 )
600 from lp.registry.interfaces.product import NoSuchProduct
601 from lp.registry.model.sourcepackage import SourcePackage
602@@ -379,7 +380,9 @@
603 # Given a privacy policy for a namespace, branches are created with
604 # the correct information type.
605 person = self.factory.makePerson()
606- team = self.factory.makeTeam(owner=person)
607+ team = self.factory.makeTeam(
608+ subscription_policy=TeamSubscriptionPolicy.MODERATED,
609+ owner=person)
610 product = self.factory.makeProduct()
611 namespace = ProductNamespace(team, product)
612 product.setBranchVisibilityTeamPolicy(
613@@ -1337,38 +1340,33 @@
614 * "charlie", who is a member of zulu.
615 * "doug", who is a member of no teams.
616 """
617- TestCaseWithFactory.setUp(self, 'admin@canonical.com')
618+ super(BranchVisibilityPolicyTestCase, self).setUp(
619+ 'admin@canonical.com')
620 # Our test product.
621 self.product = self.factory.makeProduct()
622 # Create some test people.
623 self.albert = self.factory.makePerson(
624- email='albert@code.ninja.nz',
625 name='albert', displayname='Albert Tester')
626 self.bob = self.factory.makePerson(
627- email='bob@code.ninja.nz',
628 name='bob', displayname='Bob Tester')
629 self.charlie = self.factory.makePerson(
630- email='charlie@code.ninja.nz',
631 name='charlie', displayname='Charlie Tester')
632 self.doug = self.factory.makePerson(
633- email='doug@code.ninja.nz',
634 name='doug', displayname='Doug Tester')
635-
636 self.people = (self.albert, self.bob, self.charlie, self.doug)
637
638 # And create some test teams.
639- self.xray = self.factory.makeTeam(name='xray')
640- self.yankee = self.factory.makeTeam(name='yankee')
641- self.zulu = self.factory.makeTeam(name='zulu')
642+ self.xray = self.factory.makeTeam(
643+ name='xray', members=[self.albert],
644+ subscription_policy=TeamSubscriptionPolicy.MODERATED)
645+ self.yankee = self.factory.makeTeam(
646+ name='yankee', members=[self.albert, self.bob],
647+ subscription_policy=TeamSubscriptionPolicy.MODERATED)
648+ self.zulu = self.factory.makeTeam(
649+ name='zulu', members=[self.albert, self.charlie],
650+ subscription_policy=TeamSubscriptionPolicy.MODERATED)
651 self.teams = (self.xray, self.yankee, self.zulu)
652
653- # Set the memberships of our test people to the test teams.
654- self.albert.join(self.xray)
655- self.albert.join(self.yankee)
656- self.albert.join(self.zulu)
657- self.bob.join(self.yankee)
658- self.charlie.join(self.zulu)
659-
660 def defineTeamPolicies(self, team_policies):
661 """Shortcut to help define team policies."""
662 for team, rule in team_policies:
663
664=== modified file 'lib/lp/code/model/tests/test_branchsubscription.py'
665--- lib/lp/code/model/tests/test_branchsubscription.py 2012-01-01 02:58:52 +0000
666+++ lib/lp/code/model/tests/test_branchsubscription.py 2012-06-08 06:05:25 +0000
667@@ -1,15 +1,19 @@
668-# Copyright 2010 Canonical Ltd. This software is licensed under the
669+# Copyright 2010-2012 Canonical Ltd. This software is licensed under the
670 # GNU Affero General Public License version 3 (see the file LICENSE).
671
672 """Tests for the BranchSubscrptions model object.."""
673
674 __metaclass__ = type
675
676-from lp.app.errors import UserCannotUnsubscribePerson
677+from lp.app.errors import (
678+ SubscriptionPrivacyViolation,
679+ UserCannotUnsubscribePerson,
680+ )
681 from lp.code.enums import (
682 BranchSubscriptionNotificationLevel,
683 CodeReviewNotificationLevel,
684 )
685+from lp.registry.enums import InformationType
686 from lp.testing import (
687 person_logged_in,
688 TestCaseWithFactory,
689@@ -67,6 +71,30 @@
690 subscription.person,
691 self.factory.makePerson())
692
693+ def test_cannot_subscribe_open_team_to_private_branch(self):
694+ """It is forbidden to subscribe a open team to a private branch."""
695+ owner = self.factory.makePerson()
696+ branch = self.factory.makeBranch(
697+ information_type=InformationType.USERDATA, owner=owner)
698+ team = self.factory.makeTeam()
699+ with person_logged_in(owner):
700+ self.assertRaises(
701+ SubscriptionPrivacyViolation, branch.subscribe, team, None,
702+ None, None, owner)
703+
704+ def test_subscribe_gives_access(self):
705+ """Subscribing a user to a branch gives them access."""
706+ owner = self.factory.makePerson()
707+ branch = self.factory.makeBranch(
708+ information_type=InformationType.USERDATA, owner=owner)
709+ subscribee = self.factory.makePerson()
710+ with person_logged_in(owner):
711+ self.assertFalse(branch.visibleByUser(subscribee))
712+ branch.subscribe(
713+ subscribee, BranchSubscriptionNotificationLevel.NOEMAIL,
714+ None, CodeReviewNotificationLevel.NOEMAIL, owner)
715+ self.assertTrue(branch.visibleByUser(subscribee))
716+
717
718 class TestBranchSubscriptionCanBeUnsubscribedbyUser(TestCaseWithFactory):
719 """Tests for BranchSubscription.canBeUnsubscribedByUser."""
720
721=== modified file 'lib/lp/code/model/tests/test_branchvisibility.py'
722--- lib/lp/code/model/tests/test_branchvisibility.py 2012-05-31 03:54:13 +0000
723+++ lib/lp/code/model/tests/test_branchvisibility.py 2012-06-08 06:05:25 +0000
724@@ -27,6 +27,7 @@
725 )
726 from lp.code.interfaces.branch import IBranchSet
727 from lp.registry.enums import InformationType
728+from lp.registry.interfaces.person import TeamSubscriptionPolicy
729 from lp.registry.interfaces.role import IPersonRoles
730 from lp.security import AccessBranch
731 from lp.services.webapp.authorization import (
732@@ -106,7 +107,9 @@
733 naked_branch = removeSecurityProxy(branch)
734 person = self.factory.makePerson()
735 teamowner = self.factory.makePerson()
736- team = self.factory.makeTeam(owner=teamowner, members=[person])
737+ team = self.factory.makeTeam(
738+ subscription_policy=TeamSubscriptionPolicy.MODERATED,
739+ owner=teamowner, members=[person])
740
741 # Not visible to an unsubscribed person.
742 access = AccessBranch(naked_branch)
743
744=== modified file 'lib/lp/registry/tests/test_distro_webservice.py'
745--- lib/lp/registry/tests/test_distro_webservice.py 2012-05-31 03:54:13 +0000
746+++ lib/lp/registry/tests/test_distro_webservice.py 2012-06-08 06:05:25 +0000
747@@ -21,6 +21,7 @@
748 SeriesSourcePackageBranchSet,
749 )
750 from lp.registry.enums import InformationType
751+from lp.registry.interfaces.person import TeamSubscriptionPolicy
752 from lp.registry.interfaces.pocket import PackagePublishingPocket
753 from lp.testing import (
754 api_url,
755@@ -198,7 +199,9 @@
756 # it is visible.
757 branch, distro = self.makeBranch()
758 person = self.factory.makePerson()
759- team = self.factory.makeTeam(members=[person])
760+ team = self.factory.makeTeam(
761+ subscription_policy=TeamSubscriptionPolicy.MODERATED,
762+ members=[person])
763 removeSecurityProxy(branch).subscribe(
764 team, BranchSubscriptionNotificationLevel.NOEMAIL,
765 BranchSubscriptionDiffSize.NODIFF,
766
767=== modified file 'lib/lp/testing/factory.py'
768--- lib/lp/testing/factory.py 2012-06-03 23:11:40 +0000
769+++ lib/lp/testing/factory.py 2012-06-08 06:05:25 +0000
770@@ -1514,7 +1514,7 @@
771 person = self.makePerson()
772 if subscribed_by is None:
773 subscribed_by = person
774- return branch.subscribe(person,
775+ return branch.subscribe(removeSecurityProxy(person),
776 BranchSubscriptionNotificationLevel.NOEMAIL, None,
777 CodeReviewNotificationLevel.NOEMAIL, subscribed_by)
778