From 048c5693ada0e0866a1f286b17ae3422b7eb26a0 Mon Sep 17 00:00:00 2001 From: index-git Date: Tue, 19 Sep 2023 15:14:41 +0200 Subject: [PATCH 1/5] Test WellKnownName element in generic GeoServer style --- src/micka/__init__.py | 1 + .../layer_style/sld_wellknownname_test.py | 31 +++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 tests/dynamic_data/publications/layer_style/sld_wellknownname_test.py diff --git a/src/micka/__init__.py b/src/micka/__init__.py index bdd3a220c..ff6c62d79 100644 --- a/src/micka/__init__.py +++ b/src/micka/__init__.py @@ -14,6 +14,7 @@ 'srv': 'http://www.isotc211.org/2005/srv', 'soap': 'http://www.w3.org/2003/05/soap-envelope', 'hs': 'http://www.hsrs.cz/micka', + 'sld': "http://www.opengis.net/sld" } diff --git a/tests/dynamic_data/publications/layer_style/sld_wellknownname_test.py b/tests/dynamic_data/publications/layer_style/sld_wellknownname_test.py new file mode 100644 index 000000000..22a3eacaa --- /dev/null +++ b/tests/dynamic_data/publications/layer_style/sld_wellknownname_test.py @@ -0,0 +1,31 @@ +from lxml import etree as ET +import requests +import pytest + +from geoserver import util as gs_util +from micka import NAMESPACES +from test_tools import process_client +from tests import Publication +from tests.dynamic_data import base_test + + +@pytest.mark.xfail(reason="Geoserver removes default values.") +class TestPublication(base_test.TestSingleRestPublication): + workspace = 'dynamic_test_workspace_sld_wellknownname' + publication_type = process_client.LAYER_TYPE + layername = 'layer_sld_wellknownname' + + def test_sld_wellknownname(self, ): + self.post_publication(Publication(self.workspace, self.publication_type, self.layername)) + response = requests.get( + gs_util.get_workspace_style_url(self.workspace + '_wms', self.layername), + auth=gs_util.GS_AUTH, + headers=gs_util.headers_sld['1.0.0'], + timeout=gs_util.GS_REST_TIMEOUT, + ) + parser = ET.XMLParser(remove_blank_text=True) + resp_tree = ET.fromstring(response.content, parser=parser) + response_wkn = resp_tree.xpath('//sld:WellKnownName', namespaces=NAMESPACES) + assert len(response_wkn) == 1, f'{ET.tostring(resp_tree, encoding="unicode", pretty_print=True)}' + response_wkn = response_wkn[0] + assert response_wkn.text == 'square' From 6b61bdf3e85849372f52259ade4ce743cbf23199 Mon Sep 17 00:00:00 2001 From: index-git Date: Tue, 19 Sep 2023 15:18:27 +0200 Subject: [PATCH 2/5] Post styles on GeoServer with raw=True --- CHANGELOG.md | 1 + src/geoserver/util.py | 1 + .../publications/layer_style/sld_wellknownname_test.py | 2 -- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c1f0abe86..4f4798dfc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ - requests to [WMS](doc/endpoints.md#web-map-service) and [WFS](doc/endpoints.md#web-feature-service) endpoints - [#868](https://github.com/LayerManager/layman/issues/868) Responses to [GET Workspace Layer Metadata Comparison](doc/rest.md#get-workspace-layer-metadata-comparison) and [GET Workspace Map Metadata Comparison](doc/rest.md#get-workspace-map-metadata-comparison) do not respect [HTTP header `X-Forwarded-Prefix`](doc/client-proxy.md#x-forwarded-prefix-http-header) of the request intentionally, in order to keep URLs in canonical form. - [#868](https://github.com/LayerManager/layman/issues/868) Relations between map and [internal layers](doc/models.md#internal-map-layer) are updated in `map_layer` table when calling [POST Workspace Maps](doc/rest.md#post-workspace-maps), [PATCH Workspace Map](doc/rest.md#patch-workspace-map), [DELETE Workspace Map](doc/rest.md#delete-workspace-map), and [DELETE Workspace Maps](doc/rest.md#delete-workspace-maps). +- [#927](https://github.com/LayerManager/layman/issues/927) Send styles to GeoServer with [`raw`](https://docs.geoserver.org/2.21.x/en/user/rest/api/styles.html#raw) param set to `True`. - [#880](https://github.com/LayerManager/layman/issues/880) Use Docker Compose v2 (`docker compose`) in Makefile without `compatibility` flag and remove `Makefile_docker-compose_v1` file. Docker containers are named according to Docker Compose v2 and may have different name after upgrade. - [#765](https://github.com/LayerManager/layman/issues/765) Stop saving OAuth2 claims in filesystem, use prime DB schema only. - [#893](https://github.com/LayerManager/layman/issues/893) It is possible to specify logging level by new environment variable [LAYMAN_LOGLEVEL](doc/env-settings.md#LAYMAN_LOGLEVEL). Default level is `INFO`. diff --git a/src/geoserver/util.py b/src/geoserver/util.py index 8029d6dc6..f32817c4a 100644 --- a/src/geoserver/util.py +++ b/src/geoserver/util.py @@ -405,6 +405,7 @@ def post_workspace_sld_style(geoserver_workspace, layername, sld_file, launder_f 'Content-type': sld_content_type, }, auth=GS_AUTH, + params={'raw': True}, timeout=GS_REST_TIMEOUT, ) if response.status_code == 400: diff --git a/tests/dynamic_data/publications/layer_style/sld_wellknownname_test.py b/tests/dynamic_data/publications/layer_style/sld_wellknownname_test.py index 22a3eacaa..28b9534db 100644 --- a/tests/dynamic_data/publications/layer_style/sld_wellknownname_test.py +++ b/tests/dynamic_data/publications/layer_style/sld_wellknownname_test.py @@ -1,6 +1,5 @@ from lxml import etree as ET import requests -import pytest from geoserver import util as gs_util from micka import NAMESPACES @@ -9,7 +8,6 @@ from tests.dynamic_data import base_test -@pytest.mark.xfail(reason="Geoserver removes default values.") class TestPublication(base_test.TestSingleRestPublication): workspace = 'dynamic_test_workspace_sld_wellknownname' publication_type = process_client.LAYER_TYPE From debdaabdb4d627d4f90093c8d6391d0f51667e9c Mon Sep 17 00:00:00 2001 From: index-git Date: Tue, 19 Sep 2023 15:24:57 +0200 Subject: [PATCH 3/5] Small optimization --- src/geoserver/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/geoserver/util.py b/src/geoserver/util.py index f32817c4a..b51b1c1d7 100644 --- a/src/geoserver/util.py +++ b/src/geoserver/util.py @@ -384,8 +384,8 @@ def post_workspace_sld_style(geoserver_workspace, layername, sld_file, launder_f else: sld_content_type = 'application/vnd.ogc.sld+xml' - propertname_els = tree.findall('.//{http://www.opengis.net/ogc}PropertyName') if launder_function: + propertname_els = tree.findall('.//{http://www.opengis.net/ogc}PropertyName') for element in propertname_els: element.text = launder_function(element.text) From d954a9eb2e2b9bf6119b3060ba3f37f1705abbc5 Mon Sep 17 00:00:00 2001 From: index-git Date: Tue, 19 Sep 2023 16:02:28 +0200 Subject: [PATCH 4/5] Simplify post style to GeoServer --- src/geoserver/util.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/geoserver/util.py b/src/geoserver/util.py index b51b1c1d7..805572b54 100644 --- a/src/geoserver/util.py +++ b/src/geoserver/util.py @@ -368,15 +368,6 @@ def post_workspace_sld_style(geoserver_workspace, layername, sld_file, launder_f ) response.raise_for_status() sld_file = io.BytesIO(response.content) - response = requests.post( - get_workspace_style_url(geoserver_workspace), - data=f"", - headers=headers_xml, - auth=GS_AUTH, - timeout=GS_REST_TIMEOUT, - ) - response.raise_for_status() - tree = ET.parse(sld_file) root = tree.getroot() if 'version' in root.attrib and root.attrib['version'] == '1.1.0': @@ -397,15 +388,16 @@ def post_workspace_sld_style(geoserver_workspace, layername, sld_file, launder_f ) sld_file.seek(0) - response = requests.put( - get_workspace_style_url(geoserver_workspace, layername), + response = requests.post( + get_workspace_style_url(geoserver_workspace), data=sld_file.read(), headers={ 'Accept': 'application/json', 'Content-type': sld_content_type, }, auth=GS_AUTH, - params={'raw': True}, + params={'raw': True, + 'name': layername, }, timeout=GS_REST_TIMEOUT, ) if response.status_code == 400: From d800052d6b492b5542368f5726666b0589e5e402 Mon Sep 17 00:00:00 2001 From: index-git Date: Wed, 20 Sep 2023 14:56:23 +0200 Subject: [PATCH 5/5] Raise LaymanError(46) when invalid style file is uploaded --- src/layman/layer/filesystem/input_style.py | 22 ++++++++++++------- .../layer/filesystem/input_style_test.py | 15 ++++++++----- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/src/layman/layer/filesystem/input_style.py b/src/layman/layer/filesystem/input_style.py index b1bb7dd3f..f4d5de034 100644 --- a/src/layman/layer/filesystem/input_style.py +++ b/src/layman/layer/filesystem/input_style.py @@ -73,14 +73,20 @@ def get_layer_file(workspace, layername): def get_style_type_from_file_storage(file_storage): if file_storage: - xml = file_storage.read() - file_storage.seek(0) - xml_tree = etree.fromstring(xml) - root_tag = xml_tree.tag - root_attribute = etree.QName(root_tag).localname - result = next((sd for sd in layer.STYLE_TYPES_DEF if sd.root_element == root_attribute), None) - if not result: - raise LaymanError(46) + try: + xml = file_storage.read() + file_storage.seek(0) + xml_tree = etree.fromstring(xml) + root_tag = xml_tree.tag + root_attribute = etree.QName(root_tag).localname + result = next((sd for sd in layer.STYLE_TYPES_DEF if sd.root_element == root_attribute), None) + if not result: + raise LaymanError(46, { + 'message': f"Unknown root element.", + 'expected': f"Root element is one of {[sd.root_element for sd in layer.STYLE_TYPES_DEF if sd.root_element]}", + }) + except etree.XMLSyntaxError as exc: + raise LaymanError(46, "Unable to parse style file.",) from exc else: result = layer.NO_STYLE_DEF return result diff --git a/src/layman/layer/filesystem/input_style_test.py b/src/layman/layer/filesystem/input_style_test.py index 205a3be77..8e7aa53fd 100644 --- a/src/layman/layer/filesystem/input_style_test.py +++ b/src/layman/layer/filesystem/input_style_test.py @@ -1,4 +1,3 @@ -import lxml import pytest from werkzeug.datastructures import FileStorage @@ -23,15 +22,21 @@ def test_get_style_type_from_xml_file(file_path, assert detected_type.code == expected_type -@pytest.mark.parametrize('file_path, expected_error, expected_code', [ - ('sample/style/no_style.xml', LaymanError, 46), - ('test_tools/data/thumbnail/countries_wms_blue.png', lxml.etree.XMLSyntaxError, 4), +@pytest.mark.parametrize('file_path, expected_error, expected_code, expected_data', [ + ('sample/style/no_style.xml', LaymanError, 46, { + 'message': 'Unknown root element.', + 'expected': "Root element is one of ['StyledLayerDescriptor', 'qgis']", + }), + ('sample/style/generic-invalid.xml', LaymanError, 46, 'Unable to parse style file.'), + ('test_tools/data/thumbnail/countries_wms_blue.png', LaymanError, 46, 'Unable to parse style file.'), ]) def test_get_style_type_from_xml_file_errors(file_path, expected_error, - expected_code): + expected_code, + expected_data): with pytest.raises(expected_error) as exc_info: with open(file_path, 'rb') as file: file = FileStorage(file) input_style.get_style_type_from_file_storage(file) assert exc_info.value.code == expected_code + assert exc_info.value.data == expected_data