c2cgeoportal-geoportal 2.3.5.80__py3-none-any.whl → 2.9rc45__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (198) hide show
  1. c2cgeoportal_geoportal/__init__.py +960 -0
  2. c2cgeoportal_geoportal/lib/__init__.py +256 -0
  3. c2cgeoportal_geoportal/lib/authentication.py +250 -0
  4. c2cgeoportal_geoportal/lib/bashcolor.py +46 -0
  5. c2cgeoportal_geoportal/lib/cacheversion.py +77 -0
  6. c2cgeoportal_geoportal/lib/caching.py +176 -0
  7. c2cgeoportal_geoportal/lib/check_collector.py +80 -0
  8. c2cgeoportal_geoportal/lib/checker.py +295 -0
  9. c2cgeoportal_geoportal/lib/common_headers.py +172 -0
  10. c2cgeoportal_geoportal/lib/dbreflection.py +266 -0
  11. c2cgeoportal_geoportal/lib/filter_capabilities.py +360 -0
  12. c2cgeoportal_geoportal/lib/fulltextsearch.py +50 -0
  13. c2cgeoportal_geoportal/lib/functionality.py +166 -0
  14. c2cgeoportal_geoportal/lib/headers.py +62 -0
  15. c2cgeoportal_geoportal/lib/i18n.py +38 -0
  16. c2cgeoportal_geoportal/lib/layers.py +132 -0
  17. c2cgeoportal_geoportal/lib/lingva_extractor.py +937 -0
  18. c2cgeoportal_geoportal/lib/loader.py +57 -0
  19. c2cgeoportal_geoportal/lib/metrics.py +117 -0
  20. c2cgeoportal_geoportal/lib/oauth2.py +1186 -0
  21. c2cgeoportal_geoportal/lib/oidc.py +304 -0
  22. c2cgeoportal_geoportal/lib/wmstparsing.py +353 -0
  23. c2cgeoportal_geoportal/lib/xsd.py +166 -0
  24. c2cgeoportal_geoportal/py.typed +0 -0
  25. c2cgeoportal_geoportal/resources.py +49 -0
  26. c2cgeoportal_geoportal/scaffolds/advance_create/ci/config.yaml +26 -0
  27. c2cgeoportal_geoportal/scaffolds/advance_create/cookiecutter.json +18 -0
  28. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/.dockerignore +6 -0
  29. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/.eslintrc.yaml +19 -0
  30. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/.prospector.yaml +30 -0
  31. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/Dockerfile +75 -0
  32. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/Makefile +6 -0
  33. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/alembic.ini +58 -0
  34. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/alembic.yaml +19 -0
  35. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/development.ini +121 -0
  36. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/gunicorn.conf.py +139 -0
  37. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/language_mapping +3 -0
  38. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/lingva-client.cfg +5 -0
  39. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/lingva-server.cfg +6 -0
  40. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/production.ini +38 -0
  41. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/requirements.txt +2 -0
  42. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/setup.py +25 -0
  43. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/webpack.api.js +41 -0
  44. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/webpack.apps.js +64 -0
  45. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/webpack.commons.js +11 -0
  46. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/webpack.config.js +22 -0
  47. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/__init__.py +42 -0
  48. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/authentication.py +10 -0
  49. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/dev.py +14 -0
  50. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/models.py +8 -0
  51. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/multi_organization.py +7 -0
  52. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/resources.py +11 -0
  53. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static-ngeo/api/index.js +12 -0
  54. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static-ngeo/js/{{cookiecutter.package}}module.js +25 -0
  55. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/subscribers.py +39 -0
  56. c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/views/__init__.py +0 -0
  57. c2cgeoportal_geoportal/scaffolds/advance_update/cookiecutter.json +18 -0
  58. c2cgeoportal_geoportal/scaffolds/advance_update/{{cookiecutter.project}}/geoportal/CONST_Makefile +121 -0
  59. c2cgeoportal_geoportal/scaffolds/create/cookiecutter.json +18 -0
  60. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.dockerignore +14 -0
  61. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.editorconfig +17 -0
  62. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.github/workflows/main.yaml +73 -0
  63. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.github/workflows/rebuild.yaml +50 -0
  64. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.github/workflows/update_l10n.yaml +66 -0
  65. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.gitignore +16 -0
  66. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.pre-commit-config.yaml +35 -0
  67. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.prettierignore +1 -0
  68. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.prettierrc.yaml +2 -0
  69. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/Dockerfile +75 -0
  70. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/Makefile +70 -0
  71. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/README.rst +29 -0
  72. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/build +179 -0
  73. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/ci/config.yaml +22 -0
  74. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/ci/docker-compose-check +25 -0
  75. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/ci/requirements.txt +2 -0
  76. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose-db.yaml +24 -0
  77. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose-lib.yaml +513 -0
  78. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose-qgis.yaml +21 -0
  79. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose.override.sample.yaml +65 -0
  80. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose.yaml +121 -0
  81. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/env.default +102 -0
  82. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/env.project +69 -0
  83. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/vars.yaml +430 -0
  84. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/locale/en/LC_MESSAGES/{{cookiecutter.package}}_geoportal-client.po +6 -0
  85. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/css/desktop.css +0 -0
  86. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/css/iframe_api.css +0 -0
  87. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/css/mobile.css +0 -0
  88. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/images/banner_left.png +0 -0
  89. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/images/banner_right.png +0 -0
  90. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/images/blank.png +0 -0
  91. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/images/markers/marker-blue.png +0 -0
  92. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/images/markers/marker-gold.png +0 -0
  93. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/images/markers/marker-green.png +0 -0
  94. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/images/markers/marker.png +0 -0
  95. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/static/robot.txt.tmpl +3 -0
  96. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/data/Readme.txt +69 -0
  97. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/data/TM_EUROPE_BORDERS-0.3.sql +70 -0
  98. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/demo.map.tmpl +224 -0
  99. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/Arial.ttf +0 -0
  100. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/Arialbd.ttf +0 -0
  101. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/Arialbi.ttf +0 -0
  102. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/Ariali.ttf +0 -0
  103. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/NotoSans-Bold.ttf +0 -0
  104. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/NotoSans-BoldItalic.ttf +0 -0
  105. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/NotoSans-Italic.ttf +0 -0
  106. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/NotoSans-Regular.ttf +0 -0
  107. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/Verdana.ttf +0 -0
  108. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/Verdanab.ttf +0 -0
  109. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/Verdanai.ttf +0 -0
  110. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts/Verdanaz.ttf +0 -0
  111. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/fonts.conf +12 -0
  112. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/mapserver.conf +16 -0
  113. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/mapserver.map.tmpl +87 -0
  114. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/tinyows.xml.tmpl +36 -0
  115. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/A3_Landscape.jrxml +207 -0
  116. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/A3_Portrait.jrxml +185 -0
  117. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/A4_Landscape.jrxml +200 -0
  118. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/A4_Portrait.jrxml +170 -0
  119. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/config.yaml.tmpl +175 -0
  120. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/legend.jrxml +109 -0
  121. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/localisation.properties +4 -0
  122. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/localisation_fr.properties +4 -0
  123. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/logo.png +0 -0
  124. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/north.svg +93 -0
  125. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/results.jrxml +25 -0
  126. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/project.yaml +18 -0
  127. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/pyproject.toml +7 -0
  128. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/qgisserver/pg_service.conf.tmpl +15 -0
  129. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/run_alembic.sh +11 -0
  130. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/scripts/db-backup +126 -0
  131. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/scripts/db-restore +132 -0
  132. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/setup.cfg +7 -0
  133. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/spell-ignore-words.txt +5 -0
  134. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/tests/__init__.py +0 -0
  135. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/tests/test_app.py +78 -0
  136. c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/tilegeneration/config.yaml.tmpl +195 -0
  137. c2cgeoportal_geoportal/scaffolds/update/cookiecutter.json +18 -0
  138. c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/.upgrade.yaml +67 -0
  139. c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/CONST_CHANGELOG.txt +304 -0
  140. c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/CONST_create_template/tests/test_testapp.py +48 -0
  141. c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/geoportal/.CONST_vars.yaml.swp +0 -0
  142. c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/geoportal/CONST_config-schema.yaml +927 -0
  143. c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/geoportal/CONST_vars.yaml +1503 -0
  144. c2cgeoportal_geoportal/scripts/__init__.py +64 -0
  145. c2cgeoportal_geoportal/scripts/c2cupgrade.py +879 -0
  146. c2cgeoportal_geoportal/scripts/create_demo_theme.py +83 -0
  147. c2cgeoportal_geoportal/scripts/manage_users.py +140 -0
  148. c2cgeoportal_geoportal/scripts/pcreate.py +296 -0
  149. c2cgeoportal_geoportal/scripts/theme2fts.py +347 -0
  150. c2cgeoportal_geoportal/scripts/urllogin.py +81 -0
  151. c2cgeoportal_geoportal/templates/login.html +90 -0
  152. c2cgeoportal_geoportal/templates/notlogin.html +62 -0
  153. c2cgeoportal_geoportal/templates/testi18n.html +12 -0
  154. c2cgeoportal_geoportal/views/__init__.py +59 -0
  155. c2cgeoportal_geoportal/views/dev.py +57 -0
  156. c2cgeoportal_geoportal/views/dynamic.py +209 -0
  157. c2cgeoportal_geoportal/views/entry.py +174 -0
  158. c2cgeoportal_geoportal/views/fulltextsearch.py +189 -0
  159. c2cgeoportal_geoportal/views/geometry_processing.py +75 -0
  160. c2cgeoportal_geoportal/views/i18n.py +129 -0
  161. c2cgeoportal_geoportal/views/layers.py +713 -0
  162. c2cgeoportal_geoportal/views/login.py +684 -0
  163. c2cgeoportal_geoportal/views/mapserverproxy.py +234 -0
  164. c2cgeoportal_geoportal/views/memory.py +90 -0
  165. c2cgeoportal_geoportal/views/ogcproxy.py +120 -0
  166. c2cgeoportal_geoportal/views/pdfreport.py +245 -0
  167. c2cgeoportal_geoportal/views/printproxy.py +143 -0
  168. c2cgeoportal_geoportal/views/profile.py +192 -0
  169. c2cgeoportal_geoportal/views/proxy.py +261 -0
  170. c2cgeoportal_geoportal/views/raster.py +233 -0
  171. c2cgeoportal_geoportal/views/resourceproxy.py +73 -0
  172. c2cgeoportal_geoportal/views/shortener.py +152 -0
  173. c2cgeoportal_geoportal/views/theme.py +1322 -0
  174. c2cgeoportal_geoportal/views/tinyowsproxy.py +189 -0
  175. c2cgeoportal_geoportal/views/vector_tiles.py +83 -0
  176. {c2cgeoportal_geoportal-2.3.5.80.dist-info → c2cgeoportal_geoportal-2.9rc45.dist-info}/METADATA +21 -24
  177. c2cgeoportal_geoportal-2.9rc45.dist-info/RECORD +193 -0
  178. {c2cgeoportal_geoportal-2.3.5.80.dist-info → c2cgeoportal_geoportal-2.9rc45.dist-info}/WHEEL +1 -1
  179. c2cgeoportal_geoportal-2.9rc45.dist-info/entry_points.txt +28 -0
  180. c2cgeoportal_geoportal-2.9rc45.dist-info/top_level.txt +2 -0
  181. tests/__init__.py +100 -0
  182. tests/test_cachebuster.py +71 -0
  183. tests/test_caching.py +275 -0
  184. tests/test_checker.py +85 -0
  185. tests/test_decimaljson.py +47 -0
  186. tests/test_headerstween.py +64 -0
  187. tests/test_i18n.py +31 -0
  188. tests/test_init.py +193 -0
  189. tests/test_locale_negociator.py +69 -0
  190. tests/test_mapserverproxy_route_predicate.py +64 -0
  191. tests/test_raster.py +267 -0
  192. tests/test_wmstparsing.py +238 -0
  193. tests/xmlstr.py +103 -0
  194. c2cgeoportal_geoportal-2.3.5.80.dist-info/DESCRIPTION.rst +0 -8
  195. c2cgeoportal_geoportal-2.3.5.80.dist-info/RECORD +0 -7
  196. c2cgeoportal_geoportal-2.3.5.80.dist-info/entry_points.txt +0 -22
  197. c2cgeoportal_geoportal-2.3.5.80.dist-info/metadata.json +0 -1
  198. c2cgeoportal_geoportal-2.3.5.80.dist-info/top_level.txt +0 -1
