Merge ~lgp171188/launchpad:split-security.py-blueprints into launchpad:master

Proposed by Guruprasad
Status: Merged
Approved by: Guruprasad
Approved revision: 01a3e3a8b000ee27e7aa7cd5141bf08e33b4dbc6
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~lgp171188/launchpad:split-security.py-blueprints
Merge into: launchpad:master
Diff against target: 450 lines (+210/-188)
4 files modified
lib/lp/blueprints/configure.zcml (+1/-0)
lib/lp/blueprints/security.py (+204/-0)
lib/lp/blueprints/tests/test_specification.py (+5/-5)
lib/lp/security.py (+0/-183)
Reviewer Review Type Date Requested Status
Colin Watson (community) Approve
Review via email: mp+426620@code.launchpad.net

Commit message

Move the blueprints-related security adapters to lp.blueprints.security

To post a comment you must log in.
Revision history for this message
Colin Watson (cjwatson) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/lib/lp/blueprints/configure.zcml b/lib/lp/blueprints/configure.zcml
2index 6279e87..36b9af7 100644
3--- a/lib/lp/blueprints/configure.zcml
4+++ b/lib/lp/blueprints/configure.zcml
5@@ -11,6 +11,7 @@
6 xmlns:lp="http://namespaces.canonical.com/lp"
7 i18n_domain="launchpad">
8
9+ <authorizations module=".security" />
10 <include package=".browser"/>
11 <include package=".vocabularies"/>
12
13diff --git a/lib/lp/blueprints/security.py b/lib/lp/blueprints/security.py
14new file mode 100644
15index 0000000..b4d8da2
16--- /dev/null
17+++ b/lib/lp/blueprints/security.py
18@@ -0,0 +1,204 @@
19+# Copyright 2009-2022 Canonical Ltd. This software is licensed under the
20+# GNU Affero General Public License version 3 (see the file LICENSE).
21+
22+"""Security adapters for the blueprints package."""
23+
24+__all__ = []
25+
26+from lp.app.security import AnonymousAuthorization, AuthorizationBase
27+from lp.blueprints.interfaces.specification import (
28+ ISpecification,
29+ ISpecificationPublic,
30+ ISpecificationView,
31+)
32+from lp.blueprints.interfaces.specificationbranch import ISpecificationBranch
33+from lp.blueprints.interfaces.specificationsubscription import (
34+ ISpecificationSubscription,
35+)
36+from lp.blueprints.interfaces.sprint import ISprint
37+from lp.blueprints.interfaces.sprintspecification import ISprintSpecification
38+from lp.security import EditByOwnersOrAdmins, ModerateByRegistryExpertsOrAdmins
39+
40+
41+class EditSpecificationBranch(AuthorizationBase):
42+
43+ usedfor = ISpecificationBranch
44+ permission = "launchpad.Edit"
45+
46+ def checkAuthenticated(self, user):
47+ """See `IAuthorization.checkAuthenticated`.
48+
49+ :return: True or False.
50+ """
51+ return True
52+
53+
54+class ViewSpecificationBranch(EditSpecificationBranch):
55+
56+ permission = "launchpad.View"
57+
58+ def checkUnauthenticated(self):
59+ """See `IAuthorization.checkUnauthenticated`.
60+
61+ :return: True or False.
62+ """
63+ return True
64+
65+
66+class AnonymousAccessToISpecificationPublic(AnonymousAuthorization):
67+ """Anonymous users have launchpad.View on ISpecificationPublic.
68+
69+ This is only needed because lazr.restful is hard-coded to check that
70+ permission before returning things in a collection.
71+ """
72+
73+ permission = "launchpad.View"
74+ usedfor = ISpecificationPublic
75+
76+
77+class ViewSpecification(AuthorizationBase):
78+
79+ permission = "launchpad.LimitedView"
80+ usedfor = ISpecificationView
81+
82+ def checkAuthenticated(self, user):
83+ return self.obj.userCanView(user)
84+
85+ def checkUnauthenticated(self):
86+ return self.obj.userCanView(None)
87+
88+
89+class EditSpecificationByRelatedPeople(AuthorizationBase):
90+ """We want everybody "related" to a specification to be able to edit it.
91+ You are related if you have a role on the spec, or if you have a role on
92+ the spec target (distro/product) or goal (distroseries/productseries).
93+ """
94+
95+ permission = "launchpad.Edit"
96+ usedfor = ISpecification
97+
98+ def checkAuthenticated(self, user):
99+ assert self.obj.target
100+ goal = self.obj.goal
101+ if goal is not None:
102+ if user.isOwner(goal) or user.isDriver(goal):
103+ return True
104+ return (
105+ user.in_admin
106+ or user.in_registry_experts
107+ or user.isOwner(self.obj.target)
108+ or user.isDriver(self.obj.target)
109+ or user.isOneOf(
110+ self.obj, ["owner", "drafter", "assignee", "approver"]
111+ )
112+ )
113+
114+
115+class AdminSpecification(AuthorizationBase):
116+ permission = "launchpad.Admin"
117+ usedfor = ISpecification
118+
119+ def checkAuthenticated(self, user):
120+ assert self.obj.target
121+ return (
122+ user.in_admin
123+ or user.in_registry_experts
124+ or user.isOwner(self.obj.target)
125+ or user.isDriver(self.obj.target)
126+ )
127+
128+
129+class DriverSpecification(AuthorizationBase):
130+ permission = "launchpad.Driver"
131+ usedfor = ISpecification
132+
133+ def checkAuthenticated(self, user):
134+ # If no goal is proposed for the spec then there can be no
135+ # drivers for it - we use launchpad.Driver on a spec to decide
136+ # if the person can see the page which lets you decide whether
137+ # to accept the goal, and if there is no goal then this is
138+ # extremely difficult to do :-)
139+ return self.obj.goal and self.forwardCheckAuthenticated(
140+ user, self.obj.goal
141+ )
142+
143+
144+class EditSprintSpecification(AuthorizationBase):
145+ """The sprint owner or driver can say what makes it onto the agenda for
146+ the sprint.
147+ """
148+
149+ permission = "launchpad.Driver"
150+ usedfor = ISprintSpecification
151+
152+ def checkAuthenticated(self, user):
153+ sprint = self.obj.sprint
154+ return user.isOwner(sprint) or user.isDriver(sprint) or user.in_admin
155+
156+
157+class DriveSprint(AuthorizationBase):
158+ """The sprint owner or driver can say what makes it onto the agenda for
159+ the sprint.
160+ """
161+
162+ permission = "launchpad.Driver"
163+ usedfor = ISprint
164+
165+ def checkAuthenticated(self, user):
166+ return (
167+ user.isOwner(self.obj) or user.isDriver(self.obj) or user.in_admin
168+ )
169+
170+
171+class ViewSprint(AuthorizationBase):
172+ """An attendee, owner, or driver of a sprint."""
173+
174+ permission = "launchpad.View"
175+ usedfor = ISprint
176+
177+ def checkAuthenticated(self, user):
178+ return (
179+ user.isOwner(self.obj)
180+ or user.isDriver(self.obj)
181+ or user.person
182+ in [attendance.attendee for attendance in self.obj.attendances]
183+ or user.in_admin
184+ )
185+
186+
187+class EditSprint(EditByOwnersOrAdmins):
188+ usedfor = ISprint
189+
190+
191+class ModerateSprint(ModerateByRegistryExpertsOrAdmins):
192+ """The sprint owner, registry experts, and admins can moderate sprints."""
193+
194+ permission = "launchpad.Moderate"
195+ usedfor = ISprint
196+
197+ def checkAuthenticated(self, user):
198+ return super().checkAuthenticated(user) or user.isOwner(self.obj)
199+
200+
201+class EditSpecificationSubscription(AuthorizationBase):
202+ """The subscriber, and people related to the spec or the target of the
203+ spec can determine who is essential."""
204+
205+ permission = "launchpad.Edit"
206+ usedfor = ISpecificationSubscription
207+
208+ def checkAuthenticated(self, user):
209+ if self.obj.specification.goal is not None:
210+ if user.isDriver(self.obj.specification.goal):
211+ return True
212+ else:
213+ if user.isDriver(self.obj.specification.target):
214+ return True
215+ return (
216+ user.inTeam(self.obj.person)
217+ or user.isOneOf(
218+ self.obj.specification,
219+ ["owner", "drafter", "assignee", "approver"],
220+ )
221+ or user.in_admin
222+ )
223diff --git a/lib/lp/blueprints/tests/test_specification.py b/lib/lp/blueprints/tests/test_specification.py
224index 211cb7d..bb01e66 100644
225--- a/lib/lp/blueprints/tests/test_specification.py
226+++ b/lib/lp/blueprints/tests/test_specification.py
227@@ -34,17 +34,17 @@ from lp.blueprints.model.specification import Specification
228 from lp.blueprints.model.specificationsearch import (
229 get_specification_privacy_filter,
230 )
231+from lp.blueprints.security import (
232+ AdminSpecification,
233+ EditSpecificationByRelatedPeople,
234+ ViewSpecification,
235+)
236 from lp.registry.enums import SharingPermission, SpecificationSharingPolicy
237 from lp.registry.interfaces.accesspolicy import (
238 IAccessArtifactGrantSource,
239 IAccessPolicySource,
240 )
241 from lp.registry.interfaces.person import IPersonSet
242-from lp.security import (
243- AdminSpecification,
244- EditSpecificationByRelatedPeople,
245- ViewSpecification,
246-)
247 from lp.services.propertycache import get_property_cache
248 from lp.services.webapp.authorization import check_permission
249 from lp.services.webapp.interaction import ANONYMOUS
250diff --git a/lib/lp/security.py b/lib/lp/security.py
251index bfe9868..46c0f4a 100644
252--- a/lib/lp/security.py
253+++ b/lib/lp/security.py
254@@ -34,17 +34,6 @@ from lp.app.security import (
255 DelegatedAuthorization,
256 )
257 from lp.archivepublisher.interfaces.publisherconfig import IPublisherConfig
258-from lp.blueprints.interfaces.specification import (
259- ISpecification,
260- ISpecificationPublic,
261- ISpecificationView,
262- )
263-from lp.blueprints.interfaces.specificationbranch import ISpecificationBranch
264-from lp.blueprints.interfaces.specificationsubscription import (
265- ISpecificationSubscription,
266- )
267-from lp.blueprints.interfaces.sprint import ISprint
268-from lp.blueprints.interfaces.sprintspecification import ISprintSpecification
269 from lp.bugs.interfaces.bugtarget import IOfficialBugTagTargetRestricted
270 from lp.bugs.interfaces.structuralsubscription import IStructuralSubscription
271 from lp.buildmaster.interfaces.builder import (
272@@ -268,178 +257,6 @@ class EditByOwnersOrAdmins(AuthorizationBase):
273 return user.isOwner(self.obj) or user.in_admin
274
275
276-class EditSpecificationBranch(AuthorizationBase):
277-
278- usedfor = ISpecificationBranch
279- permission = 'launchpad.Edit'
280-
281- def checkAuthenticated(self, user):
282- """See `IAuthorization.checkAuthenticated`.
283-
284- :return: True or False.
285- """
286- return True
287-
288-
289-class ViewSpecificationBranch(EditSpecificationBranch):
290-
291- permission = 'launchpad.View'
292-
293- def checkUnauthenticated(self):
294- """See `IAuthorization.checkUnauthenticated`.
295-
296- :return: True or False.
297- """
298- return True
299-
300-
301-class AnonymousAccessToISpecificationPublic(AnonymousAuthorization):
302- """Anonymous users have launchpad.View on ISpecificationPublic.
303-
304- This is only needed because lazr.restful is hard-coded to check that
305- permission before returning things in a collection.
306- """
307-
308- permission = 'launchpad.View'
309- usedfor = ISpecificationPublic
310-
311-
312-class ViewSpecification(AuthorizationBase):
313-
314- permission = 'launchpad.LimitedView'
315- usedfor = ISpecificationView
316-
317- def checkAuthenticated(self, user):
318- return self.obj.userCanView(user)
319-
320- def checkUnauthenticated(self):
321- return self.obj.userCanView(None)
322-
323-
324-class EditSpecificationByRelatedPeople(AuthorizationBase):
325- """We want everybody "related" to a specification to be able to edit it.
326- You are related if you have a role on the spec, or if you have a role on
327- the spec target (distro/product) or goal (distroseries/productseries).
328- """
329-
330- permission = 'launchpad.Edit'
331- usedfor = ISpecification
332-
333- def checkAuthenticated(self, user):
334- assert self.obj.target
335- goal = self.obj.goal
336- if goal is not None:
337- if user.isOwner(goal) or user.isDriver(goal):
338- return True
339- return (user.in_admin or
340- user.in_registry_experts or
341- user.isOwner(self.obj.target) or
342- user.isDriver(self.obj.target) or
343- user.isOneOf(
344- self.obj, ['owner', 'drafter', 'assignee', 'approver']))
345-
346-
347-class AdminSpecification(AuthorizationBase):
348- permission = 'launchpad.Admin'
349- usedfor = ISpecification
350-
351- def checkAuthenticated(self, user):
352- assert self.obj.target
353- return (
354- user.in_admin or
355- user.in_registry_experts or
356- user.isOwner(self.obj.target) or
357- user.isDriver(self.obj.target))
358-
359-
360-class DriverSpecification(AuthorizationBase):
361- permission = 'launchpad.Driver'
362- usedfor = ISpecification
363-
364- def checkAuthenticated(self, user):
365- # If no goal is proposed for the spec then there can be no
366- # drivers for it - we use launchpad.Driver on a spec to decide
367- # if the person can see the page which lets you decide whether
368- # to accept the goal, and if there is no goal then this is
369- # extremely difficult to do :-)
370- return (
371- self.obj.goal and
372- self.forwardCheckAuthenticated(user, self.obj.goal))
373-
374-
375-class EditSprintSpecification(AuthorizationBase):
376- """The sprint owner or driver can say what makes it onto the agenda for
377- the sprint.
378- """
379- permission = 'launchpad.Driver'
380- usedfor = ISprintSpecification
381-
382- def checkAuthenticated(self, user):
383- sprint = self.obj.sprint
384- return user.isOwner(sprint) or user.isDriver(sprint) or user.in_admin
385-
386-
387-class DriveSprint(AuthorizationBase):
388- """The sprint owner or driver can say what makes it onto the agenda for
389- the sprint.
390- """
391- permission = 'launchpad.Driver'
392- usedfor = ISprint
393-
394- def checkAuthenticated(self, user):
395- return (user.isOwner(self.obj) or
396- user.isDriver(self.obj) or
397- user.in_admin)
398-
399-
400-class ViewSprint(AuthorizationBase):
401- """An attendee, owner, or driver of a sprint."""
402- permission = 'launchpad.View'
403- usedfor = ISprint
404-
405- def checkAuthenticated(self, user):
406- return (user.isOwner(self.obj) or
407- user.isDriver(self.obj) or
408- user.person in [attendance.attendee
409- for attendance in self.obj.attendances] or
410- user.in_admin)
411-
412-
413-class EditSprint(EditByOwnersOrAdmins):
414- usedfor = ISprint
415-
416-
417-class ModerateSprint(ModerateByRegistryExpertsOrAdmins):
418- """The sprint owner, registry experts, and admins can moderate sprints."""
419- permission = 'launchpad.Moderate'
420- usedfor = ISprint
421-
422- def checkAuthenticated(self, user):
423- return (
424- super().checkAuthenticated(user) or
425- user.isOwner(self.obj))
426-
427-
428-class EditSpecificationSubscription(AuthorizationBase):
429- """The subscriber, and people related to the spec or the target of the
430- spec can determine who is essential."""
431- permission = 'launchpad.Edit'
432- usedfor = ISpecificationSubscription
433-
434- def checkAuthenticated(self, user):
435- if self.obj.specification.goal is not None:
436- if user.isDriver(self.obj.specification.goal):
437- return True
438- else:
439- if user.isDriver(self.obj.specification.target):
440- return True
441- return (user.inTeam(self.obj.person) or
442- user.isOneOf(
443- self.obj.specification,
444- ['owner', 'drafter', 'assignee', 'approver']) or
445- user.in_admin)
446-
447-
448 class OnlyRosettaExpertsAndAdmins(AuthorizationBase):
449 """Base class that allow access to Rosetta experts and Launchpad admins.
450 """

Subscribers

People subscribed via source and target branches

to status/vote changes: