Mercurial > p > roundup > code
comparison test/test_cgi.py @ 6693:9a1f5e496e6c
issue2551203 - Add support for CORS preflight request
Add support for unauthenticated CORS preflight and fix headers for
CORS.
client.py:
pass through unauthenticated CORS preflight to rest backend. Normal
rest OPTION handlers (including tracker defined extensions) can
see and handle the request.
make some error cases return error json with crrect mime type rather
than plain text tracebacks.
create new functions to verify origin and referer that filter using
allowed origins setting.
remove tracker base url from error message is referer is not at an
allowed origin.
rest.py:
fix up OPTION methods handlers to include
Access-Control-Allow-Methods that are the same as the Allow
header.
set cache to one week for all Access-Control headers for CORS
preflight only.
remove self.client.setHeader("Access-Control-Allow-Origin", "*") and
set Access-Control-Allow-Origin to the client supplied origin if
it passes allowed origin checks. Required for CORS otherwise data
isn't available to caller. Set for all responses.
set Vary header now includes Origin as responses can differ based on
Origin for all responses.
set Access-Control-Allow-Credentials to true on all responses.
test_liveserver.py:
run server with setting to enforce origin csrf header check
run server with setting to enforce x-requested-with csrf header check
run server with setting for allowed_api_origins
requests now set required csrf headers
test preflight request on collections
check new headers and Origin is no longer '*'
rewrite all compression checks to use a single method with argument
to use different compression methods. Reduce a lot of code
duplication and makes updating for new headers easier.
test_cgi:
test new error messages in client.py
account for new headers
test preflight and new code paths
| author | John Rouillard <rouilj@ieee.org> |
|---|---|
| date | Tue, 07 Jun 2022 09:39:35 -0400 |
| parents | ab2ed11c021e |
| children | 1181157d7cec |
comparison
equal
deleted
inserted
replaced
| 6692:1fbfb4a277d7 | 6693:9a1f5e496e6c |
|---|---|
| 1167 print("result of subtest invalid origin:", out[0]) | 1167 print("result of subtest invalid origin:", out[0]) |
| 1168 self.assertEqual(match_at, 36) | 1168 self.assertEqual(match_at, 36) |
| 1169 del(cl.env['HTTP_ORIGIN']) | 1169 del(cl.env['HTTP_ORIGIN']) |
| 1170 cl.db.config.WEB_ALLOWED_API_ORIGINS = "" | 1170 cl.db.config.WEB_ALLOWED_API_ORIGINS = "" |
| 1171 del(out[0]) | 1171 del(out[0]) |
| 1172 | |
| 1173 # test by setting allowed api origins to * | |
| 1174 # this should not redirect as it is not an API call. | |
| 1175 cl.db.config.WEB_ALLOWED_API_ORIGINS = " * " | |
| 1176 cl.env['HTTP_ORIGIN'] = 'http://whoami.com' | |
| 1177 cl.env['HTTP_REFERER'] = 'https://baz.edu/path/' | |
| 1178 cl.inner_main() | |
| 1179 match_at=out[0].find('Invalid Referer: https://baz.edu/path/') | |
| 1180 print("result of subtest invalid referer:", out[0]) | |
| 1181 self.assertEqual(match_at, 36) | |
| 1182 del(cl.env['HTTP_ORIGIN']) | |
| 1183 del(cl.env['HTTP_REFERER']) | |
| 1184 cl.db.config.WEB_ALLOWED_API_ORIGINS = "" | |
| 1185 del(out[0]) | |
| 1172 | 1186 |
| 1173 # clean up from email log | 1187 # clean up from email log |
| 1174 if os.path.exists(SENDMAILDEBUG): | 1188 if os.path.exists(SENDMAILDEBUG): |
| 1175 os.remove(SENDMAILDEBUG) | 1189 os.remove(SENDMAILDEBUG) |
| 1176 #raise ValueError | 1190 #raise ValueError |
| 1213 cl.write = wh # capture output | 1227 cl.write = wh # capture output |
| 1214 | 1228 |
| 1215 # Should return explanation because content type is text/plain | 1229 # Should return explanation because content type is text/plain |
| 1216 # and not text/xml | 1230 # and not text/xml |
| 1217 cl.handle_rest() | 1231 cl.handle_rest() |
| 1218 self.assertEqual(b2s(out[0]), "<class 'roundup.exceptions.UsageError'>: Required Header Missing\n") | 1232 self.assertEqual(b2s(out[0]), '{ "error": { "status": 400, "msg": "Required Header Missing"}}') |
| 1219 del(out[0]) | 1233 del(out[0]) |
| 1220 | 1234 |
| 1221 cl = client.Client(self.instance, None, | 1235 cl = client.Client(self.instance, None, |
| 1222 {'REQUEST_METHOD':'POST', | 1236 {'REQUEST_METHOD':'POST', |
| 1223 'PATH_INFO':'rest/data/issue', | 1237 'PATH_INFO':'rest/data/issue', |
| 1318 cl.write = wh # capture output | 1332 cl.write = wh # capture output |
| 1319 | 1333 |
| 1320 # Should return explanation because content type is text/plain | 1334 # Should return explanation because content type is text/plain |
| 1321 # and not text/xml | 1335 # and not text/xml |
| 1322 cl.handle_rest() | 1336 cl.handle_rest() |
| 1323 self.assertEqual(b2s(out[0]), "<class 'roundup.exceptions.Unauthorised'>: Invalid Origin httxs://bar.edu\n") | 1337 self.assertEqual(b2s(out[0]), '{ "error": { "status": 400, "msg": "Invalid Origin httxs://bar.edu"}}') |
| 1324 del(out[0]) | 1338 del(out[0]) |
| 1325 | 1339 |
| 1326 | 1340 |
| 1327 cl.db.config.WEB_ALLOWED_API_ORIGINS = " * " | 1341 cl.db.config.WEB_ALLOWED_API_ORIGINS = " * " |
| 1328 cl = client.Client(self.instance, None, | 1342 cl = client.Client(self.instance, None, |
| 1330 'PATH_INFO':'rest/data/issue', | 1344 'PATH_INFO':'rest/data/issue', |
| 1331 'CONTENT_TYPE': 'application/x-www-form-urlencoded', | 1345 'CONTENT_TYPE': 'application/x-www-form-urlencoded', |
| 1332 'HTTP_ORIGIN': 'httxs://bar.edu', | 1346 'HTTP_ORIGIN': 'httxs://bar.edu', |
| 1333 'HTTP_X_REQUESTED_WITH': 'rest', | 1347 'HTTP_X_REQUESTED_WITH': 'rest', |
| 1334 'HTTP_AUTHORIZATION': 'Basic YWRtaW46YWRtaW4=', | 1348 'HTTP_AUTHORIZATION': 'Basic YWRtaW46YWRtaW4=', |
| 1335 'HTTP_REFERER': 'http://whoami.com/path/', | 1349 'HTTP_REFERER': 'httxp://bar.edu/path/', |
| 1336 'HTTP_ACCEPT': "application/json;version=1" | 1350 'HTTP_ACCEPT': "application/json;version=1" |
| 1337 }, form) | 1351 }, form) |
| 1338 cl.db = self.db | 1352 cl.db = self.db |
| 1339 cl.base = 'http://whoami.com/path/' | 1353 cl.base = 'http://whoami.com/path/' |
| 1340 cl._socket_op = lambda *x : True | 1354 cl._socket_op = lambda *x : True |
| 1344 'accept': 'application/json' } | 1358 'accept': 'application/json' } |
| 1345 cl.request.headers = MockNull(**h) | 1359 cl.request.headers = MockNull(**h) |
| 1346 | 1360 |
| 1347 cl.write = wh # capture output | 1361 cl.write = wh # capture output |
| 1348 | 1362 |
| 1349 # create third issue | 1363 # create fourth issue |
| 1350 cl.handle_rest() | 1364 cl.handle_rest() |
| 1351 self.assertIn('"id": "3"', b2s(out[0])) | 1365 self.assertIn('"id": "3"', b2s(out[0])) |
| 1366 del(out[0]) | |
| 1367 | |
| 1368 cl.db.config.WEB_ALLOWED_API_ORIGINS = "httxs://bar.foo.edu httxs://bar.edu" | |
| 1369 for referer in [ 'httxs://bar.edu/path/foo', | |
| 1370 'httxs://bar.edu/path/foo?g=zz', | |
| 1371 'httxs://bar.edu']: | |
| 1372 cl = client.Client(self.instance, None, | |
| 1373 {'REQUEST_METHOD':'POST', | |
| 1374 'PATH_INFO':'rest/data/issue', | |
| 1375 'CONTENT_TYPE': 'application/x-www-form-urlencoded', | |
| 1376 'HTTP_ORIGIN': 'httxs://bar.edu', | |
| 1377 'HTTP_X_REQUESTED_WITH': 'rest', | |
| 1378 'HTTP_AUTHORIZATION': 'Basic YWRtaW46YWRtaW4=', | |
| 1379 'HTTP_REFERER': referer, | |
| 1380 'HTTP_ACCEPT': "application/json;version=1" | |
| 1381 }, form) | |
| 1382 cl.db = self.db | |
| 1383 cl.base = 'http://whoami.com/path/' | |
| 1384 cl._socket_op = lambda *x : True | |
| 1385 cl._error_message = [] | |
| 1386 cl.request = MockNull() | |
| 1387 h = { 'content-type': 'application/json', | |
| 1388 'accept': 'application/json' } | |
| 1389 cl.request.headers = MockNull(**h) | |
| 1390 | |
| 1391 cl.write = wh # capture output | |
| 1392 | |
| 1393 # create fourth issue | |
| 1394 cl.handle_rest() | |
| 1395 self.assertIn('"id": "', b2s(out[0])) | |
| 1396 del(out[0]) | |
| 1397 | |
| 1398 cl.db.config.WEB_ALLOWED_API_ORIGINS = "httxs://bar.foo.edu httxs://bar.edu" | |
| 1399 cl = client.Client(self.instance, None, | |
| 1400 {'REQUEST_METHOD':'POST', | |
| 1401 'PATH_INFO':'rest/data/issue', | |
| 1402 'CONTENT_TYPE': 'application/x-www-form-urlencoded', | |
| 1403 'HTTP_ORIGIN': 'httxs://bar.edu', | |
| 1404 'HTTP_X_REQUESTED_WITH': 'rest', | |
| 1405 'HTTP_AUTHORIZATION': 'Basic YWRtaW46YWRtaW4=', | |
| 1406 'HTTP_REFERER': 'httxp://bar.edu/path/', | |
| 1407 'HTTP_ACCEPT': "application/json;version=1" | |
| 1408 }, form) | |
| 1409 cl.db = self.db | |
| 1410 cl.base = 'http://whoami.com/path/' | |
| 1411 cl._socket_op = lambda *x : True | |
| 1412 cl._error_message = [] | |
| 1413 cl.request = MockNull() | |
| 1414 h = { 'content-type': 'application/json', | |
| 1415 'accept': 'application/json' } | |
| 1416 cl.request.headers = MockNull(**h) | |
| 1417 | |
| 1418 cl.write = wh # capture output | |
| 1419 | |
| 1420 # create fourth issue | |
| 1421 cl.handle_rest() | |
| 1422 self.assertEqual(b2s(out[0]), '{ "error": { "status": 400, "msg": "Invalid Referer: httxp://bar.edu/path/"}}') | |
| 1352 del(out[0]) | 1423 del(out[0]) |
| 1353 | 1424 |
| 1354 def testXmlrpcCsrfProtection(self): | 1425 def testXmlrpcCsrfProtection(self): |
| 1355 # set the password for admin so we can log in. | 1426 # set the password for admin so we can log in. |
| 1356 passwd=password.Password('admin') | 1427 passwd=password.Password('admin') |
