Commit c233041e 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 558b5452
......@@ -8,6 +8,9 @@ http://docs.zope.org/zope2/
2.13.23 (unreleased)
--------------------
- LP #789863: Ensure that Request objects cannot be published / traversed
directly via a URL.
- Issue #27: Fix publishing of ``ZPublisher.Iterators.IStreamIterator`` under
WSGI. This interface does not have ``seek`` or ``tell``. Introduce
``ZPublisher.Iterators.IUnboundStreamIterator`` to support publishing
......
......@@ -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
......@@ -276,6 +277,9 @@ class BaseRequest:
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:
......
......@@ -315,6 +315,7 @@ class HTTPRequest(BaseRequest):
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
......
......@@ -189,6 +189,19 @@ class TestBaseRequest(unittest.TestCase, BaseRequest_factory):
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())
......@@ -468,7 +481,7 @@ class TestBaseRequest(unittest.TestCase, BaseRequest_factory):
self.assertRaises(NotFound, r.traverse, 'not_found')
class TestBaseRequestZope3Views(unittest.TestCase, BaseRequest_factory):
class TestRequestZope3ViewsBase(unittest.TestCase, BaseRequest_factory):
_dummy_interface = None
......@@ -479,13 +492,27 @@ class TestBaseRequestZope3Views(unittest.TestCase, BaseRequest_factory):
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
......@@ -613,6 +640,9 @@ class TestBaseRequestZope3Views(unittest.TestCase, BaseRequest_factory):
gsm.registerAdapter(name, (self._dummyInterface(), IBrowserRequest),
IDefaultViewName, '')
class TestBaseRequestZope3Views(TestRequestZope3ViewsBase):
def test_traverse_view(self):
#simple view
root, folder = self._makeRootAndFolder()
......
import unittest
from ZPublisher.tests.testBaseRequest import TestRequestZope3ViewsBase
class RecordTests(unittest.TestCase):
......@@ -11,7 +13,8 @@ class RecordTests(unittest.TestCase):
d = eval(r)
self.assertEqual(d, rec.__dict__)
class HTTPRequestTests(unittest.TestCase):
class HTTPRequestFactoryMixin(object):
def _getTargetClass(self):
from ZPublisher.HTTPRequest import HTTPRequest
......@@ -19,7 +22,7 @@ class HTTPRequestTests(unittest.TestCase):
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()
......@@ -36,20 +39,12 @@ class HTTPRequestTests(unittest.TestCase):
environ['SERVER_PORT'] = '8080'
if response is None:
class _FauxResponse(object):
_auth = None
debug_mode = False
errmsg = 'OK'
response = HTTPResponse(stdout=StringIO())
def notFoundError(self, message):
raise NotFound, message
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
......@@ -137,6 +132,19 @@ class HTTPRequestTests(unittest.TestCase):
"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)
......@@ -1046,6 +1054,34 @@ class HTTPRequestTests(unittest.TestCase):
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',
......@@ -1077,4 +1113,5 @@ def test_suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(RecordTests))
suite.addTest(unittest.makeSuite(HTTPRequestTests))
suite.addTest(unittest.makeSuite(TestHTTPRequestZope3Views))
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