Merge lp:~wgrant/launchpad/bug-restrict-type into lp:launchpad

Proposed by William Grant
Status: Merged
Approved by: William Grant
Approved revision: no longer in the source branch.
Merged at revision: 15887
Proposed branch: lp:~wgrant/launchpad/bug-restrict-type
Merge into: lp:launchpad
Diff against target: 934 lines (+188/-282)
19 files modified
lib/lp/bugs/browser/bugalsoaffects.py (+1/-1)
lib/lp/bugs/browser/bugtask.py (+11/-21)
lib/lp/bugs/browser/tests/test_bug_views.py (+10/-3)
lib/lp/bugs/browser/tests/test_bugs.py (+1/-1)
lib/lp/bugs/browser/tests/test_bugtask.py (+14/-25)
lib/lp/bugs/doc/bug.txt (+0/-95)
lib/lp/bugs/errors.py (+0/-6)
lib/lp/bugs/mail/tests/test_commands.py (+8/-4)
lib/lp/bugs/model/bug.py (+9/-8)
lib/lp/bugs/model/bugtask.py (+9/-3)
lib/lp/bugs/model/tests/test_bug.py (+38/-7)
lib/lp/bugs/model/tests/test_bugtask.py (+42/-69)
lib/lp/bugs/stories/bug-also-affects/xx-bug-also-affects.txt (+14/-14)
lib/lp/bugs/tests/test_bugs_webservice.py (+7/-3)
lib/lp/code/errors.py (+0/-5)
lib/lp/code/model/branch.py (+3/-3)
lib/lp/code/model/tests/test_branch.py (+11/-14)
lib/lp/registry/enums.py (+4/-0)
lib/lp/registry/errors.py (+6/-0)
To merge this branch: bzr merge lp:~wgrant/launchpad/bug-restrict-type
Reviewer Review Type Date Requested Status
Steve Kowalik (community) code Approve
Review via email: mp+121989@code.launchpad.net

Commit message

Reject bug target or information type changes that would violate sharing policies.

Description of the change

This branch prevents bugs from transitioning to an information type that's illegal in their targets, or affecting a new target that disallows their information type.

I replaced the exceptions BranchCannotChangeInformationType and BugCannotBePrivate with a generic CannotChangeInformationType. transitionToInformationType checks directly that the new type is permitted, and createBug/createTask/transitionToTarget all use validate_target to confirm that the type is permitted in the new target.

I also extended the Proprietary single-pillar checks to Embargoed, as they were intended to have been from the start.

To post a comment you must log in.
Revision history for this message
Steve Kowalik (stevenk) wrote :

This looks like excellent work. I have one mis-giving:

373 - BranchCannotChangeInformationType,
374 - branch.setPrivate,
375 - True, branch.owner)
376 + CannotChangeInformationType,
377 + branch.setPrivate, True, branch.owner)

395 - BranchCannotChangeInformationType,
396 - branch.setPrivate,
397 - False, branch.owner)
398 + CannotChangeInformationType,
399 + branch.setPrivate, False, branch.owner)

