Merge lp:~lifeless/js-oopsd/post into lp:js-oopsd

Proposed by Robert Collins on 2012-09-10
Status: Merged
Merged at revision: 13
Proposed branch: lp:~lifeless/js-oopsd/post
Merge into: lp:js-oopsd
Diff against target: 104 lines (+42/-6)
3 files modified
NEWS (+3/-0)
js_oopsd/main.py (+14/-2)
js_oopsd/tests/test_main.py (+25/-4)
To merge this branch: bzr merge lp:~lifeless/js-oopsd/post
Reviewer Review Type Date Requested Status
John A Meinel (community) code* Approve on 2012-09-10
Ian Booth (community) 2012-09-10 Approve on 2012-09-10
Review via email: mp+123474@code.launchpad.net

Description of the Change

keep browsers happier - implement CORS rules to permit any browser to use
POST or GET, and permit preflight checks.

To post a comment you must log in.
Robert Collins (lifeless) wrote :
Ian Booth (wallyworld) wrote :

Looks ok, but I'm not really sufficiently across the finer detail of the specs to know for sure that nothing unintended is not being introduced. So a conditional approval from me.

review: Approve
John A Meinel (jameinel) wrote :

I think the headers listed here aren't quite correct. You mention wanting to support GET and POST, but you list PUT and DELETE.

I also think it is good to test the exact headers in the test suite, so that changing the input in 'main.py' won't have the test suite still pass without updating. (It is good to test that the headers make it into the output, but we *also* need to test that the headers contain the fields we need in the real world.)

Beyond that, I think getting something landed, so it can be tested with real browsers is the most important thing. Having an allow '*' is a little concerning for me, I'd be happier with an Allow *.canonical.com *.launchpad.net or something like that, but I haven't dug into the spec to be sure its all ok.

And in the end, I think we're pretty confident that POST/GET from OOPS isn't going to be a cross-site-scripting attack, so we can just allow it.

So approve with some tweaks.

review: Approve (code*)
lp:~lifeless/js-oopsd/post updated on 2012-09-10
14. By Robert Collins on 2012-09-10

Fix methods, make one non-circular test.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'NEWS'
2--- NEWS 2012-09-09 04:08:16 +0000
3+++ NEWS 2012-09-10 05:30:25 +0000
4@@ -7,3 +7,6 @@
5 ----
6
7 * GET and POST support added.
8+
9+* CORS headers added to the responses, and CORS preflight OPTIONS check added.
10+ (Robert Collins)
11
12=== modified file 'js_oopsd/main.py'
13--- js_oopsd/main.py 2012-09-09 04:08:16 +0000
14+++ js_oopsd/main.py 2012-09-10 05:30:25 +0000
15@@ -29,6 +29,14 @@
16 import oops_wsgi
17 from paste.request import parse_formvars
18
19+cors_headers = (
20+ ('Access-Control-Allow-Origin', '*'),
21+ ('Access-Control-Max-Age', '3628800'),
22+ ('Access-Control-Allow-Methods', 'GET, POST'),
23+ ('Access-Control-Expose-Headers', 'X-OOPS-ID'),
24+ )
25+
26+
27 def make_app(jsoops_config):
28 def app(environ, start_response):
29 # NB: probably want oops on_create hooks and to create the context, not the
30@@ -53,9 +61,12 @@
31 elif method == 'GET':
32 query = environ.get('QUERY_STRING', '')
33 json_content = unquote(query)
34+ elif method == 'OPTIONS':
35+ start_response('200 OK', list(cors_headers))
36+ return
37 else:
38 # Not supported
39- start_response('405 Method Not Allowed', [('Allow', 'GET, POST')])
40+ start_response('405 Method Not Allowed', [('Allow', 'GET, OPTIONS, POST')])
41 return
42 if not json_content:
43 start_response('400 Empty OOPS report', [])
44@@ -77,7 +88,8 @@
45 # future.
46 report.update(report_extra)
47 jsoops_config.publish(report)
48- start_response('200 OK', [('X-OOPS-ID', str(report['id']))])
49+ start_response('200 OK',
50+ list(cors_headers) + [('X-OOPS-ID', str(report['id']))])
51 yield ''
52 return app
53
54
55=== modified file 'js_oopsd/tests/test_main.py'
56--- js_oopsd/tests/test_main.py 2012-09-09 04:08:16 +0000
57+++ js_oopsd/tests/test_main.py 2012-09-10 05:30:25 +0000
58@@ -20,7 +20,11 @@
59 from testtools import TestCase
60 from testtools.matchers import Equals
61
62-from js_oopsd.main import make_app
63+from js_oopsd.main import (
64+ cors_headers,
65+ make_app,
66+ )
67+
68
69 def capture():
70 results = []
71@@ -55,8 +59,25 @@
72 output = list(app(environ, start_response))
73 self.assertThat(output, Equals([]))
74 self.assertThat(events,
75- Equals([('405 Method Not Allowed', [('Allow', 'GET, POST')])]))
76-
77+ Equals([('405 Method Not Allowed',
78+ [('Allow', 'GET, OPTIONS, POST')])]))
79+
80+ def test_options(self):
81+ # CORS requires us to tell browsers they are allowed to use the
82+ # service.
83+ app, events, start_response = test_app()
84+ environ = {
85+ 'PATH_INFO': '/',
86+ 'REQUEST_METHOD': 'OPTIONS',
87+ }
88+ output = list(app(environ, start_response))
89+ self.assertThat(output, Equals([]))
90+ self.assertThat(events, Equals([('200 OK',
91+ [('Access-Control-Allow-Origin', '*'),
92+ ('Access-Control-Max-Age', '3628800'),
93+ ('Access-Control-Allow-Methods', 'GET, POST'),
94+ ('Access-Control-Expose-Headers', 'X-OOPS-ID')])]))
95+
96 def test_post_empty(self):
97 # Empty generates a error response.
98 app, events, start_response = test_app()
99@@ -97,5 +118,5 @@
100 self.assertThat(output, Equals(['']))
101 self.assertThat(events,
102 Equals([{'id': '1', u'reporter': u'fred'},
103- ('200 OK', [('X-OOPS-ID', '1')])]))
104+ ('200 OK', list(cors_headers) + [('X-OOPS-ID', '1')])]))
105

Subscribers

People subscribed via source and target branches

to all changes: