Merge lp:~michael.nelson/launchpad/530180-partner-permissions into lp:launchpad

Proposed by Michael Nelson
Status: Merged
Approved by: Michael Nelson
Approved revision: no longer in the source branch.
Merged at revision: not available
Proposed branch: lp:~michael.nelson/launchpad/530180-partner-permissions
Merge into: lp:launchpad
Diff against target: 473 lines (+257/-51)
8 files modified
lib/canonical/launchpad/security.py (+2/-1)
lib/lp/soyuz/browser/queue.py (+1/-1)
lib/lp/soyuz/browser/tests/test_queue.py (+191/-0)
lib/lp/soyuz/doc/archivepermission.txt (+3/-2)
lib/lp/soyuz/interfaces/archivepermission.py (+2/-1)
lib/lp/soyuz/model/archivepermission.py (+12/-7)
lib/lp/soyuz/stories/soyuz/xx-queue-pages.txt (+42/-35)
lib/lp/soyuz/tests/test_publishing.py (+4/-4)
To merge this branch: bzr merge lp:~michael.nelson/launchpad/530180-partner-permissions
Reviewer Review Type Date Requested Status
Graham Binns (community) code Approve
Review via email: mp+20947@code.launchpad.net

Commit message

If a person has permission to administer the queue page for one of the main archives then the view will allow them to post (while still checking individual queue items).

Description of the change

Summary
=======

This branch ensures that if a person has permission to administer the queue for one of the main archives for a distro, then they the /distro/series/+queue page will allow them to post.

Previously the security adapter checked only for permissions on the primary archive. The provided unit test show that now it is possible to have permission for the partner archive only, and be able to post to the view.

Note: the view still checks the permission on each individual item, also shown in the new unit tests.

To test:
bin/test -vv -t doc/archivepermission.txt -t xx-queue-pages.txt -t TestAcceptQueueUploads

The tests were originally written to demonstrate bug 530180, but as identified there, they actually showed that the original problem on the bug was related to incorrect permissions.

To post a comment you must log in.
Revision history for this message
Graham Binns (gmb) wrote :

Hi Michael,

You need to change the copyright notice in your unit test to read 2010 instead of 2009 (and if you'd do the templates as well I'd be grateful).

