Merge lp:~stevenk/launchpad/branch-subscribe-aag into lp:launchpad
- branch-subscribe-aag
- Merge into devel
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 |
Related bugs: |
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.
Ian Booth (wallyworld) wrote : | # |
> <wallyworld_> StevenK: TestBranchSubsc
> 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_
> 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.
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.
Preview Diff
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 |
<wallyworld_> StevenK: TestBranchSubsc riptionAddOther View could iterate over all types of open team and test with each one gives_access test passes is that the branch collection assumes subscribed = visible
<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_
<wallyworld_> so that bit of code is sort of pointless atm