c2cgeoportal-geoportal 2.3.5.80__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.80.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.80.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.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,347 @@
1
+ # Copyright (c) 2014-2023, 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 gettext
30
+ import os
31
+ import sys
32
+ from argparse import ArgumentParser, Namespace
33
+ from collections.abc import Iterator
34
+ from typing import TYPE_CHECKING, Any, Optional
35
+
36
+ import pyramid.config
37
+ import transaction
38
+ from sqlalchemy import func
39
+ from sqlalchemy.orm.session import Session
40
+
41
+ from c2cgeoportal_geoportal.lib.bashcolor import Color, colorize
42
+ from c2cgeoportal_geoportal.lib.fulltextsearch import Normalize
43
+ from c2cgeoportal_geoportal.lib.i18n import LOCALE_PATH
44
+ from c2cgeoportal_geoportal.scripts import fill_arguments, get_appsettings, get_session
45
+
46
+ if TYPE_CHECKING:
47
+ import c2cgeoportal_commons.models.main
48
+
49
+
50
+ def get_argparser() -> ArgumentParser:
51
+ """Get the argument parser for this script."""
52
+ parser = ArgumentParser(
53
+ prog=sys.argv[0],
54
+ add_help=True,
55
+ description="Tool to fill the tsearch table (full-text search) from the theme information.",
56
+ )
57
+
58
+ parser.add_argument(
59
+ "--locale-folder",
60
+ default=LOCALE_PATH,
61
+ help=f"The folder where the locale files are stored (default is {LOCALE_PATH})",
62
+ )
63
+ parser.add_argument("--interfaces", action="append", help="the interfaces to export")
64
+ parser.add_argument(
65
+ "--exclude-interfaces",
66
+ action="append",
67
+ default=["api"],
68
+ help="the interfaces to exclude (can't be used with --interfaces)",
69
+ )
70
+ parser.add_argument(
71
+ "--duplicate-name",
72
+ action="store_true",
73
+ dest="name",
74
+ help="allows to add a name more than one time,\n"
75
+ "by default if we find more than one element with the same name "
76
+ "only one will be imported",
77
+ )
78
+ parser.add_argument("--no-themes", action="store_false", dest="themes", help="do not import the themes")
79
+ parser.add_argument(
80
+ "--no-blocks",
81
+ action="store_false",
82
+ dest="blocks",
83
+ help="do not import the blocks (first level layer groups)",
84
+ )
85
+ parser.add_argument(
86
+ "--no-folders", action="store_false", dest="folders", help="do not import the folders (tree folders)"
87
+ )
88
+ parser.add_argument(
89
+ "--no-layers", action="store_false", dest="layers", help="do not import the layers (tree leaf)"
90
+ )
91
+ parser.add_argument("--package", help="the application package")
92
+ fill_arguments(parser)
93
+ return parser
94
+
95
+
96
+ def main() -> None:
97
+ """Run the command."""
98
+
99
+ options = get_argparser().parse_args()
100
+ settings = get_appsettings(options)
101
+
102
+ with transaction.manager:
103
+ session = get_session(settings, transaction.manager)
104
+
105
+ Import(session, settings, options)
106
+
107
+
108
+ class Import:
109
+ """
110
+ To import all the themes, layer groups and layers names into the full-text search table.
111
+
112
+ Done by interface and by language.
113
+ """
114
+
115
+ def __init__(self, session: Session, settings: pyramid.config.Configurator, options: Namespace):
116
+ self.options = options
117
+ self.imported: set[Any] = set()
118
+ package = settings["package"]
119
+
120
+ self.fts_languages = settings["fulltextsearch"]["languages"]
121
+ self.languages = settings["available_locale_names"]
122
+ self.fts_normalizer = Normalize(settings["fulltextsearch"])
123
+
124
+ fts_missing_langs = [lang for lang in self.languages if lang not in self.fts_languages]
125
+ if fts_missing_langs:
126
+ msg = f"Keys {fts_missing_langs} are missing in fulltextsearch languages configuration."
127
+ if os.environ.get("IGNORE_I18N_ERRORS", "FALSE") == "TRUE":
128
+ print(colorize(msg, Color.RED))
129
+ self.languages = [lang for lang in self.languages if lang in self.fts_languages]
130
+ else:
131
+ raise KeyError(KeyError(msg))
132
+
133
+ # must be done only once we have loaded the project config
134
+ from c2cgeoportal_commons.models.main import ( # pylint: disable=import-outside-toplevel
135
+ FullTextSearch,
136
+ Interface,
137
+ Role,
138
+ Theme,
139
+ )
140
+
141
+ self.session = session
142
+ self.session.execute(FullTextSearch.__table__.delete().where(FullTextSearch.from_theme))
143
+
144
+ self._: dict[str, gettext.NullTranslations] = {}
145
+ for lang in self.languages:
146
+ try:
147
+ self._[lang] = gettext.translation(
148
+ f"{package}_geoportal-client",
149
+ options.locale_folder.format(package=package),
150
+ [lang],
151
+ )
152
+ except OSError as e:
153
+ self._[lang] = gettext.NullTranslations()
154
+ print(f"Warning: {e} (language: {lang})")
155
+
156
+ query = self.session.query(Interface)
157
+ if options.interfaces is not None:
158
+ query = query.filter(Interface.name.in_(options.interfaces))
159
+ else:
160
+ query = query.filter(Interface.name.notin_(options.exclude_interfaces))
161
+ self.interfaces = query.all()
162
+
163
+ self.public_theme: dict[int, list[int]] = {}
164
+ self.public_group: dict[int, list[int]] = {}
165
+ for interface in self.interfaces:
166
+ self.public_theme[interface.id] = []
167
+ self.public_group[interface.id] = []
168
+
169
+ for theme in self.session.query(Theme).filter_by(public=True).all():
170
+ self._add_theme(theme)
171
+
172
+ for role in self.session.query(Role).all():
173
+ for theme in self.session.query(Theme).all():
174
+ self._add_theme(theme, role)
175
+
176
+ def _add_fts(
177
+ self,
178
+ item: "c2cgeoportal_commons.models.main.TreeItem",
179
+ interface: "c2cgeoportal_commons.models.main.Interface",
180
+ action: str,
181
+ role: Optional["c2cgeoportal_commons.models.main.Role"],
182
+ ) -> None:
183
+ from c2cgeoportal_commons.models.main import FullTextSearch # pylint: disable=import-outside-toplevel
184
+
185
+ key = (
186
+ item.name if self.options.name else item.id,
187
+ interface.id,
188
+ role.id if role is not None else None,
189
+ )
190
+ if key not in self.imported:
191
+ self.imported.add(key)
192
+ for lang in self.languages:
193
+ fts = FullTextSearch()
194
+ fts.label = self._render_label(item, lang)
195
+ fts.role = role
196
+ fts.interface = interface
197
+ fts.lang = lang
198
+ fts.public = role is None
199
+ fts.ts = func.to_tsvector(
200
+ self.fts_languages[lang],
201
+ " ".join(
202
+ [self.fts_normalizer(self._[lang].gettext(item.name))]
203
+ + [v.strip() for m in item.get_metadata("searchAlias") for v in m.value.split(",")]
204
+ ),
205
+ )
206
+ fts.actions = [{"action": action, "data": item.name}]
207
+ fts.from_theme = True
208
+ self.session.add(fts)
209
+
210
+ def _add_theme(
211
+ self,
212
+ theme: "c2cgeoportal_commons.models.main.Theme",
213
+ role: Optional["c2cgeoportal_commons.models.main.Role"] = None,
214
+ ) -> None:
215
+ fill = False
216
+ for interface in self.interfaces:
217
+ if interface in theme.interfaces:
218
+ for child in theme.children:
219
+ fill = self._add_block(child, interface, role) or fill
220
+
221
+ if fill and self.options.themes:
222
+ if role is None:
223
+ self.public_theme[interface.id].append(theme.id)
224
+
225
+ if role is None or theme.id not in self.public_theme[interface.id]:
226
+ self._add_fts(theme, interface, "add_theme", role)
227
+
228
+ def _add_block(
229
+ self,
230
+ group: "c2cgeoportal_commons.models.main.LayerGroup",
231
+ interface: "c2cgeoportal_commons.models.main.Interface",
232
+ role: Optional["c2cgeoportal_commons.models.main.Role"],
233
+ ) -> bool:
234
+ return self._add_group(group, interface, self.options.blocks, role)
235
+
236
+ def _add_folder(
237
+ self,
238
+ group: "c2cgeoportal_commons.models.main.LayerGroup",
239
+ interface: "c2cgeoportal_commons.models.main.Interface",
240
+ role: Optional["c2cgeoportal_commons.models.main.Role"],
241
+ ) -> bool:
242
+ return self._add_group(group, interface, self.options.folders, role)
243
+
244
+ def _add_group(
245
+ self,
246
+ group: "c2cgeoportal_commons.models.main.LayerGroup",
247
+ interface: "c2cgeoportal_commons.models.main.Interface",
248
+ export: bool,
249
+ role: Optional["c2cgeoportal_commons.models.main.Role"],
250
+ ) -> bool:
251
+ from c2cgeoportal_commons.models.main import LayerGroup # pylint: disable=import-outside-toplevel
252
+
253
+ fill = False
254
+ for child in group.children:
255
+ if isinstance(child, LayerGroup):
256
+ fill = self._add_folder(child, interface, role) or fill
257
+ else:
258
+ fill = self._add_layer(child, interface, role) or fill
259
+
260
+ if fill and export:
261
+ if role is None:
262
+ self.public_group[interface.id].append(group.id)
263
+
264
+ if role is None or group.id not in self.public_group[interface.id]:
265
+ self._add_fts(group, interface, "add_group", role)
266
+
267
+ return fill
268
+
269
+ @staticmethod
270
+ def _layer_visible(
271
+ layer: "c2cgeoportal_commons.models.main.Layer", role: "c2cgeoportal_commons.models.main.Role"
272
+ ) -> bool:
273
+ for restrictionarea in layer.restrictionareas:
274
+ if role in restrictionarea.roles:
275
+ return True
276
+ return False
277
+
278
+ def _add_layer(
279
+ self,
280
+ layer: "c2cgeoportal_commons.models.main.Layer",
281
+ interface: "c2cgeoportal_commons.models.main.Interface",
282
+ role: Optional["c2cgeoportal_commons.models.main.Role"],
283
+ ) -> bool:
284
+ if role is None:
285
+ fill = layer.public and interface in layer.interfaces
286
+ else:
287
+ fill = interface in layer.interfaces and not layer.public and self._layer_visible(layer, role)
288
+
289
+ if fill and self.options.layers:
290
+ self._add_fts(layer, interface, "add_layer", role)
291
+
292
+ return fill
293
+
294
+ def _render_label(
295
+ self,
296
+ item: "c2cgeoportal_commons.models.main.TreeItem",
297
+ lang: str,
298
+ ) -> str:
299
+ patterns = item.get_metadata("searchLabelPattern")
300
+ if not patterns:
301
+ return self._[lang].gettext(item.name)
302
+ pattern = patterns[0]
303
+ assert isinstance(pattern.value, str)
304
+ tree_paths = list(self._get_paths(item))
305
+ # Remove paths where the last element isn't a theme
306
+ tree_paths = [p for p in tree_paths if p[-1].item_type == "theme"]
307
+ result = None
308
+ current_result = None
309
+ if tree_paths:
310
+ for path in tree_paths:
311
+ if len(path) == 2:
312
+ current_result = pattern.value.format(
313
+ name=self._[lang].gettext(item.name),
314
+ theme=self._[lang].gettext(path[-1].name),
315
+ parent=self._[lang].gettext(path[1].name),
316
+ )
317
+ elif len(path) > 2:
318
+ current_result = pattern.value.format(
319
+ name=self._[lang].gettext(item.name),
320
+ theme=self._[lang].gettext(path[-1].name),
321
+ parent=self._[lang].gettext(path[1].name),
322
+ block=self._[lang].gettext(path[-2].name),
323
+ )
324
+ if result and current_result != result:
325
+ sys.stderr.write(
326
+ f"WARNING: the item {item.name} (id: {item.id}) has a label pattern and inconsistent "
327
+ f"multiple parents\n"
328
+ )
329
+ return self._[lang].gettext(item.name)
330
+ result = current_result
331
+ return result or pattern.value.format(
332
+ name=self._[lang].gettext(item.name),
333
+ theme=self._[lang].gettext(item.name),
334
+ )
335
+
336
+ def _get_paths(
337
+ self,
338
+ item: "c2cgeoportal_commons.models.main.TreeItem",
339
+ ) -> Iterator[list["c2cgeoportal_commons.models.main.TreeItem"]]:
340
+ if item is None:
341
+ return
342
+ if any(item.parents):
343
+ for parent in item.parents:
344
+ for path in self._get_paths(parent):
345
+ yield [item, *path]
346
+ else:
347
+ yield [item]
@@ -0,0 +1,81 @@
1
+ # Copyright (c) 2012-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 argparse
29
+ import binascii
30
+ import json
31
+ import sys
32
+ import time
33
+ from typing import cast
34
+
35
+ from Crypto.Cipher import AES # nosec
36
+ from Crypto.Cipher.ChaCha20_Poly1305 import ChaCha20Poly1305Cipher # nosec
37
+
38
+ from c2cgeoportal_geoportal.scripts import fill_arguments, get_appsettings
39
+
40
+
41
+ def create_token(aeskey: str, user: str, password: str, valid: bool) -> str:
42
+ """Create the authenticated URL token."""
43
+ auth = {"u": user, "p": password, "t": int(time.time()) + valid * 3600 * 24}
44
+
45
+ if aeskey is None:
46
+ print("urllogin is not configured")
47
+ sys.exit(1)
48
+ cipher = cast(ChaCha20Poly1305Cipher, AES.new(aeskey.encode("ascii"), AES.MODE_EAX))
49
+ data = json.dumps(auth)
50
+ mod_len = len(data) % 16
51
+ if mod_len != 0:
52
+ data += "".join([" " for i in range(16 - mod_len)])
53
+ ciphertext, tag = cipher.encrypt_and_digest(data.encode("utf-8"))
54
+ return binascii.hexlify(cipher.nonce + tag + ciphertext).decode("ascii")
55
+
56
+
57
+ def get_argparser() -> argparse.ArgumentParser:
58
+ """Get the argument parser for this script."""
59
+
60
+ parser = argparse.ArgumentParser(description="Generate an auth token")
61
+ fill_arguments(parser, use_attribute=True)
62
+ parser.add_argument("user", help="The username")
63
+ parser.add_argument("password", help="The password")
64
+ parser.add_argument("valid", type=int, default=1, nargs="?", help="Is valid for, in days")
65
+ return parser
66
+
67
+
68
+ def main() -> None:
69
+ """Run the command."""
70
+
71
+ args = get_argparser().parse_args()
72
+ settings = get_appsettings(args)
73
+ urllogin = settings.get("urllogin", {})
74
+ aeskey = urllogin.get("aes_key")
75
+ auth_enc = create_token(aeskey, args.user, args.password, args.valid)
76
+
77
+ print(f"Use: auth={auth_enc}")
78
+
79
+
80
+ if __name__ == "__main__":
81
+ main()
@@ -0,0 +1,90 @@
1
+ <!doctype html>
2
+ <html lang="${lang}">
3
+ <head>
4
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
5
+ <meta name="Content-Language" content="${lang}" />
6
+ <title>${_('Login to Geoportal Application')}</title>
7
+ <style>
8
+ body {
9
+ background: #444444;
10
+ font-family: Helvetica, Arial, sans-serif;
11
+ }
12
+ * {
13
+ padding: 0;
14
+ margin: 0;
15
+ }
16
+ div#content {
17
+ background: #f5f5ef;
18
+ padding: 20px 30px;
19
+ width: 330px;
20
+ margin: 100px auto auto auto;
21
+ -webkit-border-radius: 10px;
22
+ border-radius: 10px;
23
+ }
24
+ td.field {
25
+ padding-top: 20px;
26
+ }
27
+ td.field p {
28
+ font-weight: bold;
29
+ color: #444444;
30
+ font-size: 80%;
31
+ margin-bottom: 3px;
32
+ }
33
+ td.field input {
34
+ -webkit-border-radius: 2px;
35
+ border-radius: 2px;
36
+ border: solid 1px #444444;
37
+ padding: 3px 6px;
38
+ font-size: 110%;
39
+ width: 250px;
40
+ }
41
+ td.buttons {
42
+ padding-top: 25px;
43
+ padding-bottom: 20px;
44
+ text-align: right;
45
+ }
46
+ td.buttons input {
47
+ padding: 1px 6px;
48
+ font-weight: bold;
49
+ color: #444444;
50
+ }
51
+ </style>
52
+ </head>
53
+ <body>
54
+ <div id="content">
55
+ <form method="POST" id="loginForm" action="${request.route_url('login', _query=login_params)}">
56
+ <table style="margin-left: auto; margin-right: auto">
57
+ <tbody>
58
+ <tr>
59
+ <td class="field">
60
+ <p>
61
+ <label for="email">${_('Username')}</label>
62
+ </p>
63
+ <input id="login" name="login" type="text" />
64
+ </td>
65
+ </tr>
66
+ <tr>
67
+ <td class="field">
68
+ <p>
69
+ <label for="password">${_('Password')}</label>
70
+ </p>
71
+ <input id="password" name="password" type="password" />
72
+ </td>
73
+ </tr>
74
+ % if two_fa:
75
+ <tr>
76
+ <td class="field">
77
+ <p><label for="otp">${_('Authentication code')}</label></p>
78
+ <input id="otp" name="otp" type="text" autocomplete="off" />
79
+ </td>
80
+ </tr>
81
+ % endif
82
+ <tr>
83
+ <td class="buttons"><input tabindex="4" value="${_('Login')}" type="submit" /></td>
84
+ </tr>
85
+ </tbody>
86
+ </table>
87
+ </form>
88
+ </div>
89
+ </body>
90
+ </html>
@@ -0,0 +1,62 @@
1
+ <!doctype html>
2
+ <html lang="${lang}">
3
+ <head>
4
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
5
+ <meta name="Content-Language" content="${lang}" />
6
+ <title>${_('Login to Geoportal Application')}</title>
7
+ <style>
8
+ body {
9
+ background: #444444;
10
+ font-family: Helvetica, Arial, sans-serif;
11
+ }
12
+ * {
13
+ padding: 0;
14
+ margin: 0;
15
+ }
16
+ div#content {
17
+ background: #f5f5ef;
18
+ padding: 20px 30px;
19
+ width: 330px;
20
+ margin: 100px auto auto auto;
21
+ -webkit-border-radius: 10px;
22
+ border-radius: 10px;
23
+ }
24
+ td.field {
25
+ padding-top: 20px;
26
+ }
27
+ td.field p {
28
+ font-weight: bold;
29
+ color: #444444;
30
+ font-size: 80%;
31
+ margin-bottom: 3px;
32
+ }
33
+ td.field input {
34
+ -webkit-border-radius: 2px;
35
+ border-radius: 2px;
36
+ border: solid 1px #444444;
37
+ padding: 3px 6px;
38
+ font-size: 110%;
39
+ width: 250px;
40
+ }
41
+ td.buttons {
42
+ padding-top: 25px;
43
+ padding-bottom: 20px;
44
+ text-align: right;
45
+ }
46
+ td.buttons input {
47
+ padding: 1px 6px;
48
+ font-weight: bold;
49
+ color: #444444;
50
+ }
51
+ </style>
52
+ </head>
53
+ <body>
54
+ <div id="content">
55
+ <p>
56
+ ${_('The user is not correctly initialized you should login in the main application and try '
57
+ 'again.')}
58
+ </p>
59
+ <p><a href="${request.route_url('base')}">${_('Main application')}</a></p>
60
+ </div>
61
+ </body>
62
+ </html>
@@ -0,0 +1,12 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3
+ <html xmlns="http://www.w3.org/1999/xhtml">
4
+ <head>
5
+ <meta charset="UTF-8" />
6
+ <title>${title}</title>
7
+ </head>
8
+ <body>
9
+ <h1>${title}</h1>
10
+ <p>${_('my translation test')}</p>
11
+ </body>
12
+ </html>
@@ -0,0 +1,59 @@
1
+ # Copyright (c) 2011-2023, 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 pyramid.config
30
+ import pyramid.request
31
+ from pyramid.httpexceptions import HTTPFound
32
+
33
+
34
+ def add_ending_slash(request: pyramid.request.Request) -> HTTPFound:
35
+ """Add an ending slash view."""
36
+ return HTTPFound(location=request.path + "/")
37
+
38
+
39
+ def add_redirect(config: pyramid.config.Configurator, name: str, from_: str, to: str) -> None:
40
+ """Add a redirect view."""
41
+
42
+ def redirect_view(request: pyramid.request.Request) -> HTTPFound:
43
+ return HTTPFound(location=request.route_url(to))
44
+
45
+ config.add_route(name, from_, request_method="GET")
46
+ config.add_view(redirect_view, route_name=name)
47
+
48
+
49
+ def restrict_headers(headers: dict[str, str], whitelist: list[str], blacklist: list[str]) -> dict[str, str]:
50
+ """
51
+ Filter headers with a whitelist then a blacklist.
52
+
53
+ Some default pyramid headers will be added back by pyramid.
54
+ """
55
+ if len(whitelist) > 0:
56
+ headers = {key: value for key, value in headers.items() if key in whitelist}
57
+
58
+ headers = {key: value for key, value in headers.items() if key not in blacklist}
59
+ return headers