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):

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