c2cgeoportal-geoportal 2.6.0__py2.py3-none-any.whl → 2.8.1.180__py2.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 +245 -95
- c2cgeoportal_geoportal/lib/__init__.py +67 -43
- c2cgeoportal_geoportal/lib/authentication.py +50 -26
- c2cgeoportal_geoportal/lib/bashcolor.py +17 -13
- c2cgeoportal_geoportal/lib/cacheversion.py +16 -8
- c2cgeoportal_geoportal/lib/caching.py +65 -193
- c2cgeoportal_geoportal/lib/check_collector.py +17 -10
- c2cgeoportal_geoportal/lib/checker.py +67 -65
- c2cgeoportal_geoportal/lib/common_headers.py +167 -0
- c2cgeoportal_geoportal/lib/dbreflection.py +61 -46
- c2cgeoportal_geoportal/lib/filter_capabilities.py +126 -88
- c2cgeoportal_geoportal/lib/fulltextsearch.py +6 -5
- c2cgeoportal_geoportal/lib/functionality.py +20 -17
- c2cgeoportal_geoportal/lib/headers.py +14 -5
- c2cgeoportal_geoportal/lib/i18n.py +4 -4
- c2cgeoportal_geoportal/lib/layers.py +30 -11
- c2cgeoportal_geoportal/lib/lingua_extractor.py +363 -240
- c2cgeoportal_geoportal/lib/loader.py +11 -16
- c2cgeoportal_geoportal/lib/metrics.py +28 -17
- c2cgeoportal_geoportal/lib/oauth2.py +392 -206
- c2cgeoportal_geoportal/lib/wmstparsing.py +105 -84
- c2cgeoportal_geoportal/lib/xsd.py +26 -16
- c2cgeoportal_geoportal/resources.py +15 -9
- 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/{create/geoportal/+dot+prospector.yaml → advance_create/{{cookiecutter.project}}/geoportal/.prospector.yaml} +8 -2
- c2cgeoportal_geoportal/scaffolds/{create/geoportal/Dockerfile_tmpl → advance_create/{{cookiecutter.project}}/geoportal/Dockerfile} +22 -15
- c2cgeoportal_geoportal/scaffolds/{create/geoportal/alembic.yaml_tmpl → advance_create/{{cookiecutter.project}}/geoportal/alembic.yaml} +1 -1
- c2cgeoportal_geoportal/scaffolds/{create/geoportal/development.ini_tmpl → advance_create/{{cookiecutter.project}}/geoportal/development.ini} +34 -15
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/gunicorn.conf.py +100 -0
- c2cgeoportal_geoportal/scaffolds/{create → advance_create/{{cookiecutter.project}}}/geoportal/lingua-client.cfg +1 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/production.ini +38 -0
- c2cgeoportal_geoportal/scaffolds/{create/geoportal/setup.py_tmpl → advance_create/{{cookiecutter.project}}/geoportal/setup.py} +6 -7
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/tsconfig.json +8 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/webpack.api.js +77 -0
- c2cgeoportal_geoportal/scaffolds/{create/geoportal/webpack.apps.js_tmpl → advance_create/{{cookiecutter.project}}/geoportal/webpack.apps.js} +29 -28
- c2cgeoportal_geoportal/scaffolds/{create → advance_create/{{cookiecutter.project}}}/geoportal/webpack.commons.js +4 -7
- c2cgeoportal_geoportal/scaffolds/{create → advance_create/{{cookiecutter.project}}}/geoportal/webpack.config.js +1 -1
- 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/{create/geoportal/+package+_geoportal → advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal}/resources.py +4 -3
- c2cgeoportal_geoportal/scaffolds/{create/geoportal/+package+_geoportal/static-ngeo/api/index.js_tmpl → advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static-ngeo/api/index.js} +1 -2
- c2cgeoportal_geoportal/scaffolds/{create/geoportal/+package+_geoportal/static-ngeo/js/+package+module.js_tmpl → advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static-ngeo/js/{{cookiecutter.package}}module.js} +4 -4
- c2cgeoportal_geoportal/scaffolds/{create/geoportal/+package+_geoportal/subscribers.py_tmpl → advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/subscribers.py} +1 -3
- c2cgeoportal_geoportal/scaffolds/advance_update/cookiecutter.json +18 -0
- c2cgeoportal_geoportal/scaffolds/{update/geoportal/CONST_Makefile_tmpl → advance_update/{{cookiecutter.project}}/geoportal/CONST_Makefile} +3 -27
- c2cgeoportal_geoportal/scaffolds/create/cookiecutter.json +18 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.dockerignore +14 -0
- c2cgeoportal_geoportal/scaffolds/create/{+dot+editorconfig → {{cookiecutter.project}}/.editorconfig} +2 -5
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.github/workflows/main.yaml +57 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.github/workflows/rebuild.yaml +46 -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}}/.prettierignore +1 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.prettierrc.yaml +2 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/Dockerfile +76 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/Makefile +70 -0
- c2cgeoportal_geoportal/scaffolds/create/{README.rst_tmpl → {{cookiecutter.project}}/README.rst} +4 -4
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/build +186 -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 +1 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose-db.yaml +26 -0
- c2cgeoportal_geoportal/scaffolds/create/{docker-compose-lib.yaml → {{cookiecutter.project}}/docker-compose-lib.yaml} +165 -22
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose-qgis.yaml +23 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose.override.sample.yaml +66 -0
- c2cgeoportal_geoportal/scaffolds/create/{docker-compose.yaml → {{cookiecutter.project}}/docker-compose.yaml} +20 -15
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/env.default +101 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/env.project +69 -0
- c2cgeoportal_geoportal/scaffolds/create/{geoportal/vars.yaml_tmpl → {{cookiecutter.project}}/geoportal/vars.yaml} +126 -36
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/css/mobile.css +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/data/Readme.txt +3 -3
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/demo.map.tmpl +224 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/mapserver.conf +15 -0
- c2cgeoportal_geoportal/scaffolds/create/{mapserver/mapserver.map.tmpl_tmpl → {{cookiecutter.project}}/mapserver/mapserver.map.tmpl} +9 -18
- c2cgeoportal_geoportal/scaffolds/create/{print/print-apps/+package+ → {{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}}/A3_Landscape.jrxml +13 -8
- c2cgeoportal_geoportal/scaffolds/create/{print/print-apps/+package+ → {{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}}/A3_Portrait.jrxml +13 -8
- c2cgeoportal_geoportal/scaffolds/create/{print/print-apps/+package+ → {{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}}/A4_Landscape.jrxml +13 -8
- c2cgeoportal_geoportal/scaffolds/create/{print/print-apps/+package+ → {{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}}/A4_Portrait.jrxml +13 -8
- c2cgeoportal_geoportal/scaffolds/create/{print/print-apps/+package+ → {{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}}/config.yaml.tmpl +11 -4
- 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/{project.yaml_tmpl → {{cookiecutter.project}}/project.yaml} +6 -6
- c2cgeoportal_geoportal/scaffolds/create/{pyproject.toml → {{cookiecutter.project}}/pyproject.toml} +4 -0
- c2cgeoportal_geoportal/scaffolds/create/{qgisserver/pg_service.conf.tmpl_tmpl → {{cookiecutter.project}}/qgisserver/pg_service.conf.tmpl} +2 -2
- c2cgeoportal_geoportal/scaffolds/create/{run_alembic.sh → {{cookiecutter.project}}/run_alembic.sh} +3 -5
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/scripts/db-backup +110 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/scripts/db-restore +114 -0
- c2cgeoportal_geoportal/scaffolds/create/{setup.cfg_tmpl → {{cookiecutter.project}}/setup.cfg} +1 -1
- 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 +38 -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 +61 -0
- c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/CONST_CHANGELOG.txt +273 -0
- c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/CONST_create_template/tests/test_testapp.py +48 -0
- c2cgeoportal_geoportal/scaffolds/update/{geoportal → {{cookiecutter.project}}/geoportal}/CONST_config-schema.yaml +64 -17
- c2cgeoportal_geoportal/scaffolds/update/{geoportal/CONST_vars.yaml_tmpl → {{cookiecutter.project}}/geoportal/CONST_vars.yaml} +396 -19
- c2cgeoportal_geoportal/scripts/__init__.py +16 -30
- c2cgeoportal_geoportal/scripts/c2cupgrade.py +272 -234
- c2cgeoportal_geoportal/scripts/create_demo_theme.py +3 -6
- c2cgeoportal_geoportal/scripts/manage_users.py +34 -39
- c2cgeoportal_geoportal/scripts/pcreate.py +310 -0
- c2cgeoportal_geoportal/scripts/theme2fts.py +128 -24
- c2cgeoportal_geoportal/scripts/urllogin.py +19 -11
- c2cgeoportal_geoportal/templates/login.html +88 -84
- c2cgeoportal_geoportal/templates/notlogin.html +59 -59
- c2cgeoportal_geoportal/templates/testi18n.html +6 -8
- c2cgeoportal_geoportal/views/__init__.py +23 -6
- c2cgeoportal_geoportal/views/dev.py +9 -7
- c2cgeoportal_geoportal/views/dynamic.py +56 -19
- c2cgeoportal_geoportal/views/entry.py +85 -24
- c2cgeoportal_geoportal/views/fulltextsearch.py +29 -23
- c2cgeoportal_geoportal/views/geometry_processing.py +17 -9
- c2cgeoportal_geoportal/views/i18n.py +91 -9
- c2cgeoportal_geoportal/views/layers.py +166 -133
- c2cgeoportal_geoportal/views/login.py +161 -93
- c2cgeoportal_geoportal/views/mapserverproxy.py +47 -31
- c2cgeoportal_geoportal/views/memory.py +12 -12
- c2cgeoportal_geoportal/views/ogcproxy.py +52 -30
- c2cgeoportal_geoportal/views/pdfreport.py +30 -26
- c2cgeoportal_geoportal/views/printproxy.py +60 -52
- c2cgeoportal_geoportal/views/profile.py +24 -23
- c2cgeoportal_geoportal/views/proxy.py +88 -72
- c2cgeoportal_geoportal/views/raster.py +37 -26
- c2cgeoportal_geoportal/views/resourceproxy.py +13 -11
- c2cgeoportal_geoportal/views/shortener.py +27 -25
- c2cgeoportal_geoportal/views/theme.py +472 -332
- c2cgeoportal_geoportal/views/tinyowsproxy.py +42 -44
- c2cgeoportal_geoportal/views/vector_tiles.py +80 -0
- {c2cgeoportal_geoportal-2.6.0.dist-info → c2cgeoportal_geoportal-2.8.1.180.dist-info}/METADATA +19 -8
- c2cgeoportal_geoportal-2.8.1.180.dist-info/RECORD +191 -0
- {c2cgeoportal_geoportal-2.6.0.dist-info → c2cgeoportal_geoportal-2.8.1.180.dist-info}/WHEEL +1 -1
- {c2cgeoportal_geoportal-2.6.0.dist-info → c2cgeoportal_geoportal-2.8.1.180.dist-info}/entry_points.txt +3 -0
- tests/__init__.py +10 -5
- tests/test_cachebuster.py +3 -5
- tests/test_caching.py +18 -26
- tests/test_checker.py +1 -3
- tests/test_decimaljson.py +5 -5
- tests/test_headerstween.py +1 -3
- tests/test_i18n.py +2 -2
- tests/test_init.py +16 -20
- tests/test_locale_negociator.py +4 -6
- tests/test_mapserverproxy_route_predicate.py +1 -4
- tests/test_raster.py +15 -17
- tests/test_wmstparsing.py +10 -12
- tests/xmlstr.py +1 -3
- c2cgeoportal_geoportal/scaffolds/__init__.py +0 -227
- c2cgeoportal_geoportal/scaffolds/create/+dot+dockerignore_tmpl +0 -12
- c2cgeoportal_geoportal/scaffolds/create/+dot+github/workflows/main.yaml_tmpl +0 -89
- c2cgeoportal_geoportal/scaffolds/create/+dot+github/workflows/rebuild.yaml_tmpl +0 -78
- c2cgeoportal_geoportal/scaffolds/create/+dot+gitignore_tmpl +0 -16
- c2cgeoportal_geoportal/scaffolds/create/Dockerfile_tmpl +0 -67
- c2cgeoportal_geoportal/scaffolds/create/Makefile +0 -3
- c2cgeoportal_geoportal/scaffolds/create/build_tmpl +0 -167
- c2cgeoportal_geoportal/scaffolds/create/ci/config.yaml_tmpl +0 -23
- c2cgeoportal_geoportal/scaffolds/create/ci/requirements.txt +0 -1
- c2cgeoportal_geoportal/scaffolds/create/ci/trigger +0 -68
- c2cgeoportal_geoportal/scaffolds/create/docker-compose.override.sample.yaml +0 -54
- c2cgeoportal_geoportal/scaffolds/create/env.default_tmpl +0 -67
- c2cgeoportal_geoportal/scaffolds/create/env.project_tmpl +0 -48
- c2cgeoportal_geoportal/scaffolds/create/geoportal/+dot+dockerignore_tmpl +0 -6
- c2cgeoportal_geoportal/scaffolds/create/geoportal/+dot+eslintrc_tmpl +0 -15
- c2cgeoportal_geoportal/scaffolds/create/geoportal/+package+_geoportal/__init__.py_tmpl +0 -58
- c2cgeoportal_geoportal/scaffolds/create/geoportal/+package+_geoportal/models.py_tmpl +0 -10
- c2cgeoportal_geoportal/scaffolds/create/geoportal/+package+_geoportal/static/robot.txt +0 -3
- c2cgeoportal_geoportal/scaffolds/create/geoportal/production.ini_tmpl +0 -106
- c2cgeoportal_geoportal/scaffolds/create/geoportal/tools/extract-messages.js +0 -39
- c2cgeoportal_geoportal/scaffolds/create/geoportal/tsconfig.json_tmpl +0 -9
- c2cgeoportal_geoportal/scaffolds/create/geoportal/webpack.api.js_tmpl +0 -72
- c2cgeoportal_geoportal/scaffolds/create/mapserver/demo.map.tmpl_tmpl +0 -262
- c2cgeoportal_geoportal/scaffolds/create/mapserver/tinyows.xml +0 -36
- c2cgeoportal_geoportal/scaffolds/create/print/print-apps/+package+/config.yaml +0 -168
- c2cgeoportal_geoportal/scaffolds/create/qgisserver/geomapfish.yaml.tmpl_tmpl +0 -16
- c2cgeoportal_geoportal/scaffolds/create/spell-ignore-words.txt +0 -1
- c2cgeoportal_geoportal/scaffolds/create/tilegeneration/config.yaml.tmpl_tmpl +0 -185
- c2cgeoportal_geoportal/scaffolds/create/yamllint.yaml +0 -11
- c2cgeoportal_geoportal/scaffolds/update/+dot+upgrade.yaml_tmpl +0 -181
- c2cgeoportal_geoportal/scaffolds/update/CONST_CHANGELOG.txt_tmpl +0 -454
- c2cgeoportal_geoportal/templates/dynamic.js +0 -21
- c2cgeoportal_geoportal-2.6.0.dist-info/RECORD +0 -173
- /c2cgeoportal_geoportal/{scaffolds/create/geoportal/+package+_geoportal/static/css/desktop.css → py.typed} +0 -0
- /c2cgeoportal_geoportal/scaffolds/{create/geoportal/Makefile_tmpl → advance_create/{{cookiecutter.project}}/geoportal/Makefile} +0 -0
- /c2cgeoportal_geoportal/scaffolds/{create → advance_create/{{cookiecutter.project}}}/geoportal/alembic.ini +0 -0
- /c2cgeoportal_geoportal/scaffolds/{create → advance_create/{{cookiecutter.project}}}/geoportal/language_mapping +0 -0
- /c2cgeoportal_geoportal/scaffolds/{create → advance_create/{{cookiecutter.project}}}/geoportal/lingua-server.cfg +0 -0
- /c2cgeoportal_geoportal/scaffolds/{create → advance_create/{{cookiecutter.project}}}/geoportal/requirements.txt +0 -0
- /c2cgeoportal_geoportal/scaffolds/{create/geoportal/+package+_geoportal → advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal}/views/__init__.py +0 -0
- /c2cgeoportal_geoportal/scaffolds/create/{geoportal/+package+_geoportal/locale/en/LC_MESSAGES/+package+_geoportal-client.po → {{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/locale/en/LC_MESSAGES/{{cookiecutter.package}}_geoportal-client.po} +0 -0
- /c2cgeoportal_geoportal/scaffolds/create/{geoportal/+package+_geoportal/static/css/iframe_api.css → {{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/css/desktop.css} +0 -0
- /c2cgeoportal_geoportal/scaffolds/create/{geoportal/+package+_geoportal/static/css/mobile.css → {{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/css/iframe_api.css} +0 -0
- /c2cgeoportal_geoportal/scaffolds/create/{geoportal/+package+_geoportal → {{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal}/static/images/banner_left.png +0 -0
- /c2cgeoportal_geoportal/scaffolds/create/{geoportal/+package+_geoportal → {{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal}/static/images/banner_right.png +0 -0
- /c2cgeoportal_geoportal/scaffolds/create/{geoportal/+package+_geoportal → {{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal}/static/images/blank.png +0 -0
- /c2cgeoportal_geoportal/scaffolds/create/{geoportal/+package+_geoportal → {{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal}/static/images/markers/marker-blue.png +0 -0
- /c2cgeoportal_geoportal/scaffolds/create/{geoportal/+package+_geoportal → {{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal}/static/images/markers/marker-gold.png +0 -0
- /c2cgeoportal_geoportal/scaffolds/create/{geoportal/+package+_geoportal → {{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal}/static/images/markers/marker-green.png +0 -0
- /c2cgeoportal_geoportal/scaffolds/create/{geoportal/+package+_geoportal → {{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal}/static/images/markers/marker.png +0 -0
- /c2cgeoportal_geoportal/scaffolds/create/{geoportal/+package+_geoportal → {{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal}/static/robot.txt.tmpl +0 -0
- /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/data/TM_EUROPE_BORDERS-0.3.sql +0 -0
- /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/Arial.ttf +0 -0
- /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/Arialbd.ttf +0 -0
- /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/Arialbi.ttf +0 -0
- /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/Ariali.ttf +0 -0
- /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/NotoSans-Bold.ttf +0 -0
- /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/NotoSans-BoldItalic.ttf +0 -0
- /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/NotoSans-Italic.ttf +0 -0
- /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/NotoSans-Regular.ttf +0 -0
- /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/Verdana.ttf +0 -0
- /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/Verdanab.ttf +0 -0
- /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/Verdanai.ttf +0 -0
- /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts/Verdanaz.ttf +0 -0
- /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/fonts.conf +0 -0
- /c2cgeoportal_geoportal/scaffolds/create/{mapserver → {{cookiecutter.project}}/mapserver}/tinyows.xml.tmpl +0 -0
- /c2cgeoportal_geoportal/scaffolds/create/{print/print-apps/+package+ → {{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}}/legend.jrxml +0 -0
- /c2cgeoportal_geoportal/scaffolds/create/{print/print-apps/+package+ → {{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}}/logo.png +0 -0
- /c2cgeoportal_geoportal/scaffolds/create/{print/print-apps/+package+ → {{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}}/north.svg +0 -0
- /c2cgeoportal_geoportal/scaffolds/create/{print/print-apps/+package+ → {{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}}/results.jrxml +0 -0
- {c2cgeoportal_geoportal-2.6.0.dist-info → c2cgeoportal_geoportal-2.8.1.180.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,4 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
# Copyright (c) 2011-2021, Camptocamp SA
|
1
|
+
# Copyright (c) 2011-2024, Camptocamp SA
|
4
2
|
# All rights reserved.
|
5
3
|
|
6
4
|
# Redistribution and use in source and binary forms, with or without
|
@@ -31,11 +29,13 @@
|
|
31
29
|
import json
|
32
30
|
import logging
|
33
31
|
import sys
|
34
|
-
import
|
32
|
+
import urllib.parse
|
35
33
|
from random import Random
|
36
|
-
from typing import Dict,
|
34
|
+
from typing import Any, Dict, List, Optional, Tuple, Union
|
37
35
|
|
38
36
|
import pyotp
|
37
|
+
import pyramid.request
|
38
|
+
import pyramid.response
|
39
39
|
from pyramid.httpexceptions import (
|
40
40
|
HTTPBadRequest,
|
41
41
|
HTTPForbidden,
|
@@ -45,15 +45,16 @@ from pyramid.httpexceptions import (
|
|
45
45
|
)
|
46
46
|
from pyramid.response import Response
|
47
47
|
from pyramid.security import forget, remember
|
48
|
-
from pyramid.view import view_config
|
48
|
+
from pyramid.view import forbidden_view_config, view_config
|
49
49
|
from sqlalchemy.orm.exc import NoResultFound
|
50
50
|
|
51
51
|
from c2cgeoportal_commons import models
|
52
52
|
from c2cgeoportal_commons.lib.email_ import send_email_config
|
53
53
|
from c2cgeoportal_commons.models import static
|
54
|
-
from c2cgeoportal_geoportal import
|
54
|
+
from c2cgeoportal_geoportal import is_valid_referrer
|
55
55
|
from c2cgeoportal_geoportal.lib import get_setting, is_intranet, oauth2
|
56
|
-
from c2cgeoportal_geoportal.lib.caching import
|
56
|
+
from c2cgeoportal_geoportal.lib.caching import get_region
|
57
|
+
from c2cgeoportal_geoportal.lib.common_headers import Cache, set_common_headers
|
57
58
|
from c2cgeoportal_geoportal.lib.functionality import get_functionality
|
58
59
|
|
59
60
|
LOG = logging.getLogger(__name__)
|
@@ -61,7 +62,13 @@ CACHE_REGION = get_region("std")
|
|
61
62
|
|
62
63
|
|
63
64
|
class Login:
|
64
|
-
|
65
|
+
"""
|
66
|
+
All the login, logout, oauth2, user information views.
|
67
|
+
|
68
|
+
Also manage the 2fa.
|
69
|
+
"""
|
70
|
+
|
71
|
+
def __init__(self, request: pyramid.request.Request):
|
65
72
|
self.request = request
|
66
73
|
self.settings = request.registry.settings
|
67
74
|
self.lang = request.locale_name
|
@@ -71,7 +78,7 @@ class Login:
|
|
71
78
|
self.two_factor_auth = authentication_settings.get("two_factor", False)
|
72
79
|
self.two_factor_issuer_name = authentication_settings.get("two_factor_issuer_name")
|
73
80
|
|
74
|
-
def _functionality(self):
|
81
|
+
def _functionality(self) -> Dict[str, List[Union[str, int, float, bool, List[Any], Dict[str, Any]]]]:
|
75
82
|
functionality = {}
|
76
83
|
for func_ in get_setting(self.settings, ("functionalities", "available_in_templates"), []):
|
77
84
|
functionality[func_] = get_functionality(func_, self.request, is_intranet(self.request))
|
@@ -79,26 +86,28 @@ class Login:
|
|
79
86
|
|
80
87
|
def _referer_log(self) -> None:
|
81
88
|
if not hasattr(self.request, "is_valid_referer"):
|
82
|
-
self.request.is_valid_referer =
|
89
|
+
self.request.is_valid_referer = is_valid_referrer(self.request)
|
83
90
|
if not self.request.is_valid_referer:
|
84
|
-
LOG.info("Invalid
|
91
|
+
LOG.info("Invalid referrer for %s: %s", self.request.path_qs, repr(self.request.referrer))
|
85
92
|
|
86
|
-
@
|
87
|
-
def loginform403(self):
|
88
|
-
if self.request.authenticated_userid:
|
89
|
-
return
|
93
|
+
@forbidden_view_config(renderer="login.html") # type: ignore
|
94
|
+
def loginform403(self) -> Union[Dict[str, Any], pyramid.response.Response]:
|
95
|
+
if self.request.authenticated_userid is not None:
|
96
|
+
return HTTPForbidden()
|
90
97
|
|
91
|
-
set_common_headers(self.request, "login",
|
98
|
+
set_common_headers(self.request, "login", Cache.PRIVATE_NO)
|
92
99
|
|
93
100
|
return {
|
94
101
|
"lang": self.lang,
|
95
|
-
"login_params": {
|
102
|
+
"login_params": {
|
103
|
+
"came_from": (f"{self.request.path}?{urllib.parse.urlencode(self.request.GET)}")
|
104
|
+
},
|
96
105
|
"two_fa": self.two_factor_auth,
|
97
106
|
}
|
98
107
|
|
99
|
-
@view_config(route_name="loginform", renderer="login.html")
|
100
|
-
def loginform(self):
|
101
|
-
set_common_headers(self.request, "login",
|
108
|
+
@view_config(route_name="loginform", renderer="login.html") # type: ignore
|
109
|
+
def loginform(self) -> Dict[str, Any]:
|
110
|
+
set_common_headers(self.request, "login", Cache.PUBLIC)
|
102
111
|
|
103
112
|
return {
|
104
113
|
"lang": self.lang,
|
@@ -107,18 +116,18 @@ class Login:
|
|
107
116
|
}
|
108
117
|
|
109
118
|
@staticmethod
|
110
|
-
def _validate_2fa_totp(user, otp: str) -> bool:
|
119
|
+
def _validate_2fa_totp(user: static.User, otp: str) -> bool:
|
111
120
|
if pyotp.TOTP(user.tech_data.get("2fa_totp_secret", "")).verify(otp):
|
112
121
|
return True
|
113
122
|
return False
|
114
123
|
|
115
|
-
@view_config(route_name="login")
|
116
|
-
def login(self):
|
124
|
+
@view_config(route_name="login") # type: ignore
|
125
|
+
def login(self) -> pyramid.response.Response:
|
117
126
|
self._referer_log()
|
118
127
|
|
119
128
|
login = self.request.POST.get("login")
|
120
129
|
password = self.request.POST.get("password")
|
121
|
-
if login is None or password is None:
|
130
|
+
if login is None or password is None:
|
122
131
|
raise HTTPBadRequest("'login' and 'password' should be available in request params.")
|
123
132
|
username = self.request.registry.validate_user(self.request, login, password)
|
124
133
|
if username is not None:
|
@@ -133,7 +142,7 @@ class Login:
|
|
133
142
|
return set_common_headers(
|
134
143
|
self.request,
|
135
144
|
"login",
|
136
|
-
|
145
|
+
Cache.PRIVATE_NO,
|
137
146
|
response=Response(
|
138
147
|
json.dumps(
|
139
148
|
{
|
@@ -164,7 +173,7 @@ class Login:
|
|
164
173
|
return set_common_headers(
|
165
174
|
self.request,
|
166
175
|
"login",
|
167
|
-
|
176
|
+
Cache.PRIVATE_NO,
|
168
177
|
response=Response(
|
169
178
|
json.dumps(
|
170
179
|
{
|
@@ -189,7 +198,7 @@ class Login:
|
|
189
198
|
return set_common_headers(
|
190
199
|
self.request,
|
191
200
|
"login",
|
192
|
-
|
201
|
+
Cache.PRIVATE_NO,
|
193
202
|
response=Response(json.dumps(self._user(self.request.get_user(username))), headers=headers),
|
194
203
|
)
|
195
204
|
user = models.DBSession.query(static.User).filter(static.User.username == login).one_or_none()
|
@@ -207,7 +216,7 @@ class Login:
|
|
207
216
|
self.request.tm.commit()
|
208
217
|
raise HTTPUnauthorized("See server logs for details")
|
209
218
|
|
210
|
-
def _oauth2_login(self, user):
|
219
|
+
def _oauth2_login(self, user: static.User) -> pyramid.response.Response:
|
211
220
|
self.request.user_ = user
|
212
221
|
LOG.debug(
|
213
222
|
"Call OAuth create_authorization_response with:\nurl: %s\nmethod: %s\nbody:\n%s",
|
@@ -236,12 +245,12 @@ class Login:
|
|
236
245
|
return set_common_headers(
|
237
246
|
self.request,
|
238
247
|
"login",
|
239
|
-
|
248
|
+
Cache.PRIVATE_NO,
|
240
249
|
response=Response(body, headers=headers.items()),
|
241
250
|
)
|
242
251
|
|
243
|
-
@view_config(route_name="logout")
|
244
|
-
def logout(self):
|
252
|
+
@view_config(route_name="logout") # type: ignore
|
253
|
+
def logout(self) -> pyramid.response.Response:
|
245
254
|
headers = forget(self.request)
|
246
255
|
|
247
256
|
if not self.request.user:
|
@@ -251,9 +260,11 @@ class Login:
|
|
251
260
|
LOG.info("User '%s' (%s) logging out.", self.request.user.username, self.request.user.id)
|
252
261
|
|
253
262
|
headers.append(("Content-Type", "text/json"))
|
254
|
-
return set_common_headers(
|
263
|
+
return set_common_headers(
|
264
|
+
self.request, "login", Cache.PRIVATE_NO, response=Response("true", headers=headers)
|
265
|
+
)
|
255
266
|
|
256
|
-
def _user(self, user=None):
|
267
|
+
def _user(self, user: Optional[static.User] = None) -> Dict[str, Any]:
|
257
268
|
result = {
|
258
269
|
"functionalities": self._functionality(),
|
259
270
|
"is_intranet": is_intranet(self.request),
|
@@ -270,122 +281,155 @@ class Login:
|
|
270
281
|
)
|
271
282
|
return result
|
272
283
|
|
273
|
-
@view_config(route_name="loginuser", renderer="json")
|
274
|
-
def loginuser(self):
|
284
|
+
@view_config(route_name="loginuser", renderer="json") # type: ignore
|
285
|
+
def loginuser(self) -> Dict[str, Any]:
|
275
286
|
LOG.info("Client IP address: %s", self.request.client_addr)
|
276
|
-
set_common_headers(self.request, "login",
|
287
|
+
set_common_headers(self.request, "login", Cache.PRIVATE_NO)
|
277
288
|
return self._user()
|
278
289
|
|
279
|
-
@view_config(route_name="change_password", renderer="json")
|
280
|
-
def change_password(self):
|
281
|
-
set_common_headers(self.request, "login",
|
290
|
+
@view_config(route_name="change_password", renderer="json") # type: ignore
|
291
|
+
def change_password(self) -> pyramid.response.Response:
|
292
|
+
set_common_headers(self.request, "login", Cache.PRIVATE_NO)
|
282
293
|
|
283
294
|
login = self.request.POST.get("login")
|
284
295
|
old_password = self.request.POST.get("oldPassword")
|
285
296
|
new_password = self.request.POST.get("newPassword")
|
286
297
|
new_password_confirm = self.request.POST.get("confirmNewPassword")
|
298
|
+
otp = self.request.POST.get("otp")
|
287
299
|
if new_password is None or new_password_confirm is None or old_password is None:
|
288
300
|
raise HTTPBadRequest(
|
289
301
|
"'oldPassword', 'newPassword' and 'confirmNewPassword' should be available in "
|
290
302
|
"request params."
|
291
303
|
)
|
304
|
+
if self.two_factor_auth and otp is None:
|
305
|
+
raise HTTPBadRequest("The second factor is missing.")
|
306
|
+
if login is None and self.request.user is None:
|
307
|
+
raise HTTPBadRequest("You should be logged in or 'login' should be available in request params.")
|
308
|
+
if new_password != new_password_confirm:
|
309
|
+
raise HTTPBadRequest("The new password and the new password confirmation do not match")
|
292
310
|
|
293
311
|
if login is not None:
|
294
|
-
|
295
|
-
|
296
|
-
if user is None:
|
297
|
-
LOG.info("The login '%s' does not exist.", login)
|
298
|
-
raise HTTPUnauthorized("See server logs for details")
|
299
|
-
except NoResultFound: # pragma: no cover
|
312
|
+
user = models.DBSession.query(static.User).filter_by(username=login).one_or_none()
|
313
|
+
if user is None:
|
300
314
|
LOG.info("The login '%s' does not exist.", login)
|
301
315
|
raise HTTPUnauthorized("See server logs for details")
|
302
316
|
|
303
317
|
if self.two_factor_auth:
|
304
|
-
otp = self.request.POST.get("otp")
|
305
|
-
if otp is None:
|
306
|
-
raise HTTPBadRequest("The second factor is missing.")
|
307
318
|
if not self._validate_2fa_totp(user, otp):
|
308
319
|
LOG.info("The second factor is wrong for user '%s'.", login)
|
309
320
|
raise HTTPUnauthorized("See server logs for details")
|
310
|
-
|
311
321
|
else:
|
312
|
-
|
313
|
-
user = self.request.user
|
314
|
-
else:
|
315
|
-
raise HTTPBadRequest(
|
316
|
-
"You should be logged in or 'login' should be available in request params."
|
317
|
-
)
|
322
|
+
user = self.request.user
|
318
323
|
|
319
|
-
|
320
|
-
|
321
|
-
LOG.info("The old password is wrong for user '%s'.", username)
|
324
|
+
if self.request.registry.validate_user(self.request, user.username, old_password) is None:
|
325
|
+
LOG.info("The old password is wrong for user '%s'.", user.username)
|
322
326
|
raise HTTPUnauthorized("See server logs for details")
|
323
327
|
|
324
|
-
if new_password != new_password_confirm:
|
325
|
-
raise HTTPBadRequest("The new password and the new password confirmation do not match")
|
326
|
-
|
327
328
|
user.password = new_password
|
328
329
|
user.is_password_changed = True
|
329
330
|
models.DBSession.flush()
|
330
|
-
LOG.info("Password changed for user '%s'", username)
|
331
|
+
LOG.info("Password changed for user '%s'", user.username)
|
331
332
|
|
332
|
-
headers = remember(self.request, username)
|
333
|
+
headers = remember(self.request, user.username)
|
333
334
|
headers.append(("Content-Type", "text/json"))
|
334
335
|
return set_common_headers(
|
335
|
-
self.request,
|
336
|
+
self.request,
|
337
|
+
"login",
|
338
|
+
Cache.PRIVATE_NO,
|
339
|
+
response=Response(json.dumps(self._user(user)), headers=headers),
|
336
340
|
)
|
337
341
|
|
338
342
|
@staticmethod
|
339
|
-
def generate_password():
|
343
|
+
def generate_password() -> str:
|
340
344
|
allchars = "123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
341
|
-
rand = Random()
|
345
|
+
rand = Random() # nosec
|
342
346
|
|
343
|
-
password = ""
|
347
|
+
password = "" # nosec
|
344
348
|
for _ in range(8):
|
345
349
|
password += rand.choice(allchars)
|
346
350
|
|
347
351
|
return password
|
348
352
|
|
349
|
-
def _loginresetpassword(
|
353
|
+
def _loginresetpassword(
|
354
|
+
self,
|
355
|
+
) -> Tuple[Optional[static.User], Optional[str], Optional[str], Optional[str]]:
|
350
356
|
username = self.request.POST.get("login")
|
351
357
|
if username is None:
|
352
358
|
raise HTTPBadRequest("'login' should be available in request params.")
|
353
|
-
username = self.request.POST["login"]
|
354
359
|
try:
|
355
360
|
user = models.DBSession.query(static.User).filter(static.User.username == username).one()
|
356
|
-
except NoResultFound:
|
357
|
-
return None, None, None, "The login '{}' does not exist."
|
361
|
+
except NoResultFound:
|
362
|
+
return None, None, None, f"The login '{username}' does not exist."
|
358
363
|
|
359
|
-
if user.email is None or user.email == "":
|
360
|
-
return None, None, None, "The user '{}' has no registered email address."
|
364
|
+
if user.email is None or user.email == "":
|
365
|
+
return None, None, None, f"The user '{user.username}' has no registered email address."
|
361
366
|
|
362
367
|
password = self.generate_password()
|
363
368
|
user.set_temp_password(password)
|
364
369
|
|
365
370
|
return user, username, password, None
|
366
371
|
|
367
|
-
@view_config(route_name="loginresetpassword", renderer="json")
|
368
|
-
def loginresetpassword(self)
|
369
|
-
set_common_headers(self.request, "login",
|
372
|
+
@view_config(route_name="loginresetpassword", renderer="json") # type: ignore
|
373
|
+
def loginresetpassword(self) -> Dict[str, Any]:
|
374
|
+
set_common_headers(self.request, "login", Cache.PRIVATE_NO)
|
370
375
|
|
371
376
|
user, username, password, error = self._loginresetpassword()
|
372
377
|
if error is not None:
|
373
378
|
LOG.info(error)
|
374
|
-
|
379
|
+
return {"success": True}
|
380
|
+
|
381
|
+
if user is None:
|
382
|
+
LOG.info("The user is not found without any error.")
|
383
|
+
return {"success": True}
|
384
|
+
|
375
385
|
if user.deactivated:
|
376
386
|
LOG.info("The user '%s' is deactivated", username)
|
377
|
-
|
387
|
+
return {"success": True}
|
378
388
|
|
379
389
|
send_email_config(
|
380
|
-
self.request.registry.settings,
|
390
|
+
self.request.registry.settings,
|
391
|
+
"reset_password",
|
392
|
+
user.email,
|
393
|
+
user=username,
|
394
|
+
password=password,
|
395
|
+
application_url=self.request.route_url("base"),
|
396
|
+
current_url=self.request.current_route_url(),
|
381
397
|
)
|
382
398
|
|
383
399
|
return {"success": True}
|
384
400
|
|
385
|
-
@view_config(route_name="
|
386
|
-
def
|
387
|
-
|
401
|
+
@view_config(route_name="oauth2introspect") # type: ignore
|
402
|
+
def oauth2introspect(self) -> pyramid.response.Response:
|
403
|
+
LOG.debug(
|
404
|
+
"Call OAuth create_introspect_response with:\nurl: %s\nmethod: %s\nbody:\n%s",
|
405
|
+
self.request.current_route_url(_query=self.request.GET),
|
406
|
+
self.request.method,
|
407
|
+
self.request.body,
|
408
|
+
)
|
409
|
+
headers, body, status = oauth2.get_oauth_client(
|
410
|
+
self.request.registry.settings
|
411
|
+
).create_introspect_response(
|
412
|
+
self.request.current_route_url(_query=self.request.GET),
|
413
|
+
self.request.method,
|
414
|
+
self.request.body,
|
415
|
+
self.request.headers,
|
416
|
+
)
|
417
|
+
LOG.debug("OAuth create_introspect_response return status: %s", status)
|
418
|
+
|
419
|
+
# All requests to /token will return a json response, no redirection.
|
420
|
+
if status != 200:
|
421
|
+
if body:
|
422
|
+
raise exception_response(status, detail=body)
|
423
|
+
raise exception_response(status)
|
424
|
+
return set_common_headers(
|
425
|
+
self.request,
|
426
|
+
"login",
|
427
|
+
Cache.PRIVATE_NO,
|
428
|
+
response=Response(body, headers=headers.items()),
|
429
|
+
)
|
388
430
|
|
431
|
+
@view_config(route_name="oauth2token") # type: ignore
|
432
|
+
def oauth2token(self) -> pyramid.response.Response:
|
389
433
|
LOG.debug(
|
390
434
|
"Call OAuth create_token_response with:\nurl: %s\nmethod: %s\nbody:\n%s",
|
391
435
|
self.request.current_route_url(_query=self.request.GET),
|
@@ -401,9 +445,6 @@ class Login:
|
|
401
445
|
)
|
402
446
|
LOG.debug("OAuth create_token_response return status: %s", status)
|
403
447
|
|
404
|
-
if hasattr(self.request, "tm"):
|
405
|
-
self.request.tm.commit()
|
406
|
-
|
407
448
|
# All requests to /token will return a json response, no redirection.
|
408
449
|
if status != 200:
|
409
450
|
if body:
|
@@ -412,13 +453,40 @@ class Login:
|
|
412
453
|
return set_common_headers(
|
413
454
|
self.request,
|
414
455
|
"login",
|
415
|
-
|
456
|
+
Cache.PRIVATE_NO,
|
457
|
+
response=Response(body, headers=headers.items()),
|
458
|
+
)
|
459
|
+
|
460
|
+
@view_config(route_name="oauth2revoke_token") # type: ignore
|
461
|
+
def oauth2revoke_token(self) -> pyramid.response.Response:
|
462
|
+
LOG.debug(
|
463
|
+
"Call OAuth create_revocation_response with:\nurl: %s\nmethod: %s\nbody:\n%s",
|
464
|
+
self.request.create_revocation_response(_query=self.request.GET),
|
465
|
+
self.request.method,
|
466
|
+
self.request.body,
|
467
|
+
)
|
468
|
+
headers, body, status = oauth2.get_oauth_client(
|
469
|
+
self.request.registry.settings
|
470
|
+
).create_authorize_response(
|
471
|
+
self.request.current_route_url(_query=self.request.GET),
|
472
|
+
self.request.method,
|
473
|
+
self.request.body,
|
474
|
+
self.request.headers,
|
475
|
+
)
|
476
|
+
if status != 200:
|
477
|
+
if body:
|
478
|
+
raise exception_response(status, detail=body)
|
479
|
+
raise exception_response(status)
|
480
|
+
return set_common_headers(
|
481
|
+
self.request,
|
482
|
+
"login",
|
483
|
+
Cache.PRIVATE_NO,
|
416
484
|
response=Response(body, headers=headers.items()),
|
417
485
|
)
|
418
486
|
|
419
|
-
@view_config(route_name="oauth2loginform", renderer="login.html")
|
420
|
-
def oauth2loginform(self):
|
421
|
-
set_common_headers(self.request, "login",
|
487
|
+
@view_config(route_name="oauth2loginform", renderer="login.html") # type: ignore
|
488
|
+
def oauth2loginform(self) -> Dict[str, Any]:
|
489
|
+
set_common_headers(self.request, "login", Cache.PUBLIC)
|
422
490
|
|
423
491
|
if self.request.user:
|
424
492
|
self._oauth2_login(self.request.user)
|
@@ -431,8 +499,8 @@ class Login:
|
|
431
499
|
"two_fa": self.two_factor_auth,
|
432
500
|
}
|
433
501
|
|
434
|
-
@view_config(route_name="notlogin", renderer="notlogin.html")
|
435
|
-
def notlogin(self):
|
436
|
-
set_common_headers(self.request, "login",
|
502
|
+
@view_config(route_name="notlogin", renderer="notlogin.html") # type: ignore
|
503
|
+
def notlogin(self) -> Dict[str, Any]:
|
504
|
+
set_common_headers(self.request, "login", Cache.PUBLIC)
|
437
505
|
|
438
506
|
return {"lang": self.lang}
|
@@ -1,6 +1,4 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
# Copyright (c) 2011-2021, Camptocamp SA
|
1
|
+
# Copyright (c) 2011-2024, Camptocamp SA
|
4
2
|
# All rights reserved.
|
5
3
|
|
6
4
|
# Redistribution and use in source and binary forms, with or without
|
@@ -29,16 +27,18 @@
|
|
29
27
|
|
30
28
|
|
31
29
|
import logging
|
32
|
-
from typing import Any, Dict
|
30
|
+
from typing import Any, Dict, Set
|
33
31
|
|
34
|
-
from pyramid.httpexceptions import HTTPUnauthorized
|
32
|
+
from pyramid.httpexceptions import HTTPForbidden, HTTPFound, HTTPInternalServerError, HTTPUnauthorized
|
35
33
|
from pyramid.request import Request
|
36
34
|
from pyramid.response import Response
|
37
35
|
from pyramid.view import view_config
|
38
36
|
|
37
|
+
from c2cgeoportal_commons.lib.url import Url
|
39
38
|
from c2cgeoportal_commons.models import main
|
40
39
|
from c2cgeoportal_geoportal.lib import get_roles_id, get_roles_name
|
41
|
-
from c2cgeoportal_geoportal.lib.caching import
|
40
|
+
from c2cgeoportal_geoportal.lib.caching import get_region
|
41
|
+
from c2cgeoportal_geoportal.lib.common_headers import Cache
|
42
42
|
from c2cgeoportal_geoportal.lib.filter_capabilities import filter_capabilities
|
43
43
|
from c2cgeoportal_geoportal.lib.functionality import get_mapserver_substitution_params
|
44
44
|
from c2cgeoportal_geoportal.views.ogcproxy import OGCProxy
|
@@ -48,6 +48,7 @@ LOG = logging.getLogger(__name__)
|
|
48
48
|
|
49
49
|
|
50
50
|
class MapservProxy(OGCProxy):
|
51
|
+
"""Proxy for OGC (WMS/WFS) servers."""
|
51
52
|
|
52
53
|
params: Dict[str, str] = {}
|
53
54
|
|
@@ -55,8 +56,10 @@ class MapservProxy(OGCProxy):
|
|
55
56
|
OGCProxy.__init__(self, request)
|
56
57
|
self.user = self.request.user
|
57
58
|
|
58
|
-
@view_config(route_name="mapserverproxy")
|
59
|
-
@view_config(route_name="mapserverproxy_post")
|
59
|
+
@view_config(route_name="mapserverproxy") # type: ignore
|
60
|
+
@view_config(route_name="mapserverproxy_post") # type: ignore
|
61
|
+
@view_config(route_name="mapserverproxy_get_path") # type: ignore
|
62
|
+
@view_config(route_name="mapserverproxy_post_path") # type: ignore
|
60
63
|
def proxy(self) -> Response:
|
61
64
|
if self.user is None and "authentication_required" in self.request.params:
|
62
65
|
LOG.debug("proxy() detected authentication_required")
|
@@ -64,7 +67,7 @@ class MapservProxy(OGCProxy):
|
|
64
67
|
raise HTTPUnauthorized(
|
65
68
|
headers={"WWW-Authenticate": 'Basic realm="Access to restricted layers"'}
|
66
69
|
)
|
67
|
-
raise
|
70
|
+
raise HTTPForbidden("Basic auth is not enabled")
|
68
71
|
|
69
72
|
# We have a user logged in. We need to set group_id and possible layer_name in the params. We set
|
70
73
|
# layer_name when either QUERY_PARAMS or LAYERS is set in the WMS params, i.e. for GetMap and
|
@@ -75,7 +78,7 @@ class MapservProxy(OGCProxy):
|
|
75
78
|
self.params["role_ids"] = ",".join([str(e) for e in get_roles_id(self.request)])
|
76
79
|
|
77
80
|
# In some application we want to display the features owned by a user than we need his id.
|
78
|
-
self.params["user_id"] = self.user.id if self.user is not None else "-1"
|
81
|
+
self.params["user_id"] = self.user.id if self.user is not None else "-1"
|
79
82
|
|
80
83
|
# Do not allows direct variable substitution
|
81
84
|
for k in list(self.params.keys()):
|
@@ -97,14 +100,14 @@ class MapservProxy(OGCProxy):
|
|
97
100
|
# DescribeFeatureType requests
|
98
101
|
use_cache = False
|
99
102
|
|
103
|
+
errors: Set[str] = set()
|
100
104
|
if method == "GET":
|
101
105
|
# For GET requests, params are added only if the self.request
|
102
106
|
# parameter is actually provided.
|
103
107
|
if "request" not in self.lower_params:
|
104
|
-
self.params = {}
|
108
|
+
self.params = {}
|
105
109
|
else:
|
106
110
|
if self.ogc_server.type != main.OGCSERVER_TYPE_QGISSERVER or "user_id" not in self.params:
|
107
|
-
|
108
111
|
use_cache = self.lower_params["request"] in ("getlegendgraphic",)
|
109
112
|
|
110
113
|
# no user_id and role_id or cached queries
|
@@ -114,28 +117,33 @@ class MapservProxy(OGCProxy):
|
|
114
117
|
del self.params["role_ids"]
|
115
118
|
|
116
119
|
if "service" in self.lower_params and self.lower_params["service"] == "wfs":
|
117
|
-
_url = self._get_wfs_url()
|
120
|
+
_url = self._get_wfs_url(errors)
|
118
121
|
else:
|
119
|
-
_url = self._get_wms_url()
|
122
|
+
_url = self._get_wms_url(errors)
|
120
123
|
else:
|
121
124
|
# POST means WFS
|
122
|
-
_url = self._get_wfs_url()
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
125
|
+
_url = self._get_wfs_url(errors)
|
126
|
+
|
127
|
+
if _url is None:
|
128
|
+
LOG.error("Error getting the URL:\n%s", "\n".join(errors))
|
129
|
+
raise HTTPInternalServerError()
|
130
|
+
|
131
|
+
cache_control = (
|
132
|
+
Cache.PRIVATE
|
133
|
+
if method == "GET"
|
134
|
+
and self.lower_params.get("request")
|
135
|
+
in (
|
136
|
+
"getcapabilities",
|
137
|
+
"getlegendgraphic",
|
138
|
+
"describefeaturetype",
|
139
|
+
"describelayer",
|
140
|
+
)
|
141
|
+
else Cache.PRIVATE_NO
|
142
|
+
)
|
143
|
+
|
144
|
+
headers = self.get_headers()
|
137
145
|
# Add headers for Geoserver
|
138
|
-
if self.ogc_server.auth == main.OGCSERVER_AUTH_GEOSERVER:
|
146
|
+
if self.ogc_server.auth == main.OGCSERVER_AUTH_GEOSERVER and self.user is not None:
|
139
147
|
headers["sec-username"] = self.user.username
|
140
148
|
headers["sec-roles"] = ";".join(get_roles_name(self.request))
|
141
149
|
|
@@ -157,7 +165,15 @@ class MapservProxy(OGCProxy):
|
|
157
165
|
|
158
166
|
return response
|
159
167
|
|
160
|
-
def _proxy_callback(
|
168
|
+
def _proxy_callback(
|
169
|
+
self, cache_control: Cache, url: Url, params: Dict[str, str], **kwargs: Any
|
170
|
+
) -> Response:
|
171
|
+
if self.request.matched_route.name.endswith("_path"):
|
172
|
+
if self.request.matchdict["path"] == ("favicon.ico",):
|
173
|
+
return HTTPFound("/favicon.ico")
|
174
|
+
url = url.clone()
|
175
|
+
url.path = self.request.path
|
176
|
+
|
161
177
|
response = self._proxy(url=url, params=params, **kwargs)
|
162
178
|
|
163
179
|
content = response.content
|