From 43f4a8ef639af884909655ef271e15ecc3d3b328 Mon Sep 17 00:00:00 2001
From: Jiri Kozel <jirik@users.noreply.github.com>
Date: Mon, 18 Sep 2023 07:58:11 +0200
Subject: [PATCH 01/14] Add geckodriver.log to .gitignore

---
 .gitignore | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/.gitignore b/.gitignore
index d7124f304..70bdb6888 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,3 +17,5 @@
 !/.env.dev
 !/.env.test
 /deps/**/venv
+
+**/geckodriver.log

From 47a62f7477a55c8cfa99bb0ab8234b199c21be26 Mon Sep 17 00:00:00 2001
From: Jiri Kozel <jirik@users.noreply.github.com>
Date: Thu, 14 Sep 2023 15:08:55 +0200
Subject: [PATCH 02/14] Install test Python dependency jsonpath-ng

---
 CHANGELOG.md        |  10 +-
 doc/dependencies.md |   1 +
 docker/Pipfile      |   1 +
 docker/Pipfile.lock | 358 +++++++++++++++++++++++++-------------------
 4 files changed, 210 insertions(+), 160 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4d50ff09c..9583cafc6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -21,18 +21,22 @@
 - [#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`.
+- Add new test Python dependency:
+  - jsonpath-ng 1.6.0
 - Upgrade Python dependencies
   - certifi 2023.5.7 -> 2023.7.22 (suggested by dependabot)
+  - tornado 6.3.2 -> 6.3.3 (suggested by dependabot)
+  - flask 2.3.2 -> 2.3.3
   - jsonschema 4.17.3 -> 4.19.0
   - lxml 4.9.2 -> 4.9.3
   - owslib 0.28.1 -> 0.29.2
   - psycopg2-binary 2.9.5 -> 2.9.7
-  - redis 4.5.5 -> 4.6.0
-  - autopep8 2.0.1 -> 2.0.2
+  - redis 4.5.5 -> 5.0.0
+  - autopep8 2.0.1 -> 2.0.4
   - flake8 6.0.0 -> 6.1.0
   - pillow 9.3.0 -> 10.0.0
   - pycodestyle 2.10.0 -> 2.11.0 (to be consistent with GitHub Actions)
-  - pytest 7.2.0 -> 7.4.0
+  - pytest 7.2.0 -> 7.4.2
   - pytest-rerunfailures 10.3 -> 12.0
   - watchdog 2.2.0 -> 3.0.0
 
diff --git a/doc/dependencies.md b/doc/dependencies.md
index 71b5cecb3..246f8d0bd 100644
--- a/doc/dependencies.md
+++ b/doc/dependencies.md
@@ -56,6 +56,7 @@
 | [pytest-rerunfailures](https://github.com/pytest-dev/pytest-rerunfailures) | MPL | Pipfile | test | bin | to automatically rerun flaky tests |
 | [pytest-timeout](https://pypi.org/project/pytest-timeout/) | MIT | Pipfile | test | bin | to automatically stop tests after given timeout |
 | [pillow](https://github.com/python-pillow/Pillow) | HPND | Pipfile | test | bin | to ensure similarity of images |
+| [jsonpath-ng](https://github.com/h2non/jsonpath-ng) | HPND | Pipfile | test | bin | to query JSON files |
 
 ### Node.js dependencies
 | name | license | used by | env | bin or src | purpose |
diff --git a/docker/Pipfile b/docker/Pipfile
index 2a40c3db6..da99a8f83 100644
--- a/docker/Pipfile
+++ b/docker/Pipfile
@@ -13,6 +13,7 @@ autopep8 = "*"
 pytest-rerunfailures = "*"
 pytest-timeout = "*"
 pillow = "*"
+jsonpath-ng = "*"
 
 [packages]
 celery = {extras = ["redis"],version = "<5.3.0"}
diff --git a/docker/Pipfile.lock b/docker/Pipfile.lock
index 2aec4d5e9..dec45e981 100644
--- a/docker/Pipfile.lock
+++ b/docker/Pipfile.lock
@@ -1,7 +1,7 @@
 {
     "_meta": {
         "hash": {
-            "sha256": "7dcd145396d69ac76fee217c87d8f21d35462b1f991a18537ac4fcc7ab49dc0b"
+            "sha256": "b64b60ccacf9440767380c4757ea1d4845f982b42e7c465e7cb98466bbe59a1d"
         },
         "pipfile-spec": 6,
         "requires": {
@@ -26,11 +26,11 @@
         },
         "async-timeout": {
             "hashes": [
-                "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15",
-                "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"
+                "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f",
+                "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"
             ],
             "markers": "python_full_version <= '3.11.2'",
-            "version": "==4.0.2"
+            "version": "==4.0.3"
         },
         "attrs": {
             "hashes": [
@@ -165,11 +165,11 @@
         },
         "click": {
             "hashes": [
-                "sha256:48ee849951919527a045bfe3bf7baa8a959c423134e1a5b98c05c20ba75a1cbd",
-                "sha256:fa244bb30b3b5ee2cae3da8f55c9e5e0c0e86093306301fb418eb9dc40fbded5"
+                "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28",
+                "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"
             ],
             "markers": "python_version >= '3.7'",
-            "version": "==8.1.6"
+            "version": "==8.1.7"
         },
         "click-didyoumean": {
             "hashes": [
@@ -196,19 +196,19 @@
         },
         "exceptiongroup": {
             "hashes": [
-                "sha256:12c3e887d6485d16943a309616de20ae5582633e0a2eda17f4e10fd61c1e8af5",
-                "sha256:e346e69d186172ca7cf029c8c1d16235aa0e04035e5750b4b95039e65204328f"
+                "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9",
+                "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"
             ],
             "markers": "python_version < '3.11'",
-            "version": "==1.1.2"
+            "version": "==1.1.3"
         },
         "flask": {
             "hashes": [
-                "sha256:77fd4e1249d8c9923de34907236b747ced06e5467ecac1a7bb7115ae0e9670b0",
-                "sha256:8c2f9abd47a9e8df7f0c3f091ce9497d011dc3b31effcf4c85a6e2b50f4114ef"
+                "sha256:09c347a92aa7ff4a8e7f3206795f30d826654baf38b873d0744cd571ca609efc",
+                "sha256:f69fcd559dc907ed196ab9df0e48471709175e696d6e698dd4dbe940f96ce66b"
             ],
             "index": "pypi",
-            "version": "==2.3.2"
+            "version": "==2.3.3"
         },
         "flower": {
             "hashes": [
@@ -228,11 +228,11 @@
         },
         "humanize": {
             "hashes": [
-                "sha256:7ca0e43e870981fa684acb5b062deb307218193bca1a01f2b2676479df849b3a",
-                "sha256:df7c429c2d27372b249d3f26eb53b07b166b661326e0325793e0a988082e3889"
+                "sha256:8bc9e2bb9315e61ec06bf690151ae35aeb65651ab091266941edf97c90836404",
+                "sha256:9783373bf1eec713a770ecaa7c2d7a7902c98398009dfa3d8a2df91eec9311e8"
             ],
             "markers": "python_version >= '3.8'",
-            "version": "==4.7.0"
+            "version": "==4.8.0"
         },
         "idna": {
             "hashes": [
@@ -255,7 +255,7 @@
                 "sha256:134832a506243891221b88b4ae1213327eea96ceb4e407a00d790bb0626f45cf",
                 "sha256:4359457e42708462b9626a04657c6208ad799ceb41e5c58c57ffa0e6a098a5d4"
             ],
-            "markers": "python_version < '3.9' and python_version < '3.9'",
+            "markers": "python_version < '3.9'",
             "version": "==6.0.1"
         },
         "itsdangerous": {
@@ -402,8 +402,11 @@
                 "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e",
                 "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431",
                 "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686",
+                "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c",
                 "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559",
                 "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc",
+                "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb",
+                "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939",
                 "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c",
                 "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0",
                 "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4",
@@ -411,6 +414,7 @@
                 "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575",
                 "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba",
                 "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d",
+                "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd",
                 "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3",
                 "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00",
                 "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155",
@@ -419,6 +423,7 @@
                 "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f",
                 "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8",
                 "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b",
+                "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007",
                 "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24",
                 "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea",
                 "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198",
@@ -426,9 +431,12 @@
                 "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee",
                 "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be",
                 "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2",
+                "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1",
                 "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707",
                 "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6",
+                "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c",
                 "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58",
+                "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823",
                 "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779",
                 "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636",
                 "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c",
@@ -447,7 +455,9 @@
                 "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9",
                 "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57",
                 "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc",
-                "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"
+                "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc",
+                "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2",
+                "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"
             ],
             "markers": "python_version >= '3.7'",
             "version": "==2.1.3"
@@ -578,19 +588,21 @@
                 "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86",
                 "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"
             ],
-            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'",
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
             "version": "==2.8.2"
         },
         "pytz": {
             "hashes": [
-                "sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588",
-                "sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb"
+                "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b",
+                "sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7"
             ],
-            "version": "==2023.3"
+            "version": "==2023.3.post1"
         },
         "pyyaml": {
             "hashes": [
+                "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5",
                 "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc",
+                "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df",
                 "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741",
                 "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206",
                 "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27",
@@ -598,7 +610,10 @@
                 "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62",
                 "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98",
                 "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696",
+                "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290",
+                "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9",
                 "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d",
+                "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6",
                 "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867",
                 "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47",
                 "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486",
@@ -606,9 +621,12 @@
                 "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3",
                 "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007",
                 "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938",
+                "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0",
                 "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c",
                 "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735",
                 "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d",
+                "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28",
+                "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4",
                 "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba",
                 "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8",
                 "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5",
@@ -623,7 +641,9 @@
                 "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43",
                 "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859",
                 "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673",
+                "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54",
                 "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a",
+                "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b",
                 "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab",
                 "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa",
                 "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c",
@@ -636,10 +656,10 @@
         },
         "redis": {
             "hashes": [
-                "sha256:585dc516b9eb042a619ef0a39c3d7d55fe81bdb4df09a52c9cdde0d07bf1aa7d",
-                "sha256:e2b03db868160ee4591de3cb90d40ebb50a90dd302138775937f6a42b7ed183c"
+                "sha256:06570d0b2d84d46c21defc550afbaada381af82f5b83e5b3777600e05d8e2ed0",
+                "sha256:5cea6c0d335c9a7332a460ed8729ceabb4d0c489c7285b0a86dbbf8a017bd120"
             ],
-            "version": "==4.6.0"
+            "version": "==5.0.0"
         },
         "referencing": {
             "hashes": [
@@ -659,106 +679,106 @@
         },
         "rpds-py": {
             "hashes": [
-                "sha256:0173c0444bec0a3d7d848eaeca2d8bd32a1b43f3d3fde6617aac3731fa4be05f",
-                "sha256:01899794b654e616c8625b194ddd1e5b51ef5b60ed61baa7a2d9c2ad7b2a4238",
-                "sha256:02938432352359805b6da099c9c95c8a0547fe4b274ce8f1a91677401bb9a45f",
-                "sha256:03421628f0dc10a4119d714a17f646e2837126a25ac7a256bdf7c3943400f67f",
-                "sha256:03975db5f103997904c37e804e5f340c8fdabbb5883f26ee50a255d664eed58c",
-                "sha256:0766babfcf941db8607bdaf82569ec38107dbb03c7f0b72604a0b346b6eb3298",
-                "sha256:07e2c54bef6838fa44c48dfbc8234e8e2466d851124b551fc4e07a1cfeb37260",
-                "sha256:0836d71ca19071090d524739420a61580f3f894618d10b666cf3d9a1688355b1",
-                "sha256:095b460e117685867d45548fbd8598a8d9999227e9061ee7f012d9d264e6048d",
-                "sha256:0e7521f5af0233e89939ad626b15278c71b69dc1dfccaa7b97bd4cdf96536bb7",
-                "sha256:0f2996fbac8e0b77fd67102becb9229986396e051f33dbceada3debaacc7033f",
-                "sha256:1054a08e818f8e18910f1bee731583fe8f899b0a0a5044c6e680ceea34f93876",
-                "sha256:13b602dc3e8dff3063734f02dcf05111e887f301fdda74151a93dbbc249930fe",
-                "sha256:141acb9d4ccc04e704e5992d35472f78c35af047fa0cfae2923835d153f091be",
-                "sha256:14c408e9d1a80dcb45c05a5149e5961aadb912fff42ca1dd9b68c0044904eb32",
-                "sha256:159fba751a1e6b1c69244e23ba6c28f879a8758a3e992ed056d86d74a194a0f3",
-                "sha256:190ca6f55042ea4649ed19c9093a9be9d63cd8a97880106747d7147f88a49d18",
-                "sha256:196cb208825a8b9c8fc360dc0f87993b8b260038615230242bf18ec84447c08d",
-                "sha256:1fcdee18fea97238ed17ab6478c66b2095e4ae7177e35fb71fbe561a27adf620",
-                "sha256:207f57c402d1f8712618f737356e4b6f35253b6d20a324d9a47cb9f38ee43a6b",
-                "sha256:24a81c177379300220e907e9b864107614b144f6c2a15ed5c3450e19cf536fae",
-                "sha256:29cd8bfb2d716366a035913ced99188a79b623a3512292963d84d3e06e63b496",
-                "sha256:2d8b3b3a2ce0eaa00c5bbbb60b6713e94e7e0becab7b3db6c5c77f979e8ed1f1",
-                "sha256:35da5cc5cb37c04c4ee03128ad59b8c3941a1e5cd398d78c37f716f32a9b7f67",
-                "sha256:44659b1f326214950a8204a248ca6199535e73a694be8d3e0e869f820767f12f",
-                "sha256:47c5f58a8e0c2c920cc7783113df2fc4ff12bf3a411d985012f145e9242a2764",
-                "sha256:4bd4dc3602370679c2dfb818d9c97b1137d4dd412230cfecd3c66a1bf388a196",
-                "sha256:4ea6b73c22d8182dff91155af018b11aac9ff7eca085750455c5990cb1cfae6e",
-                "sha256:50025635ba8b629a86d9d5474e650da304cb46bbb4d18690532dd79341467846",
-                "sha256:517cbf6e67ae3623c5127206489d69eb2bdb27239a3c3cc559350ef52a3bbf0b",
-                "sha256:5855c85eb8b8a968a74dc7fb014c9166a05e7e7a8377fb91d78512900aadd13d",
-                "sha256:5a46859d7f947061b4010e554ccd1791467d1b1759f2dc2ec9055fa239f1bc26",
-                "sha256:65a0583c43d9f22cb2130c7b110e695fff834fd5e832a776a107197e59a1898e",
-                "sha256:674c704605092e3ebbbd13687b09c9f78c362a4bc710343efe37a91457123044",
-                "sha256:682726178138ea45a0766907957b60f3a1bf3acdf212436be9733f28b6c5af3c",
-                "sha256:686ba516e02db6d6f8c279d1641f7067ebb5dc58b1d0536c4aaebb7bf01cdc5d",
-                "sha256:6a5d3fbd02efd9cf6a8ffc2f17b53a33542f6b154e88dd7b42ef4a4c0700fdad",
-                "sha256:6aa8326a4a608e1c28da191edd7c924dff445251b94653988efb059b16577a4d",
-                "sha256:700375326ed641f3d9d32060a91513ad668bcb7e2cffb18415c399acb25de2ab",
-                "sha256:71f2f7715935a61fa3e4ae91d91b67e571aeb5cb5d10331ab681256bda2ad920",
-                "sha256:745f5a43fdd7d6d25a53ab1a99979e7f8ea419dfefebcab0a5a1e9095490ee5e",
-                "sha256:79f594919d2c1a0cc17d1988a6adaf9a2f000d2e1048f71f298b056b1018e872",
-                "sha256:7d68dc8acded354c972116f59b5eb2e5864432948e098c19fe6994926d8e15c3",
-                "sha256:7f67da97f5b9eac838b6980fc6da268622e91f8960e083a34533ca710bec8611",
-                "sha256:83b32f0940adec65099f3b1c215ef7f1d025d13ff947975a055989cb7fd019a4",
-                "sha256:876bf9ed62323bc7dcfc261dbc5572c996ef26fe6406b0ff985cbcf460fc8a4c",
-                "sha256:890ba852c16ace6ed9f90e8670f2c1c178d96510a21b06d2fa12d8783a905193",
-                "sha256:8b08605d248b974eb02f40bdcd1a35d3924c83a2a5e8f5d0fa5af852c4d960af",
-                "sha256:8b2eb034c94b0b96d5eddb290b7b5198460e2d5d0c421751713953a9c4e47d10",
-                "sha256:8b9ec12ad5f0a4625db34db7e0005be2632c1013b253a4a60e8302ad4d462afd",
-                "sha256:8c8d7594e38cf98d8a7df25b440f684b510cf4627fe038c297a87496d10a174f",
-                "sha256:8d3335c03100a073883857e91db9f2e0ef8a1cf42dc0369cbb9151c149dbbc1b",
-                "sha256:8d70e8f14900f2657c249ea4def963bed86a29b81f81f5b76b5a9215680de945",
-                "sha256:9039a11bca3c41be5a58282ed81ae422fa680409022b996032a43badef2a3752",
-                "sha256:91378d9f4151adc223d584489591dbb79f78814c0734a7c3bfa9c9e09978121c",
-                "sha256:9251eb8aa82e6cf88510530b29eef4fac825a2b709baf5b94a6094894f252387",
-                "sha256:933a7d5cd4b84f959aedeb84f2030f0a01d63ae6cf256629af3081cf3e3426e8",
-                "sha256:978fa96dbb005d599ec4fd9ed301b1cc45f1a8f7982d4793faf20b404b56677d",
-                "sha256:987b06d1cdb28f88a42e4fb8a87f094e43f3c435ed8e486533aea0bf2e53d931",
-                "sha256:99b1c16f732b3a9971406fbfe18468592c5a3529585a45a35adbc1389a529a03",
-                "sha256:99e7c4bb27ff1aab90dcc3e9d37ee5af0231ed98d99cb6f5250de28889a3d502",
-                "sha256:9c439fd54b2b9053717cca3de9583be6584b384d88d045f97d409f0ca867d80f",
-                "sha256:9ea4d00850ef1e917815e59b078ecb338f6a8efda23369677c54a5825dbebb55",
-                "sha256:9f30d205755566a25f2ae0382944fcae2f350500ae4df4e795efa9e850821d82",
-                "sha256:a06418fe1155e72e16dddc68bb3780ae44cebb2912fbd8bb6ff9161de56e1798",
-                "sha256:a0805911caedfe2736935250be5008b261f10a729a303f676d3d5fea6900c96a",
-                "sha256:a1f044792e1adcea82468a72310c66a7f08728d72a244730d14880cd1dabe36b",
-                "sha256:a216b26e5af0a8e265d4efd65d3bcec5fba6b26909014effe20cd302fd1138fa",
-                "sha256:a987578ac5214f18b99d1f2a3851cba5b09f4a689818a106c23dbad0dfeb760f",
-                "sha256:aad51239bee6bff6823bbbdc8ad85136c6125542bbc609e035ab98ca1e32a192",
-                "sha256:ab2299e3f92aa5417d5e16bb45bb4586171c1327568f638e8453c9f8d9e0f020",
-                "sha256:ab6919a09c055c9b092798ce18c6c4adf49d24d4d9e43a92b257e3f2548231e7",
-                "sha256:b0c43f8ae8f6be1d605b0465671124aa8d6a0e40f1fb81dcea28b7e3d87ca1e1",
-                "sha256:b1440c291db3f98a914e1afd9d6541e8fc60b4c3aab1a9008d03da4651e67386",
-                "sha256:b52e7c5ae35b00566d244ffefba0f46bb6bec749a50412acf42b1c3f402e2c90",
-                "sha256:bf4151acb541b6e895354f6ff9ac06995ad9e4175cbc6d30aaed08856558201f",
-                "sha256:c27ee01a6c3223025f4badd533bea5e87c988cb0ba2811b690395dfe16088cfe",
-                "sha256:c545d9d14d47be716495076b659db179206e3fd997769bc01e2d550eeb685596",
-                "sha256:c5934e2833afeaf36bd1eadb57256239785f5af0220ed8d21c2896ec4d3a765f",
-                "sha256:c7671d45530fcb6d5e22fd40c97e1e1e01965fc298cbda523bb640f3d923b387",
-                "sha256:c861a7e4aef15ff91233751619ce3a3d2b9e5877e0fcd76f9ea4f6847183aa16",
-                "sha256:d25b1c1096ef0447355f7293fbe9ad740f7c47ae032c2884113f8e87660d8f6e",
-                "sha256:d55777a80f78dd09410bd84ff8c95ee05519f41113b2df90a69622f5540c4f8b",
-                "sha256:d576c3ef8c7b2d560e301eb33891d1944d965a4d7a2eacb6332eee8a71827db6",
-                "sha256:dd9da77c6ec1f258387957b754f0df60766ac23ed698b61941ba9acccd3284d1",
-                "sha256:de0b6eceb46141984671802d412568d22c6bacc9b230174f9e55fc72ef4f57de",
-                "sha256:e07e5dbf8a83c66783a9fe2d4566968ea8c161199680e8ad38d53e075df5f0d0",
-                "sha256:e564d2238512c5ef5e9d79338ab77f1cbbda6c2d541ad41b2af445fb200385e3",
-                "sha256:ed89861ee8c8c47d6beb742a602f912b1bb64f598b1e2f3d758948721d44d468",
-                "sha256:ef1f08f2a924837e112cba2953e15aacfccbbfcd773b4b9b4723f8f2ddded08e",
-                "sha256:f411330a6376fb50e5b7a3e66894e4a39e60ca2e17dce258d53768fea06a37bd",
-                "sha256:f68996a3b3dc9335037f82754f9cdbe3a95db42bde571d8c3be26cc6245f2324",
-                "sha256:f7fdf55283ad38c33e35e2855565361f4bf0abd02470b8ab28d499c663bc5d7c",
-                "sha256:f963c6b1218b96db85fc37a9f0851eaf8b9040aa46dec112611697a7023da535",
-                "sha256:fa2818759aba55df50592ecbc95ebcdc99917fa7b55cc6796235b04193eb3c55",
-                "sha256:fae5cb554b604b3f9e2c608241b5d8d303e410d7dfb6d397c335f983495ce7f6",
-                "sha256:fb39aca7a64ad0c9490adfa719dbeeb87d13be137ca189d2564e596f8ba32c07"
+                "sha256:015de2ce2af1586ff5dc873e804434185199a15f7d96920ce67e50604592cae9",
+                "sha256:061c3ff1f51ecec256e916cf71cc01f9975af8fb3af9b94d3c0cc8702cfea637",
+                "sha256:08a80cf4884920863623a9ee9a285ee04cef57ebedc1cc87b3e3e0f24c8acfe5",
+                "sha256:09362f86ec201288d5687d1dc476b07bf39c08478cde837cb710b302864e7ec9",
+                "sha256:0bb4f48bd0dd18eebe826395e6a48b7331291078a879295bae4e5d053be50d4c",
+                "sha256:106af1653007cc569d5fbb5f08c6648a49fe4de74c2df814e234e282ebc06957",
+                "sha256:11fdd1192240dda8d6c5d18a06146e9045cb7e3ba7c06de6973000ff035df7c6",
+                "sha256:16a472300bc6c83fe4c2072cc22b3972f90d718d56f241adabc7ae509f53f154",
+                "sha256:176287bb998fd1e9846a9b666e240e58f8d3373e3bf87e7642f15af5405187b8",
+                "sha256:177914f81f66c86c012311f8c7f46887ec375cfcfd2a2f28233a3053ac93a569",
+                "sha256:177c9dd834cdf4dc39c27436ade6fdf9fe81484758885f2d616d5d03c0a83bd2",
+                "sha256:187700668c018a7e76e89424b7c1042f317c8df9161f00c0c903c82b0a8cac5c",
+                "sha256:1d9b5ee46dcb498fa3e46d4dfabcb531e1f2e76b477e0d99ef114f17bbd38453",
+                "sha256:22da15b902f9f8e267020d1c8bcfc4831ca646fecb60254f7bc71763569f56b1",
+                "sha256:24cd91a03543a0f8d09cb18d1cb27df80a84b5553d2bd94cba5979ef6af5c6e7",
+                "sha256:255f1a10ae39b52122cce26ce0781f7a616f502feecce9e616976f6a87992d6b",
+                "sha256:271c360fdc464fe6a75f13ea0c08ddf71a321f4c55fc20a3fe62ea3ef09df7d9",
+                "sha256:2ed83d53a8c5902ec48b90b2ac045e28e1698c0bea9441af9409fc844dc79496",
+                "sha256:2f3e1867dd574014253b4b8f01ba443b9c914e61d45f3674e452a915d6e929a3",
+                "sha256:35fbd23c1c8732cde7a94abe7fb071ec173c2f58c0bd0d7e5b669fdfc80a2c7b",
+                "sha256:37d0c59548ae56fae01c14998918d04ee0d5d3277363c10208eef8c4e2b68ed6",
+                "sha256:39d05e65f23a0fe897b6ac395f2a8d48c56ac0f583f5d663e0afec1da89b95da",
+                "sha256:3ad59efe24a4d54c2742929001f2d02803aafc15d6d781c21379e3f7f66ec842",
+                "sha256:3aed39db2f0ace76faa94f465d4234aac72e2f32b009f15da6492a561b3bbebd",
+                "sha256:3bbac1953c17252f9cc675bb19372444aadf0179b5df575ac4b56faaec9f6294",
+                "sha256:40bc802a696887b14c002edd43c18082cb7b6f9ee8b838239b03b56574d97f71",
+                "sha256:42f712b4668831c0cd85e0a5b5a308700fe068e37dcd24c0062904c4e372b093",
+                "sha256:448a66b8266de0b581246ca7cd6a73b8d98d15100fb7165974535fa3b577340e",
+                "sha256:485301ee56ce87a51ccb182a4b180d852c5cb2b3cb3a82f7d4714b4141119d8c",
+                "sha256:485747ee62da83366a44fbba963c5fe017860ad408ccd6cd99aa66ea80d32b2e",
+                "sha256:4cf0855a842c5b5c391dd32ca273b09e86abf8367572073bd1edfc52bc44446b",
+                "sha256:4eca20917a06d2fca7628ef3c8b94a8c358f6b43f1a621c9815243462dcccf97",
+                "sha256:4ed172d0c79f156c1b954e99c03bc2e3033c17efce8dd1a7c781bc4d5793dfac",
+                "sha256:5267cfda873ad62591b9332fd9472d2409f7cf02a34a9c9cb367e2c0255994bf",
+                "sha256:52b5cbc0469328e58180021138207e6ec91d7ca2e037d3549cc9e34e2187330a",
+                "sha256:53d7a3cd46cdc1689296348cb05ffd4f4280035770aee0c8ead3bbd4d6529acc",
+                "sha256:563646d74a4b4456d0cf3b714ca522e725243c603e8254ad85c3b59b7c0c4bf0",
+                "sha256:570cc326e78ff23dec7f41487aa9c3dffd02e5ee9ab43a8f6ccc3df8f9327623",
+                "sha256:5aca759ada6b1967fcfd4336dcf460d02a8a23e6abe06e90ea7881e5c22c4de6",
+                "sha256:5de11c041486681ce854c814844f4ce3282b6ea1656faae19208ebe09d31c5b8",
+                "sha256:5e271dd97c7bb8eefda5cca38cd0b0373a1fea50f71e8071376b46968582af9b",
+                "sha256:642ed0a209ced4be3a46f8cb094f2d76f1f479e2a1ceca6de6346a096cd3409d",
+                "sha256:6446002739ca29249f0beaaf067fcbc2b5aab4bc7ee8fb941bd194947ce19aff",
+                "sha256:691d50c99a937709ac4c4cd570d959a006bd6a6d970a484c84cc99543d4a5bbb",
+                "sha256:69b857a7d8bd4f5d6e0db4086da8c46309a26e8cefdfc778c0c5cc17d4b11e08",
+                "sha256:6ac3fefb0d168c7c6cab24fdfc80ec62cd2b4dfd9e65b84bdceb1cb01d385c33",
+                "sha256:6c9141af27a4e5819d74d67d227d5047a20fa3c7d4d9df43037a955b4c748ec5",
+                "sha256:7170cbde4070dc3c77dec82abf86f3b210633d4f89550fa0ad2d4b549a05572a",
+                "sha256:763ad59e105fca09705d9f9b29ecffb95ecdc3b0363be3bb56081b2c6de7977a",
+                "sha256:77076bdc8776a2b029e1e6ffbe6d7056e35f56f5e80d9dc0bad26ad4a024a762",
+                "sha256:7cd020b1fb41e3ab7716d4d2c3972d4588fdfbab9bfbbb64acc7078eccef8860",
+                "sha256:821392559d37759caa67d622d0d2994c7a3f2fb29274948ac799d496d92bca73",
+                "sha256:829e91f3a8574888b73e7a3feb3b1af698e717513597e23136ff4eba0bc8387a",
+                "sha256:850c272e0e0d1a5c5d73b1b7871b0a7c2446b304cec55ccdb3eaac0d792bb065",
+                "sha256:87d9b206b1bd7a0523375dc2020a6ce88bca5330682ae2fe25e86fd5d45cea9c",
+                "sha256:8bd01ff4032abaed03f2db702fa9a61078bee37add0bd884a6190b05e63b028c",
+                "sha256:8d54bbdf5d56e2c8cf81a1857250f3ea132de77af543d0ba5dce667183b61fec",
+                "sha256:8efaeb08ede95066da3a3e3c420fcc0a21693fcd0c4396d0585b019613d28515",
+                "sha256:8f94fdd756ba1f79f988855d948ae0bad9ddf44df296770d9a58c774cfbcca72",
+                "sha256:95cde244e7195b2c07ec9b73fa4c5026d4a27233451485caa1cd0c1b55f26dbd",
+                "sha256:975382d9aa90dc59253d6a83a5ca72e07f4ada3ae3d6c0575ced513db322b8ec",
+                "sha256:9dd9d9d9e898b9d30683bdd2b6c1849449158647d1049a125879cb397ee9cd12",
+                "sha256:a019a344312d0b1f429c00d49c3be62fa273d4a1094e1b224f403716b6d03be1",
+                "sha256:a4d9bfda3f84fc563868fe25ca160c8ff0e69bc4443c5647f960d59400ce6557",
+                "sha256:a657250807b6efd19b28f5922520ae002a54cb43c2401e6f3d0230c352564d25",
+                "sha256:a771417c9c06c56c9d53d11a5b084d1de75de82978e23c544270ab25e7c066ff",
+                "sha256:aad6ed9e70ddfb34d849b761fb243be58c735be6a9265b9060d6ddb77751e3e8",
+                "sha256:ae87137951bb3dc08c7d8bfb8988d8c119f3230731b08a71146e84aaa919a7a9",
+                "sha256:af247fd4f12cca4129c1b82090244ea5a9d5bb089e9a82feb5a2f7c6a9fe181d",
+                "sha256:b5d4bdd697195f3876d134101c40c7d06d46c6ab25159ed5cbd44105c715278a",
+                "sha256:b9255e7165083de7c1d605e818025e8860636348f34a79d84ec533546064f07e",
+                "sha256:c22211c165166de6683de8136229721f3d5c8606cc2c3d1562da9a3a5058049c",
+                "sha256:c55f9821f88e8bee4b7a72c82cfb5ecd22b6aad04033334f33c329b29bfa4da0",
+                "sha256:c7aed97f2e676561416c927b063802c8a6285e9b55e1b83213dfd99a8f4f9e48",
+                "sha256:cd2163f42868865597d89399a01aa33b7594ce8e2c4a28503127c81a2f17784e",
+                "sha256:ce5e7504db95b76fc89055c7f41e367eaadef5b1d059e27e1d6eabf2b55ca314",
+                "sha256:cff7351c251c7546407827b6a37bcef6416304fc54d12d44dbfecbb717064717",
+                "sha256:d27aa6bbc1f33be920bb7adbb95581452cdf23005d5611b29a12bb6a3468cc95",
+                "sha256:d3b52a67ac66a3a64a7e710ba629f62d1e26ca0504c29ee8cbd99b97df7079a8",
+                "sha256:de61e424062173b4f70eec07e12469edde7e17fa180019a2a0d75c13a5c5dc57",
+                "sha256:e10e6a1ed2b8661201e79dff5531f8ad4cdd83548a0f81c95cf79b3184b20c33",
+                "sha256:e1a0ffc39f51aa5f5c22114a8f1906b3c17eba68c5babb86c5f77d8b1bba14d1",
+                "sha256:e22491d25f97199fc3581ad8dd8ce198d8c8fdb8dae80dea3512e1ce6d5fa99f",
+                "sha256:e626b864725680cd3904414d72e7b0bd81c0e5b2b53a5b30b4273034253bb41f",
+                "sha256:e8c71ea77536149e36c4c784f6d420ffd20bea041e3ba21ed021cb40ce58e2c9",
+                "sha256:e8d0f0eca087630d58b8c662085529781fd5dc80f0a54eda42d5c9029f812599",
+                "sha256:ea65b59882d5fa8c74a23f8960db579e5e341534934f43f3b18ec1839b893e41",
+                "sha256:ea93163472db26ac6043e8f7f93a05d9b59e0505c760da2a3cd22c7dd7111391",
+                "sha256:eab75a8569a095f2ad470b342f2751d9902f7944704f0571c8af46bede438475",
+                "sha256:ed8313809571a5463fd7db43aaca68ecb43ca7a58f5b23b6e6c6c5d02bdc7882",
+                "sha256:ef5fddfb264e89c435be4adb3953cef5d2936fdeb4463b4161a6ba2f22e7b740",
+                "sha256:ef750a20de1b65657a1425f77c525b0183eac63fe7b8f5ac0dd16f3668d3e64f",
+                "sha256:efb9ece97e696bb56e31166a9dd7919f8f0c6b31967b454718c6509f29ef6fee",
+                "sha256:f4c179a7aeae10ddf44c6bac87938134c1379c49c884529f090f9bf05566c836",
+                "sha256:f602881d80ee4228a2355c68da6b296a296cd22bbb91e5418d54577bbf17fa7c",
+                "sha256:fc2200e79d75b5238c8d69f6a30f8284290c777039d331e7340b6c17cad24a5a",
+                "sha256:fcc1ebb7561a3e24a6588f7c6ded15d80aec22c66a070c757559b57b17ffd1cb"
             ],
             "markers": "python_version >= '3.8'",
-            "version": "==0.9.2"
+            "version": "==0.10.3"
         },
         "selenium": {
             "hashes": [
@@ -772,7 +792,7 @@
                 "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
                 "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
             ],
-            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'",
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
             "version": "==1.16.0"
         },
         "sniffio": {
@@ -792,20 +812,20 @@
         },
         "tornado": {
             "hashes": [
-                "sha256:05615096845cf50a895026f749195bf0b10b8909f9be672f50b0fe69cba368e4",
-                "sha256:0c325e66c8123c606eea33084976c832aa4e766b7dff8aedd7587ea44a604cdf",
-                "sha256:29e71c847a35f6e10ca3b5c2990a52ce38b233019d8e858b755ea6ce4dcdd19d",
-                "sha256:4b927c4f19b71e627b13f3db2324e4ae660527143f9e1f2e2fb404f3a187e2ba",
-                "sha256:5b17b1cf5f8354efa3d37c6e28fdfd9c1c1e5122f2cb56dac121ac61baa47cbe",
-                "sha256:6a0848f1aea0d196a7c4f6772197cbe2abc4266f836b0aac76947872cd29b411",
-                "sha256:7efcbcc30b7c654eb6a8c9c9da787a851c18f8ccd4a5a3a95b05c7accfa068d2",
-                "sha256:834ae7540ad3a83199a8da8f9f2d383e3c3d5130a328889e4cc991acc81e87a0",
-                "sha256:b46a6ab20f5c7c1cb949c72c1994a4585d2eaa0be4853f50a03b5031e964fc7c",
-                "sha256:c2de14066c4a38b4ecbbcd55c5cc4b5340eb04f1c5e81da7451ef555859c833f",
-                "sha256:c367ab6c0393d71171123ca5515c61ff62fe09024fa6bf299cd1339dc9456829"
+                "sha256:1bd19ca6c16882e4d37368e0152f99c099bad93e0950ce55e71daed74045908f",
+                "sha256:22d3c2fa10b5793da13c807e6fc38ff49a4f6e1e3868b0a6f4164768bb8e20f5",
+                "sha256:502fba735c84450974fec147340016ad928d29f1e91f49be168c0a4c18181e1d",
+                "sha256:65ceca9500383fbdf33a98c0087cb975b2ef3bfb874cb35b8de8740cf7f41bd3",
+                "sha256:71a8db65160a3c55d61839b7302a9a400074c9c753040455494e2af74e2501f2",
+                "sha256:7ac51f42808cca9b3613f51ffe2a965c8525cb1b00b7b2d56828b8045354f76a",
+                "sha256:7d01abc57ea0dbb51ddfed477dfe22719d376119844e33c661d873bf9c0e4a16",
+                "sha256:805d507b1f588320c26f7f097108eb4023bbaa984d63176d1652e184ba24270a",
+                "sha256:9dc4444c0defcd3929d5c1eb5706cbe1b116e762ff3e0deca8b715d14bf6ec17",
+                "sha256:ceb917a50cd35882b57600709dd5421a418c29ddc852da8bcdab1f0db33406b0",
+                "sha256:e7d8db41c0181c80d76c982aacc442c0783a2c54d6400fe028954201a2e032fe"
             ],
             "markers": "python_version >= '3.8'",
-            "version": "==6.3.2"
+            "version": "==6.3.3"
         },
         "trio": {
             "hashes": [
@@ -817,11 +837,11 @@
         },
         "trio-websocket": {
             "hashes": [
-                "sha256:1a748604ad906a7dcab9a43c6eb5681e37de4793ba0847ef0bc9486933ed027b",
-                "sha256:a9937d48e8132ebf833019efde2a52ca82d223a30a7ea3e8d60a7d28f75a4e3a"
+                "sha256:c7a620c4013c34b7e4477d89fe76695da1e455e4510a8d7ae13f81c632bdce1d",
+                "sha256:e66b3db3e2453017431dfbd352081006654e1241c2a6800dc2f43d7df54d55c5"
             ],
             "markers": "python_version >= '3.7'",
-            "version": "==0.10.3"
+            "version": "==0.10.4"
         },
         "unidecode": {
             "hashes": [
@@ -859,11 +879,11 @@
         },
         "werkzeug": {
             "hashes": [
-                "sha256:935539fa1413afbb9195b24880778422ed620c0fc09670945185cce4d91a8890",
-                "sha256:98c774df2f91b05550078891dee5f0eb0cb797a522c757a2452b9cee5b202330"
+                "sha256:2b8c0e447b4b9dbcc85dd97b6eeb4dcbaf6c8b6c3be0bd654e25553e0a2157d8",
+                "sha256:effc12dba7f3bd72e605ce49807bbe692bd729c3bb122a3b91747a6ae77df528"
             ],
             "markers": "python_version >= '3.8'",
-            "version": "==2.3.6"
+            "version": "==2.3.7"
         },
         "wsproto": {
             "hashes": [
@@ -893,11 +913,11 @@
         },
         "autopep8": {
             "hashes": [
-                "sha256:86e9303b5e5c8160872b2f5ef611161b2893e9bfe8ccc7e2f76385947d57a2f1",
-                "sha256:f9849cdd62108cb739dbcdbfb7fdcc9a30d1b63c4cc3e1c1f893b5360941b61c"
+                "sha256:067959ca4a07b24dbd5345efa8325f5f58da4298dab0dde0443d5ed765de80cb",
+                "sha256:2913064abd97b3419d1cc83ea71f042cb821f87e45b9c88cad5ad3c4ea87fe0c"
             ],
             "index": "pypi",
-            "version": "==2.0.2"
+            "version": "==2.0.4"
         },
         "dill": {
             "hashes": [
@@ -909,11 +929,11 @@
         },
         "exceptiongroup": {
             "hashes": [
-                "sha256:12c3e887d6485d16943a309616de20ae5582633e0a2eda17f4e10fd61c1e8af5",
-                "sha256:e346e69d186172ca7cf029c8c1d16235aa0e04035e5750b4b95039e65204328f"
+                "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9",
+                "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"
             ],
             "markers": "python_version < '3.11'",
-            "version": "==1.1.2"
+            "version": "==1.1.3"
         },
         "flake8": {
             "hashes": [
@@ -939,6 +959,13 @@
             "markers": "python_version >= '3.8'",
             "version": "==5.12.0"
         },
+        "jsonpath-ng": {
+            "hashes": [
+                "sha256:5483f8e9d74c39c9abfab554c070ae783c1c8cbadf5df60d561bc705ac68a07e"
+            ],
+            "index": "pypi",
+            "version": "==1.6.0"
+        },
         "lazy-object-proxy": {
             "hashes": [
                 "sha256:09763491ce220c0299688940f8dc2c5d05fd1f45af1e42e636b2e8b2303e4382",
@@ -1069,11 +1096,18 @@
         },
         "pluggy": {
             "hashes": [
-                "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849",
-                "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3"
+                "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12",
+                "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"
             ],
-            "markers": "python_version >= '3.7'",
-            "version": "==1.2.0"
+            "markers": "python_version >= '3.8'",
+            "version": "==1.3.0"
+        },
+        "ply": {
+            "hashes": [
+                "sha256:00c7c1aaa88358b9c765b6d3000c6eec0ba42abca5351b095321aef446081da3",
+                "sha256:096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce"
+            ],
+            "version": "==3.11"
         },
         "pycodestyle": {
             "hashes": [
@@ -1101,11 +1135,11 @@
         },
         "pytest": {
             "hashes": [
-                "sha256:78bf16451a2eb8c7a2ea98e32dc119fd2aa758f1d5d66dbf0a59d69a3969df32",
-                "sha256:b4bf8c45bd59934ed84001ad51e11b4ee40d40a1229d2c79f9c592b0a3f6bd8a"
+                "sha256:1d881c6124e08ff0a1bb75ba3ec0bfd8b5354a01c194ddd5a0a870a48d99b002",
+                "sha256:a766259cfab564a2ad52cb1aae1b881a75c3eb7e34ca3779697c23ed47c47069"
             ],
             "index": "pypi",
-            "version": "==7.4.0"
+            "version": "==7.4.2"
         },
         "pytest-rerunfailures": {
             "hashes": [
@@ -1125,7 +1159,9 @@
         },
         "pyyaml": {
             "hashes": [
+                "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5",
                 "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc",
+                "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df",
                 "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741",
                 "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206",
                 "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27",
@@ -1133,7 +1169,10 @@
                 "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62",
                 "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98",
                 "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696",
+                "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290",
+                "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9",
                 "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d",
+                "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6",
                 "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867",
                 "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47",
                 "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486",
@@ -1141,9 +1180,12 @@
                 "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3",
                 "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007",
                 "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938",
+                "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0",
                 "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c",
                 "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735",
                 "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d",
+                "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28",
+                "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4",
                 "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba",
                 "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8",
                 "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5",
@@ -1158,7 +1200,9 @@
                 "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43",
                 "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859",
                 "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673",
+                "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54",
                 "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a",
+                "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b",
                 "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab",
                 "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa",
                 "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c",
@@ -1174,7 +1218,7 @@
                 "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc",
                 "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"
             ],
-            "markers": "python_version < '3.11' and python_version < '3.11'",
+            "markers": "python_version < '3.11'",
             "version": "==2.0.1"
         },
         "tomlkit": {
@@ -1190,7 +1234,7 @@
                 "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36",
                 "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"
             ],
-            "markers": "python_version < '3.11' and python_version < '3.10'",
+            "markers": "python_version < '3.11'",
             "version": "==4.7.1"
         },
         "watchdog": {

From c359d58ec23f79dc486c172bb4db43d05031fcdf Mon Sep 17 00:00:00 2001
From: Jiri Kozel <jirik@users.noreply.github.com>
Date: Mon, 18 Sep 2023 07:54:23 +0200
Subject: [PATCH 03/14] Enable to use base test class without
 pytest_generate_tests method

Usage is in the next commit
---
 tests/dynamic_data/base_test.py | 15 ++++++++-------
 1 file changed, 8 insertions(+), 7 deletions(-)

diff --git a/tests/dynamic_data/base_test.py b/tests/dynamic_data/base_test.py
index 2a705fdb8..52a10331c 100644
--- a/tests/dynamic_data/base_test.py
+++ b/tests/dynamic_data/base_test.py
@@ -263,11 +263,12 @@ def function_cleanup(self, request):
 
     @pytest.fixture(scope='function', autouse=True)
     def post_before_test(self, request):
-        publication, method, post_before_test_args = request.param
-        assert self.post_before_test_scope in {'function', 'class'}
-        if method.name != RestMethod.POST.name:
-            if self.post_before_test_scope == 'function':
-                self.post_publication(publication, args=post_before_test_args)
-            else:
-                self.ensure_publication(publication, args=post_before_test_args, scope='class')
+        if hasattr(request, 'param'):
+            publication, method, post_before_test_args = request.param
+            assert self.post_before_test_scope in {'function', 'class'}
+            if method.name != RestMethod.POST.name:
+                if self.post_before_test_scope == 'function':
+                    self.post_publication(publication, args=post_before_test_args)
+                else:
+                    self.ensure_publication(publication, args=post_before_test_args, scope='class')
         yield

From 28725337b5eb382eb983cb5d2875e316223a0ef2 Mon Sep 17 00:00:00 2001
From: Jiri Kozel <jirik@users.noreply.github.com>
Date: Mon, 18 Sep 2023 07:56:24 +0200
Subject: [PATCH 04/14] Adjust url and protocol.url in GET Workspace Map File

---
 src/layman/map/prime_db_schema/tasks.py       |  2 +-
 src/layman/map/rest_workspace_map_file.py     |  6 +-
 src/layman/map/util.py                        | 33 ++++++-
 src/layman/upgrade/upgrade_v1_16.py           |  2 +-
 test_tools/process_client.py                  | 17 ++++
 .../internal_wms_and_wfs.json                 | 91 +++++++++++++++++++
 .../x_forwarded_prefix/map_file_test.py       | 55 +++++++++++
 7 files changed, 201 insertions(+), 5 deletions(-)
 create mode 100644 tests/dynamic_data/publications/x_forwarded_prefix/internal_wms_and_wfs.json
 create mode 100644 tests/dynamic_data/publications/x_forwarded_prefix/map_file_test.py

diff --git a/src/layman/map/prime_db_schema/tasks.py b/src/layman/map/prime_db_schema/tasks.py
index 6e05cdc66..a04b189a8 100644
--- a/src/layman/map/prime_db_schema/tasks.py
+++ b/src/layman/map/prime_db_schema/tasks.py
@@ -26,7 +26,7 @@ def refresh_file_data(
     if self.is_aborted():
         raise AbortedException
 
-    mapjson = util.get_map_file_json(workspace, mapname)
+    mapjson = util.get_map_file_json(workspace, mapname, adjust_urls=False)
     native_bbox = util.get_native_bbox_from_json(mapjson)
     crs = util.get_crs_from_json(mapjson)
     set_bbox(workspace, MAP_TYPE, mapname, native_bbox, crs, )
diff --git a/src/layman/map/rest_workspace_map_file.py b/src/layman/map/rest_workspace_map_file.py
index 0aab8a157..f29938227 100644
--- a/src/layman/map/rest_workspace_map_file.py
+++ b/src/layman/map/rest_workspace_map_file.py
@@ -1,4 +1,4 @@
-from flask import Blueprint, jsonify, current_app as app, g
+from flask import Blueprint, jsonify, current_app as app, g, request
 
 from layman import LaymanError, util as layman_util
 from layman.util import check_workspace_name_decorator
@@ -28,7 +28,9 @@ def after_request(response):
 def get(workspace, mapname):
     app.logger.info(f"GET Map File, actor={g.user}")
 
-    map_json = util.get_map_file_json(workspace, mapname)
+    x_forwarded_prefix = layman_util.get_x_forwarded_prefix(request.headers)
+
+    map_json = util.get_map_file_json(workspace, mapname, x_forwarded_prefix=x_forwarded_prefix)
 
     if map_json is not None:
         return jsonify(map_json), 200
diff --git a/src/layman/map/util.py b/src/layman/map/util.py
index f8d7c2826..dd60b6b97 100644
--- a/src/layman/map/util.py
+++ b/src/layman/map/util.py
@@ -3,6 +3,7 @@
 import os
 import re
 import subprocess
+from urllib.parse import urlparse
 import requests
 from jsonschema import validate, Draft7Validator
 from flask import current_app, request
@@ -338,8 +339,38 @@ def get_same_or_missing_prop_names(workspace, mapname):
     return metadata_common.get_same_or_missing_prop_names(prop_names, md_comparison)
 
 
-def get_map_file_json(workspace, mapname):
+def _adjust_url(*, url_obj, url_key, proxy_prefix):
+    url_str = url_obj.get(url_key, '')
+    if not url_str:
+        return
+    gs_path_pattern = r'^' + layman_util.CLIENT_PROXY_ONLY_PATTERN + \
+                      re.escape(layman_settings.LAYMAN_GS_PATH) + r'(?P<path_postfix>.*)$'
+    parsed_url = urlparse(url_str)
+    match = re.match(gs_path_pattern, parsed_url.path)
+    if match:
+        # replace scheme and netloc, because LAYMAN_PUBLIC_URL_SCHEME or LAYMAN_PROXY_SERVER_NAME may have changed
+        new_url = parsed_url._replace(
+            scheme=settings.LAYMAN_PUBLIC_URL_SCHEME,
+            netloc=settings.LAYMAN_PROXY_SERVER_NAME,
+            path=proxy_prefix + layman_settings.LAYMAN_GS_PATH + match.group('path_postfix'),
+        )
+        url_obj[url_key] = new_url.geturl()
+
+
+def get_map_file_json(workspace, mapname, *, adjust_urls=True, x_forwarded_prefix=None):
+    x_forwarded_prefix = x_forwarded_prefix or ''
     map_json = input_file.get_map_json(workspace, mapname)
+
+    if adjust_urls:
+        input_file.unquote_urls(map_json)
+        map_layers = layman_util.get_publication_info(workspace, MAP_TYPE, mapname, context={'keys': ['map_layers']})['_map_layers']
+        ml_indices = {ml['index'] for ml in map_layers}
+
+        for ml_idx in ml_indices:
+            map_layer = map_json['layers'][ml_idx]
+            _adjust_url(url_obj=map_layer, url_key='url', proxy_prefix=x_forwarded_prefix)
+            _adjust_url(url_obj=map_layer.get('protocol', {}), url_key='url', proxy_prefix=x_forwarded_prefix)
+
     if map_json is not None:
         map_json['user'] = get_map_owner_info(workspace)
         map_json.pop("groups", None)
diff --git a/src/layman/upgrade/upgrade_v1_16.py b/src/layman/upgrade/upgrade_v1_16.py
index 1e040b8c8..e24bfead1 100644
--- a/src/layman/upgrade/upgrade_v1_16.py
+++ b/src/layman/upgrade/upgrade_v1_16.py
@@ -58,7 +58,7 @@ def adjust_maps():
     for workspace, publication in publications:
         logger.info(f'      Adjusting {workspace}.{publication}')
         map_file_path = input_file.get_map_file(workspace, publication)
-        mapjson = map_util.get_map_file_json(workspace, publication)
+        mapjson = map_util.get_map_file_json(workspace, publication, adjust_urls=False)
         bbox_json = map_util.get_bbox_from_json(mapjson)
         crs = map_util.get_crs_from_json(mapjson)
         assert crs in settings.INPUT_SRS_LIST
diff --git a/test_tools/process_client.py b/test_tools/process_client.py
index d03b887d4..91fbf643f 100644
--- a/test_tools/process_client.py
+++ b/test_tools/process_client.py
@@ -675,3 +675,20 @@ def patch_after_feature_change(workspace, publ_type, name):
     queue = LAYMAN_CELERY_QUEUE
     with app.app_context():
         layman_util.patch_after_feature_change(workspace, publ_type, name, queue=queue)
+
+
+def get_workspace_map_file(publication_type, workspace, name, headers=None, actor_name=None):
+    headers = headers or {}
+    assert publication_type == MAP_TYPE
+    publication_type_def = PUBLICATION_TYPES_DEF[publication_type]
+    if actor_name:
+        assert TOKEN_HEADER not in headers
+
+    if actor_name and actor_name != settings.ANONYM_USER:
+        headers.update(get_authz_headers(actor_name))
+
+    with app.app_context():
+        r_url = url_for('rest_workspace_map_file.get', **{publication_type_def.url_param_name: name}, workspace=workspace)
+    response = requests.get(r_url, headers=headers, timeout=HTTP_TIMEOUT)
+    raise_layman_error(response)
+    return response.json()
diff --git a/tests/dynamic_data/publications/x_forwarded_prefix/internal_wms_and_wfs.json b/tests/dynamic_data/publications/x_forwarded_prefix/internal_wms_and_wfs.json
new file mode 100644
index 000000000..6d3068721
--- /dev/null
+++ b/tests/dynamic_data/publications/x_forwarded_prefix/internal_wms_and_wfs.json
@@ -0,0 +1,91 @@
+{
+  "describedBy": "https://raw.githubusercontent.com/hslayers/map-compositions/2.0.0/schema.json",
+  "schema_version": "2.0.0",
+  "abstract": "World places and boundaries abstract",
+  "title": "World places and boundaries",
+  "extent": [
+    -35.0,
+    -48.5,
+    179,
+    81.5
+  ],
+  "nativeExtent": [
+    -3896182.18,
+    -6190443.81,
+    19926188.85,
+    16579785.82
+  ],
+  "projection": "epsg:3857",
+  "layers": [
+    {
+      "metadata": {},
+      "visibility": true,
+      "opacity": 1,
+      "title": "Defini\u010dn\u00ed body administrativn\u00edch celk\u016f",
+      "className": "HSLayers.Layer.WMS",
+      "singleTile": true,
+      "wmsMaxScale": 0,
+      "legends": [
+        "https://geoportal.kraj-lbc.cz/cgi-bin/mapserv?map=/data/gis/MapServer/projects/wms/atlas/administrativni_cleneni.map&version=1.3.0&service=WMS&request=GetLegendGraphic&sld_version=1.1.0&layer=definicni_body_administrativnich_celku&format=image/png&STYLE=default"
+      ],
+      "maxResolution": null,
+      "minResolution": 0,
+      "url": "https://geoportal.kraj-lbc.cz/cgi-bin/mapserv?map=/data/gis/MapServer/projects/wms/atlas/administrativni_cleneni.map&",
+      "params": {
+        "LAYERS": "definicni_body_administrativnich_celku",
+        "INFO_FORMAT": "application\/vnd.ogc.gml",
+        "FORMAT": "image\/png",
+        "FROMCRS": "EPSG:3857",
+        "VERSION": "1.3.0"
+      },
+      "dimensions": {}
+    },
+    {
+      "metadata": {},
+      "visibility": true,
+      "opacity": 1,
+      "title": "Hranice",
+      "className": "HSLayers.Layer.WMS",
+      "singleTile": true,
+      "url": "http://localhost:8000/client-proxy-365/geoserver/ows",
+      "params": {
+        "LAYERS": "map_file_workspace_wms:hranice",
+        "FORMAT": "image\/png"
+      }
+    },
+    {
+      "metadata": {},
+      "visibility": true,
+      "opacity": 1,
+      "title": "Mista",
+      "className": "HSLayers.Layer.WMS",
+      "singleTile": true,
+      "url": "http://localhost:8000/geoserver/map_file_workspace_wms/ows",
+      "params": {
+        "LAYERS": "mista",
+        "FORMAT": "image\/png"
+      }
+    },
+    {
+      "className": "OpenLayers.Layer.Vector",
+      "dimensions": {},
+      "legends": [
+        ""
+      ],
+      "maxResolution": null,
+      "metadata": {},
+      "minResolution": 0,
+      "name": "hranice",
+      "opacity": 1,
+      "protocol": {
+        "format": "hs.format.WFS",
+        "url": "http://localhost:8000/geoserver/map_file_workspace_workspace/wfs"
+      },
+      "ratio": 1.5,
+      "singleTile": true,
+      "title": "Hranice",
+      "visibility": false,
+      "wmsMaxScale": 0
+    }
+  ]
+}
diff --git a/tests/dynamic_data/publications/x_forwarded_prefix/map_file_test.py b/tests/dynamic_data/publications/x_forwarded_prefix/map_file_test.py
new file mode 100644
index 000000000..fefd32d75
--- /dev/null
+++ b/tests/dynamic_data/publications/x_forwarded_prefix/map_file_test.py
@@ -0,0 +1,55 @@
+import logging
+import os
+import pytest
+
+import jsonpath_ng as jp
+from layman import settings
+from test_tools import process_client
+from tests import Publication
+from tests.dynamic_data import base_test
+
+logger = logging.getLogger(__name__)
+
+DIRECTORY = os.path.dirname(os.path.abspath(__file__))
+
+WORKSPACE = 'map_file_workspace'
+
+LAYER_HRANICE = Publication(WORKSPACE, process_client.LAYER_TYPE, 'hranice')
+MAP = Publication(WORKSPACE, process_client.MAP_TYPE, 'map_hranice')
+
+
+@pytest.mark.usefixtures('oauth2_provider_mock')
+class TestPublication(base_test.TestSingleRestPublication):
+    workspace = WORKSPACE
+    publication_type = process_client.MAP_TYPE
+
+    rest_parametrization = []
+
+    def before_class(self):
+        self.post_publication(MAP, args={
+            'file_paths': [os.path.join(DIRECTORY, 'internal_wms_and_wfs.json')],
+        }, scope='class')
+
+    @pytest.mark.parametrize('headers', [
+        pytest.param({}, id='no-client-proxy'),
+        pytest.param({'X-Forwarded-Prefix': ''}, id='empty-client-proxy'),
+        pytest.param({'X-Forwarded-Prefix': '/some-client-proxy'}, id='some-client-proxy'),
+    ])
+    def test_x_forwarded_prefix(self, headers):
+        map = MAP
+        resp = process_client.get_workspace_map_file(map.type, map.workspace, map.name, headers=headers)
+
+        exp_adjusted_urls = [
+            ('$.layers[1].url', '/geoserver/ows'),
+            ('$.layers[2].url', '/geoserver/map_file_workspace_wms/ows'),
+            ('$.layers[3].protocol.url', '/geoserver/map_file_workspace_workspace/wfs'),
+        ]
+
+        x_forwarded_prefix = headers.get('X-Forwarded-Prefix', '')
+        for json_path_str, exp_url_postfix in exp_adjusted_urls:
+            json_path_expr = jp.parse(json_path_str)
+            values = [m.value for m in json_path_expr.find(resp)]
+            assert len(values) == 1, f"key={json_path_str}"
+            found_value = values[0]
+            exp_value = f"http://{settings.LAYMAN_PROXY_SERVER_NAME}{x_forwarded_prefix}{exp_url_postfix}"
+            assert found_value == exp_value, f"key={json_path_str}"

From c34ea274eb75ad04192f3e2feb080f0bcedb6154 Mon Sep 17 00:00:00 2001
From: Jiri Kozel <jirik@users.noreply.github.com>
Date: Mon, 18 Sep 2023 10:45:27 +0200
Subject: [PATCH 05/14] Assert also unchanged URLs

---
 .../x_forwarded_prefix/map_file_test.py       | 28 +++++++++++++++----
 1 file changed, 23 insertions(+), 5 deletions(-)

diff --git a/tests/dynamic_data/publications/x_forwarded_prefix/map_file_test.py b/tests/dynamic_data/publications/x_forwarded_prefix/map_file_test.py
index fefd32d75..c09b3fec7 100644
--- a/tests/dynamic_data/publications/x_forwarded_prefix/map_file_test.py
+++ b/tests/dynamic_data/publications/x_forwarded_prefix/map_file_test.py
@@ -1,3 +1,4 @@
+import json
 import logging
 import os
 import pytest
@@ -16,6 +17,14 @@
 
 LAYER_HRANICE = Publication(WORKSPACE, process_client.LAYER_TYPE, 'hranice')
 MAP = Publication(WORKSPACE, process_client.MAP_TYPE, 'map_hranice')
+MAP_FILE_PATH = os.path.join(DIRECTORY, 'internal_wms_and_wfs.json')
+
+
+def _find_single_value_in_json(json_path_str, json_obj):
+    json_path_expr = jp.parse(json_path_str)
+    values = [m.value for m in json_path_expr.find(json_obj)]
+    assert len(values) == 1, f"key={json_path_str}"
+    return values[0]
 
 
 @pytest.mark.usefixtures('oauth2_provider_mock')
@@ -27,7 +36,7 @@ class TestPublication(base_test.TestSingleRestPublication):
 
     def before_class(self):
         self.post_publication(MAP, args={
-            'file_paths': [os.path.join(DIRECTORY, 'internal_wms_and_wfs.json')],
+            'file_paths': [MAP_FILE_PATH],
         }, scope='class')
 
     @pytest.mark.parametrize('headers', [
@@ -47,9 +56,18 @@ def test_x_forwarded_prefix(self, headers):
 
         x_forwarded_prefix = headers.get('X-Forwarded-Prefix', '')
         for json_path_str, exp_url_postfix in exp_adjusted_urls:
-            json_path_expr = jp.parse(json_path_str)
-            values = [m.value for m in json_path_expr.find(resp)]
-            assert len(values) == 1, f"key={json_path_str}"
-            found_value = values[0]
+            found_value = _find_single_value_in_json(json_path_str, resp)
             exp_value = f"http://{settings.LAYMAN_PROXY_SERVER_NAME}{x_forwarded_prefix}{exp_url_postfix}"
             assert found_value == exp_value, f"key={json_path_str}"
+
+        with open(MAP_FILE_PATH, encoding='utf-8') as file:
+            orig_json = json.load(file)
+
+        exp_unchanged_urls = [
+            '$.layers[0].url',
+            '$.layers[0].legends[0]',
+        ]
+        for json_path_str in exp_unchanged_urls:
+            exp_url = _find_single_value_in_json(json_path_str, orig_json)
+            found_value = _find_single_value_in_json(json_path_str, resp)
+            assert found_value == exp_url, f"key={json_path_str}"

From 588387843220928e01a83e748dcd7f1f1c021f32 Mon Sep 17 00:00:00 2001
From: Jiri Kozel <jirik@users.noreply.github.com>
Date: Mon, 18 Sep 2023 13:27:27 +0200
Subject: [PATCH 06/14] _adjust_url works also for lists

---
 src/layman/map/util.py | 12 +++++++++---
 1 file changed, 9 insertions(+), 3 deletions(-)

diff --git a/src/layman/map/util.py b/src/layman/map/util.py
index dd60b6b97..8ac01ebe2 100644
--- a/src/layman/map/util.py
+++ b/src/layman/map/util.py
@@ -339,8 +339,11 @@ def get_same_or_missing_prop_names(workspace, mapname):
     return metadata_common.get_same_or_missing_prop_names(prop_names, md_comparison)
 
 
-def _adjust_url(*, url_obj, url_key, proxy_prefix):
-    url_str = url_obj.get(url_key, '')
+def _adjust_url(*, url_obj=None, url_key=None, url_list=None, url_idx=None, proxy_prefix):
+    assert (url_obj is None) == (url_key is None)
+    assert (url_list is None) == (url_idx is None)
+    assert (url_obj is None) != (url_list is None)
+    url_str = url_obj.get(url_key, '') if url_obj is not None else url_list[url_idx]
     if not url_str:
         return
     gs_path_pattern = r'^' + layman_util.CLIENT_PROXY_ONLY_PATTERN + \
@@ -354,7 +357,10 @@ def _adjust_url(*, url_obj, url_key, proxy_prefix):
             netloc=settings.LAYMAN_PROXY_SERVER_NAME,
             path=proxy_prefix + layman_settings.LAYMAN_GS_PATH + match.group('path_postfix'),
         )
-        url_obj[url_key] = new_url.geturl()
+        if url_obj is not None:
+            url_obj[url_key] = new_url.geturl()
+        else:
+            url_list[url_idx] = new_url.geturl()
 
 
 def get_map_file_json(workspace, mapname, *, adjust_urls=True, x_forwarded_prefix=None):

From 8908a04e035f40083993950f58d24c5d690c9e25 Mon Sep 17 00:00:00 2001
From: Jiri Kozel <jirik@users.noreply.github.com>
Date: Mon, 18 Sep 2023 13:49:07 +0200
Subject: [PATCH 07/14] Adjust legends[*] in GET Workspace Map File

---
 src/layman/map/util.py                           | 16 +++++++++++++---
 .../x_forwarded_prefix/internal_wms_and_wfs.json |  6 +++++-
 .../x_forwarded_prefix/map_file_test.py          |  2 ++
 3 files changed, 20 insertions(+), 4 deletions(-)

diff --git a/src/layman/map/util.py b/src/layman/map/util.py
index 8ac01ebe2..36e29ead2 100644
--- a/src/layman/map/util.py
+++ b/src/layman/map/util.py
@@ -345,11 +345,12 @@ def _adjust_url(*, url_obj=None, url_key=None, url_list=None, url_idx=None, prox
     assert (url_obj is None) != (url_list is None)
     url_str = url_obj.get(url_key, '') if url_obj is not None else url_list[url_idx]
     if not url_str:
-        return
+        return None
     gs_path_pattern = r'^' + layman_util.CLIENT_PROXY_ONLY_PATTERN + \
                       re.escape(layman_settings.LAYMAN_GS_PATH) + r'(?P<path_postfix>.*)$'
     parsed_url = urlparse(url_str)
     match = re.match(gs_path_pattern, parsed_url.path)
+    found_original_base_url = None
     if match:
         # replace scheme and netloc, because LAYMAN_PUBLIC_URL_SCHEME or LAYMAN_PROXY_SERVER_NAME may have changed
         new_url = parsed_url._replace(
@@ -361,6 +362,8 @@ def _adjust_url(*, url_obj=None, url_key=None, url_list=None, url_idx=None, prox
             url_obj[url_key] = new_url.geturl()
         else:
             url_list[url_idx] = new_url.geturl()
+        found_original_base_url = f"{parsed_url.scheme}://{parsed_url.netloc}"
+    return found_original_base_url
 
 
 def get_map_file_json(workspace, mapname, *, adjust_urls=True, x_forwarded_prefix=None):
@@ -374,8 +377,15 @@ def get_map_file_json(workspace, mapname, *, adjust_urls=True, x_forwarded_prefi
 
         for ml_idx in ml_indices:
             map_layer = map_json['layers'][ml_idx]
-            _adjust_url(url_obj=map_layer, url_key='url', proxy_prefix=x_forwarded_prefix)
-            _adjust_url(url_obj=map_layer.get('protocol', {}), url_key='url', proxy_prefix=x_forwarded_prefix)
+            orig_base_url = _adjust_url(url_obj=map_layer, url_key='url', proxy_prefix=x_forwarded_prefix)
+            orig_base_url = _adjust_url(url_obj=map_layer.get('protocol', {}), url_key='url',
+                                        proxy_prefix=x_forwarded_prefix) or orig_base_url
+            if orig_base_url:
+                legends = map_layer.get('legends', [])
+                for idx, legend_url in enumerate(legends):
+                    # use orig_base_url, because LAYMAN_PUBLIC_URL_SCHEME or LAYMAN_PROXY_SERVER_NAME may have changed
+                    if legend_url.startswith(orig_base_url):
+                        _adjust_url(url_list=legends, url_idx=idx, proxy_prefix=x_forwarded_prefix)
 
     if map_json is not None:
         map_json['user'] = get_map_owner_info(workspace)
diff --git a/tests/dynamic_data/publications/x_forwarded_prefix/internal_wms_and_wfs.json b/tests/dynamic_data/publications/x_forwarded_prefix/internal_wms_and_wfs.json
index 6d3068721..6d6d2234a 100644
--- a/tests/dynamic_data/publications/x_forwarded_prefix/internal_wms_and_wfs.json
+++ b/tests/dynamic_data/publications/x_forwarded_prefix/internal_wms_and_wfs.json
@@ -51,7 +51,11 @@
       "params": {
         "LAYERS": "map_file_workspace_wms:hranice",
         "FORMAT": "image\/png"
-      }
+      },
+      "legends": [
+        "http://localhost:8000/client-proxy-365/geoserver/ows?service=WMS&version=1.3.0&request=GetLegendGraphic&format=image%2Fpng&width=20&height=20&layer=map_file_workspace_wms%3Ahranice",
+        "https://example.com/legend.png"
+      ]
     },
     {
       "metadata": {},
diff --git a/tests/dynamic_data/publications/x_forwarded_prefix/map_file_test.py b/tests/dynamic_data/publications/x_forwarded_prefix/map_file_test.py
index c09b3fec7..ac9ae7943 100644
--- a/tests/dynamic_data/publications/x_forwarded_prefix/map_file_test.py
+++ b/tests/dynamic_data/publications/x_forwarded_prefix/map_file_test.py
@@ -50,6 +50,7 @@ def test_x_forwarded_prefix(self, headers):
 
         exp_adjusted_urls = [
             ('$.layers[1].url', '/geoserver/ows'),
+            ('$.layers[1].legends[0]', '/geoserver/ows?service=WMS&version=1.3.0&request=GetLegendGraphic&format=image%2Fpng&width=20&height=20&layer=map_file_workspace_wms%3Ahranice'),
             ('$.layers[2].url', '/geoserver/map_file_workspace_wms/ows'),
             ('$.layers[3].protocol.url', '/geoserver/map_file_workspace_workspace/wfs'),
         ]
@@ -66,6 +67,7 @@ def test_x_forwarded_prefix(self, headers):
         exp_unchanged_urls = [
             '$.layers[0].url',
             '$.layers[0].legends[0]',
+            '$.layers[1].legends[1]',
         ]
         for json_path_str in exp_unchanged_urls:
             exp_url = _find_single_value_in_json(json_path_str, orig_json)

From e768a60d5b2bb13b5afba963c00e58f1c86f1362 Mon Sep 17 00:00:00 2001
From: Jiri Kozel <jirik@users.noreply.github.com>
Date: Mon, 18 Sep 2023 14:14:51 +0200
Subject: [PATCH 08/14] Adjust style URL in GET Workspace Map File

---
 src/layman/map/util.py                        | 20 ++++++++++++-------
 .../internal_wms_and_wfs.json                 | 17 +++++++++++++---
 .../x_forwarded_prefix/map_file_test.py       |  3 +++
 3 files changed, 30 insertions(+), 10 deletions(-)

diff --git a/src/layman/map/util.py b/src/layman/map/util.py
index 36e29ead2..5752752d9 100644
--- a/src/layman/map/util.py
+++ b/src/layman/map/util.py
@@ -339,15 +339,14 @@ def get_same_or_missing_prop_names(workspace, mapname):
     return metadata_common.get_same_or_missing_prop_names(prop_names, md_comparison)
 
 
-def _adjust_url(*, url_obj=None, url_key=None, url_list=None, url_idx=None, proxy_prefix):
+def _adjust_url(*, url_obj=None, url_key=None, url_list=None, url_idx=None, proxy_prefix, path_prefix):
     assert (url_obj is None) == (url_key is None)
     assert (url_list is None) == (url_idx is None)
     assert (url_obj is None) != (url_list is None)
     url_str = url_obj.get(url_key, '') if url_obj is not None else url_list[url_idx]
     if not url_str:
         return None
-    gs_path_pattern = r'^' + layman_util.CLIENT_PROXY_ONLY_PATTERN + \
-                      re.escape(layman_settings.LAYMAN_GS_PATH) + r'(?P<path_postfix>.*)$'
+    gs_path_pattern = r'^' + layman_util.CLIENT_PROXY_ONLY_PATTERN + re.escape(path_prefix) + r'(?P<path_postfix>.*)$'
     parsed_url = urlparse(url_str)
     match = re.match(gs_path_pattern, parsed_url.path)
     found_original_base_url = None
@@ -356,7 +355,7 @@ def _adjust_url(*, url_obj=None, url_key=None, url_list=None, url_idx=None, prox
         new_url = parsed_url._replace(
             scheme=settings.LAYMAN_PUBLIC_URL_SCHEME,
             netloc=settings.LAYMAN_PROXY_SERVER_NAME,
-            path=proxy_prefix + layman_settings.LAYMAN_GS_PATH + match.group('path_postfix'),
+            path=proxy_prefix + path_prefix + match.group('path_postfix'),
         )
         if url_obj is not None:
             url_obj[url_key] = new_url.geturl()
@@ -377,15 +376,22 @@ def get_map_file_json(workspace, mapname, *, adjust_urls=True, x_forwarded_prefi
 
         for ml_idx in ml_indices:
             map_layer = map_json['layers'][ml_idx]
-            orig_base_url = _adjust_url(url_obj=map_layer, url_key='url', proxy_prefix=x_forwarded_prefix)
+            orig_base_url = _adjust_url(url_obj=map_layer, url_key='url', proxy_prefix=x_forwarded_prefix,
+                                        path_prefix=layman_settings.LAYMAN_GS_PATH)
             orig_base_url = _adjust_url(url_obj=map_layer.get('protocol', {}), url_key='url',
-                                        proxy_prefix=x_forwarded_prefix) or orig_base_url
+                                        proxy_prefix=x_forwarded_prefix, path_prefix=layman_settings.LAYMAN_GS_PATH
+                                        ) or orig_base_url
             if orig_base_url:
                 legends = map_layer.get('legends', [])
                 for idx, legend_url in enumerate(legends):
                     # use orig_base_url, because LAYMAN_PUBLIC_URL_SCHEME or LAYMAN_PROXY_SERVER_NAME may have changed
                     if legend_url.startswith(orig_base_url):
-                        _adjust_url(url_list=legends, url_idx=idx, proxy_prefix=x_forwarded_prefix)
+                        _adjust_url(url_list=legends, url_idx=idx, proxy_prefix=x_forwarded_prefix,
+                                    path_prefix=layman_settings.LAYMAN_GS_PATH)
+                maybe_style = map_layer.get('style')
+                if isinstance(maybe_style, str) and maybe_style.startswith(orig_base_url):
+                    _adjust_url(url_obj=map_layer, url_key='style', proxy_prefix=x_forwarded_prefix,
+                                path_prefix='/rest/')
 
     if map_json is not None:
         map_json['user'] = get_map_owner_info(workspace)
diff --git a/tests/dynamic_data/publications/x_forwarded_prefix/internal_wms_and_wfs.json b/tests/dynamic_data/publications/x_forwarded_prefix/internal_wms_and_wfs.json
index 6d6d2234a..14fe8adb8 100644
--- a/tests/dynamic_data/publications/x_forwarded_prefix/internal_wms_and_wfs.json
+++ b/tests/dynamic_data/publications/x_forwarded_prefix/internal_wms_and_wfs.json
@@ -55,7 +55,8 @@
       "legends": [
         "http://localhost:8000/client-proxy-365/geoserver/ows?service=WMS&version=1.3.0&request=GetLegendGraphic&format=image%2Fpng&width=20&height=20&layer=map_file_workspace_wms%3Ahranice",
         "https://example.com/legend.png"
-      ]
+      ],
+      "style": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?> <StyledLayerDescriptor version=\"1.0.0\" xsi:schemaLocation=\"http://www.opengis.net/sld StyledLayerDescriptor.xsd\" xmlns=\"http://www.opengis.net/sld\" xmlns:ogc=\"http://www.opengis.net/ogc\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"> <NamedLayer> <Name>GeoServer SLD Cook Book: Simple point with stroke</Name> <UserStyle> <Name>GeoServer SLD Cook Book: Simple point with stroke</Name> <Title>GeoServer SLD Cook Book: Simple point with stroke</Title> <FeatureTypeStyle> <Rule> <Name/> <PointSymbolizer> <Graphic> <Mark> <WellKnownName>circle</WellKnownName> <Fill> <CssParameter name=\"fill\">#FF0000</CssParameter> </Fill> <Stroke> <CssParameter name=\"stroke\">#000000</CssParameter> <CssParameter name=\"stroke-width\">2</CssParameter> </Stroke> </Mark> <Size>18</Size> </Graphic> </PointSymbolizer> </Rule> </FeatureTypeStyle> </UserStyle> </NamedLayer> </StyledLayerDescriptor>"
     },
     {
       "metadata": {},
@@ -68,7 +69,8 @@
       "params": {
         "LAYERS": "mista",
         "FORMAT": "image\/png"
-      }
+      },
+      "style": "http://localhost:8000/client-proxy-abcd/rest/workspaces/map_file_workspace/layers/mista/style"
     },
     {
       "className": "OpenLayers.Layer.Vector",
@@ -89,7 +91,16 @@
       "singleTile": true,
       "title": "Hranice",
       "visibility": false,
-      "wmsMaxScale": 0
+      "wmsMaxScale": 0,
+      "style": {
+        "stroke": {
+          "color": "rgba(238, 156, 150, 1)",
+          "width": 23
+        },
+        "fill": {
+          "color": "rgba(238, 156, 150, 1)"
+        }
+      }
     }
   ]
 }
diff --git a/tests/dynamic_data/publications/x_forwarded_prefix/map_file_test.py b/tests/dynamic_data/publications/x_forwarded_prefix/map_file_test.py
index ac9ae7943..606d1a328 100644
--- a/tests/dynamic_data/publications/x_forwarded_prefix/map_file_test.py
+++ b/tests/dynamic_data/publications/x_forwarded_prefix/map_file_test.py
@@ -52,6 +52,7 @@ def test_x_forwarded_prefix(self, headers):
             ('$.layers[1].url', '/geoserver/ows'),
             ('$.layers[1].legends[0]', '/geoserver/ows?service=WMS&version=1.3.0&request=GetLegendGraphic&format=image%2Fpng&width=20&height=20&layer=map_file_workspace_wms%3Ahranice'),
             ('$.layers[2].url', '/geoserver/map_file_workspace_wms/ows'),
+            ('$.layers[2].style', '/rest/workspaces/map_file_workspace/layers/mista/style'),
             ('$.layers[3].protocol.url', '/geoserver/map_file_workspace_workspace/wfs'),
         ]
 
@@ -68,6 +69,8 @@ def test_x_forwarded_prefix(self, headers):
             '$.layers[0].url',
             '$.layers[0].legends[0]',
             '$.layers[1].legends[1]',
+            '$.layers[1].style',
+            '$.layers[3].style',
         ]
         for json_path_str in exp_unchanged_urls:
             exp_url = _find_single_value_in_json(json_path_str, orig_json)

From ba454c2da901b54e48ed541099ef4674732e90f0 Mon Sep 17 00:00:00 2001
From: Jiri Kozel <jirik@users.noreply.github.com>
Date: Tue, 19 Sep 2023 11:33:56 +0200
Subject: [PATCH 09/14] Test Layman from another domain in map_file_test.py

---
 .../x_forwarded_prefix/internal_wms_and_wfs.json   | 14 ++++++++++++++
 .../x_forwarded_prefix/map_file_test.py            |  2 ++
 2 files changed, 16 insertions(+)

diff --git a/tests/dynamic_data/publications/x_forwarded_prefix/internal_wms_and_wfs.json b/tests/dynamic_data/publications/x_forwarded_prefix/internal_wms_and_wfs.json
index 14fe8adb8..af05f828f 100644
--- a/tests/dynamic_data/publications/x_forwarded_prefix/internal_wms_and_wfs.json
+++ b/tests/dynamic_data/publications/x_forwarded_prefix/internal_wms_and_wfs.json
@@ -101,6 +101,20 @@
           "color": "rgba(238, 156, 150, 1)"
         }
       }
+    },
+    {
+      "metadata": {},
+      "visibility": true,
+      "opacity": 1,
+      "title": "Mista from another Layman instance",
+      "className": "HSLayers.Layer.WMS",
+      "singleTile": true,
+      "url": "http://anotherdomain/geoserver/map_file_workspace_wms/ows",
+      "params": {
+        "LAYERS": "mista",
+        "FORMAT": "image\/png"
+      },
+      "style": "http://anotherdomain/client-proxy-abcd/rest/workspaces/map_file_workspace/layers/mista/style"
     }
   ]
 }
diff --git a/tests/dynamic_data/publications/x_forwarded_prefix/map_file_test.py b/tests/dynamic_data/publications/x_forwarded_prefix/map_file_test.py
index 606d1a328..e9a2e97a0 100644
--- a/tests/dynamic_data/publications/x_forwarded_prefix/map_file_test.py
+++ b/tests/dynamic_data/publications/x_forwarded_prefix/map_file_test.py
@@ -71,6 +71,8 @@ def test_x_forwarded_prefix(self, headers):
             '$.layers[1].legends[1]',
             '$.layers[1].style',
             '$.layers[3].style',
+            '$.layers[4].url',
+            '$.layers[4].style',
         ]
         for json_path_str in exp_unchanged_urls:
             exp_url = _find_single_value_in_json(json_path_str, orig_json)

From 9559ce24949740a80fc500a4546c1e9558b7ff7f Mon Sep 17 00:00:00 2001
From: Jiri Kozel <jirik@users.noreply.github.com>
Date: Tue, 19 Sep 2023 08:20:11 +0200
Subject: [PATCH 10/14] Restructure 868 in CHANGELOG.md

---
 CHANGELOG.md | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9583cafc6..1663e4bc8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,8 +15,14 @@
 - [#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), [POST Workspace Maps](doc/rest.md#post-workspace-maps), [PATCH Workspace Layer](doc/rest.md#patch-workspace-layer), [PATCH Workspace Map](doc/rest.md#patch-workspace-map) 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.
-  - Header does not affect responses of [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).
+- [#868](https://github.com/LayerManager/layman/issues/868) Responses to many requests respects [HTTP header `X-Forwarded-Prefix`](doc/client-proxy.md#x-forwarded-prefix-http-header) of the request. Those requests are:
+  - GET [Publications](doc/rest.md#get-publications), [Layers](doc/rest.md#get-layers), [Workspace Layers](doc/rest.md#get-workspace-layers), [Maps](doc/rest.md#get-maps), and [Workspace Maps](doc/rest.md#get-workspace-maps)
+  - [GET](doc/rest.md#get-workspace-layer), [PATCH](doc/rest.md#patch-workspace-layer), and [DELETE](doc/rest.md#delete-workspace-layer) Workspace Layer
+  - [GET](doc/rest.md#get-workspace-map), [PATCH](doc/rest.md#patch-workspace-map), and [DELETE](doc/rest.md#delete-workspace-map) Workspace Map
+  - [POST](doc/rest.md#post-workspace-layers) and [DELETE](doc/rest.md#delete-workspace-layers) Workspace Layers
+  - [POST](doc/rest.md#post-workspace-maps) and [DELETE](doc/rest.md#delete-workspace-maps) Workspace Maps
+  - 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).
 - [#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.

From 3386017966a3697f1e216c908b4e669cb24818a3 Mon Sep 17 00:00:00 2001
From: Jiri Kozel <jirik@users.noreply.github.com>
Date: Tue, 19 Sep 2023 08:26:22 +0200
Subject: [PATCH 11/14] Document adjusting URLs in GET Workspace Map File

---
 CHANGELOG.md        | 1 +
 doc/client-proxy.md | 7 +++++++
 doc/rest.md         | 2 ++
 3 files changed, 10 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1663e4bc8..c1f0abe86 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -19,6 +19,7 @@
   - GET [Publications](doc/rest.md#get-publications), [Layers](doc/rest.md#get-layers), [Workspace Layers](doc/rest.md#get-workspace-layers), [Maps](doc/rest.md#get-maps), and [Workspace Maps](doc/rest.md#get-workspace-maps)
   - [GET](doc/rest.md#get-workspace-layer), [PATCH](doc/rest.md#patch-workspace-layer), and [DELETE](doc/rest.md#delete-workspace-layer) Workspace Layer
   - [GET](doc/rest.md#get-workspace-map), [PATCH](doc/rest.md#patch-workspace-map), and [DELETE](doc/rest.md#delete-workspace-map) Workspace Map
+  - [GET Workspace Map File](doc/rest.md#get-workspace-map-file)
   - [POST](doc/rest.md#post-workspace-layers) and [DELETE](doc/rest.md#delete-workspace-layers) Workspace Layers
   - [POST](doc/rest.md#post-workspace-maps) and [DELETE](doc/rest.md#delete-workspace-maps) Workspace Maps
   - requests to [WMS](doc/endpoints.md#web-map-service) and [WFS](doc/endpoints.md#web-feature-service) endpoints
diff --git a/doc/client-proxy.md b/doc/client-proxy.md
index a91579ac3..59ba00f28 100644
--- a/doc/client-proxy.md
+++ b/doc/client-proxy.md
@@ -63,6 +63,13 @@ Currently, value of `X-Forwarded-Prefix` affects following URLs:
   * `file`.`url` key
   * `thumbnail`.`url` key
   * `metadata`.`comparison_url` key
+* [GET Workspace Map File](rest.md#get-workspace-map-file)
+  * some URLs of each [internal layer](models.md#internal-map-layer):
+    * `url` key
+    * `protocol`.`url` key
+    * each `legends` key if its HTTP protocol and netloc corresponds with `url` or `protocol`.`url`
+    * `style` key if its HTTP protocol and netloc corresponds with `url` or `protocol`.`url`
+  * NOTE: If client proxy prefix was used in URLs in uploaded file, then such prefix is also replaced with prefix according to `X-Forwarded-Prefix` header value. Such prefix is removed for requests without `X-Forwarded-Prefix` header.
 * [POST Workspace Layers](rest.md#post-workspace-layers)
   * `url` key
 * [DELETE Workspace Layer](rest.md#delete-workspace-layer)
diff --git a/doc/rest.md b/doc/rest.md
index 1e72c50ca..1d7ecee85 100644
--- a/doc/rest.md
+++ b/doc/rest.md
@@ -578,6 +578,7 @@ Content-Type: `multipart/form-data`
 Body parameters:
 - **file**, JSON file
    - must be valid against [map-composition schema](https://github.com/hslayers/hslayers-ng/wiki/Composition-schema)
+   - URLs of [internal layers](models.md#internal-map-layer) may contain [client-proxy prefix](client-proxy.md)
 - *name*, string
    - computer-friendly identifier of the map
    - must be unique among all maps of one workspace
@@ -738,6 +739,7 @@ Notice that some JSON properties are automatically updated by layman, so file ob
    - **email** set to email of the owner, or empty string if not known
    - other properties will be deleted
 - **groups** are removed
+- [some layer URLs](client-proxy.md#x-forwarded-prefix-http-header) according to `X-Forwarded-Prefix` header
 
 #### Request
 No action parameters.

From 19088dba9ad170a6c4f4a2a85d53c3abdf7206d2 Mon Sep 17 00:00:00 2001
From: Jiri Kozel <jirik@users.noreply.github.com>
Date: Tue, 19 Sep 2023 08:36:34 +0200
Subject: [PATCH 12/14] Add proxy prefix to map_layer_relation test

---
 .../publications/map_layer_relation/internal_wms_and_wfs.json   | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tests/dynamic_data/publications/map_layer_relation/internal_wms_and_wfs.json b/tests/dynamic_data/publications/map_layer_relation/internal_wms_and_wfs.json
index 028da83fb..4bfdbfc04 100644
--- a/tests/dynamic_data/publications/map_layer_relation/internal_wms_and_wfs.json
+++ b/tests/dynamic_data/publications/map_layer_relation/internal_wms_and_wfs.json
@@ -47,7 +47,7 @@
       "title": "Hranice",
       "className": "HSLayers.Layer.WMS",
       "singleTile": true,
-      "url": "http://localhost:8000/geoserver/layer_map_relation_workspace_wms/ows",
+      "url": "http://localhost:8000/some-client-proxy/geoserver/layer_map_relation_workspace_wms/ows",
       "params": {
         "LAYERS": "hranice",
         "FORMAT": "image\/png"

From 040ba0ff02b793399a501393f05389787d6f1889 Mon Sep 17 00:00:00 2001
From: Jiri Kozel <jirik@users.noreply.github.com>
Date: Tue, 19 Sep 2023 11:35:00 +0200
Subject: [PATCH 13/14] Fix typo in client-recommendations.md

---
 doc/oauth2/client-recommendations.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/doc/oauth2/client-recommendations.md b/doc/oauth2/client-recommendations.md
index 2b52139da..0cb6fe3eb 100644
--- a/doc/oauth2/client-recommendations.md
+++ b/doc/oauth2/client-recommendations.md
@@ -10,7 +10,7 @@ Both **Authorization Code** and **Authorization Code with PKCE** grant flows bet
 ## Storing Tokens on a Client
 An important decision when implementing OAuth2 *client* is where to store access tokens and refresh tokens. The recommendations differ based on [*client profile*](https://tools.ietf.org/html/rfc6749#section-2.1).
  
-[*Web Applications*](https://tools.ietf.org/html/rfc6749#section-2.1) are able to store tokens either on server side or client side. There exists quite straightforward recommendations (see e.g. [auth0](https://auth0.com/docs/security/store-tokens#regular-web-apps), [DZone](https://dzone.com/articles/security-best-practices-for-managing-api-access-to) or [StackOverflow](https://security.stackexchange.com/a/209388)). Generally three options are available:
+[*Web Applications*](https://tools.ietf.org/html/rfc6749#section-2.1) are able to store tokens either on server side or client side. There exists quite straightforward recommendations (see e.g. [auth0](https://auth0.com/docs/security/store-tokens#regular-web-apps), [DZone](https://dzone.com/articles/security-best-practices-for-managing-api-access-to) or [StackExchange](https://security.stackexchange.com/a/209388)). Generally three options are available:
 1. Store access tokens in browser memory. It would mean to provide new authorization request against authorization server and manual end-user authorization consent on every page open or page reload.
 2. Store access tokens in both secure and HTTP-only cookies. This option requires to have server side to set access tokens in cookies, and to refresh them using refresh tokens. Also refresh tokens need to be saved on server side.
 3. Store access token on server side in any well-protected database.

From 1b493417db4db56578319086b59f4c20d981ec7c Mon Sep 17 00:00:00 2001
From: Jiri Kozel <jirik@users.noreply.github.com>
Date: Tue, 19 Sep 2023 11:55:41 +0200
Subject: [PATCH 14/14] Add security.stackexchange.com to .remarkrc

---
 .remarkrc | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.remarkrc b/.remarkrc
index 4f754d15e..cee54ad2f 100644
--- a/.remarkrc
+++ b/.remarkrc
@@ -8,6 +8,7 @@
     "lint-no-dead-urls": {
       "skipLocalhost": true,
       "skipUrlPatterns": [
+        "https://security.stackexchange.com",
         "https://help.liferay.com",
         "https://github.com/LayerManager/layman/issues/"
       ]