Merge lp:~adeuring/launchpad/product-lp-limitedview into lp:launchpad

Proposed by Abel Deuring
Status: Merged
Approved by: Abel Deuring
Approved revision: no longer in the source branch.
Merged at revision: 16239
Proposed branch: lp:~adeuring/launchpad/product-lp-limitedview
Merge into: lp:launchpad
Diff against target: 273 lines (+108/-22)
6 files modified
lib/lp/registry/configure.zcml (+4/-1)
lib/lp/registry/interfaces/product.py (+23/-17)
lib/lp/registry/interfaces/sharingservice.py (+3/-0)
lib/lp/registry/services/sharingservice.py (+13/-3)
lib/lp/registry/tests/test_product.py (+53/-1)
lib/lp/security.py (+12/-0)
To merge this branch: bzr merge lp:~adeuring/launchpad/product-lp-limitedview
Reviewer Review Type Date Requested Status
Francesco Banconi (community) Approve
Review via email: mp+133046@code.launchpad.net

Commit message

split IProductLimitedView into IProductView and IProductLimitedView; require the permission launchpad.View for IProductView and launchapd.LimietdView for IProductLimitedView

Description of the change

This branch splits the existing interface IProductLimitedView into
IProductView and IProductLimitedView. IProductView now requires the
permission lauchpad.View, and IProductLimitedView requires the
permission launchpad.LimitedView.

We want to make it possible to use artifact grants on bugs/branches/
blueprints for prioprietary/embargoed products, so that grantees
can see the artifacts but not all details about the product.

Even a call of canonical_url(artifact) requires access to
artifact.target.name, so people with artifact grants must be able
to see target.name, as well as several other attributes. I don't know
yet which attributes or even how many attributes need to be moved from
IProductView to IProductLimitedView, but I suspect that it will be
quite a few. Doing this all in one branch has the risk of a
"diff length explosion", so I started with just one attribute.

The new security adapter LimitedViewProduct allows access
not only for people with grants on the product itself, but it also
checks if the current user has any artifact grants related to the
product.

The new check is implementd in SharingService.userHasGrantsOnPillar(),
which is just a modified variant of the existing method
getSharedArtifacts(): While the latter method returns all artifacts
a user may access, the former method just checks, if the user has
any artifact grants. The common SQL query is generated in the new
method getArtifactGrantsForPersonOnPillar().

test:

bin/test -vvt lp.registry.tests.test_product.TestProduct.test_access_LimitedView
bin/test -vvt lp.registry.tests.test_product.TestProduct.test_get_permissions

no lint

To post a comment you must log in.
Revision history for this message
Francesco Banconi (frankban) wrote :

This branch looks good Abel, some minors follow.

28 +class IProductLimitedView(Interface):
29 + """Attributes that must be visible for person with artifact grants
30 + on bugs, branches or specifications for the product,
31 + """

The docstring ends with a comma.

203 + def test_access_LimitedView_proprietary_product(self):

