Skip to content

Commit

Permalink
Ensure that request objects cannot be published directly via a URL.
Browse files Browse the repository at this point in the history
  • Loading branch information
tseaver committed Jun 24, 2015
1 parent 9fcdc00 commit ce2086b
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 15 deletions.
3 changes: 3 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ http://docs.zope.org/zope2/
Bugs Fixed
++++++++++

- LP #789863: Ensure that Request objects cannot be published / traversed
directly via a URL.

- Document running Zope as a WSGI application.

- Queue additional warning filters at the beginning of the queue in order to
Expand Down
4 changes: 4 additions & 0 deletions src/ZPublisher/BaseRequest.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ class BaseRequest:
def __init__(self, other=None, **kw):
"""The constructor is not allowed to raise errors
"""
self.__doc__ = None # Make BaseRequest objects unpublishable
if other is None: other=kw
else: other.update(kw)
self.other=other
Expand Down Expand Up @@ -276,6 +277,9 @@ def __getitem__(self, key, default=_marker):
raise KeyError, key
return v

def __bobo_traverse__(self, name):
raise KeyError(name)

def __getattr__(self, key, default=_marker):
v = self.get(key, default)
if v is _marker:
Expand Down
1 change: 1 addition & 0 deletions src/ZPublisher/HTTPRequest.py
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,7 @@ def setupLocale(self):
self._locale = locales.getLocale(None, None, None)

def __init__(self, stdin, environ, response, clean=0):
self.__doc__ = None # Make HTTPRequest objects unpublishable
self._orig_env = environ
# Avoid the overhead of scrubbing the environment in the
# case of request cloning for traversal purposes. If the
Expand Down
34 changes: 32 additions & 2 deletions src/ZPublisher/tests/testBaseRequest.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,19 @@ def _makeRootAndFolder(self):
folder = root._setObject('folder', self._makeBasicObject())
return root, folder

def test_no_docstring_on_instance(self):
root, folder = self._makeRootAndFolder()
r = self._makeOne(root)
self.assertTrue(r.__doc__ is None)

def test___bobo_traverse___raises(self):
root, folder = self._makeRootAndFolder()
folder._setObject('objBasic', self._makeBasicObject())
r = self._makeOne(root)
self.assertRaises(KeyError, r.__bobo_traverse__, 'REQUEST')
self.assertRaises(KeyError, r.__bobo_traverse__, 'BODY')
self.assertRaises(KeyError, r.__bobo_traverse__, 'BODYFILE')

def test_traverse_basic(self):
root, folder = self._makeRootAndFolder()
folder._setObject('objBasic', self._makeBasicObject())
Expand Down Expand Up @@ -468,7 +481,7 @@ def test_traverse_publishTraverse_error(self):
self.assertRaises(NotFound, r.traverse, 'not_found')


class TestBaseRequestZope3Views(unittest.TestCase, BaseRequest_factory):
class TestRequestZope3ViewsBase(unittest.TestCase, BaseRequest_factory):

_dummy_interface = None

Expand All @@ -479,13 +492,27 @@ def _getTargetClass(self):
def _makeOne(self, root):
from zope.interface import directlyProvides
from zope.publisher.browser import IDefaultBrowserLayer
request = super(TestBaseRequestZope3Views, self)._makeOne(root)
request = super(TestRequestZope3ViewsBase, self)._makeOne(root)
# The request needs to implement the proper interface
directlyProvides(request, IDefaultBrowserLayer)
return request

def _makeDummyAclUsers(self):
from Acquisition import Implicit
from AccessControl.ZopeSecurityPolicy import _noroles

class AclUsers(Implicit):
def validate(self, request, auth='', roles=_noroles):
# always validate access as anonymous, good for checking
# if things are publishable regardless of authorization
from AccessControl.SpecialUsers import nobody
return nobody.__of__(self)
acl_users = AclUsers()
return acl_users

def _makeRootAndFolder(self):
root = self._makeBasicObject()
root.__allow_groups__ = self._makeDummyAclUsers()
folder = root._setObject('folder', self._makeDummyObject('folder'))
return root, folder

Expand Down Expand Up @@ -613,6 +640,9 @@ def _setDefaultViewName(self, name):
gsm.registerAdapter(name, (self._dummyInterface(), IBrowserRequest),
IDefaultViewName, '')


class TestBaseRequestZope3Views(TestRequestZope3ViewsBase):

