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