diff --git a/docs/source/endpoints/querystring.md b/docs/source/endpoints/querystring.md index b376fa0625..4083e4966c 100644 --- a/docs/source/endpoints/querystring.md +++ b/docs/source/endpoints/querystring.md @@ -21,6 +21,8 @@ The `operators` property contains additional metadata about each operation. If an index uses a vocabulary, the vocabulary values are included in the `values` property. The vocabulary is resolved in the same context where the `/@querystring` endpoint is called (requires `plone.app.querystring >= 2.1.0`). +If an index uses a vocabulary that the current user does not have permission to access, that index will not be included in the results. + ## Get `querystring` configuration To get the metadata about all query operations available in the portal, call the `/@querystring` endpoint with a `GET` request: diff --git a/src/plone/restapi/services/querystring/get.py b/src/plone/restapi/services/querystring/get.py index 359bf8625c..f1d6206437 100644 --- a/src/plone/restapi/services/querystring/get.py +++ b/src/plone/restapi/services/querystring/get.py @@ -3,19 +3,29 @@ from plone.restapi.services import Service from zope.component import getMultiAdapter from zope.component import getUtility - +from plone.restapi.services.vocabularies.get import VocabulariesGet class QuerystringGet(Service): - """Returns the querystring configuration. - - This basically does the same thing as the '@@querybuilderjsonconfig' - view from p.a.querystring, but exposes the config via the REST API. - """ - + """Returns the querystring configuration, filtering based on user permissions.""" + def reply(self): registry = getUtility(IRegistry) reader = getMultiAdapter((registry, self.request), IQuerystringRegistryReader) reader.vocab_context = self.context result = reader() - result["@id"] = "%s/@querystring" % self.context.absolute_url() + + # Filter vocabularies based on user permissions + vocabularies_get_service = VocabulariesGet(self.context, self.request) + indexes_to_remove = [] + + for index_name, index_data in result['indexes'].items(): + if 'vocabulary' in index_data: + vocabulary_name = index_data['vocabulary'] + if not vocabularies_get_service._has_permission_to_access_vocabulary(vocabulary_name): + indexes_to_remove.append(index_name) + + for index_name in indexes_to_remove: + del result['indexes'][index_name] + + result["@id"] = f"{self.context.absolute_url()}/@querystring" return result diff --git a/src/plone/restapi/tests/test_querystring_get.py b/src/plone/restapi/tests/test_querystring_get.py new file mode 100644 index 0000000000..ddcf054a1e --- /dev/null +++ b/src/plone/restapi/tests/test_querystring_get.py @@ -0,0 +1,27 @@ +from plone.app.testing import PLONE_RESTAPI_FUNCTIONAL_TESTING +from plone.restapi.testing import RelativeSession +import unittest + +class TestQuerystringGet(unittest.TestCase): + + layer = PLONE_RESTAPI_FUNCTIONAL_TESTING + + def setUp(self): + self.portal = self.layer["portal"] + self.api_session = RelativeSession(self.portal.absolute_url()) + self.api_session.headers.update({"Accept": "application/json"}) + + def test_vocabularies_permission_filtering(self): + response = self.api_session.get("/@querystring") + self.assertEqual(response.status_code, 200) + result = response.json() + + # Assert that a sensitive vocabulary is filtered out + self.assertNotIn('plone.app.vocabularies.Users', result['indexes']) + self.assertNotIn('plone.app.vocabularies.Groups', result['indexes']) + + # Assert that public vocabularies are still present + self.assertIn('plone.app.vocabularies.Keywords', result['indexes']) + + def tearDown(self): + self.api_session.close()