Merge lp:~adeuring/launchpad/productseries-sec-adapter into lp:launchpad
- productseries-sec-adapter
- Merge into devel
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 |
Related bugs: |
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/
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.
(lp.services.
has a "shortcut" for the permission launchpad.
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 AnonymousAuthor
This does not make sense anymore, instead the class now derives
AuthorizationBase and calls the new method ProductSeries,
for real authorization check.
The new class ChangeProductSeries does the authorization check for
the permission launchpad.
lp/registry/
The existing interface IProductSeriesP
and the method userCanView(), all other attributes are defined in
the new class IProductSeriesView.
lp/registry/
The new method userCanView(). The actual permission check is done
by IProduct.
lp/registry/
Tests for the permissions.
The test class properties expected_
expected_
are acutally used for IProductSeries.
test:
./bin/test registry -vvt lp.registry.
no lint
Update:
I'm running an EC2 test for this branch; several failures are
fixed in r 10250.
The tests lp.registry.
and lp.registry.
failed in lp.registry.
productseries.
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.
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.
lp.code.
lp.registry.
lp.registry.
lp.registry.
lp.registry.
Deryck Hodge (deryck) wrote : | # |
Thanks for merging my work and using adaptation here. Looks good to me now!
Preview Diff
1 | === modified file 'lib/lp/bugs/model/bug.py' | |||
2 | === modified file 'lib/lp/code/browser/tests/test_branchlisting.py' | |||
3 | --- lib/lp/code/browser/tests/test_branchlisting.py 2012-10-08 07:15:07 +0000 | |||
4 | +++ lib/lp/code/browser/tests/test_branchlisting.py 2012-10-19 08:28:25 +0000 | |||
5 | @@ -596,10 +596,11 @@ | |||
6 | 596 | # series on the main site, not the code site. | 596 | # series on the main site, not the code site. |
7 | 597 | branch = self.factory.makeProductBranch() | 597 | branch = self.factory.makeProductBranch() |
8 | 598 | series = self.factory.makeProductSeries(product=branch.product) | 598 | series = self.factory.makeProductSeries(product=branch.product) |
9 | 599 | series_name = series.name | ||
10 | 599 | remove_security_proxy_and_shout_at_engineer(series).branch = branch | 600 | remove_security_proxy_and_shout_at_engineer(series).branch = branch |
11 | 600 | browser = self.getUserBrowser( | 601 | browser = self.getUserBrowser( |
12 | 601 | canonical_url(branch.product, rootsite='code')) | 602 | canonical_url(branch.product, rootsite='code')) |
14 | 602 | link = browser.getLink(re.compile('^' + series.name + '$')) | 603 | link = browser.getLink(re.compile('^' + series_name + '$')) |
15 | 603 | self.assertEqual('launchpad.dev', URI(link.url).host) | 604 | self.assertEqual('launchpad.dev', URI(link.url).host) |
16 | 604 | 605 | ||
17 | 605 | 606 | ||
18 | 606 | 607 | ||
19 | === modified file 'lib/lp/registry/browser/tests/test_productseries_views.py' | |||
20 | --- lib/lp/registry/browser/tests/test_productseries_views.py 2012-10-18 16:56:54 +0000 | |||
21 | +++ lib/lp/registry/browser/tests/test_productseries_views.py 2012-10-19 08:28:25 +0000 | |||
22 | @@ -26,6 +26,7 @@ | |||
23 | 26 | from lp.testing.views import create_initialized_view | 26 | from lp.testing.views import create_initialized_view |
24 | 27 | 27 | ||
25 | 28 | 28 | ||
26 | 29 | <<<<<<< TREE | ||
27 | 29 | class TestProductSeries(BrowserTestCase): | 30 | class TestProductSeries(BrowserTestCase): |
28 | 30 | 31 | ||
29 | 31 | layer = DatabaseFunctionalLayer | 32 | layer = DatabaseFunctionalLayer |
30 | @@ -73,6 +74,56 @@ | |||
31 | 73 | privacy_portlet_proprietary)) | 74 | privacy_portlet_proprietary)) |
32 | 74 | 75 | ||
33 | 75 | 76 | ||
34 | 77 | ======= | ||
35 | 78 | class TestProductSeries(BrowserTestCase): | ||
36 | 79 | |||
37 | 80 | layer = DatabaseFunctionalLayer | ||
38 | 81 | |||
39 | 82 | def test_information_type_public(self): | ||
40 | 83 | # A ProductSeries view should include its information_type, | ||
41 | 84 | # which defaults to Public for new projects. | ||
42 | 85 | series = self.factory.makeProductSeries() | ||
43 | 86 | view = create_initialized_view(series, '+index') | ||
44 | 87 | self.assertEqual('Public', view.information_type) | ||
45 | 88 | |||
46 | 89 | def test_information_type_proprietary(self): | ||
47 | 90 | # A ProductSeries view should get its information_type | ||
48 | 91 | # from the related product even if the product is changed to | ||
49 | 92 | # PROPRIETARY. | ||
50 | 93 | owner = self.factory.makePerson() | ||
51 | 94 | information_type = InformationType.PROPRIETARY | ||
52 | 95 | product = self.factory.makeProduct( | ||
53 | 96 | owner=owner, information_type=information_type) | ||
54 | 97 | series = self.factory.makeProductSeries(product=product) | ||
55 | 98 | with person_logged_in(owner): | ||
56 | 99 | view = create_initialized_view(series, '+index') | ||
57 | 100 | self.assertEqual('Proprietary', view.information_type) | ||
58 | 101 | |||
59 | 102 | def test_privacy_portlet(self): | ||
60 | 103 | # A ProductSeries page should include a privacy portlet that | ||
61 | 104 | # accurately describes the information_type. | ||
62 | 105 | owner = self.factory.makePerson() | ||
63 | 106 | information_type = InformationType.PROPRIETARY | ||
64 | 107 | product = self.factory.makeProduct( | ||
65 | 108 | owner=owner, information_type=information_type) | ||
66 | 109 | series = self.factory.makeProductSeries(product=product) | ||
67 | 110 | privacy_portlet = soupmatchers.Tag( | ||
68 | 111 | 'info-type-portlet', 'span', | ||
69 | 112 | attrs={'id': 'information-type-summary'}) | ||
70 | 113 | privacy_portlet_proprietary = soupmatchers.Tag( | ||
71 | 114 | 'info-type-text', 'strong', attrs={'id': 'information-type'}, | ||
72 | 115 | text='Proprietary') | ||
73 | 116 | browser = self.getViewBrowser(series, '+index', user=owner) | ||
74 | 117 | # First, assert that the portlet exists. | ||
75 | 118 | self.assertThat( | ||
76 | 119 | browser.contents, soupmatchers.HTMLContains(privacy_portlet)) | ||
77 | 120 | # Then, assert that the text displayed matches the information_type. | ||
78 | 121 | self.assertThat( | ||
79 | 122 | browser.contents, soupmatchers.HTMLContains( | ||
80 | 123 | privacy_portlet_proprietary)) | ||
81 | 124 | |||
82 | 125 | |||
83 | 126 | >>>>>>> MERGE-SOURCE | ||
84 | 76 | class TestProductSeriesHelp(TestCaseWithFactory): | 127 | class TestProductSeriesHelp(TestCaseWithFactory): |
85 | 77 | layer = DatabaseFunctionalLayer | 128 | layer = DatabaseFunctionalLayer |
86 | 78 | 129 | ||
87 | 79 | 130 | ||
88 | === modified file 'lib/lp/registry/browser/tests/test_sourcepackage_views.py' | |||
89 | --- lib/lp/registry/browser/tests/test_sourcepackage_views.py 2012-10-15 15:35:32 +0000 | |||
90 | +++ lib/lp/registry/browser/tests/test_sourcepackage_views.py 2012-10-19 08:28:25 +0000 | |||
91 | @@ -319,6 +319,7 @@ | |||
92 | 319 | product = self.factory.makeProduct( | 319 | product = self.factory.makeProduct( |
93 | 320 | name=product_name, owner=product_owner) | 320 | name=product_name, owner=product_owner) |
94 | 321 | series = self.factory.makeProductSeries(product=product) | 321 | series = self.factory.makeProductSeries(product=product) |
95 | 322 | series_displayname = series.displayname | ||
96 | 322 | ubuntu_series = self.factory.makeUbuntuDistroSeries() | 323 | ubuntu_series = self.factory.makeUbuntuDistroSeries() |
97 | 323 | sp = self.factory.makeSourcePackage(distroseries=ubuntu_series) | 324 | sp = self.factory.makeSourcePackage(distroseries=ubuntu_series) |
98 | 324 | browser = self.getViewBrowser( | 325 | browser = self.getViewBrowser( |
99 | @@ -327,7 +328,7 @@ | |||
100 | 327 | browser.getControl('Continue').click() | 328 | browser.getControl('Continue').click() |
101 | 328 | with person_logged_in(product_owner): | 329 | with person_logged_in(product_owner): |
102 | 329 | product.information_type = InformationType.PROPRIETARY | 330 | product.information_type = InformationType.PROPRIETARY |
104 | 330 | browser.getControl(series.displayname).selected = True | 331 | browser.getControl(series_displayname).selected = True |
105 | 331 | browser.getControl('Change').click() | 332 | browser.getControl('Change').click() |
106 | 332 | self.assertIn( | 333 | self.assertIn( |
107 | 333 | 'Only Public projects can be packaged, not Proprietary.', | 334 | 'Only Public projects can be packaged, not Proprietary.', |
108 | 334 | 335 | ||
109 | === modified file 'lib/lp/registry/configure.zcml' | |||
110 | --- lib/lp/registry/configure.zcml 2012-10-17 20:18:20 +0000 | |||
111 | +++ lib/lp/registry/configure.zcml 2012-10-19 08:28:25 +0000 | |||
112 | @@ -1508,10 +1508,16 @@ | |||
113 | 1508 | <allow | 1508 | <allow |
114 | 1509 | interface="lp.registry.interfaces.productseries.IProductSeriesPublic"/> | 1509 | interface="lp.registry.interfaces.productseries.IProductSeriesPublic"/> |
115 | 1510 | <require | 1510 | <require |
116 | 1511 | permission="launchpad.View" | ||
117 | 1512 | interface="lp.registry.interfaces.productseries.IProductSeriesView"/> | ||
118 | 1513 | <require | ||
119 | 1511 | permission="launchpad.Edit" | 1514 | permission="launchpad.Edit" |
120 | 1512 | interface="lp.registry.interfaces.productseries.IProductSeriesEditRestricted"/> | 1515 | interface="lp.registry.interfaces.productseries.IProductSeriesEditRestricted"/> |
123 | 1513 | <allow interface="lp.bugs.interfaces.bugsummary.IBugSummaryDimension"/> | 1516 | <require |
124 | 1514 | <allow | 1517 | permission="launchpad.View" |
125 | 1518 | interface="lp.bugs.interfaces.bugsummary.IBugSummaryDimension"/> | ||
126 | 1519 | <require | ||
127 | 1520 | permission="launchpad.View" | ||
128 | 1515 | interface="lp.bugs.interfaces.bugtarget.ISeriesBugTarget"/> | 1521 | interface="lp.bugs.interfaces.bugtarget.ISeriesBugTarget"/> |
129 | 1516 | <require | 1522 | <require |
130 | 1517 | permission="launchpad.Edit" | 1523 | permission="launchpad.Edit" |
131 | @@ -1522,16 +1528,17 @@ | |||
132 | 1522 | translations_branch status releasefileglob | 1528 | translations_branch status releasefileglob |
133 | 1523 | translations_autoimport_mode"/> | 1529 | translations_autoimport_mode"/> |
134 | 1524 | <require | 1530 | <require |
136 | 1525 | permission="launchpad.AnyPerson" | 1531 | permission="launchpad.AnyAllowedPerson" |
137 | 1526 | set_attributes="importstatus rcstype cvsroot cvsmodule | 1532 | set_attributes="importstatus rcstype cvsroot cvsmodule |
138 | 1527 | cvstarfileurl cvsbranch svnrepository"/> | 1533 | cvstarfileurl cvsbranch svnrepository"/> |
139 | 1528 | 1534 | ||
140 | 1529 | <!-- IStructuralSubscriptionTarget --> | 1535 | <!-- IStructuralSubscriptionTarget --> |
141 | 1530 | 1536 | ||
143 | 1531 | <allow | 1537 | <require |
144 | 1538 | permission="launchpad.View" | ||
145 | 1532 | interface="lp.bugs.interfaces.structuralsubscription.IStructuralSubscriptionTargetRead" /> | 1539 | interface="lp.bugs.interfaces.structuralsubscription.IStructuralSubscriptionTargetRead" /> |
146 | 1533 | <require | 1540 | <require |
148 | 1534 | permission="launchpad.AnyPerson" | 1541 | permission="launchpad.AnyAllowedPerson" |
149 | 1535 | interface="lp.bugs.interfaces.structuralsubscription.IStructuralSubscriptionTargetWrite" /> | 1542 | interface="lp.bugs.interfaces.structuralsubscription.IStructuralSubscriptionTargetWrite" /> |
150 | 1536 | 1543 | ||
151 | 1537 | </class> | 1544 | </class> |
152 | 1538 | 1545 | ||
153 | === modified file 'lib/lp/registry/interfaces/productseries.py' | |||
154 | --- lib/lp/registry/interfaces/productseries.py 2012-10-06 23:40:20 +0000 | |||
155 | +++ lib/lp/registry/interfaces/productseries.py 2012-10-19 08:28:25 +0000 | |||
156 | @@ -12,6 +12,7 @@ | |||
157 | 12 | 'IProductSeriesEditRestricted', | 12 | 'IProductSeriesEditRestricted', |
158 | 13 | 'IProductSeriesPublic', | 13 | 'IProductSeriesPublic', |
159 | 14 | 'IProductSeriesSet', | 14 | 'IProductSeriesSet', |
160 | 15 | 'IProductSeriesView', | ||
161 | 15 | 'NoSuchProductSeries', | 16 | 'NoSuchProductSeries', |
162 | 16 | 'ITimelineProductSeries', | 17 | 'ITimelineProductSeries', |
163 | 17 | ] | 18 | ] |
164 | @@ -129,13 +130,18 @@ | |||
165 | 129 | """Create a new milestone for this ProjectSeries.""" | 130 | """Create a new milestone for this ProjectSeries.""" |
166 | 130 | 131 | ||
167 | 131 | 132 | ||
169 | 132 | class IProductSeriesPublic( | 133 | class IProductSeriesPublic(Interface): |
170 | 134 | """Public IProductSeries properties.""" | ||
171 | 135 | id = Int(title=_('ID')) | ||
172 | 136 | |||
173 | 137 | def userCanView(user): | ||
174 | 138 | """True if the given user has access to this product.""" | ||
175 | 139 | |||
176 | 140 | |||
177 | 141 | class IProductSeriesView( | ||
178 | 133 | ISeriesMixin, IHasAppointedDriver, IHasOwner, IBugTarget, | 142 | ISeriesMixin, IHasAppointedDriver, IHasOwner, IBugTarget, |
179 | 134 | ISpecificationGoal, IHasMilestones, IHasOfficialBugTags, IHasExpirableBugs, | 143 | ISpecificationGoal, IHasMilestones, IHasOfficialBugTags, IHasExpirableBugs, |
180 | 135 | IHasTranslationImports, IHasTranslationTemplates, IServiceUsage): | 144 | IHasTranslationImports, IHasTranslationTemplates, IServiceUsage): |
181 | 136 | """Public IProductSeries properties.""" | ||
182 | 137 | id = Int(title=_('ID')) | ||
183 | 138 | |||
184 | 139 | product = exported( | 145 | product = exported( |
185 | 140 | ReferenceChoice(title=_('Project'), required=True, | 146 | ReferenceChoice(title=_('Project'), required=True, |
186 | 141 | vocabulary='Product', schema=Interface), # really IProduct | 147 | vocabulary='Product', schema=Interface), # really IProduct |
187 | @@ -330,7 +336,7 @@ | |||
188 | 330 | 336 | ||
189 | 331 | 337 | ||
190 | 332 | class IProductSeries(IProductSeriesEditRestricted, IProductSeriesPublic, | 338 | class IProductSeries(IProductSeriesEditRestricted, IProductSeriesPublic, |
192 | 333 | IStructuralSubscriptionTarget): | 339 | IProductSeriesView, IStructuralSubscriptionTarget): |
193 | 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'.""" |
194 | 335 | export_as_webservice_entry('project_series') | 341 | export_as_webservice_entry('project_series') |
195 | 336 | 342 | ||
196 | 337 | 343 | ||
197 | === modified file 'lib/lp/registry/model/packaging.py' | |||
198 | --- lib/lp/registry/model/packaging.py 2012-10-12 19:37:30 +0000 | |||
199 | +++ lib/lp/registry/model/packaging.py 2012-10-19 08:28:25 +0000 | |||
200 | @@ -111,6 +111,10 @@ | |||
201 | 111 | (sourcepackagename.name, distroseries.name)) | 111 | (sourcepackagename.name, distroseries.name)) |
202 | 112 | # XXX: AaronBentley: 2012-08-12 bug=1066063 Cannot adapt ProductSeries | 112 | # XXX: AaronBentley: 2012-08-12 bug=1066063 Cannot adapt ProductSeries |
203 | 113 | # to IInformationType. | 113 | # to IInformationType. |
204 | 114 | # The line below causes a failure of | ||
205 | 115 | # lp.registry.tests.test_distroseries.TestDistroSeriesPackaging. | ||
206 | 116 | # test_getPrioritizedPackagings_bug_tracker because | ||
207 | 117 | # productseries.product loses all set permissions. | ||
208 | 114 | # info_type = IInformationType(productseries).information_type | 118 | # info_type = IInformationType(productseries).information_type |
209 | 115 | info_type = productseries.product.information_type | 119 | info_type = productseries.product.information_type |
210 | 116 | if info_type != InformationType.PUBLIC: | 120 | if info_type != InformationType.PUBLIC: |
211 | 117 | 121 | ||
212 | === modified file 'lib/lp/registry/model/productseries.py' | |||
213 | --- lib/lp/registry/model/productseries.py 2012-10-11 15:01:49 +0000 | |||
214 | +++ lib/lp/registry/model/productseries.py 2012-10-19 08:28:25 +0000 | |||
215 | @@ -679,6 +679,11 @@ | |||
216 | 679 | return OrderedBugTask(3, bugtask.id, bugtask) | 679 | return OrderedBugTask(3, bugtask.id, bugtask) |
217 | 680 | return weight_function | 680 | return weight_function |
218 | 681 | 681 | ||
219 | 682 | def userCanView(self, user): | ||
220 | 683 | """See `IproductSeriesPublic`.""" | ||
221 | 684 | # Deleate the permission check to the parent product. | ||
222 | 685 | return self.product.userCanView(user) | ||
223 | 686 | |||
224 | 682 | 687 | ||
225 | 683 | class TimelineProductSeries: | 688 | class TimelineProductSeries: |
226 | 684 | """See `ITimelineProductSeries`.""" | 689 | """See `ITimelineProductSeries`.""" |
227 | 685 | 690 | ||
228 | === modified file 'lib/lp/registry/tests/test_packaging.py' | |||
229 | --- lib/lp/registry/tests/test_packaging.py 2012-10-11 16:06:49 +0000 | |||
230 | +++ lib/lp/registry/tests/test_packaging.py 2012-10-19 08:28:25 +0000 | |||
231 | @@ -174,25 +174,33 @@ | |||
232 | 174 | 174 | ||
233 | 175 | def test_createPackaging_refuses_PROPRIETARY(self): | 175 | def test_createPackaging_refuses_PROPRIETARY(self): |
234 | 176 | """Packaging cannot be created for PROPRIETARY productseries""" | 176 | """Packaging cannot be created for PROPRIETARY productseries""" |
235 | 177 | owner = self.factory.makePerson() | ||
236 | 177 | product = self.factory.makeProduct( | 178 | product = self.factory.makeProduct( |
237 | 179 | owner=owner, | ||
238 | 178 | information_type=InformationType.PROPRIETARY) | 180 | information_type=InformationType.PROPRIETARY) |
239 | 179 | series = self.factory.makeProductSeries(product=product) | 181 | series = self.factory.makeProductSeries(product=product) |
245 | 180 | with ExpectedException(CannotPackageProprietaryProduct, | 182 | expected_message = ( |
246 | 181 | 'Only Public project series can be packaged, not Proprietary.'): | 183 | 'Only Public project series can be packaged, not Proprietary.') |
247 | 182 | self.packaging_util.createPackaging( | 184 | with person_logged_in(owner): |
248 | 183 | series, self.sourcepackagename, self.distroseries, | 185 | with ExpectedException(CannotPackageProprietaryProduct, |
249 | 184 | PackagingType.PRIME, owner=self.owner) | 186 | expected_message): |
250 | 187 | self.packaging_util.createPackaging( | ||
251 | 188 | series, self.sourcepackagename, self.distroseries, | ||
252 | 189 | PackagingType.PRIME, owner=self.owner) | ||
253 | 185 | 190 | ||
254 | 186 | def test_createPackaging_refuses_EMBARGOED(self): | 191 | def test_createPackaging_refuses_EMBARGOED(self): |
255 | 187 | """Packaging cannot be created for EMBARGOED productseries""" | 192 | """Packaging cannot be created for EMBARGOED productseries""" |
256 | 193 | owner = self.factory.makePerson() | ||
257 | 188 | product = self.factory.makeProduct( | 194 | product = self.factory.makeProduct( |
258 | 195 | owner=owner, | ||
259 | 189 | information_type=InformationType.EMBARGOED) | 196 | information_type=InformationType.EMBARGOED) |
260 | 190 | series = self.factory.makeProductSeries(product=product) | 197 | series = self.factory.makeProductSeries(product=product) |
266 | 191 | with ExpectedException(CannotPackageProprietaryProduct, | 198 | with person_logged_in(owner): |
267 | 192 | 'Only Public project series can be packaged, not Embargoed.'): | 199 | with ExpectedException(CannotPackageProprietaryProduct, |
268 | 193 | self.packaging_util.createPackaging( | 200 | 'Only Public project series can be packaged, not Embargoed.'): |
269 | 194 | series, self.sourcepackagename, self.distroseries, | 201 | self.packaging_util.createPackaging( |
270 | 195 | PackagingType.PRIME, owner=self.owner) | 202 | series, self.sourcepackagename, self.distroseries, |
271 | 203 | PackagingType.PRIME, owner=self.owner) | ||
272 | 196 | 204 | ||
273 | 197 | 205 | ||
274 | 198 | class TestPackagingEntryExists(PackagingUtilMixin, TestCaseWithFactory): | 206 | class TestPackagingEntryExists(PackagingUtilMixin, TestCaseWithFactory): |
275 | 199 | 207 | ||
276 | === modified file 'lib/lp/registry/tests/test_productseries.py' | |||
277 | --- lib/lp/registry/tests/test_productseries.py 2012-10-16 15:12:09 +0000 | |||
278 | +++ lib/lp/registry/tests/test_productseries.py 2012-10-19 08:28:25 +0000 | |||
279 | @@ -5,13 +5,25 @@ | |||
280 | 5 | 5 | ||
281 | 6 | __metaclass__ = type | 6 | __metaclass__ = type |
282 | 7 | 7 | ||
283 | 8 | from storm.exceptions import NoneError | ||
284 | 8 | from testtools.testcase import ExpectedException | 9 | from testtools.testcase import ExpectedException |
285 | 9 | import transaction | 10 | import transaction |
286 | 11 | from zope.security.checker import ( | ||
287 | 12 | CheckerPublic, | ||
288 | 13 | getChecker, | ||
289 | 14 | ) | ||
290 | 10 | from zope.component import getUtility | 15 | from zope.component import getUtility |
291 | 16 | from zope.security.interfaces import Unauthorized | ||
292 | 11 | from zope.security.proxy import removeSecurityProxy | 17 | from zope.security.proxy import removeSecurityProxy |
293 | 12 | 18 | ||
294 | 13 | from lp.app.enums import InformationType | 19 | from lp.app.enums import InformationType |
296 | 14 | from lp.app.interfaces.informationtype import IInformationType | 20 | <<<<<<< TREE |
297 | 21 | from lp.app.interfaces.informationtype import IInformationType | ||
298 | 22 | ======= | ||
299 | 23 | from lp.app.interfaces.informationtype import IInformationType | ||
300 | 24 | from lp.app.interfaces.services import IService | ||
301 | 25 | from lp.registry.enums import SharingPermission | ||
302 | 26 | >>>>>>> MERGE-SOURCE | ||
303 | 15 | from lp.registry.errors import CannotPackageProprietaryProduct | 27 | from lp.registry.errors import CannotPackageProprietaryProduct |
304 | 16 | from lp.registry.interfaces.distribution import IDistributionSet | 28 | from lp.registry.interfaces.distribution import IDistributionSet |
305 | 17 | from lp.registry.interfaces.distroseries import IDistroSeriesSet | 29 | from lp.registry.interfaces.distroseries import IDistroSeriesSet |
306 | @@ -22,7 +34,10 @@ | |||
307 | 22 | from lp.registry.interfaces.series import SeriesStatus | 34 | from lp.registry.interfaces.series import SeriesStatus |
308 | 23 | from lp.services.database.lpstorm import IStore | 35 | from lp.services.database.lpstorm import IStore |
309 | 24 | from lp.testing import ( | 36 | from lp.testing import ( |
310 | 37 | ANONYMOUS, | ||
311 | 38 | celebrity_logged_in, | ||
312 | 25 | login, | 39 | login, |
313 | 40 | person_logged_in, | ||
314 | 26 | TestCaseWithFactory, | 41 | TestCaseWithFactory, |
315 | 27 | WebServiceTestCase, | 42 | WebServiceTestCase, |
316 | 28 | ) | 43 | ) |
317 | @@ -37,6 +52,7 @@ | |||
318 | 37 | ) | 52 | ) |
319 | 38 | 53 | ||
320 | 39 | 54 | ||
321 | 55 | <<<<<<< TREE | ||
322 | 40 | 56 | ||
323 | 41 | class TestProductSeries(TestCaseWithFactory): | 57 | class TestProductSeries(TestCaseWithFactory): |
324 | 42 | """Tests for ProductSeries.""" | 58 | """Tests for ProductSeries.""" |
325 | @@ -54,6 +70,25 @@ | |||
326 | 54 | IInformationType(series).information_type, information_type) | 70 | IInformationType(series).information_type, information_type) |
327 | 55 | 71 | ||
328 | 56 | 72 | ||
329 | 73 | ======= | ||
330 | 74 | class TestProductSeries(TestCaseWithFactory): | ||
331 | 75 | """Tests for ProductSeries.""" | ||
332 | 76 | |||
333 | 77 | layer = DatabaseFunctionalLayer | ||
334 | 78 | |||
335 | 79 | def test_information_type_from_product(self): | ||
336 | 80 | # ProductSeries should inherit information_type from its product.""" | ||
337 | 81 | owner = self.factory.makePerson() | ||
338 | 82 | information_type = InformationType.PROPRIETARY | ||
339 | 83 | product = self.factory.makeProduct( | ||
340 | 84 | owner=owner, information_type=information_type) | ||
341 | 85 | series = self.factory.makeProductSeries(product=product) | ||
342 | 86 | with person_logged_in(owner): | ||
343 | 87 | self.assertEqual( | ||
344 | 88 | IInformationType(series).information_type, information_type) | ||
345 | 89 | |||
346 | 90 | |||
347 | 91 | >>>>>>> MERGE-SOURCE | ||
348 | 57 | class ProductSeriesReleasesTestCase(TestCaseWithFactory): | 92 | class ProductSeriesReleasesTestCase(TestCaseWithFactory): |
349 | 58 | """Test for ProductSeries.release property.""" | 93 | """Test for ProductSeries.release property.""" |
350 | 59 | 94 | ||
351 | @@ -570,3 +605,293 @@ | |||
352 | 570 | mode = TranslationsBranchImportMode.IMPORT_TRANSLATIONS | 605 | mode = TranslationsBranchImportMode.IMPORT_TRANSLATIONS |
353 | 571 | ws_series.translations_autoimport_mode = mode.title | 606 | ws_series.translations_autoimport_mode = mode.title |
354 | 572 | ws_series.lp_save() | 607 | ws_series.lp_save() |
355 | 608 | |||
356 | 609 | |||
357 | 610 | class ProductSeriesSecurityAdaperTestCase(TestCaseWithFactory): | ||
358 | 611 | """Test for permissions of IProductSeries.""" | ||
359 | 612 | |||
360 | 613 | layer = DatabaseFunctionalLayer | ||
361 | 614 | |||
362 | 615 | def setUp(self): | ||
363 | 616 | super(ProductSeriesSecurityAdaperTestCase, self).setUp() | ||
364 | 617 | self.public_product = self.factory.makeProduct() | ||
365 | 618 | self.public_series = self.factory.makeProductSeries( | ||
366 | 619 | product=self.public_product) | ||
367 | 620 | self.proprietary_product_owner = self.factory.makePerson() | ||
368 | 621 | self.proprietary_product = self.factory.makeProduct( | ||
369 | 622 | owner=self.proprietary_product_owner, | ||
370 | 623 | information_type=InformationType.PROPRIETARY) | ||
371 | 624 | self.proprietary_series = self.factory.makeProductSeries( | ||
372 | 625 | product=self.proprietary_product) | ||
373 | 626 | |||
374 | 627 | expected_get_permissions = { | ||
375 | 628 | CheckerPublic: set(( | ||
376 | 629 | 'id', 'userCanView', | ||
377 | 630 | )), | ||
378 | 631 | 'launchpad.AnyAllowedPerson': set(( | ||
379 | 632 | 'addBugSubscription', 'addBugSubscriptionFilter', | ||
380 | 633 | 'addSubscription', 'removeBugSubscription', | ||
381 | 634 | )), | ||
382 | 635 | 'launchpad.Edit': set(('newMilestone', )), | ||
383 | 636 | 'launchpad.View': set(( | ||
384 | 637 | '_all_specifications', '_getOfficialTagClause', | ||
385 | 638 | '_valid_specifications', 'active', 'all_milestones', | ||
386 | 639 | 'answers_usage', 'blueprints_usage', 'branch', | ||
387 | 640 | 'bug_reported_acknowledgement', 'bug_reporting_guidelines', | ||
388 | 641 | 'bug_subscriptions', 'bug_supervisor', 'bug_tracking_usage', | ||
389 | 642 | 'bugtargetdisplayname', 'bugtarget_parent', 'bugtargetname', | ||
390 | 643 | 'codehosting_usage', 'createBug', 'datecreated', 'displayname', | ||
391 | 644 | 'driver', 'drivers', 'enable_bugfiling_duplicate_search', | ||
392 | 645 | 'getAllowedSpecificationInformationTypes', | ||
393 | 646 | 'getBugSummaryContextWhereClause', | ||
394 | 647 | 'getBugTaskWeightFunction', 'getCachedReleases', | ||
395 | 648 | 'getCurrentTemplatesCollection', 'getCurrentTranslationFiles', | ||
396 | 649 | 'getCurrentTranslationTemplates', | ||
397 | 650 | 'getDefaultSpecificationInformationType', | ||
398 | 651 | 'getFirstEntryToImport', 'getLatestRelease', 'getPOTemplate', | ||
399 | 652 | 'getPackage', 'getPackagingInDistribution', 'getRelease', | ||
400 | 653 | 'getSharingPartner', 'getSpecification', 'getSubscription', | ||
401 | 654 | 'getSubscriptions', 'getTemplatesAndLanguageCounts', | ||
402 | 655 | 'getTemplatesCollection', 'getTimeline', | ||
403 | 656 | 'getTranslationImportQueueEntries', | ||
404 | 657 | 'getTranslationTemplateByName', 'getTranslationTemplateFormats', | ||
405 | 658 | 'getTranslationTemplates', 'getUbuntuTranslationFocusPackage', | ||
406 | 659 | 'getUsedBugTagsWithOpenCounts', | ||
407 | 660 | 'has_current_translation_templates', 'has_milestones', | ||
408 | 661 | 'has_obsolete_translation_templates', | ||
409 | 662 | 'has_sharing_translation_templates', 'has_translation_files', | ||
410 | 663 | 'has_translation_templates', 'is_development_focus', 'milestones', | ||
411 | 664 | 'name', 'official_bug_tags', 'owner', 'packagings', 'parent', | ||
412 | 665 | 'parent_subscription_target', | ||
413 | 666 | 'personHasDriverRights', 'pillar', 'potemplate_count', 'product', | ||
414 | 667 | 'productID', 'productserieslanguages', 'release_files', | ||
415 | 668 | 'releasefileglob', 'releases', 'releaseverstyle', 'searchTasks', | ||
416 | 669 | 'series', 'setPackaging', 'sourcepackages', 'specifications', | ||
417 | 670 | 'status', 'summary', 'target_type_display', 'title', | ||
418 | 671 | 'translations_autoimport_mode', 'userCanAlterBugSubscription', | ||
419 | 672 | 'userCanAlterSubscription', 'userHasBugSubscriptions', | ||
420 | 673 | 'translations_branch', 'translations_usage', 'uses_launchpad', | ||
421 | 674 | )), | ||
422 | 675 | } | ||
423 | 676 | |||
424 | 677 | def test_get_permissions(self): | ||
425 | 678 | checker = getChecker(self.public_series) | ||
426 | 679 | self.checkPermissions( | ||
427 | 680 | self.expected_get_permissions, checker.get_permissions, 'get') | ||
428 | 681 | |||
429 | 682 | expected_set_permissions = { | ||
430 | 683 | 'launchpad.Edit': set(( | ||
431 | 684 | 'answers_usage', 'blueprints_usage', 'branch', | ||
432 | 685 | 'bug_tracking_usage', 'codehosting_usage', 'driver', 'name', | ||
433 | 686 | 'owner', 'product', 'releasefileglob', 'status', 'summary', | ||
434 | 687 | 'translations_autoimport_mode', 'translations_branch', | ||
435 | 688 | 'translations_usage', 'uses_launchpad', | ||
436 | 689 | )), | ||
437 | 690 | 'launchpad.AnyAllowedPerson': set(( | ||
438 | 691 | 'cvsbranch', 'cvsmodule', 'cvsroot', 'cvstarfileurl', | ||
439 | 692 | 'importstatus', 'rcstype', 'svnrepository', | ||
440 | 693 | )), | ||
441 | 694 | } | ||
442 | 695 | |||
443 | 696 | def test_set_permissions(self): | ||
444 | 697 | checker = getChecker(self.public_series) | ||
445 | 698 | self.checkPermissions( | ||
446 | 699 | self.expected_set_permissions, checker.set_permissions, 'set') | ||
447 | 700 | |||
448 | 701 | def assertAccessAuthorized(self, attribute_names, obj): | ||
449 | 702 | # Try to access the given attributes of obj. No exception | ||
450 | 703 | # should be raised. | ||
451 | 704 | for name in attribute_names: | ||
452 | 705 | getattr(obj, name) | ||
453 | 706 | |||
454 | 707 | def assertAccessUnauthorized(self, attribute_names, obj): | ||
455 | 708 | # Try to access the given attributes of obj. Unauthorized | ||
456 | 709 | # should be raised. | ||
457 | 710 | for name in attribute_names: | ||
458 | 711 | self.assertRaises(Unauthorized, getattr, obj, name) | ||
459 | 712 | |||
460 | 713 | def assertChangeAuthorized(self, attribute_names, obj): | ||
461 | 714 | # Try to changes the given attributes of obj. Unauthorized | ||
462 | 715 | # should not be raised. | ||
463 | 716 | for name in attribute_names: | ||
464 | 717 | # Not all attributes declared in configure.zcml to be | ||
465 | 718 | # settable actually exist or are settable. Attempts to set | ||
466 | 719 | # them raise an AttributeError. Similary, the naive attempt | ||
467 | 720 | # to set an attribute to None may raise a NoneError | ||
468 | 721 | # | ||
469 | 722 | # Both errors can be ignored here: This method intends only | ||
470 | 723 | # to prove that Unauthorized is not raised. | ||
471 | 724 | try: | ||
472 | 725 | setattr(obj, name, None) | ||
473 | 726 | except (AttributeError, NoneError): | ||
474 | 727 | pass | ||
475 | 728 | |||
476 | 729 | def assertChangeUnauthorized(self, attribute_names, obj): | ||
477 | 730 | # Try to changes the given attributes of obj. Unauthorized | ||
478 | 731 | # should be raised. | ||
479 | 732 | for name in attribute_names: | ||
480 | 733 | self.assertRaises(Unauthorized, setattr, obj, name, None) | ||
481 | 734 | |||
482 | 735 | def test_access_for_anonymous(self): | ||
483 | 736 | # Anonymous users have access to public attributes of | ||
484 | 737 | # a series for a private or public product. | ||
485 | 738 | with person_logged_in(ANONYMOUS): | ||
486 | 739 | self.assertAccessAuthorized( | ||
487 | 740 | self.expected_get_permissions[CheckerPublic], | ||
488 | 741 | self.public_series) | ||
489 | 742 | self.assertAccessAuthorized( | ||
490 | 743 | self.expected_get_permissions[CheckerPublic], | ||
491 | 744 | self.proprietary_series) | ||
492 | 745 | |||
493 | 746 | # They have access to attributes requiring the permission | ||
494 | 747 | # launchpad.View of a series for a public product... | ||
495 | 748 | self.assertAccessAuthorized( | ||
496 | 749 | self.expected_get_permissions['launchpad.View'], | ||
497 | 750 | self.public_series) | ||
498 | 751 | |||
499 | 752 | # ...but not to the same attributes of a series for s private | ||
500 | 753 | # product. | ||
501 | 754 | self.assertAccessUnauthorized( | ||
502 | 755 | self.expected_get_permissions['launchpad.View'], | ||
503 | 756 | self.proprietary_series) | ||
504 | 757 | |||
505 | 758 | # The cannot access other attributes of a series for | ||
506 | 759 | # public and private products. | ||
507 | 760 | for permission, names in self.expected_get_permissions.items(): | ||
508 | 761 | if permission in (CheckerPublic, 'launchpad.View'): | ||
509 | 762 | continue | ||
510 | 763 | self.assertAccessUnauthorized(names, self.public_series) | ||
511 | 764 | self.assertAccessUnauthorized(names, self.proprietary_series) | ||
512 | 765 | |||
513 | 766 | # They cannot change any attributes. | ||
514 | 767 | for permission, names in self.expected_set_permissions.items(): | ||
515 | 768 | self.assertChangeUnauthorized(names, self.public_series) | ||
516 | 769 | self.assertChangeUnauthorized(names, self.proprietary_series) | ||
517 | 770 | |||
518 | 771 | def test_access_for_regular_user(self): | ||
519 | 772 | # Regular users have access to public attributes of | ||
520 | 773 | # a series for a private or public product. | ||
521 | 774 | user = self.factory.makePerson() | ||
522 | 775 | with person_logged_in(user): | ||
523 | 776 | self.assertAccessAuthorized( | ||
524 | 777 | self.expected_get_permissions[CheckerPublic], | ||
525 | 778 | self.public_series) | ||
526 | 779 | self.assertAccessAuthorized( | ||
527 | 780 | self.expected_get_permissions[CheckerPublic], | ||
528 | 781 | self.proprietary_series) | ||
529 | 782 | |||
530 | 783 | # They have access to attributes requiring the permissions | ||
531 | 784 | # launchpad.View and launchpadAnyAllowedPerson of a series | ||
532 | 785 | # for a public product... | ||
533 | 786 | self.assertAccessAuthorized( | ||
534 | 787 | self.expected_get_permissions['launchpad.View'], | ||
535 | 788 | self.public_series) | ||
536 | 789 | self.assertAccessAuthorized( | ||
537 | 790 | self.expected_get_permissions['launchpad.AnyAllowedPerson'], | ||
538 | 791 | self.public_series) | ||
539 | 792 | |||
540 | 793 | # ...but not to the same attributes of a series for s private | ||
541 | 794 | # product. | ||
542 | 795 | self.assertAccessUnauthorized( | ||
543 | 796 | self.expected_get_permissions['launchpad.View'], | ||
544 | 797 | self.proprietary_series) | ||
545 | 798 | self.assertAccessUnauthorized( | ||
546 | 799 | self.expected_get_permissions['launchpad.AnyAllowedPerson'], | ||
547 | 800 | self.proprietary_series) | ||
548 | 801 | |||
549 | 802 | # The cannot access other attributes of a series for | ||
550 | 803 | # public and private products. | ||
551 | 804 | for permission, names in self.expected_get_permissions.items(): | ||
552 | 805 | if permission in (CheckerPublic, 'launchpad.View', | ||
553 | 806 | 'launchpad.AnyAllowedPerson'): | ||
554 | 807 | continue | ||
555 | 808 | self.assertAccessUnauthorized(names, self.public_series) | ||
556 | 809 | self.assertAccessUnauthorized(names, self.proprietary_series) | ||
557 | 810 | |||
558 | 811 | # They can change attributes requiring the permission | ||
559 | 812 | # launchpad.AnyAllowedPerson of a series for a public project... | ||
560 | 813 | self.assertChangeAuthorized( | ||
561 | 814 | self.expected_set_permissions['launchpad.AnyAllowedPerson'], | ||
562 | 815 | self.public_series) | ||
563 | 816 | #... but not for a private project. | ||
564 | 817 | self.assertChangeUnauthorized( | ||
565 | 818 | self.expected_set_permissions['launchpad.AnyAllowedPerson'], | ||
566 | 819 | self.proprietary_series) | ||
567 | 820 | |||
568 | 821 | # They cannot change any attributes that require another | ||
569 | 822 | # permission than launchpad.AnyALlowedPerson. | ||
570 | 823 | for permission, names in self.expected_set_permissions.items(): | ||
571 | 824 | if permission == 'launchpad.AnyAllowedPerson': | ||
572 | 825 | continue | ||
573 | 826 | self.assertChangeUnauthorized(names, self.public_series) | ||
574 | 827 | self.assertChangeUnauthorized(names, self.proprietary_series) | ||
575 | 828 | |||
576 | 829 | def test_access_for_user_with_policy_grant(self): | ||
577 | 830 | # Users with a policy grant for the parent product can access | ||
578 | 831 | # properties requring the permission lanchpad.View or | ||
579 | 832 | # launchpad.ANyALlowedPerson of a series. | ||
580 | 833 | user = self.factory.makePerson() | ||
581 | 834 | with person_logged_in(self.proprietary_product_owner): | ||
582 | 835 | getUtility(IService, 'sharing').sharePillarInformation( | ||
583 | 836 | self.proprietary_product, user, self.proprietary_product_owner, | ||
584 | 837 | {InformationType.PROPRIETARY: SharingPermission.ALL}) | ||
585 | 838 | with person_logged_in(user): | ||
586 | 839 | self.assertAccessAuthorized( | ||
587 | 840 | self.expected_get_permissions['launchpad.View'], | ||
588 | 841 | self.proprietary_series) | ||
589 | 842 | self.assertAccessAuthorized( | ||
590 | 843 | self.expected_get_permissions['launchpad.AnyAllowedPerson'], | ||
591 | 844 | self.proprietary_series) | ||
592 | 845 | |||
593 | 846 | # The cannot access other attributes of a series for | ||
594 | 847 | # private products. | ||
595 | 848 | for permission, names in self.expected_get_permissions.items(): | ||
596 | 849 | if permission in (CheckerPublic, 'launchpad.View', | ||
597 | 850 | 'launchpad.AnyAllowedPerson'): | ||
598 | 851 | continue | ||
599 | 852 | self.assertAccessUnauthorized(names, self.proprietary_series) | ||
600 | 853 | |||
601 | 854 | # They can change attributes requiring the permission | ||
602 | 855 | # launchpad.AnyAllowedPerson of a series for a provate project... | ||
603 | 856 | self.assertChangeAuthorized( | ||
604 | 857 | self.expected_set_permissions['launchpad.AnyAllowedPerson'], | ||
605 | 858 | self.proprietary_series) | ||
606 | 859 | |||
607 | 860 | # They cannot change any attributes that require another | ||
608 | 861 | # permission than launchpad.AnyALlowedPerson. | ||
609 | 862 | for permission, names in self.expected_set_permissions.items(): | ||
610 | 863 | if permission == 'launchpad.AnyAllowedPerson': | ||
611 | 864 | continue | ||
612 | 865 | self.assertChangeUnauthorized(names, self.proprietary_series) | ||
613 | 866 | |||
614 | 867 | def test_access_for_product_owner(self): | ||
615 | 868 | # The owner of a project has access to all attributes of | ||
616 | 869 | # a product series. | ||
617 | 870 | with person_logged_in(self.proprietary_product_owner): | ||
618 | 871 | for permission, names in self.expected_get_permissions.items(): | ||
619 | 872 | self.assertAccessAuthorized(names, self.proprietary_series) | ||
620 | 873 | |||
621 | 874 | # They can change all attributes. | ||
622 | 875 | for permission, names in self.expected_set_permissions.items(): | ||
623 | 876 | self.assertChangeAuthorized(names, self.proprietary_series) | ||
624 | 877 | |||
625 | 878 | with person_logged_in(self.public_product.owner): | ||
626 | 879 | for permission, names in self.expected_get_permissions.items(): | ||
627 | 880 | self.assertAccessAuthorized(names, self.public_series) | ||
628 | 881 | |||
629 | 882 | # They can change all attributes. | ||
630 | 883 | for permission, names in self.expected_set_permissions.items(): | ||
631 | 884 | self.assertChangeAuthorized(names, self.public_series) | ||
632 | 885 | |||
633 | 886 | def test_access_for_lp_admins(self): | ||
634 | 887 | # Launchpad admins can access and change any attribute of a series | ||
635 | 888 | # of public and private product. | ||
636 | 889 | with celebrity_logged_in('admin'): | ||
637 | 890 | for permission, names in self.expected_get_permissions.items(): | ||
638 | 891 | self.assertAccessAuthorized(names, self.public_series) | ||
639 | 892 | self.assertAccessAuthorized(names, self.proprietary_series) | ||
640 | 893 | |||
641 | 894 | # They can change all attributes. | ||
642 | 895 | for permission, names in self.expected_set_permissions.items(): | ||
643 | 896 | self.assertChangeAuthorized(names, self.public_series) | ||
644 | 897 | self.assertChangeAuthorized(names, self.proprietary_series) | ||
645 | 573 | 898 | ||
646 | === modified file 'lib/lp/registry/tests/test_sourcepackage.py' | |||
647 | --- lib/lp/registry/tests/test_sourcepackage.py 2012-10-11 18:32:36 +0000 | |||
648 | +++ lib/lp/registry/tests/test_sourcepackage.py 2012-10-19 08:28:25 +0000 | |||
649 | @@ -327,14 +327,19 @@ | |||
650 | 327 | 327 | ||
651 | 328 | def test_refuses_PROPRIETARY(self): | 328 | def test_refuses_PROPRIETARY(self): |
652 | 329 | """Packaging cannot be created for PROPRIETARY productseries""" | 329 | """Packaging cannot be created for PROPRIETARY productseries""" |
653 | 330 | owner = self.factory.makePerson() | ||
654 | 330 | product = self.factory.makeProduct( | 331 | product = self.factory.makeProduct( |
655 | 332 | owner=owner, | ||
656 | 331 | information_type=InformationType.PROPRIETARY) | 333 | information_type=InformationType.PROPRIETARY) |
657 | 332 | series = self.factory.makeProductSeries(product=product) | 334 | series = self.factory.makeProductSeries(product=product) |
658 | 333 | ubuntu_series = self.factory.makeUbuntuDistroSeries() | 335 | ubuntu_series = self.factory.makeUbuntuDistroSeries() |
659 | 334 | sp = self.factory.makeSourcePackage(distroseries=ubuntu_series) | 336 | sp = self.factory.makeSourcePackage(distroseries=ubuntu_series) |
663 | 335 | with ExpectedException(CannotPackageProprietaryProduct, | 337 | with person_logged_in(owner): |
664 | 336 | 'Only Public project series can be packaged, not Proprietary.'): | 338 | with ExpectedException( |
665 | 337 | sp.setPackaging(series, series.owner) | 339 | CannotPackageProprietaryProduct, |
666 | 340 | 'Only Public project series can be packaged, not ' | ||
667 | 341 | 'Proprietary.'): | ||
668 | 342 | sp.setPackaging(series, owner) | ||
669 | 338 | 343 | ||
670 | 339 | def test_setPackagingReturnSharingDetailPermissions__ordinary_user(self): | 344 | def test_setPackagingReturnSharingDetailPermissions__ordinary_user(self): |
671 | 340 | """An ordinary user can create a packaging link but he cannot | 345 | """An ordinary user can create a packaging link but he cannot |
672 | 341 | 346 | ||
673 | === modified file 'lib/lp/security.py' | |||
674 | --- lib/lp/security.py 2012-10-18 14:43:24 +0000 | |||
675 | +++ lib/lp/security.py 2012-10-19 08:28:25 +0000 | |||
676 | @@ -154,6 +154,7 @@ | |||
677 | 154 | ) | 154 | ) |
678 | 155 | from lp.registry.interfaces.productseries import ( | 155 | from lp.registry.interfaces.productseries import ( |
679 | 156 | IProductSeries, | 156 | IProductSeries, |
680 | 157 | IProductSeriesView, | ||
681 | 157 | ITimelineProductSeries, | 158 | ITimelineProductSeries, |
682 | 158 | ) | 159 | ) |
683 | 159 | from lp.registry.interfaces.projectgroup import ( | 160 | from lp.registry.interfaces.projectgroup import ( |
684 | @@ -1285,9 +1286,26 @@ | |||
685 | 1285 | or False) | 1286 | or False) |
686 | 1286 | 1287 | ||
687 | 1287 | 1288 | ||
691 | 1288 | class ViewProductSeries(AnonymousAuthorization): | 1289 | class ViewProductSeries(AuthorizationBase): |
692 | 1289 | 1290 | permission = 'launchpad.View' | |
693 | 1290 | usedfor = IProductSeries | 1291 | usedfor = IProductSeriesView |
694 | 1292 | |||
695 | 1293 | def checkAuthenticated(self, user): | ||
696 | 1294 | return self.obj.userCanView(user) | ||
697 | 1295 | |||
698 | 1296 | def checkUnauthenticated(self): | ||
699 | 1297 | return self.obj.userCanView(None) | ||
700 | 1298 | |||
701 | 1299 | |||
702 | 1300 | class ChangeProductSeries(ViewProductSeries): | ||
703 | 1301 | permission = 'launchpad.AnyAllowedPerson' | ||
704 | 1302 | usedfor = IProductSeriesView | ||
705 | 1303 | |||
706 | 1304 | def checkAuthenticated(self, user): | ||
707 | 1305 | return self.obj.userCanView(user) | ||
708 | 1306 | |||
709 | 1307 | def checkUnauthenticated(self): | ||
710 | 1308 | return False | ||
711 | 1291 | 1309 | ||
712 | 1292 | 1310 | ||
713 | 1293 | class EditProductSeries(EditByOwnersOrAdmins): | 1311 | class EditProductSeries(EditByOwnersOrAdmins): |
714 | 1294 | 1312 | ||
715 | === modified file 'lib/lp/services/features/flags.py' | |||
716 | === modified file 'lib/lp/translations/stories/webservice/xx-potemplate.txt' | |||
717 | --- lib/lp/translations/stories/webservice/xx-potemplate.txt 2012-10-09 10:28:02 +0000 | |||
718 | +++ lib/lp/translations/stories/webservice/xx-potemplate.txt 2012-10-19 08:28:25 +0000 | |||
719 | @@ -71,6 +71,7 @@ | |||
720 | 71 | 71 | ||
721 | 72 | >>> login('admin@canonical.com') | 72 | >>> login('admin@canonical.com') |
722 | 73 | >>> productseries = factory.makeProductSeries() | 73 | >>> productseries = factory.makeProductSeries() |
723 | 74 | >>> productseries_name = productseries.name | ||
724 | 74 | >>> product_name = productseries.product.name | 75 | >>> product_name = productseries.product.name |
725 | 75 | >>> potemplate_1 = factory.makePOTemplate(productseries=productseries) | 76 | >>> potemplate_1 = factory.makePOTemplate(productseries=productseries) |
726 | 76 | >>> potemplate_2 = factory.makePOTemplate(productseries=productseries) | 77 | >>> potemplate_2 = factory.makePOTemplate(productseries=productseries) |
727 | @@ -79,7 +80,7 @@ | |||
728 | 79 | >>> all_translation_templates = anon_webservice.named_get( | 80 | >>> all_translation_templates = anon_webservice.named_get( |
729 | 80 | ... '/%s/%s' % ( | 81 | ... '/%s/%s' % ( |
730 | 81 | ... product_name, | 82 | ... product_name, |
732 | 82 | ... productseries.name), | 83 | ... productseries_name), |
733 | 83 | ... 'getTranslationTemplates' | 84 | ... 'getTranslationTemplates' |
734 | 84 | ... ).jsonBody() | 85 | ... ).jsonBody() |
735 | 85 | >>> api_count = all_translation_templates['total_size'] | 86 | >>> api_count = all_translation_templates['total_size'] |
Looks alright. Thanks Abel.