Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix WebDAV traversal #196

Merged
merged 1 commit into from
Feb 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions news/195.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix a traversal error that happens when traversing a WebDAV resource and the virtual host monster is used.
[ale-rt]
9 changes: 9 additions & 0 deletions plone/dexterity/browser/traversal.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
73 changes: 73 additions & 0 deletions plone/dexterity/tests/test_webdav.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
from ZPublisher.Iterators import IStreamIterator

import re
import unittest


XML_PROLOG = b'<?xml version="1.0" encoding="utf-8" ?>'
Expand Down Expand Up @@ -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)