c2cgeoportal-geoportal 2.3.5.80__py3-none-any.whl → 2.9rc45__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- c2cgeoportal_geoportal/__init__.py +960 -0
- c2cgeoportal_geoportal/lib/__init__.py +256 -0
- c2cgeoportal_geoportal/lib/authentication.py +250 -0
- c2cgeoportal_geoportal/lib/bashcolor.py +46 -0
- c2cgeoportal_geoportal/lib/cacheversion.py +77 -0
- c2cgeoportal_geoportal/lib/caching.py +176 -0
- c2cgeoportal_geoportal/lib/check_collector.py +80 -0
- c2cgeoportal_geoportal/lib/checker.py +295 -0
- c2cgeoportal_geoportal/lib/common_headers.py +172 -0
- c2cgeoportal_geoportal/lib/dbreflection.py +266 -0
- c2cgeoportal_geoportal/lib/filter_capabilities.py +360 -0
- c2cgeoportal_geoportal/lib/fulltextsearch.py +50 -0
- c2cgeoportal_geoportal/lib/functionality.py +166 -0
- c2cgeoportal_geoportal/lib/headers.py +62 -0
- c2cgeoportal_geoportal/lib/i18n.py +38 -0
- c2cgeoportal_geoportal/lib/layers.py +132 -0
- c2cgeoportal_geoportal/lib/lingva_extractor.py +937 -0
- c2cgeoportal_geoportal/lib/loader.py +57 -0
- c2cgeoportal_geoportal/lib/metrics.py +117 -0
- c2cgeoportal_geoportal/lib/oauth2.py +1186 -0
- c2cgeoportal_geoportal/lib/oidc.py +304 -0
- c2cgeoportal_geoportal/lib/wmstparsing.py +353 -0
- c2cgeoportal_geoportal/lib/xsd.py +166 -0
- c2cgeoportal_geoportal/py.typed +0 -0
- c2cgeoportal_geoportal/resources.py +49 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/ci/config.yaml +26 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/cookiecutter.json +18 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/.dockerignore +6 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/.eslintrc.yaml +19 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/.prospector.yaml +30 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/Dockerfile +75 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/Makefile +6 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/alembic.ini +58 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/alembic.yaml +19 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/development.ini +121 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/gunicorn.conf.py +139 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/language_mapping +3 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/lingva-client.cfg +5 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/lingva-server.cfg +6 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/production.ini +38 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/requirements.txt +2 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/setup.py +25 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/webpack.api.js +41 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/webpack.apps.js +64 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/webpack.commons.js +11 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/webpack.config.js +22 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/__init__.py +42 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/authentication.py +10 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/dev.py +14 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/models.py +8 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/multi_organization.py +7 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/resources.py +11 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static-ngeo/api/index.js +12 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static-ngeo/js/{{cookiecutter.package}}module.js +25 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/subscribers.py +39 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/views/__init__.py +0 -0
- c2cgeoportal_geoportal/scaffolds/advance_update/cookiecutter.json +18 -0
- c2cgeoportal_geoportal/scaffolds/advance_update/{{cookiecutter.project}}/geoportal/CONST_Makefile +121 -0
- c2cgeoportal_geoportal/scaffolds/create/cookiecutter.json +18 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.dockerignore +14 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.editorconfig +17 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.github/workflows/main.yaml +73 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.github/workflows/rebuild.yaml +50 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.github/workflows/update_l10n.yaml +66 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.gitignore +16 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.pre-commit-config.yaml +35 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.prettierignore +1 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.prettierrc.yaml +2 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/Dockerfile +75 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/Makefile +70 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/README.rst +29 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/build +179 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/ci/config.yaml +22 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/ci/docker-compose-check +25 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/ci/requirements.txt +2 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose-db.yaml +24 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose-lib.yaml +513 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose-qgis.yaml +21 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose.override.sample.yaml +65 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose.yaml +121 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/env.default +102 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/env.project +69 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/vars.yaml +430 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/locale/en/LC_MESSAGES/{{cookiecutter.package}}_geoportal-client.po +6 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/css/desktop.css +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/css/iframe_api.css +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/css/mobile.css +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/images/banner_left.png +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/images/banner_right.png +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/images/blank.png +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/images/markers/marker-blue.png +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/images/markers/marker-gold.png +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/images/markers/marker-green.png +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/images/markers/marker.png +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/robot.txt.tmpl +3 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/data/Readme.txt +69 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/data/TM_EUROPE_BORDERS-0.3.sql +70 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/demo.map.tmpl +224 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/Arial.ttf +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/Arialbd.ttf +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/Arialbi.ttf +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/Ariali.ttf +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/NotoSans-Bold.ttf +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/NotoSans-BoldItalic.ttf +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/NotoSans-Italic.ttf +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/NotoSans-Regular.ttf +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/Verdana.ttf +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/Verdanab.ttf +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/Verdanai.ttf +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/Verdanaz.ttf +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts.conf +12 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/mapserver.conf +16 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/mapserver.map.tmpl +87 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/tinyows.xml.tmpl +36 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/A3_Landscape.jrxml +207 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/A3_Portrait.jrxml +185 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/A4_Landscape.jrxml +200 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/A4_Portrait.jrxml +170 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/config.yaml.tmpl +175 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/legend.jrxml +109 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/localisation.properties +4 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/localisation_fr.properties +4 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/logo.png +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/north.svg +93 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/results.jrxml +25 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/project.yaml +18 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/pyproject.toml +7 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/qgisserver/pg_service.conf.tmpl +15 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/run_alembic.sh +11 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/scripts/db-backup +126 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/scripts/db-restore +132 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/setup.cfg +7 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/spell-ignore-words.txt +5 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/tests/__init__.py +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/tests/test_app.py +78 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/tilegeneration/config.yaml.tmpl +195 -0
- c2cgeoportal_geoportal/scaffolds/update/cookiecutter.json +18 -0
- c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/.upgrade.yaml +67 -0
- c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/CONST_CHANGELOG.txt +304 -0
- c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/CONST_create_template/tests/test_testapp.py +48 -0
- c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/geoportal/.CONST_vars.yaml.swp +0 -0
- c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/geoportal/CONST_config-schema.yaml +927 -0
- c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/geoportal/CONST_vars.yaml +1503 -0
- c2cgeoportal_geoportal/scripts/__init__.py +64 -0
- c2cgeoportal_geoportal/scripts/c2cupgrade.py +879 -0
- c2cgeoportal_geoportal/scripts/create_demo_theme.py +83 -0
- c2cgeoportal_geoportal/scripts/manage_users.py +140 -0
- c2cgeoportal_geoportal/scripts/pcreate.py +296 -0
- c2cgeoportal_geoportal/scripts/theme2fts.py +347 -0
- c2cgeoportal_geoportal/scripts/urllogin.py +81 -0
- c2cgeoportal_geoportal/templates/login.html +90 -0
- c2cgeoportal_geoportal/templates/notlogin.html +62 -0
- c2cgeoportal_geoportal/templates/testi18n.html +12 -0
- c2cgeoportal_geoportal/views/__init__.py +59 -0
- c2cgeoportal_geoportal/views/dev.py +57 -0
- c2cgeoportal_geoportal/views/dynamic.py +209 -0
- c2cgeoportal_geoportal/views/entry.py +174 -0
- c2cgeoportal_geoportal/views/fulltextsearch.py +189 -0
- c2cgeoportal_geoportal/views/geometry_processing.py +75 -0
- c2cgeoportal_geoportal/views/i18n.py +129 -0
- c2cgeoportal_geoportal/views/layers.py +713 -0
- c2cgeoportal_geoportal/views/login.py +684 -0
- c2cgeoportal_geoportal/views/mapserverproxy.py +234 -0
- c2cgeoportal_geoportal/views/memory.py +90 -0
- c2cgeoportal_geoportal/views/ogcproxy.py +120 -0
- c2cgeoportal_geoportal/views/pdfreport.py +245 -0
- c2cgeoportal_geoportal/views/printproxy.py +143 -0
- c2cgeoportal_geoportal/views/profile.py +192 -0
- c2cgeoportal_geoportal/views/proxy.py +261 -0
- c2cgeoportal_geoportal/views/raster.py +233 -0
- c2cgeoportal_geoportal/views/resourceproxy.py +73 -0
- c2cgeoportal_geoportal/views/shortener.py +152 -0
- c2cgeoportal_geoportal/views/theme.py +1322 -0
- c2cgeoportal_geoportal/views/tinyowsproxy.py +189 -0
- c2cgeoportal_geoportal/views/vector_tiles.py +83 -0
- {c2cgeoportal_geoportal-2.3.5.80.dist-info → c2cgeoportal_geoportal-2.9rc45.dist-info}/METADATA +21 -24
- c2cgeoportal_geoportal-2.9rc45.dist-info/RECORD +193 -0
- {c2cgeoportal_geoportal-2.3.5.80.dist-info → c2cgeoportal_geoportal-2.9rc45.dist-info}/WHEEL +1 -1
- c2cgeoportal_geoportal-2.9rc45.dist-info/entry_points.txt +28 -0
- c2cgeoportal_geoportal-2.9rc45.dist-info/top_level.txt +2 -0
- tests/__init__.py +100 -0
- tests/test_cachebuster.py +71 -0
- tests/test_caching.py +275 -0
- tests/test_checker.py +85 -0
- tests/test_decimaljson.py +47 -0
- tests/test_headerstween.py +64 -0
- tests/test_i18n.py +31 -0
- tests/test_init.py +193 -0
- tests/test_locale_negociator.py +69 -0
- tests/test_mapserverproxy_route_predicate.py +64 -0
- tests/test_raster.py +267 -0
- tests/test_wmstparsing.py +238 -0
- tests/xmlstr.py +103 -0
- c2cgeoportal_geoportal-2.3.5.80.dist-info/DESCRIPTION.rst +0 -8
- c2cgeoportal_geoportal-2.3.5.80.dist-info/RECORD +0 -7
- c2cgeoportal_geoportal-2.3.5.80.dist-info/entry_points.txt +0 -22
- c2cgeoportal_geoportal-2.3.5.80.dist-info/metadata.json +0 -1
- c2cgeoportal_geoportal-2.3.5.80.dist-info/top_level.txt +0 -1
@@ -0,0 +1,360 @@
|
|
1
|
+
# Copyright (c) 2014-2024, Camptocamp SA
|
2
|
+
# All rights reserved.
|
3
|
+
|
4
|
+
# Redistribution and use in source and binary forms, with or without
|
5
|
+
# modification, are permitted provided that the following conditions are met:
|
6
|
+
|
7
|
+
# 1. Redistributions of source code must retain the above copyright notice, this
|
8
|
+
# list of conditions and the following disclaimer.
|
9
|
+
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
10
|
+
# this list of conditions and the following disclaimer in the documentation
|
11
|
+
# and/or other materials provided with the distribution.
|
12
|
+
|
13
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
14
|
+
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
15
|
+
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
16
|
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
17
|
+
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
18
|
+
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
19
|
+
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
20
|
+
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
21
|
+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
22
|
+
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
23
|
+
|
24
|
+
# The views and conclusions contained in the software and documentation are those
|
25
|
+
# of the authors and should not be interpreted as representing official policies,
|
26
|
+
# either expressed or implied, of the FreeBSD Project.
|
27
|
+
|
28
|
+
|
29
|
+
import copy
|
30
|
+
import logging
|
31
|
+
import xml.sax.handler # nosec
|
32
|
+
import xml.sax.xmlreader # nosec
|
33
|
+
from collections.abc import Callable
|
34
|
+
from io import StringIO
|
35
|
+
from typing import Any, Union
|
36
|
+
from xml.sax.saxutils import XMLFilterBase, XMLGenerator # nosec
|
37
|
+
|
38
|
+
import defusedxml.expatreader
|
39
|
+
import pyramid.httpexceptions
|
40
|
+
import pyramid.request
|
41
|
+
import requests
|
42
|
+
from owslib.map.wms111 import ContentMetadata as ContentMetadata111
|
43
|
+
from owslib.map.wms130 import ContentMetadata as ContentMetadata130
|
44
|
+
from owslib.wms import WebMapService
|
45
|
+
from pyramid.httpexceptions import HTTPBadGateway
|
46
|
+
|
47
|
+
from c2cgeoportal_commons.lib.url import Url
|
48
|
+
from c2cgeoportal_geoportal.lib import caching, get_ogc_server_wfs_url_ids, get_ogc_server_wms_url_ids
|
49
|
+
from c2cgeoportal_geoportal.lib.layers import get_private_layers, get_protected_layers, get_writable_layers
|
50
|
+
|
51
|
+
_CACHE_REGION = caching.get_region("std")
|
52
|
+
_LOG = logging.getLogger(__name__)
|
53
|
+
ContentMetadata = Union[ContentMetadata111, ContentMetadata130]
|
54
|
+
|
55
|
+
|
56
|
+
@_CACHE_REGION.cache_on_arguments()
|
57
|
+
def wms_structure(request: pyramid.request.Request, wms_url: Url, host: str) -> dict[str, list[str]]:
|
58
|
+
"""Get a simple serializable structure of the WMS capabilities."""
|
59
|
+
url = wms_url.clone().add_query({"SERVICE": "WMS", "VERSION": "1.1.1", "REQUEST": "GetCapabilities"})
|
60
|
+
|
61
|
+
# Forward request to target (without Host Header)
|
62
|
+
headers = {}
|
63
|
+
if url.hostname == "localhost" and host is not None:
|
64
|
+
headers["Host"] = host
|
65
|
+
try:
|
66
|
+
response = requests.get(
|
67
|
+
url.url(), headers=headers, **request.registry.settings.get("http_options", {})
|
68
|
+
)
|
69
|
+
except Exception:
|
70
|
+
_LOG.exception("Unable to GetCapabilities from wms_url '%s'", wms_url)
|
71
|
+
raise HTTPBadGateway( # pylint: disable=raise-missing-from
|
72
|
+
"Unable to GetCapabilities, see logs for details"
|
73
|
+
)
|
74
|
+
|
75
|
+
if not response.ok:
|
76
|
+
raise HTTPBadGateway(
|
77
|
+
f"GetCapabilities from wms_url {url.url()} return the error: "
|
78
|
+
f"{response.status_code:d} {response.reason}"
|
79
|
+
)
|
80
|
+
|
81
|
+
try:
|
82
|
+
wms = WebMapService(None, xml=response.content)
|
83
|
+
result: dict[str, list[str]] = {}
|
84
|
+
|
85
|
+
def _fill(name: str, parent: ContentMetadata) -> None:
|
86
|
+
if parent is None:
|
87
|
+
return
|
88
|
+
if parent.name not in result:
|
89
|
+
result[parent.name] = []
|
90
|
+
result[parent.name].append(name)
|
91
|
+
_fill(name, parent.parent)
|
92
|
+
|
93
|
+
for layer in list(wms.contents.values()):
|
94
|
+
_fill(layer.name, layer.parent)
|
95
|
+
return result
|
96
|
+
|
97
|
+
except AttributeError:
|
98
|
+
error = "WARNING! an error occurred while trying to read the mapfile and recover the themes."
|
99
|
+
error = f"{error}\nurl: {wms_url}\nxml:\n{response.text}"
|
100
|
+
_LOG.exception(error)
|
101
|
+
raise HTTPBadGateway(error) # pylint: disable=raise-missing-from
|
102
|
+
|
103
|
+
except SyntaxError:
|
104
|
+
error = "WARNING! an error occurred while trying to read the mapfile and recover the themes."
|
105
|
+
error = f"{error}\nurl: {wms_url}\nxml:\n{response.text}"
|
106
|
+
_LOG.exception(error)
|
107
|
+
raise HTTPBadGateway(error) # pylint: disable=raise-missing-from
|
108
|
+
|
109
|
+
|
110
|
+
def filter_capabilities(
|
111
|
+
request: pyramid.request.Request, content: str, wms: bool, url: Url, headers: dict[str, str]
|
112
|
+
) -> str:
|
113
|
+
"""Filter the WMS/WFS capabilities."""
|
114
|
+
|
115
|
+
wms_structure_ = wms_structure(request, url, headers.get("Host"))
|
116
|
+
|
117
|
+
ogc_server_ids = (
|
118
|
+
get_ogc_server_wms_url_ids(request, request.host)
|
119
|
+
if wms
|
120
|
+
else get_ogc_server_wfs_url_ids(request, request.host)
|
121
|
+
).get(url.url())
|
122
|
+
gmf_private_layers = copy.copy(get_private_layers(ogc_server_ids))
|
123
|
+
for id_ in list(get_protected_layers(request, ogc_server_ids).keys()):
|
124
|
+
if id_ in gmf_private_layers:
|
125
|
+
del gmf_private_layers[id_]
|
126
|
+
|
127
|
+
private_layers = set()
|
128
|
+
for gmf_layer in list(gmf_private_layers.values()):
|
129
|
+
for ogc_layer in gmf_layer.layer.split(","):
|
130
|
+
private_layers.add(ogc_layer)
|
131
|
+
if ogc_layer in wms_structure_:
|
132
|
+
private_layers.update(wms_structure_[ogc_layer])
|
133
|
+
|
134
|
+
_LOG.debug(
|
135
|
+
"Filter capabilities of OGC server %s\nprivate_layers: %s",
|
136
|
+
", ".join([str(e) for e in ogc_server_ids]),
|
137
|
+
", ".join(private_layers),
|
138
|
+
)
|
139
|
+
|
140
|
+
parser = defusedxml.expatreader.create_parser(forbid_external=False)
|
141
|
+
# skip inclusion of DTDs
|
142
|
+
parser.setFeature(xml.sax.handler.feature_external_ges, False)
|
143
|
+
parser.setFeature(xml.sax.handler.feature_external_pes, False)
|
144
|
+
|
145
|
+
result = StringIO()
|
146
|
+
downstream_handler = XMLGenerator(result, "utf-8")
|
147
|
+
filter_handler = _CapabilitiesFilter(
|
148
|
+
parser, downstream_handler, "Layer" if wms else "FeatureType", layers_blacklist=private_layers
|
149
|
+
)
|
150
|
+
filter_handler.parse(StringIO(content))
|
151
|
+
return result.getvalue()
|
152
|
+
|
153
|
+
|
154
|
+
def filter_wfst_capabilities(content: str, wfs_url: Url, request: pyramid.request.Request) -> str:
|
155
|
+
"""Filter the WTS capabilities."""
|
156
|
+
|
157
|
+
writable_layers: set[str] = set()
|
158
|
+
ogc_server_ids = get_ogc_server_wfs_url_ids(request, request.host).get(wfs_url.url())
|
159
|
+
if ogc_server_ids is None:
|
160
|
+
_LOG.error("No OGC server found for WFS URL %s", wfs_url)
|
161
|
+
raise pyramid.httpexceptions.HTTPInternalServerError("No OGC server found for WFS URL")
|
162
|
+
|
163
|
+
for gmf_layer in list(get_writable_layers(request, ogc_server_ids).values()):
|
164
|
+
writable_layers |= set(gmf_layer.layer.split(","))
|
165
|
+
|
166
|
+
_LOG.debug(
|
167
|
+
"Filter WFS-T capabilities of OGC server %s\nlayers: %s",
|
168
|
+
", ".join([str(e) for e in ogc_server_ids]),
|
169
|
+
", ".join(writable_layers),
|
170
|
+
)
|
171
|
+
|
172
|
+
parser = defusedxml.expatreader.create_parser(forbid_external=False)
|
173
|
+
# skip inclusion of DTDs
|
174
|
+
parser.setFeature(xml.sax.handler.feature_external_ges, False)
|
175
|
+
parser.setFeature(xml.sax.handler.feature_external_pes, False)
|
176
|
+
|
177
|
+
result = StringIO()
|
178
|
+
downstream_handler = XMLGenerator(result, "utf-8")
|
179
|
+
filter_handler = _CapabilitiesFilter(
|
180
|
+
parser, downstream_handler, "FeatureType", layers_whitelist=writable_layers
|
181
|
+
)
|
182
|
+
filter_handler.parse(StringIO(content))
|
183
|
+
return result.getvalue()
|
184
|
+
|
185
|
+
|
186
|
+
class _Layer:
|
187
|
+
def __init__(self, self_hidden: bool = False):
|
188
|
+
self.accumulator: list[Callable[[], None]] = []
|
189
|
+
self.hidden = True
|
190
|
+
self.self_hidden = self_hidden
|
191
|
+
self.has_children = False
|
192
|
+
self.children_nb = 0
|
193
|
+
|
194
|
+
|
195
|
+
class _CapabilitiesFilter(XMLFilterBase):
|
196
|
+
"""
|
197
|
+
SAX filter to show only the allowed layers in a GetCapabilities request.
|
198
|
+
|
199
|
+
The filter removes elements of type `tag_name` where the `name` attribute is part of the set
|
200
|
+
`layers_blacklist` (when `layers_blacklist` is given) or is not part of the set `layers_whitelist` (when
|
201
|
+
`layers_whitelist` is given).
|
202
|
+
"""
|
203
|
+
|
204
|
+
def __init__(
|
205
|
+
self,
|
206
|
+
upstream: XMLFilterBase,
|
207
|
+
downstream: XMLGenerator,
|
208
|
+
tag_name: str,
|
209
|
+
layers_blacklist: set[str] | None = None,
|
210
|
+
layers_whitelist: set[str] | None = None,
|
211
|
+
):
|
212
|
+
XMLFilterBase.__init__(self, upstream)
|
213
|
+
self._downstream = downstream
|
214
|
+
self._accumulator: list[str] = []
|
215
|
+
|
216
|
+
assert (
|
217
|
+
layers_blacklist is not None or layers_whitelist is not None
|
218
|
+
), "either layers_blacklist OR layers_whitelist must be set"
|
219
|
+
assert not (
|
220
|
+
layers_blacklist is not None and layers_whitelist is not None
|
221
|
+
), "only either layers_blacklist OR layers_whitelist can be set"
|
222
|
+
|
223
|
+
if layers_blacklist is not None:
|
224
|
+
layers_blacklist = {layer.lower() for layer in layers_blacklist}
|
225
|
+
if layers_whitelist is not None:
|
226
|
+
layers_whitelist = {layer.lower() for layer in layers_whitelist}
|
227
|
+
self.layers_blacklist = layers_blacklist
|
228
|
+
self.layers_whitelist = layers_whitelist
|
229
|
+
|
230
|
+
self.layers_path: list[_Layer] = []
|
231
|
+
self.in_name = False
|
232
|
+
self.tag_name = tag_name
|
233
|
+
self.level = 0
|
234
|
+
|
235
|
+
def _complete_text_node(self) -> None:
|
236
|
+
if self._accumulator:
|
237
|
+
self._downstream.characters("".join(self._accumulator))
|
238
|
+
self._accumulator = []
|
239
|
+
|
240
|
+
def _do(self, action: Callable[[], Any]) -> None:
|
241
|
+
if self.layers_path:
|
242
|
+
self.layers_path[-1].accumulator.append(action)
|
243
|
+
else:
|
244
|
+
self._complete_text_node()
|
245
|
+
action()
|
246
|
+
|
247
|
+
def _add_child(self, layer: _Layer) -> None:
|
248
|
+
if not layer.hidden and not (layer.has_children and layer.children_nb == 0):
|
249
|
+
for action in layer.accumulator:
|
250
|
+
self._complete_text_node()
|
251
|
+
action()
|
252
|
+
layer.accumulator = []
|
253
|
+
|
254
|
+
def setDocumentLocator(self, locator: xml.sax.xmlreader.Locator) -> None: # noqa: ignore=N802
|
255
|
+
self._downstream.setDocumentLocator(locator)
|
256
|
+
|
257
|
+
def startDocument(self) -> None: # noqa: ignore=N802
|
258
|
+
self._downstream.startDocument()
|
259
|
+
|
260
|
+
def endDocument(self) -> None: # noqa: ignore=N802
|
261
|
+
self._downstream.endDocument()
|
262
|
+
|
263
|
+
def startPrefixMapping(self, prefix: str | None, uri: str) -> None: # noqa: ignore=N802
|
264
|
+
self._downstream.startPrefixMapping(prefix, uri)
|
265
|
+
|
266
|
+
def endPrefixMapping(self, prefix: str | None) -> None: # noqa: ignore=N802
|
267
|
+
self._downstream.endPrefixMapping(prefix)
|
268
|
+
|
269
|
+
def startElement(self, name: str, attrs: xml.sax.xmlreader.AttributesImpl) -> None: # noqa: ignore=N802
|
270
|
+
if name == self.tag_name:
|
271
|
+
self.level += 1
|
272
|
+
if self.layers_path:
|
273
|
+
parent_layer = self.layers_path[-1]
|
274
|
+
parent_layer.has_children = True
|
275
|
+
parent_layer.children_nb += 1
|
276
|
+
layer = _Layer(parent_layer.self_hidden) if len(self.layers_path) > 1 else _Layer()
|
277
|
+
self.layers_path.append(layer)
|
278
|
+
|
279
|
+
parent_layer.accumulator.append(lambda: self._add_child(layer))
|
280
|
+
else:
|
281
|
+
layer = _Layer()
|
282
|
+
self.layers_path.append(layer)
|
283
|
+
elif name == "Name" and self.layers_path:
|
284
|
+
self.in_name = True
|
285
|
+
|
286
|
+
self._do(lambda: self._downstream.startElement(name, attrs))
|
287
|
+
|
288
|
+
def endElement(self, name: str) -> None: # noqa: ignore=N802
|
289
|
+
self._do(lambda: self._downstream.endElement(name))
|
290
|
+
|
291
|
+
if name == self.tag_name:
|
292
|
+
self.level -= 1
|
293
|
+
if self.level == 0 and not self.layers_path[0].hidden:
|
294
|
+
for action in self.layers_path[0].accumulator:
|
295
|
+
self._complete_text_node()
|
296
|
+
action()
|
297
|
+
self.layers_path.pop()
|
298
|
+
elif name == "Name":
|
299
|
+
self.in_name = False
|
300
|
+
|
301
|
+
def startElementNS( # noqa: ignore=N802
|
302
|
+
self, name: tuple[str, str], qname: str, attrs: xml.sax.xmlreader.AttributesNSImpl
|
303
|
+
) -> None:
|
304
|
+
self._do(lambda: self._downstream.startElementNS(name, qname, attrs))
|
305
|
+
|
306
|
+
def endElementNS(self, name: tuple[str, str], qname: str) -> None: # noqa: ignore=N802
|
307
|
+
self._do(lambda: self._downstream.endElementNS(name, qname))
|
308
|
+
|
309
|
+
def _keep_layer(self, layer_name: str) -> bool:
|
310
|
+
return (self.layers_blacklist is not None and layer_name not in self.layers_blacklist) or (
|
311
|
+
self.layers_whitelist is not None and layer_name in self.layers_whitelist
|
312
|
+
)
|
313
|
+
|
314
|
+
def characters(self, content: str) -> None:
|
315
|
+
if self.in_name and self.layers_path and not self.layers_path[-1].self_hidden is True:
|
316
|
+
layer_name = normalize_typename(content)
|
317
|
+
if self._keep_layer(layer_name):
|
318
|
+
for layer in self.layers_path:
|
319
|
+
layer.hidden = False
|
320
|
+
else:
|
321
|
+
# remove layer
|
322
|
+
self.layers_path[-1].self_hidden = True
|
323
|
+
if len(self.layers_path) > 1:
|
324
|
+
self.layers_path[-2].children_nb -= 1
|
325
|
+
|
326
|
+
self._do(lambda: self._accumulator.append(content))
|
327
|
+
|
328
|
+
def ignorableWhitespace(self, chars: str) -> None: # noqa: ignore=N802
|
329
|
+
self._do(lambda: self._accumulator.append(chars))
|
330
|
+
|
331
|
+
def processingInstruction(self, target: str, data: str) -> None: # noqa: ignore=N802
|
332
|
+
self._do(lambda: self._downstream.processingInstruction(target, data))
|
333
|
+
|
334
|
+
def skippedEntity(self, name: str) -> None: # noqa: ignore=N802
|
335
|
+
self._downstream.skippedEntity(name)
|
336
|
+
|
337
|
+
|
338
|
+
def normalize_tag(tag: str) -> str:
|
339
|
+
"""
|
340
|
+
Drop the namespace from a tag and converts to lower case.
|
341
|
+
|
342
|
+
e.g. '{https://....}TypeName' -> 'TypeName'
|
343
|
+
"""
|
344
|
+
normalized = tag
|
345
|
+
if len(tag) >= 3:
|
346
|
+
if tag[0] == "{":
|
347
|
+
normalized = tag[1:].split("}")[1]
|
348
|
+
return normalized.lower()
|
349
|
+
|
350
|
+
|
351
|
+
def normalize_typename(typename: str) -> str:
|
352
|
+
"""
|
353
|
+
Drop the namespace from a type name and converts to lower case.
|
354
|
+
|
355
|
+
e.g. 'tows:parks' -> 'parks'
|
356
|
+
"""
|
357
|
+
normalized = typename
|
358
|
+
if ":" in typename:
|
359
|
+
normalized = typename.split(":")[1]
|
360
|
+
return normalized.lower()
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# Copyright (c) 2020-2023, Camptocamp SA
|
2
|
+
# All rights reserved.
|
3
|
+
|
4
|
+
# Redistribution and use in source and binary forms, with or without
|
5
|
+
# modification, are permitted provided that the following conditions are met:
|
6
|
+
|
7
|
+
# 1. Redistributions of source code must retain the above copyright notice, this
|
8
|
+
# list of conditions and the following disclaimer.
|
9
|
+
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
10
|
+
# this list of conditions and the following disclaimer in the documentation
|
11
|
+
# and/or other materials provided with the distribution.
|
12
|
+
|
13
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
14
|
+
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
15
|
+
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
16
|
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
17
|
+
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
18
|
+
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
19
|
+
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
20
|
+
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
21
|
+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
22
|
+
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
23
|
+
|
24
|
+
# The views and conclusions contained in the software and documentation are those
|
25
|
+
# of the authors and should not be interpreted as representing official policies,
|
26
|
+
# either expressed or implied, of the FreeBSD Project.
|
27
|
+
|
28
|
+
import re
|
29
|
+
from typing import Any
|
30
|
+
|
31
|
+
|
32
|
+
class Normalize:
|
33
|
+
"""Normalize a text for the Full text search."""
|
34
|
+
|
35
|
+
def __init__(self, config: dict[str, Any]) -> None:
|
36
|
+
split = config.get("split_regex")
|
37
|
+
self.split_re = re.compile(split) if split is not None else None
|
38
|
+
|
39
|
+
self.word_replace = []
|
40
|
+
for search_regex, text in config.get("replace", {}).items():
|
41
|
+
self.word_replace.append((re.compile(search_regex), text))
|
42
|
+
|
43
|
+
def __call__(self, text: str) -> str:
|
44
|
+
if self.split_re is not None:
|
45
|
+
text = " ".join(self.split_re.split(text))
|
46
|
+
|
47
|
+
for search, replace in self.word_replace:
|
48
|
+
text = search.sub(replace, text)
|
49
|
+
|
50
|
+
return text
|
@@ -0,0 +1,166 @@
|
|
1
|
+
# Copyright (c) 2011-2024, Camptocamp SA
|
2
|
+
# All rights reserved.
|
3
|
+
|
4
|
+
# Redistribution and use in source and binary forms, with or without
|
5
|
+
# modification, are permitted provided that the following conditions are met:
|
6
|
+
|
7
|
+
# 1. Redistributions of source code must retain the above copyright notice, this
|
8
|
+
# list of conditions and the following disclaimer.
|
9
|
+
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
10
|
+
# this list of conditions and the following disclaimer in the documentation
|
11
|
+
# and/or other materials provided with the distribution.
|
12
|
+
|
13
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
14
|
+
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
15
|
+
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
16
|
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
17
|
+
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
18
|
+
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
19
|
+
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
20
|
+
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
21
|
+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
22
|
+
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
23
|
+
|
24
|
+
# The views and conclusions contained in the software and documentation are those
|
25
|
+
# of the authors and should not be interpreted as representing official policies,
|
26
|
+
# either expressed or implied, of the FreeBSD Project.
|
27
|
+
|
28
|
+
|
29
|
+
import logging.config
|
30
|
+
from typing import Any, cast
|
31
|
+
|
32
|
+
import pyramid.request
|
33
|
+
from sqlalchemy.orm import joinedload
|
34
|
+
|
35
|
+
from c2cgeoportal_commons.models import main, static
|
36
|
+
from c2cgeoportal_geoportal.lib import get_typed, get_types_map, is_intranet
|
37
|
+
from c2cgeoportal_geoportal.lib.caching import get_region
|
38
|
+
|
39
|
+
_LOG = logging.getLogger(__name__)
|
40
|
+
_CACHE_REGION_OBJ = get_region("obj")
|
41
|
+
_CACHE_REGION = get_region("std")
|
42
|
+
|
43
|
+
|
44
|
+
@_CACHE_REGION_OBJ.cache_on_arguments()
|
45
|
+
def _get_role(name: str) -> dict[str, Any]:
|
46
|
+
from c2cgeoportal_commons.models import DBSession # pylint: disable=import-outside-toplevel
|
47
|
+
|
48
|
+
assert DBSession is not None
|
49
|
+
|
50
|
+
role = (
|
51
|
+
DBSession.query(main.Role)
|
52
|
+
.filter(main.Role.name == name)
|
53
|
+
.options(joinedload(main.Role.functionalities))
|
54
|
+
.one_or_none()
|
55
|
+
)
|
56
|
+
struct = _role_to_struct(role)
|
57
|
+
return {"settings_functionalities": struct, "roles_functionalities": {name: struct}}
|
58
|
+
|
59
|
+
|
60
|
+
def _user_to_struct(user: static.User) -> dict[str, Any]:
|
61
|
+
return {
|
62
|
+
"settings_functionalities": _role_to_struct(user.settings_role),
|
63
|
+
"roles_functionalities": {role.name: _role_to_struct(role) for role in user.roles},
|
64
|
+
}
|
65
|
+
|
66
|
+
|
67
|
+
def _role_to_struct(role: main.Role | None) -> list[dict[str, Any]]:
|
68
|
+
return [{"name": f.name, "value": f.value} for f in role.functionalities] if role else []
|
69
|
+
|
70
|
+
|
71
|
+
def _get_db_functionality(
|
72
|
+
name: str,
|
73
|
+
user: dict[str, Any],
|
74
|
+
types: dict[str, dict[str, Any]],
|
75
|
+
request: pyramid.request.Request,
|
76
|
+
errors: set[str],
|
77
|
+
) -> list[str | int | float | bool | list[Any] | dict[str, Any]]:
|
78
|
+
if types.get(name, {}).get("single", False):
|
79
|
+
values = [
|
80
|
+
get_typed(name, functionality["value"], types, request, errors)
|
81
|
+
for functionality in user["settings_functionalities"]
|
82
|
+
if functionality["name"] == name
|
83
|
+
]
|
84
|
+
return [r for r in values if r is not None]
|
85
|
+
functionalities = {
|
86
|
+
functionality["value"]
|
87
|
+
for functionalities in user["roles_functionalities"].values()
|
88
|
+
for functionality in functionalities
|
89
|
+
if functionality["name"] == name
|
90
|
+
}
|
91
|
+
values = [
|
92
|
+
get_typed(name, functionality_value, types, request, errors)
|
93
|
+
for functionality_value in functionalities
|
94
|
+
]
|
95
|
+
|
96
|
+
return [r for r in values if r is not None]
|
97
|
+
|
98
|
+
|
99
|
+
@_CACHE_REGION_OBJ.cache_on_arguments()
|
100
|
+
def _get_functionalities_type(request: pyramid.request.Request) -> dict[str, dict[str, Any]]:
|
101
|
+
return get_types_map(
|
102
|
+
request.registry.settings.get("admin_interface", {}).get("available_functionalities", [])
|
103
|
+
)
|
104
|
+
|
105
|
+
|
106
|
+
def get_functionality(
|
107
|
+
name: str, request: pyramid.request.Request, is_intranet_: bool
|
108
|
+
) -> list[str | int | float | bool | list[Any] | dict[str, Any]]:
|
109
|
+
"""Get all the functionality for the current user."""
|
110
|
+
result: list[str | int | float | bool | list[Any] | dict[str, Any]] = []
|
111
|
+
errors: set[str] = set()
|
112
|
+
|
113
|
+
if request.user is not None:
|
114
|
+
result = _get_db_functionality(
|
115
|
+
name, _user_to_struct(request.user), _get_functionalities_type(request), request, errors
|
116
|
+
)
|
117
|
+
if not result:
|
118
|
+
result = _get_db_functionality(
|
119
|
+
name,
|
120
|
+
_get_role(request.get_organization_role("registered")),
|
121
|
+
_get_functionalities_type(request),
|
122
|
+
request,
|
123
|
+
errors,
|
124
|
+
)
|
125
|
+
|
126
|
+
if not result and is_intranet_:
|
127
|
+
result = _get_db_functionality(
|
128
|
+
name,
|
129
|
+
_get_role(request.get_organization_role("intranet")),
|
130
|
+
_get_functionalities_type(request),
|
131
|
+
request,
|
132
|
+
errors,
|
133
|
+
)
|
134
|
+
|
135
|
+
if not result:
|
136
|
+
result = _get_db_functionality(
|
137
|
+
name,
|
138
|
+
_get_role(request.get_organization_role("anonymous")),
|
139
|
+
_get_functionalities_type(request),
|
140
|
+
request,
|
141
|
+
errors,
|
142
|
+
)
|
143
|
+
|
144
|
+
if errors != set():
|
145
|
+
_LOG.error("\n".join(errors))
|
146
|
+
return result
|
147
|
+
|
148
|
+
|
149
|
+
def get_mapserver_substitution_params(request: pyramid.request.Request) -> dict[str, str]:
|
150
|
+
"""Get the parameters used by the mapserver substitution."""
|
151
|
+
params: dict[str, str] = {}
|
152
|
+
mss = get_functionality("mapserver_substitution", request, is_intranet(request))
|
153
|
+
if mss:
|
154
|
+
for s_ in mss:
|
155
|
+
s = cast(str, s_)
|
156
|
+
index = s.find("=")
|
157
|
+
if index > 0:
|
158
|
+
attribute = "s_" + s[:index]
|
159
|
+
value = s[index + 1 :]
|
160
|
+
if attribute in params:
|
161
|
+
params[attribute] += "," + value
|
162
|
+
else:
|
163
|
+
params[attribute] = value
|
164
|
+
else:
|
165
|
+
_LOG.warning("Mapserver Substitution '%s' does not respect pattern: <attribute>=<value>", s)
|
166
|
+
return params
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# Copyright (c) 2013-2024, Camptocamp SA
|
2
|
+
# All rights reserved.
|
3
|
+
|
4
|
+
# Redistribution and use in source and binary forms, with or without
|
5
|
+
# modification, are permitted provided that the following conditions are met:
|
6
|
+
|
7
|
+
# 1. Redistributions of source code must retain the above copyright notice, this
|
8
|
+
# list of conditions and the following disclaimer.
|
9
|
+
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
10
|
+
# this list of conditions and the following disclaimer in the documentation
|
11
|
+
# and/or other materials provided with the distribution.
|
12
|
+
|
13
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
14
|
+
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
15
|
+
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
16
|
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
17
|
+
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
18
|
+
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
19
|
+
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
20
|
+
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
21
|
+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
22
|
+
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
23
|
+
|
24
|
+
# The views and conclusions contained in the software and documentation are those
|
25
|
+
# of the authors and should not be interpreted as representing official policies,
|
26
|
+
# either expressed or implied, of the FreeBSD Project.
|
27
|
+
|
28
|
+
|
29
|
+
import re
|
30
|
+
from collections.abc import Callable
|
31
|
+
|
32
|
+
import pyramid.registry
|
33
|
+
import pyramid.request
|
34
|
+
import pyramid.response
|
35
|
+
|
36
|
+
|
37
|
+
class HeadersTween:
|
38
|
+
"""Add the header on all the application."""
|
39
|
+
|
40
|
+
def __init__(
|
41
|
+
self,
|
42
|
+
handler: Callable[[pyramid.request.Request], pyramid.response.Response],
|
43
|
+
registry: pyramid.registry.Registry,
|
44
|
+
) -> None:
|
45
|
+
self.handler = handler
|
46
|
+
self.settings = [
|
47
|
+
(re.compile(e["pattern"]), e["headers"]) for e in registry.settings["global_headers"]
|
48
|
+
]
|
49
|
+
|
50
|
+
def __call__(self, request: pyramid.request.Request) -> pyramid.response.Response:
|
51
|
+
response = self.handler(request)
|
52
|
+
|
53
|
+
for pattern, headers in self.settings:
|
54
|
+
if pattern.match(request.path_info):
|
55
|
+
for header, value in headers.items():
|
56
|
+
if value is None:
|
57
|
+
if header in response.headers:
|
58
|
+
del response.headers[header]
|
59
|
+
else:
|
60
|
+
response.headers[header] = value
|
61
|
+
|
62
|
+
return response
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# Copyright (c) 2020-2023, Camptocamp SA
|
2
|
+
# All rights reserved.
|
3
|
+
|
4
|
+
# Redistribution and use in source and binary forms, with or without
|
5
|
+
# modification, are permitted provided that the following conditions are met:
|
6
|
+
|
7
|
+
# 1. Redistributions of source code must retain the above copyright notice, this
|
8
|
+
# list of conditions and the following disclaimer.
|
9
|
+
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
10
|
+
# this list of conditions and the following disclaimer in the documentation
|
11
|
+
# and/or other materials provided with the distribution.
|
12
|
+
|
13
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
14
|
+
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
15
|
+
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
16
|
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
17
|
+
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
18
|
+
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
19
|
+
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
20
|
+
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
21
|
+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
22
|
+
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
23
|
+
|
24
|
+
# The views and conclusions contained in the software and documentation are those
|
25
|
+
# of the authors and should not be interpreted as representing official policies,
|
26
|
+
# either expressed or implied, of the FreeBSD Project.
|
27
|
+
|
28
|
+
|
29
|
+
import os
|
30
|
+
|
31
|
+
LOCALE_PATH = "/etc/geomapfish/locale/"
|
32
|
+
|
33
|
+
|
34
|
+
def available_locale_names(path: str = LOCALE_PATH) -> list[str]:
|
35
|
+
"""Get the available locales."""
|
36
|
+
if not os.path.exists(path):
|
37
|
+
return []
|
38
|
+
return [d for d in os.listdir(path) if os.path.isdir(os.path.join(path, d))]
|