Merge lp:~adeuring/launchpad/productseries-sec-adapter into lp:launchpad

Proposed by Abel Deuring
Status: Merged
Approved by: j.c.sackett
Approved revision: no longer in the source branch.
Merged at revision: 16174
Proposed branch: lp:~adeuring/launchpad/productseries-sec-adapter
Merge into: lp:launchpad
Diff against target: 735 lines (+462/-30) (has conflicts)
12 files modified
lib/lp/code/browser/tests/test_branchlisting.py (+2/-1)
lib/lp/registry/browser/tests/test_productseries_views.py (+51/-0)
lib/lp/registry/browser/tests/test_sourcepackage_views.py (+2/-1)
lib/lp/registry/configure.zcml (+12/-5)
lib/lp/registry/interfaces/productseries.py (+11/-5)
lib/lp/registry/model/packaging.py (+4/-0)
lib/lp/registry/model/productseries.py (+5/-0)
lib/lp/registry/tests/test_packaging.py (+18/-10)
lib/lp/registry/tests/test_productseries.py (+326/-1)
lib/lp/registry/tests/test_sourcepackage.py (+8/-3)
lib/lp/security.py (+21/-3)
lib/lp/translations/stories/webservice/xx-potemplate.txt (+2/-1)
Text conflict in lib/lp/registry/browser/tests/test_productseries_views.py
Text conflict in lib/lp/registry/tests/test_productseries.py
To merge this branch: bzr merge lp:~adeuring/launchpad/productseries-sec-adapter
Reviewer Review Type Date Requested Status
Deryck Hodge (community) Approve
j.c.sackett (community) Approve
Review via email: mp+130305@code.launchpad.net

Commit message

privacy aware security adapter for IProductSeries

Description of the change

This branch adds a "sharing aware" security adapter for IProductSeries.

Serieses of private products are now only shown to persons
with policy grants for the product; the only exception are some
attributes that do not leak any "real" information: the database ID,
and the method userCanView().

Details of the change:

lp/registry/configure.zcml:

Access to most attributes and to "partial" interfaces that were public
now requires the permission launchpad.View; the permission
launchpad.AnyPerson is replaced with launchapd.AnyAllowedPerson.

(lp.services.webapp.authorization.LaunchpadSecurityPolicy.checkPermission()
has a "shortcut" for the permission launchpad.AnyPerson: no
dedicated security adapters are looked up for this permission,
so the new rule "data for serieses of private products should only
be visible for persons having a policy grant" cannot be implemented
with this permission.)

lp/security.py:

The existing class ViewProductSeries derived AnonymousAuthorization.
This does not make sense anymore, instead the class now derives
AuthorizationBase and calls the new method ProductSeries,userCanView()
for real authorization check.

The new class ChangeProductSeries does the authorization check for
the permission launchpad.AnyAllowedPerson.

lp/registry/interfaces/productseries.py

The existing interface IProductSeriesPublic now defines only the DB ID
and the method userCanView(), all other attributes are defined in
the new class IProductSeriesView.

lp/registry/model/productseries.py:

The new method userCanView(). The actual permission check is done
by IProduct.userCanView().

lp/registry/tests/test_productseries.py:

Tests for the permissions.

The test class properties expected_get_permissions and
expected_set_permissions are also intended to document which permissions
are acutally used for IProductSeries.

test:

./bin/test registry -vvt lp.registry.tests.test_productseries.ProductSeriesSecurityAdaperTestCase

no lint

Update:

I'm running an EC2 test for this branch; several failures are
fixed in r 10250.

The tests lp.registry.tests.test_packaging.TestCreatePackaging.test_createPackaging_refuses_EMBARGOED
and lp.registry.tests.test_packaging.TestCreatePackaging.test_createPackaging_refuses_PROPRIETARY
failed in lp.registry.packaging.PackagingUtil.createPackaging() because
productseries.product is no longer available. To fix these tests, I added
the interfaces IInformationType to IProduct.

The other failures were either Unauthorized execption, or, in browser
tests, "AttributeError: 'thread._local' object has no attribute 'interaction'".

WHen a borwser instance is created, an existing interaction in terminated,
so that thread._local.interaction is no longer available outside a
a browser call -- but the current user is stored as an attribute of
interaction, and the new security tests require a check if this user has
access rights. The most easy fix is to access some required attribute
the broswer object is created.

additional tests:

lp.registry.tests.test_productseries.TestProductSeriesInformationType
lp.code.browser.tests.test_branchlisting.TestProductSeriesTemplate.test_product_series_link
lp.registry.browser.tests.test_sourcepackage_views.TestSourcePackageChangeUpstreamView.test_error_on_proprietary_productseries
lp.registry.tests.test_packaging.TestCreatePackaging.test_createPackaging_refuses_EMBARGOED
lp.registry.tests.test_packaging.TestCreatePackaging.test_createPackaging_refuses_PROPRIETARY
lp.registry.tests.test_sourcepackage.TestSourcePackage.test_refuses_PROPRIETARY

To post a comment you must log in.
Revision history for this message
j.c.sackett (jcsackett) wrote :

Looks alright. Thanks Abel.

review: Approve
Revision history for this message
Deryck Hodge (deryck) wrote :

