diff --git a/last_commit.txt b/last_commit.txt index 511f545196..9541b16b75 100644 --- a/last_commit.txt +++ b/last_commit.txt @@ -1,68 +1,39 @@ -Repository: plone.testing +Repository: plone.rest Branch: refs/heads/master -Date: 2023-09-20T21:24:17+02:00 +Date: 2023-09-19T12:39:57+02:00 Author: Maurits van Rees (mauritsvanrees) -Commit: https://github.com/plone/plone.testing/commit/786074e81efb0c2c2f9f489191f6de1629ff4fd4 +Commit: https://github.com/plone/plone.rest/commit/db0cb9adb7f483bffb3e1ab06d1270c7db0b04a8 -Fix tests when run with ZODB 5.8.1+. +When ++api++ is in the url multiple times, redirect to the proper url. -Test failures: -``` -Failure in test /Users/maurits/community/plone-coredev/6.0/src/plone.testing/src/plone/testing/zodb.rst -Failed doctest test for zodb.rst - File "/Users/maurits/community/plone-coredev/6.0/src/plone.testing/src/plone/testing/zodb.rst", line 0 - ----------------------------------------------------------------------- -File "/Users/maurits/community/plone-coredev/6.0/src/plone.testing/src/plone/testing/zodb.rst", line 55, in zodb.rst -Failed example: - zodb.EMPTY_ZODB['zodbConnection'] -Expected: - <Connection at ...> -Got: - <ZODB.Connection.Connection object at 0x1071e7f10> ----------------------------------------------------------------------- -File "/Users/maurits/community/plone-coredev/6.0/src/plone.testing/src/plone/testing/zodb.rst", line 138, in zodb.rst -Failed example: - POPULATED_ZODB['zodbConnection'] -Expected: - <Connection at ...> -Got: - <ZODB.Connection.Connection object at 0x107256a10> ----------------------------------------------------------------------- -File "/Users/maurits/community/plone-coredev/6.0/src/plone.testing/src/plone/testing/zodb.rst", line 233, in zodb.rst -Failed example: - EXPANDED_ZODB['zodbConnection'] -Expected: - <Connection at ...> -Got: - <ZODB.Connection.Connection object at 0x107268550> -``` +When the url is badly formed, for example `++api++/something/++api++`, give a 404 NotFound. +Fixes a denial of service. Files changed: -A news/581.bugfix -M pyproject.toml -M src/plone/testing/zodb.rst +A news/1.bugfix +M src/plone/rest/tests/test_traversal.py +M src/plone/rest/traverse.py -b'diff --git a/news/581.bugfix b/news/581.bugfix\nnew file mode 100644\nindex 0000000..69f5d06\n--- /dev/null\n+++ b/news/581.bugfix\n@@ -0,0 +1,2 @@\n+Fix tests when run with ZODB 5.8.1+.\n+[maurits]\ndiff --git a/pyproject.toml b/pyproject.toml\nindex 05b615d..12c6bc8 100644\n--- a/pyproject.toml\n+++ b/pyproject.toml\n@@ -1,6 +1,6 @@\n [tool.towncrier]\n-filename = "CHANGES.rst"\n directory = "news/"\n+filename = "CHANGES.rst"\n title_format = "{version} ({project_date})"\n underlines = ["-", ""]\n \n@@ -18,3 +18,21 @@ showcontent = true\n directory = "bugfix"\n name = "Bug fixes:"\n showcontent = true\n+\n+[[tool.towncrier.type]]\n+directory = "internal"\n+name = "Internal:"\n+showcontent = true\n+\n+[[tool.towncrier.type]]\n+directory = "documentation"\n+name = "Documentation:"\n+showcontent = true\n+\n+[[tool.towncrier.type]]\n+directory = "tests"\n+name = "Tests"\n+showcontent = true\n+\n+[tool.isort]\n+profile = "plone"\ndiff --git a/src/plone/testing/zodb.rst b/src/plone/testing/zodb.rst\nindex 6a9cf73..a18c473 100644\n--- a/src/plone/testing/zodb.rst\n+++ b/src/plone/testing/zodb.rst\n@@ -53,7 +53,7 @@ Let\'s now simulate a test.::\n The test would then execute. It may use the ZODB root.::\n \n >>> zodb.EMPTY_ZODB[\'zodbConnection\']\n- \n+ <...Connection...at ...>\n \n >>> zodb.EMPTY_ZODB[\'zodbRoot\']\n {}\n@@ -136,7 +136,7 @@ Let\'s now simulate a test.::\n The test would then execute. It may use the ZODB root.::\n \n >>> POPULATED_ZODB[\'zodbConnection\']\n- \n+ <...Connection...at ...>\n \n >>> POPULATED_ZODB[\'zodbRoot\']\n {\'someData\': \'a string\'}\n@@ -231,7 +231,7 @@ Let\'s now simulate a test.::\n The test would then execute. It may use the ZODB root.::\n \n >>> EXPANDED_ZODB[\'zodbConnection\']\n- \n+ <...Connection...at ...>\n \n >>> EXPANDED_ZODB[\'zodbRoot\'] == dict(someData=\'a string\', additionalData=\'Some new data\')\n True\n' +b'diff --git a/news/1.bugfix b/news/1.bugfix\nnew file mode 100644\nindex 0000000..019268d\n--- /dev/null\n+++ b/news/1.bugfix\n@@ -0,0 +1,5 @@\n+When ``++api++`` is in the url multiple times, redirect to the proper url.\n+When the url is badly formed, for example ``++api++/something/++api++``, give a 404 NotFound.\n+Fixes a denial of service.\n+See `security advisory `_.\n+[maurits]\ndiff --git a/src/plone/rest/tests/test_traversal.py b/src/plone/rest/tests/test_traversal.py\nindex 963d1c4..5d5389d 100644\n--- a/src/plone/rest/tests/test_traversal.py\n+++ b/src/plone/rest/tests/test_traversal.py\n@@ -10,6 +10,8 @@\n from plone.app.testing import TEST_USER_ID\n from plone.rest.service import Service\n from plone.rest.testing import PLONE_REST_INTEGRATION_TESTING\n+from zExceptions import NotFound\n+from zExceptions import Redirect\n from zope.event import notify\n from zope.interface import alsoProvides\n from zope.publisher.interfaces.browser import IBrowserView\n@@ -106,6 +108,34 @@ def test_html_request_on_existing_view_returns_view(self):\n obj = self.traverse(path="/plone/folder1/search", accept="text/html")\n self.assertFalse(isinstance(obj, Service), "Got a service")\n \n+ def test_html_request_via_api_returns_service(self):\n+ obj = self.traverse(path="/plone/++api++", accept="text/html")\n+ self.assertTrue(isinstance(obj, Service), "Not a service")\n+\n+ def test_html_request_via_double_apis_raises_redirect(self):\n+ portal_url = self.portal.absolute_url()\n+ with self.assertRaises(Redirect) as exc:\n+ self.traverse(path="/plone/++api++/++api++", accept="text/html")\n+ self.assertEqual(\n+ exc.exception.headers["Location"],\n+ f"{portal_url}/++api++",\n+ )\n+\n+ def test_html_request_via_multiple_apis_raises_redirect(self):\n+ portal_url = self.portal.absolute_url()\n+ with self.assertRaises(Redirect) as exc:\n+ self.traverse(\n+ path="/plone/++api++/++api++/++api++/search", accept="text/html"\n+ )\n+ self.assertEqual(\n+ exc.exception.headers["Location"],\n+ f"{portal_url}/++api++/search",\n+ )\n+\n+ def test_html_request_via_multiple_bad_apis_raises_not_found(self):\n+ with self.assertRaises(NotFound):\n+ self.traverse(path="/plone/++api++/search/++api++", accept="text/html")\n+\n def test_virtual_hosting(self):\n app = self.layer["app"]\n vhm = VirtualHostMonster()\ndiff --git a/src/plone/rest/traverse.py b/src/plone/rest/traverse.py\nindex f8d4a23..0a151c8 100644\n--- a/src/plone/rest/traverse.py\n+++ b/src/plone/rest/traverse.py\n@@ -5,6 +5,7 @@\n from plone.rest.interfaces import IAPIRequest\n from plone.rest.interfaces import IService\n from plone.rest.events import mark_as_api_request\n+from zExceptions import Redirect\n from zope.component import adapter\n from zope.component import queryMultiAdapter\n from zope.interface import implementer\n@@ -64,6 +65,18 @@ def __init__(self, context, request):\n self.request = request\n \n def traverse(self, name_ignored, subpath_ignored):\n+ name = "/++api++"\n+ url = self.request.ACTUAL_URL\n+ if url.count(name) > 1:\n+ # Redirect to proper url.\n+ while f"{name}{name}" in url:\n+ url = url.replace(f"{name}{name}", name)\n+ if url.count(name) > 1:\n+ # Something like: .../++api++/something/++api++\n+ # Return nothing, so a NotFound is raised.\n+ return\n+ # Raise a redirect exception to stop execution of the current request.\n+ raise Redirect(url)\n mark_as_api_request(self.request, "application/json")\n return self.context\n \n' -Repository: plone.testing +Repository: plone.rest Branch: refs/heads/master -Date: 2023-09-20T22:17:18+02:00 +Date: 2023-09-21T13:15:43+02:00 Author: Maurits van Rees (mauritsvanrees) -Commit: https://github.com/plone/plone.testing/commit/5cbdff3fa9d1a7b1102d6807db73a059203af475 +Commit: https://github.com/plone/plone.rest/commit/77846a9842889b24f35e8bedc2e9d461388d3302 -Merge pull request #84 from plone/maurits-fix-zodb-581-tests +Merge pull request from GHSA-h6rp-mprm-xgcq -Fix tests when run with ZODB 5.8.1+. +When ++api++ is in the url multiple times, redirect to the proper url. [master] Files changed: -A news/581.bugfix -M pyproject.toml -M src/plone/testing/zodb.rst +A news/1.bugfix +M src/plone/rest/tests/test_traversal.py +M src/plone/rest/traverse.py -b'diff --git a/news/581.bugfix b/news/581.bugfix\nnew file mode 100644\nindex 0000000..69f5d06\n--- /dev/null\n+++ b/news/581.bugfix\n@@ -0,0 +1,2 @@\n+Fix tests when run with ZODB 5.8.1+.\n+[maurits]\ndiff --git a/pyproject.toml b/pyproject.toml\nindex 05b615d..12c6bc8 100644\n--- a/pyproject.toml\n+++ b/pyproject.toml\n@@ -1,6 +1,6 @@\n [tool.towncrier]\n-filename = "CHANGES.rst"\n directory = "news/"\n+filename = "CHANGES.rst"\n title_format = "{version} ({project_date})"\n underlines = ["-", ""]\n \n@@ -18,3 +18,21 @@ showcontent = true\n directory = "bugfix"\n name = "Bug fixes:"\n showcontent = true\n+\n+[[tool.towncrier.type]]\n+directory = "internal"\n+name = "Internal:"\n+showcontent = true\n+\n+[[tool.towncrier.type]]\n+directory = "documentation"\n+name = "Documentation:"\n+showcontent = true\n+\n+[[tool.towncrier.type]]\n+directory = "tests"\n+name = "Tests"\n+showcontent = true\n+\n+[tool.isort]\n+profile = "plone"\ndiff --git a/src/plone/testing/zodb.rst b/src/plone/testing/zodb.rst\nindex 6a9cf73..a18c473 100644\n--- a/src/plone/testing/zodb.rst\n+++ b/src/plone/testing/zodb.rst\n@@ -53,7 +53,7 @@ Let\'s now simulate a test.::\n The test would then execute. It may use the ZODB root.::\n \n >>> zodb.EMPTY_ZODB[\'zodbConnection\']\n- \n+ <...Connection...at ...>\n \n >>> zodb.EMPTY_ZODB[\'zodbRoot\']\n {}\n@@ -136,7 +136,7 @@ Let\'s now simulate a test.::\n The test would then execute. It may use the ZODB root.::\n \n >>> POPULATED_ZODB[\'zodbConnection\']\n- \n+ <...Connection...at ...>\n \n >>> POPULATED_ZODB[\'zodbRoot\']\n {\'someData\': \'a string\'}\n@@ -231,7 +231,7 @@ Let\'s now simulate a test.::\n The test would then execute. It may use the ZODB root.::\n \n >>> EXPANDED_ZODB[\'zodbConnection\']\n- \n+ <...Connection...at ...>\n \n >>> EXPANDED_ZODB[\'zodbRoot\'] == dict(someData=\'a string\', additionalData=\'Some new data\')\n True\n' +b'diff --git a/news/1.bugfix b/news/1.bugfix\nnew file mode 100644\nindex 0000000..019268d\n--- /dev/null\n+++ b/news/1.bugfix\n@@ -0,0 +1,5 @@\n+When ``++api++`` is in the url multiple times, redirect to the proper url.\n+When the url is badly formed, for example ``++api++/something/++api++``, give a 404 NotFound.\n+Fixes a denial of service.\n+See `security advisory `_.\n+[maurits]\ndiff --git a/src/plone/rest/tests/test_traversal.py b/src/plone/rest/tests/test_traversal.py\nindex 963d1c4..5d5389d 100644\n--- a/src/plone/rest/tests/test_traversal.py\n+++ b/src/plone/rest/tests/test_traversal.py\n@@ -10,6 +10,8 @@\n from plone.app.testing import TEST_USER_ID\n from plone.rest.service import Service\n from plone.rest.testing import PLONE_REST_INTEGRATION_TESTING\n+from zExceptions import NotFound\n+from zExceptions import Redirect\n from zope.event import notify\n from zope.interface import alsoProvides\n from zope.publisher.interfaces.browser import IBrowserView\n@@ -106,6 +108,34 @@ def test_html_request_on_existing_view_returns_view(self):\n obj = self.traverse(path="/plone/folder1/search", accept="text/html")\n self.assertFalse(isinstance(obj, Service), "Got a service")\n \n+ def test_html_request_via_api_returns_service(self):\n+ obj = self.traverse(path="/plone/++api++", accept="text/html")\n+ self.assertTrue(isinstance(obj, Service), "Not a service")\n+\n+ def test_html_request_via_double_apis_raises_redirect(self):\n+ portal_url = self.portal.absolute_url()\n+ with self.assertRaises(Redirect) as exc:\n+ self.traverse(path="/plone/++api++/++api++", accept="text/html")\n+ self.assertEqual(\n+ exc.exception.headers["Location"],\n+ f"{portal_url}/++api++",\n+ )\n+\n+ def test_html_request_via_multiple_apis_raises_redirect(self):\n+ portal_url = self.portal.absolute_url()\n+ with self.assertRaises(Redirect) as exc:\n+ self.traverse(\n+ path="/plone/++api++/++api++/++api++/search", accept="text/html"\n+ )\n+ self.assertEqual(\n+ exc.exception.headers["Location"],\n+ f"{portal_url}/++api++/search",\n+ )\n+\n+ def test_html_request_via_multiple_bad_apis_raises_not_found(self):\n+ with self.assertRaises(NotFound):\n+ self.traverse(path="/plone/++api++/search/++api++", accept="text/html")\n+\n def test_virtual_hosting(self):\n app = self.layer["app"]\n vhm = VirtualHostMonster()\ndiff --git a/src/plone/rest/traverse.py b/src/plone/rest/traverse.py\nindex f8d4a23..0a151c8 100644\n--- a/src/plone/rest/traverse.py\n+++ b/src/plone/rest/traverse.py\n@@ -5,6 +5,7 @@\n from plone.rest.interfaces import IAPIRequest\n from plone.rest.interfaces import IService\n from plone.rest.events import mark_as_api_request\n+from zExceptions import Redirect\n from zope.component import adapter\n from zope.component import queryMultiAdapter\n from zope.interface import implementer\n@@ -64,6 +65,18 @@ def __init__(self, context, request):\n self.request = request\n \n def traverse(self, name_ignored, subpath_ignored):\n+ name = "/++api++"\n+ url = self.request.ACTUAL_URL\n+ if url.count(name) > 1:\n+ # Redirect to proper url.\n+ while f"{name}{name}" in url:\n+ url = url.replace(f"{name}{name}", name)\n+ if url.count(name) > 1:\n+ # Something like: .../++api++/something/++api++\n+ # Return nothing, so a NotFound is raised.\n+ return\n+ # Raise a redirect exception to stop execution of the current request.\n+ raise Redirect(url)\n mark_as_api_request(self.request, "application/json")\n return self.context\n \n'