From 5895439eaf55f0e9a3aef3da6bce6b68ba41d279 Mon Sep 17 00:00:00 2001 From: Alessandro Pisa Date: Thu, 22 Feb 2024 17:28:31 +0100 Subject: [PATCH] Fix WebDAV traversal Fix a traversal error that happens when traversing a WebDAV resource and the virtual host monster is used. Fixes #195 --- news/195.bugfix | 2 + plone/dexterity/browser/traversal.py | 9 ++++ plone/dexterity/tests/test_webdav.py | 73 ++++++++++++++++++++++++++++ 3 files changed, 84 insertions(+) create mode 100644 news/195.bugfix diff --git a/news/195.bugfix b/news/195.bugfix new file mode 100644 index 00000000..4be48e5c --- /dev/null +++ b/news/195.bugfix @@ -0,0 +1,2 @@ +Fix a traversal error that happens when traversing a WebDAV resource and the virtual host monster is used. +[ale-rt] diff --git a/plone/dexterity/browser/traversal.py b/plone/dexterity/browser/traversal.py index 55e6b9e7..b7dd6976 100644 --- a/plone/dexterity/browser/traversal.py +++ b/plone/dexterity/browser/traversal.py @@ -4,6 +4,7 @@ from plone.dexterity.filerepresentation import FolderDataResource from plone.dexterity.interfaces import DAV_FOLDER_DATA_ID from plone.dexterity.interfaces import IDexterityContent +from Products.SiteAccess.VirtualHostMonster import VirtualHostMonster from webdav.NullResource import NullResource from zope.component import adapter from zope.publisher.interfaces.browser import IBrowserRequest @@ -38,6 +39,14 @@ def publishTraverse(self, request, name): defaultTraversal = super().publishTraverse(request, name) + if isinstance(defaultTraversal, VirtualHostMonster): + # If we are traversing to a VHM, we want to just return it immediately. + # For WebDAV requests, the check that controls if the parent + # of the traversed object is the same as the context + # will most probably fail because VHM parent will usually be + # the Zope App object. + return defaultTraversal + # If this is a WebDAV PUT/PROPFIND/PROPPATCH request, don't acquire # things. If we did, we couldn't create a new object with PUT, for # example, because the acquired object would shadow the NullResource diff --git a/plone/dexterity/tests/test_webdav.py b/plone/dexterity/tests/test_webdav.py index 04828a4e..8d17cfbe 100644 --- a/plone/dexterity/tests/test_webdav.py +++ b/plone/dexterity/tests/test_webdav.py @@ -41,6 +41,7 @@ from ZPublisher.Iterators import IStreamIterator import re +import unittest XML_PROLOG = b'' @@ -1261,3 +1262,75 @@ def __browser_default__(self, request): ), traversal.browserDefault(request), ) + + +class TestDexterityPublishTraverse(unittest.TestCase): + + def setUp(self): + """Inspired by webdav.tests.testPUT_factory.TestPUTFactory""" + from Testing.makerequest import makerequest + + import Zope2 + + # Create a basic data structure + self.app = makerequest(Zope2.app()) + + self.app.manage_addFolder("folder", "") + self.folder = self.app.folder + + self.folder.manage_addFolder("subfolder", "") + self.subfolder = self.folder.subfolder + + @property + def get_request(self): + request = self.app.REQUEST + request["PARENTS"] = [self.app] + return request + + @property + def lock_request(self): + lock_request = self.get_request.clone() + lock_request["REQUEST_METHOD"] = "LOCK" + lock_request.maybe_webdav_client = True + return lock_request + + def test_get_subfolder(self): + traversal = DexterityPublishTraverse(self.folder, None) + traversed = traversal.publishTraverse(self.get_request, "subfolder") + self.assertEqual(traversed, self.subfolder) + + def test_lock_subfolder(self): + traversal = DexterityPublishTraverse(self.folder, None) + traversed = traversal.publishTraverse(self.lock_request, "subfolder") + self.assertEqual(traversed, self.subfolder) + + def test_get_acquired(self): + traversal = DexterityPublishTraverse(self.subfolder, None) + traversed = traversal.publishTraverse(self.get_request, "folder") + self.assertEqual(traversed, self.folder) + + def test_lock_acquired(self): + """Ensure we are protected against acquisition: + traversing to an acquired object should return a NullResource + """ + from webdav.NullResource import NullResource + + traversal = DexterityPublishTraverse(self.subfolder, None) + traversed = traversal.publishTraverse(self.lock_request, "folder") + self.assertIsInstance(traversed, NullResource) + + def test_get_vhm(self): + """Ensure we can handle virtual hosting with regular requests""" + from Products.SiteAccess.VirtualHostMonster import VirtualHostMonster + + traversal = DexterityPublishTraverse(self.folder, None) + traversed = traversal.publishTraverse(self.get_request, "virtual_hosting") + self.assertIsInstance(traversed, VirtualHostMonster) + + def test_lock_vhm(self): + """Ensure we can handle virtual hosting with dav requests""" + from Products.SiteAccess.VirtualHostMonster import VirtualHostMonster + + traversal = DexterityPublishTraverse(self.folder, None) + traversed = traversal.publishTraverse(self.lock_request, "virtual_hosting") + self.assertIsInstance(traversed, VirtualHostMonster)