Given you've added text to the branch cases that raise CannotChangeInformationType, can you flip these tests to making use of self.assertRaisesWithContent() and make certain the exception text is right.

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/bugalsoaffects.py'
2--- lib/lp/bugs/browser/bugalsoaffects.py 2012-06-29 08:40:05 +0000
3+++ lib/lp/bugs/browser/bugalsoaffects.py 2012-08-30 06:21:19 +0000
4@@ -174,7 +174,7 @@
5 def validateStep(self, data):
6 if data.get('product'):
7 try:
8- validate_target(self.context.bug, data.get('product'))
9+ validate_new_target(self.context.bug, data.get('product'))
10 except IllegalTarget as e:
11 self.setFieldError('product', e[0])
12 return
13
14=== modified file 'lib/lp/bugs/browser/bugtask.py'
15--- lib/lp/bugs/browser/bugtask.py 2012-08-29 06:24:05 +0000
16+++ lib/lp/bugs/browser/bugtask.py 2012-08-30 06:21:19 +0000
17@@ -222,7 +222,10 @@
18 from lp.bugs.model.bugtasksearch import orderby_expression
19 from lp.code.interfaces.branchcollection import IAllBranches
20 from lp.layers import FeedsLayer
21-from lp.registry.enums import InformationType
22+from lp.registry.enums import (
23+ InformationType,
24+ PROPRIETARY_INFORMATION_TYPES,
25+ )
26 from lp.registry.interfaces.distribution import (
27 IDistribution,
28 IDistributionSet,
29@@ -699,7 +702,6 @@
30 cancel_url = canonical_url(self.context)
31 return cancel_url
32
33-
34 @cachedproperty
35 def is_duplicate_active(self):
36 active = True
37@@ -3892,18 +3894,12 @@
38 def canAddProjectTask(self):
39 """Can a new bug task on a project be added to this bug?
40
41- If a bug has any bug tasks already, were it to be private, it cannot
42- be marked as also affecting any other project, so return False.
43-
44- Note: this check is currently only relevant if a bug is private.
45- Eventually, even public bugs will have this restriction too. So what
46- happens now is that this API is used by the tales to add a class
47- called 'disallow-private' to the Also Affects Project link. A css rule
48- is used to hide the link when body.private is True.
49-
50+ If a bug has any bug tasks already, were it to be Proprietary or
51+ Embargoed, it cannot be marked as also affecting any other
52+ project, so return False.
53 """
54 bug = self.context
55- if bug.information_type != InformationType.PROPRIETARY:
56+ if bug.information_type not in PROPRIETARY_INFORMATION_TYPES:
57 return True
58 return len(bug.bugtasks) == 0
59
60@@ -3911,20 +3907,14 @@
61 """Can a new bug task on a src pkg be added to this bug?
62
63 If a bug has any existing bug tasks on a project, were it to
64- be private, then it cannot be marked as affecting a package,
65- so return False.
66+ be Proprietary or Embargoed, then it cannot be marked as
67+ affecting a package, so return False.
68
69 A task on a given package may still be illegal to add, but
70 this will be caught when bug.addTask() is attempted.
71-
72- Note: this check is currently only relevant if a bug is private.
73- Eventually, even public bugs will have this restriction too. So what
74- happens now is that this API is used by the tales to add a class
75- called 'disallow-private' to the Also Affects Package link. A css rule
76- is used to hide the link when body.private is True.
77 """
78 bug = self.context
79- if bug.information_type != InformationType.PROPRIETARY:
80+ if bug.information_type not in PROPRIETARY_INFORMATION_TYPES:
81 return True
82 for pillar in bug.affected_pillars:
83 if IProduct.providedBy(pillar):
84
85=== modified file 'lib/lp/bugs/browser/tests/test_bug_views.py'
86--- lib/lp/bugs/browser/tests/test_bug_views.py 2012-08-29 06:57:53 +0000
87+++ lib/lp/bugs/browser/tests/test_bug_views.py 2012-08-30 06:21:19 +0000
88@@ -84,8 +84,11 @@
89 # We expect that both Also Affects links (for project and distro) are
90 # disallowed.
91 owner = self.factory.makePerson()
92+ product = self.factory.makeProduct(
93+ bug_sharing_policy=BugSharingPolicy.PROPRIETARY)
94 bug = self.factory.makeBug(
95- information_type=InformationType.PROPRIETARY, owner=owner)
96+ target=product, owner=owner,
97+ information_type=InformationType.PROPRIETARY)
98 url = canonical_url(bug, rootsite="bugs")
99 browser = self.getUserBrowser(url, user=owner)
100 also_affects = find_tag_by_id(
101@@ -101,10 +104,14 @@
102 # We expect that only the Also Affects Project link is disallowed.
103 distro = self.factory.makeDistribution()
104 owner = self.factory.makePerson()
105- self.factory.makeAccessPolicy(pillar=distro)
106+ # XXX wgrant 2012-08-30 bug=1041002: Distributions don't have
107+ # sharing policies yet, so it isn't possible legitimately create
108+ # a Proprietary distro bug.
109 bug = self.factory.makeBug(
110 target=distro,
111- information_type=InformationType.PROPRIETARY, owner=owner)
112+ information_type=InformationType.PRIVATESECURITY, owner=owner)
113+ removeSecurityProxy(bug).information_type = (
114+ InformationType.PROPRIETARY)
115 url = canonical_url(bug, rootsite="bugs")
116 browser = self.getUserBrowser(url, user=owner)
117 also_affects = find_tag_by_id(
118
119=== modified file 'lib/lp/bugs/browser/tests/test_bugs.py'
120--- lib/lp/bugs/browser/tests/test_bugs.py 2012-08-29 03:19:38 +0000
121+++ lib/lp/bugs/browser/tests/test_bugs.py 2012-08-30 06:21:19 +0000
122@@ -222,7 +222,7 @@
123
124 def test_createBug_private_bug_private_bugs_false(self):
125 # createBug() adapts a kwarg to InformationType if one is is not None.
126- project = self.factory.makeProduct(
127+ project = self.factory.makeLegacyProduct(
128 licenses=[License.OTHER_PROPRIETARY])
129 with person_logged_in(project.owner):
130 project.setPrivateBugs(False, project.owner)
131
132=== modified file 'lib/lp/bugs/browser/tests/test_bugtask.py'
133--- lib/lp/bugs/browser/tests/test_bugtask.py 2012-08-28 09:36:42 +0000
134+++ lib/lp/bugs/browser/tests/test_bugtask.py 2012-08-30 06:21:19 +0000
135@@ -58,7 +58,10 @@
136 FeedsLayer,
137 setFirstLayer,
138 )
139-from lp.registry.enums import InformationType
140+from lp.registry.enums import (
141+ BugSharingPolicy,
142+ InformationType,
143+ )
144 from lp.registry.interfaces.person import PersonVisibility
145 from lp.services.config import config
146 from lp.services.database.constants import UTC_NOW
147@@ -1105,8 +1108,11 @@
148 # A bug affecting a project cannot also affect another project or
149 # package.
150 owner = self.factory.makePerson()
151+ product = self.factory.makeProduct(
152+ bug_sharing_policy=BugSharingPolicy.PROPRIETARY_OR_PUBLIC)
153 bug = self.factory.makeBug(
154- information_type=InformationType.PROPRIETARY, owner=owner)
155+ target=product, owner=owner,
156+ information_type=InformationType.PROPRIETARY)
157 with person_logged_in(owner):
158 view = self._createView(bug)
159 self.assertFalse(view.canAddProjectTask())
160@@ -1120,31 +1126,14 @@
161 # could affect another package.
162 distro = self.factory.makeDistribution()
163 owner = self.factory.makePerson()
164- self.factory.makeAccessPolicy(pillar=distro)
165 bug = self.factory.makeBug(
166 target=distro, owner=owner,
167- information_type=InformationType.PROPRIETARY)
168- with person_logged_in(owner):
169- view = self._createView(bug)
170- self.assertFalse(view.canAddProjectTask())
171- self.assertTrue(view.canAddPackageTask())
172- bug.transitionToInformationType(InformationType.USERDATA, owner)
173- self.assertTrue(view.canAddProjectTask())
174- self.assertTrue(view.canAddPackageTask())
175-
176- def test_sourcepkg_bug_cannot_affect_project(self):
177- # A bug affecting a source pkg cannot also affect another project but
178- # it could affect another package.
179- distro = self.factory.makeDistribution()
180- distroseries = self.factory.makeDistroSeries(distribution=distro)
181- sp_name = self.factory.getOrMakeSourcePackageName()
182- sp = self.factory.makeSourcePackage(
183- sourcepackagename=sp_name, distroseries=distroseries)
184- owner = self.factory.makePerson()
185- self.factory.makeAccessPolicy(pillar=distro)
186- bug = self.factory.makeBug(
187- target=sp.distribution_sourcepackage, owner=owner,
188- information_type=InformationType.PROPRIETARY)
189+ information_type=InformationType.PRIVATESECURITY)
190+ # XXX wgrant 2012-08-30 bug=1041002: Distributions don't have
191+ # sharing policies yet, so it isn't possible legitimately create
192+ # a Proprietary distro bug.
193+ removeSecurityProxy(bug).information_type = (
194+ InformationType.PROPRIETARY)
195 with person_logged_in(owner):
196 view = self._createView(bug)
197 self.assertFalse(view.canAddProjectTask())
198
199=== modified file 'lib/lp/bugs/doc/bug.txt'
200--- lib/lp/bugs/doc/bug.txt 2012-08-27 23:58:18 +0000
201+++ lib/lp/bugs/doc/bug.txt 2012-08-30 06:21:19 +0000
202@@ -654,101 +654,6 @@
203 >>> print firefox_bug.default_bugtask.bugtargetdisplayname
204 Mozilla Firefox
205
206-It's not always possible to add another bug task to a bug.
207-Proprietary bugs are only allowed to affect a single project or distribution.
208-
209- >>> params = CreateBugParams(
210- ... title="a test private bug",
211- ... comment="a description of the bug",
212- ... owner=current_user())
213- >>> private_bug = firefox.createBug(params)
214- >>> ignored = factory.makeAccessPolicy(pillar=firefox)
215- >>> private_bug.transitionToInformationType(
216- ... InformationType.PROPRIETARY, current_user())
217- True
218-
219- >>> params = CreateBugParams(
220- ... title="a test public bug",
221- ... comment="a description of the bug",
222- ... owner=current_user())
223- >>> public_bug = firefox.createBug(params)
224-
225-We can always add any new bug task to a public bug.
226-
227- >>> tomcat = getUtility(IProductSet).getByName('tomcat')
228- >>> public_bug.addTask(owner=foobar, target=tomcat)
229- <BugTask for bug...
230-
231- >>> target = tomcat.getSeries('trunk')
232- >>> public_bug.addTask(owner=foobar, target=target)
233- <BugTask for bug...
234-
235-If we try and add an invalid bug task to a private bug we get an exception.
236-
237- >>> private_bug.addTask(owner=foobar, target=tomcat)
238- Traceback (most recent call last):
239- ...
240- IllegalTarget: This proprietary bug already affects Mozilla Firefox.
241- Proprietary bugs cannot affect multiple projects.
242-
243- >>> private_bug.addTask(owner=foobar, target=ubuntu)
244- Traceback (most recent call last):
245- ...
246- IllegalTarget: This proprietary bug already affects Mozilla Firefox.
247- Proprietary bugs cannot affect multiple projects.
248-
249-We can add a new product series task so long as it's for the same product as
250-is already targeted.
251-
252- >>> target = firefox.getSeries('1.0')
253- >>> private_bug.addTask(owner=foobar, target=target)
254- <BugTask for bug...
255-
256- >>> target = tomcat.getSeries('trunk')
257- >>> private_bug.addTask(owner=foobar, target=target)
258- Traceback (most recent call last):
259- ...
260- IllegalTarget: This proprietary bug already affects Mozilla Firefox.
261- Proprietary bugs cannot affect multiple projects.
262-
263-Now we create a bug on a distribution.
264-
265- >>> private_bug = ubuntu.createBug(params)
266- >>> private_bug.setPrivate(True, current_user())
267- True
268-
269-We can add a new distro series task so long as it's for the same distro as
270-is already targeted.
271-
272- >>> private_bug.addTask(owner=foobar, target=warty)
273- <BugTask for bug...
274-
275-We can also add an allowed distro series source package.
276-
277- >>> private_bug.addTask(owner=foobar, target=warty_fox_package)
278- <BugTask for bug...
279-
280-We cannot add distro series or source package tasks for different distros.
281-
282- >>> private_bug = tubuntu.createBug(params)
283- >>> ignored = factory.makeAccessPolicy(pillar=tubuntu)
284- >>> private_bug.transitionToInformationType(
285- ... InformationType.PROPRIETARY, current_user())
286- True
287-
288- >>> private_bug.addTask(owner=foobar, target=warty)
289- Traceback (most recent call last):
290- ...
291- IllegalTarget: This proprietary bug already affects Tubuntu.
292- Proprietary bugs cannot affect multiple projects.
293-
294- >>> private_bug.addTask(owner=foobar, target=warty_fox_package)
295- Traceback (most recent call last):
296- ...
297- IllegalTarget: This proprietary bug already affects Tubuntu.
298- Proprietary bugs cannot affect multiple projects.
299-
300-
301 Changing bug visibility.
302
303 >>> bug_before_modification = Snapshot(firefox_bug, providing=IBug)
304
305=== modified file 'lib/lp/bugs/errors.py'
306--- lib/lp/bugs/errors.py 2012-08-08 04:48:40 +0000
307+++ lib/lp/bugs/errors.py 2012-08-30 06:21:19 +0000
308@@ -5,7 +5,6 @@
309
310 __metaclass__ = type
311 __all__ = [
312- 'BugCannotBePrivate',
313 'InvalidDuplicateValue',
314 ]
315
316@@ -19,8 +18,3 @@
317 @error_status(httplib.EXPECTATION_FAILED)
318 class InvalidDuplicateValue(LaunchpadValidationError):
319 """A bug cannot be set as the duplicate of another."""
320-
321-
322-@error_status(httplib.BAD_REQUEST)
323-class BugCannotBePrivate(Exception):
324- """The bug is not allowed to be private."""
325
326=== modified file 'lib/lp/bugs/mail/tests/test_commands.py'
327--- lib/lp/bugs/mail/tests/test_commands.py 2012-08-27 23:58:18 +0000
328+++ lib/lp/bugs/mail/tests/test_commands.py 2012-08-30 06:21:19 +0000
329@@ -20,7 +20,10 @@
330 TagEmailCommand,
331 UnsubscribeEmailCommand,
332 )
333-from lp.registry.enums import InformationType
334+from lp.registry.enums import (
335+ BugSharingPolicy,
336+ InformationType,
337+ )
338 from lp.services.mail.interfaces import (
339 BugTargetNotFound,
340 EmailProcessingError,
341@@ -286,11 +289,12 @@
342 def test_execute_bug_cannot_add_task(self):
343 # Test that attempts to invalidly add a new bug task results in the
344 # expected error message.
345- product = self.factory.makeProduct()
346- self.factory.makeAccessPolicy(pillar=product)
347+ product = self.factory.makeProduct(
348+ bug_sharing_policy=BugSharingPolicy.PROPRIETARY)
349 bug = self.factory.makeBug(
350 target=product, information_type=InformationType.PROPRIETARY)
351- self.factory.makeProduct(name='fnord')
352+ self.factory.makeProduct(
353+ name='fnord', bug_sharing_policy=BugSharingPolicy.PROPRIETARY)
354 login_celebrity('admin')
355 login_person(bug.owner)
356 command = AffectsEmailCommand('affects', ['fnord'])
357
358=== modified file 'lib/lp/bugs/model/bug.py'
359--- lib/lp/bugs/model/bug.py 2012-08-24 05:09:51 +0000
360+++ lib/lp/bugs/model/bug.py 2012-08-30 06:21:19 +0000
361@@ -100,10 +100,7 @@
362 UnsubscribedFromBug,
363 )
364 from lp.bugs.enums import BugNotificationLevel
365-from lp.bugs.errors import (
366- BugCannotBePrivate,
367- InvalidDuplicateValue,
368- )
369+from lp.bugs.errors import InvalidDuplicateValue
370 from lp.bugs.interfaces.bug import (
371 IBug,
372 IBugBecameQuestionEvent,
373@@ -160,8 +157,10 @@
374 from lp.registry.enums import (
375 InformationType,
376 PRIVATE_INFORMATION_TYPES,
377+ PROPRIETARY_INFORMATION_TYPES,
378 SECURITY_INFORMATION_TYPES,
379 )
380+from lp.registry.errors import CannotChangeInformationType
381 from lp.registry.interfaces.accesspolicy import (
382 IAccessArtifactGrantSource,
383 IAccessArtifactSource,
384@@ -1710,10 +1709,12 @@
385 """See `IBug`."""
386 if self.information_type == information_type:
387 return False
388- if (information_type == InformationType.PROPRIETARY and
389- len(self.affected_pillars) > 1):
390- raise BugCannotBePrivate(
391- "Multi-pillar bugs cannot be proprietary.")
392+ if information_type not in self.getAllowedInformationTypes(who):
393+ raise CannotChangeInformationType("Forbidden by project policy.")
394+ if (information_type in PROPRIETARY_INFORMATION_TYPES
395+ and len(self.affected_pillars) > 1):
396+ raise CannotChangeInformationType(
397+ "Proprietary bugs can only affect one project.")
398 if information_type in PRIVATE_INFORMATION_TYPES:
399 self.who_made_private = who
400 self.date_made_private = UTC_NOW
401
402=== modified file 'lib/lp/bugs/model/bugtask.py'
403--- lib/lp/bugs/model/bugtask.py 2012-08-29 06:24:05 +0000
404+++ lib/lp/bugs/model/bugtask.py 2012-08-30 06:21:19 +0000
405@@ -94,7 +94,7 @@
406 )
407 from lp.bugs.interfaces.bugtasksearch import BugTaskSearchParams
408 from lp.registry.enums import (
409- InformationType,
410+ PROPRIETARY_INFORMATION_TYPES,
411 PUBLIC_INFORMATION_TYPES,
412 )
413 from lp.registry.interfaces.distribution import (
414@@ -337,7 +337,13 @@
415 except NotFoundError as e:
416 raise IllegalTarget(e[0])
417
418- if bug.information_type == InformationType.PROPRIETARY:
419+ legal_types = target.pillar.getAllowedBugInformationTypes()
420+ if bug.information_type not in legal_types:
421+ raise IllegalTarget(
422+ "%s doesn't allow %s bugs." % (
423+ target.pillar.bugtargetdisplayname, bug.information_type.title))
424+
425+ if bug.information_type in PROPRIETARY_INFORMATION_TYPES:
426 # Perhaps we are replacing the one and only existing bugtask, in
427 # which case that's ok.
428 if retarget_existing and len(bug.bugtasks) <= 1:
429@@ -348,7 +354,7 @@
430 raise IllegalTarget(
431 "This proprietary bug already affects %s. "
432 "Proprietary bugs cannot affect multiple projects."
433- % bug.default_bugtask.target.bugtargetdisplayname)
434+ % bug.default_bugtask.target.pillar.bugtargetdisplayname)
435
436
437 def validate_new_target(bug, target):
438
439=== modified file 'lib/lp/bugs/model/tests/test_bug.py'
440--- lib/lp/bugs/model/tests/test_bug.py 2012-08-28 01:35:08 +0000
441+++ lib/lp/bugs/model/tests/test_bug.py 2012-08-30 06:21:19 +0000
442@@ -20,7 +20,6 @@
443 BugNotificationLevel,
444 BugNotificationStatus,
445 )
446-from lp.bugs.errors import BugCannotBePrivate
447 from lp.bugs.interfaces.bugnotification import IBugNotificationSet
448 from lp.bugs.interfaces.bugtask import BugTaskStatus
449 from lp.bugs.mail.bugnotificationrecipients import BugNotificationRecipients
450@@ -28,7 +27,11 @@
451 BugNotification,
452 BugSubscriptionInfo,
453 )
454-from lp.registry.enums import InformationType
455+from lp.registry.enums import (
456+ BugSharingPolicy,
457+ InformationType,
458+ )
459+from lp.registry.errors import CannotChangeInformationType
460 from lp.registry.interfaces.accesspolicy import (
461 IAccessArtifactSource,
462 IAccessPolicyArtifactSource,
463@@ -753,12 +756,17 @@
464
465 def test_multipillar_proprietary_bugs_disallowed(self):
466 # A multi-pillar bug cannot be made proprietary.
467- bug = self.factory.makeBug()
468- product = self.factory.makeProduct()
469- self.factory.makeBugTask(bug=bug, target=product)
470+ p1 = self.factory.makeProduct(
471+ bug_sharing_policy=BugSharingPolicy.PUBLIC_OR_PROPRIETARY)
472+ p2 = self.factory.makeProduct(
473+ bug_sharing_policy=BugSharingPolicy.PUBLIC_OR_PROPRIETARY)
474+ bug = self.factory.makeBug(target=p1)
475+ self.factory.makeBugTask(bug=bug, target=p2)
476 login_person(bug.owner)
477- self.assertRaises(
478- BugCannotBePrivate, bug.transitionToInformationType,
479+ self.assertRaisesWithContent(
480+ CannotChangeInformationType,
481+ "Proprietary bugs can only affect one project.",
482+ bug.transitionToInformationType,
483 InformationType.PROPRIETARY, bug.owner)
484 bug.transitionToInformationType(InformationType.USERDATA, bug.owner)
485 self.assertTrue(bug.private)
486@@ -869,6 +877,29 @@
487 InformationType.PRIVATESECURITY, InformationType.USERDATA],
488 self.factory.makeBug().getAllowedInformationTypes(None))
489
490+ def test_transitionToInformationType_respects_allowed_proprietary(self):
491+ # transitionToInformationType rejects types that aren't allowed
492+ # for the bug.
493+ product = self.factory.makeProduct()
494+ with person_logged_in(product.owner):
495+ bug = self.factory.makeBug(target=product)
496+ self.assertRaisesWithContent(
497+ CannotChangeInformationType, "Forbidden by project policy.",
498+ bug.transitionToInformationType,
499+ InformationType.PROPRIETARY, bug.owner)
500+
501+ def test_transitionToInformationType_respects_allowed_public(self):
502+ # transitionToInformationType rejects types that aren't allowed
503+ # for the bug.
504+ product = self.factory.makeProduct(
505+ bug_sharing_policy=BugSharingPolicy.PROPRIETARY)
506+ with person_logged_in(product.owner):
507+ bug = self.factory.makeBug(target=product)
508+ self.assertRaisesWithContent(
509+ CannotChangeInformationType, "Forbidden by project policy.",
510+ bug.transitionToInformationType,
511+ InformationType.PUBLIC, bug.owner)
512+
513
514 class TestBugPrivateAndSecurityRelatedUpdatesPrivateProject(
515 TestBugPrivateAndSecurityRelatedUpdatesMixin, TestCaseWithFactory):
516
517=== modified file 'lib/lp/bugs/model/tests/test_bugtask.py'
518--- lib/lp/bugs/model/tests/test_bugtask.py 2012-08-29 06:24:05 +0000
519+++ lib/lp/bugs/model/tests/test_bugtask.py 2012-08-30 06:21:19 +0000
520@@ -56,6 +56,7 @@
521 )
522 from lp.bugs.tests.bug import create_old_bug
523 from lp.registry.enums import (
524+ BugSharingPolicy,
525 InformationType,
526 TeamMembershipPolicy,
527 )
528@@ -2847,52 +2848,26 @@
529 a private bugs to check for multi-tenant constraints.
530 """
531
532- def test_private_multi_tenanted_forbidden(self):
533- # A new task project cannot be added if there is already one from
534- # another pillar.
535- d = self.factory.makeDistribution()
536- bug = self.factory.makeBug(target=d)
537- if not self.multi_tenant_test_one_task_only:
538- self.factory.makeBugTask(bug=bug)
539- p = self.factory.makeProduct()
540- self.factory.makeAccessPolicy(pillar=d)
541- with person_logged_in(bug.owner):
542- bug.transitionToInformationType(
543- InformationType.PROPRIETARY, bug.owner)
544- self.assertRaisesWithContent(
545- IllegalTarget,
546- "This proprietary bug already affects %s. "
547- "Proprietary bugs cannot affect multiple projects."
548- % d.displayname,
549- self.validate_method, bug, p)
550- bug.transitionToInformationType(
551- InformationType.USERDATA, bug.owner)
552- self.validate_method(bug, p)
553-
554 def test_private_incorrect_pillar_task_forbidden(self):
555- # A product or distro cannot be added if there is already a bugtask.
556- p1 = self.factory.makeProduct()
557- p2 = self.factory.makeProduct()
558- d = self.factory.makeDistribution()
559- bug = self.factory.makeBug(target=p1)
560+ # Another pillar cannot be added if there is already a bugtask.
561+ p1 = self.factory.makeProduct(
562+ bug_sharing_policy=BugSharingPolicy.PROPRIETARY_OR_PUBLIC)
563+ p2 = self.factory.makeProduct(
564+ bug_sharing_policy=BugSharingPolicy.PROPRIETARY_OR_PUBLIC)
565+ owner = self.factory.makePerson()
566+ bug = self.factory.makeBug(target=p1, owner=owner)
567+ # validate_target allows cross-pillar transitions if there's
568+ # only one task, so we might need to create a second task to test.
569 if not self.multi_tenant_test_one_task_only:
570- self.factory.makeBugTask(bug=bug)
571- self.factory.makeAccessPolicy(pillar=p1)
572- with person_logged_in(bug.owner):
573- bug.transitionToInformationType(
574- InformationType.PROPRIETARY, bug.owner)
575+ self.factory.makeBugTask(
576+ bug=bug, target=self.factory.makeProductSeries(product=p1))
577+ with person_logged_in(owner):
578 self.assertRaisesWithContent(
579 IllegalTarget,
580 "This proprietary bug already affects %s. "
581 "Proprietary bugs cannot affect multiple projects."
582 % p1.displayname,
583 self.validate_method, bug, p2)
584- self.assertRaisesWithContent(
585- IllegalTarget,
586- "This proprietary bug already affects %s. "
587- "Proprietary bugs cannot affect multiple projects."
588- % p1.displayname,
589- self.validate_method, bug, d)
590 bug.transitionToInformationType(
591 InformationType.USERDATA, bug.owner)
592 self.validate_method(bug, p2)
593@@ -2900,16 +2875,19 @@
594 def test_private_incorrect_product_series_task_forbidden(self):
595 # A product series cannot be added if there is already a bugtask for
596 # a different product.
597- p1 = self.factory.makeProduct()
598- p2 = self.factory.makeProduct()
599+ p1 = self.factory.makeProduct(
600+ bug_sharing_policy=BugSharingPolicy.PROPRIETARY_OR_PUBLIC)
601+ p2 = self.factory.makeProduct(
602+ bug_sharing_policy=BugSharingPolicy.PROPRIETARY_OR_PUBLIC)
603 series = self.factory.makeProductSeries(product=p2)
604- bug = self.factory.makeBug(target=p1)
605+ owner = self.factory.makePerson()
606+ bug = self.factory.makeBug(target=p1, owner=owner)
607+ # validate_target allows cross-pillar transitions if there's
608+ # only one task, so we might need to create a second task to test.
609 if not self.multi_tenant_test_one_task_only:
610- self.factory.makeBugTask(bug=bug)
611- self.factory.makeAccessPolicy(pillar=p1)
612- with person_logged_in(bug.owner):
613- bug.transitionToInformationType(
614- InformationType.PROPRIETARY, bug.owner)
615+ self.factory.makeBugTask(
616+ bug=bug, target=self.factory.makeProductSeries(product=p1))
617+ with person_logged_in(owner):
618 self.assertRaisesWithContent(
619 IllegalTarget,
620 "This proprietary bug already affects %s. "
621@@ -2920,29 +2898,6 @@
622 InformationType.USERDATA, bug.owner)
623 self.validate_method(bug, series)
624
625- def test_private_incorrect_distro_series_task_forbidden(self):
626- # A distro series cannot be added if there is already a bugtask for
627- # a different distro.
628- d1 = self.factory.makeDistribution()
629- d2 = self.factory.makeDistribution()
630- series = self.factory.makeDistroSeries(distribution=d2)
631- bug = self.factory.makeBug(target=d1)
632- if not self.multi_tenant_test_one_task_only:
633- self.factory.makeBugTask(bug=bug)
634- self.factory.makeAccessPolicy(pillar=d1)
635- with person_logged_in(bug.owner):
636- bug.transitionToInformationType(
637- InformationType.PROPRIETARY, bug.owner)
638- self.assertRaisesWithContent(
639- IllegalTarget,
640- "This proprietary bug already affects %s. "
641- "Proprietary bugs cannot affect multiple projects."
642- % d1.displayname,
643- self.validate_method, bug, series)
644- bug.transitionToInformationType(
645- InformationType.USERDATA, bug.owner)
646- self.validate_method(bug, series)
647-
648
649 class TestValidateTarget(TestCaseWithFactory, ValidateTargetMixin):
650
651@@ -3082,6 +3037,24 @@
652 % (dsp.sourcepackagename.name, dsp.distribution.displayname),
653 validate_target, task.bug, dsp)
654
655+ def test_illegal_information_type_disallowed(self):
656+ # The bug's current information_type must be permitted by the
657+ # new target.
658+ free_prod = self.factory.makeProduct()
659+ other_free_prod = self.factory.makeProduct()
660+ commercial_prod = self.factory.makeProduct(
661+ bug_sharing_policy=BugSharingPolicy.PROPRIETARY)
662+ bug = self.factory.makeBug(target=free_prod)
663+
664+ # The new bug is Public, which is legal on the other free product.
665+ self.assertIs(None, validate_target(bug, other_free_prod))
666+
667+ # But not on the proprietary-only product.
668+ self.assertRaisesWithContent(
669+ IllegalTarget,
670+ "%s doesn't allow Public bugs." % commercial_prod.displayname,
671+ validate_target, bug, commercial_prod)
672+
673
674 class TestValidateNewTarget(TestCaseWithFactory, ValidateTargetMixin):
675
676
677=== modified file 'lib/lp/bugs/stories/bug-also-affects/xx-bug-also-affects.txt'
678--- lib/lp/bugs/stories/bug-also-affects/xx-bug-also-affects.txt 2012-08-27 23:58:18 +0000
679+++ lib/lp/bugs/stories/bug-also-affects/xx-bug-also-affects.txt 2012-08-30 06:21:19 +0000
680@@ -201,41 +201,41 @@
681 ...>pmount (Debian)</a>...
682 ...
683
684-We cannot allow private bugs to affect more than one pillar.
685+We cannot allow proprietary bugs to affect more than one pillar.
686
687 >>> from lp.services.webapp import canonical_url
688 >>> from lp.services.webapp.interfaces import ILaunchBag
689 >>> from lp.bugs.interfaces.bug import CreateBugParams
690- >>> from lp.registry.interfaces.product import IProductSet
691- >>> from lp.registry.enums import InformationType
692+ >>> from lp.registry.enums import BugSharingPolicy, InformationType
693
694 >>> def current_user():
695 ... return getUtility(ILaunchBag).user
696
697- >>> login("foo.bar@canonical.com")
698- >>> productset = getUtility(IProductSet)
699- >>> firefox = productset.get(4)
700- >>> ignored = factory.makeAccessPolicy(pillar=firefox)
701+ >>> login("test@canonical.com")
702+ >>> product = factory.makeProduct(
703+ ... displayname='Proprietary Product', name='proprietary-product',
704+ ... bug_sharing_policy=BugSharingPolicy.PROPRIETARY)
705+ >>> other_product = factory.makeProduct(
706+ ... official_malone=True,
707+ ... bug_sharing_policy=BugSharingPolicy.PROPRIETARY)
708 >>> params = CreateBugParams(
709 ... title="a test private bug",
710 ... comment="a description of the bug",
711 ... information_type=InformationType.PROPRIETARY,
712 ... owner=current_user())
713- >>> private_bug = firefox.createBug(params)
714- >>> private_bug.subscribe(firefox.owner, firefox.owner)
715- <lp.bugs.model.bugsubscription.BugSubscription object at ...>
716+ >>> private_bug = product.createBug(params)
717 >>> logout()
718
719 >>> browser.open(canonical_url(private_bug, rootsite='bugs'))
720- >>> browser.getLink(url='+distrotask').click()
721- >>> browser.getControl(name='field.distribution').value = ['debian']
722+ >>> browser.getLink(url='+choose-affected-product').click()
723+ >>> browser.getControl(name='field.product').value = other_product.name
724 >>> browser.getControl('Continue').click()
725 >>> print browser.url
726- http://bugs.launchpad.dev/firefox/+bug/16/+distrotask
727+ http://bugs.launchpad.dev/proprietary-product/+bug/16/+choose-affected-product
728
729 >>> print get_feedback_messages(browser.contents)
730 [u'There is 1 error.',
731- u'This proprietary bug already affects Mozilla Firefox.
732+ u'This proprietary bug already affects Proprietary Product.
733 Proprietary bugs cannot affect multiple projects.']
734
735
736
737=== modified file 'lib/lp/bugs/tests/test_bugs_webservice.py'
738--- lib/lp/bugs/tests/test_bugs_webservice.py 2012-08-29 06:57:53 +0000
739+++ lib/lp/bugs/tests/test_bugs_webservice.py 2012-08-30 06:21:19 +0000
740@@ -361,15 +361,19 @@
741 # a proprietary bug. In this case, we cannot mark a proprietary bug
742 # as affecting more than one project.
743 owner = self.factory.makePerson()
744+ product1 = self.factory.makeProduct(
745+ bug_sharing_policy=BugSharingPolicy.PROPRIETARY)
746+ product2 = self.factory.makeProduct(
747+ bug_sharing_policy=BugSharingPolicy.PROPRIETARY)
748 bug = self.factory.makeBug(
749- owner=owner, information_type=InformationType.PROPRIETARY)
750- product = self.factory.makeProduct()
751+ target=product1, owner=owner,
752+ information_type=InformationType.PROPRIETARY)
753
754 login_person(owner)
755 launchpad = launchpadlib_for('test', owner)
756 lp_bug = launchpad.load(api_url(bug))
757 self.assertRaises(
758- BadRequest, lp_bug.addTask, target=api_url(product))
759+ BadRequest, lp_bug.addTask, target=api_url(product2))
760
761
762 class BugSetTestCase(TestCaseWithFactory):
763
764=== modified file 'lib/lp/code/errors.py'
765--- lib/lp/code/errors.py 2012-07-11 09:36:13 +0000
766+++ lib/lp/code/errors.py 2012-08-30 06:21:19 +0000
767@@ -8,7 +8,6 @@
768 'AlreadyLatestFormat',
769 'BadBranchMergeProposalSearchContext',
770 'BadStateTransition',
771- 'BranchCannotChangeInformationType',
772 'BranchCreationException',
773 'BranchCreationForbidden',
774 'BranchCreatorNotMemberOfOwnerTeam',
775@@ -146,10 +145,6 @@
776 """
777
778
779-class BranchCannotChangeInformationType(Exception):
780- """The information type of this branch cannot be changed."""
781-
782-
783 class InvalidBranchException(Exception):
784 """Base exception for an error resolving a branch for a component.
785
786
787=== modified file 'lib/lp/code/model/branch.py'
788--- lib/lp/code/model/branch.py 2012-08-29 02:40:36 +0000
789+++ lib/lp/code/model/branch.py 2012-08-30 06:21:19 +0000
790@@ -80,7 +80,6 @@
791 )
792 from lp.code.errors import (
793 AlreadyLatestFormat,
794- BranchCannotChangeInformationType,
795 BranchMergeProposalExists,
796 BranchTargetError,
797 BranchTypeError,
798@@ -137,6 +136,7 @@
799 PRIVATE_INFORMATION_TYPES,
800 PUBLIC_INFORMATION_TYPES,
801 )
802+from lp.registry.errors import CannotChangeInformationType
803 from lp.registry.interfaces.accesspolicy import (
804 IAccessArtifactGrantSource,
805 IAccessArtifactSource,
806@@ -249,10 +249,10 @@
807 if (self.stacked_on
808 and self.stacked_on.information_type in PRIVATE_INFORMATION_TYPES
809 and information_type in PUBLIC_INFORMATION_TYPES):
810- raise BranchCannotChangeInformationType()
811+ raise CannotChangeInformationType("Must match stacked-on branch.")
812 if (verify_policy
813 and information_type not in self.getAllowedInformationTypes(who)):
814- raise BranchCannotChangeInformationType()
815+ raise CannotChangeInformationType("Forbidden by project policy.")
816 self.information_type = information_type
817 self._reconcileAccess()
818 if information_type in PRIVATE_INFORMATION_TYPES and self.subscribers:
819
820=== modified file 'lib/lp/code/model/tests/test_branch.py'
821--- lib/lp/code/model/tests/test_branch.py 2012-08-29 03:19:38 +0000
822+++ lib/lp/code/model/tests/test_branch.py 2012-08-30 06:21:19 +0000
823@@ -54,7 +54,6 @@
824 )
825 from lp.code.errors import (
826 AlreadyLatestFormat,
827- BranchCannotChangeInformationType,
828 BranchCreatorNotMemberOfOwnerTeam,
829 BranchCreatorNotOwner,
830 BranchTargetError,
831@@ -120,6 +119,7 @@
832 PUBLIC_INFORMATION_TYPES,
833 TeamMembershipPolicy,
834 )
835+from lp.registry.errors import CannotChangeInformationType
836 from lp.registry.interfaces.accesspolicy import (
837 IAccessArtifactSource,
838 IAccessPolicyArtifactSource,
839@@ -2480,13 +2480,12 @@
840
841 def test_public_to_private_not_allowed(self):
842 # If there are no privacy policies allowing private branches, then
843- # BranchCannotChangeInformationType is rasied.
844+ # CannotChangeInformationType is raised.
845 product = self.factory.makeLegacyProduct()
846 branch = self.factory.makeBranch(product=product)
847- self.assertRaises(
848- BranchCannotChangeInformationType,
849- branch.setPrivate,
850- True, branch.owner)
851+ self.assertRaisesWithContent(
852+ CannotChangeInformationType, 'Forbidden by project policy.',
853+ branch.setPrivate, True, branch.owner)
854
855 def test_public_to_private_for_admins(self):
856 # Admins can override the default behaviour and make any public branch
857@@ -2522,8 +2521,7 @@
858
859 def test_private_to_public_not_allowed(self):
860 # If the namespace policy does not allow public branches, attempting
861- # to change the branch to be public raises
862- # BranchCannotChangeInformationType.
863+ # to change the branch to be public raises CannotChangeInformationType.
864 product = self.factory.makeLegacyProduct()
865 branch = self.factory.makeBranch(
866 product=product,
867@@ -2532,10 +2530,9 @@
868 None, BranchVisibilityRule.FORBIDDEN)
869 branch.product.setBranchVisibilityTeamPolicy(
870 branch.owner, BranchVisibilityRule.PRIVATE_ONLY)
871- self.assertRaises(
872- BranchCannotChangeInformationType,
873- branch.setPrivate,
874- False, branch.owner)
875+ self.assertRaisesWithContent(
876+ CannotChangeInformationType, 'Forbidden by project policy.',
877+ branch.setPrivate, False, branch.owner)
878
879 def test_cannot_transition_with_private_stacked_on(self):
880 # If a public branch is stacked on a private branch, it can not
881@@ -2543,8 +2540,8 @@
882 stacked_on = self.factory.makeBranch(
883 information_type=InformationType.USERDATA)
884 branch = self.factory.makeBranch(stacked_on=stacked_on)
885- self.assertRaises(
886- BranchCannotChangeInformationType,
887+ self.assertRaisesWithContent(
888+ CannotChangeInformationType, 'Must match stacked-on branch.',
889 branch.transitionToInformationType, InformationType.PUBLIC,
890 branch.owner)
891
892
893=== modified file 'lib/lp/registry/enums.py'
894--- lib/lp/registry/enums.py 2012-08-20 13:26:21 +0000
895+++ lib/lp/registry/enums.py 2012-08-30 06:21:19 +0000
896@@ -18,6 +18,7 @@
897 'PersonTransferJobType',
898 'PersonVisibility',
899 'PRIVATE_INFORMATION_TYPES',
900+ 'PROPRIETARY_INFORMATION_TYPES',
901 'PUBLIC_INFORMATION_TYPES',
902 'ProductJobType',
903 'SECURITY_INFORMATION_TYPES',
904@@ -97,6 +98,9 @@
905 FREE_INFORMATION_TYPES = (
906 PUBLIC_INFORMATION_TYPES + FREE_PRIVATE_INFORMATION_TYPES)
907
908+PROPRIETARY_INFORMATION_TYPES = (
909+ InformationType.PROPRIETARY, InformationType.EMBARGOED)
910+
911
912 class SharingPermission(DBEnumeratedType):
913 """Sharing permission.
914
915=== modified file 'lib/lp/registry/errors.py'
916--- lib/lp/registry/errors.py 2012-08-13 20:17:56 +0000
917+++ lib/lp/registry/errors.py 2012-08-30 06:21:19 +0000
918@@ -5,6 +5,7 @@
919 __all__ = [
920 'DistroSeriesDifferenceError',
921 'NotADerivedSeriesError',
922+ 'CannotChangeInformationType',
923 'CannotDeleteCommercialSubscription',
924 'CannotTransitionToCountryMirror',
925 'CommercialSubscribersOnly',
926@@ -190,3 +191,8 @@
927
928 class CannotDeleteCommercialSubscription(Exception):
929 """Raised when a commercial subscription cannot be deleted."""
930+
931+
932+@error_status(httplib.BAD_REQUEST)
933+class CannotChangeInformationType(Exception):
934+ """The information type cannot be changed."""