Merge lp:~julian-edwards/launchpad/queue-accept-with-private-bugs into lp:launchpad

Proposed by Julian Edwards
Status: Merged
Approved by: Julian Edwards
Approved revision: no longer in the source branch.
Merged at revision: 11556
Proposed branch: lp:~julian-edwards/launchpad/queue-accept-with-private-bugs
Merge into: lp:launchpad
Diff against target: 167 lines (+62/-3)
3 files modified
lib/lp/soyuz/scripts/processaccepted.py (+10/-0)
lib/lp/soyuz/scripts/tests/test_queue.py (+50/-2)
lib/lp/testing/factory.py (+2/-1)
To merge this branch: bzr merge lp:~julian-edwards/launchpad/queue-accept-with-private-bugs
Reviewer Review Type Date Requested Status
Deryck Hodge (community) code Approve
Tim Penhey (community) mentor Approve
Steve Kowalik (community) code* Approve
Robert Collins code Pending
Review via email: mp+35438@code.launchpad.net

Commit message

Fix an OOPS when an archive admin uses the +queue page to accept uploads that close private bugs.

Description of the change

= Summary =
Fix private bug closing when accepting package uploads.

As described in the linked bugs, we have a problem when someone accepts a held
upload if that upload is trying to access a bug that the user is not allowed
to see.

== Proposed fix ==
Normally this is not a problem for auto-accepted uploads because they run in
zopeless scripts, which don't care about the security. For this reason, I
feel it is ok to remove the security proxy in the code that is shared with the
webapp request.

== Pre-implementation notes ==
See the bug(s).

== Implementation details ==
A simple removeSecurityProxy() is used to remove the wrapper from the bug
before it changes its status to RELEASED and adds the janitor message.

I'm inviting Deryck and Robert to review this as well, to see if you think
this approach is safe.

== Tests ==
bin/test -cvvt TestQueuePageClosingBugs

== Demo and Q/A ==
Set up a private bugtask on a source package using user "A". Make an upload
of that package to a frozen distroseries, with a Launchpad-bugs-fixed: header
referring to it. The package will be held in the unapproved queue, where user
"B" can try to accept it.

To post a comment you must log in.
Revision history for this message
Steve Kowalik (stevenk) wrote :

This change looks sane to me.

review: Approve (code*)
Revision history for this message
Tim Penhey (thumper) wrote :

If the work should be moved, how about you file a bug and add an XXX comment?

Also I think your test is a bit more convoluted than it needs to be.

Instead of:
  removeSecurityProxy(spr).changelog_entry = "blah"
you should tweak
  self.factory.makeSourcePackageRelease()
to accept a changelog_entry. This is simple, quick, and avoids the need
for a removeSecurityProxy.

We don't care who the bug reporter is, so you can get rid of that makePerson,
and remove the owner from the makeBug and makeBugTask calls.

The archive_admin person isn't really an archive admin are they? they are
just some random person. Should it be a particular user? Or just someone
who can't see the bug?

Also, we don't need to be the bug reporter to see the change, we could just
use an admin user as we know they can see everything.

Revision history for this message
Julian Edwards (julian-edwards) wrote :

Hello Tim, haven't had one of your reviews for ages :)

On Wednesday 15 September 2010 05:53:43 Tim Penhey wrote:
> If the work should be moved, how about you file a bug and add an XXX
> comment?

I was waiting for someone to say that! It's highly unlikely it will ever get
done if this actually works, my comment is referring to utopia. I think we
should only file bugs that we really intend to fix.

> Also I think your test is a bit more convoluted than it needs to be.
>
> Instead of:
> removeSecurityProxy(spr).changelog_entry = "blah"
> you should tweak
> self.factory.makeSourcePackageRelease()
> to accept a changelog_entry. This is simple, quick, and avoids the need
> for a removeSecurityProxy.

Fair point, I'll fix it.

> We don't care who the bug reporter is, so you can get rid of that
> makePerson, and remove the owner from the makeBug and makeBugTask calls.

We kinda do actually, it's critical to the test that it demonstrates the two
people are different so you can see that the bug is fixed. Anyway, I've got
rid of the extra makePerson and replaced it with an assertion to prove that
the archive_admin person can't see the bug. That removes the implicit
assumption that the two users have different permissions and makes it
explicit.

The only thing that's annoying is the import from lp.bugs :/

> The archive_admin person isn't really an archive admin are they? they are
> just some random person. Should it be a particular user? Or just someone
> who can't see the bug?

It's just a meaningful variable name to show that the person coming along to
work the +queue page is an archive admin - nobody else has that permission.
Admittedly I'm not driving the queue page here but the point still stands.

> Also, we don't need to be the bug reporter to see the change, we could just
> use an admin user as we know they can see everything.