Feel free to ignore this suggestion if you want: maybe this test could be
splitted in two parts: test unauthorized users and then test users with
access grant on something.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/registry/configure.zcml'
2--- lib/lp/registry/configure.zcml 2012-10-25 14:36:30 +0000
3+++ lib/lp/registry/configure.zcml 2012-11-06 14:20:25 +0000
4@@ -1248,10 +1248,13 @@
5 <allow
6 interface="lp.registry.interfaces.pillar.IPillar"/>
7 <require
8- permission="launchpad.View"
9+ permission="launchpad.LimitedView"
10 interface="lp.registry.interfaces.product.IProductLimitedView"/>
11 <require
12 permission="launchpad.View"
13+ interface="lp.registry.interfaces.product.IProductView"/>
14+ <require
15+ permission="launchpad.View"
16 interface="lp.bugs.interfaces.bugsummary.IBugSummaryDimension"/>
17 <require
18 permission="launchpad.View"
19
20=== modified file 'lib/lp/registry/interfaces/product.py'
21--- lib/lp/registry/interfaces/product.py 2012-10-24 14:21:58 +0000
22+++ lib/lp/registry/interfaces/product.py 2012-11-06 14:20:25 +0000
23@@ -428,7 +428,22 @@
24 """True if the given user has access to this product."""
25
26
27-class IProductLimitedView(
28+class IProductLimitedView(Interface):
29+ """Attributes that must be visible for person with artifact grants
30+ on bugs, branches or specifications for the product.
31+ """
32+
33+ name = exported(
34+ ProductNameField(
35+ title=_('Name'),
36+ constraint=name_validator,
37+ description=_(
38+ "At least one lowercase letter or number, followed by "
39+ "letters, numbers, dots, hyphens or pluses. "
40+ "Keep this name short; it is used in URLs as shown above.")))
41+
42+
43+class IProductView(
44 IBugTarget, ICanGetMilestonesDirectly, IHasAppointedDriver, IHasBranches,
45 IHasDrivers, IHasExternalBugTracker, IHasIcon,
46 IHasLogo, IHasMergeProposals, IHasMilestones, IHasExpirableBugs,
47@@ -488,15 +503,6 @@
48 "required because there might be a project driver and also a "
49 "driver appointed in the overarching project group.")
50
51- name = exported(
52- ProductNameField(
53- title=_('Name'),
54- constraint=name_validator,
55- description=_(
56- "At least one lowercase letter or number, followed by "
57- "letters, numbers, dots, hyphens or pluses. "
58- "Keep this name short; it is used in URLs as shown above.")))
59-
60 displayname = exported(
61 TextLine(
62 title=_('Display Name'),
63@@ -874,9 +880,9 @@
64 class IProductEditRestricted(IOfficialBugTagTargetRestricted):
65 """`IProduct` properties which require launchpad.Edit permission."""
66
67- @mutator_for(IProductLimitedView['bug_sharing_policy'])
68+ @mutator_for(IProductView['bug_sharing_policy'])
69 @operation_parameters(bug_sharing_policy=copy_field(
70- IProductLimitedView['bug_sharing_policy']))
71+ IProductView['bug_sharing_policy']))
72 @export_write_operation()
73 @operation_for_version("devel")
74 def setBugSharingPolicy(bug_sharing_policy):
75@@ -885,10 +891,10 @@
76 Checks authorization and entitlement.
77 """
78
79- @mutator_for(IProductLimitedView['branch_sharing_policy'])
80+ @mutator_for(IProductView['branch_sharing_policy'])
81 @operation_parameters(
82 branch_sharing_policy=copy_field(
83- IProductLimitedView['branch_sharing_policy']))
84+ IProductView['branch_sharing_policy']))
85 @export_write_operation()
86 @operation_for_version("devel")
87 def setBranchSharingPolicy(branch_sharing_policy):
88@@ -897,10 +903,10 @@
89 Checks authorization and entitlement.
90 """
91
92- @mutator_for(IProductLimitedView['specification_sharing_policy'])
93+ @mutator_for(IProductView['specification_sharing_policy'])
94 @operation_parameters(
95 specification_sharing_policy=copy_field(
96- IProductLimitedView['specification_sharing_policy']))
97+ IProductView['specification_sharing_policy']))
98 @export_write_operation()
99 @operation_for_version("devel")
100 def setSpecificationSharingPolicy(specification_sharing_policy):
101@@ -912,7 +918,7 @@
102
103 class IProduct(
104 IHasBugSupervisor, IProductEditRestricted,
105- IProductModerateRestricted, IProductDriverRestricted,
106+ IProductModerateRestricted, IProductDriverRestricted, IProductView,
107 IProductLimitedView, IProductPublic, IQuestionTarget, IRootContext,
108 IStructuralSubscriptionTarget, IInformationType, IPillar):
109 """A Product.
110
111=== modified file 'lib/lp/registry/interfaces/sharingservice.py'
112--- lib/lp/registry/interfaces/sharingservice.py 2012-10-10 03:20:17 +0000
113+++ lib/lp/registry/interfaces/sharingservice.py 2012-11-06 14:20:25 +0000
114@@ -117,6 +117,9 @@
115 :return: a (bugtasks, branches, specifications) tuple
116 """
117
118+ def userHasGrantsOnPillar(pillar, user):
119+ """Return True if user has any grants on pillar else return False."""
120+
121 @export_read_operation()
122 @call_with(user=REQUEST_USER)
123 @operation_parameters(
124
125=== modified file 'lib/lp/registry/services/sharingservice.py'
126--- lib/lp/registry/services/sharingservice.py 2012-10-10 02:45:36 +0000
127+++ lib/lp/registry/services/sharingservice.py 2012-11-06 14:20:25 +0000
128@@ -186,16 +186,21 @@
129 """See `ISharingService`."""
130 return self._getSharedPillars(person, user, Distribution)
131
132+ def getArtifactGrantsForPersonOnPillar(self, pillar, person):
133+ """Return the artifact grants for the given person and pillar."""
134+ policies = getUtility(IAccessPolicySource).findByPillar([pillar])
135+ flat_source = getUtility(IAccessPolicyGrantFlatSource)
136+ return flat_source.findArtifactsByGrantee(person, policies)
137+
138 @available_with_permission('launchpad.Driver', 'pillar')
139 def getSharedArtifacts(self, pillar, person, user, include_bugs=True,
140 include_branches=True, include_specifications=True):
141 """See `ISharingService`."""
142- policies = getUtility(IAccessPolicySource).findByPillar([pillar])
143- flat_source = getUtility(IAccessPolicyGrantFlatSource)
144 bug_ids = set()
145 branch_ids = set()
146 specification_ids = set()
147- for artifact in flat_source.findArtifactsByGrantee(person, policies):
148+ for artifact in self.getArtifactGrantsForPersonOnPillar(
149+ pillar, person):
150 if artifact.bug_id and include_bugs:
151 bug_ids.add(artifact.bug_id)
152 elif artifact.branch_id and include_branches:
153@@ -222,6 +227,11 @@
154
155 return bugtasks, branches, specifications
156
157+ def userHasGrantsOnPillar(self, pillar, user):
158+ """See `ISharingService`."""
159+ return not self.getArtifactGrantsForPersonOnPillar(
160+ pillar, user).is_empty()
161+
162 @available_with_permission('launchpad.Driver', 'pillar')
163 def getSharedBugs(self, pillar, person, user):
164 """See `ISharingService`."""
165
166=== modified file 'lib/lp/registry/tests/test_product.py'
167--- lib/lp/registry/tests/test_product.py 2012-11-02 20:08:55 +0000
168+++ lib/lp/registry/tests/test_product.py 2012-11-06 14:20:25 +0000
169@@ -551,6 +551,7 @@
170 CheckerPublic: set((
171 'active', 'id', 'information_type', 'pillar_category', 'private',
172 'userCanView',)),
173+ 'launchpad.LimitedView': set(('name', )),
174 'launchpad.View': set((
175 '_getOfficialTagClause', '_all_specifications',
176 '_valid_specifications', 'active_or_packaged_series',
177@@ -594,7 +595,7 @@
178 'homepageurl', 'icon', 'invitesTranslationEdits',
179 'invitesTranslationSuggestions',
180 'license_info', 'license_status', 'licenses', 'logo', 'milestones',
181- 'mugshot', 'name', 'name_with_project', 'newCodeImport',
182+ 'mugshot', 'name_with_project', 'newCodeImport',
183 'obsolete_translatable_series', 'official_answers',
184 'official_anything', 'official_blueprints', 'official_bug_tags',
185 'official_codehosting', 'official_malone', 'owner',
186@@ -766,6 +767,57 @@
187 for attribute_name in names:
188 getattr(product, attribute_name)
189
190+ def test_access_LimitedView_public_product(self):
191+ # Everybody can access attributes of public products that
192+ # require the permission launchpad.LimitedView.
193+ product = self.factory.makeProduct()
194+ names = self.expected_get_permissions['launchpad.LimitedView']
195+ with person_logged_in(None):
196+ for attribute_name in names:
197+ getattr(product, attribute_name)
198+ ordinary_user = self.factory.makePerson()
199+ with person_logged_in(ordinary_user):
200+ for attribute_name in names:
201+ getattr(product, attribute_name)
202+
203+ def test_access_LimitedView_proprietary_product(self):
204+ # Anonymous users and ordinary logged in users cannot access
205+ # attributes of private products that require the permission
206+ # launchpad.LimitedView.
207+ owner = self.factory.makePerson()
208+ product = self.factory.makeProduct(
209+ owner=owner,
210+ information_type=InformationType.PROPRIETARY)
211+ names = self.expected_get_permissions['launchpad.LimitedView']
212+ with person_logged_in(None):
213+ for attribute_name in names:
214+ self.assertRaises(
215+ Unauthorized, getattr, product, attribute_name)
216+ user = self.factory.makePerson()
217+ with person_logged_in(user):
218+ for attribute_name in names:
219+ self.assertRaises(
220+ Unauthorized, getattr, product, attribute_name)
221+ # Users with a grant on an artifact related to the product
222+ # can access the attributes.
223+ with person_logged_in(owner):
224+ bug = self.factory.makeBug(
225+ target=product, information_type=InformationType.PROPRIETARY)
226+ getUtility(IService, 'sharing').ensureAccessGrants(
227+ [user], owner, bugs=[bug])
228+ with person_logged_in(user):
229+ for attribute_name in names:
230+ getattr(product, attribute_name)
231+ # Users with a policy grant for the product also have access.
232+ user2 = self.factory.makePerson()
233+ with person_logged_in(owner):
234+ getUtility(IService, 'sharing').sharePillarInformation(
235+ product, user2, owner,
236+ {InformationType.PROPRIETARY: SharingPermission.ALL})
237+ with person_logged_in(user2):
238+ for attribute_name in names:
239+ getattr(product, attribute_name)
240+
241 def test_access_launchpad_AnyAllowedPerson_public_product(self):
242 # Only logged in persons have access to properties of public products
243 # that require the permission launchpad.AnyAllowedPerson.
244
245=== modified file 'lib/lp/security.py'
246--- lib/lp/security.py 2012-11-03 13:49:04 +0000
247+++ lib/lp/security.py 2012-11-06 14:20:25 +0000
248@@ -37,6 +37,7 @@
249 AuthorizationBase,
250 DelegatedAuthorization,
251 )
252+from lp.app.interfaces.services import IService
253 from lp.archivepublisher.interfaces.publisherconfig import IPublisherConfig
254 from lp.blueprints.interfaces.specification import (
255 ISpecification,
256@@ -441,6 +442,17 @@
257 return self.obj.userCanView(None)
258
259
260+class LimitedViewProduct(ViewProduct):
261+ permission = 'launchpad.LimitedView'
262+ usedfor = IProduct
263+
264+ def checkAuthenticated(self, user):
265+ return (
266+ super(LimitedViewProduct, self).checkAuthenticated(user) or
267+ getUtility(IService, 'sharing').userHasGrantsOnPillar(
268+ self.obj, user))
269+
270+
271 class ChangeProduct(ViewProduct):
272 """Used for attributes of IProduct that are accessible to any logged
273 in user for public product but only to persons with access grants