Mercurial > p > roundup > code
comparison test/test_cgi.py @ 6681:ab2ed11c021e
issue2551205: Add support for specifying valid origins for api: xmlrpc/rest
We now have an allow list to filter the hosts allowed to do api
requests. An element of this allow list must match the http ORIGIN
header exactly or the rest/xmlrpc CORS request will result in an
error.
The tracker host is always allowed to do a request.
| author | John Rouillard <rouilj@ieee.org> |
|---|---|
| date | Tue, 17 May 2022 17:18:51 -0400 |
| parents | da6c9050a79e |
| children | 9a1f5e496e6c |
comparison
equal
deleted
inserted
replaced
| 6680:b4d0b48b3096 | 6681:ab2ed11c021e |
|---|---|
| 953 | 953 |
| 954 def testCsrfProtection(self): | 954 def testCsrfProtection(self): |
| 955 # need to set SENDMAILDEBUG to prevent | 955 # need to set SENDMAILDEBUG to prevent |
| 956 # downstream issue when email is sent on successful | 956 # downstream issue when email is sent on successful |
| 957 # issue creation. Also delete the file afterwards | 957 # issue creation. Also delete the file afterwards |
| 958 # just tomake sure that someother test looking for | 958 # just to make sure that some other test looking for |
| 959 # SENDMAILDEBUG won't trip over ours. | 959 # SENDMAILDEBUG won't trip over ours. |
| 960 if 'SENDMAILDEBUG' not in os.environ: | 960 if 'SENDMAILDEBUG' not in os.environ: |
| 961 os.environ['SENDMAILDEBUG'] = 'mail-test1.log' | 961 os.environ['SENDMAILDEBUG'] = 'mail-test1.log' |
| 962 SENDMAILDEBUG = os.environ['SENDMAILDEBUG'] | 962 SENDMAILDEBUG = os.environ['SENDMAILDEBUG'] |
| 963 | 963 |
| 1155 print("result of subtest 13:", out[0]) | 1155 print("result of subtest 13:", out[0]) |
| 1156 self.assertEqual(match_at, 36) | 1156 self.assertEqual(match_at, 36) |
| 1157 del(out[0]) | 1157 del(out[0]) |
| 1158 | 1158 |
| 1159 del(cl.env['HTTP_REFERER']) | 1159 del(cl.env['HTTP_REFERER']) |
| 1160 | |
| 1161 # test by setting allowed api origins to * | |
| 1162 # this should not redirect as it is not an API call. | |
| 1163 cl.db.config.WEB_ALLOWED_API_ORIGINS = " * " | |
| 1164 cl.env['HTTP_ORIGIN'] = 'https://baz.edu' | |
| 1165 cl.inner_main() | |
| 1166 match_at=out[0].find('Invalid Origin https://baz.edu') | |
| 1167 print("result of subtest invalid origin:", out[0]) | |
| 1168 self.assertEqual(match_at, 36) | |
| 1169 del(cl.env['HTTP_ORIGIN']) | |
| 1170 cl.db.config.WEB_ALLOWED_API_ORIGINS = "" | |
| 1171 del(out[0]) | |
| 1160 | 1172 |
| 1161 # clean up from email log | 1173 # clean up from email log |
| 1162 if os.path.exists(SENDMAILDEBUG): | 1174 if os.path.exists(SENDMAILDEBUG): |
| 1163 os.remove(SENDMAILDEBUG) | 1175 os.remove(SENDMAILDEBUG) |
| 1164 #raise ValueError | 1176 #raise ValueError |
| 1235 # compare as dicts not strings due to different key ordering | 1247 # compare as dicts not strings due to different key ordering |
| 1236 # between python versions. | 1248 # between python versions. |
| 1237 response=json.loads(b2s(out[0])) | 1249 response=json.loads(b2s(out[0])) |
| 1238 expected=json.loads(answer) | 1250 expected=json.loads(answer) |
| 1239 self.assertEqual(response,expected) | 1251 self.assertEqual(response,expected) |
| 1252 del(out[0]) | |
| 1253 | |
| 1254 | |
| 1255 # rest has no form content | |
| 1256 cl.db.config.WEB_ALLOWED_API_ORIGINS = "https://bar.edu http://bar.edu" | |
| 1257 form = cgi.FieldStorage() | |
| 1258 form.list = [ | |
| 1259 cgi.MiniFieldStorage('title', 'A new issue'), | |
| 1260 cgi.MiniFieldStorage('status', '1'), | |
| 1261 cgi.MiniFieldStorage('@pretty', 'false'), | |
| 1262 cgi.MiniFieldStorage('@apiver', '1'), | |
| 1263 ] | |
| 1264 cl = client.Client(self.instance, None, | |
| 1265 {'REQUEST_METHOD':'POST', | |
| 1266 'PATH_INFO':'rest/data/issue', | |
| 1267 'CONTENT_TYPE': 'application/x-www-form-urlencoded', | |
| 1268 'HTTP_ORIGIN': 'https://bar.edu', | |
| 1269 'HTTP_X_REQUESTED_WITH': 'rest', | |
| 1270 'HTTP_AUTHORIZATION': 'Basic YWRtaW46YWRtaW4=', | |
| 1271 'HTTP_REFERER': 'http://whoami.com/path/', | |
| 1272 'HTTP_ACCEPT': "application/json;version=1" | |
| 1273 }, form) | |
| 1274 cl.db = self.db | |
| 1275 cl.base = 'http://whoami.com/path/' | |
| 1276 cl._socket_op = lambda *x : True | |
| 1277 cl._error_message = [] | |
| 1278 cl.request = MockNull() | |
| 1279 h = { 'content-type': 'application/json', | |
| 1280 'accept': 'application/json' } | |
| 1281 cl.request.headers = MockNull(**h) | |
| 1282 | |
| 1283 cl.write = wh # capture output | |
| 1284 | |
| 1285 # Should return explanation because content type is text/plain | |
| 1286 # and not text/xml | |
| 1287 cl.handle_rest() | |
| 1288 answer='{"data": {"link": "http://tracker.example/cgi-bin/roundup.cgi/bugs/rest/data/issue/2", "id": "2"}}\n' | |
| 1289 # check length to see if pretty is turned off. | |
| 1290 self.assertEqual(len(out[0]), 99) | |
| 1291 | |
| 1292 # compare as dicts not strings due to different key ordering | |
| 1293 # between python versions. | |
| 1294 response=json.loads(b2s(out[0])) | |
| 1295 expected=json.loads(answer) | |
| 1296 self.assertEqual(response,expected) | |
| 1297 del(out[0]) | |
| 1298 | |
| 1299 ##### | |
| 1300 cl = client.Client(self.instance, None, | |
| 1301 {'REQUEST_METHOD':'POST', | |
| 1302 'PATH_INFO':'rest/data/issue', | |
| 1303 'CONTENT_TYPE': 'application/x-www-form-urlencoded', | |
| 1304 'HTTP_ORIGIN': 'httxs://bar.edu', | |
| 1305 'HTTP_AUTHORIZATION': 'Basic YWRtaW46YWRtaW4=', | |
| 1306 'HTTP_REFERER': 'http://whoami.com/path/', | |
| 1307 'HTTP_ACCEPT': "application/json;version=1" | |
| 1308 }, form) | |
| 1309 cl.db = self.db | |
| 1310 cl.base = 'http://whoami.com/path/' | |
| 1311 cl._socket_op = lambda *x : True | |
| 1312 cl._error_message = [] | |
| 1313 cl.request = MockNull() | |
| 1314 h = { 'content-type': 'application/json', | |
| 1315 'accept': 'application/json' } | |
| 1316 cl.request.headers = MockNull(**h) | |
| 1317 | |
| 1318 cl.write = wh # capture output | |
| 1319 | |
| 1320 # Should return explanation because content type is text/plain | |
| 1321 # and not text/xml | |
| 1322 cl.handle_rest() | |
| 1323 self.assertEqual(b2s(out[0]), "<class 'roundup.exceptions.Unauthorised'>: Invalid Origin httxs://bar.edu\n") | |
| 1324 del(out[0]) | |
| 1325 | |
| 1326 | |
| 1327 cl.db.config.WEB_ALLOWED_API_ORIGINS = " * " | |
| 1328 cl = client.Client(self.instance, None, | |
| 1329 {'REQUEST_METHOD':'POST', | |
| 1330 'PATH_INFO':'rest/data/issue', | |
| 1331 'CONTENT_TYPE': 'application/x-www-form-urlencoded', | |
| 1332 'HTTP_ORIGIN': 'httxs://bar.edu', | |
| 1333 'HTTP_X_REQUESTED_WITH': 'rest', | |
| 1334 'HTTP_AUTHORIZATION': 'Basic YWRtaW46YWRtaW4=', | |
| 1335 'HTTP_REFERER': 'http://whoami.com/path/', | |
| 1336 'HTTP_ACCEPT': "application/json;version=1" | |
| 1337 }, form) | |
| 1338 cl.db = self.db | |
| 1339 cl.base = 'http://whoami.com/path/' | |
| 1340 cl._socket_op = lambda *x : True | |
| 1341 cl._error_message = [] | |
| 1342 cl.request = MockNull() | |
| 1343 h = { 'content-type': 'application/json', | |
| 1344 'accept': 'application/json' } | |
| 1345 cl.request.headers = MockNull(**h) | |
| 1346 | |
| 1347 cl.write = wh # capture output | |
| 1348 | |
| 1349 # create third issue | |
| 1350 cl.handle_rest() | |
| 1351 self.assertIn('"id": "3"', b2s(out[0])) | |
| 1240 del(out[0]) | 1352 del(out[0]) |
| 1241 | 1353 |
| 1242 def testXmlrpcCsrfProtection(self): | 1354 def testXmlrpcCsrfProtection(self): |
| 1243 # set the password for admin so we can log in. | 1355 # set the password for admin so we can log in. |
| 1244 passwd=password.Password('admin') | 1356 passwd=password.Password('admin') |
| 1661 from roundup.cgi.timestamp import pack_timestamp | 1773 from roundup.cgi.timestamp import pack_timestamp |
| 1662 | 1774 |
| 1663 # need to set SENDMAILDEBUG to prevent | 1775 # need to set SENDMAILDEBUG to prevent |
| 1664 # downstream issue when email is sent on successful | 1776 # downstream issue when email is sent on successful |
| 1665 # issue creation. Also delete the file afterwards | 1777 # issue creation. Also delete the file afterwards |
| 1666 # just tomake sure that someother test looking for | 1778 # just to make sure that some other test looking for |
| 1667 # SENDMAILDEBUG won't trip over ours. | 1779 # SENDMAILDEBUG won't trip over ours. |
| 1668 if 'SENDMAILDEBUG' not in os.environ: | 1780 if 'SENDMAILDEBUG' not in os.environ: |
| 1669 os.environ['SENDMAILDEBUG'] = 'mail-test1.log' | 1781 os.environ['SENDMAILDEBUG'] = 'mail-test1.log' |
| 1670 SENDMAILDEBUG = os.environ['SENDMAILDEBUG'] | 1782 SENDMAILDEBUG = os.environ['SENDMAILDEBUG'] |
| 1671 | |
| 1672 | 1783 |
| 1673 # missing opaqueregister | 1784 # missing opaqueregister |
| 1674 cl = self._make_client({'username':'new_user1', 'password':'secret', | 1785 cl = self._make_client({'username':'new_user1', 'password':'secret', |
| 1675 '@confirm@password':'secret', 'address':'new_user@bork.bork'}, | 1786 '@confirm@password':'secret', 'address':'new_user@bork.bork'}, |
| 1676 nodeid=None, userid='2') | 1787 nodeid=None, userid='2') |
| 1725 | 1836 |
| 1726 def testRegisterActionUnusedUserCheck(self): | 1837 def testRegisterActionUnusedUserCheck(self): |
| 1727 # need to set SENDMAILDEBUG to prevent | 1838 # need to set SENDMAILDEBUG to prevent |
| 1728 # downstream issue when email is sent on successful | 1839 # downstream issue when email is sent on successful |
| 1729 # issue creation. Also delete the file afterwards | 1840 # issue creation. Also delete the file afterwards |
| 1730 # just tomake sure that someother test looking for | 1841 # just to make sure that some other test looking for |
| 1731 # SENDMAILDEBUG won't trip over ours. | 1842 # SENDMAILDEBUG won't trip over ours. |
| 1732 if 'SENDMAILDEBUG' not in os.environ: | 1843 if 'SENDMAILDEBUG' not in os.environ: |
| 1733 os.environ['SENDMAILDEBUG'] = 'mail-test1.log' | 1844 os.environ['SENDMAILDEBUG'] = 'mail-test1.log' |
| 1734 SENDMAILDEBUG = os.environ['SENDMAILDEBUG'] | 1845 SENDMAILDEBUG = os.environ['SENDMAILDEBUG'] |
| 1735 | 1846 |
