Merge lp:~cjwatson/launchpad/pocket-queue-admin into lp:launchpad

Proposed by Colin Watson on 2012-08-01
Status: Merged
Approved by: Steve Kowalik on 2012-08-08
Approved revision: no longer in the source branch.
Merged at revision: 15774
Proposed branch: lp:~cjwatson/launchpad/pocket-queue-admin
Merge into: lp:launchpad
Diff against target: 1186 lines (+570/-88)
12 files modified
lib/lp/_schema_circular_imports.py (+17/-0)
lib/lp/security.py (+14/-5)
lib/lp/soyuz/browser/archivepermission.py (+3/-0)
lib/lp/soyuz/browser/queue.py (+14/-4)
lib/lp/soyuz/browser/tests/test_queue.py (+128/-12)
lib/lp/soyuz/interfaces/archive.py (+98/-4)
lib/lp/soyuz/interfaces/archivepermission.py (+54/-0)
lib/lp/soyuz/interfaces/queue.py (+2/-2)
lib/lp/soyuz/model/archive.py (+46/-13)
lib/lp/soyuz/model/archivepermission.py (+73/-14)
lib/lp/soyuz/scripts/packagecopier.py (+2/-1)
lib/lp/soyuz/stories/webservice/xx-archive.txt (+119/-33)
To merge this branch: bzr merge lp:~cjwatson/launchpad/pocket-queue-admin
Reviewer Review Type Date Requested Status
Steve Kowalik (community) code 2012-08-01 Approve on 2012-08-08
Review via email: mp+117630@code.launchpad.net

Commit Message

Allow per-pocket (and optionally per-series) queue admin permissions.

Description of the Change

== Summary ==

Bug 648611 reports that we don't have sufficient ability to delegate queue administration tasks for specific pockets (which are often managed in quite different ways by quite different sets of people). As a result, everything ends up going through the overworked ~ubuntu-archive team, and we often end up granting overly-broad privileges to people who don't need it in order to try to manage that overloading. Instead, we'd like to be able to grant explicit queue admin permissions to particular pockets.

== Proposed fix ==

Add the usual large cluster of Archive and ArchivePermission methods to support per-pocket queue admin permissions, and extend EditPackageUploadQueue and Archive.canAdministerQueue to check these.

I made the necessary schema changes some time ago and they've already been deployed.

== Pre-implementation notes ==

Discussion with Scott Kitterman revealed that at least some per-pocket queue admin permissions will need to be per-distroseries as well: for example, ~ubuntu-sru should only have queue admin on PROPOSED and UPDATES in stable releases, while ~ubuntu-release should only have queue admin on RELEASE and PROPOSED in the development release. I've implemented this. See also:

  https://code.launchpad.net/~cjwatson/launchpad/db-pocket-queue-admin/+merge/115734

== LOC Rationale ==

Archive permission handling generally seems to wind up being fairly verbose, and this has come out at +485. I have 3911 lines of credit at the moment so I think this should be tolerated; and this should let us get rid of delayed copies and the ubuntu-security celebrity, which will comfortably offset this.

== Tests ==

bin/test -vvct soyuz.browser.tests.test_queue -t archivepermission -t lib/lp/soyuz/stories/webservice/xx-archive.txt

== Demo and Q/A ==

On dogfood, create a pocket queue admin permission using Archive.newPocketQueueAdmin for a user who doesn't otherwise have queue admin permission, and try to accept/reject a package from an appropriate queue. Do likewise with a series-specific permission.

== Lint ==

A false positive due to pocketlint not understanding property setters, and doctest line length cruft which I don't think is worth major contortions to avoid (it's basically due to testing long URLs).

./lib/lp/soyuz/model/archive.py
     346: redefinition of function 'private' from line 342
./lib/lp/soyuz/stories/webservice/xx-archive.txt
      43: want exceeds 78 characters.
      47: want exceeds 78 characters.
     173: want exceeds 78 characters.
     190: want exceeds 78 characters.
     207: want exceeds 78 characters.
     224: want exceeds 78 characters.
     371: want exceeds 78 characters.
     432: want exceeds 78 characters.
     563: want exceeds 78 characters.
     625: want exceeds 78 characters.
     705: want exceeds 78 characters.
     725: want exceeds 78 characters.

To post a comment you must log in.
Steve Kowalik (stevenk) wrote :