def test_traverse_view(self):
#simple view
root, folder = self._makeRootAndFolder()
Expand Down
62 changes: 49 additions & 13 deletions src/ZPublisher/tests/testHTTPRequest.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import unittest
from ZPublisher.tests.testBaseRequest import TestRequestZope3ViewsBase


from zope.testing.cleanup import cleanUp

Expand All @@ -15,7 +17,7 @@ def test_repr(self):
self.assertEqual(d, rec.__dict__)


class HTTPRequestTests(unittest.TestCase):
class HTTPRequestFactoryMixin(object):

def tearDown(self):
cleanUp()
Expand All @@ -26,7 +28,7 @@ def _getTargetClass(self):

def _makeOne(self, stdin=None, environ=None, response=None, clean=1):
from StringIO import StringIO
from ZPublisher import NotFound
from ZPublisher.HTTPResponse import HTTPResponse
if stdin is None:
stdin = StringIO()

Expand All @@ -43,20 +45,12 @@ def _makeOne(self, stdin=None, environ=None, response=None, clean=1):
environ['SERVER_PORT'] = '8080'

if response is None:
class _FauxResponse(object):
_auth = None
debug_mode = False
errmsg = 'OK'

def notFoundError(self, message):
raise NotFound, message
response = HTTPResponse(stdout=StringIO())

def exception(self, *args, **kw):
pass
return self._getTargetClass()(stdin, environ, response, clean)

response = _FauxResponse()

return self._getTargetClass()(stdin, environ, response, clean)
class HTTPRequestTests(unittest.TestCase, HTTPRequestFactoryMixin):

def _processInputs(self, inputs):
from urllib import quote_plus
Expand Down Expand Up @@ -144,6 +138,19 @@ def _taintedKeysAlsoInForm(self, req):
"Key %s not correctly reproduced in tainted; expected %r, "
"got %r" % (key, req.form[key], req.taintedform[key]))

def test_no_docstring_on_instance(self):
env = {'SERVER_NAME': 'testingharnas', 'SERVER_PORT': '80'}
req = self._makeOne(environ=env)
self.assertTrue(req.__doc__ is None)

def test___bobo_traverse___raises(self):
env = {'SERVER_NAME': 'testingharnas', 'SERVER_PORT': '80'}
req = self._makeOne(environ=env)
self.assertRaises(KeyError, req.__bobo_traverse__, 'REQUEST')
self.assertRaises(KeyError, req.__bobo_traverse__, 'BODY')
self.assertRaises(KeyError, req.__bobo_traverse__, 'BODYFILE')
self.assertRaises(KeyError, req.__bobo_traverse__, 'RESPONSE')

def test_processInputs_wo_query_string(self):
env = {'SERVER_NAME': 'testingharnas', 'SERVER_PORT': '80'}
req = self._makeOne(environ=env)
Expand Down Expand Up @@ -1078,6 +1085,34 @@ def test_getVirtualRoot(self):
req._script = ['foo', 'bar']
self.assertEquals(req.getVirtualRoot(), '/foo/bar')


class TestHTTPRequestZope3Views(TestRequestZope3ViewsBase,):

def _makeOne(self, root):
from zope.interface import directlyProvides
from zope.publisher.browser import IDefaultBrowserLayer
request = HTTPRequestFactoryMixin()._makeOne()
request['PARENTS'] = [root]
# The request needs to implement the proper interface
directlyProvides(request, IDefaultBrowserLayer)
return request

def test_no_traversal_of_view_request_attribute(self):
# make sure views don't accidentally publish the 'request' attribute
from ZPublisher import NotFound
root, _ = self._makeRootAndFolder()

# make sure the view itself is traversable:
view = self._makeOne(root).traverse('folder/@@meth')
from ZPublisher.HTTPRequest import HTTPRequest
self.assertEqual(view.request.__class__, HTTPRequest,)

# but not the request:
self.assertRaises(
NotFound,
self._makeOne(root).traverse, 'folder/@@meth/request'
)

TEST_ENVIRON = {
'CONTENT_TYPE': 'multipart/form-data; boundary=12345',
'REQUEST_METHOD': 'POST',
Expand Down Expand Up @@ -1109,4 +1144,5 @@ def test_suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(RecordTests))
suite.addTest(unittest.makeSuite(HTTPRequestTests))
suite.addTest(unittest.makeSuite(TestHTTPRequestZope3Views))
return suite

0 comments on commit ce2086b

Please sign in to comment.