diff --git a/doc/client-proxy.md b/doc/client-proxy.md index 59ba00f28..9a3e8b8b2 100644 --- a/doc/client-proxy.md +++ b/doc/client-proxy.md @@ -23,7 +23,7 @@ By default, Layman will not adjust URLs in its response to contain also URL path ## X-Forwarded-Prefix HTTP header -The value of the `X-Forwarded-Prefix` HTTP header will be used as prefix in some URL paths of Layman response. +The value of the `X-Forwarded-Prefix` HTTP header will be used as prefix in some URL paths of Layman response and is required to match regular expression `^(?:/[a-z0-9_-]+)*$`. For example, if you send request to `/layman-client-proxy/rest/publications` with HTTP header `X-Forwarded-Prefix=/layman-client-proxy` then response will change to diff --git a/src/layman/error_list.py b/src/layman/error_list.py index 3b045f2d8..b0eccf9bb 100644 --- a/src/layman/error_list.py +++ b/src/layman/error_list.py @@ -54,4 +54,5 @@ 51: (500, 'Error when generating thumbnail'), 52: (400, 'GeoServer HTTP or connection error'), 53: (500, 'Error when publishing on GeoServer. It happens for example for raster files with wrong explicit CRS.'), + 54: (400, 'Wrong header value'), } diff --git a/src/layman/layer/util.py b/src/layman/layer/util.py index 83dbfbbf9..d20bda218 100644 --- a/src/layman/layer/util.py +++ b/src/layman/layer/util.py @@ -418,7 +418,7 @@ def parse_and_validate_external_table_uri_str(external_table_uri_str): if unsafe_column_names: raise LaymanError(2, { 'parameter': 'external_table_uri', - 'message': 'Expected table with all column names mathing regular expression ' + SAFE_PG_IDENTIFIER_PATTERN, + 'message': 'Expected table with all column names matching regular expression ' + SAFE_PG_IDENTIFIER_PATTERN, 'found': { 'external_table_uri': external_table_uri_str, 'schema': schema, diff --git a/src/layman/layer/util_test.py b/src/layman/layer/util_test.py index 81e1f57be..e4faf2066 100644 --- a/src/layman/layer/util_test.py +++ b/src/layman/layer/util_test.py @@ -399,7 +399,7 @@ def test_parse_external_table_uri_str(external_table_uri_str, exp_result): 'code': 2, 'data': { 'parameter': 'external_table_uri', - 'message': 'Expected table with all column names mathing regular expression ^[a-zA-Z_][a-zA-Z_0-9]*$', + 'message': 'Expected table with all column names matching regular expression ^[a-zA-Z_][a-zA-Z_0-9]*$', 'found': { 'external_table_uri': 'postgresql://docker:docker@postgresql:5432/external_test_db?schema=schema_name&table=table_with_unsafe_column_name&geo_column=geo_wkb_column', 'schema': 'schema_name', diff --git a/src/layman/util.py b/src/layman/util.py index 12cb8c60f..5412e6e32 100644 --- a/src/layman/util.py +++ b/src/layman/util.py @@ -548,4 +548,15 @@ def ensure_home_dir(): def get_x_forwarded_prefix(request_headers): - return request_headers.get('X-Forwarded-Prefix') + header_key = 'X-Forwarded-Prefix' + header_value = request_headers.get(header_key) + if header_value and not re.match(CLIENT_PROXY_PATTERN, header_value): + raise LaymanError(54, + {'header': header_key, + 'message': f'Optional header {header_key} is expected to be valid URL subpath starting with slash, or empty string.', + 'expected': f'Expected header matching regular expression {CLIENT_PROXY_PATTERN}', + 'found': header_value, + + } + ) + return header_value diff --git a/src/layman/util_test.py b/src/layman/util_test.py index 3535f809c..4691e17f9 100644 --- a/src/layman/util_test.py +++ b/src/layman/util_test.py @@ -1,6 +1,7 @@ import importlib import pytest +from test_tools import util as test_util from . import app, settings, LaymanError, util @@ -134,3 +135,32 @@ def test__url_for(endpoint, internal, params, expected_url): # pylint: disable=protected-access assert util._url_for(endpoint, server_name=server_name, proxy_server_name=proxy_server_name, internal=internal, **params) == expected_url + + +@pytest.mark.parametrize('headers, exp_result', [ + pytest.param({'X-Forwarded-Prefix': '/layman-proxy'}, '/layman-proxy', id='simple_header'), + pytest.param({}, None, id='without_header'), +]) +def test_get_x_forwarded_prefix(headers, exp_result): + result = util.get_x_forwarded_prefix(headers) + assert result == exp_result + + +@pytest.mark.parametrize('headers, exp_error', [ + pytest.param( + {'X-Forwarded-Prefix': 'layman-proxy'}, + { + 'http_code': 400, + 'code': 54, + 'data': { + 'header': 'X-Forwarded-Prefix', + 'message': 'Optional header X-Forwarded-Prefix is expected to be valid URL subpath starting with slash, or empty string.', + 'expected': 'Expected header matching regular expression ^(?:/[a-z0-9_-]+)*$', + 'found': 'layman-proxy', + }, + }, id='without_slash'), +]) +def test_get_x_forwarded_prefix_raises(headers, exp_error): + with pytest.raises(LaymanError) as exc_info: + util.get_x_forwarded_prefix(headers) + test_util.assert_error(exp_error, exc_info)