Skip to content

Commit

Permalink
Merge pull request qgis#58257 from elpaso/bugfix-server-oapif-schema-…
Browse files Browse the repository at this point in the history
…permissions

[server][oapif] Remove forbidden operations from schema
  • Loading branch information
elpaso authored Aug 12, 2024
2 parents e1bfad1 + 0adf325 commit 5a9061c
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 4 deletions.
36 changes: 34 additions & 2 deletions src/server/services/wfs3/qgswfs3handlers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1085,6 +1085,19 @@ 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 ) )
{
data[ path.toStdString() ].erase( "post" );
}

#endif

} // end for loop
return data;
}
Expand Down Expand Up @@ -2087,8 +2100,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() ) );
Expand Down Expand Up @@ -2281,6 +2294,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;
}
52 changes: 50 additions & 2 deletions tests/src/python/test_qgsserver_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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(
Expand Down

0 comments on commit 5a9061c

Please sign in to comment.