Broadly, this looks excellent. Have you considered using login_person() from lp.testing so that you don't have to set e-mail addresses when you create roles for tests?

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/_schema_circular_imports.py'
2--- lib/lp/_schema_circular_imports.py 2012-07-25 04:35:35 +0000
3+++ lib/lp/_schema_circular_imports.py 2012-08-08 12:21:37 +0000
4@@ -412,6 +412,10 @@
5 patch_collection_return_type(
6 IArchive, 'getComponentsForQueueAdmin', IArchivePermission)
7 patch_collection_return_type(
8+ IArchive, 'getQueueAdminsForPocket', IArchivePermission)
9+patch_collection_return_type(
10+ IArchive, 'getPocketsForQueueAdmin', IArchivePermission)
11+patch_collection_return_type(
12 IArchive, 'getPocketsForUploader', IArchivePermission)
13 patch_collection_return_type(
14 IArchive, 'getUploadersForPocket', IArchivePermission)
15@@ -420,6 +424,7 @@
16 patch_entry_return_type(IArchive, 'newComponentUploader', IArchivePermission)
17 patch_entry_return_type(IArchive, 'newPocketUploader', IArchivePermission)
18 patch_entry_return_type(IArchive, 'newQueueAdmin', IArchivePermission)
19+patch_entry_return_type(IArchive, 'newPocketQueueAdmin', IArchivePermission)
20 patch_plain_parameter_type(IArchive, 'syncSources', 'from_archive', IArchive)
21 patch_plain_parameter_type(IArchive, 'syncSource', 'from_archive', IArchive)
22 patch_plain_parameter_type(IArchive, 'copyPackage', 'from_archive', IArchive)
23@@ -455,9 +460,21 @@
24 patch_choice_parameter_type(
25 IArchive, 'getUploadersForPocket', 'pocket', PackagePublishingPocket)
26 patch_choice_parameter_type(
27+ IArchive, 'getQueueAdminsForPocket', 'pocket', PackagePublishingPocket)
28+patch_plain_parameter_type(
29+ IArchive, 'getQueueAdminsForPocket', 'distroseries', IDistroSeries)
30+patch_choice_parameter_type(
31 IArchive, 'newPocketUploader', 'pocket', PackagePublishingPocket)
32 patch_choice_parameter_type(
33+ IArchive, 'newPocketQueueAdmin', 'pocket', PackagePublishingPocket)
34+patch_plain_parameter_type(
35+ IArchive, 'newPocketQueueAdmin', 'distroseries', IDistroSeries)
36+patch_choice_parameter_type(
37 IArchive, 'deletePocketUploader', 'pocket', PackagePublishingPocket)
38+patch_choice_parameter_type(
39+ IArchive, 'deletePocketQueueAdmin', 'pocket', PackagePublishingPocket)
40+patch_plain_parameter_type(
41+ IArchive, 'deletePocketQueueAdmin', 'distroseries', IDistroSeries)
42 patch_plain_parameter_type(
43 IArchive, 'newPackagesetUploader', 'packageset', IPackageset)
44 patch_plain_parameter_type(
45
46=== modified file 'lib/lp/security.py'
47--- lib/lp/security.py 2012-07-24 19:50:54 +0000
48+++ lib/lp/security.py 2012-08-08 12:21:37 +0000
49@@ -1659,10 +1659,18 @@
50 return True
51
52 permission_set = getUtility(IArchivePermissionSet)
53- permissions = permission_set.componentsForQueueAdmin(
54- self.obj.distroseries.distribution.all_distro_archives,
55- user.person)
56- return not permissions.is_empty()
57+ component_permissions = permission_set.componentsForQueueAdmin(
58+ self.obj.distroseries.distribution.all_distro_archives,
59+ user.person)
60+ if not component_permissions.is_empty():
61+ return True
62+ pocket_permissions = permission_set.pocketsForQueueAdmin(
63+ self.obj.distroseries.distribution.all_distro_archives,
64+ user.person)
65+ for permission in pocket_permissions:
66+ if permission.distroseries in (None, self.obj.distroseries):
67+ return True
68+ return False
69
70
71 class EditPlainPackageCopyJob(AuthorizationBase):
72@@ -1731,7 +1739,8 @@
73 return archive_append.checkAuthenticated(user)
74
75 return self.obj.archive.canAdministerQueue(
76- user.person, self.obj.components)
77+ user.person, self.obj.components, self.obj.pocket,
78+ self.obj.distroseries)
79
80
81 class AdminByBuilddAdmin(AuthorizationBase):
82
83=== modified file 'lib/lp/soyuz/browser/archivepermission.py'
84--- lib/lp/soyuz/browser/archivepermission.py 2012-06-13 11:11:22 +0000
85+++ lib/lp/soyuz/browser/archivepermission.py 2012-08-08 12:21:37 +0000
86@@ -50,6 +50,9 @@
87 self.context.distro_series_name))
88 elif self.context.pocket is not None:
89 item = "type=pocket&item=%s" % self.context.pocket.name
90+ # Queue admin permissions for pockets may be granted by series.
91+ if self.context.distroseries is not None:
92+ item += "&series=%s" % self.context.distroseries.name
93 else:
94 raise AssertionError(
95 "One of component, sourcepackagename or package set should "
96
97=== modified file 'lib/lp/soyuz/browser/queue.py'
98--- lib/lp/soyuz/browser/queue.py 2012-07-23 01:15:11 +0000
99+++ lib/lp/soyuz/browser/queue.py 2012-08-08 12:21:37 +0000
100@@ -335,10 +335,12 @@
101 # Get a list of components for which the user has rights to
102 # override to or from.
103 permission_set = getUtility(IArchivePermissionSet)
104- permissions = permission_set.componentsForQueueAdmin(
105+ component_permissions = permission_set.componentsForQueueAdmin(
106 self.context.main_archive, self.user)
107 allowed_components = set(
108- permission.component for permission in permissions)
109+ permission.component for permission in component_permissions)
110+ pocket_permissions = permission_set.pocketsForQueueAdmin(
111+ self.context.main_archive, self.user)
112
113 try:
114 if section_override:
115@@ -385,15 +387,23 @@
116 # Sources and binaries are mutually exclusive when it comes to
117 # overriding, so only one of these will be set.
118 try:
119+ for permission in pocket_permissions:
120+ if (permission.pocket == queue_item.pocket and
121+ permission.distroseries in (
122+ None, queue_item.distroseries)):
123+ item_allowed_components = (
124+ queue_item.distroseries.upload_components)
125+ else:
126+ item_allowed_components = allowed_components
127 source_overridden = queue_item.overrideSource(
128- new_component, new_section, allowed_components)
129+ new_component, new_section, item_allowed_components)
130 binary_changes = [{
131 "component": new_component,
132 "section": new_section,
133 "priority": new_priority,
134 }]
135 binary_overridden = queue_item.overrideBinaries(
136- binary_changes, allowed_components)
137+ binary_changes, item_allowed_components)
138 except (QueueAdminUnauthorizedError,
139 QueueInconsistentStateError) as info:
140 failure.append("FAILED: %s (%s)" %
141
142=== modified file 'lib/lp/soyuz/browser/tests/test_queue.py'
143--- lib/lp/soyuz/browser/tests/test_queue.py 2012-01-01 02:58:52 +0000
144+++ lib/lp/soyuz/browser/tests/test_queue.py 2012-08-08 12:21:37 +0000
145@@ -1,4 +1,4 @@
146-# Copyright 2010-2011 Canonical Ltd. This software is licensed under the
147+# Copyright 2010-2012 Canonical Ltd. This software is licensed under the
148 # GNU Affero General Public License version 3 (see the file LICENSE).
149
150 """Unit tests for QueueItemsView."""
151@@ -15,6 +15,7 @@
152 )
153
154 from lp.archiveuploader.tests import datadir
155+from lp.registry.interfaces.pocket import PackagePublishingPocket
156 from lp.services.webapp.servers import LaunchpadTestRequest
157 from lp.soyuz.browser.queue import CompletePackageUpload
158 from lp.soyuz.enums import PackageUploadStatus
159@@ -23,6 +24,7 @@
160 from lp.soyuz.tests.test_publishing import SoyuzTestPublisher
161 from lp.testing import (
162 login,
163+ login_person,
164 logout,
165 person_logged_in,
166 TestCaseWithFactory,
167@@ -50,6 +52,9 @@
168 self.test_publisher = SoyuzTestPublisher()
169 self.test_publisher.prepareBreezyAutotest()
170 distribution = self.test_publisher.distroseries.distribution
171+ self.second_series = self.factory.makeDistroSeries(
172+ distribution=distribution)
173+ self.factory.makeComponentSelection(self.second_series, 'main')
174 self.main_archive = distribution.getArchiveByComponent('main')
175 self.partner_archive = distribution.getArchiveByComponent('partner')
176
177@@ -68,6 +73,17 @@
178 sourcename='main-upload', spr_only=True,
179 component='main', changes_file_content=changes_file_content)
180 self.main_spr.package_upload.setNew()
181+ self.proposed_spr = self.test_publisher.getPubSource(
182+ sourcename='proposed-upload', spr_only=True,
183+ component='main', changes_file_content=changes_file_content,
184+ pocket=PackagePublishingPocket.PROPOSED)
185+ self.proposed_spr.package_upload.setNew()
186+ self.proposed_series_spr = self.test_publisher.getPubSource(
187+ sourcename='proposed-series-upload', spr_only=True,
188+ component='main', changes_file_content=changes_file_content,
189+ pocket=PackagePublishingPocket.PROPOSED,
190+ distroseries=self.second_series)
191+ self.proposed_series_spr.package_upload.setNew()
192
193 # Define the form that will be used to post to the view.
194 self.form = {
195@@ -78,26 +94,35 @@
196 # Create a user with queue admin rights for main, and a separate
197 # user with queue admin rights for partner (on the partner
198 # archive).
199- self.main_queue_admin = self.factory.makePerson(
200- email='main-queue@example.org')
201+ self.main_queue_admin = self.factory.makePerson()
202 getUtility(IArchivePermissionSet).newQueueAdmin(
203 distribution.getArchiveByComponent('main'),
204 self.main_queue_admin, self.main_spr.component)
205- self.partner_queue_admin = self.factory.makePerson(
206- email='partner-queue@example.org')
207+ self.partner_queue_admin = self.factory.makePerson()
208 getUtility(IArchivePermissionSet).newQueueAdmin(
209 distribution.getArchiveByComponent('partner'),
210 self.partner_queue_admin, self.partner_spr.component)
211
212+ # Create users with various pocket queue admin rights.
213+ self.proposed_queue_admin = self.factory.makePerson()
214+ getUtility(IArchivePermissionSet).newPocketQueueAdmin(
215+ self.main_archive, self.proposed_queue_admin,
216+ PackagePublishingPocket.PROPOSED)
217+ self.proposed_series_queue_admin = self.factory.makePerson()
218+ getUtility(IArchivePermissionSet).newPocketQueueAdmin(
219+ self.main_archive, self.proposed_series_queue_admin,
220+ PackagePublishingPocket.PROPOSED, distroseries=self.second_series)
221+
222 # We need to commit to ensure the changes file exists in the
223 # librarian.
224 transaction.commit()
225 logout()
226
227- def setupQueueView(self, request):
228+ def setupQueueView(self, request, series=None):
229 """A helper to create and setup the view for testing."""
230- view = queryMultiAdapter(
231- (self.test_publisher.distroseries, request), name="+queue")
232+ if series is None:
233+ series = self.test_publisher.distroseries
234+ view = queryMultiAdapter((series, request), name="+queue")
235 view.setupQueueList()
236 view.performQueueAction()
237 return view
238@@ -105,7 +130,7 @@
239 def test_main_admin_can_accept_main_upload(self):
240 # A person with queue admin access for main
241 # can accept uploads to the main archive.
242- login('main-queue@example.org')
243+ login_person(self.main_queue_admin)
244 self.assertTrue(
245 self.main_archive.canAdministerQueue(
246 self.main_queue_admin, self.main_spr.component))
247@@ -123,7 +148,7 @@
248 def test_main_admin_cannot_accept_partner_upload(self):
249 # A person with queue admin access for main cannot necessarily
250 # accept uploads to partner.
251- login('main-queue@example.org')
252+ login_person(self.main_queue_admin)
253 self.assertFalse(
254 self.partner_archive.canAdministerQueue(
255 self.main_queue_admin, self.partner_spr.component))
256@@ -160,7 +185,7 @@
257 def test_partner_admin_can_accept_partner_upload(self):
258 # A person with queue admin access for partner
259 # can accept uploads to the partner archive.
260- login('partner-queue@example.org')
261+ login_person(self.partner_queue_admin)
262 self.assertTrue(
263 self.partner_archive.canAdministerQueue(
264 self.partner_queue_admin, self.partner_spr.component))
265@@ -178,7 +203,7 @@
266 def test_partner_admin_cannot_accept_main_upload(self):
267 # A person with queue admin access for partner cannot necessarily
268 # accept uploads to main.
269- login('partner-queue@example.org')
270+ login_person(self.partner_queue_admin)
271 self.assertFalse(
272 self.main_archive.canAdministerQueue(
273 self.partner_queue_admin, self.main_spr.component))
274@@ -197,6 +222,97 @@
275 'NEW',
276 getUtility(IPackageUploadSet).get(package_upload_id).status.name)
277
278+ def test_proposed_admin_can_accept_proposed_upload(self):
279+ # A person with queue admin access for proposed can accept uploads
280+ # to the proposed pocket for any series.
281+ login_person(self.proposed_queue_admin)
282+ self.assertTrue(
283+ self.main_archive.canAdministerQueue(
284+ self.proposed_queue_admin,
285+ pocket=PackagePublishingPocket.PROPOSED))
286+ for distroseries in self.test_publisher.distroseries.distribution:
287+ self.assertTrue(
288+ self.main_archive.canAdministerQueue(
289+ self.proposed_queue_admin,
290+ pocket=PackagePublishingPocket.PROPOSED,
291+ distroseries=distroseries))
292+ package_upload_set = getUtility(IPackageUploadSet)
293+
294+ for spr in (self.proposed_spr, self.proposed_series_spr):
295+ package_upload_id = spr.package_upload.id
296+ self.form['QUEUE_ID'] = [package_upload_id]
297+ request = LaunchpadTestRequest(form=self.form)
298+ request.method = 'POST'
299+ self.setupQueueView(request, series=spr.upload_distroseries)
300+
301+ self.assertEqual(
302+ 'DONE', package_upload_set.get(package_upload_id).status.name)
303+
304+ def test_proposed_admin_cannot_accept_release_upload(self):
305+ # A person with queue admin access for proposed cannot necessarly
306+ # accept uploads to the release pocket.
307+ login_person(self.proposed_queue_admin)
308+ self.assertFalse(
309+ self.main_archive.canAdministerQueue(
310+ self.proposed_queue_admin,
311+ pocket=PackagePublishingPocket.RELEASE))
312+
313+ package_upload_id = self.main_spr.package_upload.id
314+ self.form['QUEUE_ID'] = [package_upload_id]
315+ request = LaunchpadTestRequest(form=self.form)
316+ request.method = 'POST'
317+ view = self.setupQueueView(request)
318+
319+ self.assertEqual(
320+ "FAILED: main-upload (You have no rights to accept "
321+ "component(s) 'main')",
322+ view.request.response.notifications[0].message)
323+ self.assertEqual(
324+ 'NEW',
325+ getUtility(IPackageUploadSet).get(package_upload_id).status.name)
326+
327+ def test_proposed_series_admin_can_accept_that_series_upload(self):
328+ # A person with queue admin access for proposed for one series can
329+ # accept uploads to that series.
330+ login_person(self.proposed_series_queue_admin)
331+ self.assertTrue(
332+ self.main_archive.canAdministerQueue(
333+ self.proposed_series_queue_admin,
334+ pocket=PackagePublishingPocket.PROPOSED,
335+ distroseries=self.second_series))
336+
337+ package_upload_id = self.proposed_series_spr.package_upload.id
338+ self.form['QUEUE_ID'] = [package_upload_id]
339+ request = LaunchpadTestRequest(form=self.form)
340+ request.method = 'POST'
341+ self.setupQueueView(request, series=self.second_series)
342+
343+ self.assertEqual(
344+ 'DONE',
345+ getUtility(IPackageUploadSet).get(package_upload_id).status.name)
346+
347+ def test_proposed_series_admin_cannot_accept_other_series_upload(self):
348+ # A person with queue admin access for proposed for one series
349+ # cannot necessarily accept uploads to other series.
350+ login_person(self.proposed_series_queue_admin)
351+ self.assertFalse(
352+ self.main_archive.canAdministerQueue(
353+ self.proposed_series_queue_admin,
354+ pocket=PackagePublishingPocket.PROPOSED,
355+ distroseries=self.test_publisher.distroseries))
356+
357+ package_upload_id = self.proposed_spr.package_upload.id
358+ self.form['QUEUE_ID'] = [package_upload_id]
359+ request = LaunchpadTestRequest(form=self.form)
360+ request.method = 'POST'
361+ view = self.setupQueueView(request)
362+
363+ self.assertEqual(
364+ "You do not have permission to act on queue items.", view.error)
365+ self.assertEqual(
366+ 'NEW',
367+ getUtility(IPackageUploadSet).get(package_upload_id).status.name)
368+
369
370 class TestQueueItemsView(TestCaseWithFactory):
371 """Unit tests for `QueueItemsView`."""
372
373=== modified file 'lib/lp/soyuz/interfaces/archive.py'
374--- lib/lp/soyuz/interfaces/archive.py 2012-07-30 16:48:37 +0000
375+++ lib/lp/soyuz/interfaces/archive.py 2012-08-08 12:21:37 +0000
376@@ -680,15 +680,20 @@
377 None otherwise.
378 """
379
380- def canAdministerQueue(person, components):
381+ def canAdministerQueue(person, components=None, pocket=None,
382+ distroseries=None):
383 """Check to see if person is allowed to administer queue items.
384
385 :param person: An `IPerson` who should be checked for authentication.
386 :param components: The context `IComponent`(s) for the check.
387+ :param pocket: The context `PackagePublishingPocket` for the check.
388+ :param distroseries: The context `IDistroSeries` for the check.
389
390 :return: True if 'person' is allowed to administer the package upload
391- queue for all given 'components'. If 'components' is empty or None,
392- check if 'person' has any queue admin permissions for this archive.
393+ queue for all given 'components', or for the given 'pocket'
394+ (optionally restricted to a single 'distroseries'). If 'components'
395+ is empty or None and 'pocket' is None, check if 'person' has any
396+ queue admin permissions for this archive.
397 """
398
399 def getFileByName(filename):
400@@ -1236,6 +1241,41 @@
401 :return: A list of `IArchivePermission` records.
402 """
403
404+ @operation_parameters(
405+ pocket=Choice(
406+ title=_("Pocket"),
407+ # Really PackagePublishingPocket, circular import fixed below.
408+ vocabulary=DBEnumeratedType,
409+ required=True),
410+ distroseries=Reference(
411+ # Really IDistroSeries, avoiding a circular import here.
412+ Interface,
413+ title=_("Distro series"), required=False),
414+ )
415+ # Really IArchivePermission, set below to avoid circular import.
416+ @operation_returns_collection_of(Interface)
417+ @export_read_operation()
418+ @operation_for_version("devel")
419+ def getQueueAdminsForPocket(pocket, distroseries=None):
420+ """Return `IArchivePermission` records for authorised queue admins.
421+
422+ :param pocket: A `PackagePublishingPocket`.
423+ :param distroseries: An optional `IDistroSeries`.
424+ :return: A list of `IArchivePermission` records.
425+ """
426+
427+ @operation_parameters(person=Reference(schema=IPerson))
428+ # Really IArchivePermission, set below to avoid circular import.
429+ @operation_returns_collection_of(Interface)
430+ @export_read_operation()
431+ @operation_for_version("devel")
432+ def getPocketsForQueueAdmin(person):
433+ """Return `IArchivePermission` for the person's queue admin pockets.
434+
435+ :param person: An `IPerson`.
436+ :return: A list of `IArchivePermission` records.
437+ """
438+
439 def hasAnyPermission(person):
440 """Whether or not this person has any permission at all on this
441 archive.
442@@ -1607,7 +1647,7 @@
443 """Add permission for a person to upload to a pocket.
444
445 :param person: An `IPerson` whom should be given permission.
446- :param component: A `PackagePublishingPocket`.
447+ :param pocket: A `PackagePublishingPocket`.
448 :return: An `IArchivePermission` which is the newly-created
449 permission.
450 :raises InvalidPocketForPartnerArchive: if this archive is a partner
451@@ -1636,6 +1676,34 @@
452
453 @operation_parameters(
454 person=Reference(schema=IPerson),
455+ pocket=Choice(
456+ title=_("Pocket"),
457+ # Really PackagePublishingPocket, circular import fixed below.
458+ vocabulary=DBEnumeratedType,
459+ required=True),
460+ distroseries=Reference(
461+ # Really IDistroSeries, avoiding a circular import here.
462+ Interface,
463+ title=_("Distro series"), required=True),
464+ )
465+ # Really IArchivePermission, set below to avoid circular import.
466+ @export_factory_operation(Interface, [])
467+ @operation_for_version("devel")
468+ def newPocketQueueAdmin(person, pocket, distroseries=None):
469+ """Add permission for a person to administer a distroseries queue.
470+
471+ The supplied person will gain permission to administer the
472+ distroseries queue for packages in the supplied series and pocket.
473+
474+ :param person: An `IPerson` whom should be given permission.
475+ :param pocket: A `PackagePublishingPocket`.
476+ :param distroseries: An optional `IDistroSeries`.
477+ :return: An `IArchivePermission` which is the newly-created
478+ permission.
479+ """
480+
481+ @operation_parameters(
482+ person=Reference(schema=IPerson),
483 # Really IPackageset, corrected in _schema_circular_imports to avoid
484 # circular import.
485 packageset=Reference(
486@@ -1696,6 +1764,7 @@
487 """Revoke permission for the person to upload to the pocket.
488
489 :param person: An `IPerson` whose permission should be revoked.
490+ :param distroseries: An `IDistroSeries`.
491 :param pocket: A `PackagePublishingPocket`.
492 """
493
494@@ -1716,6 +1785,31 @@
495
496 @operation_parameters(
497 person=Reference(schema=IPerson),
498+ pocket=Choice(
499+ title=_("Pocket"),
500+ # Really PackagePublishingPocket, circular import fixed below.
501+ vocabulary=DBEnumeratedType,
502+ required=True),
503+ distroseries=Reference(
504+ # Really IDistroSeries, avoiding a circular import here.
505+ Interface,
506+ title=_("Distro series"), required=True),
507+ )
508+ @export_write_operation()
509+ @operation_for_version("devel")
510+ def deletePocketQueueAdmin(person, pocket, distroseries=None):
511+ """Revoke permission for the person to administer distroseries queues.
512+
513+ The supplied person will lose permission to administer the
514+ distroseries queue for packages in the supplied series and pocket.
515+
516+ :param person: An `IPerson` whose permission should be revoked.
517+ :param pocket: A `PackagePublishingPocket`.
518+ :param distroseries: An optional `IDistroSeries`.
519+ """
520+
521+ @operation_parameters(
522+ person=Reference(schema=IPerson),
523 # Really IPackageset, corrected in _schema_circular_imports to avoid
524 # circular import.
525 packageset=Reference(
526
527=== modified file 'lib/lp/soyuz/interfaces/archivepermission.py'
528--- lib/lp/soyuz/interfaces/archivepermission.py 2012-06-07 10:28:07 +0000
529+++ lib/lp/soyuz/interfaces/archivepermission.py 2012-08-08 12:21:37 +0000
530@@ -31,6 +31,7 @@
531 )
532
533 from lp import _
534+from lp.registry.interfaces.distroseries import IDistroSeries
535 from lp.registry.interfaces.pocket import PackagePublishingPocket
536 from lp.registry.interfaces.sourcepackagename import ISourcePackageName
537 from lp.services.fields import PublicPersonChoice
538@@ -125,6 +126,15 @@
539 vocabulary=PackagePublishingPocket,
540 required=True))
541
542+ distroseries = exported(
543+ Reference(
544+ IDistroSeries,
545+ title=_("Distro series"),
546+ description=_(
547+ "The distro series that this permission is for (only for "
548+ "pocket permissions)."),
549+ required=False))
550+
551
552 class IArchiveUploader(IArchivePermission):
553 """Marker interface for URL traversal of uploader permissions."""
554@@ -357,6 +367,29 @@
555 'person' is allowed to administer the queue for.
556 """
557
558+ def queueAdminsForPocket(archive, pocket, distroseries=None):
559+ """The `ArchivePermission` records for authorised pocket queue admins.
560+
561+ :param archive: The context `IArchive` for the permission check.
562+ :param pocket: A `PackagePublishingPocket`.
563+ :param distroseries: An optional `IDistroSeries`.
564+
565+ :return: `ArchivePermission` records for all the persons who are
566+ allowed to administer the pocket upload queue.
567+ """
568+
569+ def pocketsForQueueAdmin(archive, person):
570+ """Return `ArchivePermission` for the person's queue admin pockets.
571+
572+ :param archive: The context `IArchive` for the permission check, or
573+ an iterable of `IArchive`s.
574+ :param person: An `IPerson` for whom you want to find out which
575+ pockets he has access to.
576+
577+ :return: `ArchivePermission` records for all the pockets that
578+ 'person' is allowed to administer the queue for.
579+ """
580+
581 def newPackageUploader(archive, person, sourcepackagename):
582 """Create and return a new `ArchivePermission` for an uploader.
583
584@@ -421,6 +454,18 @@
585 already exists.
586 """
587
588+ def newPocketQueueAdmin(archive, person, pocket, distroseries=None):
589+ """Create and return a new `ArchivePermission` for a queue admin.
590+
591+ :param archive: The context `IArchive` for the permission check.
592+ :param person: An `IPerson` for whom you want to add permission.
593+ :param pocket: A `PackagePublishingPocket`.
594+ :param distroseries: An optional `IDistroSeries`.
595+
596+ :return: The new `ArchivePermission`, or the existing one if it
597+ already exists.
598+ """
599+
600 def deletePackageUploader(archive, person, sourcepackagename):
601 """Revoke upload permissions for a person.
602
603@@ -466,3 +511,12 @@
604 :param person: An `IPerson` for whom you want to revoke permission.
605 :param component: An `IComponent` or a string package name.
606 """
607+
608+ def deletePocketQueueAdmin(archive, person, pocket, distroseries=None):
609+ """Revoke queue admin permissions for a person.
610+
611+ :param archive: The context `IArchive` for the permission check.
612+ :param person: An `IPerson` for whom you want to revoke permission.
613+ :param pocket: A `PackagePublishingPocket`.
614+ :param distroseries: An optional `IDistroSeries`.
615+ """
616
617=== modified file 'lib/lp/soyuz/interfaces/queue.py'
618--- lib/lp/soyuz/interfaces/queue.py 2012-07-23 01:15:11 +0000
619+++ lib/lp/soyuz/interfaces/queue.py 2012-08-08 12:21:37 +0000
620@@ -105,7 +105,7 @@
621 class IPackageUploadQueue(Interface):
622 """Used to establish permission to a group of package uploads.
623
624- Recieves an IDistroSeries and a PackageUploadStatus dbschema
625+ Receives an IDistroSeries and a PackageUploadStatus dbschema
626 on initialization.
627 No attributes exposed via interface, only used to check permissions.
628 """
629@@ -812,4 +812,4 @@
630 """An Object that has queue items"""
631
632 def getPackageUploadQueue(state):
633- """Return an IPackageUploadeQueue occording the given state."""
634+ """Return an IPackageUploadQueue according to the given state."""
635
636=== modified file 'lib/lp/soyuz/model/archive.py'
637--- lib/lp/soyuz/model/archive.py 2012-07-30 16:48:37 +0000
638+++ lib/lp/soyuz/model/archive.py 2012-08-08 12:21:37 +0000
639@@ -1052,10 +1052,11 @@
640 raise ComponentNotFound(e)
641 return self.addArchiveDependency(dependency, pocket, component)
642
643- def getPermissions(self, user, item, perm_type):
644+ def getPermissions(self, user, item, perm_type, distroseries=None):
645 """See `IArchive`."""
646 permission_set = getUtility(IArchivePermissionSet)
647- return permission_set.checkAuthenticated(user, self, perm_type, item)
648+ return permission_set.checkAuthenticated(
649+ user, self, perm_type, item, distroseries=distroseries)
650
651 def getPermissionsForPerson(self, person):
652 """See `IArchive`."""
653@@ -1087,6 +1088,17 @@
654 permission_set = getUtility(IArchivePermissionSet)
655 return permission_set.componentsForQueueAdmin(self, person)
656
657+ def getQueueAdminsForPocket(self, pocket, distroseries=None):
658+ """See `IArchive`."""
659+ permission_set = getUtility(IArchivePermissionSet)
660+ return permission_set.queueAdminsForPocket(
661+ self, pocket, distroseries=distroseries)
662+
663+ def getPocketsForQueueAdmin(self, person):
664+ """See `IArchive`."""
665+ permission_set = getUtility(IArchivePermissionSet)
666+ return permission_set.pocketsForQueueAdmin(self, person)
667+
668 def hasAnyPermission(self, person):
669 """See `IArchive`."""
670 # Avoiding circular imports.
671@@ -1347,24 +1359,33 @@
672
673 return None
674
675- def canAdministerQueue(self, user, components):
676+ def canAdministerQueue(self, user, components=None, pocket=None,
677+ distroseries=None):
678 """See `IArchive`."""
679 if components is None:
680 components = []
681 elif IComponent.providedBy(components):
682 components = [components]
683- permissions = self.getComponentsForQueueAdmin(user)
684- if permissions.count() == 0:
685- return False
686- allowed_components = set(
687- permission.component for permission in permissions)
688- # The intersection of allowed_components and components must be
689- # equal to components to allow the operation to go ahead.
690- return allowed_components.intersection(components) == set(components)
691+ component_permissions = self.getComponentsForQueueAdmin(user)
692+ if not component_permissions.is_empty():
693+ allowed_components = set(
694+ permission.component for permission in component_permissions)
695+ # The intersection of allowed_components and components must be
696+ # equal to components to allow the operation to go ahead.
697+ if allowed_components.intersection(components) == set(components):
698+ return True
699+ if pocket is not None:
700+ pocket_permissions = self.getPocketsForQueueAdmin(user)
701+ for permission in pocket_permissions:
702+ if (permission.pocket == pocket and
703+ permission.distroseries in (None, distroseries)):
704+ return True
705+ return False
706
707- def _authenticate(self, user, item, permission):
708+ def _authenticate(self, user, item, permission, distroseries=None):
709 """Private helper method to check permissions."""
710- permissions = self.getPermissions(user, item, permission)
711+ permissions = self.getPermissions(
712+ user, item, permission, distroseries=distroseries)
713 return bool(permissions)
714
715 def newPackageUploader(self, person, source_package_name):
716@@ -1416,6 +1437,12 @@
717 permission_set = getUtility(IArchivePermissionSet)
718 return permission_set.newQueueAdmin(self, person, component_name)
719
720+ def newPocketQueueAdmin(self, person, pocket, distroseries=None):
721+ """See `IArchive`."""
722+ permission_set = getUtility(IArchivePermissionSet)
723+ return permission_set.newPocketQueueAdmin(
724+ self, person, pocket, distroseries=distroseries)
725+
726 def deletePackageUploader(self, person, source_package_name):
727 """See `IArchive`."""
728 permission_set = getUtility(IArchivePermissionSet)
729@@ -1438,6 +1465,12 @@
730 permission_set = getUtility(IArchivePermissionSet)
731 return permission_set.deleteQueueAdmin(self, person, component_name)
732
733+ def deletePocketQueueAdmin(self, person, pocket, distroseries=None):
734+ """See `IArchive`."""
735+ permission_set = getUtility(IArchivePermissionSet)
736+ return permission_set.deletePocketQueueAdmin(
737+ self, person, pocket, distroseries=distroseries)
738+
739 def getUploadersForPackageset(self, packageset, direct_permissions=True):
740 """See `IArchive`."""
741 permission_set = getUtility(IArchivePermissionSet)
742
743=== modified file 'lib/lp/soyuz/model/archivepermission.py'
744--- lib/lp/soyuz/model/archivepermission.py 2012-06-07 10:28:07 +0000
745+++ lib/lp/soyuz/model/archivepermission.py 2012-08-08 12:21:37 +0000
746@@ -106,6 +106,9 @@
747
748 pocket = EnumCol(dbName="pocket", schema=PackagePublishingPocket)
749
750+ distroseries = ForeignKey(
751+ foreignKey='DistroSeries', dbName='distroseries', notNull=False)
752+
753 def _init(self, *args, **kw):
754 """Provide the right interface for URL traversal."""
755 SQLBase._init(self, *args, **kw)
756@@ -150,6 +153,8 @@
757 """See `IArchivePermission`"""
758 if self.packageset:
759 return self.packageset.distroseries.name
760+ elif self.distroseries:
761+ return self.distroseries.name
762 else:
763 return None
764
765@@ -158,7 +163,8 @@
766 """See `IArchivePermissionSet`."""
767 implements(IArchivePermissionSet)
768
769- def checkAuthenticated(self, person, archive, permission, item):
770+ def checkAuthenticated(self, person, archive, permission, item,
771+ distroseries=None):
772 """See `IArchivePermissionSet`."""
773 clauses = ["""
774 ArchivePermission.archive = %s AND
775@@ -184,6 +190,12 @@
776 elif (zope_isinstance(item, DBItem) and
777 item.enum.name == "PackagePublishingPocket"):
778 clauses.append("ArchivePermission.pocket = %s" % sqlvalues(item))
779+ if distroseries is not None:
780+ clauses.append(
781+ "ArchivePermission.distroseries IS NULL OR "
782+ "ArchivePermission.distroseries = %s" %
783+ sqlvalues(distroseries))
784+ prejoins.append("distroseries")
785 else:
786 raise AssertionError(
787 "'item' %r is not an IComponent, IPackageset, "
788@@ -248,7 +260,7 @@
789 archive, person, ArchivePermissionType.UPLOAD)
790
791 def uploadersForComponent(self, archive, component=None):
792- "See `IArchivePermissionSet`."""
793+ """See `IArchivePermissionSet`."""
794 clauses = ["""
795 ArchivePermission.archive = %s AND
796 ArchivePermission.permission = %s
797@@ -278,33 +290,42 @@
798 prejoins=["sourcepackagename"])
799
800 def uploadersForPackage(self, archive, sourcepackagename):
801- "See `IArchivePermissionSet`."""
802+ """See `IArchivePermissionSet`."""
803 sourcepackagename = self._nameToSourcePackageName(sourcepackagename)
804 results = ArchivePermission.selectBy(
805 archive=archive, permission=ArchivePermissionType.UPLOAD,
806 sourcepackagename=sourcepackagename)
807 return results.prejoin(["sourcepackagename"])
808
809+ def _pocketsFor(self, archives, person, permission_type):
810+ """Helper function to get ArchivePermission objects."""
811+ if IArchive.providedBy(archives):
812+ archive_ids = [archives.id]
813+ else:
814+ archive_ids = [archive.id for archive in archives]
815+
816+ return ArchivePermission.select("""
817+ ArchivePermission.archive IN %s AND
818+ ArchivePermission.permission = %s AND
819+ ArchivePermission.pocket IS NOT NULL AND
820+ EXISTS (SELECT TeamParticipation.person
821+ FROM TeamParticipation
822+ WHERE TeamParticipation.person = %s AND
823+ TeamParticipation.team = ArchivePermission.person)
824+ """ % sqlvalues(archive_ids, permission_type, person))
825+
826 def pocketsForUploader(self, archive, person):
827 """See `IArchivePermissionSet`."""
828- return ArchivePermission.select("""
829- ArchivePermission.archive = %s AND
830- ArchivePermission.permission = %s AND
831- ArchivePermission.pocket IS NOT NULL AND
832- EXISTS (SELECT TeamParticipation.person
833- FROM TeamParticipation
834- WHERE TeamParticipation.person = %s AND
835- TeamParticipation.team = ArchivePermission.person)
836- """ % sqlvalues(archive, ArchivePermissionType.UPLOAD, person))
837+ return self._pocketsFor(archive, person, ArchivePermissionType.UPLOAD)
838
839 def uploadersForPocket(self, archive, pocket):
840- "See `IArchivePermissionSet`."""
841+ """See `IArchivePermissionSet`."""
842 return ArchivePermission.selectBy(
843 archive=archive, permission=ArchivePermissionType.UPLOAD,
844 pocket=pocket)
845
846 def queueAdminsForComponent(self, archive, component):
847- "See `IArchivePermissionSet`."""
848+ """See `IArchivePermissionSet`."""
849 component = self._nameToComponent(component)
850 results = ArchivePermission.selectBy(
851 archive=archive, permission=ArchivePermissionType.QUEUE_ADMIN,
852@@ -316,6 +337,20 @@
853 return self._componentsFor(
854 archive, person, ArchivePermissionType.QUEUE_ADMIN)
855
856+ def queueAdminsForPocket(self, archive, pocket, distroseries=None):
857+ """See `IArchivePermissionSet`."""
858+ kwargs = {}
859+ if distroseries is not None:
860+ kwargs["distroseries"] = distroseries
861+ return ArchivePermission.selectBy(
862+ archive=archive, permission=ArchivePermissionType.QUEUE_ADMIN,
863+ pocket=pocket, **kwargs)
864+
865+ def pocketsForQueueAdmin(self, archive, person):
866+ """See `IArchivePermissionSet`."""
867+ return self._pocketsFor(
868+ archive, person, ArchivePermissionType.QUEUE_ADMIN)
869+
870 def newPackageUploader(self, archive, person, sourcepackagename):
871 """See `IArchivePermissionSet`."""
872 sourcepackagename = self._nameToSourcePackageName(sourcepackagename)
873@@ -364,6 +399,19 @@
874 archive=archive, person=person, component=component,
875 permission=ArchivePermissionType.QUEUE_ADMIN)
876
877+ def newPocketQueueAdmin(self, archive, person, pocket, distroseries=None):
878+ """See `IArchivePermissionSet`."""
879+ existing = self.checkAuthenticated(
880+ person, archive, ArchivePermissionType.QUEUE_ADMIN, pocket,
881+ distroseries=distroseries)
882+ try:
883+ return existing[0]
884+ except IndexError:
885+ return ArchivePermission(
886+ archive=archive, person=person, pocket=pocket,
887+ distroseries=distroseries,
888+ permission=ArchivePermissionType.QUEUE_ADMIN)
889+
890 @staticmethod
891 def _remove_permission(permission):
892 if permission is None:
893@@ -404,6 +452,17 @@
894 permission=ArchivePermissionType.QUEUE_ADMIN)
895 self._remove_permission(permission)
896
897+ def deletePocketQueueAdmin(self, archive, person, pocket,
898+ distroseries=None):
899+ """See `IArchivePermissionSet`."""
900+ kwargs = {}
901+ if distroseries is not None:
902+ kwargs["distroseries"] = distroseries
903+ permission = ArchivePermission.selectOneBy(
904+ archive=archive, person=person, pocket=pocket,
905+ permission=ArchivePermissionType.QUEUE_ADMIN, **kwargs)
906+ self._remove_permission(permission)
907+
908 def _nameToPackageset(self, packageset):
909 """Helper to convert a possible string name to IPackageset."""
910 if isinstance(packageset, basestring):
911
912=== modified file 'lib/lp/soyuz/scripts/packagecopier.py'
913--- lib/lp/soyuz/scripts/packagecopier.py 2012-08-08 05:43:18 +0000
914+++ lib/lp/soyuz/scripts/packagecopier.py 2012-08-08 12:21:37 +0000
915@@ -225,7 +225,8 @@
916 strict_component=strict_component, pocket=pocket)
917 if reason is not None:
918 # Queue admins are allowed to copy even if they can't upload.
919- if not archive.canAdministerQueue(person, destination_component):
920+ if not archive.canAdministerQueue(
921+ person, destination_component, pocket, dest_series):
922 raise CannotCopy(reason)
923
924
925
926=== modified file 'lib/lp/soyuz/stories/webservice/xx-archive.txt'
927--- lib/lp/soyuz/stories/webservice/xx-archive.txt 2012-06-14 03:22:43 +0000
928+++ lib/lp/soyuz/stories/webservice/xx-archive.txt 2012-08-08 12:21:37 +0000
929@@ -253,10 +253,11 @@
930 ... print entry['component_name']
931 ... print entry['source_package_name']
932 ... print entry['pocket']
933+ ... print entry['distroseries_link']
934
935 >>> show_permission_entries(permissions)
936- Archive Upload Rights ...~ubuntu-team main None None
937- Archive Upload Rights ...~ubuntu-team universe None None
938+ Archive Upload Rights ...~ubuntu-team main None None None
939+ Archive Upload Rights ...~ubuntu-team universe None None None
940
941 `getUploadersForPackage` returns all the permissions where someone can
942 upload a particular package.
943@@ -268,7 +269,7 @@
944 ... show_permission_entries(permissions)
945
946 >>> show_mozilla_permissions()
947- Archive Upload Rights ...~carlos None mozilla-firefox None
948+ Archive Upload Rights ...~carlos None mozilla-firefox None None
949
950 Passing a bad package name results in an error:
951
952@@ -294,7 +295,7 @@
953
954 Let's also make a new Person to own the Ubuntu distro.
955
956- >>> ubuntu_owner = factory.makePerson()
957+ >>> ubuntu_owner = factory.makePerson(name='ubuntu-owner')
958 >>> ubuntu_db.owner = ubuntu_owner
959
960 >>> logout()
961@@ -370,8 +371,8 @@
962 http://.../ubuntu/+archive/primary/+upload/name12?type=packagename&item=mozilla-firefox
963
964 >>> show_mozilla_permissions()
965- Archive Upload Rights ...~carlos None mozilla-firefox None
966- Archive Upload Rights ...~name12 None mozilla-firefox None
967+ Archive Upload Rights ...~carlos None mozilla-firefox None None
968+ Archive Upload Rights ...~name12 None mozilla-firefox None None
969
970 deletePackageUploader() removes that permission:
971
972@@ -385,7 +386,7 @@
973 And we can see that it's gone:
974
975 >>> show_mozilla_permissions()
976- Archive Upload Rights ...~carlos None mozilla-firefox None
977+ Archive Upload Rights ...~carlos None mozilla-firefox None None
978
979 getUploadersForComponent returns all the permissions where someone can
980 upload to a particular component:
981@@ -397,7 +398,7 @@
982 ... show_permission_entries(permissions)
983
984 >>> show_component_permissions("main")
985- Archive Upload Rights ...~ubuntu-team main None None
986+ Archive Upload Rights ...~ubuntu-team main None None None
987
988 Passing a bad component name results in an error:
989
990@@ -411,8 +412,8 @@
991 all components.
992
993 >>> show_component_permissions()
994- Archive Upload Rights ...~ubuntu-team main None None
995- Archive Upload Rights ...~ubuntu-team universe None None
996+ Archive Upload Rights ...~ubuntu-team main None None None
997+ Archive Upload Rights ...~ubuntu-team universe None None None
998
999 newComponentUploader adds a new permission for a person to upload to a
1000 component.
1001@@ -431,10 +432,10 @@
1002 http://.../ubuntu/+archive/primary/+upload/name12?type=component&item=restricted
1003
1004 >>> show_component_permissions()
1005- Archive Upload Rights ...~name12 restricted None None
1006- Archive Upload Rights ...~ubuntu-team main None None
1007- Archive Upload Rights ...~ubuntu-team restricted None None
1008- Archive Upload Rights ...~ubuntu-team universe None None
1009+ Archive Upload Rights ...~name12 restricted None None None
1010+ Archive Upload Rights ...~ubuntu-team main None None None
1011+ Archive Upload Rights ...~ubuntu-team restricted None None None
1012+ Archive Upload Rights ...~ubuntu-team universe None None None
1013
1014 We can use ``checkUpload`` to verify that a person can upload a
1015 sourcepackage.
1016@@ -461,9 +462,9 @@
1017 And we can see that it's gone:
1018
1019 >>> show_component_permissions()
1020- Archive Upload Rights ...~ubuntu-team main None None
1021- Archive Upload Rights ...~ubuntu-team restricted None None
1022- Archive Upload Rights ...~ubuntu-team universe None None
1023+ Archive Upload Rights ...~ubuntu-team main None None None
1024+ Archive Upload Rights ...~ubuntu-team restricted None None None
1025+ Archive Upload Rights ...~ubuntu-team universe None None None
1026
1027 And ``checkUpload`` now also no longer passes:
1028
1029@@ -527,8 +528,8 @@
1030 ... show_permission_entries(permissions)
1031
1032 >>> show_admins_for_component("main")
1033- Queue Administration Rights ...~name12 main None None
1034- Queue Administration Rights ...~ubuntu-team main None None
1035+ Queue Administration Rights ...~name12 main None None None
1036+ Queue Administration Rights ...~ubuntu-team main None None None
1037
1038 getComponentsForQueueAdmin returns all the permissions relating to components
1039 where the user is able to administer distroseries queues.
1040@@ -540,10 +541,10 @@
1041 ... show_permission_entries(permissions)
1042
1043 >>> show_components_for_admin(name12)
1044- Queue Administration Rights ...~name12 main None None
1045- Queue Administration Rights ...~name12 multiverse None None
1046- Queue Administration Rights ...~name12 restricted None None
1047- Queue Administration Rights ...~name12 universe None None
1048+ Queue Administration Rights ...~name12 main None None None
1049+ Queue Administration Rights ...~name12 multiverse None None None
1050+ Queue Administration Rights ...~name12 restricted None None None
1051+ Queue Administration Rights ...~name12 universe None None None
1052
1053 newQueueAdmin adds a new permission for a person to administer distroseries
1054 queues in a particular component.
1055@@ -562,11 +563,11 @@
1056 http://.../ubuntu/+archive/primary/+queue-admin/name12?type=component&item=partner
1057
1058 >>> show_components_for_admin(name12)
1059- Queue Administration Rights ...~name12 main None None
1060- Queue Administration Rights ...~name12 multiverse None None
1061- Queue Administration Rights ...~name12 partner None None
1062- Queue Administration Rights ...~name12 restricted None None
1063- Queue Administration Rights ...~name12 universe None None
1064+ Queue Administration Rights ...~name12 main None None None
1065+ Queue Administration Rights ...~name12 multiverse None None None
1066+ Queue Administration Rights ...~name12 partner None None None
1067+ Queue Administration Rights ...~name12 restricted None None None
1068+ Queue Administration Rights ...~name12 universe None None None
1069
1070 deleteQueueAdmin removes that permission.
1071
1072@@ -580,10 +581,10 @@
1073 And we can see that it's gone:
1074
1075 >>> show_components_for_admin(name12)
1076- Queue Administration Rights ...~name12 main None None
1077- Queue Administration Rights ...~name12 multiverse None None
1078- Queue Administration Rights ...~name12 restricted None None
1079- Queue Administration Rights ...~name12 universe None None
1080+ Queue Administration Rights ...~name12 main None None None
1081+ Queue Administration Rights ...~name12 multiverse None None None
1082+ Queue Administration Rights ...~name12 restricted None None None
1083+ Queue Administration Rights ...~name12 universe None None None
1084
1085 getUploadersForPocket returns all the permissions where someone can upload
1086 to a particular pocket:
1087@@ -624,7 +625,7 @@
1088 http://.../ubuntu/+archive/primary/+upload/name12?type=pocket&item=PROPOSED
1089
1090 >>> show_pocket_permissions('Proposed')
1091- Archive Upload Rights ...~name12 None None Proposed
1092+ Archive Upload Rights ...~name12 None None Proposed None
1093
1094 The person named in the permission can upload a package to this pocket.
1095
1096@@ -661,6 +662,91 @@
1097 The signer of this package has no upload rights to
1098 this distribution's primary archive. Did you mean to upload to a PPA?
1099
1100+getQueueAdminsForPocket returns all the permissions where someone can
1101+administer distroseries queues in a particular pocket.
1102+
1103+ >>> def show_admins_for_pocket(pocket, distroseries=None):
1104+ ... kwargs = {}
1105+ ... if distroseries is not None:
1106+ ... kwargs['distroseries'] = distroseries
1107+ ... permissions = webservice.named_get(
1108+ ... ubuntu_devel['main_archive_link'], 'getQueueAdminsForPocket',
1109+ ... api_version='devel', pocket=pocket, **kwargs).jsonBody()
1110+ ... show_permission_entries(permissions)
1111+
1112+ >>> show_admins_for_pocket('Security')
1113+ >>> show_admins_for_pocket('Security', distroseries=grumpy['self_link'])
1114+
1115+getPocketsForQueueAdmin returns all the permissions relating to pockets
1116+where the user is able to administer distroseries queues.
1117+
1118+ >>> def show_pockets_for_admin(person):
1119+ ... permissions = webservice.named_get(
1120+ ... ubuntu_devel['main_archive_link'], 'getPocketsForQueueAdmin',
1121+ ... api_version='devel', person=person['self_link']).jsonBody()
1122+ ... show_permission_entries(permissions)
1123+
1124+ >>> show_pockets_for_admin(name12)
1125+
1126+newPocketQueueAdmin adds a new permission for a person to administer
1127+distroseries queues in a particular pocket.
1128+
1129+ >>> response = ubuntu_owner_webservice.named_post(
1130+ ... ubuntu_devel['main_archive_link'], 'newPocketQueueAdmin', {},
1131+ ... api_version='devel', person=name12['self_link'],
1132+ ... pocket='Security')
1133+ >>> print response
1134+ HTTP/1.1 201 Created
1135+ ...
1136+
1137+ >>> new_permission = ubuntu_owner_webservice.get(
1138+ ... response.getHeader('Location')).jsonBody()
1139+ >>> print new_permission['self_link']
1140+ http://.../ubuntu/+archive/primary/+queue-admin/name12?type=pocket&item=SECURITY
1141+
1142+ >>> show_pockets_for_admin(name12)
1143+ Queue Administration Rights ...~name12 None None Security None
1144+
1145+It can also grant series-specific pocket queue admin permissions.
1146+
1147+ >>> ubuntu_owner_ws = ubuntu_owner_webservice.get(
1148+ ... "/~ubuntu-owner").jsonBody()
1149+ >>> response = ubuntu_owner_webservice.named_post(
1150+ ... ubuntu_devel['main_archive_link'], 'newPocketQueueAdmin', {},
1151+ ... api_version='devel', person=ubuntu_owner_ws['self_link'],
1152+ ... pocket='Security', distroseries=grumpy['self_link'])
1153+ >>> print response
1154+ HTTP/1.1 201 Created
1155+ ...
1156+
1157+ >>> new_permission = ubuntu_owner_webservice.get(
1158+ ... response.getHeader('Location')).jsonBody()
1159+ >>> print new_permission['self_link']
1160+ http://.../ubuntu/+archive/primary/+queue-admin/ubuntu-owner?type=pocket&item=SECURITY&series=grumpy
1161+
1162+ >>> show_pockets_for_admin(ubuntu_owner_ws)
1163+ Queue Administration Rights ...~ubuntu-owner None None Security .../grumpy
1164+
1165+deletePocketQueueAdmin removes these permissions.
1166+
1167+ >>> print ubuntu_owner_webservice.named_post(
1168+ ... ubuntu_devel['main_archive_link'], 'deletePocketQueueAdmin', {},
1169+ ... api_version='devel', person=name12['self_link'],
1170+ ... pocket='Security')
1171+ HTTP/1.1 200 Ok
1172+ ...
1173+ >>> print ubuntu_owner_webservice.named_post(
1174+ ... ubuntu_devel['main_archive_link'], 'deletePocketQueueAdmin', {},
1175+ ... api_version='devel', person=ubuntu_owner_ws['self_link'],
1176+ ... pocket='Security', distroseries=grumpy['self_link'])
1177+ HTTP/1.1 200 Ok
1178+ ...
1179+
1180+And we can see that they're gone:
1181+
1182+ >>> show_pockets_for_admin(name12)
1183+ >>> show_pockets_for_admin(ubuntu_owner_ws)
1184+
1185 Malformed archive permission URLs
1186 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1187