Merge lp:~benji/launchpad/bug-597324 into lp:launchpad
- bug-597324
- Merge into devel
Proposed by
Benji York
Status: | Merged |
---|---|
Approved by: | Māris Fogels |
Approved revision: | no longer in the source branch. |
Merged at revision: | 11378 |
Proposed branch: | lp:~benji/launchpad/bug-597324 |
Merge into: | lp:launchpad |
Diff against target: |
368 lines (+184/-102) 4 files modified
lib/canonical/launchpad/webapp/login.py (+0/-9) lib/canonical/launchpad/webapp/publication.py (+85/-72) lib/canonical/launchpad/webapp/tests/test_login.py (+0/-18) lib/canonical/launchpad/webapp/tests/test_publication.py (+99/-3) |
To merge this branch: | bzr merge lp:~benji/launchpad/bug-597324 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Māris Fogels (community) | Approve | ||
Review via email: mp+32886@code.launchpad.net |
Commit message
Description of the change
This merge proposal covers three relatively small changes to this branch
(r11114-r11118):
- removal an attempted (and ill-fated) work-around for bug 608920
- addition of tests for all existing maybe_block_
paths (in preparation to add another exception)
- addition of referer requirement exception for OpenID callback (fixes
the error described in
https:/
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'lib/canonical/launchpad/webapp/login.py' | |||
2 | --- lib/canonical/launchpad/webapp/login.py 2010-07-24 17:43:17 +0000 | |||
3 | +++ lib/canonical/launchpad/webapp/login.py 2010-08-17 16:05:20 +0000 | |||
4 | @@ -254,15 +254,6 @@ | |||
5 | 254 | 'Did not expect multi-valued fields.') | 254 | 'Did not expect multi-valued fields.') |
6 | 255 | params[key] = value[0] | 255 | params[key] = value[0] |
7 | 256 | 256 | ||
8 | 257 | # XXX benji 2010-07-23 bug=608920 | ||
9 | 258 | # The production OpenID provider has some Django middleware that | ||
10 | 259 | # generates a token used to prevent XSRF attacks and stuffs it into | ||
11 | 260 | # every form. Unfortunately that includes forms that have off-site | ||
12 | 261 | # targets and since our OpenID client verifies that no form values have | ||
13 | 262 | # been injected as a security precaution, this breaks logging-in in | ||
14 | 263 | # certain circumstances (see bug 597324). The best we can do at the | ||
15 | 264 | # moment is to remove the token before invoking the OpenID library. | ||
16 | 265 | params.pop('csrfmiddlewaretoken', None) | ||
17 | 266 | return params | 257 | return params |
18 | 267 | 258 | ||
19 | 268 | def _get_requested_url(self, request): | 259 | def _get_requested_url(self, request): |
20 | 269 | 260 | ||
21 | === modified file 'lib/canonical/launchpad/webapp/publication.py' | |||
22 | --- lib/canonical/launchpad/webapp/publication.py 2010-07-30 00:06:00 +0000 | |||
23 | +++ lib/canonical/launchpad/webapp/publication.py 2010-08-17 16:05:20 +0000 | |||
24 | @@ -60,6 +60,90 @@ | |||
25 | 60 | 60 | ||
26 | 61 | METHOD_WRAPPER_TYPE = type({}.__setitem__) | 61 | METHOD_WRAPPER_TYPE = type({}.__setitem__) |
27 | 62 | 62 | ||
28 | 63 | OFFSITE_POST_WHITELIST = ('/+storeblob', '/+request-token', '/+access-token', | ||
29 | 64 | '/+hwdb/+submit', '/+openid') | ||
30 | 65 | |||
31 | 66 | |||
32 | 67 | def maybe_block_offsite_form_post(request): | ||
33 | 68 | """Check if an attempt was made to post a form from a remote site. | ||
34 | 69 | |||
35 | 70 | This is a cross-site request forgery (XSRF/CSRF) countermeasure. | ||
36 | 71 | |||
37 | 72 | The OffsiteFormPostError exception is raised if the following | ||
38 | 73 | holds true: | ||
39 | 74 | 1. the request method is POST *AND* | ||
40 | 75 | 2. a. the HTTP referer header is empty *OR* | ||
41 | 76 | b. the host portion of the referrer is not a registered vhost | ||
42 | 77 | """ | ||
43 | 78 | if request.method != 'POST': | ||
44 | 79 | return | ||
45 | 80 | if (IOAuthSignedRequest.providedBy(request) | ||
46 | 81 | or not IBrowserRequest.providedBy(request)): | ||
47 | 82 | # We only want to check for the referrer header if we are | ||
48 | 83 | # in the middle of a request initiated by a web browser. A | ||
49 | 84 | # request to the web service (which is necessarily | ||
50 | 85 | # OAuth-signed) or a request that does not implement | ||
51 | 86 | # IBrowserRequest (such as an XML-RPC request) can do | ||
52 | 87 | # without a Referer. | ||
53 | 88 | return | ||
54 | 89 | if request['PATH_INFO'] in OFFSITE_POST_WHITELIST: | ||
55 | 90 | # XXX: jamesh 2007-11-23 bug=124421: | ||
56 | 91 | # Allow offsite posts to our TestOpenID endpoint. Ideally we'd | ||
57 | 92 | # have a better way of marking this URL as allowing offsite | ||
58 | 93 | # form posts. | ||
59 | 94 | # | ||
60 | 95 | # XXX gary 2010-03-09 bug=535122,538097 | ||
61 | 96 | # The one-off exceptions are necessary because existing | ||
62 | 97 | # non-browser applications make requests to these URLs | ||
63 | 98 | # without providing a Referer. Apport makes POST requests | ||
64 | 99 | # to +storeblob without providing a Referer (bug 538097), | ||
65 | 100 | # and launchpadlib used to make POST requests to | ||
66 | 101 | # +request-token and +access-token without providing a | ||
67 | 102 | # Referer. | ||
68 | 103 | # | ||
69 | 104 | # XXX Abel Deuring 2010-04-09 bug=550973 | ||
70 | 105 | # The HWDB client "checkbox" accesses /+hwdb/+submit without | ||
71 | 106 | # a referer. This will change in the version in Ubuntu 10.04, | ||
72 | 107 | # but Launchpad should support HWDB submissions from older | ||
73 | 108 | # Ubuntu versions during their support period. | ||
74 | 109 | # | ||
75 | 110 | # We'll have to keep an application's one-off exception | ||
76 | 111 | # until the application has been changed to send a | ||
77 | 112 | # Referer, and until we have no legacy versions of that | ||
78 | 113 | # application to support. For instance, we can't get rid | ||
79 | 114 | # of the apport exception until after Lucid's end-of-life | ||
80 | 115 | # date. We should be able to get rid of the launchpadlib | ||
81 | 116 | # exception after Karmic's end-of-life date. | ||
82 | 117 | return | ||
83 | 118 | if request['PATH_INFO'].startswith('/+openid-callback'): | ||
84 | 119 | # If this is a callback from an OpenID provider, we don't require an | ||
85 | 120 | # on-site referer (because the provider may be off-site). This | ||
86 | 121 | # exception was added as a result of bug 597324 (message #10 in | ||
87 | 122 | # particular). | ||
88 | 123 | return | ||
89 | 124 | referrer = request.getHeader('referer') # match HTTP spec misspelling | ||
90 | 125 | if not referrer: | ||
91 | 126 | raise NoReferrerError('No value for REFERER header') | ||
92 | 127 | # XXX: jamesh 2007-04-26 bug=98437: | ||
93 | 128 | # The Zope testing infrastructure sets a default (incorrect) | ||
94 | 129 | # referrer value of "localhost" or "localhost:9000" if no | ||
95 | 130 | # referrer is included in the request. We let it pass through | ||
96 | 131 | # here for the benefits of the tests. Web browsers send full | ||
97 | 132 | # URLs so this does not open us up to extra XSRF attacks. | ||
98 | 133 | if referrer in ['localhost', 'localhost:9000']: | ||
99 | 134 | return | ||
100 | 135 | # Extract the hostname from the referrer URI | ||
101 | 136 | try: | ||
102 | 137 | hostname = URI(referrer).host | ||
103 | 138 | except InvalidURIError: | ||
104 | 139 | hostname = None | ||
105 | 140 | if hostname not in allvhosts.hostnames: | ||
106 | 141 | raise OffsiteFormPostError(referrer) | ||
107 | 142 | |||
108 | 143 | |||
109 | 144 | class ProfilingOops(Exception): | ||
110 | 145 | """Fake exception used to log OOPS information when profiling pages.""" | ||
111 | 146 | |||
112 | 63 | 147 | ||
113 | 64 | class LoginRoot: | 148 | class LoginRoot: |
114 | 65 | """Object that provides IPublishTraverse to return only itself. | 149 | """Object that provides IPublishTraverse to return only itself. |
115 | @@ -191,7 +275,7 @@ | |||
116 | 191 | principal = self.getPrincipal(request) | 275 | principal = self.getPrincipal(request) |
117 | 192 | request.setPrincipal(principal) | 276 | request.setPrincipal(principal) |
118 | 193 | self.maybeRestrictToTeam(request) | 277 | self.maybeRestrictToTeam(request) |
120 | 194 | self.maybeBlockOffsiteFormPost(request) | 278 | maybe_block_offsite_form_post(request) |
121 | 195 | self.maybeNotifyReadOnlyMode(request) | 279 | self.maybeNotifyReadOnlyMode(request) |
122 | 196 | 280 | ||
123 | 197 | def maybeNotifyReadOnlyMode(self, request): | 281 | def maybeNotifyReadOnlyMode(self, request): |
124 | @@ -295,77 +379,6 @@ | |||
125 | 295 | uri = uri.replace(query=query_string) | 379 | uri = uri.replace(query=query_string) |
126 | 296 | return str(uri) | 380 | return str(uri) |
127 | 297 | 381 | ||
128 | 298 | def maybeBlockOffsiteFormPost(self, request): | ||
129 | 299 | """Check if an attempt was made to post a form from a remote site. | ||
130 | 300 | |||
131 | 301 | The OffsiteFormPostError exception is raised if the following | ||
132 | 302 | holds true: | ||
133 | 303 | 1. the request method is POST *AND* | ||
134 | 304 | 2. a. the HTTP referer header is empty *OR* | ||
135 | 305 | b. the host portion of the referrer is not a registered vhost | ||
136 | 306 | """ | ||
137 | 307 | if request.method != 'POST': | ||
138 | 308 | return | ||
139 | 309 | # XXX: jamesh 2007-11-23 bug=124421: | ||
140 | 310 | # Allow offsite posts to our TestOpenID endpoint. Ideally we'd | ||
141 | 311 | # have a better way of marking this URL as allowing offsite | ||
142 | 312 | # form posts. | ||
143 | 313 | if request['PATH_INFO'] == '/+openid': | ||
144 | 314 | return | ||
145 | 315 | if (IOAuthSignedRequest.providedBy(request) | ||
146 | 316 | or not IBrowserRequest.providedBy(request) | ||
147 | 317 | or request['PATH_INFO'] in ( | ||
148 | 318 | '/+storeblob', '/+request-token', '/+access-token', | ||
149 | 319 | '/+hwdb/+submit')): | ||
150 | 320 | # We only want to check for the referrer header if we are | ||
151 | 321 | # in the middle of a request initiated by a web browser. A | ||
152 | 322 | # request to the web service (which is necessarily | ||
153 | 323 | # OAuth-signed) or a request that does not implement | ||
154 | 324 | # IBrowserRequest (such as an XML-RPC request) can do | ||
155 | 325 | # without a Referer. | ||
156 | 326 | # | ||
157 | 327 | # XXX gary 2010-03-09 bug=535122,538097 | ||
158 | 328 | # The one-off exceptions are necessary because existing | ||
159 | 329 | # non-browser applications make requests to these URLs | ||
160 | 330 | # without providing a Referer. Apport makes POST requests | ||
161 | 331 | # to +storeblob without providing a Referer (bug 538097), | ||
162 | 332 | # and launchpadlib used to make POST requests to | ||
163 | 333 | # +request-token and +access-token without providing a | ||
164 | 334 | # Referer. | ||
165 | 335 | # | ||
166 | 336 | # XXX Abel Deuring 2010-04-09 bug=550973 | ||
167 | 337 | # The HWDB client "checkbox" accesses /+hwdb/+submit without | ||
168 | 338 | # a referer. This will change in the version in Ubuntu 10.04, | ||
169 | 339 | # but Launchpad should support HWDB submissions from older | ||
170 | 340 | # Ubuntu versions during their support period. | ||
171 | 341 | # | ||
172 | 342 | # We'll have to keep an application's one-off exception | ||
173 | 343 | # until the application has been changed to send a | ||
174 | 344 | # Referer, and until we have no legacy versions of that | ||
175 | 345 | # application to support. For instance, we can't get rid | ||
176 | 346 | # of the apport exception until after Lucid's end-of-life | ||
177 | 347 | # date. We should be able to get rid of the launchpadlib | ||
178 | 348 | # exception after Karmic's end-of-life date. | ||
179 | 349 | return | ||
180 | 350 | referrer = request.getHeader('referer') # match HTTP spec misspelling | ||
181 | 351 | if not referrer: | ||
182 | 352 | raise NoReferrerError('No value for REFERER header') | ||
183 | 353 | # XXX: jamesh 2007-04-26 bug=98437: | ||
184 | 354 | # The Zope testing infrastructure sets a default (incorrect) | ||
185 | 355 | # referrer value of "localhost" or "localhost:9000" if no | ||
186 | 356 | # referrer is included in the request. We let it pass through | ||
187 | 357 | # here for the benefits of the tests. Web browsers send full | ||
188 | 358 | # URLs so this does not open us up to extra XSRF attacks. | ||
189 | 359 | if referrer in ['localhost', 'localhost:9000']: | ||
190 | 360 | return | ||
191 | 361 | # Extract the hostname from the referrer URI | ||
192 | 362 | try: | ||
193 | 363 | hostname = URI(referrer).host | ||
194 | 364 | except InvalidURIError: | ||
195 | 365 | hostname = None | ||
196 | 366 | if hostname not in allvhosts.hostnames: | ||
197 | 367 | raise OffsiteFormPostError(referrer) | ||
198 | 368 | |||
199 | 369 | def constructPageID(self, view, context): | 382 | def constructPageID(self, view, context): |
200 | 370 | """Given a view, figure out what its page ID should be. | 383 | """Given a view, figure out what its page ID should be. |
201 | 371 | 384 | ||
202 | 372 | 385 | ||
203 | === modified file 'lib/canonical/launchpad/webapp/tests/test_login.py' | |||
204 | --- lib/canonical/launchpad/webapp/tests/test_login.py 2010-08-12 21:26:53 +0000 | |||
205 | +++ lib/canonical/launchpad/webapp/tests/test_login.py 2010-08-17 16:05:20 +0000 | |||
206 | @@ -219,24 +219,6 @@ | |||
207 | 219 | view = OpenIDCallbackView(context=None, request=None) | 219 | view = OpenIDCallbackView(context=None, request=None) |
208 | 220 | self.assertRaises(ValueError, view._gather_params, request) | 220 | self.assertRaises(ValueError, view._gather_params, request) |
209 | 221 | 221 | ||
210 | 222 | def test_csrfmiddlewaretoken_is_ignored(self): | ||
211 | 223 | # Show that the _gather_params filters out the errant | ||
212 | 224 | # csrfmiddlewaretoken form field. See comment in _gather_params for | ||
213 | 225 | # more info. | ||
214 | 226 | request = LaunchpadTestRequest( | ||
215 | 227 | SERVER_URL='http://example.com', | ||
216 | 228 | QUERY_STRING='foo=bar', | ||
217 | 229 | form={'starting_url': 'http://launchpad.dev/after-login', | ||
218 | 230 | 'csrfmiddlewaretoken': '12345'}, | ||
219 | 231 | environ={'PATH_INFO': '/'}) | ||
220 | 232 | view = OpenIDCallbackView(context=None, request=None) | ||
221 | 233 | params = view._gather_params(request) | ||
222 | 234 | expected_params = { | ||
223 | 235 | 'starting_url': 'http://launchpad.dev/after-login', | ||
224 | 236 | 'foo': 'bar', | ||
225 | 237 | } | ||
226 | 238 | self.assertEquals(params, expected_params) | ||
227 | 239 | |||
228 | 240 | def test_get_requested_url(self): | 222 | def test_get_requested_url(self): |
229 | 241 | # The OpenIDCallbackView needs to pass the currently-being-requested | 223 | # The OpenIDCallbackView needs to pass the currently-being-requested |
230 | 242 | # URL to the OpenID library. OpenIDCallbackView._get_requested_url | 224 | # URL to the OpenID library. OpenIDCallbackView._get_requested_url |
231 | 243 | 225 | ||
232 | === modified file 'lib/canonical/launchpad/webapp/tests/test_publication.py' | |||
233 | --- lib/canonical/launchpad/webapp/tests/test_publication.py 2010-05-27 07:53:38 +0000 | |||
234 | +++ lib/canonical/launchpad/webapp/tests/test_publication.py 2010-08-17 16:05:20 +0000 | |||
235 | @@ -10,6 +10,7 @@ | |||
236 | 10 | import unittest | 10 | import unittest |
237 | 11 | 11 | ||
238 | 12 | from contrib.oauth import OAuthRequest, OAuthSignatureMethod_PLAINTEXT | 12 | from contrib.oauth import OAuthRequest, OAuthSignatureMethod_PLAINTEXT |
239 | 13 | from zope.interface import directlyProvides, noLongerProvides | ||
240 | 13 | 14 | ||
241 | 14 | from storm.database import STATE_DISCONNECTED, STATE_RECONNECT | 15 | from storm.database import STATE_DISCONNECTED, STATE_RECONNECT |
242 | 15 | from storm.exceptions import DisconnectionError | 16 | from storm.exceptions import DisconnectionError |
243 | @@ -18,20 +19,25 @@ | |||
244 | 18 | from zope.component import getUtility | 19 | from zope.component import getUtility |
245 | 19 | from zope.error.interfaces import IErrorReportingUtility | 20 | from zope.error.interfaces import IErrorReportingUtility |
246 | 20 | from zope.publisher.interfaces import Retry | 21 | from zope.publisher.interfaces import Retry |
247 | 22 | from zope.publisher.interfaces.browser import IBrowserRequest | ||
248 | 21 | 23 | ||
249 | 22 | from canonical.config import dbconfig | 24 | from canonical.config import dbconfig |
250 | 23 | from canonical.launchpad.database.emailaddress import EmailAddress | 25 | from canonical.launchpad.database.emailaddress import EmailAddress |
251 | 24 | from canonical.launchpad.interfaces.lpstorm import IMasterStore | 26 | from canonical.launchpad.interfaces.lpstorm import IMasterStore |
253 | 25 | from canonical.launchpad.interfaces.oauth import IOAuthConsumerSet | 27 | from canonical.launchpad.interfaces.oauth import ( |
254 | 28 | IOAuthConsumerSet, IOAuthSignedRequest) | ||
255 | 26 | from canonical.launchpad.ftests import ANONYMOUS, login | 29 | from canonical.launchpad.ftests import ANONYMOUS, login |
256 | 27 | from canonical.launchpad.readonly import is_read_only | 30 | from canonical.launchpad.readonly import is_read_only |
257 | 28 | from canonical.launchpad.tests.readonly import ( | 31 | from canonical.launchpad.tests.readonly import ( |
258 | 29 | remove_read_only_file, touch_read_only_file) | 32 | remove_read_only_file, touch_read_only_file) |
259 | 30 | import canonical.launchpad.webapp.adapter as dbadapter | 33 | import canonical.launchpad.webapp.adapter as dbadapter |
260 | 34 | from canonical.launchpad.webapp.vhosts import allvhosts | ||
261 | 31 | from canonical.launchpad.webapp.interfaces import ( | 35 | from canonical.launchpad.webapp.interfaces import ( |
263 | 32 | IStoreSelector, MAIN_STORE, MASTER_FLAVOR, OAuthPermission, SLAVE_FLAVOR) | 36 | IStoreSelector, MAIN_STORE, MASTER_FLAVOR, OAuthPermission, SLAVE_FLAVOR, |
264 | 37 | OffsiteFormPostError, NoReferrerError) | ||
265 | 33 | from canonical.launchpad.webapp.publication import ( | 38 | from canonical.launchpad.webapp.publication import ( |
267 | 34 | is_browser, LaunchpadBrowserPublication) | 39 | is_browser, LaunchpadBrowserPublication, maybe_block_offsite_form_post, |
268 | 40 | OFFSITE_POST_WHITELIST) | ||
269 | 35 | from canonical.launchpad.webapp.servers import ( | 41 | from canonical.launchpad.webapp.servers import ( |
270 | 36 | LaunchpadTestRequest, WebServicePublication) | 42 | LaunchpadTestRequest, WebServicePublication) |
271 | 37 | from canonical.testing import DatabaseFunctionalLayer, FunctionalLayer | 43 | from canonical.testing import DatabaseFunctionalLayer, FunctionalLayer |
272 | @@ -312,6 +318,96 @@ | |||
273 | 312 | self.assertFalse(is_browser(request)) | 318 | self.assertFalse(is_browser(request)) |
274 | 313 | 319 | ||
275 | 314 | 320 | ||
276 | 321 | class TestBlockingOffsitePosts(TestCase): | ||
277 | 322 | """We are very particular about what form POSTs we will accept.""" | ||
278 | 323 | |||
279 | 324 | def test_NoReferrerError(self): | ||
280 | 325 | # If this request is a POST and there is no referrer, an exception is | ||
281 | 326 | # raised. | ||
282 | 327 | request = LaunchpadTestRequest( | ||
283 | 328 | method='POST', environ=dict(PATH_INFO='/')) | ||
284 | 329 | self.assertRaises( | ||
285 | 330 | NoReferrerError, maybe_block_offsite_form_post, request) | ||
286 | 331 | |||
287 | 332 | def test_nonPOST_requests(self): | ||
288 | 333 | # If the request isn't a POST it is always allowed. | ||
289 | 334 | request = LaunchpadTestRequest(method='SOMETHING') | ||
290 | 335 | maybe_block_offsite_form_post(request) | ||
291 | 336 | |||
292 | 337 | def test_localhost_is_ok(self): | ||
293 | 338 | # we accept "localhost" and "localhost:9000" as valid referrers. See | ||
294 | 339 | # comments in the code as to why and for a related bug report. | ||
295 | 340 | request = LaunchpadTestRequest( | ||
296 | 341 | method='POST', environ=dict(PATH_INFO='/', REFERER='localhost')) | ||
297 | 342 | # this doesn't raise an exception | ||
298 | 343 | maybe_block_offsite_form_post(request) | ||
299 | 344 | |||
300 | 345 | def test_whitelisted_paths(self): | ||
301 | 346 | # There are a few whitelisted POST targets that don't require the | ||
302 | 347 | # referrer be LP. See comments in the code as to why and for related | ||
303 | 348 | # bug reports. | ||
304 | 349 | for path in OFFSITE_POST_WHITELIST: | ||
305 | 350 | request = LaunchpadTestRequest( | ||
306 | 351 | method='POST', environ=dict(PATH_INFO=path)) | ||
307 | 352 | # this call shouldn't raise an exception | ||
308 | 353 | maybe_block_offsite_form_post(request) | ||
309 | 354 | |||
310 | 355 | def test_OAuth_signed_requests(self): | ||
311 | 356 | # Requests that are OAuth signed are allowed. | ||
312 | 357 | request = LaunchpadTestRequest( | ||
313 | 358 | method='POST', environ=dict(PATH_INFO='/')) | ||
314 | 359 | directlyProvides(request, IOAuthSignedRequest) | ||
315 | 360 | # this call shouldn't raise an exception | ||
316 | 361 | maybe_block_offsite_form_post(request) | ||
317 | 362 | |||
318 | 363 | def test_nonbrowser_requests(self): | ||
319 | 364 | # Requests that are from non-browsers are allowed. | ||
320 | 365 | class FakeNonBrowserRequest: | ||
321 | 366 | method = 'SOMETHING' | ||
322 | 367 | |||
323 | 368 | # this call shouldn't raise an exception | ||
324 | 369 | maybe_block_offsite_form_post(FakeNonBrowserRequest) | ||
325 | 370 | |||
326 | 371 | def test_onsite_posts(self): | ||
327 | 372 | # Other than the explicit execptions, all POSTs have to come from a | ||
328 | 373 | # known LP virtual host. | ||
329 | 374 | for hostname in allvhosts.hostnames: | ||
330 | 375 | referer = 'http://' + hostname + '/foo' | ||
331 | 376 | request = LaunchpadTestRequest( | ||
332 | 377 | method='POST', environ=dict(PATH_INFO='/', REFERER=referer)) | ||
333 | 378 | # this call shouldn't raise an exception | ||
334 | 379 | maybe_block_offsite_form_post(request) | ||
335 | 380 | |||
336 | 381 | def test_offsite_posts(self): | ||
337 | 382 | # If a post comes from an unknown host an execption is raised. | ||
338 | 383 | disallowed_hosts = ['example.com', 'not-subdomain.launchpad.net'] | ||
339 | 384 | for hostname in disallowed_hosts: | ||
340 | 385 | referer = 'http://' + hostname + '/foo' | ||
341 | 386 | request = LaunchpadTestRequest( | ||
342 | 387 | method='POST', environ=dict(PATH_INFO='/', REFERER=referer)) | ||
343 | 388 | self.assertRaises( | ||
344 | 389 | OffsiteFormPostError, maybe_block_offsite_form_post, request) | ||
345 | 390 | |||
346 | 391 | def test_unparsable_referer(self): | ||
347 | 392 | # If a post has a referer that is unparsable as a URI an exception is | ||
348 | 393 | # raised. | ||
349 | 394 | referer = 'this is not a URI' | ||
350 | 395 | request = LaunchpadTestRequest( | ||
351 | 396 | method='POST', environ=dict(PATH_INFO='/', REFERER=referer)) | ||
352 | 397 | self.assertRaises( | ||
353 | 398 | OffsiteFormPostError, maybe_block_offsite_form_post, request) | ||
354 | 399 | |||
355 | 400 | |||
356 | 401 | def test_openid_callback_with_query_string(self): | ||
357 | 402 | # An OpenId provider (OP) may post to the +openid-callback URL with a | ||
358 | 403 | # query string and without a referer. These posts need to be allowed. | ||
359 | 404 | path_info = u'/+openid-callback?starting_url=...' | ||
360 | 405 | request = LaunchpadTestRequest( | ||
361 | 406 | method='POST', environ=dict(PATH_INFO=path_info)) | ||
362 | 407 | # this call shouldn't raise an exception | ||
363 | 408 | maybe_block_offsite_form_post(request) | ||
364 | 409 | |||
365 | 410 | |||
366 | 315 | def test_suite(): | 411 | def test_suite(): |
367 | 316 | suite = unittest.TestLoader().loadTestsFromName(__name__) | 412 | suite = unittest.TestLoader().loadTestsFromName(__name__) |
368 | 317 | return suite | 413 | return suite |
Hi Benji,
This change looks good to me. The test suite addition is particularly welcome. That code is absolutely critical judging by the number and detail of the comments, so the additional test coverage counts many many times over. Big +1 for that.
On line 352 of the diff you have a comment that says '# this should not raise an exception', and then on the next line you call self.assertRais es(). The comment probably needs to be deleted.
Otherwise, this change looks good. r=mars
Maris