Commit 0e32c197 authored by Michael Howitz's avatar Michael Howitz Committed by GitHub

Add ability to paste objects without limits. [2.13] (#270)

* Add tests to check the DoS prevention code.

* Add `._pasteObjects()` to be able to paste objects without limits.

Fixes #217.
parent a819388c
[buildout]
extensions = mr.developer
index = https://pypi.python.org/simple/
allow-hosts =
*.python.org
*.zope.org
argparse.googlecode.com
index = https://pypi.org/simple/
show-picked-versions = true
always-accept-server-certificate = true
develop = .
......
......@@ -8,7 +8,9 @@ http://docs.zope.org/zope2/
2.13.28 (unreleased)
--------------------
- ...
- Add ``OFS.CopySupport.CopyContainer._pasteObjects()`` to be able to paste
objects no matter how many objects where cut or copied.
(`#217 <https://github.com/zopefoundation/Zope/issues/217>`_)
2.13.27 (2018-01-27)
......
......@@ -169,28 +169,29 @@ class CopyContainer(Base):
id='copy%s_of_%s' % (n and n+1 or '', orig_id)
n=n+1
security.declareProtected(view_management_screens, 'manage_pasteObjects')
def manage_pasteObjects(self, cb_copy_data=None, REQUEST=None):
def _pasteObjects(self, cp, cb_maxsize=0):
"""Paste previously copied objects into the current object.
If calling manage_pasteObjects from python code, pass the result of a
``cp`` is the list of objects for paste as encoded by ``_cb_encode``.
If calling _pasteObjects from python code, pass the result of a
previous call to manage_cutObjects or manage_copyObjects as the first
argument.
Also sends IObjectCopiedEvent and IObjectClonedEvent
``cb_maxsize`` is the maximum size of the JSON representation of the
object list. Set it to a non-zero value to prevent DoS attacks with
huge object lists or zlib bombs.
This method sends IObjectCopiedEvent and IObjectClonedEvent
or IObjectWillBeMovedEvent and IObjectMovedEvent.
Returns tuple of (operator, list of {'id': orig_id, 'new_id': new_id}).
Where `operator` is 0 for a copy operation and 1 for a move operation.
"""
if cb_copy_data is not None:
cp = cb_copy_data
elif REQUEST is not None and REQUEST.has_key('__cp'):
cp = REQUEST['__cp']
else:
cp = None
if cp is None:
raise CopyError(eNoData)
try:
op, mdatas = _cb_decode(cp)
op, mdatas = _cb_decode(cp, cb_maxsize)
except:
raise CopyError(eInvalid)
......@@ -243,10 +244,6 @@ class CopyContainer(Base):
notify(ObjectClonedEvent(ob))
if REQUEST is not None:
return self.manage_main(self, REQUEST, update_menu=1,
cb_dataValid=1)
elif op == 1:
# Move operation
for ob in oblist:
......@@ -311,13 +308,40 @@ class CopyContainer(Base):
# try to make ownership implicit if possible
ob.manage_changeOwnershipType(explicit=0)
if REQUEST is not None:
return op, result
security.declareProtected(view_management_screens, 'manage_pasteObjects')
def manage_pasteObjects(self, cb_copy_data=None, REQUEST=None):
"""Paste previously copied objects into the current object.
If calling manage_pasteObjects from python code, pass the result of a
previous call to manage_cutObjects or manage_copyObjects as the first
argument.
Also sends IObjectCopiedEvent and IObjectClonedEvent
or IObjectWillBeMovedEvent and IObjectMovedEvent.
If `REQUEST` is None it returns a
list of dicts {'id': orig_id, 'new_id': new_id} otherwise it renders
a HTML page.
"""
if (cb_copy_data is None and
REQUEST is not None and
REQUEST.has_key('__cp')):
cb_copy_data = REQUEST['__cp']
op, result = self._pasteObjects(cb_copy_data, cb_maxsize=8192)
if REQUEST is not None:
if op == 0:
cb_valid = 1
elif op == 1:
REQUEST['RESPONSE'].setCookie('__cp', 'deleted',
path='%s' % cookie_path(REQUEST),
expires='Wed, 31-Dec-97 23:59:59 GMT')
REQUEST['__cp'] = None
return self.manage_main(self, REQUEST, update_menu=1,
cb_dataValid=0)
cb_valid = 0
return self.manage_main(self, REQUEST, update_menu=1,
cb_dataValid=cb_valid)
return result
......@@ -684,6 +708,13 @@ def _cb_encode(d):
return quote(compress(dumps(d), 9))
def _cb_decode(s, maxsize=8192):
"""Decode a list of IDs from storage in a cookie.
``s`` is text as encoded by ``_cb_encode``.
``maxsize`` is the maximum size of uncompressed data. ``0`` means no limit.
Return a list of text IDs.
"""
dec = decompressobj()
data = dec.decompress(unquote(s), maxsize)
if dec.unconsumed_tail:
......
import random
import string
import unittest
import cStringIO
......@@ -283,6 +285,40 @@ class TestCopySupport( CopySupportTestBase ):
{'id':'file1', 'new_id':'copy_of_file1'},
{'id':'file2', 'new_id':'copy_of_file2'}])
def assertCopyError(self, callable, error_text, *args):
from OFS.CopySupport import CopyError
try:
callable(*args)
except CopyError as err:
if error_text:
self.assertTrue(error_text in str(err))
else:
self.fail('CopyError not raised.')
def testPasteNoData(self):
self.assertCopyError(self.folder1.manage_pasteObjects, '')
def testPasteTooBigData(self):
from OFS.CopySupport import _cb_encode
def make_data(lenght):
return _cb_encode(
(1, [''.join(random.sample(string.printable, 20))
for x in range(lenght)]))
# Protect against DoS attack with too big data:
self.assertCopyError(
self.folder1.manage_pasteObjects, 'Clipboard Error',
make_data(350))
# But not too much data is allowed:
self.assertCopyError(
self.folder1.manage_pasteObjects, 'Item Not Found',
make_data(300))
# _pasteObjects allows to paste without restriction:
self.assertCopyError(
self.folder1._pasteObjects, 'Item Not Found', make_data(3500))
class _SensitiveSecurityPolicy:
def __init__( self, validate_lambda, checkPermission_lambda ):
......
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