c2cgeoportal-geoportal 2.5.0.100__py2.py3-none-any.whl → 2.7.1.83__py2.py3-none-any.whl

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