Mercurial > p > roundup > code
comparison test/test_cgi.py @ 5218:44f7e6b958fe
Added tests for csrf with xmlrpc.
Fixed the code for xmlrpc csrf defense:
raise UsageError if X-REQUESTED-WITH header is required and missing.
if HTTP_AUTHORIZATION is used, properly seed the random number
generator using the password.
| author | John Rouillard <rouilj@ieee.org> |
|---|---|
| date | Mon, 27 Mar 2017 22:37:30 -0400 |
| parents | 7da56980754d |
| children | 14d8f61e6ef2 |
comparison
equal
deleted
inserted
replaced
| 5217:17b213eab274 | 5218:44f7e6b958fe |
|---|---|
| 10 | 10 |
| 11 import unittest, os, shutil, errno, sys, difflib, cgi, re, StringIO | 11 import unittest, os, shutil, errno, sys, difflib, cgi, re, StringIO |
| 12 | 12 |
| 13 from roundup.cgi import client, actions, exceptions | 13 from roundup.cgi import client, actions, exceptions |
| 14 from roundup.cgi.exceptions import FormError | 14 from roundup.cgi.exceptions import FormError |
| 15 from roundup.exceptions import UsageError | |
| 15 from roundup.cgi.templating import HTMLItem, HTMLRequest, NoTemplate, anti_csrf_nonce | 16 from roundup.cgi.templating import HTMLItem, HTMLRequest, NoTemplate, anti_csrf_nonce |
| 16 from roundup.cgi.templating import HTMLProperty, _HTMLItem | 17 from roundup.cgi.templating import HTMLProperty, _HTMLItem |
| 17 from roundup.cgi.form_parser import FormParser | 18 from roundup.cgi.form_parser import FormParser |
| 18 from roundup import init, instance, password, hyperdb, date | 19 from roundup import init, instance, password, hyperdb, date |
| 19 | 20 |
| 1044 # clean up from email log | 1045 # clean up from email log |
| 1045 if os.path.exists(SENDMAILDEBUG): | 1046 if os.path.exists(SENDMAILDEBUG): |
| 1046 os.remove(SENDMAILDEBUG) | 1047 os.remove(SENDMAILDEBUG) |
| 1047 #raise ValueError | 1048 #raise ValueError |
| 1048 | 1049 |
| 1050 def testXmlrpcCsrfProtection(self): | |
| 1051 # set the password for admin so we can log in. | |
| 1052 passwd=password.Password('admin') | |
| 1053 self.db.user.set('1', password=passwd) | |
| 1054 | |
| 1055 out = [] | |
| 1056 def wh(s): | |
| 1057 out.append(s) | |
| 1058 | |
| 1059 # xmlrpc has no form content | |
| 1060 form = {} | |
| 1061 cl = client.Client(self.instance, None, | |
| 1062 {'REQUEST_METHOD':'POST', | |
| 1063 'PATH_INFO':'xmlrpc', | |
| 1064 'CONTENT_TYPE': 'text/plain', | |
| 1065 'HTTP_AUTHORIZATION': 'Basic YWRtaW46YWRtaW4=', | |
| 1066 'HTTP_REFERER': 'http://whoami.com/path/', | |
| 1067 'HTTP_X-REQUESTED-WITH': "XMLHttpRequest" | |
| 1068 }, form) | |
| 1069 cl.db = self.db | |
| 1070 cl.base = 'http://whoami.com/path/' | |
| 1071 cl._socket_op = lambda *x : True | |
| 1072 cl._error_message = [] | |
| 1073 cl.request = MockNull() | |
| 1074 cl.write = wh # capture output | |
| 1075 | |
| 1076 # Should return explanation because content type is text/plain | |
| 1077 # and not text/xml | |
| 1078 cl.handle_xmlrpc() | |
| 1080 del(out[0]) | |
| 1081 | |
| 1082 # Should return admin user indicating auth works and | |
| 1083 # header checks succeed (REFERER and X-REQUESTED-WITH) | |
| 1084 cl.env['CONTENT_TYPE'] = "text/xml" | |
| 1085 # ship the form with the value holding the xml value. | |
| 1086 # I have no clue why this works but .... | |
| 1087 cl.form = MockNull(file = True, value = "<?xml version='1.0'?>\n<methodCall>\n<methodName>display</methodName>\n<params>\n<param>\n<value><string>user1</string></value>\n</param>\n<param>\n<value><string>username</string></value>\n</param>\n</params>\n</methodCall>\n" ) | |
| 1088 answer ="<?xml version='1.0'?>\n<methodResponse>\n<params>\n<param>\n<value><struct>\n<member>\n<name>username</name>\n<value><string>admin</string></value>\n</member>\n</struct></value>\n</param>\n</params>\n</methodResponse>\n" | |
| 1089 cl.handle_xmlrpc() | |
| 1090 print out | |
| 1091 self.assertEqual(out[0], answer) | |
| 1092 del(out[0]) | |
| 1093 | |
| 1094 # remove the X-REQUESTED-WITH header and get a failure. | |
| 1095 del(cl.env['HTTP_X-REQUESTED-WITH']) | |
| 1096 self.assertRaises(UsageError,cl.handle_xmlrpc) | |
| 1097 | |
| 1098 # change config to not require X-REQUESTED-WITH header | |
| 1099 cl.db.config['WEB_CSRF_ENFORCE_HEADER_X-REQUESTED-WITH'] = 'logfailure' | |
| 1100 cl.handle_xmlrpc() | |
| 1101 print out | |
| 1102 self.assertEqual(out[0], answer) | |
| 1103 del(out[0]) | |
| 1104 | |
| 1049 # | 1105 # |
| 1050 # SECURITY | 1106 # SECURITY |
| 1051 # | 1107 # |
| 1052 # XXX test all default permissions | 1108 # XXX test all default permissions |
| 1053 def _make_client(self, form, classname='user', nodeid='1', | 1109 def _make_client(self, form, classname='user', nodeid='1', |
| 1479 self.assertEqual((self.client.classname, self.client.template, self.client.nodeid), ('user', 'forgotten|item', None)) | 1535 self.assertEqual((self.client.classname, self.client.template, self.client.nodeid), ('user', 'forgotten|item', None)) |
| 1480 self.assertEqual(self.client._ok_message, []) | 1536 self.assertEqual(self.client._ok_message, []) |
| 1481 | 1537 |
| 1482 result = self.client.renderContext() | 1538 result = self.client.renderContext() |
| 1483 print result | 1539 print result |
| 1540 # sha1sum of classic tracker user.forgotten.template must be found | |
| 1484 self.assertNotEqual(-1, | 1541 self.assertNotEqual(-1, |
| 1485 result.index('<!-- SHA: eb5dd0bec7a57d58cb7edbeb939fb0390ed1bf74 -->')) | 1542 result.index('<!-- SHA: eb5dd0bec7a57d58cb7edbeb939fb0390ed1bf74 -->')) |
| 1486 | 1543 |
| 1487 # now set an error in the form to get error template user.item.html | 1544 # now set an error in the form to get error template user.item.html |
| 1488 self.client.form=makeForm({"@template": "forgotten|item", | 1545 self.client.form=makeForm({"@template": "forgotten|item", |
| 1493 self.assertEqual(self.client._ok_message, []) | 1550 self.assertEqual(self.client._ok_message, []) |
| 1494 self.assertEqual(self.client._error_message, ["this is an error"]) | 1551 self.assertEqual(self.client._error_message, ["this is an error"]) |
| 1495 | 1552 |
| 1496 result = self.client.renderContext() | 1553 result = self.client.renderContext() |
| 1497 print result | 1554 print result |
| 1555 # sha1sum of classic tracker user.item.template must be found | |
| 1498 self.assertNotEqual(-1, | 1556 self.assertNotEqual(-1, |
| 1499 result.index('<!-- SHA: 3b7ce7cbf24f77733c9b9f64a569d6429390cc3f -->')) | 1557 result.index('<!-- SHA: 3b7ce7cbf24f77733c9b9f64a569d6429390cc3f -->')) |
| 1500 | 1558 |
| 1501 | 1559 |
| 1502 def testexamine_url(self): | 1560 def testexamine_url(self): |
