Merge ~lgp171188/launchpad:split-security.py-charms-oci-snappy into launchpad:master
- Git
- lp:~lgp171188/launchpad
- split-security.py-charms-oci-snappy
- Merge into master
Proposed by
Guruprasad
Status: | Merged |
---|---|
Approved by: | Guruprasad |
Approved revision: | 064be2fb8291f6e94991404a98bc4f332a6220d0 |
Merge reported by: | Otto Co-Pilot |
Merged at revision: | not available |
Proposed branch: | ~lgp171188/launchpad:split-security.py-charms-oci-snappy |
Merge into: | launchpad:master |
Diff against target: |
911 lines (+438/-397) 7 files modified
lib/lp/charms/configure.zcml (+1/-0) lib/lp/charms/security.py (+112/-0) lib/lp/oci/configure.zcml (+1/-0) lib/lp/oci/security.py (+157/-0) lib/lp/security.py (+1/-397) lib/lp/snappy/configure.zcml (+1/-0) lib/lp/snappy/security.py (+165/-0) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Colin Watson (community) | Approve | ||
Review via email: mp+426643@code.launchpad.net |
Commit message
Split and move the security adapters for the charms, oci, security packages
Description of the change
To post a comment you must log in.
Revision history for this message
Colin Watson (cjwatson) : | # |
review:
Approve
Revision history for this message
Guruprasad (lgp171188) : | # |
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/lib/lp/charms/configure.zcml b/lib/lp/charms/configure.zcml |
2 | index 330a72d..74d490d 100644 |
3 | --- a/lib/lp/charms/configure.zcml |
4 | +++ b/lib/lp/charms/configure.zcml |
5 | @@ -11,6 +11,7 @@ |
6 | xmlns:xmlrpc="http://namespaces.zope.org/xmlrpc" |
7 | i18n_domain="launchpad"> |
8 | |
9 | + <authorizations module=".security" /> |
10 | <include package=".browser" /> |
11 | |
12 | <lp:help-folder folder="help" name="+help-charms" /> |
13 | diff --git a/lib/lp/charms/security.py b/lib/lp/charms/security.py |
14 | new file mode 100644 |
15 | index 0000000..9d8dad2 |
16 | --- /dev/null |
17 | +++ b/lib/lp/charms/security.py |
18 | @@ -0,0 +1,112 @@ |
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 charms package.""" |
23 | + |
24 | +__all__ = [] |
25 | + |
26 | +from lp.app.security import ( |
27 | + AnonymousAuthorization, |
28 | + AuthorizationBase, |
29 | + DelegatedAuthorization, |
30 | +) |
31 | +from lp.charms.interfaces.charmbase import ICharmBase, ICharmBaseSet |
32 | +from lp.charms.interfaces.charmrecipe import ( |
33 | + ICharmRecipe, |
34 | + ICharmRecipeBuildRequest, |
35 | +) |
36 | +from lp.charms.interfaces.charmrecipebuild import ICharmRecipeBuild |
37 | +from lp.security import AdminByBuilddAdmin, EditByRegistryExpertsOrAdmins |
38 | + |
39 | + |
40 | +class ViewCharmRecipe(AuthorizationBase): |
41 | + """Private charm recipes are only visible to their owners and admins.""" |
42 | + |
43 | + permission = "launchpad.View" |
44 | + usedfor = ICharmRecipe |
45 | + |
46 | + def checkAuthenticated(self, user): |
47 | + return self.obj.visibleByUser(user.person) |
48 | + |
49 | + def checkUnauthenticated(self): |
50 | + return self.obj.visibleByUser(None) |
51 | + |
52 | + |
53 | +class EditCharmRecipe(AuthorizationBase): |
54 | + permission = "launchpad.Edit" |
55 | + usedfor = ICharmRecipe |
56 | + |
57 | + def checkAuthenticated(self, user): |
58 | + return ( |
59 | + user.isOwner(self.obj) or user.in_commercial_admin or user.in_admin |
60 | + ) |
61 | + |
62 | + |
63 | +class AdminCharmRecipe(AuthorizationBase): |
64 | + """Restrict changing build settings on charm recipes. |
65 | + |
66 | + The security of the non-virtualised build farm depends on these |
67 | + settings, so they can only be changed by "PPA"/commercial admins, or by |
68 | + "PPA" self admins on charm recipes that they can already edit. |
69 | + """ |
70 | + |
71 | + permission = "launchpad.Admin" |
72 | + usedfor = ICharmRecipe |
73 | + |
74 | + def checkAuthenticated(self, user): |
75 | + if user.in_ppa_admin or user.in_commercial_admin or user.in_admin: |
76 | + return True |
77 | + return user.in_ppa_self_admins and EditCharmRecipe( |
78 | + self.obj |
79 | + ).checkAuthenticated(user) |
80 | + |
81 | + |
82 | +class ViewCharmRecipeBuildRequest(DelegatedAuthorization): |
83 | + permission = "launchpad.View" |
84 | + usedfor = ICharmRecipeBuildRequest |
85 | + |
86 | + def __init__(self, obj): |
87 | + super().__init__(obj, obj.recipe, "launchpad.View") |
88 | + |
89 | + |
90 | +class ViewCharmRecipeBuild(DelegatedAuthorization): |
91 | + permission = "launchpad.View" |
92 | + usedfor = ICharmRecipeBuild |
93 | + |
94 | + def iter_objects(self): |
95 | + yield self.obj.recipe |
96 | + |
97 | + |
98 | +class EditCharmRecipeBuild(AdminByBuilddAdmin): |
99 | + permission = "launchpad.Edit" |
100 | + usedfor = ICharmRecipeBuild |
101 | + |
102 | + def checkAuthenticated(self, user): |
103 | + """Check edit access for snap package builds. |
104 | + |
105 | + Allow admins, buildd admins, and the owner of the charm recipe. |
106 | + (Note that the requester of the build is required to be in the team |
107 | + that owns the charm recipe.) |
108 | + """ |
109 | + auth_recipe = EditCharmRecipe(self.obj.recipe) |
110 | + if auth_recipe.checkAuthenticated(user): |
111 | + return True |
112 | + return super().checkAuthenticated(user) |
113 | + |
114 | + |
115 | +class AdminCharmRecipeBuild(AdminByBuilddAdmin): |
116 | + usedfor = ICharmRecipeBuild |
117 | + |
118 | + |
119 | +class ViewCharmBase(AnonymousAuthorization): |
120 | + """Anyone can view an `ICharmBase`.""" |
121 | + |
122 | + usedfor = ICharmBase |
123 | + |
124 | + |
125 | +class EditCharmBase(EditByRegistryExpertsOrAdmins): |
126 | + usedfor = ICharmBase |
127 | + |
128 | + |
129 | +class EditCharmBaseSet(EditByRegistryExpertsOrAdmins): |
130 | + usedfor = ICharmBaseSet |
131 | diff --git a/lib/lp/oci/configure.zcml b/lib/lp/oci/configure.zcml |
132 | index 82cf7ff..098a26d 100644 |
133 | --- a/lib/lp/oci/configure.zcml |
134 | +++ b/lib/lp/oci/configure.zcml |
135 | @@ -8,6 +8,7 @@ |
136 | xmlns:webservice="http://namespaces.canonical.com/webservice" |
137 | i18n_domain="launchpad"> |
138 | |
139 | + <authorizations module=".security" /> |
140 | <include package=".browser" /> |
141 | <include file="vocabularies.zcml" /> |
142 | |
143 | diff --git a/lib/lp/oci/security.py b/lib/lp/oci/security.py |
144 | new file mode 100644 |
145 | index 0000000..0546859 |
146 | --- /dev/null |
147 | +++ b/lib/lp/oci/security.py |
148 | @@ -0,0 +1,157 @@ |
149 | +# Copyright 2009-2022 Canonical Ltd. This software is licensed under the |
150 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
151 | + |
152 | +"""Security adapters for the oci package.""" |
153 | + |
154 | +__all__ = [] |
155 | + |
156 | +from lp.app.security import ( |
157 | + AnonymousAuthorization, |
158 | + AuthorizationBase, |
159 | + DelegatedAuthorization, |
160 | + ) |
161 | +from lp.oci.interfaces.ocipushrule import IOCIPushRule |
162 | +from lp.oci.interfaces.ocirecipe import ( |
163 | + IOCIRecipe, |
164 | + IOCIRecipeBuildRequest, |
165 | + ) |
166 | +from lp.oci.interfaces.ocirecipebuild import IOCIRecipeBuild |
167 | +from lp.oci.interfaces.ocirecipesubscription import IOCIRecipeSubscription |
168 | +from lp.oci.interfaces.ociregistrycredentials import IOCIRegistryCredentials |
169 | +from lp.security import AdminByBuilddAdmin |
170 | + |
171 | + |
172 | +class ViewOCIRecipeBuildRequest(DelegatedAuthorization): |
173 | + permission = 'launchpad.View' |
174 | + usedfor = IOCIRecipeBuildRequest |
175 | + |
176 | + def __init__(self, obj): |
177 | + super().__init__(obj, obj.recipe, 'launchpad.View') |
178 | + |
179 | + |
180 | +class ViewOCIRecipe(AnonymousAuthorization): |
181 | + """Anyone can view public `IOCIRecipe`, but only subscribers can view |
182 | + private ones. |
183 | + """ |
184 | + usedfor = IOCIRecipe |
185 | + |
186 | + def checkUnauthenticated(self): |
187 | + return self.obj.visibleByUser(None) |
188 | + |
189 | + def checkAuthenticated(self, user): |
190 | + return self.obj.visibleByUser(user.person) |
191 | + |
192 | + |
193 | +class EditOCIRecipe(AuthorizationBase): |
194 | + permission = 'launchpad.Edit' |
195 | + usedfor = IOCIRecipe |
196 | + |
197 | + def checkAuthenticated(self, user): |
198 | + return ( |
199 | + user.isOwner(self.obj) or |
200 | + user.in_commercial_admin or user.in_admin) |
201 | + |
202 | + |
203 | +class AdminOCIRecipe(AuthorizationBase): |
204 | + """Restrict changing build settings on OCI recipes. |
205 | + |
206 | + The security of the non-virtualised build farm depends on these |
207 | + settings, so they can only be changed by "PPA"/commercial admins, or by |
208 | + "PPA" self admins on OCI recipes that they can already edit. |
209 | + """ |
210 | + permission = 'launchpad.Admin' |
211 | + usedfor = IOCIRecipe |
212 | + |
213 | + def checkAuthenticated(self, user): |
214 | + if user.in_ppa_admin or user.in_commercial_admin or user.in_admin: |
215 | + return True |
216 | + return ( |
217 | + user.in_ppa_self_admins |
218 | + and EditOCIRecipe(self.obj).checkAuthenticated(user)) |
219 | + |
220 | + |
221 | +class OCIRecipeSubscriptionEdit(AuthorizationBase): |
222 | + permission = 'launchpad.Edit' |
223 | + usedfor = IOCIRecipeSubscription |
224 | + |
225 | + def checkAuthenticated(self, user): |
226 | + """Is the user able to edit an OCI recipe subscription? |
227 | + |
228 | + Any team member can edit a OCI recipe subscription for their |
229 | + team. |
230 | + Launchpad Admins can also edit any OCI recipe subscription. |
231 | + The owner of the subscribed OCI recipe can edit the subscription. If |
232 | + the OCI recipe owner is a team, then members of the team can edit |
233 | + the subscription. |
234 | + """ |
235 | + return (user.inTeam(self.obj.recipe.owner) or |
236 | + user.inTeam(self.obj.person) or |
237 | + user.inTeam(self.obj.subscribed_by) or |
238 | + user.in_admin) |
239 | + |
240 | + |
241 | +class OCIRecipeSubscriptionView(AuthorizationBase): |
242 | + permission = 'launchpad.View' |
243 | + usedfor = IOCIRecipeSubscription |
244 | + |
245 | + def checkUnauthenticated(self): |
246 | + return self.obj.recipe.visibleByUser(None) |
247 | + |
248 | + def checkAuthenticated(self, user): |
249 | + return self.obj.recipe.visibleByUser(user.person) |
250 | + |
251 | + |
252 | +class ViewOCIRecipeBuild(DelegatedAuthorization): |
253 | + permission = 'launchpad.View' |
254 | + usedfor = IOCIRecipeBuild |
255 | + |
256 | + def iter_objects(self): |
257 | + yield self.obj.recipe |
258 | + |
259 | + |
260 | +class EditOCIRecipeBuild(AdminByBuilddAdmin): |
261 | + permission = 'launchpad.Edit' |
262 | + usedfor = IOCIRecipeBuild |
263 | + |
264 | + def checkAuthenticated(self, user): |
265 | + """Check edit access for OCI recipe builds. |
266 | + |
267 | + Allow admins, buildd admins, and the owner of the OCI recipe. |
268 | + (Note that the requester of the build is required to be in the team |
269 | + that owns the OCI recipe.) |
270 | + """ |
271 | + auth_recipe = EditOCIRecipe(self.obj.recipe) |
272 | + if auth_recipe.checkAuthenticated(user): |
273 | + return True |
274 | + return super().checkAuthenticated(user) |
275 | + |
276 | + |
277 | +class AdminOCIRecipeBuild(AdminByBuilddAdmin): |
278 | + usedfor = IOCIRecipeBuild |
279 | + |
280 | + |
281 | +class ViewOCIRegistryCredentials(AuthorizationBase): |
282 | + permission = 'launchpad.View' |
283 | + usedfor = IOCIRegistryCredentials |
284 | + |
285 | + def checkAuthenticated(self, user): |
286 | + # This must be kept in sync with user_can_edit_credentials_for_owner |
287 | + # in lp.oci.interfaces.ociregistrycredentials. |
288 | + return ( |
289 | + user.isOwner(self.obj) or |
290 | + user.in_admin) |
291 | + |
292 | + |
293 | +class ViewOCIPushRule(AnonymousAuthorization): |
294 | + """Anyone can view an `IOCIPushRule`.""" |
295 | + usedfor = IOCIPushRule |
296 | + |
297 | + |
298 | +class OCIPushRuleEdit(AuthorizationBase): |
299 | + permission = 'launchpad.Edit' |
300 | + usedfor = IOCIPushRule |
301 | + |
302 | + def checkAuthenticated(self, user): |
303 | + return ( |
304 | + user.isOwner(self.obj.recipe) or |
305 | + user.in_commercial_admin or user.in_admin) |
306 | diff --git a/lib/lp/security.py b/lib/lp/security.py |
307 | index a87f341..71c71e8 100644 |
308 | --- a/lib/lp/security.py |
309 | +++ b/lib/lp/security.py |
310 | @@ -26,11 +26,7 @@ from datetime import ( |
311 | import pytz |
312 | from zope.interface import Interface |
313 | |
314 | -from lp.app.security import ( |
315 | - AnonymousAuthorization, |
316 | - AuthorizationBase, |
317 | - DelegatedAuthorization, |
318 | - ) |
319 | +from lp.app.security import AuthorizationBase |
320 | from lp.archivepublisher.interfaces.publisherconfig import IPublisherConfig |
321 | from lp.bugs.interfaces.bugtarget import IOfficialBugTagTargetRestricted |
322 | from lp.bugs.interfaces.structuralsubscription import IStructuralSubscription |
323 | @@ -40,40 +36,9 @@ from lp.buildmaster.interfaces.builder import ( |
324 | ) |
325 | from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJob |
326 | from lp.buildmaster.interfaces.packagebuild import IPackageBuild |
327 | -from lp.charms.interfaces.charmbase import ( |
328 | - ICharmBase, |
329 | - ICharmBaseSet, |
330 | - ) |
331 | -from lp.charms.interfaces.charmrecipe import ( |
332 | - ICharmRecipe, |
333 | - ICharmRecipeBuildRequest, |
334 | - ) |
335 | -from lp.charms.interfaces.charmrecipebuild import ICharmRecipeBuild |
336 | -from lp.oci.interfaces.ocipushrule import IOCIPushRule |
337 | -from lp.oci.interfaces.ocirecipe import ( |
338 | - IOCIRecipe, |
339 | - IOCIRecipeBuildRequest, |
340 | - ) |
341 | -from lp.oci.interfaces.ocirecipebuild import IOCIRecipeBuild |
342 | -from lp.oci.interfaces.ocirecipesubscription import IOCIRecipeSubscription |
343 | -from lp.oci.interfaces.ociregistrycredentials import IOCIRegistryCredentials |
344 | from lp.registry.interfaces.role import IHasOwner |
345 | from lp.services.config import config |
346 | from lp.services.webapp.interfaces import ILaunchpadRoot |
347 | -from lp.snappy.interfaces.snap import ( |
348 | - ISnap, |
349 | - ISnapBuildRequest, |
350 | - ) |
351 | -from lp.snappy.interfaces.snapbase import ( |
352 | - ISnapBase, |
353 | - ISnapBaseSet, |
354 | - ) |
355 | -from lp.snappy.interfaces.snapbuild import ISnapBuild |
356 | -from lp.snappy.interfaces.snappyseries import ( |
357 | - ISnappySeries, |
358 | - ISnappySeriesSet, |
359 | - ) |
360 | -from lp.snappy.interfaces.snapsubscription import ISnapSubscription |
361 | |
362 | |
363 | def is_commercial_case(obj, user): |
364 | @@ -312,364 +277,3 @@ class EditPackageBuild(EditBuildFarmJob): |
365 | |
366 | class ViewPublisherConfig(AdminByAdminsTeam): |
367 | usedfor = IPublisherConfig |
368 | - |
369 | - |
370 | -class ViewSnap(AuthorizationBase): |
371 | - """Private snaps are only visible to their owners and admins.""" |
372 | - permission = 'launchpad.View' |
373 | - usedfor = ISnap |
374 | - |
375 | - def checkAuthenticated(self, user): |
376 | - return self.obj.visibleByUser(user.person) |
377 | - |
378 | - def checkUnauthenticated(self): |
379 | - return self.obj.visibleByUser(None) |
380 | - |
381 | - |
382 | -class EditSnap(AuthorizationBase): |
383 | - permission = 'launchpad.Edit' |
384 | - usedfor = ISnap |
385 | - |
386 | - def checkAuthenticated(self, user): |
387 | - return ( |
388 | - user.isOwner(self.obj) or |
389 | - user.in_commercial_admin or user.in_admin) |
390 | - |
391 | - |
392 | -class AdminSnap(AuthorizationBase): |
393 | - """Restrict changing build settings on snap packages. |
394 | - |
395 | - The security of the non-virtualised build farm depends on these |
396 | - settings, so they can only be changed by "PPA"/commercial admins, or by |
397 | - "PPA" self admins on snap packages that they can already edit. |
398 | - """ |
399 | - permission = 'launchpad.Admin' |
400 | - usedfor = ISnap |
401 | - |
402 | - def checkAuthenticated(self, user): |
403 | - if user.in_ppa_admin or user.in_commercial_admin or user.in_admin: |
404 | - return True |
405 | - return ( |
406 | - user.in_ppa_self_admins |
407 | - and EditSnap(self.obj).checkAuthenticated(user)) |
408 | - |
409 | - |
410 | -class SnapSubscriptionEdit(AuthorizationBase): |
411 | - permission = 'launchpad.Edit' |
412 | - usedfor = ISnapSubscription |
413 | - |
414 | - def checkAuthenticated(self, user): |
415 | - """Is the user able to edit a Snap recipe subscription? |
416 | - |
417 | - Any team member can edit a Snap recipe subscription for their |
418 | - team. |
419 | - Launchpad Admins can also edit any Snap recipe subscription. |
420 | - The owner of the subscribed Snap can edit the subscription. If |
421 | - the Snap owner is a team, then members of the team can edit |
422 | - the subscription. |
423 | - """ |
424 | - return (user.inTeam(self.obj.snap.owner) or |
425 | - user.inTeam(self.obj.person) or |
426 | - user.inTeam(self.obj.subscribed_by) or |
427 | - user.in_admin) |
428 | - |
429 | - |
430 | -class SnapSubscriptionView(AuthorizationBase): |
431 | - permission = 'launchpad.View' |
432 | - usedfor = ISnapSubscription |
433 | - |
434 | - def checkUnauthenticated(self): |
435 | - return self.obj.snap.visibleByUser(None) |
436 | - |
437 | - def checkAuthenticated(self, user): |
438 | - return self.obj.snap.visibleByUser(user.person) |
439 | - |
440 | - |
441 | -class ViewSnapBuildRequest(DelegatedAuthorization): |
442 | - permission = 'launchpad.View' |
443 | - usedfor = ISnapBuildRequest |
444 | - |
445 | - def __init__(self, obj): |
446 | - super().__init__(obj, obj.snap, 'launchpad.View') |
447 | - |
448 | - |
449 | -class ViewSnapBuild(DelegatedAuthorization): |
450 | - permission = 'launchpad.View' |
451 | - usedfor = ISnapBuild |
452 | - |
453 | - def iter_objects(self): |
454 | - yield self.obj.snap |
455 | - yield self.obj.archive |
456 | - |
457 | - |
458 | -class EditSnapBuild(AdminByBuilddAdmin): |
459 | - permission = 'launchpad.Edit' |
460 | - usedfor = ISnapBuild |
461 | - |
462 | - def checkAuthenticated(self, user): |
463 | - """Check edit access for snap package builds. |
464 | - |
465 | - Allow admins, buildd admins, and the owner of the snap package. |
466 | - (Note that the requester of the build is required to be in the team |
467 | - that owns the snap package.) |
468 | - """ |
469 | - auth_snap = EditSnap(self.obj.snap) |
470 | - if auth_snap.checkAuthenticated(user): |
471 | - return True |
472 | - return super().checkAuthenticated(user) |
473 | - |
474 | - |
475 | -class AdminSnapBuild(AdminByBuilddAdmin): |
476 | - usedfor = ISnapBuild |
477 | - |
478 | - |
479 | -class ViewSnappySeries(AnonymousAuthorization): |
480 | - """Anyone can view an `ISnappySeries`.""" |
481 | - usedfor = ISnappySeries |
482 | - |
483 | - |
484 | -class EditSnappySeries(EditByRegistryExpertsOrAdmins): |
485 | - usedfor = ISnappySeries |
486 | - |
487 | - |
488 | -class EditSnappySeriesSet(EditByRegistryExpertsOrAdmins): |
489 | - usedfor = ISnappySeriesSet |
490 | - |
491 | - |
492 | -class ViewSnapBase(AnonymousAuthorization): |
493 | - """Anyone can view an `ISnapBase`.""" |
494 | - usedfor = ISnapBase |
495 | - |
496 | - |
497 | -class EditSnapBase(EditByRegistryExpertsOrAdmins): |
498 | - usedfor = ISnapBase |
499 | - |
500 | - |
501 | -class EditSnapBaseSet(EditByRegistryExpertsOrAdmins): |
502 | - usedfor = ISnapBaseSet |
503 | - |
504 | - |
505 | -class ViewOCIRecipeBuildRequest(DelegatedAuthorization): |
506 | - permission = 'launchpad.View' |
507 | - usedfor = IOCIRecipeBuildRequest |
508 | - |
509 | - def __init__(self, obj): |
510 | - super().__init__(obj, obj.recipe, 'launchpad.View') |
511 | - |
512 | - |
513 | -class ViewOCIRecipe(AnonymousAuthorization): |
514 | - """Anyone can view public `IOCIRecipe`, but only subscribers can view |
515 | - private ones. |
516 | - """ |
517 | - usedfor = IOCIRecipe |
518 | - |
519 | - def checkUnauthenticated(self): |
520 | - return self.obj.visibleByUser(None) |
521 | - |
522 | - def checkAuthenticated(self, user): |
523 | - return self.obj.visibleByUser(user.person) |
524 | - |
525 | - |
526 | -class EditOCIRecipe(AuthorizationBase): |
527 | - permission = 'launchpad.Edit' |
528 | - usedfor = IOCIRecipe |
529 | - |
530 | - def checkAuthenticated(self, user): |
531 | - return ( |
532 | - user.isOwner(self.obj) or |
533 | - user.in_commercial_admin or user.in_admin) |
534 | - |
535 | - |
536 | -class AdminOCIRecipe(AuthorizationBase): |
537 | - """Restrict changing build settings on OCI recipes. |
538 | - |
539 | - The security of the non-virtualised build farm depends on these |
540 | - settings, so they can only be changed by "PPA"/commercial admins, or by |
541 | - "PPA" self admins on OCI recipes that they can already edit. |
542 | - """ |
543 | - permission = 'launchpad.Admin' |
544 | - usedfor = IOCIRecipe |
545 | - |
546 | - def checkAuthenticated(self, user): |
547 | - if user.in_ppa_admin or user.in_commercial_admin or user.in_admin: |
548 | - return True |
549 | - return ( |
550 | - user.in_ppa_self_admins |
551 | - and EditSnap(self.obj).checkAuthenticated(user)) |
552 | - |
553 | - |
554 | -class OCIRecipeSubscriptionEdit(AuthorizationBase): |
555 | - permission = 'launchpad.Edit' |
556 | - usedfor = IOCIRecipeSubscription |
557 | - |
558 | - def checkAuthenticated(self, user): |
559 | - """Is the user able to edit an OCI recipe subscription? |
560 | - |
561 | - Any team member can edit a OCI recipe subscription for their |
562 | - team. |
563 | - Launchpad Admins can also edit any OCI recipe subscription. |
564 | - The owner of the subscribed OCI recipe can edit the subscription. If |
565 | - the OCI recipe owner is a team, then members of the team can edit |
566 | - the subscription. |
567 | - """ |
568 | - return (user.inTeam(self.obj.recipe.owner) or |
569 | - user.inTeam(self.obj.person) or |
570 | - user.inTeam(self.obj.subscribed_by) or |
571 | - user.in_admin) |
572 | - |
573 | - |
574 | -class OCIRecipeSubscriptionView(AuthorizationBase): |
575 | - permission = 'launchpad.View' |
576 | - usedfor = IOCIRecipeSubscription |
577 | - |
578 | - def checkUnauthenticated(self): |
579 | - return self.obj.recipe.visibleByUser(None) |
580 | - |
581 | - def checkAuthenticated(self, user): |
582 | - return self.obj.recipe.visibleByUser(user.person) |
583 | - |
584 | - |
585 | -class ViewOCIRecipeBuild(DelegatedAuthorization): |
586 | - permission = 'launchpad.View' |
587 | - usedfor = IOCIRecipeBuild |
588 | - |
589 | - def iter_objects(self): |
590 | - yield self.obj.recipe |
591 | - |
592 | - |
593 | -class EditOCIRecipeBuild(AdminByBuilddAdmin): |
594 | - permission = 'launchpad.Edit' |
595 | - usedfor = IOCIRecipeBuild |
596 | - |
597 | - def checkAuthenticated(self, user): |
598 | - """Check edit access for OCI recipe builds. |
599 | - |
600 | - Allow admins, buildd admins, and the owner of the OCI recipe. |
601 | - (Note that the requester of the build is required to be in the team |
602 | - that owns the OCI recipe.) |
603 | - """ |
604 | - auth_recipe = EditOCIRecipe(self.obj.recipe) |
605 | - if auth_recipe.checkAuthenticated(user): |
606 | - return True |
607 | - return super().checkAuthenticated(user) |
608 | - |
609 | - |
610 | -class AdminOCIRecipeBuild(AdminByBuilddAdmin): |
611 | - usedfor = IOCIRecipeBuild |
612 | - |
613 | - |
614 | -class ViewOCIRegistryCredentials(AuthorizationBase): |
615 | - permission = 'launchpad.View' |
616 | - usedfor = IOCIRegistryCredentials |
617 | - |
618 | - def checkAuthenticated(self, user): |
619 | - # This must be kept in sync with user_can_edit_credentials_for_owner |
620 | - # in lp.oci.interfaces.ociregistrycredentials. |
621 | - return ( |
622 | - user.isOwner(self.obj) or |
623 | - user.in_admin) |
624 | - |
625 | - |
626 | -class ViewOCIPushRule(AnonymousAuthorization): |
627 | - """Anyone can view an `IOCIPushRule`.""" |
628 | - usedfor = IOCIPushRule |
629 | - |
630 | - |
631 | -class OCIPushRuleEdit(AuthorizationBase): |
632 | - permission = 'launchpad.Edit' |
633 | - usedfor = IOCIPushRule |
634 | - |
635 | - def checkAuthenticated(self, user): |
636 | - return ( |
637 | - user.isOwner(self.obj.recipe) or |
638 | - user.in_commercial_admin or user.in_admin) |
639 | - |
640 | - |
641 | -class ViewCharmRecipe(AuthorizationBase): |
642 | - """Private charm recipes are only visible to their owners and admins.""" |
643 | - permission = 'launchpad.View' |
644 | - usedfor = ICharmRecipe |
645 | - |
646 | - def checkAuthenticated(self, user): |
647 | - return self.obj.visibleByUser(user.person) |
648 | - |
649 | - def checkUnauthenticated(self): |
650 | - return self.obj.visibleByUser(None) |
651 | - |
652 | - |
653 | -class EditCharmRecipe(AuthorizationBase): |
654 | - permission = 'launchpad.Edit' |
655 | - usedfor = ICharmRecipe |
656 | - |
657 | - def checkAuthenticated(self, user): |
658 | - return ( |
659 | - user.isOwner(self.obj) or |
660 | - user.in_commercial_admin or user.in_admin) |
661 | - |
662 | - |
663 | -class AdminCharmRecipe(AuthorizationBase): |
664 | - """Restrict changing build settings on charm recipes. |
665 | - |
666 | - The security of the non-virtualised build farm depends on these |
667 | - settings, so they can only be changed by "PPA"/commercial admins, or by |
668 | - "PPA" self admins on charm recipes that they can already edit. |
669 | - """ |
670 | - permission = 'launchpad.Admin' |
671 | - usedfor = ICharmRecipe |
672 | - |
673 | - def checkAuthenticated(self, user): |
674 | - if user.in_ppa_admin or user.in_commercial_admin or user.in_admin: |
675 | - return True |
676 | - return ( |
677 | - user.in_ppa_self_admins |
678 | - and EditCharmRecipe(self.obj).checkAuthenticated(user)) |
679 | - |
680 | - |
681 | -class ViewCharmRecipeBuildRequest(DelegatedAuthorization): |
682 | - permission = 'launchpad.View' |
683 | - usedfor = ICharmRecipeBuildRequest |
684 | - |
685 | - def __init__(self, obj): |
686 | - super().__init__(obj, obj.recipe, 'launchpad.View') |
687 | - |
688 | - |
689 | -class ViewCharmRecipeBuild(DelegatedAuthorization): |
690 | - permission = 'launchpad.View' |
691 | - usedfor = ICharmRecipeBuild |
692 | - |
693 | - def iter_objects(self): |
694 | - yield self.obj.recipe |
695 | - |
696 | - |
697 | -class EditCharmRecipeBuild(AdminByBuilddAdmin): |
698 | - permission = 'launchpad.Edit' |
699 | - usedfor = ICharmRecipeBuild |
700 | - |
701 | - def checkAuthenticated(self, user): |
702 | - """Check edit access for snap package builds. |
703 | - |
704 | - Allow admins, buildd admins, and the owner of the charm recipe. |
705 | - (Note that the requester of the build is required to be in the team |
706 | - that owns the charm recipe.) |
707 | - """ |
708 | - auth_recipe = EditCharmRecipe(self.obj.recipe) |
709 | - if auth_recipe.checkAuthenticated(user): |
710 | - return True |
711 | - return super().checkAuthenticated(user) |
712 | - |
713 | - |
714 | -class AdminCharmRecipeBuild(AdminByBuilddAdmin): |
715 | - usedfor = ICharmRecipeBuild |
716 | - |
717 | - |
718 | -class ViewCharmBase(AnonymousAuthorization): |
719 | - """Anyone can view an `ICharmBase`.""" |
720 | - usedfor = ICharmBase |
721 | - |
722 | - |
723 | -class EditCharmBase(EditByRegistryExpertsOrAdmins): |
724 | - usedfor = ICharmBase |
725 | - |
726 | - |
727 | -class EditCharmBaseSet(EditByRegistryExpertsOrAdmins): |
728 | - usedfor = ICharmBaseSet |
729 | diff --git a/lib/lp/snappy/configure.zcml b/lib/lp/snappy/configure.zcml |
730 | index 3dcc7cc..5f8081a 100644 |
731 | --- a/lib/lp/snappy/configure.zcml |
732 | +++ b/lib/lp/snappy/configure.zcml |
733 | @@ -11,6 +11,7 @@ |
734 | xmlns:xmlrpc="http://namespaces.zope.org/xmlrpc" |
735 | i18n_domain="launchpad"> |
736 | |
737 | + <authorizations module=".security" /> |
738 | <include package=".browser" /> |
739 | <include file="vocabularies.zcml" /> |
740 | |
741 | diff --git a/lib/lp/snappy/security.py b/lib/lp/snappy/security.py |
742 | new file mode 100644 |
743 | index 0000000..1c24258 |
744 | --- /dev/null |
745 | +++ b/lib/lp/snappy/security.py |
746 | @@ -0,0 +1,165 @@ |
747 | +# Copyright 2009-2022 Canonical Ltd. This software is licensed under the |
748 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
749 | + |
750 | +"""Security adapters for the snappy package.""" |
751 | + |
752 | +__all__ = [] |
753 | + |
754 | +from lp.app.security import ( |
755 | + AnonymousAuthorization, |
756 | + AuthorizationBase, |
757 | + DelegatedAuthorization, |
758 | + ) |
759 | +from lp.security import ( |
760 | + AdminByBuilddAdmin, |
761 | + EditByRegistryExpertsOrAdmins, |
762 | + ) |
763 | +from lp.snappy.interfaces.snap import ( |
764 | + ISnap, |
765 | + ISnapBuildRequest, |
766 | + ) |
767 | +from lp.snappy.interfaces.snapbase import ( |
768 | + ISnapBase, |
769 | + ISnapBaseSet, |
770 | + ) |
771 | +from lp.snappy.interfaces.snapbuild import ISnapBuild |
772 | +from lp.snappy.interfaces.snappyseries import ( |
773 | + ISnappySeries, |
774 | + ISnappySeriesSet, |
775 | + ) |
776 | +from lp.snappy.interfaces.snapsubscription import ISnapSubscription |
777 | + |
778 | + |
779 | +class ViewSnap(AuthorizationBase): |
780 | + """Private snaps are only visible to their owners and admins.""" |
781 | + permission = 'launchpad.View' |
782 | + usedfor = ISnap |
783 | + |
784 | + def checkAuthenticated(self, user): |
785 | + return self.obj.visibleByUser(user.person) |
786 | + |
787 | + def checkUnauthenticated(self): |
788 | + return self.obj.visibleByUser(None) |
789 | + |
790 | + |
791 | +class EditSnap(AuthorizationBase): |
792 | + permission = 'launchpad.Edit' |
793 | + usedfor = ISnap |
794 | + |
795 | + def checkAuthenticated(self, user): |
796 | + return ( |
797 | + user.isOwner(self.obj) or |
798 | + user.in_commercial_admin or user.in_admin) |
799 | + |
800 | + |
801 | +class AdminSnap(AuthorizationBase): |
802 | + """Restrict changing build settings on snap packages. |
803 | + |
804 | + The security of the non-virtualised build farm depends on these |
805 | + settings, so they can only be changed by "PPA"/commercial admins, or by |
806 | + "PPA" self admins on snap packages that they can already edit. |
807 | + """ |
808 | + permission = 'launchpad.Admin' |
809 | + usedfor = ISnap |
810 | + |
811 | + def checkAuthenticated(self, user): |
812 | + if user.in_ppa_admin or user.in_commercial_admin or user.in_admin: |
813 | + return True |
814 | + return ( |
815 | + user.in_ppa_self_admins |
816 | + and EditSnap(self.obj).checkAuthenticated(user)) |
817 | + |
818 | + |
819 | +class SnapSubscriptionEdit(AuthorizationBase): |
820 | + permission = 'launchpad.Edit' |
821 | + usedfor = ISnapSubscription |
822 | + |
823 | + def checkAuthenticated(self, user): |
824 | + """Is the user able to edit a Snap recipe subscription? |
825 | + |
826 | + Any team member can edit a Snap recipe subscription for their |
827 | + team. |
828 | + Launchpad Admins can also edit any Snap recipe subscription. |
829 | + The owner of the subscribed Snap can edit the subscription. If |
830 | + the Snap owner is a team, then members of the team can edit |
831 | + the subscription. |
832 | + """ |
833 | + return (user.inTeam(self.obj.snap.owner) or |
834 | + user.inTeam(self.obj.person) or |
835 | + user.inTeam(self.obj.subscribed_by) or |
836 | + user.in_admin) |
837 | + |
838 | + |
839 | +class SnapSubscriptionView(AuthorizationBase): |
840 | + permission = 'launchpad.View' |
841 | + usedfor = ISnapSubscription |
842 | + |
843 | + def checkUnauthenticated(self): |
844 | + return self.obj.snap.visibleByUser(None) |
845 | + |
846 | + def checkAuthenticated(self, user): |
847 | + return self.obj.snap.visibleByUser(user.person) |
848 | + |
849 | + |
850 | +class ViewSnapBuildRequest(DelegatedAuthorization): |
851 | + permission = 'launchpad.View' |
852 | + usedfor = ISnapBuildRequest |
853 | + |
854 | + def __init__(self, obj): |
855 | + super().__init__(obj, obj.snap, 'launchpad.View') |
856 | + |
857 | + |
858 | +class ViewSnapBuild(DelegatedAuthorization): |
859 | + permission = 'launchpad.View' |
860 | + usedfor = ISnapBuild |
861 | + |
862 | + def iter_objects(self): |
863 | + yield self.obj.snap |
864 | + yield self.obj.archive |
865 | + |
866 | + |
867 | +class EditSnapBuild(AdminByBuilddAdmin): |
868 | + permission = 'launchpad.Edit' |
869 | + usedfor = ISnapBuild |
870 | + |
871 | + def checkAuthenticated(self, user): |
872 | + """Check edit access for snap package builds. |
873 | + |
874 | + Allow admins, buildd admins, and the owner of the snap package. |
875 | + (Note that the requester of the build is required to be in the team |
876 | + that owns the snap package.) |
877 | + """ |
878 | + auth_snap = EditSnap(self.obj.snap) |
879 | + if auth_snap.checkAuthenticated(user): |
880 | + return True |
881 | + return super().checkAuthenticated(user) |
882 | + |
883 | + |
884 | +class AdminSnapBuild(AdminByBuilddAdmin): |
885 | + usedfor = ISnapBuild |
886 | + |
887 | + |
888 | +class ViewSnappySeries(AnonymousAuthorization): |
889 | + """Anyone can view an `ISnappySeries`.""" |
890 | + usedfor = ISnappySeries |
891 | + |
892 | + |
893 | +class EditSnappySeries(EditByRegistryExpertsOrAdmins): |
894 | + usedfor = ISnappySeries |
895 | + |
896 | + |
897 | +class EditSnappySeriesSet(EditByRegistryExpertsOrAdmins): |
898 | + usedfor = ISnappySeriesSet |
899 | + |
900 | + |
901 | +class ViewSnapBase(AnonymousAuthorization): |
902 | + """Anyone can view an `ISnapBase`.""" |
903 | + usedfor = ISnapBase |
904 | + |
905 | + |
906 | +class EditSnapBase(EditByRegistryExpertsOrAdmins): |
907 | + usedfor = ISnapBase |
908 | + |
909 | + |
910 | +class EditSnapBaseSet(EditByRegistryExpertsOrAdmins): |
911 | + usedfor = ISnapBaseSet |