Merge lp:~wgrant/launchpad/bug-1100164 into lp:launchpad

Proposed by William Grant
Status: Merged
Approved by: Steve Kowalik
Approved revision: no longer in the source branch.
Merged at revision: 16452
Proposed branch: lp:~wgrant/launchpad/bug-1100164
Merge into: lp:launchpad
Diff against target: 591 lines (+121/-124)
16 files modified
lib/lp/registry/browser/configure.zcml (+4/-4)
lib/lp/registry/browser/productrelease.py (+2/-19)
lib/lp/registry/browser/tests/test_milestone.py (+33/-0)
lib/lp/registry/browser/tests/test_productrelease.py (+12/-55)
lib/lp/registry/configure.zcml (+4/-1)
lib/lp/registry/interfaces/productrelease.py (+7/-1)
lib/lp/registry/model/milestone.py (+0/-6)
lib/lp/registry/model/productrelease.py (+13/-1)
lib/lp/registry/stories/product/xx-product-files.txt (+12/-0)
lib/lp/registry/templates/product-index.pt (+1/-0)
lib/lp/registry/templates/productrelease-add-from-series.pt (+0/-7)
lib/lp/registry/templates/productrelease-add.pt (+0/-7)
lib/lp/registry/templates/productrelease-portlet-data.pt (+8/-2)
lib/lp/registry/tests/test_milestone.py (+2/-13)
lib/lp/registry/tests/test_productrelease.py (+17/-1)
lib/lp/security.py (+6/-7)
To merge this branch: bzr merge lp:~wgrant/launchpad/bug-1100164
Reviewer Review Type Date Requested Status
Steve Kowalik (community) code Approve
Review via email: mp+144856@code.launchpad.net

Commit message

Permit proprietary products to have releases, forbid them from adding release files.

Description of the change

Proprietary products are currently banned from having releases due to a misunderstanding in the late stages of implementation. This branch rescinds that ban and instates a new one on release *files* -- the real leak.

The addReleaseFile and +adddownloadfile reject attempts to add a file, and the listing/link are hidden from Milestone/ProductRelease:+index. I also removed the Downloads section from Product:+index for non-public projects.

And I secured the Zope permissions for ProductRelease.

