From 842c4ba6265b86a5c4043ba5490a744df2bdb781 Mon Sep 17 00:00:00 2001 From: index-git Date: Wed, 6 Sep 2023 14:38:24 +0200 Subject: [PATCH] Incorporate X-Forwarded-Prefix into GeoServer proxy --- CHANGELOG.md | 2 +- doc/client-proxy.md | 2 ++ src/geoserver/util.py | 12 ++++++------ src/layman/__init__.py | 2 +- src/layman/geoserver_proxy.py | 13 +++++++++++++ src/layman_settings.py | 1 + tests/asserts/final/publication/__init__.py | 1 + .../final/publication/geoserver_proxy.py | 19 +++++++++++++++++++ 8 files changed, 44 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 12b233c4d..045752178 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,7 @@ - [#765](https://github.com/LayerManager/layman/issues/765) Remove `authn.txt` files from workspace directories. The same information as in `authn.txt` files is saved in prime DB schema. - [#868](https://github.com/LayerManager/layman/issues/868) Fill table `map_layer` with relations between maps and [internal layers](doc/models.md#internal-map-layer) (layers published on this Layman instance). Relations to [external layers](doc/models.md#internal-map-layer) (layers of other servers) are not imported into the table. ### Changes -- [#868](https://github.com/LayerManager/layman/issues/868) Endpoints [GET Publications](doc/rest.md#get-publications), [GET Layers](doc/rest.md#get-layers), [GET Workspace Layers](doc/rest.md#get-workspace-layers), [GET Maps](doc/rest.md#get-maps), [GET Workspace Maps](doc/rest.md#get-workspace-maps), [GET Workspace Layer](doc/rest.md#get-workspace-layer), [GET Workspace Map](doc/rest.md#get-workspace-map), [POST Workspace Layers](doc/rest.md#post-workspace-layers), [DELETE Workspace Layer](doc/rest.md#delete-workspace-layer), [DELETE Workspace Layers](doc/rest.md#delete-workspace-layers), [DELETE Workspace Map](doc/rest.md#delete-workspace-map), [DELETE Workspace Maps](doc/rest.md#delete-workspace-maps) and [POST Workspace Maps](doc/rest.md#post-workspace-maps) respects [HTTP header `X-Forwarded-Prefix`](doc/client-proxy.md#x-forwarded-prefix-http-header) of the request in the response. +- [#868](https://github.com/LayerManager/layman/issues/868) Endpoints [GET Publications](doc/rest.md#get-publications), [GET Layers](doc/rest.md#get-layers), [GET Workspace Layers](doc/rest.md#get-workspace-layers), [GET Maps](doc/rest.md#get-maps), [GET Workspace Maps](doc/rest.md#get-workspace-maps), [GET Workspace Layer](doc/rest.md#get-workspace-layer), [GET Workspace Map](doc/rest.md#get-workspace-map), [POST Workspace Layers](doc/rest.md#post-workspace-layers), [DELETE Workspace Layer](doc/rest.md#delete-workspace-layer), [DELETE Workspace Layers](doc/rest.md#delete-workspace-layers), [DELETE Workspace Map](doc/rest.md#delete-workspace-map), [DELETE Workspace Maps](doc/rest.md#delete-workspace-maps), [POST Workspace Maps](doc/rest.md#post-workspace-maps) and [WMS/WFS endpoints](doc/endpoints.md) respects [HTTP header `X-Forwarded-Prefix`](doc/client-proxy.md#x-forwarded-prefix-http-header) of the request in the response. - [#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), [DELETE Workspace Map](doc/rest.md#delete-workspace-map), and [DELETE Workspace Maps](doc/rest.md#delete-workspace-maps). - [#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. diff --git a/doc/client-proxy.md b/doc/client-proxy.md index d331feaca..64b63f578 100644 --- a/doc/client-proxy.md +++ b/doc/client-proxy.md @@ -75,3 +75,5 @@ Currently, value of `X-Forwarded-Prefix` affects following URLs: * `url` key * [POST Workspace Maps](rest.md#post-workspace-maps) * `url` key +* [WMS/WFS endpoints](endpoints.md) + * all url keys diff --git a/src/geoserver/util.py b/src/geoserver/util.py index 9caa7c24f..3baf5a064 100644 --- a/src/geoserver/util.py +++ b/src/geoserver/util.py @@ -1027,10 +1027,11 @@ def get_proxy_base_url(auth, global_settings=None): def ensure_proxy_base_url(proxy_base_url, auth): global_settings = get_global_settings(auth) current_url = get_proxy_base_url(auth, global_settings=global_settings) - url_equals = proxy_base_url == current_url - if not url_equals: + set_is_needed = not (proxy_base_url == current_url and global_settings['settings']['useHeadersProxyURL']) + if set_is_needed: global_settings['settings']['proxyBaseUrl'] = proxy_base_url - logger.info(f"Current Proxy Base URL {current_url} not equals to requested {proxy_base_url}, changing.") + global_settings['settings']['useHeadersProxyURL'] = True + logger.info(f"Current Proxy Base URL {current_url} not equals to requested {proxy_base_url} or useHeadersProxyURL is not set({global_settings['settings']['useHeadersProxyURL']}), changing.") r_url = GS_REST_SETTINGS response = requests.put( r_url, @@ -1043,9 +1044,8 @@ def ensure_proxy_base_url(proxy_base_url, auth): ) response.raise_for_status() else: - logger.info(f"Current Proxy Base URL {current_url} already corresponds with requested one.") - url_changed = not url_equals - return url_changed + logger.info(f"Current Proxy Base URL {current_url} already corresponds with requested one and useHeadersProxyURL is already set.") + return set_is_needed def reset(auth): diff --git a/src/layman/__init__.py b/src/layman/__init__.py index 075376a09..6829daedd 100644 --- a/src/layman/__init__.py +++ b/src/layman/__init__.py @@ -91,7 +91,7 @@ gs_util.ensure_user_role(settings.LAYMAN_GS_USER, 'ADMIN', settings.GEOSERVER_ADMIN_AUTH) gs_util.ensure_user_role(settings.LAYMAN_GS_USER, settings.LAYMAN_GS_ROLE, settings.GEOSERVER_ADMIN_AUTH) - gs_util.ensure_proxy_base_url(settings.LAYMAN_GS_PROXY_BASE_URL, settings.LAYMAN_GS_AUTH) + gs_util.ensure_proxy_base_url(settings.LAYMAN_GS_PROXY_BASE_URL_WITH_PLACEHOLDERS, settings.LAYMAN_GS_AUTH) if not IN_UPGRADE_PROCESS: logger.info(f'Adjusting GeoServer SRS') diff --git a/src/layman/geoserver_proxy.py b/src/layman/geoserver_proxy.py index a86a32c1f..38b98cf83 100644 --- a/src/layman/geoserver_proxy.py +++ b/src/layman/geoserver_proxy.py @@ -223,6 +223,19 @@ def proxy(subpath): if is_user_with_name(authn_username): headers_req[settings.LAYMAN_GS_AUTHN_HTTP_HEADER_ATTRIBUTE] = authn_username + # adjust proxy base url headers + for header in [ + 'X-Forwarded-Proto', + 'X-Forwarded-Host', + 'X-Forwarded-For', + 'X-Forwarded-Path', + 'Forwarded', + 'Host', + ]: + headers_req.pop(header, None) + x_forwarded_prefix = layman_util.get_x_forwarded_prefix(request.headers) + headers_req['X-Forwarded-Path'] = x_forwarded_prefix or '' + # ensure layer attributes in case of WFS-T app.logger.info(f"{request.method} GeoServer proxy, headers_req={headers_req}, url={url}") wfs_t_layers = set() diff --git a/src/layman_settings.py b/src/layman_settings.py index ad375676d..7a8a6951f 100644 --- a/src/layman_settings.py +++ b/src/layman_settings.py @@ -241,6 +241,7 @@ class EnumWfsWmsStatus(Enum): LAYMAN_PUBLIC_URL_SCHEME = urlparse(LAYMAN_CLIENT_PUBLIC_URL).scheme LAYMAN_GS_PROXY_BASE_URL = urljoin(f'{LAYMAN_PUBLIC_URL_SCHEME}://{LAYMAN_PROXY_SERVER_NAME}', LAYMAN_GS_PATH) +LAYMAN_GS_PROXY_BASE_URL_WITH_PLACEHOLDERS = urljoin(f'{LAYMAN_PUBLIC_URL_SCHEME}://{LAYMAN_PROXY_SERVER_NAME}${{X-Forwarded-Path}}', LAYMAN_GS_PATH) CSW_URL = os.getenv('CSW_URL', None) CSW_PROXY_URL = os.getenv('CSW_PROXY_URL', None) diff --git a/tests/asserts/final/publication/__init__.py b/tests/asserts/final/publication/__init__.py index dc7edd9aa..41d338d13 100644 --- a/tests/asserts/final/publication/__init__.py +++ b/tests/asserts/final/publication/__init__.py @@ -27,6 +27,7 @@ Action(geoserver.is_complete_in_internal_workspace_wms, {}), Action(geoserver_proxy.is_complete_in_workspace_wms_1_3_0, {}), Action(geoserver_proxy.workspace_wfs_2_0_0_capabilities_available_if_vector, {}), + Action(geoserver_proxy.wms_with_x_forwarded_prefix, {}) ] IS_MAP_COMPLETE_AND_CONSISTENT = [ diff --git a/tests/asserts/final/publication/geoserver_proxy.py b/tests/asserts/final/publication/geoserver_proxy.py index c6fe11266..e96ddd6da 100644 --- a/tests/asserts/final/publication/geoserver_proxy.py +++ b/tests/asserts/final/publication/geoserver_proxy.py @@ -35,3 +35,22 @@ def workspace_wfs_2_0_0_capabilities_available_if_vector(workspace, publ_type, n wfs_layer = wfs_inst.contents[wfs_name] assert len(wfs_layer.metadataUrls) == 1 assert wfs_layer.metadataUrls[0]['url'].startswith('http://localhost:3080/record/xml/m-') + + +def wms_with_x_forwarded_prefix(workspace, publ_type, name, headers): + assert publ_type == process_client.LAYER_TYPE + proxy_prefix = '/layman-proxy' + headers = headers or {} + + with app.app_context(): + wms_url = test_util.url_for('geoserver_proxy_bp.proxy', subpath=workspace + settings.LAYMAN_GS_WMS_WORKSPACE_POSTFIX + '/ows') + for version in ['1.3.0', '1.1.1']: + wms_inst = gs_util.wms_proxy(wms_url, + version=version, + headers={'X-Forwarded-Prefix': proxy_prefix, + 'X-Forwarder-Path': '/some-other-proxy', + **headers, + } + ) + for style_content in wms_inst.contents[name].styles.values(): + assert style_content['legend'].startswith(f'http://localhost:8000{proxy_prefix}/geoserver/dynamic_test_workspace_standard_layer_wms/')