Thanks for merging my work and using adaptation here. Looks good to me now!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/bugs/model/bug.py'
=== modified file 'lib/lp/code/browser/tests/test_branchlisting.py'
--- lib/lp/code/browser/tests/test_branchlisting.py 2012-10-08 07:15:07 +0000
+++ lib/lp/code/browser/tests/test_branchlisting.py 2012-10-19 08:28:25 +0000
@@ -596,10 +596,11 @@
596 # series on the main site, not the code site.596 # series on the main site, not the code site.
597 branch = self.factory.makeProductBranch()597 branch = self.factory.makeProductBranch()
598 series = self.factory.makeProductSeries(product=branch.product)598 series = self.factory.makeProductSeries(product=branch.product)
599 series_name = series.name
599 remove_security_proxy_and_shout_at_engineer(series).branch = branch600 remove_security_proxy_and_shout_at_engineer(series).branch = branch
600 browser = self.getUserBrowser(601 browser = self.getUserBrowser(
601 canonical_url(branch.product, rootsite='code'))602 canonical_url(branch.product, rootsite='code'))
602 link = browser.getLink(re.compile('^' + series.name + '$'))603 link = browser.getLink(re.compile('^' + series_name + '$'))
603 self.assertEqual('launchpad.dev', URI(link.url).host)604 self.assertEqual('launchpad.dev', URI(link.url).host)
604605
605606
606607
=== modified file 'lib/lp/registry/browser/tests/test_productseries_views.py'
--- lib/lp/registry/browser/tests/test_productseries_views.py 2012-10-18 16:56:54 +0000
+++ lib/lp/registry/browser/tests/test_productseries_views.py 2012-10-19 08:28:25 +0000
@@ -26,6 +26,7 @@
26from lp.testing.views import create_initialized_view26from lp.testing.views import create_initialized_view
2727
2828
29<<<<<<< TREE
29class TestProductSeries(BrowserTestCase):30class TestProductSeries(BrowserTestCase):
3031
31 layer = DatabaseFunctionalLayer32 layer = DatabaseFunctionalLayer
@@ -73,6 +74,56 @@
73 privacy_portlet_proprietary))74 privacy_portlet_proprietary))
7475
7576
77=======
78class TestProductSeries(BrowserTestCase):
79
80 layer = DatabaseFunctionalLayer
81
82 def test_information_type_public(self):
83 # A ProductSeries view should include its information_type,
84 # which defaults to Public for new projects.
85 series = self.factory.makeProductSeries()
86 view = create_initialized_view(series, '+index')
87 self.assertEqual('Public', view.information_type)
88
89 def test_information_type_proprietary(self):
90 # A ProductSeries view should get its information_type
91 # from the related product even if the product is changed to
92 # PROPRIETARY.
93 owner = self.factory.makePerson()
94 information_type = InformationType.PROPRIETARY
95 product = self.factory.makeProduct(
96 owner=owner, information_type=information_type)
97 series = self.factory.makeProductSeries(product=product)
98 with person_logged_in(owner):
99 view = create_initialized_view(series, '+index')
100 self.assertEqual('Proprietary', view.information_type)
101
102 def test_privacy_portlet(self):
103 # A ProductSeries page should include a privacy portlet that
104 # accurately describes the information_type.
105 owner = self.factory.makePerson()
106 information_type = InformationType.PROPRIETARY
107 product = self.factory.makeProduct(
108 owner=owner, information_type=information_type)
109 series = self.factory.makeProductSeries(product=product)
110 privacy_portlet = soupmatchers.Tag(
111 'info-type-portlet', 'span',
112 attrs={'id': 'information-type-summary'})
113 privacy_portlet_proprietary = soupmatchers.Tag(
114 'info-type-text', 'strong', attrs={'id': 'information-type'},
115 text='Proprietary')
116 browser = self.getViewBrowser(series, '+index', user=owner)
117 # First, assert that the portlet exists.
118 self.assertThat(
119 browser.contents, soupmatchers.HTMLContains(privacy_portlet))
120 # Then, assert that the text displayed matches the information_type.
121 self.assertThat(
122 browser.contents, soupmatchers.HTMLContains(
123 privacy_portlet_proprietary))
124
125
126>>>>>>> MERGE-SOURCE
76class TestProductSeriesHelp(TestCaseWithFactory):127class TestProductSeriesHelp(TestCaseWithFactory):
77 layer = DatabaseFunctionalLayer128 layer = DatabaseFunctionalLayer
78129
79130
=== modified file 'lib/lp/registry/browser/tests/test_sourcepackage_views.py'
--- lib/lp/registry/browser/tests/test_sourcepackage_views.py 2012-10-15 15:35:32 +0000
+++ lib/lp/registry/browser/tests/test_sourcepackage_views.py 2012-10-19 08:28:25 +0000
@@ -319,6 +319,7 @@
319 product = self.factory.makeProduct(319 product = self.factory.makeProduct(
320 name=product_name, owner=product_owner)320 name=product_name, owner=product_owner)
321 series = self.factory.makeProductSeries(product=product)321 series = self.factory.makeProductSeries(product=product)
322 series_displayname = series.displayname
322 ubuntu_series = self.factory.makeUbuntuDistroSeries()323 ubuntu_series = self.factory.makeUbuntuDistroSeries()
323 sp = self.factory.makeSourcePackage(distroseries=ubuntu_series)324 sp = self.factory.makeSourcePackage(distroseries=ubuntu_series)
324 browser = self.getViewBrowser(325 browser = self.getViewBrowser(
@@ -327,7 +328,7 @@
327 browser.getControl('Continue').click()328 browser.getControl('Continue').click()
328 with person_logged_in(product_owner):329 with person_logged_in(product_owner):
329 product.information_type = InformationType.PROPRIETARY330 product.information_type = InformationType.PROPRIETARY
330 browser.getControl(series.displayname).selected = True331 browser.getControl(series_displayname).selected = True
331 browser.getControl('Change').click()332 browser.getControl('Change').click()
332 self.assertIn(333 self.assertIn(
333 'Only Public projects can be packaged, not Proprietary.',334 'Only Public projects can be packaged, not Proprietary.',
334335
=== modified file 'lib/lp/registry/configure.zcml'
--- lib/lp/registry/configure.zcml 2012-10-17 20:18:20 +0000
+++ lib/lp/registry/configure.zcml 2012-10-19 08:28:25 +0000
@@ -1508,10 +1508,16 @@
1508 <allow1508 <allow
1509 interface="lp.registry.interfaces.productseries.IProductSeriesPublic"/>1509 interface="lp.registry.interfaces.productseries.IProductSeriesPublic"/>
1510 <require1510 <require
1511 permission="launchpad.View"
1512 interface="lp.registry.interfaces.productseries.IProductSeriesView"/>
1513 <require
1511 permission="launchpad.Edit"1514 permission="launchpad.Edit"
1512 interface="lp.registry.interfaces.productseries.IProductSeriesEditRestricted"/>1515 interface="lp.registry.interfaces.productseries.IProductSeriesEditRestricted"/>
1513 <allow interface="lp.bugs.interfaces.bugsummary.IBugSummaryDimension"/>1516 <require
1514 <allow1517 permission="launchpad.View"
1518 interface="lp.bugs.interfaces.bugsummary.IBugSummaryDimension"/>
1519 <require
1520 permission="launchpad.View"
1515 interface="lp.bugs.interfaces.bugtarget.ISeriesBugTarget"/>1521 interface="lp.bugs.interfaces.bugtarget.ISeriesBugTarget"/>
1516 <require1522 <require
1517 permission="launchpad.Edit"1523 permission="launchpad.Edit"
@@ -1522,16 +1528,17 @@
1522 translations_branch status releasefileglob1528 translations_branch status releasefileglob
1523 translations_autoimport_mode"/>1529 translations_autoimport_mode"/>
1524 <require1530 <require
1525 permission="launchpad.AnyPerson"1531 permission="launchpad.AnyAllowedPerson"
1526 set_attributes="importstatus rcstype cvsroot cvsmodule1532 set_attributes="importstatus rcstype cvsroot cvsmodule
1527 cvstarfileurl cvsbranch svnrepository"/>1533 cvstarfileurl cvsbranch svnrepository"/>
15281534
1529 <!-- IStructuralSubscriptionTarget -->1535 <!-- IStructuralSubscriptionTarget -->
15301536
1531 <allow1537 <require
1538 permission="launchpad.View"
1532 interface="lp.bugs.interfaces.structuralsubscription.IStructuralSubscriptionTargetRead" />1539 interface="lp.bugs.interfaces.structuralsubscription.IStructuralSubscriptionTargetRead" />
1533 <require1540 <require
1534 permission="launchpad.AnyPerson"1541 permission="launchpad.AnyAllowedPerson"
1535 interface="lp.bugs.interfaces.structuralsubscription.IStructuralSubscriptionTargetWrite" />1542 interface="lp.bugs.interfaces.structuralsubscription.IStructuralSubscriptionTargetWrite" />
15361543
1537 </class>1544 </class>
15381545
=== modified file 'lib/lp/registry/interfaces/productseries.py'
--- lib/lp/registry/interfaces/productseries.py 2012-10-06 23:40:20 +0000
+++ lib/lp/registry/interfaces/productseries.py 2012-10-19 08:28:25 +0000
@@ -12,6 +12,7 @@
12 'IProductSeriesEditRestricted',12 'IProductSeriesEditRestricted',
13 'IProductSeriesPublic',13 'IProductSeriesPublic',
14 'IProductSeriesSet',14 'IProductSeriesSet',
15 'IProductSeriesView',
15 'NoSuchProductSeries',16 'NoSuchProductSeries',
16 'ITimelineProductSeries',17 'ITimelineProductSeries',
17 ]18 ]
@@ -129,13 +130,18 @@
129 """Create a new milestone for this ProjectSeries."""130 """Create a new milestone for this ProjectSeries."""
130131
131132
132class IProductSeriesPublic(133class IProductSeriesPublic(Interface):
134 """Public IProductSeries properties."""
135 id = Int(title=_('ID'))
136
137 def userCanView(user):
138 """True if the given user has access to this product."""
139
140
141class IProductSeriesView(
133 ISeriesMixin, IHasAppointedDriver, IHasOwner, IBugTarget,142 ISeriesMixin, IHasAppointedDriver, IHasOwner, IBugTarget,
134 ISpecificationGoal, IHasMilestones, IHasOfficialBugTags, IHasExpirableBugs,143 ISpecificationGoal, IHasMilestones, IHasOfficialBugTags, IHasExpirableBugs,
135 IHasTranslationImports, IHasTranslationTemplates, IServiceUsage):144 IHasTranslationImports, IHasTranslationTemplates, IServiceUsage):
136 """Public IProductSeries properties."""
137 id = Int(title=_('ID'))
138
139 product = exported(145 product = exported(
140 ReferenceChoice(title=_('Project'), required=True,146 ReferenceChoice(title=_('Project'), required=True,
141 vocabulary='Product', schema=Interface), # really IProduct147 vocabulary='Product', schema=Interface), # really IProduct
@@ -330,7 +336,7 @@
330336
331337
332class IProductSeries(IProductSeriesEditRestricted, IProductSeriesPublic,338class IProductSeries(IProductSeriesEditRestricted, IProductSeriesPublic,
333 IStructuralSubscriptionTarget):339 IProductSeriesView, IStructuralSubscriptionTarget):
334 """A series of releases. For example '2.0' or '1.3' or 'dev'."""340 """A series of releases. For example '2.0' or '1.3' or 'dev'."""
335 export_as_webservice_entry('project_series')341 export_as_webservice_entry('project_series')
336342
337343
=== modified file 'lib/lp/registry/model/packaging.py'
--- lib/lp/registry/model/packaging.py 2012-10-12 19:37:30 +0000
+++ lib/lp/registry/model/packaging.py 2012-10-19 08:28:25 +0000
@@ -111,6 +111,10 @@
111 (sourcepackagename.name, distroseries.name))111 (sourcepackagename.name, distroseries.name))
112 # XXX: AaronBentley: 2012-08-12 bug=1066063 Cannot adapt ProductSeries112 # XXX: AaronBentley: 2012-08-12 bug=1066063 Cannot adapt ProductSeries
113 # to IInformationType.113 # to IInformationType.
114 # The line below causes a failure of
115 # lp.registry.tests.test_distroseries.TestDistroSeriesPackaging.
116 # test_getPrioritizedPackagings_bug_tracker because
117 # productseries.product loses all set permissions.
114 # info_type = IInformationType(productseries).information_type118 # info_type = IInformationType(productseries).information_type
115 info_type = productseries.product.information_type119 info_type = productseries.product.information_type
116 if info_type != InformationType.PUBLIC:120 if info_type != InformationType.PUBLIC:
117121
=== modified file 'lib/lp/registry/model/productseries.py'
--- lib/lp/registry/model/productseries.py 2012-10-11 15:01:49 +0000
+++ lib/lp/registry/model/productseries.py 2012-10-19 08:28:25 +0000
@@ -679,6 +679,11 @@
679 return OrderedBugTask(3, bugtask.id, bugtask)679 return OrderedBugTask(3, bugtask.id, bugtask)
680 return weight_function680 return weight_function
681681
682 def userCanView(self, user):
683 """See `IproductSeriesPublic`."""
684 # Deleate the permission check to the parent product.
685 return self.product.userCanView(user)
686
682687
683class TimelineProductSeries:688class TimelineProductSeries:
684 """See `ITimelineProductSeries`."""689 """See `ITimelineProductSeries`."""
685690
=== modified file 'lib/lp/registry/tests/test_packaging.py'
--- lib/lp/registry/tests/test_packaging.py 2012-10-11 16:06:49 +0000
+++ lib/lp/registry/tests/test_packaging.py 2012-10-19 08:28:25 +0000
@@ -174,25 +174,33 @@
174174
175 def test_createPackaging_refuses_PROPRIETARY(self):175 def test_createPackaging_refuses_PROPRIETARY(self):
176 """Packaging cannot be created for PROPRIETARY productseries"""176 """Packaging cannot be created for PROPRIETARY productseries"""
177 owner = self.factory.makePerson()
177 product = self.factory.makeProduct(178 product = self.factory.makeProduct(
179 owner=owner,
178 information_type=InformationType.PROPRIETARY)180 information_type=InformationType.PROPRIETARY)
179 series = self.factory.makeProductSeries(product=product)181 series = self.factory.makeProductSeries(product=product)
180 with ExpectedException(CannotPackageProprietaryProduct,182 expected_message = (
181 'Only Public project series can be packaged, not Proprietary.'):183 'Only Public project series can be packaged, not Proprietary.')
182 self.packaging_util.createPackaging(184 with person_logged_in(owner):
183 series, self.sourcepackagename, self.distroseries,185 with ExpectedException(CannotPackageProprietaryProduct,
184 PackagingType.PRIME, owner=self.owner)186 expected_message):
187 self.packaging_util.createPackaging(
188 series, self.sourcepackagename, self.distroseries,
189 PackagingType.PRIME, owner=self.owner)
185190
186 def test_createPackaging_refuses_EMBARGOED(self):191 def test_createPackaging_refuses_EMBARGOED(self):
187 """Packaging cannot be created for EMBARGOED productseries"""192 """Packaging cannot be created for EMBARGOED productseries"""
193 owner = self.factory.makePerson()
188 product = self.factory.makeProduct(194 product = self.factory.makeProduct(
195 owner=owner,
189 information_type=InformationType.EMBARGOED)196 information_type=InformationType.EMBARGOED)
190 series = self.factory.makeProductSeries(product=product)197 series = self.factory.makeProductSeries(product=product)
191 with ExpectedException(CannotPackageProprietaryProduct,198 with person_logged_in(owner):
192 'Only Public project series can be packaged, not Embargoed.'):199 with ExpectedException(CannotPackageProprietaryProduct,
193 self.packaging_util.createPackaging(200 'Only Public project series can be packaged, not Embargoed.'):
194 series, self.sourcepackagename, self.distroseries,201 self.packaging_util.createPackaging(
195 PackagingType.PRIME, owner=self.owner)202 series, self.sourcepackagename, self.distroseries,
203 PackagingType.PRIME, owner=self.owner)
196204
197205
198class TestPackagingEntryExists(PackagingUtilMixin, TestCaseWithFactory):206class TestPackagingEntryExists(PackagingUtilMixin, TestCaseWithFactory):
199207
=== modified file 'lib/lp/registry/tests/test_productseries.py'
--- lib/lp/registry/tests/test_productseries.py 2012-10-16 15:12:09 +0000
+++ lib/lp/registry/tests/test_productseries.py 2012-10-19 08:28:25 +0000
@@ -5,13 +5,25 @@
55
6__metaclass__ = type6__metaclass__ = type
77
8from storm.exceptions import NoneError
8from testtools.testcase import ExpectedException9from testtools.testcase import ExpectedException
9import transaction10import transaction
11from zope.security.checker import (
12 CheckerPublic,
13 getChecker,
14 )
10from zope.component import getUtility15from zope.component import getUtility
16from zope.security.interfaces import Unauthorized
11from zope.security.proxy import removeSecurityProxy17from zope.security.proxy import removeSecurityProxy
1218
13from lp.app.enums import InformationType19from lp.app.enums import InformationType
14from lp.app.interfaces.informationtype import IInformationType20<<<<<<< TREE
21from lp.app.interfaces.informationtype import IInformationType
22=======
23from lp.app.interfaces.informationtype import IInformationType
24from lp.app.interfaces.services import IService
25from lp.registry.enums import SharingPermission
26>>>>>>> MERGE-SOURCE
15from lp.registry.errors import CannotPackageProprietaryProduct27from lp.registry.errors import CannotPackageProprietaryProduct
16from lp.registry.interfaces.distribution import IDistributionSet28from lp.registry.interfaces.distribution import IDistributionSet
17from lp.registry.interfaces.distroseries import IDistroSeriesSet29from lp.registry.interfaces.distroseries import IDistroSeriesSet
@@ -22,7 +34,10 @@
22from lp.registry.interfaces.series import SeriesStatus34from lp.registry.interfaces.series import SeriesStatus
23from lp.services.database.lpstorm import IStore35from lp.services.database.lpstorm import IStore
24from lp.testing import (36from lp.testing import (
37 ANONYMOUS,
38 celebrity_logged_in,
25 login,39 login,
40 person_logged_in,
26 TestCaseWithFactory,41 TestCaseWithFactory,
27 WebServiceTestCase,42 WebServiceTestCase,
28 )43 )
@@ -37,6 +52,7 @@
37 )52 )
3853
3954
55<<<<<<< TREE
4056
41class TestProductSeries(TestCaseWithFactory):57class TestProductSeries(TestCaseWithFactory):
42 """Tests for ProductSeries."""58 """Tests for ProductSeries."""
@@ -54,6 +70,25 @@
54 IInformationType(series).information_type, information_type)70 IInformationType(series).information_type, information_type)
5571
5672
73=======
74class TestProductSeries(TestCaseWithFactory):
75 """Tests for ProductSeries."""
76
77 layer = DatabaseFunctionalLayer
78
79 def test_information_type_from_product(self):
80 # ProductSeries should inherit information_type from its product."""
81 owner = self.factory.makePerson()
82 information_type = InformationType.PROPRIETARY
83 product = self.factory.makeProduct(
84 owner=owner, information_type=information_type)
85 series = self.factory.makeProductSeries(product=product)
86 with person_logged_in(owner):
87 self.assertEqual(
88 IInformationType(series).information_type, information_type)
89
90
91>>>>>>> MERGE-SOURCE
57class ProductSeriesReleasesTestCase(TestCaseWithFactory):92class ProductSeriesReleasesTestCase(TestCaseWithFactory):
58 """Test for ProductSeries.release property."""93 """Test for ProductSeries.release property."""
5994
@@ -570,3 +605,293 @@
570 mode = TranslationsBranchImportMode.IMPORT_TRANSLATIONS605 mode = TranslationsBranchImportMode.IMPORT_TRANSLATIONS
571 ws_series.translations_autoimport_mode = mode.title606 ws_series.translations_autoimport_mode = mode.title
572 ws_series.lp_save()607 ws_series.lp_save()
608
609
610class ProductSeriesSecurityAdaperTestCase(TestCaseWithFactory):
611 """Test for permissions of IProductSeries."""
612
613 layer = DatabaseFunctionalLayer
614
615 def setUp(self):
616 super(ProductSeriesSecurityAdaperTestCase, self).setUp()
617 self.public_product = self.factory.makeProduct()
618 self.public_series = self.factory.makeProductSeries(
619 product=self.public_product)
620 self.proprietary_product_owner = self.factory.makePerson()
621 self.proprietary_product = self.factory.makeProduct(
622 owner=self.proprietary_product_owner,
623 information_type=InformationType.PROPRIETARY)
624 self.proprietary_series = self.factory.makeProductSeries(
625 product=self.proprietary_product)
626
627 expected_get_permissions = {
628 CheckerPublic: set((
629 'id', 'userCanView',
630 )),
631 'launchpad.AnyAllowedPerson': set((
632 'addBugSubscription', 'addBugSubscriptionFilter',
633 'addSubscription', 'removeBugSubscription',
634 )),
635 'launchpad.Edit': set(('newMilestone', )),
636 'launchpad.View': set((
637 '_all_specifications', '_getOfficialTagClause',
638 '_valid_specifications', 'active', 'all_milestones',
639 'answers_usage', 'blueprints_usage', 'branch',
640 'bug_reported_acknowledgement', 'bug_reporting_guidelines',
641 'bug_subscriptions', 'bug_supervisor', 'bug_tracking_usage',
642 'bugtargetdisplayname', 'bugtarget_parent', 'bugtargetname',
643 'codehosting_usage', 'createBug', 'datecreated', 'displayname',
644 'driver', 'drivers', 'enable_bugfiling_duplicate_search',
645 'getAllowedSpecificationInformationTypes',
646 'getBugSummaryContextWhereClause',
647 'getBugTaskWeightFunction', 'getCachedReleases',
648 'getCurrentTemplatesCollection', 'getCurrentTranslationFiles',
649 'getCurrentTranslationTemplates',
650 'getDefaultSpecificationInformationType',
651 'getFirstEntryToImport', 'getLatestRelease', 'getPOTemplate',
652 'getPackage', 'getPackagingInDistribution', 'getRelease',
653 'getSharingPartner', 'getSpecification', 'getSubscription',
654 'getSubscriptions', 'getTemplatesAndLanguageCounts',
655 'getTemplatesCollection', 'getTimeline',
656 'getTranslationImportQueueEntries',
657 'getTranslationTemplateByName', 'getTranslationTemplateFormats',
658 'getTranslationTemplates', 'getUbuntuTranslationFocusPackage',
659 'getUsedBugTagsWithOpenCounts',
660 'has_current_translation_templates', 'has_milestones',
661 'has_obsolete_translation_templates',
662 'has_sharing_translation_templates', 'has_translation_files',
663 'has_translation_templates', 'is_development_focus', 'milestones',
664 'name', 'official_bug_tags', 'owner', 'packagings', 'parent',
665 'parent_subscription_target',
666 'personHasDriverRights', 'pillar', 'potemplate_count', 'product',
667 'productID', 'productserieslanguages', 'release_files',
668 'releasefileglob', 'releases', 'releaseverstyle', 'searchTasks',
669 'series', 'setPackaging', 'sourcepackages', 'specifications',
670 'status', 'summary', 'target_type_display', 'title',
671 'translations_autoimport_mode', 'userCanAlterBugSubscription',
672 'userCanAlterSubscription', 'userHasBugSubscriptions',
673 'translations_branch', 'translations_usage', 'uses_launchpad',
674 )),
675 }
676
677 def test_get_permissions(self):
678 checker = getChecker(self.public_series)
679 self.checkPermissions(
680 self.expected_get_permissions, checker.get_permissions, 'get')
681
682 expected_set_permissions = {
683 'launchpad.Edit': set((
684 'answers_usage', 'blueprints_usage', 'branch',
685 'bug_tracking_usage', 'codehosting_usage', 'driver', 'name',
686 'owner', 'product', 'releasefileglob', 'status', 'summary',
687 'translations_autoimport_mode', 'translations_branch',
688 'translations_usage', 'uses_launchpad',
689 )),
690 'launchpad.AnyAllowedPerson': set((
691 'cvsbranch', 'cvsmodule', 'cvsroot', 'cvstarfileurl',
692 'importstatus', 'rcstype', 'svnrepository',
693 )),
694 }
695
696 def test_set_permissions(self):
697 checker = getChecker(self.public_series)
698 self.checkPermissions(
699 self.expected_set_permissions, checker.set_permissions, 'set')
700
701 def assertAccessAuthorized(self, attribute_names, obj):
702 # Try to access the given attributes of obj. No exception
703 # should be raised.
704 for name in attribute_names:
705 getattr(obj, name)
706
707 def assertAccessUnauthorized(self, attribute_names, obj):
708 # Try to access the given attributes of obj. Unauthorized
709 # should be raised.
710 for name in attribute_names:
711 self.assertRaises(Unauthorized, getattr, obj, name)
712
713 def assertChangeAuthorized(self, attribute_names, obj):
714 # Try to changes the given attributes of obj. Unauthorized
715 # should not be raised.
716 for name in attribute_names:
717 # Not all attributes declared in configure.zcml to be
718 # settable actually exist or are settable. Attempts to set
719 # them raise an AttributeError. Similary, the naive attempt
720 # to set an attribute to None may raise a NoneError
721 #
722 # Both errors can be ignored here: This method intends only
723 # to prove that Unauthorized is not raised.
724 try:
725 setattr(obj, name, None)
726 except (AttributeError, NoneError):
727 pass
728
729 def assertChangeUnauthorized(self, attribute_names, obj):
730 # Try to changes the given attributes of obj. Unauthorized
731 # should be raised.
732 for name in attribute_names:
733 self.assertRaises(Unauthorized, setattr, obj, name, None)
734
735 def test_access_for_anonymous(self):
736 # Anonymous users have access to public attributes of
737 # a series for a private or public product.
738 with person_logged_in(ANONYMOUS):
739 self.assertAccessAuthorized(
740 self.expected_get_permissions[CheckerPublic],
741 self.public_series)
742 self.assertAccessAuthorized(
743 self.expected_get_permissions[CheckerPublic],
744 self.proprietary_series)
745
746 # They have access to attributes requiring the permission
747 # launchpad.View of a series for a public product...
748 self.assertAccessAuthorized(
749 self.expected_get_permissions['launchpad.View'],
750 self.public_series)
751
752 # ...but not to the same attributes of a series for s private
753 # product.
754 self.assertAccessUnauthorized(
755 self.expected_get_permissions['launchpad.View'],
756 self.proprietary_series)
757
758 # The cannot access other attributes of a series for
759 # public and private products.
760 for permission, names in self.expected_get_permissions.items():
761 if permission in (CheckerPublic, 'launchpad.View'):
762 continue
763 self.assertAccessUnauthorized(names, self.public_series)
764 self.assertAccessUnauthorized(names, self.proprietary_series)
765
766 # They cannot change any attributes.
767 for permission, names in self.expected_set_permissions.items():
768 self.assertChangeUnauthorized(names, self.public_series)
769 self.assertChangeUnauthorized(names, self.proprietary_series)
770
771 def test_access_for_regular_user(self):
772 # Regular users have access to public attributes of
773 # a series for a private or public product.
774 user = self.factory.makePerson()
775 with person_logged_in(user):
776 self.assertAccessAuthorized(
777 self.expected_get_permissions[CheckerPublic],
778 self.public_series)
779 self.assertAccessAuthorized(
780 self.expected_get_permissions[CheckerPublic],
781 self.proprietary_series)
782
783 # They have access to attributes requiring the permissions
784 # launchpad.View and launchpadAnyAllowedPerson of a series
785 # for a public product...
786 self.assertAccessAuthorized(
787 self.expected_get_permissions['launchpad.View'],
788 self.public_series)
789 self.assertAccessAuthorized(
790 self.expected_get_permissions['launchpad.AnyAllowedPerson'],
791 self.public_series)
792
793 # ...but not to the same attributes of a series for s private
794 # product.
795 self.assertAccessUnauthorized(
796 self.expected_get_permissions['launchpad.View'],
797 self.proprietary_series)
798 self.assertAccessUnauthorized(
799 self.expected_get_permissions['launchpad.AnyAllowedPerson'],
800 self.proprietary_series)
801
802 # The cannot access other attributes of a series for
803 # public and private products.
804 for permission, names in self.expected_get_permissions.items():
805 if permission in (CheckerPublic, 'launchpad.View',
806 'launchpad.AnyAllowedPerson'):
807 continue
808 self.assertAccessUnauthorized(names, self.public_series)
809 self.assertAccessUnauthorized(names, self.proprietary_series)
810
811 # They can change attributes requiring the permission
812 # launchpad.AnyAllowedPerson of a series for a public project...
813 self.assertChangeAuthorized(
814 self.expected_set_permissions['launchpad.AnyAllowedPerson'],
815 self.public_series)
816 #... but not for a private project.
817 self.assertChangeUnauthorized(
818 self.expected_set_permissions['launchpad.AnyAllowedPerson'],
819 self.proprietary_series)
820
821 # They cannot change any attributes that require another
822 # permission than launchpad.AnyALlowedPerson.
823 for permission, names in self.expected_set_permissions.items():
824 if permission == 'launchpad.AnyAllowedPerson':
825 continue
826 self.assertChangeUnauthorized(names, self.public_series)
827 self.assertChangeUnauthorized(names, self.proprietary_series)
828
829 def test_access_for_user_with_policy_grant(self):
830 # Users with a policy grant for the parent product can access
831 # properties requring the permission lanchpad.View or
832 # launchpad.ANyALlowedPerson of a series.
833 user = self.factory.makePerson()
834 with person_logged_in(self.proprietary_product_owner):
835 getUtility(IService, 'sharing').sharePillarInformation(
836 self.proprietary_product, user, self.proprietary_product_owner,
837 {InformationType.PROPRIETARY: SharingPermission.ALL})
838 with person_logged_in(user):
839 self.assertAccessAuthorized(
840 self.expected_get_permissions['launchpad.View'],
841 self.proprietary_series)
842 self.assertAccessAuthorized(
843 self.expected_get_permissions['launchpad.AnyAllowedPerson'],
844 self.proprietary_series)
845
846 # The cannot access other attributes of a series for
847 # private products.
848 for permission, names in self.expected_get_permissions.items():
849 if permission in (CheckerPublic, 'launchpad.View',
850 'launchpad.AnyAllowedPerson'):
851 continue
852 self.assertAccessUnauthorized(names, self.proprietary_series)
853
854 # They can change attributes requiring the permission
855 # launchpad.AnyAllowedPerson of a series for a provate project...
856 self.assertChangeAuthorized(
857 self.expected_set_permissions['launchpad.AnyAllowedPerson'],
858 self.proprietary_series)
859
860 # They cannot change any attributes that require another
861 # permission than launchpad.AnyALlowedPerson.
862 for permission, names in self.expected_set_permissions.items():
863 if permission == 'launchpad.AnyAllowedPerson':
864 continue
865 self.assertChangeUnauthorized(names, self.proprietary_series)
866
867 def test_access_for_product_owner(self):
868 # The owner of a project has access to all attributes of
869 # a product series.
870 with person_logged_in(self.proprietary_product_owner):
871 for permission, names in self.expected_get_permissions.items():
872 self.assertAccessAuthorized(names, self.proprietary_series)
873
874 # They can change all attributes.
875 for permission, names in self.expected_set_permissions.items():
876 self.assertChangeAuthorized(names, self.proprietary_series)
877
878 with person_logged_in(self.public_product.owner):
879 for permission, names in self.expected_get_permissions.items():
880 self.assertAccessAuthorized(names, self.public_series)
881
882 # They can change all attributes.
883 for permission, names in self.expected_set_permissions.items():
884 self.assertChangeAuthorized(names, self.public_series)
885
886 def test_access_for_lp_admins(self):
887 # Launchpad admins can access and change any attribute of a series
888 # of public and private product.
889 with celebrity_logged_in('admin'):
890 for permission, names in self.expected_get_permissions.items():
891 self.assertAccessAuthorized(names, self.public_series)
892 self.assertAccessAuthorized(names, self.proprietary_series)
893
894 # They can change all attributes.
895 for permission, names in self.expected_set_permissions.items():
896 self.assertChangeAuthorized(names, self.public_series)
897 self.assertChangeAuthorized(names, self.proprietary_series)
573898
=== modified file 'lib/lp/registry/tests/test_sourcepackage.py'
--- lib/lp/registry/tests/test_sourcepackage.py 2012-10-11 18:32:36 +0000
+++ lib/lp/registry/tests/test_sourcepackage.py 2012-10-19 08:28:25 +0000
@@ -327,14 +327,19 @@
327327
328 def test_refuses_PROPRIETARY(self):328 def test_refuses_PROPRIETARY(self):
329 """Packaging cannot be created for PROPRIETARY productseries"""329 """Packaging cannot be created for PROPRIETARY productseries"""
330 owner = self.factory.makePerson()
330 product = self.factory.makeProduct(331 product = self.factory.makeProduct(
332 owner=owner,
331 information_type=InformationType.PROPRIETARY)333 information_type=InformationType.PROPRIETARY)
332 series = self.factory.makeProductSeries(product=product)334 series = self.factory.makeProductSeries(product=product)
333 ubuntu_series = self.factory.makeUbuntuDistroSeries()335 ubuntu_series = self.factory.makeUbuntuDistroSeries()
334 sp = self.factory.makeSourcePackage(distroseries=ubuntu_series)336 sp = self.factory.makeSourcePackage(distroseries=ubuntu_series)
335 with ExpectedException(CannotPackageProprietaryProduct,337 with person_logged_in(owner):
336 'Only Public project series can be packaged, not Proprietary.'):338 with ExpectedException(
337 sp.setPackaging(series, series.owner)339 CannotPackageProprietaryProduct,
340 'Only Public project series can be packaged, not '
341 'Proprietary.'):
342 sp.setPackaging(series, owner)
338343
339 def test_setPackagingReturnSharingDetailPermissions__ordinary_user(self):344 def test_setPackagingReturnSharingDetailPermissions__ordinary_user(self):
340 """An ordinary user can create a packaging link but he cannot345 """An ordinary user can create a packaging link but he cannot
341346
=== modified file 'lib/lp/security.py'
--- lib/lp/security.py 2012-10-18 14:43:24 +0000
+++ lib/lp/security.py 2012-10-19 08:28:25 +0000
@@ -154,6 +154,7 @@
154 )154 )
155from lp.registry.interfaces.productseries import (155from lp.registry.interfaces.productseries import (
156 IProductSeries,156 IProductSeries,
157 IProductSeriesView,
157 ITimelineProductSeries,158 ITimelineProductSeries,
158 )159 )
159from lp.registry.interfaces.projectgroup import (160from lp.registry.interfaces.projectgroup import (
@@ -1285,9 +1286,26 @@
1285 or False)1286 or False)
12861287
12871288
1288class ViewProductSeries(AnonymousAuthorization):1289class ViewProductSeries(AuthorizationBase):
12891290 permission = 'launchpad.View'
1290 usedfor = IProductSeries1291 usedfor = IProductSeriesView
1292
1293 def checkAuthenticated(self, user):
1294 return self.obj.userCanView(user)
1295
1296 def checkUnauthenticated(self):
1297 return self.obj.userCanView(None)
1298
1299
1300class ChangeProductSeries(ViewProductSeries):
1301 permission = 'launchpad.AnyAllowedPerson'
1302 usedfor = IProductSeriesView
1303
1304 def checkAuthenticated(self, user):
1305 return self.obj.userCanView(user)
1306
1307 def checkUnauthenticated(self):
1308 return False
12911309
12921310
1293class EditProductSeries(EditByOwnersOrAdmins):1311class EditProductSeries(EditByOwnersOrAdmins):
12941312
=== modified file 'lib/lp/services/features/flags.py'
=== modified file 'lib/lp/translations/stories/webservice/xx-potemplate.txt'
--- lib/lp/translations/stories/webservice/xx-potemplate.txt 2012-10-09 10:28:02 +0000
+++ lib/lp/translations/stories/webservice/xx-potemplate.txt 2012-10-19 08:28:25 +0000
@@ -71,6 +71,7 @@
7171
72 >>> login('admin@canonical.com')72 >>> login('admin@canonical.com')
73 >>> productseries = factory.makeProductSeries()73 >>> productseries = factory.makeProductSeries()
74 >>> productseries_name = productseries.name
74 >>> product_name = productseries.product.name75 >>> product_name = productseries.product.name
75 >>> potemplate_1 = factory.makePOTemplate(productseries=productseries)76 >>> potemplate_1 = factory.makePOTemplate(productseries=productseries)
76 >>> potemplate_2 = factory.makePOTemplate(productseries=productseries)77 >>> potemplate_2 = factory.makePOTemplate(productseries=productseries)
@@ -79,7 +80,7 @@
79 >>> all_translation_templates = anon_webservice.named_get(80 >>> all_translation_templates = anon_webservice.named_get(
80 ... '/%s/%s' % (81 ... '/%s/%s' % (
81 ... product_name,82 ... product_name,
82 ... productseries.name),83 ... productseries_name),
83 ... 'getTranslationTemplates'84 ... 'getTranslationTemplates'
84 ... ).jsonBody()85 ... ).jsonBody()
85 >>> api_count = all_translation_templates['total_size']86 >>> api_count = all_translation_templates['total_size']