I'm not sure it makes any practical difference, but I changed it anyway at the
cost of an extra import.

Cheers.

Revision history for this message
Tim Penhey (thumper) wrote :

On Wed, 15 Sep 2010 22:09:58 you wrote:
> Hello Tim, haven't had one of your reviews for ages :)
>
> On Wednesday 15 September 2010 05:53:43 Tim Penhey wrote:
> > If the work should be moved, how about you file a bug and add an XXX
> > comment?
>
> I was waiting for someone to say that! It's highly unlikely it will ever
> get done if this actually works, my comment is referring to utopia. I
> think we should only file bugs that we really intend to fix.

Yeah... ok.

> > We don't care who the bug reporter is, so you can get rid of that
> > makePerson, and remove the owner from the makeBug and makeBugTask calls.
>
> We kinda do actually, it's critical to the test that it demonstrates the
> two people are different so you can see that the bug is fixed. Anyway,
> I've got rid of the extra makePerson and replaced it with an assertion to
> prove that the archive_admin person can't see the bug. That removes the
> implicit assumption that the two users have different permissions and
> makes it explicit.
>
> The only thing that's annoying is the import from lp.bugs :/

You could always move it into lp.bugs.enums :)
(which doesn't exist yet).

> > The archive_admin person isn't really an archive admin are they? they
> > are just some random person. Should it be a particular user? Or just
> > someone who can't see the bug?
>
> It's just a meaningful variable name to show that the person coming along
> to work the +queue page is an archive admin - nobody else has that
> permission. Admittedly I'm not driving the queue page here but the point
> still stands.

Well... no, it just confuses the issue. The person isn't in a special role,
but the name makes it appear that they are.

Also, does the test work in the DatabaseFunctionalLayer? That layer has less
dependencies.

Revision history for this message
Tim Penhey (thumper) :
review: Approve (mentor)
Revision history for this message
Julian Edwards (julian-edwards) wrote :

On Thursday 16 September 2010 04:58:43 you wrote:
> Also, does the test work in the DatabaseFunctionalLayer? That layer has
> less dependencies.

Good call, I forgot about that one. It works fine.

Cheers for the review.

Revision history for this message
Deryck Hodge (deryck) wrote :

As mentioned on IRC, I would prefer a strongly worded comment to discourage doing anything else with the non-security-proxied object. This simple change to a bug task FixReleased status is no harm, but I don't want later developers coming along and working with non-proxied bugs without extreme caution about what they are doing.

Other than that, I recognize this is the easiest way to fix the OOPS currently and the change looks fine to me.

Cheers,
deryck

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/soyuz/scripts/processaccepted.py'
2--- lib/lp/soyuz/scripts/processaccepted.py 2010-09-10 12:56:30 +0000
3+++ lib/lp/soyuz/scripts/processaccepted.py 2010-09-16 12:01:19 +0000
4@@ -7,6 +7,7 @@
5 __all__ = [
6 'close_bugs',
7 'close_bugs_for_queue_item',
8+ 'close_bugs_for_sourcepackagerelease',
9 'close_bugs_for_sourcepublication',
10 'get_bugs_from_changes_file',
11 'ProcessAccepted',
12@@ -16,6 +17,7 @@
13 import sys
14
15 from zope.component import getUtility
16+from zope.security.proxy import removeSecurityProxy
17
18 from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
19 from canonical.launchpad.webapp.errorlog import (
20@@ -165,6 +167,14 @@
21
22 janitor = getUtility(ILaunchpadCelebrities).janitor
23 for bug in bugs_to_close:
24+ # We need to remove the security proxy here because the bug
25+ # might be private and if this code is called via someone using
26+ # the +queue page they will get an OOPS. Ideally, we should
27+ # migrate this code to the Job system though, but that's a lot
28+ # of work. If you don't do that and you're changing stuff in
29+ # here, BE CAREFUL with the unproxied bug object and look at
30+ # what you're doing with it that might violate security.
31+ bug = removeSecurityProxy(bug)
32 edited_task = bug.setStatus(
33 target=source_release.sourcepackage,
34 status=BugTaskStatus.FIXRELEASED,
35
36=== modified file 'lib/lp/soyuz/scripts/tests/test_queue.py'
37--- lib/lp/soyuz/scripts/tests/test_queue.py 2010-08-27 12:21:25 +0000
38+++ lib/lp/soyuz/scripts/tests/test_queue.py 2010-09-16 12:01:19 +0000
39@@ -3,6 +3,8 @@
40
41 """queue tool base class tests."""
42
43+from __future__ import with_statement
44+
45 __metaclass__ = type
46 __all__ = [
47 'upload_bar_source',
48@@ -12,6 +14,7 @@
49 import hashlib
50 import os
51 import shutil
52+from StringIO import StringIO
53 import tempfile
54 from unittest import (
55 TestCase,
56@@ -19,6 +22,7 @@
57 )
58
59 from zope.component import getUtility
60+from zope.security.interfaces import ForbiddenAttribute
61 from zope.security.proxy import removeSecurityProxy
62
63 from canonical.config import config
64@@ -33,7 +37,10 @@
65 fillLibrarianFile,
66 )
67 from canonical.librarian.utils import filechunks
68-from canonical.testing import LaunchpadZopelessLayer
69+from canonical.testing import (
70+ DatabaseFunctionalLayer,
71+ LaunchpadZopelessLayer,
72+ )
73 from lp.archiveuploader.nascentupload import NascentUpload
74 from lp.archiveuploader.tests import (
75 datadir,
76@@ -42,7 +49,10 @@
77 mock_logger_quiet,
78 )
79 from lp.bugs.interfaces.bug import IBugSet
80-from lp.bugs.interfaces.bugtask import IBugTaskSet
81+from lp.bugs.interfaces.bugtask import (
82+ BugTaskStatus,
83+ IBugTaskSet,
84+ )
85 from lp.registry.interfaces.distribution import IDistributionSet
86 from lp.registry.interfaces.person import IPersonSet
87 from lp.registry.interfaces.pocket import PackagePublishingPocket
88@@ -56,6 +66,9 @@
89 from lp.soyuz.interfaces.archive import (
90 IArchiveSet,
91 )
92+from lp.soyuz.scripts.processaccepted import (
93+ close_bugs_for_sourcepackagerelease,
94+ )
95 from lp.soyuz.interfaces.queue import (
96 IPackageUploadSet,
97 )
98@@ -64,6 +77,11 @@
99 CommandRunnerError,
100 name_queue_map,
101 )
102+from lp.testing import (
103+ celebrity_logged_in,
104+ person_logged_in,
105+ TestCaseWithFactory,
106+ )
107
108
109 class TestQueueBase(TestCase):
110@@ -926,6 +944,36 @@
111 'override binary pmount', component_name='partner')
112
113
114+class TestQueuePageClosingBugs(TestCaseWithFactory):
115+ # The distroseries +queue page can close bug when accepting
116+ # packages. Unit tests for that belong here.
117+
118+ layer = DatabaseFunctionalLayer
119+
120+ def test_close_bugs_for_sourcepackagerelease_with_private_bug(self):
121+ # lp.soyuz.scripts.processaccepted.close_bugs_for_sourcepackagerelease
122+ # should work with private bugs where the person using the queue
123+ # page doesn't have access to it.
124+ changes_file_template = "Format: 1.7\nLaunchpad-bugs-fixed: %s\n"
125+ # changelog_entry is required for an assertion inside the function
126+ # we're testing.
127+ spr = self.factory.makeSourcePackageRelease(changelog_entry="blah")
128+ archive_admin = self.factory.makePerson()
129+ bug = self.factory.makeBug(private=True)
130+ bug_task = self.factory.makeBugTask(target=spr.sourcepackage, bug=bug)
131+ changes = StringIO(changes_file_template % bug.id)
132+
133+ with person_logged_in(archive_admin):
134+ # The archive admin user can't normally see this bug.
135+ self.assertRaises(ForbiddenAttribute, bug, 'status')
136+ # But the bug closure should work.
137+ close_bugs_for_sourcepackagerelease(spr, changes)
138+
139+ # Verify it was closed.
140+ with celebrity_logged_in("admin"):
141+ self.assertEqual(bug_task.status, BugTaskStatus.FIXRELEASED)
142+
143+
144 class TestQueueToolInJail(TestQueueBase):
145 layer = LaunchpadZopelessLayer
146 dbuser = config.uploadqueue.dbuser
147
148=== modified file 'lib/lp/testing/factory.py'
149--- lib/lp/testing/factory.py 2010-09-10 03:20:48 +0000
150+++ lib/lp/testing/factory.py 2010-09-16 12:01:19 +0000
151@@ -2537,6 +2537,7 @@
152 source_package_recipe_build=None,
153 dscsigningkey=None,
154 user_defined_fields=None,
155+ changelog_entry=None,
156 homepage=None):
157 """Make a `SourcePackageRelease`."""
158 if distroseries is None:
159@@ -2594,7 +2595,7 @@
160 build_conflicts_indep=build_conflicts_indep,
161 architecturehintlist=architecturehintlist,
162 changelog=None,
163- changelog_entry=None,
164+ changelog_entry=changelog_entry,
165 dsc=None,
166 copyright=self.getUniqueString(),
167 dscsigningkey=dscsigningkey,