Merge lp:~lifeless/launchpad/oops-polish into lp:launchpad

Proposed by Robert Collins
Status: Merged
Approved by: William Grant
Approved revision: no longer in the source branch.
Merged at revision: 14399
Proposed branch: lp:~lifeless/launchpad/oops-polish
Merge into: lp:launchpad
Diff against target: 163 lines (+71/-17)
4 files modified
lib/canonical/launchpad/webapp/errorlog.py (+5/-0)
lib/canonical/launchpad/webapp/login.py (+10/-4)
lib/canonical/launchpad/webapp/tests/test_errorlog.py (+16/-0)
lib/canonical/launchpad/webapp/tests/test_login.py (+40/-13)
To merge this branch: bzr merge lp:~lifeless/launchpad/oops-polish
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+83544@code.launchpad.net

Commit message

[r=wgrant][bug=896959,897039] Fix oopses in +login when query keys are not plain ascii, preserve original urls in return_to for +login, and generate oopses properly in those situations.

Description of the change

Two unrelated fixes; the one causing the oopses that were not importing on lp-oopses, and the serialisation of said oopses.

I stumbled on the first and fixed it in passing.

To post a comment you must log in.
Revision history for this message
William Grant (wgrant) :
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/webapp/errorlog.py'
2--- lib/canonical/launchpad/webapp/errorlog.py 2011-11-17 01:28:19 +0000
3+++ lib/canonical/launchpad/webapp/errorlog.py 2011-11-28 03:10:57 +0000
4@@ -234,6 +234,11 @@
5 value = '<hidden>'
6 if not isinstance(value, basestring):
7 value = oops.createhooks.safe_unicode(value)
8+ # keys need to be unicode objects. The form items (a subset of
9+ # request.items) are generally just the url query_string url decoded,
10+ # which means the keys may be invalid in bson docs (bson requires that
11+ # they be unicode).
12+ key = oops.createhooks.safe_unicode(key)
13 report['req_vars'][key] = value
14 if IXMLRPCRequest.providedBy(request):
15 args = request.getPositionalArguments()
16
17=== modified file 'lib/canonical/launchpad/webapp/login.py'
18--- lib/canonical/launchpad/webapp/login.py 2011-09-18 18:37:58 +0000
19+++ lib/canonical/launchpad/webapp/login.py 2011-11-28 03:10:57 +0000
20@@ -10,7 +10,6 @@
21 )
22 import urllib
23
24-from BeautifulSoup import UnicodeDammit
25 from openid.consumer.consumer import (
26 CANCEL,
27 Consumer,
28@@ -252,6 +251,10 @@
29
30 Exclude things such as 'loggingout' and starting with 'openid.', which
31 we don't want.
32+
33+ Coerces all keys and values to be ascii decode safe - either by making
34+ them unicode or by url quoting them. keys are known to be urldecoded
35+ bytestrings, so are simply re urlencoded.
36 """
37 for name, value in self.request.form.items():
38 if name == 'loggingout' or name.startswith('openid.'):
39@@ -261,9 +264,12 @@
40 else:
41 value_list = [value]
42 for value_list_item in value_list:
43- # Thanks to apport (https://launchpad.net/bugs/61171), we need
44- # to do this here.
45- value_list_item = UnicodeDammit(value_list_item).markup
46+ # The form items are byte strings simply url decoded. They may
47+ # fail to coerce to unicode as they can include arbitrary
48+ # bytesequences after url decoding. We can restore their
49+ # original url value by url quoting them again.
50+ value_list_item = urllib.quote(value_list_item)
51+ name = urllib.quote(name)
52 yield "%s=%s" % (name, value_list_item)
53
54
55
56=== modified file 'lib/canonical/launchpad/webapp/tests/test_errorlog.py'
57--- lib/canonical/launchpad/webapp/tests/test_errorlog.py 2011-11-17 01:03:35 +0000
58+++ lib/canonical/launchpad/webapp/tests/test_errorlog.py 2011-11-28 03:10:57 +0000
59@@ -262,6 +262,22 @@
60 report = utility.raising(sys.exc_info(), request)
61 self.assertEqual("(1, 2)", report['req_vars']['xmlrpc args'])
62
63+ def test_raising_non_utf8_request_param_key_bug_896959(self):
64+ # When a form has a nonutf8 request param, the key in req_vars must
65+ # still be unicode (or utf8).
66+ request = TestRequest(form={'foo\x85': 'bar'})
67+ utility = ErrorReportingUtility()
68+ del utility._oops_config.publishers[:]
69+ try:
70+ raise ArbitraryException('foo')
71+ except ArbitraryException:
72+ report = utility.raising(sys.exc_info(), request)
73+ for key in report['req_vars'].keys():
74+ if isinstance(key, str):
75+ key.decode('utf8')
76+ else:
77+ self.assertIsInstance(key, unicode)
78+
79 def test_raising_with_webservice_request(self):
80 # Test ErrorReportingUtility.raising() with a WebServiceRequest
81 # request. Only some exceptions result in OOPSes.
82
83=== modified file 'lib/canonical/launchpad/webapp/tests/test_login.py'
84--- lib/canonical/launchpad/webapp/tests/test_login.py 2011-10-27 11:36:13 +0000
85+++ lib/canonical/launchpad/webapp/tests/test_login.py 2011-11-28 03:10:57 +0000
86@@ -19,7 +19,9 @@
87 )
88 import httplib
89 import unittest
90+import urllib
91 import urllib2
92+import urlparse
93
94 import mechanize
95 from openid.consumer.consumer import (
96@@ -28,6 +30,7 @@
97 )
98 from openid.extensions import sreg
99 from openid.yadis.discover import DiscoveryFailure
100+from testtools.matchers import Contains
101 from zope.component import getUtility
102 from zope.security.management import newInteraction
103 from zope.security.proxy import removeSecurityProxy
104@@ -661,22 +664,46 @@
105 return FakeOpenIDConsumer()
106
107
108+class ForwardsCorrectly:
109+ """Match query_strings which get forwarded correctly.
110+
111+ Correctly is defined as the form parameters ending up simply urllib quoted
112+ wrapped in the return_to url.
113+ """
114+
115+ def match(self, query_string):
116+ args = dict(urlparse.parse_qsl(query_string))
117+ request = LaunchpadTestRequest(form=args)
118+ # This is a hack to make the request.getURL(1) call issued by the view
119+ # not raise an IndexError.
120+ request._app_names = ['foo']
121+ view = StubbedOpenIDLogin(object(), request)
122+ view()
123+ escaped_args = tuple(map(urllib.quote, args.items()[0]))
124+ expected_fragment = urllib.quote('%s=%s' % escaped_args)
125+ return Contains(
126+ expected_fragment).match(view.openid_request.return_to)
127+
128+ def __str__(self):
129+ return 'ForwardsCorrectly()'
130+
131+
132 class TestOpenIDLogin(TestCaseWithFactory):
133 layer = DatabaseFunctionalLayer
134
135- def test_return_to_with_non_ascii_chars(self):
136- # Sometimes the +login link will have non-ascii characters in the
137- # query string, and we need to include those in the return_to URL that
138- # we pass to the OpenID provider, so we must utf-encode them.
139- request = LaunchpadTestRequest(
140- form={'non_ascii_field': 'subproc\xc3\xa9s'})
141- # This is a hack to make the request.getURL(1) call issued by the view
142- # not raise an IndexError.
143- request._app_names = ['foo']
144- view = StubbedOpenIDLogin(object(), request)
145- view()
146- self.assertIn(
147- 'non_ascii_field%3Dsubproc%C3%A9s', view.openid_request.return_to)
148+ def test_return_to_with_non_ascii_value_bug_61171(self):
149+ # Sometimes the +login link will have non-ascii characters in the
150+ # query param values, and we need to include those in the return_to URL
151+ # that we pass to the OpenID provider. The params may not be legimate
152+ # utf8 even.
153+ self.assertThat('key=value\x85', ForwardsCorrectly())
154+
155+ def test_return_to_with_non_ascii_key_bug_897039(self):
156+ # Sometimes the +login link will have non-ascii characters in the
157+ # query param keys, and we need to include those in the return_to URL
158+ # that we pass to the OpenID provider. The params may not be legimate
159+ # utf8 even.
160+ self.assertThat('key\x85=value', ForwardsCorrectly())
161
162 def test_sreg_fields(self):
163 # We request the user's email address and Full Name (through the SREG