Commit ce2086b5 authored by Tres Seaver's avatar Tres Seaver

Ensure that request objects cannot be published directly via a URL.

See:  https://bugs.launchpad.net/zope2/+bug/789863

This change applies Leo Rochael's patch from:
https://bugs.launchpad.net/zope2/+bug/789863/+attachment/3624368/+files/0001-Make-the-request-object-unpublishable.patch

And the ideas from Richard Mitchell's patch at:
https://bugs.launchpad.net/zope2/+bug/789863/+attachment/3625765/+files/request_object.patch
parent 9fcdc00f
...@@ -11,6 +11,9 @@ http://docs.zope.org/zope2/ ...@@ -11,6 +11,9 @@ http://docs.zope.org/zope2/
Bugs Fixed Bugs Fixed
++++++++++ ++++++++++
- LP #789863: Ensure that Request objects cannot be published / traversed
directly via a URL.
- Document running Zope as a WSGI application. - Document running Zope as a WSGI application.
- Queue additional warning filters at the beginning of the queue in order to - Queue additional warning filters at the beginning of the queue in order to
......
...@@ -206,6 +206,7 @@ class BaseRequest: ...@@ -206,6 +206,7 @@ class BaseRequest:
def __init__(self, other=None, **kw): def __init__(self, other=None, **kw):
"""The constructor is not allowed to raise errors """The constructor is not allowed to raise errors
""" """
self.__doc__ = None # Make BaseRequest objects unpublishable
if other is None: other=kw if other is None: other=kw
else: other.update(kw) else: other.update(kw)
self.other=other self.other=other
...@@ -276,6 +277,9 @@ class BaseRequest: ...@@ -276,6 +277,9 @@ class BaseRequest:
raise KeyError, key raise KeyError, key
return v return v
def __bobo_traverse__(self, name):
raise KeyError(name)
def __getattr__(self, key, default=_marker): def __getattr__(self, key, default=_marker):
v = self.get(key, default) v = self.get(key, default)
if v is _marker: if v is _marker:
......
...@@ -313,6 +313,7 @@ class HTTPRequest(BaseRequest): ...@@ -313,6 +313,7 @@ class HTTPRequest(BaseRequest):
self._locale = locales.getLocale(None, None, None) self._locale = locales.getLocale(None, None, None)
def __init__(self, stdin, environ, response, clean=0): def __init__(self, stdin, environ, response, clean=0):
self.__doc__ = None # Make HTTPRequest objects unpublishable
self._orig_env = environ self._orig_env = environ
# Avoid the overhead of scrubbing the environment in the # Avoid the overhead of scrubbing the environment in the
# case of request cloning for traversal purposes. If the # case of request cloning for traversal purposes. If the
......
...@@ -189,6 +189,19 @@ class TestBaseRequest(unittest.TestCase, BaseRequest_factory): ...@@ -189,6 +189,19 @@ class TestBaseRequest(unittest.TestCase, BaseRequest_factory):
folder = root._setObject('folder', self._makeBasicObject()) folder = root._setObject('folder', self._makeBasicObject())
return root, folder 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): def test_traverse_basic(self):
root, folder = self._makeRootAndFolder() root, folder = self._makeRootAndFolder()
folder._setObject('objBasic', self._makeBasicObject()) folder._setObject('objBasic', self._makeBasicObject())
...@@ -468,7 +481,7 @@ class TestBaseRequest(unittest.TestCase, BaseRequest_factory): ...@@ -468,7 +481,7 @@ class TestBaseRequest(unittest.TestCase, BaseRequest_factory):
self.assertRaises(NotFound, r.traverse, 'not_found') self.assertRaises(NotFound, r.traverse, 'not_found')
class TestBaseRequestZope3Views(unittest.TestCase, BaseRequest_factory): class TestRequestZope3ViewsBase(unittest.TestCase, BaseRequest_factory):
_dummy_interface = None _dummy_interface = None
...@@ -479,13 +492,27 @@ class TestBaseRequestZope3Views(unittest.TestCase, BaseRequest_factory): ...@@ -479,13 +492,27 @@ class TestBaseRequestZope3Views(unittest.TestCase, BaseRequest_factory):
def _makeOne(self, root): def _makeOne(self, root):
from zope.interface import directlyProvides from zope.interface import directlyProvides
from zope.publisher.browser import IDefaultBrowserLayer 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 # The request needs to implement the proper interface
directlyProvides(request, IDefaultBrowserLayer) directlyProvides(request, IDefaultBrowserLayer)
return request 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): def _makeRootAndFolder(self):
root = self._makeBasicObject() root = self._makeBasicObject()
root.__allow_groups__ = self._makeDummyAclUsers()
folder = root._setObject('folder', self._makeDummyObject('folder')) folder = root._setObject('folder', self._makeDummyObject('folder'))
return root, folder return root, folder
...@@ -613,6 +640,9 @@ class TestBaseRequestZope3Views(unittest.TestCase, BaseRequest_factory): ...@@ -613,6 +640,9 @@ class TestBaseRequestZope3Views(unittest.TestCase, BaseRequest_factory):
gsm.registerAdapter(name, (self._dummyInterface(), IBrowserRequest), gsm.registerAdapter(name, (self._dummyInterface(), IBrowserRequest),
IDefaultViewName, '') IDefaultViewName, '')
class TestBaseRequestZope3Views(TestRequestZope3ViewsBase):
def test_traverse_view(self): def test_traverse_view(self):
#simple view #simple view
root, folder = self._makeRootAndFolder() root, folder = self._makeRootAndFolder()
......
import unittest import unittest
from ZPublisher.tests.testBaseRequest import TestRequestZope3ViewsBase
from zope.testing.cleanup import cleanUp from zope.testing.cleanup import cleanUp
...@@ -15,7 +17,7 @@ class RecordTests(unittest.TestCase): ...@@ -15,7 +17,7 @@ class RecordTests(unittest.TestCase):
self.assertEqual(d, rec.__dict__) self.assertEqual(d, rec.__dict__)
class HTTPRequestTests(unittest.TestCase): class HTTPRequestFactoryMixin(object):
def tearDown(self): def tearDown(self):
cleanUp() cleanUp()
...@@ -26,7 +28,7 @@ class HTTPRequestTests(unittest.TestCase): ...@@ -26,7 +28,7 @@ class HTTPRequestTests(unittest.TestCase):
def _makeOne(self, stdin=None, environ=None, response=None, clean=1): def _makeOne(self, stdin=None, environ=None, response=None, clean=1):
from StringIO import StringIO from StringIO import StringIO
from ZPublisher import NotFound from ZPublisher.HTTPResponse import HTTPResponse
if stdin is None: if stdin is None:
stdin = StringIO() stdin = StringIO()
...@@ -43,20 +45,12 @@ class HTTPRequestTests(unittest.TestCase): ...@@ -43,20 +45,12 @@ class HTTPRequestTests(unittest.TestCase):
environ['SERVER_PORT'] = '8080' environ['SERVER_PORT'] = '8080'
if response is None: if response is None:
class _FauxResponse(object): response = HTTPResponse(stdout=StringIO())
_auth = None
debug_mode = False
errmsg = 'OK'
def notFoundError(self, message):
raise NotFound, message
def exception(self, *args, **kw): return self._getTargetClass()(stdin, environ, response, clean)
pass
response = _FauxResponse()
return self._getTargetClass()(stdin, environ, response, clean) class HTTPRequestTests(unittest.TestCase, HTTPRequestFactoryMixin):
def _processInputs(self, inputs): def _processInputs(self, inputs):
from urllib import quote_plus from urllib import quote_plus
...@@ -144,6 +138,19 @@ class HTTPRequestTests(unittest.TestCase): ...@@ -144,6 +138,19 @@ class HTTPRequestTests(unittest.TestCase):
"Key %s not correctly reproduced in tainted; expected %r, " "Key %s not correctly reproduced in tainted; expected %r, "
"got %r" % (key, req.form[key], req.taintedform[key])) "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): def test_processInputs_wo_query_string(self):
env = {'SERVER_NAME': 'testingharnas', 'SERVER_PORT': '80'} env = {'SERVER_NAME': 'testingharnas', 'SERVER_PORT': '80'}
req = self._makeOne(environ=env) req = self._makeOne(environ=env)
...@@ -1078,6 +1085,34 @@ class HTTPRequestTests(unittest.TestCase): ...@@ -1078,6 +1085,34 @@ class HTTPRequestTests(unittest.TestCase):
req._script = ['foo', 'bar'] req._script = ['foo', 'bar']
self.assertEquals(req.getVirtualRoot(), '/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 = { TEST_ENVIRON = {
'CONTENT_TYPE': 'multipart/form-data; boundary=12345', 'CONTENT_TYPE': 'multipart/form-data; boundary=12345',
'REQUEST_METHOD': 'POST', 'REQUEST_METHOD': 'POST',
...@@ -1109,4 +1144,5 @@ def test_suite(): ...@@ -1109,4 +1144,5 @@ def test_suite():
suite = unittest.TestSuite() suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(RecordTests)) suite.addTest(unittest.makeSuite(RecordTests))
suite.addTest(unittest.makeSuite(HTTPRequestTests)) suite.addTest(unittest.makeSuite(HTTPRequestTests))
suite.addTest(unittest.makeSuite(TestHTTPRequestZope3Views))
return suite return suite
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment