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,256 @@
|
|
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 datetime
|
30
|
+
import ipaddress
|
31
|
+
import json
|
32
|
+
import logging
|
33
|
+
import re
|
34
|
+
from collections.abc import Iterable
|
35
|
+
from string import Formatter
|
36
|
+
from typing import Any, cast
|
37
|
+
|
38
|
+
import dateutil
|
39
|
+
import pyramid.request
|
40
|
+
import pyramid.response
|
41
|
+
from pyramid.interfaces import IRoutePregenerator
|
42
|
+
from zope.interface import implementer
|
43
|
+
|
44
|
+
from c2cgeoportal_commons.lib.url import get_url2
|
45
|
+
from c2cgeoportal_geoportal.lib.cacheversion import get_cache_version
|
46
|
+
from c2cgeoportal_geoportal.lib.caching import get_region
|
47
|
+
|
48
|
+
_LOG = logging.getLogger(__name__)
|
49
|
+
_CACHE_REGION = get_region("std")
|
50
|
+
_CACHE_REGION_OBJ = get_region("obj")
|
51
|
+
|
52
|
+
|
53
|
+
def get_types_map(types_array: list[dict[str, Any]]) -> dict[str, dict[str, Any]]:
|
54
|
+
"""Get the type name of a metadata or a functionality."""
|
55
|
+
return {type_["name"]: type_ for type_ in types_array}
|
56
|
+
|
57
|
+
|
58
|
+
def get_typed(
|
59
|
+
name: str,
|
60
|
+
value: str,
|
61
|
+
types: dict[str, Any],
|
62
|
+
request: pyramid.request.Request,
|
63
|
+
errors: set[str],
|
64
|
+
layer_name: str | None = None,
|
65
|
+
) -> str | int | float | bool | None | list[Any] | dict[str, Any]:
|
66
|
+
"""Get the typed (parsed) value of a metadata or a functionality."""
|
67
|
+
prefix = f"Layer '{layer_name}': " if layer_name is not None else ""
|
68
|
+
type_ = {"type": "not init"}
|
69
|
+
try:
|
70
|
+
if name not in types:
|
71
|
+
errors.add(f"{prefix}Type '{name}' not defined.")
|
72
|
+
return None
|
73
|
+
type_ = types[name]
|
74
|
+
if type_.get("type", "string") == "string":
|
75
|
+
return value
|
76
|
+
if type_["type"] == "list":
|
77
|
+
return [v.strip() for v in value.split(",")]
|
78
|
+
if type_["type"] == "boolean":
|
79
|
+
value = value.lower()
|
80
|
+
if value in ["yes", "y", "on", "1", "true"]:
|
81
|
+
return True
|
82
|
+
if value in ["no", "n", "off", "0", "false"]:
|
83
|
+
return False
|
84
|
+
errors.add(
|
85
|
+
f"{prefix}The boolean attribute '{name}'='{value.lower()}' is not in "
|
86
|
+
"[yes, y, on, 1, true, no, n, off, 0, false]."
|
87
|
+
)
|
88
|
+
elif type_["type"] == "integer":
|
89
|
+
return int(value)
|
90
|
+
elif type_["type"] == "float":
|
91
|
+
return float(value)
|
92
|
+
elif type_["type"] == "date":
|
93
|
+
date = dateutil.parser.parse(value, default=datetime.datetime(1, 1, 1, 0, 0, 0))
|
94
|
+
if date.time() != datetime.time(0, 0, 0):
|
95
|
+
errors.add(f"{prefix}The date attribute '{name}'='{value}' should not have any time")
|
96
|
+
else:
|
97
|
+
return datetime.date.strftime(date.date(), "%Y-%m-%d")
|
98
|
+
elif type_["type"] == "time":
|
99
|
+
date = dateutil.parser.parse(value, default=datetime.datetime(1, 1, 1, 0, 0, 0))
|
100
|
+
if date.date() != datetime.date(1, 1, 1):
|
101
|
+
errors.add(f"{prefix}The time attribute '{name}'='{value}' should not have any date")
|
102
|
+
else:
|
103
|
+
return datetime.time.strftime(date.time(), "%H:%M:%S")
|
104
|
+
elif type_["type"] == "datetime":
|
105
|
+
date = dateutil.parser.parse(value, default=datetime.datetime(1, 1, 1, 0, 0, 0))
|
106
|
+
return datetime.datetime.strftime(date, "%Y-%m-%dT%H:%M:%S")
|
107
|
+
elif type_["type"] == "url":
|
108
|
+
url = get_url2(f"{prefix}The attribute '{name}'", value, request, errors)
|
109
|
+
return url.url() if url else ""
|
110
|
+
elif type_["type"] == "json":
|
111
|
+
try:
|
112
|
+
return cast(dict[str, Any], json.loads(value))
|
113
|
+
except Exception as e: # pylint: disable=broad-exception-caught
|
114
|
+
errors.add(f"{prefix}The attribute '{name}'='{value}' has an error: {str(e)}")
|
115
|
+
elif type_["type"] == "regex":
|
116
|
+
pattern = type_["regex"]
|
117
|
+
if re.match(pattern, value) is None:
|
118
|
+
errors.add(
|
119
|
+
f"{prefix}The regex attribute '{name}'='{value}' "
|
120
|
+
f"does not match expected pattern '{pattern}'."
|
121
|
+
)
|
122
|
+
else:
|
123
|
+
return value
|
124
|
+
else:
|
125
|
+
errors.add(f"{prefix}Unknown type '{type_['type']}'.")
|
126
|
+
except Exception as e: # pylint: disable=broad-exception-caught
|
127
|
+
errors.add(
|
128
|
+
f"{prefix}Unable to parse the attribute '{name}'='{value}' with the type "
|
129
|
+
f"'{type_.get('type', 'string')}', error:\n{e!s}"
|
130
|
+
)
|
131
|
+
return None
|
132
|
+
|
133
|
+
|
134
|
+
def get_setting(settings: Any, path: Iterable[str], default: Any = None) -> Any:
|
135
|
+
"""Get the settings."""
|
136
|
+
value = settings
|
137
|
+
for p in path:
|
138
|
+
if value and p in value:
|
139
|
+
value = value[p]
|
140
|
+
else:
|
141
|
+
return default
|
142
|
+
return value if value else default
|
143
|
+
|
144
|
+
|
145
|
+
@_CACHE_REGION_OBJ.cache_on_arguments()
|
146
|
+
def get_ogc_server_wms_url_ids(request: pyramid.request.Request, host: str) -> dict[str, list[int]]:
|
147
|
+
"""Get the OGCServer ids mapped on the WMS URL."""
|
148
|
+
from c2cgeoportal_commons.models import DBSession # pylint: disable=import-outside-toplevel
|
149
|
+
from c2cgeoportal_commons.models.main import OGCServer # pylint: disable=import-outside-toplevel
|
150
|
+
|
151
|
+
del host # used for cache
|
152
|
+
assert DBSession is not None
|
153
|
+
|
154
|
+
errors: set[str] = set()
|
155
|
+
servers: dict[str, list[int]] = {}
|
156
|
+
for ogc_server in DBSession.query(OGCServer).all():
|
157
|
+
url = get_url2(ogc_server.name, ogc_server.url, request, errors)
|
158
|
+
if url is not None:
|
159
|
+
servers.setdefault(url.url(), []).append(ogc_server.id)
|
160
|
+
return servers
|
161
|
+
|
162
|
+
|
163
|
+
@_CACHE_REGION_OBJ.cache_on_arguments()
|
164
|
+
def get_ogc_server_wfs_url_ids(request: pyramid.request.Request, host: str) -> dict[str, list[int]]:
|
165
|
+
"""Get the OGCServer ids mapped on the WFS URL."""
|
166
|
+
from c2cgeoportal_commons.models import DBSession # pylint: disable=import-outside-toplevel
|
167
|
+
from c2cgeoportal_commons.models.main import OGCServer # pylint: disable=import-outside-toplevel
|
168
|
+
|
169
|
+
del host # used for cache
|
170
|
+
assert DBSession is not None
|
171
|
+
|
172
|
+
errors: set[str] = set()
|
173
|
+
servers: dict[str, list[int]] = {}
|
174
|
+
for ogc_server in DBSession.query(OGCServer).all():
|
175
|
+
url = get_url2(ogc_server.name, ogc_server.url_wfs or ogc_server.url, request, errors)
|
176
|
+
if url is not None:
|
177
|
+
servers.setdefault(url.url(), []).append(ogc_server.id)
|
178
|
+
return servers
|
179
|
+
|
180
|
+
|
181
|
+
@implementer(IRoutePregenerator)
|
182
|
+
class C2CPregenerator:
|
183
|
+
"""The custom pyramid pregenerator that manage the cache version."""
|
184
|
+
|
185
|
+
def __init__(self, version: bool = True, role: bool = False):
|
186
|
+
self.version = version
|
187
|
+
self.role = role
|
188
|
+
|
189
|
+
def __call__(self, request: pyramid.request.Request, elements: Any, kw: Any) -> tuple[Any, Any]:
|
190
|
+
query = {**kw.get("_query", {})}
|
191
|
+
|
192
|
+
if self.version:
|
193
|
+
query["cache_version"] = get_cache_version()
|
194
|
+
|
195
|
+
if self.role and request.user:
|
196
|
+
# The templates change if the user is logged in or not. Usually it is
|
197
|
+
# the role that is making a difference, but the username is put in
|
198
|
+
# some JS files. So we add the username to hit different cache entries.
|
199
|
+
query["username"] = request.user.username
|
200
|
+
|
201
|
+
kw["_query"] = query
|
202
|
+
return elements, kw
|
203
|
+
|
204
|
+
|
205
|
+
_formatter = Formatter()
|
206
|
+
|
207
|
+
|
208
|
+
@_CACHE_REGION_OBJ.cache_on_arguments()
|
209
|
+
def _get_intranet_networks(
|
210
|
+
request: pyramid.request.Request,
|
211
|
+
) -> list[ipaddress.IPv4Network | ipaddress.IPv6Network]:
|
212
|
+
return [
|
213
|
+
ipaddress.ip_network(network, strict=False)
|
214
|
+
for network in request.registry.settings.get("intranet", {}).get("networks", [])
|
215
|
+
]
|
216
|
+
|
217
|
+
|
218
|
+
@_CACHE_REGION.cache_on_arguments()
|
219
|
+
def get_role_id(name: str) -> int:
|
220
|
+
"""Get the role ID."""
|
221
|
+
from c2cgeoportal_commons.models import DBSession, main # pylint: disable=import-outside-toplevel
|
222
|
+
|
223
|
+
assert DBSession is not None
|
224
|
+
|
225
|
+
return cast(int, DBSession.query(main.Role.id).filter(main.Role.name == name).one()[0])
|
226
|
+
|
227
|
+
|
228
|
+
def get_roles_id(request: pyramid.request.Request) -> list[int]:
|
229
|
+
"""Get the user roles ID."""
|
230
|
+
result = [get_role_id(request.get_organization_role("anonymous"))]
|
231
|
+
if is_intranet(request):
|
232
|
+
result.append(get_role_id(request.get_organization_role("intranet")))
|
233
|
+
if request.user is not None:
|
234
|
+
result.append(get_role_id(request.get_organization_role("registered")))
|
235
|
+
result.extend([r.id for r in request.user.roles])
|
236
|
+
return result
|
237
|
+
|
238
|
+
|
239
|
+
def get_roles_name(request: pyramid.request.Request) -> pyramid.response.Response:
|
240
|
+
"""Get the user roles name."""
|
241
|
+
result = [request.get_organization_role("anonymous")]
|
242
|
+
if is_intranet(request):
|
243
|
+
result.append(request.get_organization_role("intranet"))
|
244
|
+
if request.user is not None:
|
245
|
+
result.append(request.get_organization_role("registered"))
|
246
|
+
result.extend([r.name for r in request.user.roles])
|
247
|
+
return result
|
248
|
+
|
249
|
+
|
250
|
+
def is_intranet(request: pyramid.request.Request) -> bool:
|
251
|
+
"""Get if it's an intranet user."""
|
252
|
+
address = ipaddress.ip_address(request.client_addr)
|
253
|
+
for network in _get_intranet_networks(request):
|
254
|
+
if address in network:
|
255
|
+
return True
|
256
|
+
return False
|
@@ -0,0 +1,250 @@
|
|
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 binascii
|
30
|
+
import json
|
31
|
+
import logging
|
32
|
+
import os
|
33
|
+
import re
|
34
|
+
import time
|
35
|
+
from collections.abc import Callable
|
36
|
+
from typing import Any, cast
|
37
|
+
|
38
|
+
import pyramid.request
|
39
|
+
from Crypto.Cipher import AES # nosec
|
40
|
+
from pyramid.authentication import (
|
41
|
+
AuthTktAuthenticationPolicy,
|
42
|
+
BasicAuthAuthenticationPolicy,
|
43
|
+
CallbackAuthenticationPolicy,
|
44
|
+
)
|
45
|
+
from pyramid.interfaces import IAuthenticationPolicy
|
46
|
+
from pyramid.security import remember
|
47
|
+
from pyramid_multiauth import MultiAuthenticationPolicy
|
48
|
+
from zope.interface import implementer
|
49
|
+
|
50
|
+
from c2cgeoportal_geoportal.lib import oauth2
|
51
|
+
from c2cgeoportal_geoportal.resources import defaultgroupsfinder
|
52
|
+
|
53
|
+
_LOG = logging.getLogger(__name__)
|
54
|
+
|
55
|
+
_HEX_RE = re.compile(r"^[0-9a-fA-F]+$")
|
56
|
+
|
57
|
+
|
58
|
+
@implementer(IAuthenticationPolicy)
|
59
|
+
class UrlAuthenticationPolicy(CallbackAuthenticationPolicy): # type: ignore
|
60
|
+
"""An authentication policy based on information given in the URL."""
|
61
|
+
|
62
|
+
def __init__(
|
63
|
+
self, aes_key: str, callback: Callable[[str, Any], list[str]] | None = None, debug: bool = False
|
64
|
+
):
|
65
|
+
self.aeskey = aes_key
|
66
|
+
self.callback = callback
|
67
|
+
self.debug = debug
|
68
|
+
|
69
|
+
def unauthenticated_userid(self, request: pyramid.request.Request) -> str | None:
|
70
|
+
if not request.method == "GET" or "auth" not in request.params:
|
71
|
+
return None
|
72
|
+
auth_enc = request.params.get("auth")
|
73
|
+
if auth_enc is None:
|
74
|
+
return None
|
75
|
+
try:
|
76
|
+
if self.aeskey is None:
|
77
|
+
_LOG.warning("Found auth parameter in URL query string but urllogin is not configured")
|
78
|
+
return None
|
79
|
+
|
80
|
+
if not _HEX_RE.match(auth_enc):
|
81
|
+
_LOG.warning("Found auth parameter in URL query string but it is not an hex string")
|
82
|
+
return None
|
83
|
+
|
84
|
+
if len(auth_enc) % 2 != 0:
|
85
|
+
_LOG.warning(
|
86
|
+
"Found auth parameter in URL query string but it is not an even number of characters"
|
87
|
+
)
|
88
|
+
return None
|
89
|
+
|
90
|
+
now = int(time.time())
|
91
|
+
data = binascii.unhexlify(auth_enc.encode("ascii"))
|
92
|
+
nonce = data[0:16]
|
93
|
+
tag = data[16:32]
|
94
|
+
ciphertext = data[32:]
|
95
|
+
cipher = AES.new(self.aeskey.encode("ascii"), AES.MODE_EAX, nonce)
|
96
|
+
auth = json.loads(cipher.decrypt_and_verify(ciphertext, tag).decode("utf-8"))
|
97
|
+
|
98
|
+
if "t" in auth and "u" in auth and "p" in auth:
|
99
|
+
timestamp = int(auth["t"])
|
100
|
+
|
101
|
+
if now < timestamp and request.registry.validate_user(request, auth["u"], auth["p"]):
|
102
|
+
headers = remember(request, auth["u"])
|
103
|
+
request.response.headerlist.extend(headers)
|
104
|
+
return cast(str, auth["u"])
|
105
|
+
|
106
|
+
except Exception: # pylint: disable=broad-exception-caught
|
107
|
+
_LOG.exception("URL login error on auth '%s'.", auth_enc)
|
108
|
+
|
109
|
+
return None
|
110
|
+
|
111
|
+
def remember(self, request: pyramid.request.Request, userid: str, **kw: Any) -> list[dict[str, str]]:
|
112
|
+
"""Do no-op."""
|
113
|
+
del request, userid, kw
|
114
|
+
return []
|
115
|
+
|
116
|
+
def forget(self, request: pyramid.request.Request) -> list[dict[str, str]]:
|
117
|
+
"""Do no-op."""
|
118
|
+
del request
|
119
|
+
return []
|
120
|
+
|
121
|
+
|
122
|
+
@implementer(IAuthenticationPolicy)
|
123
|
+
class OAuth2AuthenticationPolicy(CallbackAuthenticationPolicy): # type: ignore
|
124
|
+
"""The oauth2 authentication policy."""
|
125
|
+
|
126
|
+
@staticmethod
|
127
|
+
def unauthenticated_userid(request: pyramid.request.Request) -> str | None:
|
128
|
+
route_url = ""
|
129
|
+
try:
|
130
|
+
route_url = request.current_route_url(_query={**request.GET})
|
131
|
+
except ValueError:
|
132
|
+
route_url = request.route_url("base", _query={**request.GET})
|
133
|
+
|
134
|
+
_LOG.debug(
|
135
|
+
"Call OAuth verify_request with:\nurl: %s\nmethod: %s\nbody:\n%s",
|
136
|
+
route_url,
|
137
|
+
request.method,
|
138
|
+
request.body,
|
139
|
+
)
|
140
|
+
valid, oauth2_request = oauth2.get_oauth_client(request.registry.settings).verify_request(
|
141
|
+
route_url,
|
142
|
+
request.method,
|
143
|
+
request.body,
|
144
|
+
request.headers,
|
145
|
+
[],
|
146
|
+
)
|
147
|
+
_LOG.debug("OAuth verify_request: %s", valid)
|
148
|
+
if valid:
|
149
|
+
request.user_ = oauth2_request.user
|
150
|
+
|
151
|
+
return cast(str, request.user.username)
|
152
|
+
return None
|
153
|
+
|
154
|
+
def remember(self, request: pyramid.request.Request, userid: str, **kw: Any) -> list[dict[str, str]]:
|
155
|
+
"""Do no-op."""
|
156
|
+
del request, userid, kw
|
157
|
+
return []
|
158
|
+
|
159
|
+
def forget(self, request: pyramid.request.Request) -> list[dict[str, str]]:
|
160
|
+
"""Do no-op."""
|
161
|
+
del request
|
162
|
+
return []
|
163
|
+
|
164
|
+
|
165
|
+
@implementer(IAuthenticationPolicy)
|
166
|
+
class DevAuthenticationPolicy(CallbackAuthenticationPolicy): # type: ignore
|
167
|
+
"""An authentication policy for the dev base on an environment variable."""
|
168
|
+
|
169
|
+
@staticmethod
|
170
|
+
def unauthenticated_userid(request: pyramid.request.Request) -> str | None:
|
171
|
+
"""Get the user name from the environment variable."""
|
172
|
+
del request
|
173
|
+
return os.environ["DEV_LOGINNAME"]
|
174
|
+
|
175
|
+
|
176
|
+
def create_authentication(settings: dict[str, Any]) -> MultiAuthenticationPolicy:
|
177
|
+
"""Create all the authentication policies."""
|
178
|
+
timeout = settings.get("authtkt_timeout")
|
179
|
+
timeout = None if timeout is None or timeout.lower() == "none" else int(timeout)
|
180
|
+
reissue_time = settings.get("authtkt_reissue_time")
|
181
|
+
reissue_time = None if reissue_time is None or reissue_time.lower() == "none" else int(reissue_time)
|
182
|
+
max_age = settings.get("authtkt_max_age")
|
183
|
+
max_age = None if max_age is None or max_age.lower() == "none" else int(max_age)
|
184
|
+
http_only = settings.get("authtkt_http_only", "True")
|
185
|
+
http_only = http_only.lower() in ("true", "yes", "1")
|
186
|
+
secure = settings.get("authtkt_secure", "True")
|
187
|
+
secure = secure.lower() in ("true", "yes", "1")
|
188
|
+
samesite = settings.get("authtkt_samesite", "Lax")
|
189
|
+
secret = settings["authtkt_secret"]
|
190
|
+
basicauth = settings.get("basicauth", "False").lower() in ("true", "yes", "1")
|
191
|
+
if len(secret) < 64:
|
192
|
+
raise Exception( # pylint: disable=broad-exception-raised
|
193
|
+
'"authtkt_secret should be at least 64 characters.'
|
194
|
+
"See https://docs.pylonsproject.org/projects/pyramid/en/latest/api/session.html"
|
195
|
+
)
|
196
|
+
|
197
|
+
policies = []
|
198
|
+
|
199
|
+
policies.append(
|
200
|
+
UrlAuthenticationPolicy(
|
201
|
+
settings.get("urllogin", {}).get("aes_key"),
|
202
|
+
defaultgroupsfinder,
|
203
|
+
)
|
204
|
+
)
|
205
|
+
|
206
|
+
policies.append(
|
207
|
+
AuthTktAuthenticationPolicy(
|
208
|
+
secret,
|
209
|
+
callback=defaultgroupsfinder,
|
210
|
+
cookie_name=settings["authtkt_cookie_name"],
|
211
|
+
samesite=None if samesite == "" else samesite,
|
212
|
+
timeout=timeout,
|
213
|
+
max_age=max_age,
|
214
|
+
reissue_time=reissue_time,
|
215
|
+
hashalg="sha512",
|
216
|
+
http_only=http_only,
|
217
|
+
secure=secure,
|
218
|
+
)
|
219
|
+
)
|
220
|
+
|
221
|
+
authentication_config = settings.get("authentication", {})
|
222
|
+
openid_connect_config = authentication_config.get("openid_connect", {})
|
223
|
+
oauth2_config = authentication_config.get("oauth2", {})
|
224
|
+
if oauth2_config.get("enabled", not openid_connect_config.get("enabled", False)):
|
225
|
+
policies.append(OAuth2AuthenticationPolicy())
|
226
|
+
|
227
|
+
if basicauth:
|
228
|
+
if authentication_config.get("two_factor", False):
|
229
|
+
_LOG.warning(
|
230
|
+
"Basic auth and two factor auth should not be enable together, "
|
231
|
+
"you should use OAuth2 instead of Basic auth"
|
232
|
+
)
|
233
|
+
if openid_connect_config.get("enabled", False):
|
234
|
+
_LOG.warning("Basic auth and OpenID Connect should not be enable together")
|
235
|
+
|
236
|
+
basic_authentication_policy = BasicAuthAuthenticationPolicy(c2cgeoportal_check)
|
237
|
+
policies.append(basic_authentication_policy)
|
238
|
+
|
239
|
+
# Consider empty string as not configured
|
240
|
+
if "DEV_LOGINNAME" in os.environ and os.environ["DEV_LOGINNAME"]:
|
241
|
+
policies.append(DevAuthenticationPolicy())
|
242
|
+
|
243
|
+
return MultiAuthenticationPolicy(policies)
|
244
|
+
|
245
|
+
|
246
|
+
def c2cgeoportal_check(username: str, password: str, request: pyramid.request.Request) -> list[str] | None:
|
247
|
+
"""Check the user authentication."""
|
248
|
+
if request.registry.validate_user(request, username, password):
|
249
|
+
return defaultgroupsfinder(username, request)
|
250
|
+
return None
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# Copyright (c) 2013-2021, 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
|
+
from enum import Enum
|
29
|
+
|
30
|
+
|
31
|
+
class Color(Enum):
|
32
|
+
"""The available colors."""
|
33
|
+
|
34
|
+
BLACK = 0
|
35
|
+
RED = 1
|
36
|
+
GREEN = 2
|
37
|
+
YELLOW = 3
|
38
|
+
BLUE = 4
|
39
|
+
MAGENTA = 5
|
40
|
+
CYAN = 6
|
41
|
+
WHITE = 7
|
42
|
+
|
43
|
+
|
44
|
+
def colorize(text: str, color: Color) -> str:
|
45
|
+
"""Colorize a text for the bash."""
|
46
|
+
return f"\x1b[01;3{color.value}m{text}\x1b[0m"
|
@@ -0,0 +1,77 @@
|
|
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 uuid
|
30
|
+
from collections.abc import Callable
|
31
|
+
from typing import Any
|
32
|
+
from urllib.parse import urljoin
|
33
|
+
|
34
|
+
import pyramid.registry
|
35
|
+
import pyramid.request
|
36
|
+
import pyramid.response
|
37
|
+
|
38
|
+
from c2cgeoportal_geoportal.lib.caching import get_region
|
39
|
+
|
40
|
+
CACHE_REGION = get_region("std")
|
41
|
+
|
42
|
+
|
43
|
+
@CACHE_REGION.cache_on_arguments()
|
44
|
+
def get_cache_version() -> str:
|
45
|
+
"""Return a cache version that is regenerate after each cache invalidation."""
|
46
|
+
return uuid.uuid4().hex
|
47
|
+
|
48
|
+
|
49
|
+
def version_cache_buster(
|
50
|
+
request: pyramid.request.Request, subpath: str, kw: dict[str, Any]
|
51
|
+
) -> tuple[str, dict[str, Any]]:
|
52
|
+
"""Join the cash buster version with the sub path."""
|
53
|
+
del request # unused
|
54
|
+
return urljoin(get_cache_version() + "/", subpath), kw
|
55
|
+
|
56
|
+
|
57
|
+
class CachebusterTween:
|
58
|
+
"""Get back the cachebuster URL."""
|
59
|
+
|
60
|
+
def __init__(
|
61
|
+
self,
|
62
|
+
handler: Callable[[pyramid.request.Request], pyramid.response.Response],
|
63
|
+
registry: pyramid.registry.Registry,
|
64
|
+
):
|
65
|
+
self.handler = handler
|
66
|
+
self.cache_path = registry.settings["cache_path"]
|
67
|
+
|
68
|
+
def __call__(self, request: pyramid.request.Request) -> pyramid.response.Response:
|
69
|
+
path = request.path_info.split("/", 3)
|
70
|
+
if len(path) > 1 and path[1] in self.cache_path:
|
71
|
+
if len(path) == 2:
|
72
|
+
raise pyramid.httpexceptions.HTTPNotFound()
|
73
|
+
# Remove the cache buster
|
74
|
+
path.pop(2)
|
75
|
+
request.path_info = "/".join(path)
|
76
|
+
|
77
|
+
return self.handler(request)
|