Other than that this branch looks fine. r=me.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/canonical/launchpad/security.py'
2--- lib/canonical/launchpad/security.py 2010-03-09 08:24:53 +0000
3+++ lib/canonical/launchpad/security.py 2010-03-09 15:41:38 +0000
4@@ -1360,7 +1360,8 @@
5
6 permission_set = getUtility(IArchivePermissionSet)
7 permissions = permission_set.componentsForQueueAdmin(
8- self.obj.distroseries.main_archive, user.person)
9+ self.obj.distroseries.distribution.all_distro_archives,
10+ user.person)
11 return permissions.count() > 0
12
13
14
15=== modified file 'lib/lp/soyuz/browser/queue.py'
16--- lib/lp/soyuz/browser/queue.py 2009-07-19 04:41:14 +0000
17+++ lib/lp/soyuz/browser/queue.py 2010-03-09 15:41:38 +0000
18@@ -283,7 +283,7 @@
19 self.error = "Invalid component: %s" % component_override
20 return
21
22- # Get a list of components that the user has rights to accept and
23+ # Get a list of components for which the user has rights to
24 # override to or from.
25 permission_set = getUtility(IArchivePermissionSet)
26 permissions = permission_set.componentsForQueueAdmin(
27
28=== added file 'lib/lp/soyuz/browser/tests/test_queue.py'
29--- lib/lp/soyuz/browser/tests/test_queue.py 1970-01-01 00:00:00 +0000
30+++ lib/lp/soyuz/browser/tests/test_queue.py 2010-03-09 15:41:38 +0000
31@@ -0,0 +1,191 @@
32+# Copyright 2010 Canonical Ltd. This software is licensed under the
33+# GNU Affero General Public License version 3 (see the file LICENSE).
34+
35+"""Unit tests for QueueItemsView."""
36+
37+__metaclass__ = type
38+__all__ = [
39+ 'TestAcceptPartnerArchive',
40+ 'test_suite',
41+ ]
42+
43+import transaction
44+import unittest
45+from zope.component import getUtility, queryMultiAdapter
46+
47+from canonical.launchpad.webapp.servers import LaunchpadTestRequest
48+from canonical.testing import LaunchpadFunctionalLayer
49+
50+from lp.archiveuploader.tests import datadir
51+from lp.soyuz.interfaces.archivepermission import IArchivePermissionSet
52+from lp.soyuz.interfaces.queue import IPackageUploadSet, PackageUploadStatus
53+from lp.soyuz.tests.test_publishing import SoyuzTestPublisher
54+from lp.testing import login, logout, TestCaseWithFactory
55+
56+
57+class TestAcceptQueueUploads(TestCaseWithFactory):
58+ """Uploads for the partner archive can be accepted with the relevant
59+ permissions.
60+ """
61+
62+ layer = LaunchpadFunctionalLayer
63+
64+ def setUp(self):
65+ """Create two new uploads in the new state and a person with
66+ permission to upload to the partner archive."""
67+ super(TestAcceptQueueUploads, self).setUp()
68+ login('admin@canonical.com')
69+ self.test_publisher = SoyuzTestPublisher()
70+ self.test_publisher.prepareBreezyAutotest()
71+ distribution = self.test_publisher.distroseries.distribution
72+ self.main_archive = distribution.getArchiveByComponent('main')
73+ self.partner_archive = distribution.getArchiveByComponent('partner')
74+
75+ # Get some sample changes file content for the new uploads.
76+ changes_file = open(
77+ datadir('suite/bar_1.0-1/bar_1.0-1_source.changes'))
78+ changes_file_content = changes_file.read()
79+ changes_file.close()
80+
81+ self.partner_spr = self.test_publisher.getPubSource(
82+ sourcename='partner-upload', spr_only=True,
83+ component='partner', changes_file_content=changes_file_content,
84+ archive=self.partner_archive)
85+ self.partner_spr.package_upload.setNew()
86+ self.main_spr = self.test_publisher.getPubSource(
87+ sourcename='main-upload', spr_only=True,
88+ component='main', changes_file_content=changes_file_content)
89+ self.main_spr.package_upload.setNew()
90+
91+ # Define the form that will be used to post to the view.
92+ self.form = {
93+ 'queue_state': PackageUploadStatus.NEW.value,
94+ 'Accept': 'Accept',
95+ }
96+
97+ # Create a user with queue admin rights for main, and a separate
98+ # user with queue admin rights for partner (on the partner
99+ # archive).
100+ self.main_queue_admin = self.factory.makePerson(
101+ email='main-queue@example.org')
102+ getUtility(IArchivePermissionSet).newQueueAdmin(
103+ distribution.getArchiveByComponent('main'),
104+ self.main_queue_admin, self.main_spr.component)
105+ self.partner_queue_admin = self.factory.makePerson(
106+ email='partner-queue@example.org')
107+ getUtility(IArchivePermissionSet).newQueueAdmin(
108+ distribution.getArchiveByComponent('partner'),
109+ self.partner_queue_admin, self.partner_spr.component)
110+
111+
112+ # We need to commit to ensure the changes file exists in the
113+ # librarian.
114+ transaction.commit()
115+ logout()
116+
117+ def setupQueueView(self, request):
118+ """A helper to create and setup the view for testing."""
119+ view = queryMultiAdapter(
120+ (self.test_publisher.distroseries, request), name="+queue")
121+ view.setupQueueList()
122+ view.performQueueAction()
123+ return view
124+
125+ def test_main_admin_can_accept_main_upload(self):
126+ # A person with queue admin access for main
127+ # can accept uploads to the main archive.
128+ login('main-queue@example.org')
129+ self.assertTrue(
130+ self.main_archive.canAdministerQueue(
131+ self.main_queue_admin, self.main_spr.component))
132+
133+ package_upload_id = self.main_spr.package_upload.id
134+ self.form['QUEUE_ID'] = [package_upload_id]
135+ request = LaunchpadTestRequest(form=self.form)
136+ request.method = 'POST'
137+ view = self.setupQueueView(request)
138+
139+ self.assertEquals(
140+ 'DONE',
141+ getUtility(IPackageUploadSet).get(package_upload_id).status.name)
142+
143+ def test_main_admin_cannot_accept_partner_upload(self):
144+ # A person with queue admin access for main cannot necessarily
145+ # accept uploads to partner.
146+ login('main-queue@example.org')
147+ self.assertFalse(
148+ self.partner_archive.canAdministerQueue(
149+ self.main_queue_admin, self.partner_spr.component))
150+
151+ package_upload_id = self.partner_spr.package_upload.id
152+ self.form['QUEUE_ID'] = [package_upload_id]
153+ request = LaunchpadTestRequest(form=self.form)
154+ request.method = 'POST'
155+ view = self.setupQueueView(request)
156+
157+ self.assertEquals(
158+ "FAILED: partner-upload (You have no rights to accept "
159+ "component(s) 'partner')",
160+ view.request.response.notifications[0].message)
161+ self.assertEquals(
162+ 'NEW',
163+ getUtility(IPackageUploadSet).get(package_upload_id).status.name)
164+
165+ def test_admin_can_accept_partner_upload(self):
166+ # An admin can always accept packages, even for the
167+ # partner archive (note, this is *not* an archive admin).
168+ login('admin@canonical.com')
169+
170+ package_upload_id = self.partner_spr.package_upload.id
171+ self.form['QUEUE_ID'] = [package_upload_id]
172+ request = LaunchpadTestRequest(form=self.form)
173+ request.method = 'POST'
174+ view = self.setupQueueView(request)
175+
176+ self.assertEquals(
177+ 'DONE',
178+ getUtility(IPackageUploadSet).get(package_upload_id).status.name)
179+
180+ def test_partner_admin_can_accept_partner_upload(self):
181+ # A person with queue admin access for partner
182+ # can accept uploads to the partner archive.
183+ login('partner-queue@example.org')
184+ self.assertTrue(
185+ self.partner_archive.canAdministerQueue(
186+ self.partner_queue_admin, self.partner_spr.component))
187+
188+ package_upload_id = self.partner_spr.package_upload.id
189+ self.form['QUEUE_ID'] = [package_upload_id]
190+ request = LaunchpadTestRequest(form=self.form)
191+ request.method = 'POST'
192+ view = self.setupQueueView(request)
193+
194+ self.assertEquals(
195+ 'DONE',
196+ getUtility(IPackageUploadSet).get(package_upload_id).status.name)
197+
198+ def test_partner_admin_cannot_accept_main_upload(self):
199+ # A person with queue admin access for partner cannot necessarily
200+ # accept uploads to main.
201+ login('partner-queue@example.org')
202+ self.assertFalse(
203+ self.main_archive.canAdministerQueue(
204+ self.partner_queue_admin, self.main_spr.component))
205+
206+ package_upload_id = self.main_spr.package_upload.id
207+ self.form['QUEUE_ID'] = [package_upload_id]
208+ request = LaunchpadTestRequest(form=self.form)
209+ request.method = 'POST'
210+ view = self.setupQueueView(request)
211+
212+ self.assertEquals(
213+ "FAILED: main-upload (You have no rights to accept "
214+ "component(s) 'main')",
215+ view.request.response.notifications[0].message)
216+ self.assertEquals(
217+ 'NEW',
218+ getUtility(IPackageUploadSet).get(package_upload_id).status.name)
219+
220+def test_suite():
221+ return unittest.TestLoader().loadTestsFromName(__name__)
222+
223
224=== modified file 'lib/lp/soyuz/doc/archivepermission.txt'
225--- lib/lp/soyuz/doc/archivepermission.txt 2009-12-24 01:41:54 +0000
226+++ lib/lp/soyuz/doc/archivepermission.txt 2010-03-09 15:41:38 +0000
227@@ -236,7 +236,8 @@
228
229 componentsForQueueAdmin() returns the ArchivePermission records for all
230 the components that the supplied user has permission to administer in
231-the distroseries queue.
232+the distroseries queue. It can be passed a single archive or an
233+enumeration of archives.
234
235 >>> name12 = getUtility(IPersonSet).getByName("name12")
236 >>> permissions = permission_set.componentsForQueueAdmin(
237@@ -250,7 +251,7 @@
238
239 >>> no_team = getUtility(IPersonSet).getByName("no-team-memberships")
240 >>> permissions = permission_set.componentsForQueueAdmin(
241- ... ubuntu.main_archive, no_team)
242+ ... ubuntu.all_distro_archives, no_team)
243 >>> for permission in sorted(permissions, key=operator.attrgetter("id")):
244 ... print permission.component.name
245 universe
246
247=== modified file 'lib/lp/soyuz/interfaces/archivepermission.py'
248--- lib/lp/soyuz/interfaces/archivepermission.py 2009-11-03 18:44:59 +0000
249+++ lib/lp/soyuz/interfaces/archivepermission.py 2010-03-09 15:41:38 +0000
250@@ -323,7 +323,8 @@
251 def componentsForQueueAdmin(archive, person):
252 """Return `ArchivePermission` for the person's queue admin components.
253
254- :param archive: The context `IArchive` for the permission check.
255+ :param archive: The context `IArchive` for the permission check, or
256+ an iterable of `IArchive`s.
257 :param person: An `IPerson` for whom you want to find out which
258 components he has access to.
259
260
261=== modified file 'lib/lp/soyuz/model/archivepermission.py'
262--- lib/lp/soyuz/model/archivepermission.py 2009-11-03 18:44:59 +0000
263+++ lib/lp/soyuz/model/archivepermission.py 2010-03-09 15:41:38 +0000
264@@ -23,7 +23,7 @@
265 from canonical.database.sqlbase import sqlvalues, SQLBase
266
267 from lp.registry.interfaces.distribution import IDistributionSet
268-from lp.soyuz.interfaces.archive import ComponentNotFound
269+from lp.soyuz.interfaces.archive import ComponentNotFound, IArchive
270 from lp.soyuz.interfaces.archivepermission import (
271 ArchivePermissionType, IArchivePermission, IArchivePermissionSet,
272 IArchiveUploader, IArchiveQueueAdmin)
273@@ -92,7 +92,7 @@
274 def component_name(self):
275 """See `IArchivePermission`"""
276 if self.component:
277- return self.component.name
278+ return self.component.name
279 else:
280 return None
281
282@@ -189,17 +189,22 @@
283 TeamParticipation.team = ArchivePermission.person)
284 """ % sqlvalues(archive, person))
285
286- def _componentsFor(self, archive, person, permission_type):
287+ def _componentsFor(self, archives, person, permission_type):
288 """Helper function to get ArchivePermission objects."""
289+ if IArchive.providedBy(archives):
290+ archive_ids = [archives.id]
291+ else:
292+ archive_ids = [archive.id for archive in archives]
293+
294 return ArchivePermission.select("""
295- ArchivePermission.archive = %s AND
296+ ArchivePermission.archive IN %s AND
297 ArchivePermission.permission = %s AND
298 ArchivePermission.component IS NOT NULL AND
299 EXISTS (SELECT TeamParticipation.person
300 FROM TeamParticipation
301 WHERE TeamParticipation.person = %s AND
302 TeamParticipation.team = ArchivePermission.person)
303- """ % sqlvalues(archive, permission_type, person),
304+ """ % sqlvalues(archive_ids, permission_type, person),
305 prejoins=["component"])
306
307 def componentsForUploader(self, archive, person):
308@@ -504,7 +509,7 @@
309 # Query parameters for the first WHERE clause.
310 (archive.id, distroseries.id, sourcepackagename.id) +
311 # Query parameters for the second WHERE clause.
312- permission_params + archive_params +
313+ permission_params + archive_params +
314 # Query parameters for the third WHERE clause.
315 permission_params + archive_params)
316
317@@ -521,7 +526,7 @@
318 THEN (
319 SELECT COUNT(ap.id)
320 FROM
321- packagesetsources pss, archivepermission ap, packageset ps,
322+ packagesetsources pss, archivepermission ap, packageset ps,
323 teamparticipation tp
324 WHERE
325 pss.sourcepackagename = %s
326
327=== modified file 'lib/lp/soyuz/stories/soyuz/xx-queue-pages.txt'
328--- lib/lp/soyuz/stories/soyuz/xx-queue-pages.txt 2009-07-16 00:31:36 +0000
329+++ lib/lp/soyuz/stories/soyuz/xx-queue-pages.txt 2010-03-09 15:41:38 +0000
330@@ -195,37 +195,33 @@
331
332 >>> from zope.component import getUtility
333 >>> from canonical.launchpad.ftests import login, logout
334- >>> from canonical.launchpad.interfaces import (
335- ... IDistributionSet, ILibraryFileAliasSet)
336- >>> from canonical.launchpad.ftests import syncUpdate
337+ >>> from canonical.launchpad.interfaces import IDistributionSet
338+ >>> from lp.soyuz.tests.test_publishing import SoyuzTestPublisher
339
340 >>> login('foo.bar@canonical.com')
341
342 >>> ubuntu = getUtility(IDistributionSet).getByName('ubuntu')
343- >>> fake_chroot = getUtility(ILibraryFileAliasSet)[1]
344+
345 >>> breezy_autotest = ubuntu.getSeries('breezy-autotest')
346- >>> new_chroot = breezy_autotest["i386"].addOrUpdateChroot(fake_chroot)
347- >>> syncUpdate(new_chroot)
348-
349-Upload a new "bar" source so we can accept it later. We need to login to
350-upload.
351-
352- >>> from lp.archiveuploader.tests import (
353- ... datadir, getPolicy, mock_logger_quiet)
354- >>> from lp.archiveuploader.nascentupload import NascentUpload
355-
356- >>> sync_policy = getPolicy(
357- ... name='sync', distro='ubuntu', distroseries='breezy-autotest')
358- >>> bar_src = NascentUpload(
359- ... datadir('suite/bar_1.0-1/bar_1.0-1_source.changes'),
360- ... sync_policy, mock_logger_quiet)
361- >>> bar_src.process()
362- >>> bar_src.do_accept()
363- True
364- >>> bar_queue_id = str(bar_src.queue_root.id)
365-
366- >>> import transaction
367- >>> transaction.commit()
368+ >>> test_publisher = SoyuzTestPublisher()
369+ >>> ignore = test_publisher.setUpDefaultDistroSeries(breezy_autotest)
370+ >>> test_publisher.addFakeChroots(distroseries=breezy_autotest)
371+
372+Upload a new "bar" source so we can accept it later.
373+
374+ >>> from lp.archiveuploader.tests import datadir
375+ >>> changes_file = open(
376+ ... datadir('suite/bar_1.0-1/bar_1.0-1_source.changes'))
377+ >>> changes_file_content = changes_file.read()
378+ >>> changes_file.close()
379+
380+ >>> bar_src = test_publisher.getPubSource(
381+ ... sourcename='bar', distroseries=breezy_autotest, spr_only=True,
382+ ... version='1.0-1', component='universe', section='devel',
383+ ... changes_file_content=changes_file_content)
384+
385+ >>> bar_src.package_upload.setNew()
386+ >>> bar_queue_id = bar_src.package_upload.id
387 >>> logout()
388
389 Swallow any email generated at the upload:
390@@ -246,8 +242,18 @@
391
392 >>> upload_manager_browser.open(
393 ... "http://localhost/ubuntu/breezy-autotest/+queue")
394+ >>> print_queue(upload_manager_browser.contents)
395+ Package Version Component Section Priority Pocket When
396+ bar (source) 1.0-1 universe devel low Release ...
397+ netapplet...ddtp... - Release 2006-...
398+ netapplet...dist... - Release 2006-...
399+ alsa-utils (source) 1.0.9a-4... main base low Release 2006-...
400+ netapplet (source) 0.99.6-1 main web low Release 2006-...
401+ pmount (i386) 0.1-1 Release 2006-...
402+ moz...irefox (i386) 0.9 Release 2006-...
403+
404 >>> upload_manager_browser.getControl(
405- ... name="QUEUE_ID").value = [bar_queue_id]
406+ ... name="QUEUE_ID").value = [str(bar_queue_id)]
407 >>> upload_manager_browser.getControl(name="Accept").click()
408 >>> print_queue(upload_manager_browser.contents)
409 Package Version Component Section Priority Pocket When
410@@ -258,22 +264,23 @@
411 pmount (i386) 0.1-1 Release 2006-...
412 moz...irefox (i386) 0.9 Release 2006-...
413
414-Accepting queue items results in an email to the uploader and (usually) an
415-email to the distroseries' announcement list (see
416-nascentupload-announcements.txt).
417+Accepting queue items results in an email to the uploader (and the changer
418+if it is someone other than the uploader) and (usually) an email to the
419+distroseries' announcement list (see nascentupload-announcements.txt).
420
421 >>> [notification, announcement] = pop_notifications()
422- >>> notification['To']
423- 'Daniel Silverstone <daniel.silverstone@canonical.com>'
424- >>> announcement['To']
425- 'autotest_changes@ubuntu.com'
426+ >>> print notification['To']
427+ Foo Bar <foo.bar@canonical.com>,
428+ Daniel Silverstone <daniel.silverstone@canonical.com>
429+ >>> print announcement['To']
430+ autotest_changes@ubuntu.com
431
432 Forcing a duplicated submission on a queue item is recognised. Here we
433 submit the same form again via a different browser instance, which simulates
434 a double post.
435
436 >>> duplicate_submission_browser.getControl(
437- ... name="QUEUE_ID").value = [bar_queue_id]
438+ ... name="QUEUE_ID").value = [str(bar_queue_id)]
439 >>> duplicate_submission_browser.getControl(name="Accept").click()
440 >>> for message in get_feedback_messages(
441 ... duplicate_submission_browser.contents):
442
443=== modified file 'lib/lp/soyuz/tests/test_publishing.py'
444--- lib/lp/soyuz/tests/test_publishing.py 2010-02-27 20:20:03 +0000
445+++ lib/lp/soyuz/tests/test_publishing.py 2010-03-09 15:41:38 +0000
446@@ -219,15 +219,15 @@
447 upload_status=upload_status)
448 package_upload.addSource(spr)
449
450- if spr_only:
451- return spr
452-
453 if filename is None:
454 filename = "%s_%s.dsc" % (sourcename, version)
455 alias = self.addMockFile(
456 filename, filecontent, restricted=archive.private)
457 spr.addFile(alias)
458
459+ if spr_only:
460+ return spr
461+
462 if status == PackagePublishingStatus.PUBLISHED:
463 datepublished = UTC_NOW
464 else:
465@@ -405,7 +405,7 @@
466 """File with given name fragment in directory tree starting at top."""
467 for root, dirs, files in os.walk(top, topdown=False):
468 for name in files:
469- if (name.endswith('.changes') and
470+ if (name.endswith('.changes') and
471 name.find(name_fragment) > -1):
472 return os.path.join(root, name)
473 return None