@@ -0,0 +1,960 @@
1
+ # Copyright (c) 2011-2024, Camptocamp SA
2
+ # All rights reserved.
3
+
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions are met:
6
+
7
+ # 1. Redistributions of source code must retain the above copyright notice, this
8
+ # list of conditions and the following disclaimer.
9
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
10
+ # this list of conditions and the following disclaimer in the documentation
11
+ # and/or other materials provided with the distribution.
12
+
13
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
14
+ # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
17
+ # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18
+ # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19
+ # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20
+ # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22
+ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23
+
24
+ # The views and conclusions contained in the software and documentation are those
25
+ # of the authors and should not be interpreted as representing official policies,
26
+ # either expressed or implied, of the FreeBSD Project.
27
+
28
+ import datetime
29
+ import importlib
30
+ import json
31
+ import logging
32
+ import os
33
+ import re
34
+ import urllib.parse
35
+ from collections.abc import Callable
36
+ from functools import partial
37
+ from typing import TYPE_CHECKING, Any, Optional, cast
38
+
39
+ import c2cgeoform
40
+ import c2cwsgiutils
41
+ import c2cwsgiutils.db
42
+ import c2cwsgiutils.index
43
+ import dateutil.parser
44
+ import pyramid.config
45
+ import pyramid.renderers
46
+ import pyramid.request
47
+ import pyramid.response
48
+ import pyramid.security
49
+ import sqlalchemy
50
+ import sqlalchemy.orm
51
+ import zope.event.classhandler
52
+ from c2cgeoform import translator
53
+ from c2cwsgiutils.health_check import HealthCheck
54
+ from c2cwsgiutils.prometheus import MemoryMapCollector
55
+ from deform import Form
56
+ from dogpile.cache import register_backend # type: ignore[attr-defined]
57
+ from papyrus.renderers import GeoJSON
58
+ from prometheus_client.core import REGISTRY
59
+ from pyramid.config import Configurator
60
+ from pyramid.httpexceptions import HTTPException
61
+ from pyramid.path import AssetResolver
62
+ from pyramid_mako import add_mako_renderer
63
+ from sqlalchemy.orm import joinedload
64
+
65
+ import c2cgeoportal_commons.models
66
+ import c2cgeoportal_geoportal.views
67
+ from c2cgeoportal_commons.models import InvalidateCacheEvent
68
+ from c2cgeoportal_geoportal.lib import C2CPregenerator, caching, check_collector, checker, oidc
69
+ from c2cgeoportal_geoportal.lib.cacheversion import version_cache_buster
70
+ from c2cgeoportal_geoportal.lib.caching import get_region
71
+ from c2cgeoportal_geoportal.lib.common_headers import Cache, set_common_headers
72
+ from c2cgeoportal_geoportal.lib.i18n import available_locale_names
73
+ from c2cgeoportal_geoportal.lib.metrics import (
74
+ MemoryCacheSizeCollector,
75
+ RasterDataSizeCollector,
76
+ TotalPythonObjectMemoryCollector,
77
+ )
78
+ from c2cgeoportal_geoportal.lib.xsd import XSD
79
+ from c2cgeoportal_geoportal.views.entry import Entry, canvas_view, custom_view
80
+
81
+ if TYPE_CHECKING:
82
+ from c2cgeoportal_commons.models import static # pylint: disable=ungrouped-imports,useless-suppression
83
+
84
+
85
+ _LOG = logging.getLogger(__name__)
86
+
87
+ _CACHE_REGION_OBJ = get_region("obj")
88
+
89
+ # Header predicate to accept only JSON content
90
+ _JSON_CONTENT_TYPE = "Content-Type:application/json"
91
+ _GEOJSON_CONTENT_TYPE = r"Content-Type:application/geo\+json"
92
+
93
+
94
+ class AssetRendererFactory:
95
+ """Get a renderer for the assets."""
96
+
97
+ def __init__(self, info: Any):
98
+ del info # unused
99
+ self.resolver = AssetResolver("c2cgeoportal_geoportal")
100
+
101
+ def __call__(self, value: Any, system: dict[str, str]) -> bytes:
102
+ del value
103
+ asset = self.resolver.resolve(system["renderer_name"])
104
+ return cast(bytes, asset.stream().read())
105
+
106
+
107
+ INTERFACE_TYPE_NGEO = "ngeo"
108
+ INTERFACE_TYPE_CANVAS = "canvas"
109
+ INTERFACE_TYPE_CUSTOM = "custom"
110
+
111
+
112
+ def add_interface_config(config: pyramid.config.Configurator, interface_config: dict[str, Any]) -> None:
113
+ """Add the interface (desktop, mobile, ...) views and routes with only the config."""
114
+ add_interface(
115
+ config,
116
+ interface_config["name"],
117
+ interface_config.get("type", INTERFACE_TYPE_NGEO),
118
+ interface_config,
119
+ default=interface_config.get("default", False),
120
+ )
121
+
122
+
123
+ def add_interface(
124
+ config: pyramid.config.Configurator,
125
+ interface_name: str = "desktop",
126
+ interface_type: str = INTERFACE_TYPE_NGEO,
127
+ interface_config: dict[str, Any] | None = None,
128
+ default: bool = False,
129
+ **kwargs: Any,
130
+ ) -> None:
131
+ """Add the interface (desktop, mobile, ...) views and routes."""
132
+ route = "/" if default else f"/{interface_name}"
133
+ if interface_type == INTERFACE_TYPE_NGEO:
134
+ add_interface_ngeo(
135
+ config,
136
+ route_name=interface_name,
137
+ route=route,
138
+ renderer=f"/etc/static-ngeo/{interface_name}.html",
139
+ **kwargs,
140
+ )
141
+ elif interface_type == INTERFACE_TYPE_CANVAS:
142
+ assert interface_config is not None
143
+ add_interface_canvas(
144
+ config,
145
+ route_name=interface_name,
146
+ route=route,
147
+ interface_config=interface_config,
148
+ **kwargs,
149
+ )
150
+ elif interface_type == INTERFACE_TYPE_CUSTOM:
151
+ assert interface_config is not None
152
+ add_interface_custom(
153
+ config,
154
+ route_name=interface_name,
155
+ route=route,
156
+ interface_config=interface_config,
157
+ **kwargs,
158
+ )
159
+ else:
160
+ _LOG.error(
161
+ "Unknown interface type '%s', should be '%s', '%s' or '%s'.",
162
+ interface_type,
163
+ INTERFACE_TYPE_NGEO,
164
+ INTERFACE_TYPE_CANVAS,
165
+ INTERFACE_TYPE_CUSTOM,
166
+ )
167
+
168
+
169
+ def add_interface_ngeo(
170
+ config: pyramid.config.Configurator,
171
+ route_name: str,
172
+ route: str,
173
+ renderer: str | None = None,
174
+ permission: str | None = None,
175
+ ) -> None:
176
+ """Add the ngeo interfaces views and routes."""
177
+
178
+ config.add_route(route_name, route, request_method="GET")
179
+ # Permalink theme: recover the theme for generating custom viewer.js url
180
+ config.add_route(
181
+ f"{route_name}theme",
182
+ f"{route}{'' if route[-1] == '/' else '/'}theme/{{themes}}",
183
+ request_method="GET",
184
+ )
185
+ config.add_view(
186
+ Entry, attr="get_ngeo_index_vars", route_name=route_name, renderer=renderer, permission=permission
187
+ )
188
+ config.add_view(
189
+ Entry,
190
+ attr="get_ngeo_index_vars",
191
+ route_name=f"{route_name}theme",
192
+ renderer=renderer,
193
+ permission=permission,
194
+ )
195
+
196
+
197
+ def add_interface_canvas(
198
+ config: pyramid.config.Configurator,
199
+ route_name: str,
200
+ route: str,
201
+ interface_config: dict[str, Any],
202
+ permission: str | None = None,
203
+ ) -> None:
204
+ """Add the ngeo interfaces views and routes."""
205
+
206
+ renderer = f"/etc/geomapfish/interfaces/{route_name}.html.mako"
207
+ config.add_route(route_name, route, request_method="GET")
208
+ # Permalink theme: recover the theme for generating custom viewer.js URL
209
+ config.add_route(
210
+ f"{route_name}theme",
211
+ f"{route}{'' if route[-1] == '/' else '/'}theme/{{themes}}",
212
+ request_method="GET",
213
+ )
214
+ view = partial(canvas_view, interface_config=interface_config)
215
+ view.__module__ = canvas_view.__module__
216
+ config.add_view(
217
+ view,
218
+ route_name=route_name,
219
+ renderer=renderer,
220
+ permission=permission,
221
+ )
222
+ config.add_view(
223
+ view,
224
+ route_name=f"{route_name}theme",
225
+ renderer=renderer,
226
+ permission=permission,
227
+ )
228
+
229
+
230
+ def add_interface_custom(
231
+ config: pyramid.config.Configurator,
232
+ route_name: str,
233
+ route: str,
234
+ interface_config: dict[str, Any],
235
+ permission: str | None = None,
236
+ ) -> None:
237
+ """Add custom interfaces views and routes."""
238
+
239
+ config.add_route(route_name, route, request_method="GET")
240
+ # Permalink theme: recover the theme for generating custom viewer.js URL
241
+ config.add_route(
242
+ f"{route_name}theme",
243
+ f"{route}{'' if route[-1] == '/' else '/'}theme/{{themes}}",
244
+ request_method="GET",
245
+ )
246
+ view = partial(custom_view, interface_config=interface_config)
247
+ view.__module__ = custom_view.__module__
248
+ config.add_view(
249
+ view,
250
+ route_name=route_name,
251
+ permission=permission,
252
+ )
253
+ config.add_view(
254
+ view,
255
+ route_name=f"{route_name}theme",
256
+ permission=permission,
257
+ )
258
+
259
+
260
+ def add_admin_interface(config: pyramid.config.Configurator) -> None:
261
+ """Add the administration interface views and routes."""
262
+
263
+ assert c2cgeoportal_commons.models.DBSession is not None
264
+
265
+ config.add_request_method(
266
+ lambda request: c2cgeoportal_commons.models.DBSession(),
267
+ "dbsession",
268
+ reify=True,
269
+ )
270
+ config.add_view(c2cgeoportal_geoportal.views.add_ending_slash, route_name="admin_add_ending_slash")
271
+ config.add_route("admin_add_ending_slash", "/admin", request_method="GET")
272
+ config.include("c2cgeoportal_admin")
273
+
274
+
275
+ def add_getitfixed(config: pyramid.config.Configurator) -> None:
276
+ """Add the get it fixed views and routes."""
277
+ if config.get_settings()["getitfixed"].get("enabled", False):
278
+ for route_name, pattern in (
279
+ ("getitfixed_add_ending_slash", "/getitfixed"),
280
+ ("getitfixed_admin_add_ending_slash", "/getitfixed_admin"),
281
+ ):
282
+ config.add_view(c2cgeoportal_geoportal.views.add_ending_slash, route_name=route_name)
283
+ config.add_route(route_name, pattern, request_method="GET")
284
+ config.include("getitfixed")
285
+ # Register admin and getitfixed search paths together
286
+ Form.set_zpt_renderer(c2cgeoform.default_search_paths, translator=translator)
287
+
288
+
289
+ def locale_negotiator(request: pyramid.request.Request) -> str:
290
+ """Get the current language."""
291
+ lang: str = request.params.get("lang")
292
+ if lang is None:
293
+ lang = request.cookies.get("_LOCALE_")
294
+ else:
295
+ request.response.set_cookie("_LOCALE_", lang)
296
+ if lang is None:
297
+ # If best_match returns None then use the default_locale_name configuration variable
298
+ return request.accept_language.best_match(
299
+ request.registry.settings.get("available_locale_names"),
300
+ default_match=request.registry.settings.get("default_locale_name"),
301
+ )
302
+ return lang
303
+
304
+
305
+ _URL_RE = re.compile(r"^[a-z]+://.*")
306
+
307
+
308
+ @_CACHE_REGION_OBJ.cache_on_arguments()
309
+ def _get_netlocs(hosts: list[str]) -> set[str]:
310
+ return {urllib.parse.urlparse(h).netloc if _URL_RE.match(h) else h.split("/", 1)[0] for h in hosts}
311
+
312
+
313
+ def is_allowed_url(
314
+ request: pyramid.request.Request, url: str, allowed_hosts: list[str]
315
+ ) -> tuple[str | None, bool]:
316
+ """
317
+ Check if the URL is allowed.
318
+
319
+ Allowed if URL netloc is request host or is found in allowed hosts.
320
+ """
321
+
322
+ url_netloc = urllib.parse.urlparse(url).netloc
323
+ return url_netloc, url_netloc == request.host or url_netloc in _get_netlocs(allowed_hosts)
324
+
325
+
326
+ def is_allowed_host(request: pyramid.request.Request) -> bool:
327
+ """
328
+ Check if the URL is allowed.
329
+
330
+ Allowed if URL netloc is request host or is found in allowed hosts.
331
+ """
332
+
333
+ return request.host in request.registry.settings.get("allowed_hosts", [])
334
+
335
+
336
+ def is_valid_referrer(request: pyramid.request.Request, settings: dict[str, Any] | None = None) -> bool:
337
+ """Check if the referrer is valid."""
338
+ if request.referrer is not None:
339
+ if settings is None:
340
+ settings = request.registry.settings
341
+ authorized_referrers = settings.get("authorized_referers", [])
342
+ referrer_hostname, ok = is_allowed_url(request, request.referrer, authorized_referrers)
343
+ if not ok:
344
+ _LOG.info(
345
+ "Invalid referrer hostname '%s', "
346
+ "is not the current host '%s' "
347
+ "or part of authorized_referers: %s",
348
+ referrer_hostname,
349
+ request.host,
350
+ ", ".join(_get_netlocs(authorized_referrers)),
351
+ )
352
+ return False
353
+ return True
354
+
355
+
356
+ def create_get_user_from_request(
357
+ settings: dict[str, Any]
358
+ ) -> Callable[[pyramid.request.Request, str | None], Optional["static.User"]]:
359
+ """Get the get_user_from_request function."""
360
+
361
+ def get_user_from_request(
362
+ request: pyramid.request.Request, username: str | None = None
363
+ ) -> Optional["static.User"]:
364
+ """
365
+ Return the User object for the request.
366
+
367
+ Return ``None`` if:
368
+ * user is anonymous
369
+ * it does not exist in the database
370
+ * it has been deactivated
371
+ * the referrer is invalid
372
+ """
373
+ from c2cgeoportal_commons.models import DBSession # pylint: disable=import-outside-toplevel
374
+ from c2cgeoportal_commons.models.static import User # pylint: disable=import-outside-toplevel
375
+
376
+ assert DBSession is not None
377
+
378
+ if not hasattr(request, "is_valid_referer"):
379
+ request.is_valid_referer = is_valid_referrer(request, settings)
380
+ if not request.is_valid_referer:
381
+ _LOG.debug("Invalid referrer for %s: %s", request.path_qs, repr(request.referrer))
382
+ return None
383
+
384
+ if not hasattr(request, "user_"):
385
+ request.user_ = None
386
+ user_info_remember: dict[str, Any] | None = None
387
+ openid_connect_configuration = settings.get("authentication", {}).get("openid_connect", {})
388
+ openid_connect_enabled = openid_connect_configuration.get("enabled", False)
389
+ if (
390
+ openid_connect_enabled
391
+ and "Authorization" in request.headers
392
+ and request.headers["Authorization"].startswith("Bearer ")
393
+ ):
394
+ token = request.headers["Authorization"][7:]
395
+ client = oidc.get_oidc_client(request, request.host)
396
+ user_info = client.fetch_userinfo(token)
397
+ user_info_remember = {}
398
+ request.get_remember_from_user_info(user_info.dict(), user_info_remember)
399
+ elif username is None:
400
+ username = request.unauthenticated_userid
401
+ if username is not None or user_info_remember is not None:
402
+ if openid_connect_enabled:
403
+ if user_info_remember is None:
404
+ assert username is not None
405
+ user_info_remember = json.loads(username)
406
+ del username
407
+ if "access_token_expires" in user_info_remember:
408
+ access_token_expires = dateutil.parser.isoparse(
409
+ user_info_remember["access_token_expires"]
410
+ )
411
+ if access_token_expires < datetime.datetime.now():
412
+ if user_info_remember["refresh_token_expires"] is None:
413
+ return None
414
+ refresh_token_expires = dateutil.parser.isoparse(
415
+ user_info_remember["refresh_token_expires"]
416
+ )
417
+ if refresh_token_expires < datetime.datetime.now():
418
+ return None
419
+ token_response = oidc.get_oidc_client(
420
+ request, request.host
421
+ ).exchange_refresh_token(user_info_remember["refresh_token"])
422
+ user_info_remember = oidc.OidcRemember(request).remember(
423
+ token_response, request.host
424
+ )
425
+
426
+ request.user_ = request.get_user_from_remember(user_info_remember)
427
+ else:
428
+ # We know we will need the role object of the
429
+ # user so we use joined loading
430
+ request.user_ = (
431
+ DBSession.query(User)
432
+ .filter_by(username=username, deactivated=False)
433
+ .options(joinedload(User.roles))
434
+ .first()
435
+ )
436
+
437
+ return cast(User, request.user_)
438
+
439
+ return get_user_from_request
440
+
441
+
442
+ def set_user_validator(
443
+ config: pyramid.config.Configurator, user_validator: Callable[[pyramid.request.Request, str, str], str]
444
+ ) -> None:
445
+ """
446
+ Call this function to register a user validator function.
447
+
448
+ The validator function is passed three arguments: ``request``,
449
+ ``username``, and ``password``. The function should return the
450
+ user name if the credentials are valid, and ``None`` otherwise.
451
+
452
+ The validator should not do the actual authentication operation
453
+ by calling ``remember``, this is handled by the ``login`` view.
454
+ """
455
+
456
+ def register() -> None:
457
+ config.registry.validate_user = user_validator
458
+
459
+ config.action("user_validator", register)
460
+
461
+
462
+ def default_user_validator(request: pyramid.request.Request, username: str, password: str) -> str | None:
463
+ """
464
+ Validate the username/password.
465
+
466
+ This is c2cgeoportal's default user validator. Return None if we are anonymous, the string to remember
467
+ otherwise.
468
+ """
469
+ del request # unused
470
+ from c2cgeoportal_commons.models import DBSession # pylint: disable=import-outside-toplevel
471
+ from c2cgeoportal_commons.models.static import User # pylint: disable=import-outside-toplevel
472
+
473
+ assert DBSession is not None
474
+
475
+ user = DBSession.query(User).filter_by(username=username).first()
476
+ if user is None:
477
+ _LOG.info('Unknown user "%s" tried to log in', username)
478
+ return None
479
+ if user.deactivated:
480
+ _LOG.info('Deactivated user "%s" tried to log in', username)
481
+ return None
482
+ if user.expired():
483
+ _LOG.info("Expired user %s tried to log in", username)
484
+ return None
485
+ if not user.validate_password(password):
486
+ _LOG.info('User "%s" tried to log in with bad credentials', username)
487
+ return None
488
+ return username
489
+
490
+
491
+ class MapserverproxyRoutePredicate:
492
+ """
493
+ Serve as a custom route predicate function for mapserverproxy.
494
+
495
+ If the hide_capabilities setting is set and is true then we want to return 404s on GetCapabilities
496
+ requests.
497
+ """
498
+
499
+ def __init__(self, val: Any, config: pyramid.config.Configurator) -> None:
500
+ del val, config
501
+
502
+ def __call__(self, context: Any, request: pyramid.request.Request) -> bool:
503
+ del context
504
+ hide_capabilities = request.registry.settings.get("hide_capabilities")
505
+ if not hide_capabilities:
506
+ return True
507
+ params = {k.lower(): v.lower() for k, v in request.params.items()}
508
+ return "request" not in params or params["request"] not in ("getcapabilities", "capabilities")
509
+
510
+ @staticmethod
511
+ def text() -> str:
512
+ return "mapserverproxy"
513
+
514
+ phash = text
515
+
516
+
517
+ def add_cors_route(config: pyramid.config.Configurator, pattern: str, service: str) -> None:
518
+ """Add the OPTIONS route and view need for services supporting CORS."""
519
+
520
+ def view(request: pyramid.request.Request) -> pyramid.response.Response:
521
+ return set_common_headers(request, service, Cache.PRIVATE_NO)
522
+
523
+ name = pattern + "_options"
524
+ config.add_route(name, pattern, request_method="OPTIONS")
525
+ config.add_view(view, route_name=name)
526
+
527
+
528
+ def error_handler(
529
+ http_exception: HTTPException, request: pyramid.request.Request
530
+ ) -> pyramid.response.Response:
531
+ """View callable for handling all the exceptions that are not already handled."""
532
+ _LOG.warning("%s returned status code %s", request.url, http_exception.status_code)
533
+ return set_common_headers(request, "error", Cache.PRIVATE_NO, http_exception)
534
+
535
+
536
+ def call_hook(settings: pyramid.config.Configurator, name: str, *args: Any, **kwargs: Any) -> None:
537
+ """Call the hook defined in the settings."""
538
+ hooks = settings.get("hooks", {})
539
+ hook = hooks.get(name)
540
+ if hook is None:
541
+ return
542
+ parts = hook.split(".")
543
+ module = importlib.import_module(".".join(parts[0:-1]))
544
+ function_ = getattr(module, parts[-1])
545
+ function_(*args, **kwargs)
546
+
547
+
548
+ def includeme(config: pyramid.config.Configurator) -> None:
549
+ """Get the Pyramid WSGI application."""
550
+ settings = config.get_settings()
551
+
552
+ if "available_locale_names" not in settings:
553
+ settings["available_locale_names"] = available_locale_names()
554
+
555
+ call_hook(settings, "after_settings", settings)
556
+
557
+ get_user_from_request = create_get_user_from_request(settings)
558
+ config.add_request_method(get_user_from_request, name="user", property=True)
559
+ config.add_request_method(get_user_from_request, name="get_user")
560
+ # Be able for an organization to override the method to use alternate:
561
+ # - Organization roles name for the standard roles 'anonymous', 'registered' and 'intranet'.
562
+ config.add_request_method(lambda request, role_type: role_type, name="get_organization_role")
563
+ # - Organization print URL
564
+ config.add_request_method(
565
+ lambda request: request.registry.settings["print_url"], name="get_organization_print_url"
566
+ )
567
+ # - Organization interface name (in the config and in the admin interface)
568
+ config.add_request_method(lambda request, interface: interface, name="get_organization_interface")
569
+
570
+ # Configure 'locale' dir as the translation dir for c2cgeoportal app
571
+ config.add_translation_dirs("c2cgeoportal_geoportal:locale/")
572
+
573
+ config.include("pyramid_mako")
574
+ config.include("c2cwsgiutils.pyramid.includeme")
575
+ config.include(oidc.includeme)
576
+ health_check = HealthCheck(config)
577
+ config.registry["health_check"] = health_check
578
+
579
+ metrics_config = config.registry.settings["metrics"]
580
+ if metrics_config["memory_maps_rss"]:
581
+ REGISTRY.register(MemoryMapCollector("rss"))
582
+ if metrics_config["memory_maps_size"]:
583
+ REGISTRY.register(MemoryMapCollector("size"))
584
+ if metrics_config["memory_cache"]:
585
+ REGISTRY.register(MemoryCacheSizeCollector(metrics_config.get("memory_cache_all", False)))
586
+ if metrics_config["raster_data"]:
587
+ REGISTRY.register(RasterDataSizeCollector())
588
+ if metrics_config["total_python_object_memory"]:
589
+ REGISTRY.register(TotalPythonObjectMemoryCollector())
590
+
591
+ # Initialize DBSessions
592
+ init_db_sessions(settings, config, health_check)
593
+
594
+ checker.init(config, health_check)
595
+ check_collector.init(config, health_check)
596
+
597
+ # dogpile.cache configuration
598
+ if "cache" in settings:
599
+ register_backend(
600
+ "c2cgeoportal.hybrid",
601
+ "c2cgeoportal_geoportal.lib.caching",
602
+ "HybridRedisBackend",
603
+ ) # type: ignore[no-untyped-call]
604
+ register_backend(
605
+ "c2cgeoportal.hybridsentinel", "c2cgeoportal_geoportal.lib.caching", "HybridRedisSentinelBackend"
606
+ ) # type: ignore[no-untyped-call]
607
+ for name, cache_config in settings["cache"].items():
608
+ caching.init_region(cache_config, name)
609
+
610
+ @zope.event.classhandler.handler(InvalidateCacheEvent) # type: ignore[misc]
611
+ def handle(event: InvalidateCacheEvent) -> None:
612
+ del event
613
+ caching.invalidate_region("ogc-server")
614
+ caching.invalidate_region("std")
615
+ caching.invalidate_region("obj")
616
+ if caching.MEMORY_CACHE_DICT:
617
+ caching.get_region("std").delete_multi(list(caching.MEMORY_CACHE_DICT.keys()))
618
+ caching.MEMORY_CACHE_DICT.clear()
619
+
620
+ # Register a tween to get back the cache buster path.
621
+ if "cache_path" not in config.get_settings():
622
+ config.get_settings()["cache_path"] = ["static", "static-geomapfish"]
623
+ config.add_tween("c2cgeoportal_geoportal.lib.cacheversion.CachebusterTween")
624
+ config.add_tween("c2cgeoportal_geoportal.lib.headers.HeadersTween")
625
+
626
+ # Bind the mako renderer to other file extensions
627
+ add_mako_renderer(config, ".html")
628
+ add_mako_renderer(config, ".js")
629
+
630
+ # Add the "geojson" renderer
631
+ config.add_renderer("geojson", GeoJSON())
632
+
633
+ # Add the "xsd" renderer
634
+ config.add_renderer("xsd", XSD(include_foreign_keys=True))
635
+
636
+ # Add the set_user_validator directive, and set a default user validator
637
+ config.add_directive("set_user_validator", set_user_validator)
638
+ config.set_user_validator(default_user_validator)
639
+
640
+ config.add_route("oauth2introspect", "/oauth/introspect", request_method="POST")
641
+ config.add_route("oauth2token", "/oauth/token", request_method="POST")
642
+ config.add_route("oauth2loginform", "/oauth/login", request_method="GET")
643
+ config.add_route("oauth2revoke_token", "/oauth/revoke_token", request_method="GET")
644
+ config.add_route("notlogin", "/notlogin", request_method="GET")
645
+
646
+ config.add_route("dynamic", "/dynamic.json", request_method="GET")
647
+
648
+ # Add routes to the mapserver proxy
649
+ config.add_route_predicate("mapserverproxy", MapserverproxyRoutePredicate)
650
+ config.add_route(
651
+ "mapserverproxy",
652
+ "/mapserv_proxy",
653
+ mapserverproxy=True,
654
+ pregenerator=C2CPregenerator(role=True),
655
+ request_method="GET",
656
+ )
657
+ config.add_route(
658
+ "mapserverproxy_post",
659
+ "/mapserv_proxy",
660
+ mapserverproxy=True,
661
+ pregenerator=C2CPregenerator(role=True),
662
+ request_method="POST",
663
+ )
664
+ # OGC Api routes
665
+ config.add_route(
666
+ "mapserverproxy_ogcapi_mapserver",
667
+ "/mapserv_proxy/{ogcserver}/ogcapi/*path",
668
+ mapserverproxy=True,
669
+ pregenerator=C2CPregenerator(role=True),
670
+ request_method="GET",
671
+ )
672
+ config.add_route(
673
+ "mapserverproxy_ogcapi_qgisserver",
674
+ "/mapserv_proxy/{ogcserver}/wfs3/*path",
675
+ mapserverproxy=True,
676
+ pregenerator=C2CPregenerator(role=True),
677
+ request_method="GET",
678
+ )
679
+ add_cors_route(config, "/mapserv_proxy", "mapserver")
680
+
681
+ # Add route to the tinyows proxy
682
+ config.add_route("tinyowsproxy", "/tinyows_proxy", pregenerator=C2CPregenerator(role=True))
683
+
684
+ # Add routes to the entry view class
685
+ config.add_route("base", "/", static=True)
686
+ config.add_route("loginform", "/login.html", request_method="GET")
687
+ add_cors_route(config, "/login", "login")
688
+ config.add_route("login", "/login", request_method="POST")
689
+ add_cors_route(config, "/logout", "login")
690
+ config.add_route("logout", "/logout", request_method="GET")
691
+ add_cors_route(config, "/loginchangepassword", "login")
692
+ config.add_route("change_password", "/loginchangepassword", request_method="POST")
693
+ add_cors_route(config, "/loginresetpassword", "login")
694
+ config.add_route("loginresetpassword", "/loginresetpassword", request_method="POST")
695
+ add_cors_route(config, "/loginuser", "login")
696
+ config.add_route("loginuser", "/loginuser", request_method="GET")
697
+ config.add_route("testi18n", "/testi18n.html", request_method="GET")
698
+ config.add_route("oidc_login", "/oidc/login", request_method="GET")
699
+ config.add_route("oidc_callback", "/oidc/callback", request_method="GET")
700
+
701
+ config.add_renderer(".map", AssetRendererFactory)
702
+ config.add_renderer(".css", AssetRendererFactory)
703
+ config.add_renderer(".ico", AssetRendererFactory)
704
+ config.add_route("localejson", "/locale.json", request_method="GET")
705
+ config.add_route("localepot", "/locale.pot", request_method="GET")
706
+
707
+ def add_static_route(name: str, attr: str, path: str, renderer: str) -> None:
708
+ config.add_route(name, path, request_method="GET")
709
+ config.add_view(Entry, attr=attr, route_name=name, renderer=renderer)
710
+
711
+ static_files = config.get_settings().get("static_files", {})
712
+ for name, attr, path in [
713
+ ("favicon.ico", "favicon", "/favicon.ico"),
714
+ ("robot.txt", "robot_txt", "/robot.txt"),
715
+ ("api.js.map", "apijsmap", "/api.js.map"),
716
+ ("api.css", "apicss", "/api.css"),
717
+ ("apihelp.html", "apihelp", "/apihelp/index.html"),
718
+ ]:
719
+ if static_files.get(name):
720
+ add_static_route(name, attr, path, static_files[name])
721
+ config.add_route("apijs", "/api.js", request_method="GET")
722
+ c2cgeoportal_geoportal.views.add_redirect(config, "apihelp_redirect", "/apihelp.html", "apihelp.html")
723
+
724
+ config.add_route("themes", "/themes", request_method="GET", pregenerator=C2CPregenerator(role=True))
725
+ add_cors_route(config, "/themes", "themes")
726
+
727
+ config.add_route("invalidate", "/invalidate", request_method="GET")
728
+
729
+ # Print proxy routes
730
+ config.add_route("printproxy", "/printproxy", request_method="HEAD")
731
+ add_cors_route(config, "/printproxy/*all", "print")
732
+ config.add_route(
733
+ "printproxy_capabilities",
734
+ "/printproxy/capabilities.json",
735
+ request_method="GET",
736
+ pregenerator=C2CPregenerator(role=True),
737
+ )
738
+ config.add_route(
739
+ "printproxy_report_create",
740
+ "/printproxy/report.{format}",
741
+ request_method="POST",
742
+ header=_JSON_CONTENT_TYPE,
743
+ )
744
+ config.add_route("printproxy_status", "/printproxy/status/{ref}.json", request_method="GET")
745
+ config.add_route("printproxy_cancel", "/printproxy/cancel/{ref}", request_method="DELETE")
746
+ config.add_route("printproxy_report_get", "/printproxy/report/{ref}", request_method="GET")
747
+
748
+ # Full-text search routes
749
+ add_cors_route(config, "/search", "fulltextsearch")
750
+ config.add_route("fulltextsearch", "/search", request_method="GET")
751
+
752
+ # Access to raster data
753
+ add_cors_route(config, "/raster", "raster")
754
+ config.add_route("raster", "/raster", request_method="GET")
755
+
756
+ add_cors_route(config, "/profile.json", "profile")
757
+ config.add_route("profile.json", "/profile.json", request_method="POST")
758
+
759
+ # Access to vector tiles
760
+ add_cors_route(config, "/vector_tiles", "vector_tiles")
761
+ config.add_route("vector_tiles", "/vector_tiles/{layer_name}/{z}/{x}/{y}.pbf", request_method="GET")
762
+ # There is no view corresponding to that route, it is to be used from
763
+ # mako templates to get the root of the "vector_tiles" web service
764
+ config.add_route("vector_tiles_root", "/vector_tiles", request_method="HEAD")
765
+
766
+ # Shortener
767
+ add_cors_route(config, "/short/create", "shortener")
768
+ config.add_route("shortener_create", "/short/create", request_method="POST")
769
+ config.add_route("shortener_get", "/s/{ref}", request_method="GET")
770
+
771
+ # Geometry processing
772
+ config.add_route("difference", "/difference", request_method="POST")
773
+
774
+ # PDF report tool
775
+ config.add_route("pdfreport", "/pdfreport/{layername}/{ids}", request_method="GET")
776
+
777
+ # Add routes for the "layers" web service
778
+ add_cors_route(config, "/layers/*all", "layers")
779
+ config.add_route("layers_count", "/layers/{layer_id:\\d+}/count", request_method="GET")
780
+ config.add_route(
781
+ "layers_metadata",
782
+ "/layers/{layer_id:\\d+}/md.xsd",
783
+ request_method="GET",
784
+ pregenerator=C2CPregenerator(role=True),
785
+ )
786
+ config.add_route(
787
+ "layers_read_many", "/layers/{layer_id:\\d+,?(\\d+,)*\\d*$}", request_method="GET"
788
+ ) # supports URLs like /layers/1,2,3
789
+ config.add_route("layers_read_one", "/layers/{layer_id:\\d+}/{feature_id}", request_method="GET")
790
+ config.add_route(
791
+ "layers_create", "/layers/{layer_id:\\d+}", request_method="POST", header=_GEOJSON_CONTENT_TYPE
792
+ )
793
+ config.add_route(
794
+ "layers_update",
795
+ "/layers/{layer_id:\\d+}/{feature_id}",
796
+ request_method="PUT",
797
+ header=_GEOJSON_CONTENT_TYPE,
798
+ )
799
+ config.add_route("layers_delete", "/layers/{layer_id:\\d+}/{feature_id}", request_method="DELETE")
800
+ config.add_route(
801
+ "layers_enumerate_attribute_values",
802
+ "/layers/{layer_name}/values/{field_name}",
803
+ request_method="GET",
804
+ pregenerator=C2CPregenerator(),
805
+ )
806
+ # There is no view corresponding to that route, it is to be used from
807
+ # mako templates to get the root of the "layers" web service
808
+ config.add_route("layers_root", "/layers", request_method="HEAD")
809
+
810
+ # Resource proxy (load external url, useful when loading non https content)
811
+ config.add_route("resourceproxy", "/resourceproxy", request_method="GET")
812
+
813
+ # Dev
814
+ config.add_route("dev", "/dev/*path", request_method="GET")
815
+
816
+ # Used memory in caches
817
+ config.add_route("memory", "/memory", request_method="GET")
818
+
819
+ config.add_route("ogc_server_clear_cache", "clear-ogc-server-cache/{id}", request_method="GET")
820
+
821
+ # Scan view decorator for adding routes
822
+ config.scan(
823
+ ignore=[
824
+ "c2cgeoportal_geoportal.lib",
825
+ "c2cgeoportal_geoportal.scaffolds",
826
+ "c2cgeoportal_geoportal.scripts",
827
+ ]
828
+ )
829
+
830
+ admin_interface = (
831
+ config.get_settings().get("enable_admin_interface", False)
832
+ and importlib.util.find_spec("c2cgeoportal_admin") is not None
833
+ )
834
+ if admin_interface:
835
+ add_admin_interface(config)
836
+ else:
837
+ if not config.get_settings().get("enable_admin_interface", False):
838
+ _LOG.info("Admin interface disabled by configuration")
839
+ else:
840
+ _LOG.info("Admin interface disabled because c2cgeoportal_admin is not installed")
841
+ add_getitfixed(config)
842
+
843
+ # Add the project static view with cache buster
844
+ config.add_static_view(
845
+ name="static",
846
+ path="/etc/geomapfish/static",
847
+ cache_max_age=int(config.get_settings()["default_max_age"]),
848
+ )
849
+ config.add_cache_buster("/etc/geomapfish/static", version_cache_buster)
850
+
851
+ # Add the project static view without cache buster
852
+ config.add_static_view(
853
+ name="static-ngeo",
854
+ path="/etc/static-ngeo",
855
+ cache_max_age=int(config.get_settings()["default_max_age"]),
856
+ )
857
+ config.add_static_view(
858
+ name="static-frontend",
859
+ path="/etc/static-frontend",
860
+ cache_max_age=int(config.get_settings()["default_max_age"]),
861
+ )
862
+
863
+ # Add the c2cgeoportal static view with cache buster
864
+ config.add_static_view(
865
+ name="static-geomapfish",
866
+ path="c2cgeoportal_geoportal:static",
867
+ cache_max_age=int(config.get_settings()["default_max_age"]),
868
+ )
869
+ config.add_cache_buster("c2cgeoportal_geoportal:static", version_cache_buster)
870
+
871
+ # Add the project static view without cache buster
872
+ config.add_static_view(
873
+ name="static-ngeo-dist",
874
+ path="/opt/c2cgeoportal/geoportal/node_modules/ngeo/dist",
875
+ cache_max_age=int(config.get_settings()["default_max_age"]),
876
+ )
877
+
878
+ # Handles the other HTTP errors raised by the views. Without that,
879
+ # the client receives a status=200 without content.
880
+ config.add_view(error_handler, context=HTTPException)
881
+
882
+ c2cwsgiutils.index.additional_title = (
883
+ '<div class="row"><div class="col-lg-3"><h2>GeoMapFish</h2></div><div class="col-lg">'
884
+ )
885
+
886
+ c2cwsgiutils.index.additional_auth.extend(
887
+ [
888
+ '<a href="../tiles/admin/">TileCloud chain admin</a><br>',
889
+ '<a href="../tiles/c2c/">TileCloud chain c2c tools</a><br>',
890
+ '<a href="../invalidate">Invalidate the cache</a><br>',
891
+ '<a href="../memory">Memory status</a><br>',
892
+ ]
893
+ )
894
+ if admin_interface:
895
+ c2cwsgiutils.index.additional_noauth.append('<a href="../admin/">Admin</a><br>')
896
+
897
+ c2cwsgiutils.index.additional_noauth.append(
898
+ '</div></div><div class="row"><div class="col-lg-3"><h3>Interfaces</h3></div><div class="col-lg">'
899
+ )
900
+ c2cwsgiutils.index.additional_noauth.append('<a href="../">Default</a><br>')
901
+ for interface in config.get_settings().get("interfaces", []):
902
+ if not interface.get("default", False):
903
+ c2cwsgiutils.index.additional_noauth.append(
904
+ '<a href="../{interface}">{interface}</a><br>'.format(interface=interface["name"])
905
+ )
906
+ c2cwsgiutils.index.additional_noauth.append('<a href="../apihelp/index.html">API help</a><br>')
907
+ c2cwsgiutils.index.additional_noauth.append("</div></div><hr>")
908
+
909
+
910
+ def init_db_sessions(
911
+ settings: dict[str, Any], config: Configurator, health_check: HealthCheck | None = None
912
+ ) -> None:
913
+ """Initialize the database sessions."""
914
+ db_chooser = settings.get("db_chooser", {})
915
+ master_paths = [i.replace("//", "/") for i in db_chooser.get("master", [])]
916
+ slave_paths = [i.replace("//", "/") for i in db_chooser.get("slave", [])]
917
+
918
+ slave_prefix = "sqlalchemy_slave" if "sqlalchemy_slave.url" in settings else None
919
+
920
+ c2cgeoportal_commons.models.DBSession, rw_bind, _ = c2cwsgiutils.db.setup_session( # type: ignore[assignment]
921
+ config, "sqlalchemy", slave_prefix, force_master=master_paths, force_slave=slave_paths
922
+ )
923
+ c2cgeoportal_commons.models.Base.metadata.bind = rw_bind # type: ignore[attr-defined]
924
+ assert c2cgeoportal_commons.models.DBSession is not None
925
+ c2cgeoportal_commons.models.DBSessions["dbsession"] = c2cgeoportal_commons.models.DBSession
926
+
927
+ for dbsession_name, dbsession_config in settings.get("dbsessions", {}).items(): # pragma: nocover
928
+ c2cgeoportal_commons.models.DBSessions[dbsession_name] = c2cwsgiutils.db.create_session( # type: ignore[assignment]
929
+ config, dbsession_name, **dbsession_config
930
+ )
931
+
932
+ c2cgeoportal_commons.models.Base.metadata.clear()
933
+ from c2cgeoportal_commons.models import main # pylint: disable=import-outside-toplevel
934
+
935
+ if health_check is not None:
936
+ for name, session in c2cgeoportal_commons.models.DBSessions.items():
937
+ if name == "dbsession":
938
+ health_check.add_db_session_check(session, at_least_one_model=main.Theme, level=1)
939
+ alembic_ini = os.path.join(os.path.abspath(os.path.curdir), "alembic.ini")
940
+ if os.path.exists(alembic_ini):
941
+ health_check.add_alembic_check(
942
+ session,
943
+ alembic_ini_path=alembic_ini,
944
+ name="main",
945
+ version_schema=settings["schema"],
946
+ level=1,
947
+ )
948
+ health_check.add_alembic_check(
949
+ session,
950
+ alembic_ini_path=alembic_ini,
951
+ name="static",
952
+ version_schema=settings["schema_static"],
953
+ level=1,
954
+ )
955
+ else:
956
+
957
+ def check(session_: sqlalchemy.orm.scoped_session[sqlalchemy.orm.Session]) -> None:
958
+ session_.execute(sqlalchemy.text("SELECT 1"))
959
+
960
+ health_check.add_db_session_check(session, query_cb=check, level=2)