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