From 4ad1fb9c942d52389573928c861bfc04ff85b707 Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Sat, 27 Jul 2024 16:05:25 +0200 Subject: [PATCH 1/2] [server][oapif] Remove forbidden operations from schema Respect access control plugins. Funded by: Gis3W --- src/server/services/wfs3/qgswfs3handlers.cpp | 37 +++++++++++++- tests/src/python/test_qgsserver_api.py | 52 +++++++++++++++++++- 2 files changed, 85 insertions(+), 4 deletions(-) diff --git a/src/server/services/wfs3/qgswfs3handlers.cpp b/src/server/services/wfs3/qgswfs3handlers.cpp index a1feeac9626d..99acdc3d1c9b 100644 --- a/src/server/services/wfs3/qgswfs3handlers.cpp +++ b/src/server/services/wfs3/qgswfs3handlers.cpp @@ -1085,6 +1085,20 @@ json QgsWfs3CollectionsItemsHandler::schema( const QgsServerApiContext &context } } }; + +#ifdef HAVE_SERVER_PYTHON_PLUGINS + + // get access controls + QgsAccessControl *accessControl = context.serverInterface()->accessControls(); + // If the layer has no insert capabilities, remove the post operation + if ( accessControl && !accessControl->layerInsertPermission( mapLayer ) ) + { + qDebug( "Removing post operation from %s", path.toStdString().c_str() ); + data[ path.toStdString() ].erase( "post" ); + } + +#endif + } // end for loop return data; } @@ -2087,8 +2101,8 @@ void QgsWfs3CollectionsFeatureHandler::handleRequest( const QgsServerApiContext case QgsServerRequest::Method::DeleteMethod: { // First: check permissions - const QStringList wfstUpdateLayerIds = QgsServerProjectUtils::wfstDeleteLayerIds( *context.project() ); - if ( ! wfstUpdateLayerIds.contains( mapLayer->id() ) || + const QStringList wfstDeleteLayerIds = QgsServerProjectUtils::wfstDeleteLayerIds( *context.project() ); + if ( ! wfstDeleteLayerIds.contains( mapLayer->id() ) || ! mapLayer->dataProvider()->capabilities().testFlag( Qgis::VectorProviderCapability::DeleteFeatures ) ) { throw QgsServerApiPermissionDeniedException( QStringLiteral( "Features in layer '%1' cannot be deleted" ).arg( mapLayer->name() ) ); @@ -2281,6 +2295,25 @@ json QgsWfs3CollectionsFeatureHandler::schema( const QgsServerApiContext &contex } } }; + +#ifdef HAVE_SERVER_PYTHON_PLUGINS + + // get access controls + QgsAccessControl *accessControl = context.serverInterface()->accessControls(); + // If the layer has no delete capabilities, remove the delete operation + if ( accessControl && !accessControl->layerDeletePermission( mapLayer ) ) + { + data[ path ].erase( "delete" ); + } + // If the layer has no update capabilities, remove the put and patch operation + if ( accessControl && !accessControl->layerUpdatePermission( mapLayer ) ) + { + data[ path ].erase( "put" ); + data[ path ].erase( "patch" ); + } + +#endif + } // end for loop return data; } diff --git a/tests/src/python/test_qgsserver_api.py b/tests/src/python/test_qgsserver_api.py index f321b987e7c4..df98ca9883cc 100644 --- a/tests/src/python/test_qgsserver_api.py +++ b/tests/src/python/test_qgsserver_api.py @@ -294,15 +294,21 @@ def setUpClass(cls): class RestrictedLayerAccessControl(QgsAccessControlFilter): """Access control filter to exclude a list of layers by ID, used by WFS3 test""" - def __init__(self, server_iface, ecxluded_layers=[]): + def __init__(self, server_iface, ecxluded_layers=[], can_delete=False, can_edit=False, can_insert=False): self.excluded_layers = ecxluded_layers + self.can_delete = can_delete + self.can_edit = can_edit + self.can_insert = can_insert super(QgsAccessControlFilter, self).__init__(server_iface) def layerPermissions(self, layer): """ Return the layer rights """ rights = QgsAccessControlFilter.LayerPermissions() - rights.canRead = layer.id() not in self.excluded_layers + rights.canRead = layer.id() not in self.excluded_layers or self.can_edit or self.can_delete or self.can_insert + rights.canUpdate = layer.id() not in self.excluded_layers or self.can_edit + rights.canInsert = layer.id() not in self.excluded_layers or self.can_insert + rights.canDelete = layer.id() not in self.excluded_layers or self.can_delete return rights @@ -440,6 +446,48 @@ def test_wfs3_api(self): project.read(os.path.join(self.temporary_path, 'qgis_server', 'test_project_api.qgs')) self.compareApi(request, project, 'test_wfs3_api_project.json') + def test_wfs3_api_permissions(self): + """Test the API with different permissions on a layer""" + + project = QgsProject() + project.read(os.path.join(self.temporary_path, 'qgis_server', 'test_project_api.qgs')) + + server = QgsServer() + + request = QgsBufferServerRequest( + 'http://server.qgis.org/wfs3/api.openapi3') + + response = QgsBufferServerResponse() + server.handleRequest(request, response, project) + self.assertEqual(response.statusCode(), 200) + result = bytes(response.body()).decode('utf8') + jresult = json.loads(result) + paths = jresult['paths']['/wfs3/collections/testlayer èé/items/{featureId}'] + + self.assertIn('get', paths) + self.assertIn('put', paths) + self.assertIn('delete', paths) + self.assertIn('patch', paths) + + self.assertIn('post', jresult['paths']['/wfs3/collections/testlayer èé/items']) + + # Access control filter to exclude a layer + acfilter = RestrictedLayerAccessControl(server.serverInterface(), ['testlayer20150528120452665'], can_edit=True, can_delete=False, can_insert=False) + server.serverInterface().registerAccessControl(acfilter, 100) + + response = QgsBufferServerResponse() + server.handleRequest(request, response, project) + self.assertEqual(response.statusCode(), 200) + result = bytes(response.body()).decode('utf8') + jresult = json.loads(result) + paths = jresult['paths']['/wfs3/collections/testlayer èé/items/{featureId}'] + + self.assertIn('put', paths) + self.assertIn('patch', paths) + self.assertNotIn('delete', paths) + + self.assertNotIn('post', jresult['paths']['/wfs3/collections/testlayer èé/items']) + def test_wfs3_conformance(self): """Test WFS3 API""" request = QgsBufferServerRequest( From 0adf325724a145b7abf63ce7461199adb3f66665 Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Sat, 27 Jul 2024 18:33:26 +0200 Subject: [PATCH 2/2] Remove debug code --- src/server/services/wfs3/qgswfs3handlers.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/server/services/wfs3/qgswfs3handlers.cpp b/src/server/services/wfs3/qgswfs3handlers.cpp index 99acdc3d1c9b..a5438522b41e 100644 --- a/src/server/services/wfs3/qgswfs3handlers.cpp +++ b/src/server/services/wfs3/qgswfs3handlers.cpp @@ -1093,7 +1093,6 @@ json QgsWfs3CollectionsItemsHandler::schema( const QgsServerApiContext &context // If the layer has no insert capabilities, remove the post operation if ( accessControl && !accessControl->layerInsertPermission( mapLayer ) ) { - qDebug( "Removing post operation from %s", path.toStdString().c_str() ); data[ path.toStdString() ].erase( "post" ); }