Merge lp:~adeuring/launchpad/bug-1086043 into lp:launchpad

Proposed by Abel Deuring
Status: Merged
Approved by: Abel Deuring
Approved revision: no longer in the source branch.
Merged at revision: 16340
Proposed branch: lp:~adeuring/launchpad/bug-1086043
Merge into: lp:launchpad
Diff against target: 555 lines (+226/-40)
6 files modified
lib/lp/bugs/browser/tests/test_buglisting.py (+53/-0)
lib/lp/registry/configure.zcml (+10/-3)
lib/lp/registry/interfaces/productseries.py (+20/-15)
lib/lp/registry/tests/test_milestone.py (+67/-12)
lib/lp/registry/tests/test_productseries.py (+57/-10)
lib/lp/security.py (+19/-0)
To merge this branch: bzr merge lp:~adeuring/launchpad/bug-1086043
Reviewer Review Type Date Requested Status
Richard Harding (community) Approve
Review via email: mp+137882@code.launchpad.net

Commit message

Use the permission launchpad.LimitedView for some attributes of IMilestone and IProductSeries.

Description of the change

This branch fixes 1086043: https://bugs.launchpad.net/~ broken
if the user subscribed to a bug with a task for a series of a
private project.

The cause of this bug is obvious: A few properties of a product
series and a milestone are displayed in bug listings, so the current
user needs to have the permission to access these properties, even
it they are allowed to view all details about the series/milestone.

In other words: These properties should be proected by the permission
lp.LimitedView, not lp.View. (Before we noicticed this bug, I would
have sworn that we already implemented this...)

Implementation details:

I added the new test class TestPersonBugListing, which adds
tests to render a person's bug page when the user is subscribed to
a bug related to a private project.

test_grant_for_bug_with_task_for_private_product() passes without further
changes, but the two other tests (where a bug task for a product series
exists, or the bug task is linked to a milestone) needed a few more
changes:

- registry/configure.zcml now requires the permission
  lp.LimitedView for some properties of IProductSeries and IMilestone;
- new interface class IProductSeriesLimitedView which defines the
  attributes that are protected by lp.LimitedView. (There no
  corresponding change for IMilestone, because the permissions
  are specified attribute-by-attribute in configure.zcml)
- new security adapters for lp.LimitedView and IProductSeries/IMilestone
- permision and access tests for IMilestone/IProductSeries updated/
  extended.

tests:

./bin/test registry -vvt test_productseries.ProductSeriesSecurityAdaperTestCase
./bin/test bugs -vvt lp.bugs.browser.tests.test_buglisting.TestPersonBugListing.test_grant
./bin/test registry -vvt test_milestone.MilestoneSecurityAdaperTestCase

- tests that a person bug page can be rendered with an AAG bug with ersies
  target and milestone
- permission lp.LimiedView for IMilestone and IProductSeries

no lint

To post a comment you must log in.
Revision history for this message
Richard Harding (rharding) wrote :

#132

Shouldn't displayname also be moved to the interface like name? It seems

#221

