1212
1313from roundup .cgi import client , actions , exceptions
1414from roundup .cgi .exceptions import FormError
15+ from roundup .exceptions import UsageError
1516from roundup .cgi .templating import HTMLItem , HTMLRequest , NoTemplate , anti_csrf_nonce
1617from roundup .cgi .templating import HTMLProperty , _HTMLItem
1718from roundup .cgi .form_parser import FormParser
@@ -1046,6 +1047,61 @@ def hasPermission(s, p, classname=None, d=None, e=None, **kw):
10461047 os .remove (SENDMAILDEBUG )
10471048 #raise ValueError
10481049
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 ()
1079+ self .assertEqual (out [0 ], "This is the endpoint of Roundup <a href='http://www.roundup-tracker.org/docs/xmlrpc.html'>XML-RPC interface</a>." )
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+
10491105 #
10501106 # SECURITY
10511107 #
@@ -1481,6 +1537,7 @@ def testRenderAltTemplates(self):
14811537
14821538 result = self .client .renderContext ()
14831539 print result
1540+ # sha1sum of classic tracker user.forgotten.template must be found
14841541 self .assertNotEqual (- 1 ,
14851542 result .index ('<!-- SHA: eb5dd0bec7a57d58cb7edbeb939fb0390ed1bf74 -->' ))
14861543
@@ -1495,6 +1552,7 @@ def testRenderAltTemplates(self):
14951552
14961553 result = self .client .renderContext ()
14971554 print result
1555+ # sha1sum of classic tracker user.item.template must be found
14981556 self .assertNotEqual (- 1 ,
14991557 result .index ('<!-- SHA: 3b7ce7cbf24f77733c9b9f64a569d6429390cc3f -->' ))
15001558
0 commit comments