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

Roundup Issue Tracker: http://roundup-tracker.org/