The comment has a duped launchpad.View, I assume one is meant to be LimitedView

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/bugs/browser/tests/test_buglisting.py'
2--- lib/lp/bugs/browser/tests/test_buglisting.py 2012-10-08 07:36:45 +0000
3+++ lib/lp/bugs/browser/tests/test_buglisting.py 2012-12-04 16:26:21 +0000
4@@ -11,6 +11,7 @@
5 )
6 from zope.component import getUtility
7
8+from lp.app.enums import InformationType
9 from lp.app.interfaces.launchpad import ILaunchpadCelebrities
10 from lp.services.features.testing import FeatureFixture
11 from lp.services.webapp.publisher import canonical_url
12@@ -434,3 +435,55 @@
13 def _getBugTarget(self, dsp):
14 """Return the current `ISourcePackage` for the dsp."""
15 return dsp.development_version
16+
17+
18+class TestPersonBugListing(BrowserTestCase):
19+ layer = DatabaseFunctionalLayer
20+
21+ def setUp(self):
22+ super(TestPersonBugListing, self).setUp()
23+ self.user = self.factory.makePerson()
24+ self.private_product_owner = self.factory.makePerson()
25+ self.private_product = self.factory.makeProduct(
26+ owner=self.private_product_owner,
27+ information_type=InformationType.PROPRIETARY)
28+
29+ def test_grant_for_bug_with_task_for_private_product(self):
30+ # A person's own bug page is correctly rendered when the person
31+ # is subscribed to a bug with a task for a propritary product.
32+ with person_logged_in(self.private_product_owner):
33+ bug = self.factory.makeBug(
34+ target=self.private_product, owner=self.private_product_owner)
35+ bug.subscribe(self.user, subscribed_by=self.private_product_owner)
36+ url = canonical_url(self.user, rootsite='bugs')
37+ # Just ensure that no exception occurs when the page is rendered.
38+ self.getUserBrowser(url, user=self.user)
39+
40+ def test_grant_for_bug_with_task_for_private_product_series(self):
41+ # A person's own bug page is correctly rendered when the person
42+ # is subscribed to a bug with a task for a propritary product series.
43+ with person_logged_in(self.private_product_owner):
44+ series = self.factory.makeProductSeries(
45+ product=self.private_product)
46+ bug = self.factory.makeBug(
47+ target=self.private_product, series=series,
48+ owner=self.private_product_owner)
49+ bug.subscribe(self.user, subscribed_by=self.private_product_owner)
50+ url = canonical_url(self.user, rootsite='bugs')
51+ # Just ensure that no exception occurs when the page is rendered.
52+ self.getUserBrowser(url, user=self.user)
53+
54+ def test_grant_for_bug_with_task_for_private_product_and_milestone(self):
55+ # A person's own bug page is correctly rendered when the person
56+ # is subscribed to a bug with a task for a propritary product and
57+ # a milestone.
58+ with person_logged_in(self.private_product_owner):
59+ milestone = self.factory.makeMilestone(
60+ product=self.private_product)
61+ bug = self.factory.makeBug(
62+ target=self.private_product, milestone=milestone,
63+ owner=self.private_product_owner)
64+ bug.subscribe(self.user, subscribed_by=self.private_product_owner)
65+ url = canonical_url(self.user, rootsite='bugs')
66+ # Just ensure that no exception occurs when the page is rendered.
67+ self.getUserBrowser(url, user=self.user)
68
69=== modified file 'lib/lp/registry/configure.zcml'
70--- lib/lp/registry/configure.zcml 2012-11-27 22:02:34 +0000
71+++ lib/lp/registry/configure.zcml 2012-12-04 16:26:21 +0000
72@@ -1035,20 +1035,24 @@
73 bugtasks
74 code_name
75 dateexpected
76- displayname
77 distribution
78 distroseries
79 getSpecifications
80 getTags
81 getTagsData
82- name
83 product
84 product_release
85 productseries
86 series_target
87 summary
88+ title
89+ "/>
90+ <require
91+ permission="launchpad.LimitedView"
92+ attributes="
93+ displayname
94+ name
95 target
96- title
97 "/>
98 <require
99 permission="launchpad.Edit"
100@@ -1565,6 +1569,9 @@
101 <allow
102 interface="lp.registry.interfaces.productseries.IProductSeriesPublic"/>
103 <require
104+ permission="launchpad.LimitedView"
105+ interface="lp.registry.interfaces.productseries.IProductSeriesLimitedView"/>
106+ <require
107 permission="launchpad.View"
108 interface="lp.registry.interfaces.productseries.IProductSeriesView"/>
109 <require
110
111=== modified file 'lib/lp/registry/interfaces/productseries.py'
112--- lib/lp/registry/interfaces/productseries.py 2012-10-18 14:05:41 +0000
113+++ lib/lp/registry/interfaces/productseries.py 2012-12-04 16:26:21 +0000
114@@ -10,6 +10,7 @@
115 __all__ = [
116 'IProductSeries',
117 'IProductSeriesEditRestricted',
118+ 'IProductSeriesLimitedView',
119 'IProductSeriesPublic',
120 'IProductSeriesSet',
121 'IProductSeriesView',
122@@ -138,16 +139,29 @@
123 """True if the given user has access to this product."""
124
125
126-class IProductSeriesView(
127- ISeriesMixin, IHasAppointedDriver, IHasOwner, IBugTarget,
128- ISpecificationGoal, IHasMilestones, IHasOfficialBugTags, IHasExpirableBugs,
129- IHasTranslationImports, IHasTranslationTemplates, IServiceUsage):
130+class IProductSeriesLimitedView(Interface):
131+
132+ name = exported(
133+ ProductSeriesNameField(
134+ title=_('Name'),
135+ description=_(
136+ "The name of the series is a short, unique name "
137+ "that identifies it, being used in URLs. It must be all "
138+ "lowercase, with no special characters. For example, '2.0' "
139+ "or 'trunk'."),
140+ constraint=name_validator))
141+
142 product = exported(
143 ReferenceChoice(title=_('Project'), required=True,
144 vocabulary='Product', schema=Interface), # really IProduct
145 exported_as='project')
146 productID = Attribute('The product ID.')
147
148+
149+class IProductSeriesView(
150+ ISeriesMixin, IHasAppointedDriver, IHasOwner, IBugTarget,
151+ ISpecificationGoal, IHasMilestones, IHasOfficialBugTags, IHasExpirableBugs,
152+ IHasTranslationImports, IHasTranslationTemplates, IServiceUsage):
153 status = exported(
154 Choice(
155 title=_('Status'), required=True, vocabulary=SeriesStatus,
156@@ -155,16 +169,6 @@
157
158 parent = Attribute('The structural parent of this series - the product')
159
160- name = exported(
161- ProductSeriesNameField(
162- title=_('Name'),
163- description=_(
164- "The name of the series is a short, unique name "
165- "that identifies it, being used in URLs. It must be all "
166- "lowercase, with no special characters. For example, '2.0' "
167- "or 'trunk'."),
168- constraint=name_validator))
169-
170 datecreated = exported(
171 Datetime(title=_('Date Registered'),
172 required=True,
173@@ -336,7 +340,8 @@
174
175
176 class IProductSeries(IProductSeriesEditRestricted, IProductSeriesPublic,
177- IProductSeriesView, IStructuralSubscriptionTarget):
178+ IProductSeriesView, IProductSeriesLimitedView,
179+ IStructuralSubscriptionTarget):
180 """A series of releases. For example '2.0' or '1.3' or 'dev'."""
181 export_as_webservice_entry('project_series')
182
183
184=== modified file 'lib/lp/registry/tests/test_milestone.py'
185--- lib/lp/registry/tests/test_milestone.py 2012-11-26 08:33:03 +0000
186+++ lib/lp/registry/tests/test_milestone.py 2012-12-04 16:26:21 +0000
187@@ -21,7 +21,6 @@
188 from lp.app.interfaces.informationtype import IInformationType
189 from lp.app.interfaces.services import IService
190 from lp.registry.enums import (
191- BugSharingPolicy,
192 SharingPermission,
193 SpecificationSharingPolicy,
194 )
195@@ -137,16 +136,17 @@
196 'id', 'checkAuthenticated', 'checkUnauthenticated',
197 'userCanView',
198 )),
199+ 'launchpad.LimitedView': set(('displayname', 'name', 'target', )),
200 'launchpad.View': set((
201 'active', 'bug_subscriptions', 'bugtasks', 'code_name',
202- 'dateexpected', 'displayname', 'distribution', 'distroseries',
203+ 'dateexpected', 'distribution', 'distroseries',
204 '_getOfficialTagClause', 'getBugSummaryContextWhereClause',
205 'getBugTaskWeightFunction', 'getSpecifications',
206 'getSubscription', 'getSubscriptions', 'getTags', 'getTagsData',
207- 'getUsedBugTagsWithOpenCounts', 'name', 'official_bug_tags',
208+ 'getUsedBugTagsWithOpenCounts', 'official_bug_tags',
209 'parent_subscription_target', 'product', 'product_release',
210 'productseries', 'searchTasks', 'series_target',
211- 'summary', 'target', 'target_type_display', 'title',
212+ 'summary', 'target_type_display', 'title',
213 'userCanAlterBugSubscription', 'userCanAlterSubscription',
214 'userHasBugSubscriptions',
215 )),
216@@ -232,20 +232,28 @@
217 self.proprietary_milestone)
218
219 # They have access to attributes requiring the permission
220- # launchpad.View of milestones for public products...
221+ # launchpad.View or launchpad.LimitedView of milestones for
222+ # public products...
223 self.assertAccessAuthorized(
224 self.expected_get_permissions['launchpad.View'],
225 self.public_milestone)
226+ self.assertAccessAuthorized(
227+ self.expected_get_permissions['launchpad.LimitedView'],
228+ self.public_milestone)
229
230 # ...but not to the same attributes of milestones for private
231 # products.
232 self.assertAccessUnauthorized(
233 self.expected_get_permissions['launchpad.View'],
234 self.proprietary_milestone)
235+ self.assertAccessUnauthorized(
236+ self.expected_get_permissions['launchpad.LimitedView'],
237+ self.proprietary_milestone)
238
239 # They cannot access other attributes.
240 for permission, names in self.expected_get_permissions.items():
241- if permission in (CheckerPublic, 'launchpad.View'):
242+ if permission in (CheckerPublic, 'launchpad.View',
243+ 'launchpad.LimitedView'):
244 continue
245 self.assertAccessUnauthorized(names, self.public_milestone)
246 self.assertAccessUnauthorized(
247@@ -270,12 +278,16 @@
248 self.proprietary_milestone)
249
250 # They have access to attributes requiring the permission
251- # launchpad.View or launchpad.AnyAllowedPerson of milestones
252- # for public products...
253+ # launchpad.View, launchpad.LimitedView or
254+ # launchpad.AnyAllowedPerson of milestones for public
255+ # products...
256 self.assertAccessAuthorized(
257 self.expected_get_permissions['launchpad.View'],
258 self.public_milestone)
259 self.assertAccessAuthorized(
260+ self.expected_get_permissions['launchpad.LimitedView'],
261+ self.public_milestone)
262+ self.assertAccessAuthorized(
263 self.expected_get_permissions['launchpad.AnyAllowedPerson'],
264 self.public_milestone)
265
266@@ -285,13 +297,16 @@
267 self.expected_get_permissions['launchpad.View'],
268 self.proprietary_milestone)
269 self.assertAccessUnauthorized(
270+ self.expected_get_permissions['launchpad.LimitedView'],
271+ self.proprietary_milestone)
272+ self.assertAccessUnauthorized(
273 self.expected_get_permissions['launchpad.AnyAllowedPerson'],
274 self.proprietary_milestone)
275
276 # They cannot access other attributes.
277 for permission, names in self.expected_get_permissions.items():
278 if permission in (
279- CheckerPublic, 'launchpad.View',
280+ CheckerPublic, 'launchpad.View', 'launchpad.LimitedView',
281 'launchpad.AnyAllowedPerson'):
282 continue
283 self.assertAccessUnauthorized(names, self.public_milestone)
284@@ -309,6 +324,42 @@
285 # to most attributes of the private product.
286 user = self.factory.makePerson()
287 with person_logged_in(self.proprietary_product_owner):
288+ bug = self.factory.makeBug(
289+ target=self.proprietary_product,
290+ owner=self.proprietary_product_owner)
291+ bug.subscribe(user, subscribed_by=self.proprietary_product_owner)
292+
293+ with person_logged_in(user):
294+ self.assertAccessAuthorized(
295+ self.expected_get_permissions[CheckerPublic],
296+ self.proprietary_milestone)
297+
298+ # They have access to attributes requiring the permission
299+ # launchpad.LimitedView of milestones for the private
300+ # product.
301+ self.assertAccessAuthorized(
302+ self.expected_get_permissions['launchpad.LimitedView'],
303+ self.proprietary_milestone)
304+
305+ # They cannot access other attributes.
306+ for permission, names in self.expected_get_permissions.items():
307+ if permission in (
308+ CheckerPublic, 'launchpad.LimitedView'):
309+ continue
310+ self.assertAccessUnauthorized(
311+ names, self.proprietary_milestone)
312+
313+ # They cannot change attributes.
314+ for names in self.expected_set_permissions.values():
315+ self.assertChangeUnauthorized(
316+ names, self.proprietary_milestone)
317+
318+ def test_access_for_user_with_artifact_grant_for_private_product(self):
319+ # Users with an artifact grant for a private product have access
320+ # to attributes requiring the permission launchpad.LimitedView of
321+ # milestones for the private product.
322+ user = self.factory.makePerson()
323+ with person_logged_in(self.proprietary_product_owner):
324 getUtility(IService, 'sharing').sharePillarInformation(
325 self.proprietary_product, user, self.proprietary_product_owner,
326 {InformationType.PROPRIETARY: SharingPermission.ALL})
327@@ -319,19 +370,23 @@
328 self.proprietary_milestone)
329
330 # They have access to attributes requiring the permission
331- # launchpad.View or launchpad.AnyAllowedPerson of milestones
332- # for the private product.
333+ # launchpad.View, launchpad.LimitedView or
334+ # launchpad.AnyAllowedPerson of milestones for the private
335+ # product.
336 self.assertAccessAuthorized(
337 self.expected_get_permissions['launchpad.View'],
338 self.proprietary_milestone)
339 self.assertAccessAuthorized(
340+ self.expected_get_permissions['launchpad.LimitedView'],
341+ self.proprietary_milestone)
342+ self.assertAccessAuthorized(
343 self.expected_get_permissions['launchpad.AnyAllowedPerson'],
344 self.proprietary_milestone)
345
346 # They cannot access other attributes.
347 for permission, names in self.expected_get_permissions.items():
348 if permission in (
349- CheckerPublic, 'launchpad.View',
350+ CheckerPublic, 'launchpad.View', 'launchpad.LimitedView',
351 'launchpad.AnyAllowedPerson'):
352 continue
353 self.assertAccessUnauthorized(
354
355=== modified file 'lib/lp/registry/tests/test_productseries.py'
356--- lib/lp/registry/tests/test_productseries.py 2012-11-30 20:52:15 +0000
357+++ lib/lp/registry/tests/test_productseries.py 2012-12-04 16:26:21 +0000
358@@ -622,6 +622,7 @@
359 'addSubscription', 'removeBugSubscription',
360 )),
361 'launchpad.Edit': set(('newMilestone', )),
362+ 'launchpad.LimitedView': set(('name', 'product', 'productID')),
363 'launchpad.View': set((
364 '_all_specifications', '_getOfficialTagClause',
365 '_valid_specifications', 'active', 'all_milestones',
366@@ -650,10 +651,10 @@
367 'has_obsolete_translation_templates',
368 'has_sharing_translation_templates', 'has_translation_files',
369 'has_translation_templates', 'is_development_focus', 'milestones',
370- 'name', 'official_bug_tags', 'owner', 'packagings', 'parent',
371+ 'official_bug_tags', 'owner', 'packagings', 'parent',
372 'parent_subscription_target',
373- 'personHasDriverRights', 'pillar', 'potemplate_count', 'product',
374- 'productID', 'productserieslanguages', 'release_files',
375+ 'personHasDriverRights', 'pillar', 'potemplate_count',
376+ 'productserieslanguages', 'release_files',
377 'releasefileglob', 'releases', 'releaseverstyle', 'searchTasks',
378 'series', 'setPackaging', 'sourcepackages', 'specifications',
379 'status', 'summary', 'target_type_display', 'title',
380@@ -733,21 +734,29 @@
381 self.proprietary_series)
382
383 # They have access to attributes requiring the permission
384- # launchpad.View of a series for a public product...
385+ # launchpad.Viewand launchpad.LimitedView of a series for a
386+ # public product...
387 self.assertAccessAuthorized(
388 self.expected_get_permissions['launchpad.View'],
389 self.public_series)
390+ self.assertAccessAuthorized(
391+ self.expected_get_permissions['launchpad.LimitedView'],
392+ self.public_series)
393
394 # ...but not to the same attributes of a series for s private
395 # product.
396 self.assertAccessUnauthorized(
397 self.expected_get_permissions['launchpad.View'],
398 self.proprietary_series)
399+ self.assertAccessUnauthorized(
400+ self.expected_get_permissions['launchpad.LimitedView'],
401+ self.proprietary_series)
402
403 # The cannot access other attributes of a series for
404 # public and private products.
405 for permission, names in self.expected_get_permissions.items():
406- if permission in (CheckerPublic, 'launchpad.View'):
407+ if permission in (CheckerPublic, 'launchpad.LimitedView',
408+ 'launchpad.View'):
409 continue
410 self.assertAccessUnauthorized(names, self.public_series)
411 self.assertAccessUnauthorized(names, self.proprietary_series)
412@@ -770,12 +779,15 @@
413 self.proprietary_series)
414
415 # They have access to attributes requiring the permissions
416- # launchpad.View and launchpadAnyAllowedPerson of a series
417- # for a public product...
418+ # launchpad.View, launchpad.LimitedView and
419+ # launchpadAnyAllowedPerson of a series for a public product...
420 self.assertAccessAuthorized(
421 self.expected_get_permissions['launchpad.View'],
422 self.public_series)
423 self.assertAccessAuthorized(
424+ self.expected_get_permissions['launchpad.LimitedView'],
425+ self.public_series)
426+ self.assertAccessAuthorized(
427 self.expected_get_permissions['launchpad.AnyAllowedPerson'],
428 self.public_series)
429
430@@ -785,13 +797,17 @@
431 self.expected_get_permissions['launchpad.View'],
432 self.proprietary_series)
433 self.assertAccessUnauthorized(
434+ self.expected_get_permissions['launchpad.LimitedView'],
435+ self.proprietary_series)
436+ self.assertAccessUnauthorized(
437 self.expected_get_permissions['launchpad.AnyAllowedPerson'],
438 self.proprietary_series)
439
440 # The cannot access other attributes of a series for
441 # public and private products.
442 for permission, names in self.expected_get_permissions.items():
443- if permission in (CheckerPublic, 'launchpad.View',
444+ if permission in (CheckerPublic, 'launchpad.LimitedView',
445+ 'launchpad.View',
446 'launchpad.AnyAllowedPerson'):
447 continue
448 self.assertAccessUnauthorized(names, self.public_series)
449@@ -817,8 +833,8 @@
450
451 def test_access_for_user_with_policy_grant(self):
452 # Users with a policy grant for the parent product can access
453- # properties requring the permission lanchpad.View or
454- # launchpad.ANyALlowedPerson of a series.
455+ # properties requring the permission launchpad.LimitedView,
456+ # launchpad.View or launchpad.AnyALlowedPerson of a series.
457 user = self.factory.makePerson()
458 with person_logged_in(self.proprietary_product_owner):
459 getUtility(IService, 'sharing').sharePillarInformation(
460@@ -826,6 +842,9 @@
461 {InformationType.PROPRIETARY: SharingPermission.ALL})
462 with person_logged_in(user):
463 self.assertAccessAuthorized(
464+ self.expected_get_permissions['launchpad.LimitedView'],
465+ self.proprietary_series)
466+ self.assertAccessAuthorized(
467 self.expected_get_permissions['launchpad.View'],
468 self.proprietary_series)
469 self.assertAccessAuthorized(
470@@ -836,6 +855,7 @@
471 # private products.
472 for permission, names in self.expected_get_permissions.items():
473 if permission in (CheckerPublic, 'launchpad.View',
474+ 'launchpad.LimitedView',
475 'launchpad.AnyAllowedPerson'):
476 continue
477 self.assertAccessUnauthorized(names, self.proprietary_series)
478@@ -853,6 +873,33 @@
479 continue
480 self.assertChangeUnauthorized(names, self.proprietary_series)
481
482+ def test_access_for_user_with_artifact_grant(self):
483+ # Users with an artifact grant related to the parent product
484+ # can access properties requring the permission launchpad.LimitedView
485+ # of a series.
486+ user = self.factory.makePerson()
487+ with person_logged_in(self.proprietary_product_owner):
488+ bug = self.factory.makeBug(
489+ target=self.proprietary_product,
490+ owner=self.proprietary_product_owner)
491+ bug.subscribe(user, subscribed_by=self.proprietary_product_owner)
492+
493+ with person_logged_in(user):
494+ self.assertAccessAuthorized(
495+ self.expected_get_permissions['launchpad.LimitedView'],
496+ self.proprietary_series)
497+
498+ # The cannot access other attributes of a series for
499+ # private products.
500+ for permission, names in self.expected_get_permissions.items():
501+ if permission in (CheckerPublic, 'launchpad.LimitedView'):
502+ continue
503+ self.assertAccessUnauthorized(names, self.proprietary_series)
504+
505+ # They cannot change any attributes.
506+ for permission, names in self.expected_set_permissions.items():
507+ self.assertChangeUnauthorized(names, self.proprietary_series)
508+
509 def test_access_for_product_owner(self):
510 # The owner of a project has access to all attributes of
511 # a product series.
512
513=== modified file 'lib/lp/security.py'
514--- lib/lp/security.py 2012-11-27 22:02:34 +0000
515+++ lib/lp/security.py 2012-12-04 16:26:21 +0000
516@@ -156,6 +156,7 @@
517 )
518 from lp.registry.interfaces.productseries import (
519 IProductSeries,
520+ IProductSeriesLimitedView,
521 IProductSeriesView,
522 ITimelineProductSeries,
523 )
524@@ -760,6 +761,15 @@
525 return False
526
527
528+class LimitedViewMilestone(DelegatedAuthorization):
529+ permission = 'launchpad.LimitedView'
530+ usedfor = IMilestone
531+
532+ def __init__(self, obj):
533+ super(LimitedViewMilestone, self).__init__(
534+ obj, obj.target, 'launchpad.LimitedView')
535+
536+
537 class ViewMilestone(AuthorizationBase):
538 permission = 'launchpad.View'
539 usedfor = IMilestone
540@@ -1319,6 +1329,15 @@
541 or False)
542
543
544+class LimitedViewProductSeries(DelegatedAuthorization):
545+ permission = 'launchpad.LimitedView'
546+ usedfor = IProductSeriesLimitedView
547+
548+ def __init__(self, obj):
549+ super(LimitedViewProductSeries, self).__init__(
550+ obj, obj.product, 'launchpad.LimitedView')
551+
552+
553 class ViewProductSeries(AuthorizationBase):
554 permission = 'launchpad.View'
555 usedfor = IProductSeriesView