c2cgeoportal-geoportal 2.6.0__py2.py3-none-any.whl → 2.8.1.180__py2.py3-none-any.whl

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