To post a comment you must log in.
Revision history for this message
Steve Kowalik (stevenk) :
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/registry/browser/configure.zcml'
2--- lib/lp/registry/browser/configure.zcml 2012-12-10 20:07:39 +0000
3+++ lib/lp/registry/browser/configure.zcml 2013-01-25 06:18:24 +0000
4@@ -2012,7 +2012,7 @@
5 provides="lp.services.webapp.interfaces.IBreadcrumb"
6 for="lp.registry.interfaces.productrelease.IProductRelease"
7 factory="lp.registry.browser.milestone.MilestoneBreadcrumb"
8- permission="zope.Public"/>
9+ permission="launchpad.View"/>
10 <browser:url
11 for="lp.registry.interfaces.productrelease.IProductRelease"
12 path_expression="version"
13@@ -2027,21 +2027,21 @@
14 name="+index"/>
15 <browser:pages
16 for="lp.registry.interfaces.productrelease.IProductRelease"
17- permission="zope.Public">
18+ permission="launchpad.View">
19 <browser:page
20 name="+portlet-downloads"
21 template="../templates/productrelease-portlet-downloads.pt"/>
22 </browser:pages>
23 <browser:page
24 for="lp.registry.interfaces.productrelease.IProductRelease"
25- permission="zope.Public"
26+ permission="launchpad.View"
27 name="+index"
28 class="lp.registry.browser.milestone.MilestoneView"
29 template="../templates/milestone-index.pt"/>
30 <browser:page
31 for="lp.registry.interfaces.productrelease.IProductRelease"
32 class="lp.registry.browser.productrelease.ProductReleaseRdfView"
33- permission="zope.Public"
34+ permission="launchpad.View"
35 name="+rdf"
36 attribute="__call__"/>
37 <browser:page
38
39=== modified file 'lib/lp/registry/browser/productrelease.py'
40--- lib/lp/registry/browser/productrelease.py 2012-12-10 20:47:40 +0000
41+++ lib/lp/registry/browser/productrelease.py 2013-01-25 06:18:24 +0000
42@@ -39,7 +39,6 @@
43 LaunchpadEditFormView,
44 LaunchpadFormView,
45 )
46-from lp.app.enums import InformationType
47 from lp.app.widgets.date import DateTimeWidget
48 from lp.registry.browser import (
49 BaseRdfView,
50@@ -136,11 +135,6 @@
51 notify(ObjectCreatedEvent(newrelease))
52
53 @property
54- def releases_allowed(self):
55- return (self.context.product.information_type !=
56- InformationType.PROPRIETARY)
57-
58- @property
59 def label(self):
60 """The form label."""
61 return smartquote('Create a new release for %s' %
62@@ -166,11 +160,6 @@
63 ]
64
65 def initialize(self):
66- if (self.context.product.information_type ==
67- InformationType.EMBARGOED):
68- self.request.response.addWarningNotification(
69- _("Any releases added for %s will be PUBLIC." %
70- self.context.product.displayname))
71 if self.context.product_release is not None:
72 self.request.response.addErrorNotification(
73 _("A project release already exists for this milestone."))
74@@ -202,14 +191,6 @@
75 'changelog',
76 ]
77
78- def initialize(self):
79- if (self.context.product.information_type ==
80- InformationType.EMBARGOED):
81- self.request.response.addWarningNotification(
82- _("Any releases added for %s will be PUBLIC." %
83- self.context.displayname))
84- super(ProductReleaseFromSeriesAddView, self).initialize()
85-
86 def setUpFields(self):
87 super(ProductReleaseFromSeriesAddView, self).setUpFields()
88 self._prependKeepMilestoneActiveField()
89@@ -294,6 +275,8 @@
90
91 def validate(self, data):
92 """See `LaunchpadFormView`."""
93+ if not self.context.can_have_release_files:
94+ self.addError('Only public projects can have download files.')
95 file_name = None
96 filecontent = self.request.form.get(self.widgets['filecontent'].name)
97 if filecontent:
98
99=== modified file 'lib/lp/registry/browser/tests/test_milestone.py'
100--- lib/lp/registry/browser/tests/test_milestone.py 2012-11-15 22:22:38 +0000
101+++ lib/lp/registry/browser/tests/test_milestone.py 2013-01-25 06:18:24 +0000
102@@ -117,6 +117,39 @@
103 with person_logged_in(None):
104 browser = self.getViewBrowser(milestone, '+index')
105
106+ def test_downloads_listed(self):
107+ # When a release exists a list of download files and a link to
108+ # add more are shown.
109+ owner = self.factory.makePerson()
110+ product = self.factory.makeProduct(owner=owner)
111+ release = self.factory.makeProductRelease(product=product)
112+ with person_logged_in(owner):
113+ owner_browser = self.getViewBrowser(
114+ release.milestone, '+index', user=owner)
115+ html = owner_browser.contents
116+ self.assertIn('Download files for this release', html)
117+ self.assertIn('Add download file', html)
118+ with person_logged_in(None):
119+ owner_browser = self.getViewBrowser(release.milestone, '+index')
120+ html = owner_browser.contents
121+ self.assertIn('Download files for this release', html)
122+ self.assertNotIn('Add download file', html)
123+
124+ def test_downloads_section_hidden_for_proprietary_product(self):
125+ # Only public projects can have download files, so the downloads
126+ # section is replaced with a message indicating this.
127+ owner = self.factory.makePerson()
128+ product = self.factory.makeProduct(
129+ owner=owner, information_type=InformationType.PROPRIETARY)
130+ with person_logged_in(owner):
131+ release = self.factory.makeProductRelease(product=product)
132+ owner_browser = self.getViewBrowser(
133+ release.milestone, '+index', user=owner)
134+ html = owner_browser.contents
135+ self.assertIn(
136+ 'Only public projects can have download files.', html)
137+ self.assertNotIn('Add download file', html)
138+
139
140 class TestAddMilestoneViews(TestCaseWithFactory):
141
142
143=== modified file 'lib/lp/registry/browser/tests/test_productrelease.py'
144--- lib/lp/registry/browser/tests/test_productrelease.py 2012-12-11 13:40:11 +0000
145+++ lib/lp/registry/browser/tests/test_productrelease.py 2013-01-25 06:18:24 +0000
146@@ -9,7 +9,6 @@
147 from lp.app.enums import InformationType
148 from lp.services.webapp.escaping import html_escape
149 from lp.testing import (
150- BrowserTestCase,
151 person_logged_in,
152 TestCaseWithFactory,
153 )
154@@ -17,60 +16,6 @@
155 from lp.testing.views import create_initialized_view
156
157
158-class NonPublicProductReleaseViewTestCase(BrowserTestCase):
159-
160- layer = LaunchpadFunctionalLayer
161-
162- def test_proprietary_add_milestone(self):
163- owner = self.factory.makePerson()
164- product = self.factory.makeProduct(name='fnord',
165- owner=owner, information_type=InformationType.PROPRIETARY)
166- milestone = self.factory.makeMilestone(product=product)
167- with person_logged_in(owner):
168- browser = self.getViewBrowser(
169- milestone, view_name="+addrelease", user=owner)
170- msg = 'Fnord is PROPRIETARY. It cannot have any releases.'
171- self.assertTrue(html_escape(msg) in browser.contents)
172-
173- def test_proprietary_add_series(self):
174- owner = self.factory.makePerson()
175- product = self.factory.makeProduct(name='fnord',
176- owner=owner, information_type=InformationType.PROPRIETARY)
177- series = self.factory.makeProductSeries(product=product, name='bnord')
178- with person_logged_in(owner):
179- browser = self.getViewBrowser(
180- series, view_name="+addrelease", user=owner)
181- msg = ('The bnord series of Fnord is PROPRIETARY.'
182- ' It cannot have any releases.')
183- self.assertTrue(html_escape(msg) in browser.contents)
184-
185- def test_embargoed_add_milestone(self):
186- owner = self.factory.makePerson()
187- product = self.factory.makeProduct(name='fnord',
188- owner=owner, information_type=InformationType.EMBARGOED)
189- milestone = self.factory.makeMilestone(product=product)
190- with person_logged_in(owner):
191- view = create_initialized_view(milestone, name="+addrelease")
192- notifications = [
193- nm.message for nm in view.request.response.notifications]
194- self.assertEqual(
195- [html_escape("Any releases added for Fnord will be PUBLIC.")],
196- notifications)
197-
198- def test_embargoed_add_series(self):
199- owner = self.factory.makePerson()
200- product = self.factory.makeProduct(name='fnord',
201- owner=owner, information_type=InformationType.EMBARGOED)
202- series = self.factory.makeProductSeries(product=product, name='bnord')
203- with person_logged_in(owner):
204- view = create_initialized_view(series, name="+addrelease")
205- notifications = [
206- nm.message for nm in view.request.response.notifications]
207- self.assertEqual(
208- [html_escape("Any releases added for bnord will be PUBLIC.")],
209- notifications)
210-
211-
212 class ProductReleaseAddDownloadFileViewTestCase(TestCaseWithFactory):
213
214 layer = LaunchpadFunctionalLayer
215@@ -111,3 +56,15 @@
216 self.assertEqual(
217 [html_escape("The file '%s' is already uploaded." % file_name)],
218 view.errors)
219+
220+ def test_refuses_proprietary_products(self):
221+ owner = self.factory.makePerson()
222+ product = self.factory.makeProduct(
223+ owner=owner, information_type=InformationType.PROPRIETARY)
224+ with person_logged_in(owner):
225+ release = self.factory.makeProductRelease(product=product)
226+ form = self.makeForm('something.tar.gz')
227+ view = create_initialized_view(
228+ release, '+adddownloadfile', form=form)
229+ self.assertEqual(
230+ ['Only public projects can have download files.'], view.errors)
231
232=== modified file 'lib/lp/registry/configure.zcml'
233--- lib/lp/registry/configure.zcml 2012-12-06 16:09:31 +0000
234+++ lib/lp/registry/configure.zcml 2013-01-25 06:18:24 +0000
235@@ -1875,10 +1875,13 @@
236 <allow
237 interface="lp.registry.interfaces.productrelease.IProductReleasePublic"/>
238 <require
239+ permission="launchpad.View"
240+ interface="lp.registry.interfaces.productrelease.IProductReleaseView"/>
241+ <require
242 permission="launchpad.Edit"
243 interface="lp.registry.interfaces.productrelease.IProductReleaseEditRestricted"/>
244 <require
245- permission="zope.Public"
246+ permission="launchpad.Edit"
247 set_schema="lp.registry.interfaces.productrelease.IProductRelease"/>
248 </class>
249 <adapter
250
251=== modified file 'lib/lp/registry/interfaces/productrelease.py'
252--- lib/lp/registry/interfaces/productrelease.py 2012-11-21 21:29:29 +0000
253+++ lib/lp/registry/interfaces/productrelease.py 2013-01-25 06:18:24 +0000
254@@ -274,6 +274,10 @@
255
256 id = Int(title=_('ID'), required=True, readonly=True)
257
258+
259+class IProductReleaseView(Interface):
260+ """launchpad.View-restricted `IProductRelease` properties."""
261+
262 datereleased = exported(
263 Datetime(
264 title=_('Date released'), required=True,
265@@ -334,6 +338,8 @@
266 Text(title=u'Constructed title for a project release.', readonly=True)
267 )
268
269+ can_have_release_files = Attribute("Whether release files can be added.")
270+
271 product = exported(
272 Reference(title=u'The project that made this release.',
273 schema=Interface, readonly=True),
274@@ -371,7 +377,7 @@
275 """Does the release have a file that matches the name?"""
276
277
278-class IProductRelease(IProductReleaseEditRestricted,
279+class IProductRelease(IProductReleaseEditRestricted, IProductReleaseView,
280 IProductReleasePublic):
281 """A specific release (i.e. version) of a product.
282
283
284=== modified file 'lib/lp/registry/model/milestone.py'
285--- lib/lp/registry/model/milestone.py 2013-01-22 05:07:31 +0000
286+++ lib/lp/registry/model/milestone.py 2013-01-25 06:18:24 +0000
287@@ -36,7 +36,6 @@
288 from zope.component import getUtility
289 from zope.interface import implements
290
291-from lp.app.enums import InformationType
292 from lp.app.errors import NotFoundError
293 from lp.blueprints.model.specification import Specification
294 from lp.blueprints.model.specificationsearch import (
295@@ -56,7 +55,6 @@
296 from lp.bugs.model.structuralsubscription import (
297 StructuralSubscriptionTargetMixin,
298 )
299-from lp.registry.errors import ProprietaryProduct
300 from lp.registry.interfaces.milestone import (
301 IHasMilestones,
302 IMilestone,
303@@ -277,10 +275,6 @@
304 def createProductRelease(self, owner, datereleased,
305 changelog=None, release_notes=None):
306 """See `IMilestone`."""
307- info_type = self.product.information_type
308- if info_type == InformationType.PROPRIETARY:
309- raise ProprietaryProduct(
310- "Proprietary products cannot have releases.")
311 if self.product_release is not None:
312 raise MultipleProductReleases()
313 release = ProductRelease(
314
315=== modified file 'lib/lp/registry/model/productrelease.py'
316--- lib/lp/registry/model/productrelease.py 2012-11-21 21:29:29 +0000
317+++ lib/lp/registry/model/productrelease.py 2013-01-25 06:18:24 +0000
318@@ -25,8 +25,12 @@
319 from zope.component import getUtility
320 from zope.interface import implements
321
322+from lp.app.enums import InformationType
323 from lp.app.errors import NotFoundError
324-from lp.registry.errors import InvalidFilename
325+from lp.registry.errors import (
326+ InvalidFilename,
327+ ProprietaryProduct,
328+ )
329 from lp.registry.interfaces.person import (
330 validate_person,
331 validate_public_person,
332@@ -104,6 +108,11 @@
333 """See `IProductRelease`."""
334 return self.milestone.title
335
336+ @property
337+ def can_have_release_files(self):
338+ """See `IProductRelease`."""
339+ return self.product.information_type == InformationType.PUBLIC
340+
341 @staticmethod
342 def normalizeFilename(filename):
343 # Replace slashes in the filename with less problematic dashes.
344@@ -141,6 +150,9 @@
345 file_type=UpstreamFileType.CODETARBALL,
346 description=None):
347 """See `IProductRelease`."""
348+ if not self.can_have_release_files:
349+ raise ProprietaryProduct(
350+ "Only public projects can have download files.")
351 if self.hasReleaseFile(filename):
352 raise InvalidFilename
353 # Create the alias for the file.
354
355=== modified file 'lib/lp/registry/stories/product/xx-product-files.txt'
356--- lib/lp/registry/stories/product/xx-product-files.txt 2012-12-10 13:43:47 +0000
357+++ lib/lp/registry/stories/product/xx-product-files.txt 2013-01-25 06:18:24 +0000
358@@ -47,6 +47,18 @@
359 File Description Downloads
360 firefox_0.9.2.orig.tar.gz (md5) -
361
362+Only public projects can have download files, so the portlet is omitted
363+otherwise.
364+
365+ >>> from lp.app.enums import InformationType
366+ >>> login('admin@canonical.com')
367+ >>> prop_prod = factory.makeProduct(
368+ ... name='prop-prod', information_type=InformationType.PROPRIETARY)
369+ >>> logout()
370+ >>> admin_browser.open('http://launchpad.dev/prop-prod')
371+ >>> print find_tag_by_id(admin_browser.contents, 'downloads')
372+ None
373+
374
375 Deletion is only for the privileged
376 ===================================
377
378=== modified file 'lib/lp/registry/templates/product-index.pt'
379--- lib/lp/registry/templates/product-index.pt 2012-12-07 20:37:04 +0000
380+++ lib/lp/registry/templates/product-index.pt 2013-01-25 06:18:24 +0000
381@@ -236,6 +236,7 @@
382 <div tal:replace="structure context/@@+get-involved" />
383
384 <div id="downloads" class="top-portlet downloads"
385+ tal:condition="context/information_type/enumvalue:PUBLIC"
386 tal:define="release view/latest_release_with_download_files">
387 <h2>Downloads</h2>
388
389
390=== modified file 'lib/lp/registry/templates/productrelease-add-from-series.pt'
391--- lib/lp/registry/templates/productrelease-add-from-series.pt 2012-12-10 20:24:40 +0000
392+++ lib/lp/registry/templates/productrelease-add-from-series.pt 2013-01-25 06:18:24 +0000
393@@ -64,7 +64,6 @@
394 </metal:block>
395
396 <div metal:fill-slot="main">
397- <tal:releases-allowed condition="view/releases_allowed">
398 <div metal:use-macro="context/@@launchpad_form/form">
399 <div metal:fill-slot="extra_info" tal:condition="context/releases"
400 style="border-bottom: solid 1px black">
401@@ -78,12 +77,6 @@
402 </ul>
403 </div>
404 </div>
405- </tal:releases-allowed>
406- <tal:releases-forbidden condition="not: view/releases_allowed">
407- The <tal:seriesname replace="context/displayname"
408- /> series of <tal:productname replace="context/product/displayname"
409- /> is PROPRIETARY. It cannot have any releases.
410- </tal:releases-forbidden>
411 </div>
412
413 </body>
414
415=== modified file 'lib/lp/registry/templates/productrelease-add.pt'
416--- lib/lp/registry/templates/productrelease-add.pt 2012-12-10 20:24:40 +0000
417+++ lib/lp/registry/templates/productrelease-add.pt 2013-01-25 06:18:24 +0000
418@@ -14,7 +14,6 @@
419 </metal:block>
420
421 <div metal:fill-slot="main">
422- <tal:releases-allowed condition="view/releases_allowed">
423 <div metal:use-macro="context/@@launchpad_form/form">
424 <div id="other-releases"
425 metal:fill-slot="extra_info"
426@@ -30,12 +29,6 @@
427 </ul>
428 </div>
429 </div>
430- </tal:releases-allowed>
431- <tal:releases-forbidden condition="not: view/releases_allowed">
432- <tal:productname replace="context/product/displayname"
433- /> is PROPRIETARY. It cannot have any releases.
434- </tal:releases-forbidden>
435-
436 </div>
437
438 </body>
439
440=== modified file 'lib/lp/registry/templates/productrelease-portlet-data.pt'
441--- lib/lp/registry/templates/productrelease-portlet-data.pt 2012-09-10 03:45:21 +0000
442+++ lib/lp/registry/templates/productrelease-portlet-data.pt 2013-01-25 06:18:24 +0000
443@@ -6,8 +6,10 @@
444
445 <div class="portlet"
446 tal:define="has_edit view/release/required:launchpad.Edit;">
447- <form method="POST" tal:attributes="action request/URL">
448- <h2>Download files for this release</h2>
449+ <h2>Download files for this release</h2>
450+ <form
451+ tal:condition="view/release/can_have_release_files"
452+ method="POST" tal:attributes="action request/URL">
453
454 <p id="how-to-verify" tal:condition="view/download_files">
455 After you've downloaded a file, you can verify its authenticity
456@@ -64,8 +66,12 @@
457 </ul>
458 </div>
459 </form>
460+ <tal:nofiles tal:condition="not:view/release/can_have_release_files">
461+ <em>Only public projects can have download files.</em>
462+ </tal:nofiles>
463 </div>
464
465+
466 <div class="top-portlet">
467 <h2>Release notes&nbsp;<a tal:replace="structure view/release/menu:context/edit/fmt:icon" /></h2>
468
469
470=== modified file 'lib/lp/registry/tests/test_milestone.py'
471--- lib/lp/registry/tests/test_milestone.py 2012-12-26 01:04:05 +0000
472+++ lib/lp/registry/tests/test_milestone.py 2013-01-25 06:18:24 +0000
473@@ -5,8 +5,8 @@
474
475 __metaclass__ = type
476
477-import datetime
478 from operator import attrgetter
479+import unittest
480
481 from storm.exceptions import NoneError
482 from zope.component import getUtility
483@@ -45,18 +45,16 @@
484 from lp.testing.matchers import DoesNotSnapshot
485
486
487-class MilestoneTest(TestCaseWithFactory):
488+class MilestoneTest(unittest.TestCase):
489 """Milestone tests."""
490
491 layer = LaunchpadFunctionalLayer
492
493 def setUp(self):
494- super(MilestoneTest, self).setUp()
495 login(ANONYMOUS)
496
497 def tearDown(self):
498 logout()
499- super(MilestoneTest, self).tearDown()
500
501 def testMilestoneSetIterator(self):
502 """Test of MilestoneSet.__iter__()."""
503@@ -116,15 +114,6 @@
504 all_visible_milestones_ids,
505 [1, 2, 3])
506
507- def test_proprietary_product_milestones_cannot_have_releases(self):
508- owner = self.factory.makePerson()
509- product = self.factory.makeProduct(
510- owner=owner, information_type=InformationType.PROPRIETARY)
511- milestone = self.factory.makeMilestone(product=product)
512- with person_logged_in(owner):
513- self.assertRaises(ProprietaryProduct,
514- milestone.createProductRelease, owner, datetime.date.today())
515-
516
517 class MilestoneSecurityAdaperTestCase(TestCaseWithFactory):
518 """A TestCase for the security adapter of milestones."""
519
520=== modified file 'lib/lp/registry/tests/test_productrelease.py'
521--- lib/lp/registry/tests/test_productrelease.py 2012-11-19 20:42:45 +0000
522+++ lib/lp/registry/tests/test_productrelease.py 2013-01-25 06:18:24 +0000
523@@ -7,7 +7,11 @@
524
525 from zope.component import getUtility
526
527-from lp.registry.errors import InvalidFilename
528+from lp.app.enums import InformationType
529+from lp.registry.errors import (
530+ InvalidFilename,
531+ ProprietaryProduct,
532+ )
533 from lp.registry.interfaces.productrelease import (
534 IProductReleaseSet,
535 UpstreamFileType,
536@@ -70,6 +74,7 @@
537
538 def test_addReleaseFile(self):
539 release = self.factory.makeProductRelease()
540+ self.assertTrue(release.can_have_release_files)
541 maintainer = release.milestone.product.owner
542 with person_logged_in(maintainer):
543 release_file = release.addReleaseFile(
544@@ -89,3 +94,14 @@
545 self.assertRaises(
546 InvalidFilename, release.addReleaseFile,
547 library_file.filename, 'test', 'text/plain', maintainer)
548+
549+ def test_addReleaseFile_only_works_on_public_products(self):
550+ owner = self.factory.makePerson()
551+ product = self.factory.makeProduct(
552+ information_type=InformationType.PROPRIETARY, owner=owner)
553+ with person_logged_in(owner):
554+ release = self.factory.makeProductRelease(product=product)
555+ self.assertFalse(release.can_have_release_files)
556+ self.assertRaises(
557+ ProprietaryProduct, release.addReleaseFile,
558+ 'README', 'test', 'text/plain', owner)
559
560=== modified file 'lib/lp/security.py'
561--- lib/lp/security.py 2013-01-16 04:51:47 +0000
562+++ lib/lp/security.py 2013-01-25 06:18:24 +0000
563@@ -529,11 +529,6 @@
564 usedfor = IDistributionMirror
565
566
567-class ViewAbstractMilestone(AnonymousAuthorization):
568- """Anyone can view an IMilestone or an IProjectGroupMilestone."""
569- usedfor = IAbstractMilestone
570-
571-
572 class EditSpecificationBranch(AuthorizationBase):
573
574 usedfor = ISpecificationBranch
575@@ -1740,10 +1735,14 @@
576 self, user)
577
578
579-class ViewProductRelease(AnonymousAuthorization):
580-
581+class ViewProductRelease(DelegatedAuthorization):
582+ permission = 'launchpad.View'
583 usedfor = IProductRelease
584
585+ def __init__(self, obj):
586+ super(ViewProductRelease, self).__init__(
587+ obj, obj.milestone, 'launchpad.View')
588+
589
590 class AdminTranslationImportQueueEntry(AuthorizationBase):
591 permission = 'launchpad.Admin'