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
@@ -0,0 +1,1036 @@
1
+ # Copyright (c) 2021-2024, Camptocamp SA
2
+ # All rights reserved.
3
+
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions are met:
6
+
7
+ # 1. Redistributions of source code must retain the above copyright notice, this
8
+ # list of conditions and the following disclaimer.
9
+ # 2. Redistributions in binary form must reproduce the above copyright notice,
10
+ # this list of conditions and the following disclaimer in the documentation
11
+ # and/or other materials provided with the distribution.
12
+
13
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
14
+ # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
17
+ # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18
+ # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19
+ # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20
+ # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22
+ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23
+
24
+ # The views and conclusions contained in the software and documentation are those
25
+ # of the authors and should not be interpreted as representing official policies,
26
+ # either expressed or implied, of the FreeBSD Project.
27
+
28
+ import logging
29
+ from datetime import datetime, timedelta
30
+ from typing import Any, Dict, List, Union
31
+
32
+ import basicauth
33
+ import oauthlib.common
34
+ import oauthlib.oauth2
35
+ import pyramid.threadlocal
36
+
37
+ import c2cgeoportal_commons # pylint: disable=unused-import
38
+ from c2cgeoportal_geoportal.lib.caching import get_region
39
+
40
+ LOG = logging.getLogger(__name__)
41
+ OBJECT_CACHE_REGION = get_region("obj")
42
+
43
+
44
+ class RequestValidator(oauthlib.oauth2.RequestValidator): # type: ignore
45
+ """The oauth2 request validator implementation."""
46
+
47
+ def __init__(self, authorization_expires_in: int) -> None:
48
+ # in minutes
49
+ self.authorization_expires_in = authorization_expires_in
50
+
51
+ def authenticate_client( # pylint: disable=no-self-use,useless-suppression
52
+ self,
53
+ request: oauthlib.common.Request,
54
+ *args: Any,
55
+ **kwargs: Any,
56
+ ) -> bool:
57
+ """
58
+ Authenticate client through means outside the OAuth 2 spec.
59
+
60
+ Means of authentication is negotiated beforehand and may for example
61
+ be `HTTP Basic Authentication Scheme`_ which utilizes the Authorization
62
+ header.
63
+
64
+ Headers may be accesses through request.headers and parameters found in
65
+ both body and query can be obtained by direct attribute access, i.e.
66
+ request.client_id for client_id in the URL query.
67
+
68
+ Arguments:
69
+
70
+ request: oauthlib.common.Request
71
+
72
+ Returns: True or False
73
+
74
+ Method is used by:
75
+ - Authorization Code Grant
76
+ - Resource Owner Password Credentials Grant (may be disabled)
77
+ - Client Credentials Grant
78
+ - Refresh Token Grant
79
+
80
+ .. _`HTTP Basic Authentication Scheme`: https://tools.ietf.org/html/rfc1945#section-11.1
81
+ """
82
+ del args, kwargs
83
+
84
+ LOG.debug("authenticate_client => unimplemented")
85
+
86
+ raise NotImplementedError("Not implemented, the method `authenticate_client_id` should be used.")
87
+
88
+ def authenticate_client_id( # pylint: disable=no-self-use,useless-suppression
89
+ self,
90
+ client_id: str,
91
+ request: oauthlib.common.Request,
92
+ *args: Any,
93
+ **kwargs: Any,
94
+ ) -> bool:
95
+ """
96
+ Ensure client_id belong to a non-confidential client.
97
+
98
+ A non-confidential client is one that is not required to authenticate
99
+ through other means, such as using HTTP Basic.
100
+
101
+ Note, while not strictly necessary it can often be very convenient
102
+ to set request.client to the client object associated with the
103
+ given client_id.
104
+
105
+ Method is used by:
106
+ - Authorization Code Grant
107
+ """
108
+ del args, kwargs
109
+
110
+ LOG.debug("authenticate_client_id %s", client_id)
111
+
112
+ from c2cgeoportal_commons.models import DBSession, static # pylint: disable=import-outside-toplevel
113
+
114
+ params = dict(request.decoded_body)
115
+
116
+ if "client_secret" in params:
117
+ client_secret = params["client_secret"]
118
+ elif "Authorization" in request.headers:
119
+ username, password = basicauth.decode(request.headers["Authorization"])
120
+ assert client_id == username
121
+ client_secret = password
122
+ else:
123
+ # Unable to get the client secret
124
+ return False
125
+
126
+ request.client = (
127
+ DBSession.query(static.OAuth2Client)
128
+ .filter(static.OAuth2Client.client_id == client_id)
129
+ .filter(static.OAuth2Client.secret == client_secret)
130
+ .one_or_none()
131
+ )
132
+
133
+ LOG.debug("authenticate_client_id => %s", request.client is not None)
134
+ return request.client is not None
135
+
136
+ def client_authentication_required( # pylint: disable=no-self-use,useless-suppression
137
+ self,
138
+ request: oauthlib.common.Request,
139
+ *args: Any,
140
+ **kwargs: Any,
141
+ ) -> bool:
142
+ """
143
+ Determine if client authentication is required for current request.
144
+
145
+ According to the rfc6749, client authentication is required in the following cases:
146
+ - Resource Owner Password Credentials Grant, when Client type is Confidential or when
147
+ Client was issued client credentials or whenever Client provided client
148
+ authentication, see `Section 4.3.2`_.
149
+ - Authorization Code Grant, when Client type is Confidential or when Client was issued
150
+ client credentials or whenever Client provided client authentication,
151
+ see `Section 4.1.3`_.
152
+ - Refresh Token Grant, when Client type is Confidential or when Client was issued
153
+ client credentials or whenever Client provided client authentication, see
154
+ `Section 6`_
155
+
156
+ Arguments:
157
+
158
+ request: oauthlib.common.Request
159
+
160
+ Returns: True or False
161
+
162
+ Method is used by:
163
+ - Authorization Code Grant
164
+ - Resource Owner Password Credentials Grant
165
+ - Refresh Token Grant
166
+
167
+ .. _`Section 4.3.2`: https://tools.ietf.org/html/rfc6749#section-4.3.2
168
+ .. _`Section 4.1.3`: https://tools.ietf.org/html/rfc6749#section-4.1.3
169
+ .. _`Section 6`: https://tools.ietf.org/html/rfc6749#section-6
170
+ """
171
+ del request, args, kwargs
172
+
173
+ LOG.debug("client_authentication_required => False")
174
+
175
+ return False
176
+
177
+ def confirm_redirect_uri( # pylint: disable=no-self-use,useless-suppression
178
+ self,
179
+ client_id: str,
180
+ code: str,
181
+ redirect_uri: str,
182
+ client: "c2cgeoportal_commons.models.static.OAuth2Client", # noqa: F821
183
+ *args: Any,
184
+ **kwargs: Any,
185
+ ) -> bool:
186
+ """
187
+ Ensure that the authorization process is correct.
188
+
189
+ Ensure that the authorization process represented by this authorization code began with this
190
+ ``redirect_uri``.
191
+
192
+ If the client specifies a redirect_uri when obtaining code then that
193
+ redirect URI must be bound to the code and verified equal in this
194
+ method, according to RFC 6749 section 4.1.3. Do not compare against
195
+ the client's allowed redirect URIs, but against the URI used when the
196
+ code was saved.
197
+
198
+ Arguments:
199
+
200
+ client_id: Unicode client identifier
201
+ code: Unicode authorization_code.
202
+ redirect_uri: Unicode absolute URI
203
+ client: Client object set by you, see authenticate_client.
204
+ request: The HTTP Request
205
+
206
+ Returns: True or False
207
+
208
+ Method is used by:
209
+ - Authorization Code Grant (during token request)
210
+ """
211
+ del args, kwargs
212
+
213
+ LOG.debug("confirm_redirect_uri %s %s", client_id, redirect_uri)
214
+
215
+ from c2cgeoportal_commons.models import DBSession, static # pylint: disable=import-outside-toplevel
216
+
217
+ authorization_code = (
218
+ DBSession.query(static.OAuth2AuthorizationCode)
219
+ .join(static.OAuth2AuthorizationCode.client)
220
+ .filter(static.OAuth2AuthorizationCode.code == code)
221
+ .filter(static.OAuth2Client.client_id == client_id)
222
+ .filter(static.OAuth2AuthorizationCode.redirect_uri == redirect_uri)
223
+ .filter(static.OAuth2AuthorizationCode.expire_at > datetime.now())
224
+ .one_or_none()
225
+ )
226
+ LOG.debug("confirm_redirect_uri => %s", authorization_code is not None)
227
+ return authorization_code is not None
228
+
229
+ def get_code_challenge_method( # pylint: disable=no-self-use,useless-suppression
230
+ self, code: str, request: oauthlib.common.Request
231
+ ) -> None:
232
+ """
233
+ Is called during the "token" request processing.
234
+
235
+ When a ``code_verifier`` and a ``code_challenge`` has
236
+ been provided. See ``.get_code_challenge``. Must return ``plain`` or ``S256``. You can return a custom
237
+ value if you have implemented your own ``AuthorizationCodeGrant`` class.
238
+
239
+ Arguments:
240
+
241
+ code: Authorization code.
242
+ request: OAuthlib request.
243
+
244
+ Returns: code_challenge_method string
245
+
246
+ Method is used by:
247
+ - Authorization Code Grant - when PKCE is active
248
+ """
249
+ del code, request
250
+
251
+ LOG.debug("get_code_challenge_method")
252
+
253
+ raise NotImplementedError("Not implemented.")
254
+
255
+ def get_default_redirect_uri( # pylint: disable=no-self-use,useless-suppression
256
+ self,
257
+ client_id: str,
258
+ request: oauthlib.common.Request,
259
+ *args: Any,
260
+ **kwargs: Any,
261
+ ) -> str:
262
+ """
263
+ Get the default redirect URI for the client.
264
+
265
+ Arguments:
266
+
267
+ client_id: Unicode client identifier
268
+ request: The HTTP Request
269
+
270
+ Returns: The default redirect URI for the client
271
+
272
+ Method is used by:
273
+ - Authorization Code Grant
274
+ - Implicit Grant
275
+ """
276
+ del request, args, kwargs
277
+
278
+ LOG.debug("get_default_redirect_uri %s", client_id)
279
+
280
+ raise NotImplementedError("Not implemented.")
281
+
282
+ def get_default_scopes( # pylint: disable=no-self-use,useless-suppression
283
+ self,
284
+ client_id: str,
285
+ request: oauthlib.common.Request,
286
+ *args: Any,
287
+ **kwargs: Any,
288
+ ) -> List[str]:
289
+ """
290
+ Get the default scopes for the client.
291
+
292
+ Arguments:
293
+
294
+ client_id: Unicode client identifier
295
+ request: The HTTP Request
296
+
297
+ Returns: List of default scopes
298
+
299
+ Method is used by all core grant types:
300
+ - Authorization Code Grant
301
+ - Implicit Grant
302
+ - Resource Owner Password Credentials Grant
303
+ - Client Credentials grant
304
+ """
305
+ del request, args, kwargs
306
+
307
+ LOG.debug("get_default_scopes %s", client_id)
308
+
309
+ return ["geomapfish"]
310
+
311
+ def get_original_scopes( # pylint: disable=no-self-use,useless-suppression
312
+ self,
313
+ refresh_token: str,
314
+ request: oauthlib.common.Request,
315
+ *args: Any,
316
+ **kwargs: Any,
317
+ ) -> List[str]:
318
+ """
319
+ Get the list of scopes associated with the refresh token.
320
+
321
+ Arguments:
322
+
323
+ refresh_token: Unicode refresh token
324
+ request: The HTTP Request
325
+
326
+ Returns: List of scopes.
327
+
328
+ Method is used by:
329
+ - Refresh token grant
330
+ """
331
+ del refresh_token, request, args, kwargs
332
+
333
+ LOG.debug("get_original_scopes")
334
+
335
+ return []
336
+
337
+ def introspect_token( # pylint: disable=no-self-use,useless-suppression
338
+ self,
339
+ token: str,
340
+ token_type_hint: str,
341
+ request: oauthlib.common.Request,
342
+ *args: Any,
343
+ **kwargs: Any,
344
+ ) -> None:
345
+ """
346
+ Introspect an access or refresh token.
347
+
348
+ Called once the introspect request is validated. This method
349
+ should verify the *token* and either return a dictionary with the list of claims associated, or `None`
350
+ in case the token is unknown. Below the list of registered claims you should be interested in:
351
+
352
+ - scope : space-separated list of scopes
353
+ - client_id : client identifier
354
+ - username : human-readable identifier for the resource owner
355
+ - token_type : type of the token
356
+ - exp : integer timestamp indicating when this token will expire
357
+ - iat : integer timestamp indicating when this token was issued
358
+ - nbf : integer timestamp indicating when it can be "not-before" used
359
+ - sub : subject of the token - identifier of the resource owner
360
+ - aud : list of string identifiers representing the intended audience
361
+ - iss : string representing issuer of this token
362
+ - jti : string identifier for the token
363
+ Note that most of them are coming directly from JWT RFC. More details
364
+ can be found in `Introspect Claims`_ or `_JWT Claims`_.
365
+ The implementation can use *token_type_hint* to improve lookup
366
+ efficiency, but must fallback to other types to be compliant with RFC.
367
+ The dict of claims is added to request.token after this method.
368
+
369
+ Arguments:
370
+
371
+ token: The token string.
372
+ token_type_hint: access_token or refresh_token.
373
+ request: OAuthlib request.
374
+
375
+ Method is used by:
376
+ - Introspect Endpoint (all grants are compatible)
377
+
378
+ .. _`Introspect Claims`: https://tools.ietf.org/html/rfc7662#section-2.2
379
+ .. _`JWT Claims`: https://tools.ietf.org/html/rfc7519#section-4
380
+ """
381
+ del token, request, args, kwargs
382
+
383
+ LOG.debug("introspect_token %s", token_type_hint)
384
+
385
+ raise NotImplementedError("Not implemented.")
386
+
387
+ def invalidate_authorization_code( # pylint: disable=no-self-use,useless-suppression
388
+ self,
389
+ client_id: str,
390
+ code: str,
391
+ request: oauthlib.common.Request,
392
+ *args: Any,
393
+ **kwargs: Any,
394
+ ) -> None:
395
+ """
396
+ Invalidate an authorization code after use.
397
+
398
+ Arguments:
399
+
400
+ client_id: Unicode client identifier
401
+ code: The authorization code grant (request.code).
402
+ request: The HTTP Request
403
+
404
+ Method is used by:
405
+ - Authorization Code Grant
406
+ """
407
+ del args, kwargs
408
+
409
+ LOG.debug("invalidate_authorization_code %s", client_id)
410
+
411
+ from c2cgeoportal_commons.models import DBSession, static # pylint: disable=import-outside-toplevel
412
+
413
+ DBSession.delete(
414
+ DBSession.query(static.OAuth2AuthorizationCode)
415
+ .join(static.OAuth2AuthorizationCode.client)
416
+ .filter(static.OAuth2AuthorizationCode.code == code)
417
+ .filter(static.OAuth2Client.client_id == client_id)
418
+ .filter(static.OAuth2AuthorizationCode.user_id == request.user.id)
419
+ .one()
420
+ )
421
+
422
+ def is_within_original_scope( # pylint: disable=no-self-use,useless-suppression
423
+ self,
424
+ request_scopes: List[str],
425
+ refresh_token: str,
426
+ request: oauthlib.common.Request,
427
+ *args: Any,
428
+ **kwargs: Any,
429
+ ) -> bool:
430
+ """
431
+ Check if requested scopes are within a scope of the refresh token.
432
+
433
+ When access tokens are refreshed the scope of the new token
434
+ needs to be within the scope of the original token. This is
435
+ ensured by checking that all requested scopes strings are on
436
+ the list returned by the get_original_scopes. If this check
437
+ fails, is_within_original_scope is called. The method can be
438
+ used in situations where returning all valid scopes from the
439
+ get_original_scopes is not practical.
440
+
441
+ Arguments:
442
+
443
+ request_scopes: A list of scopes that were requested by client
444
+ refresh_token: Unicode refresh_token
445
+ request: The HTTP Request
446
+
447
+ Method is used by:
448
+ - Refresh token grant
449
+ """
450
+ del request, args, kwargs
451
+
452
+ LOG.debug("is_within_original_scope %s %s", request_scopes, refresh_token)
453
+
454
+ return False
455
+
456
+ def revoke_token( # pylint: disable=no-self-use,useless-suppression
457
+ self,
458
+ token: str,
459
+ token_type_hint: str,
460
+ request: oauthlib.common.Request,
461
+ *args: Any,
462
+ **kwargs: Any,
463
+ ) -> None:
464
+ """
465
+ Revoke an access or refresh token.
466
+
467
+ Arguments:
468
+
469
+ token: The token string.
470
+ token_type_hint: access_token or refresh_token.
471
+ request: The HTTP Request
472
+
473
+ Method is used by:
474
+ - Revocation Endpoint
475
+ """
476
+ del token, request, args, kwargs
477
+
478
+ LOG.debug("revoke_token %s", token_type_hint)
479
+
480
+ raise NotImplementedError("Not implemented.")
481
+
482
+ def rotate_refresh_token( # pylint: disable=no-self-use,useless-suppression
483
+ self, request: oauthlib.common.Request
484
+ ) -> bool:
485
+ """
486
+ Determine whether to rotate the refresh token. Default, yes.
487
+
488
+ When access tokens are refreshed the old refresh token can be kept
489
+ or replaced with a new one (rotated). Return True to rotate and
490
+ and False for keeping original.
491
+
492
+ Arguments:
493
+
494
+ request: oauthlib.common.Request
495
+
496
+ Method is used by:
497
+ - Refresh Token Grant
498
+ """
499
+ del request
500
+
501
+ LOG.debug("rotate_refresh_token")
502
+
503
+ return True
504
+
505
+ def save_authorization_code( # pylint: disable=no-self-use,useless-suppression
506
+ self,
507
+ client_id: str,
508
+ code: Dict[str, str],
509
+ request: oauthlib.common.Request,
510
+ *args: Any,
511
+ **kwargs: Any,
512
+ ) -> None:
513
+ """
514
+ Persist the authorization_code.
515
+
516
+ The code should at minimum be stored with:
517
+ - the client_id (client_id)
518
+ - the redirect URI used (request.redirect_uri)
519
+ - a resource owner / user (request.user)
520
+ - the authorized scopes (request.scopes)
521
+ - the client state, if given (code.get('state'))
522
+
523
+ The 'code' argument is actually a dictionary, containing at least a
524
+ 'code' key with the actual authorization code:
525
+
526
+ {'code': 'sdf345jsdf0934f'}
527
+
528
+ It may also have a 'state' key containing a nonce for the client, if it
529
+ chose to send one. That value should be saved and used in
530
+ 'validate_code'.
531
+
532
+ Arguments:
533
+
534
+ client_id: Unicode client identifier
535
+ code: A dict of the authorization code grant and, optionally, state.
536
+ request: The HTTP Request
537
+
538
+ Method is used by:
539
+ - Authorization Code Grant
540
+ """
541
+ del args, kwargs
542
+
543
+ LOG.debug("save_authorization_code %s", client_id)
544
+
545
+ from c2cgeoportal_commons.models import DBSession, static # pylint: disable=import-outside-toplevel
546
+
547
+ user = pyramid.threadlocal.get_current_request().user_
548
+
549
+ # Don't allows to have two authentications for the same user and the same client
550
+ authorization_code = (
551
+ DBSession.query(static.OAuth2AuthorizationCode)
552
+ .filter(static.OAuth2AuthorizationCode.client_id == request.client.id)
553
+ .filter(static.OAuth2AuthorizationCode.user_id == user.id)
554
+ .one_or_none()
555
+ )
556
+
557
+ if authorization_code is not None:
558
+ authorization_code.code = code["code"]
559
+ authorization_code.expire_at = datetime.now() + timedelta(minutes=self.authorization_expires_in)
560
+ authorization_code.redirect_uri = request.redirect_uri
561
+ else:
562
+ authorization_code = static.OAuth2AuthorizationCode()
563
+ authorization_code.client_id = request.client.id
564
+ authorization_code.code = code["code"]
565
+ authorization_code.user_id = user.id
566
+ authorization_code.expire_at = datetime.now() + timedelta(minutes=self.authorization_expires_in)
567
+ authorization_code.redirect_uri = request.redirect_uri
568
+
569
+ DBSession.add(authorization_code)
570
+
571
+ def save_bearer_token( # pylint: disable=no-self-use,useless-suppression
572
+ self,
573
+ token: Dict[str, Union[str, int]],
574
+ request: oauthlib.common.Request,
575
+ *args: Any,
576
+ **kwargs: Any,
577
+ ) -> None:
578
+ """
579
+ Persist the Bearer token.
580
+
581
+ The Bearer token should at minimum be associated with:
582
+ - a client and it's client_id, if available
583
+ - a resource owner / user (request.user)
584
+ - authorized scopes (request.scopes)
585
+ - an expiration time
586
+ - a refresh token, if issued
587
+
588
+ The Bearer token dict may hold a number of items::
589
+
590
+ {
591
+ 'token_type': 'Bearer',
592
+ 'access_token': 'askfjh234as9sd8',
593
+ 'expires_in': 3600,
594
+ 'scope': 'string of space separated authorized scopes',
595
+ 'refresh_token': '23sdf876234', # if issued
596
+ 'state': 'given_by_client', # if supplied by client
597
+ }
598
+
599
+ Note that while "scope" is a string-separated list of authorized scopes,
600
+ the original list is still available in request.scopes
601
+
602
+ Arguments:
603
+
604
+ client_id: Unicode client identifier
605
+ token: A Bearer token dict
606
+ request: The HTTP Request
607
+
608
+ Returns: The default redirect URI for the client
609
+
610
+ Method is used by all core grant types issuing Bearer tokens:
611
+ - Authorization Code Grant
612
+ - Implicit Grant
613
+ - Resource Owner Password Credentials Grant (might not associate a client)
614
+ - Client Credentials grant
615
+ """
616
+ del args, kwargs
617
+
618
+ LOG.debug("save_bearer_token")
619
+
620
+ from c2cgeoportal_commons.models import DBSession, static # pylint: disable=import-outside-toplevel
621
+
622
+ # Don't allows to have tow token for one user end one client
623
+ bearer_token = (
624
+ DBSession.query(static.OAuth2BearerToken)
625
+ .filter(static.OAuth2BearerToken.client_id == request.client.id)
626
+ .filter(static.OAuth2BearerToken.user_id == request.user.id)
627
+ .one_or_none()
628
+ )
629
+
630
+ if bearer_token is not None:
631
+ bearer_token.access_token = token["access_token"]
632
+ bearer_token.refresh_token = token["refresh_token"]
633
+ bearer_token.expire_at = datetime.now() + timedelta(seconds=float(token["expires_in"]))
634
+ else:
635
+ bearer_token = static.OAuth2BearerToken()
636
+ bearer_token.client_id = request.client.id
637
+ bearer_token.user_id = request.user.id
638
+ bearer_token.access_token = token["access_token"]
639
+ bearer_token.refresh_token = token["refresh_token"]
640
+ bearer_token.expire_at = datetime.now() + timedelta(seconds=float(token["expires_in"]))
641
+
642
+ DBSession.add(bearer_token)
643
+
644
+ def validate_bearer_token( # pylint: disable=no-self-use,useless-suppression
645
+ self,
646
+ token: str,
647
+ scopes: List[str],
648
+ request: oauthlib.common.Request,
649
+ ) -> bool:
650
+ """
651
+ Ensure the Bearer token is valid and authorized access to scopes.
652
+
653
+ Arguments:
654
+
655
+ token: A string of random characters.
656
+ scopes: A list of scopes associated with the protected resource.
657
+ request: The HTTP Request
658
+
659
+ A key to OAuth 2 security and restricting impact of leaked tokens is
660
+ the short expiration time of tokens, *always ensure the token has not
661
+ expired!*.
662
+
663
+ Two different approaches to scope validation:
664
+
665
+ 1) all(scopes). The token must be authorized access to all scopes
666
+ associated with the resource. For example, the
667
+ token has access to ``read-only`` and ``images``,
668
+ thus the client can view images but not upload new.
669
+ Allows for fine grained access control through
670
+ combining various scopes.
671
+
672
+ 2) any(scopes). The token must be authorized access to one of the
673
+ scopes associated with the resource. For example,
674
+ token has access to ``read-only-images``.
675
+ Allows for fine grained, although arguably less
676
+ convenient, access control.
677
+
678
+ A powerful way to use scopes would mimic UNIX ACLs and see a scope
679
+ as a group with certain privileges. For a restful API these might
680
+ map to HTTP verbs instead of read, write and execute.
681
+
682
+ Note, the request.user attribute can be set to the resource owner
683
+ associated with this token. Similarly the request.client and
684
+ request.scopes attribute can be set to associated client object
685
+ and authorized scopes. If you then use a decorator such as the
686
+ one provided for django these attributes will be made available
687
+ in all protected views as keyword arguments.
688
+
689
+ Arguments:
690
+
691
+ token: Unicode Bearer token
692
+ scopes: List of scopes (defined by you)
693
+ request: The HTTP Request
694
+
695
+ Method is indirectly used by all core Bearer token issuing grant types:
696
+ - Authorization Code Grant
697
+ - Implicit Grant
698
+ - Resource Owner Password Credentials Grant
699
+ - Client Credentials Grant
700
+ """
701
+
702
+ LOG.debug("validate_bearer_token %s", scopes)
703
+
704
+ from c2cgeoportal_commons.models import DBSession, static # pylint: disable=import-outside-toplevel
705
+
706
+ bearer_token = (
707
+ DBSession.query(static.OAuth2BearerToken)
708
+ .filter(static.OAuth2BearerToken.access_token == token)
709
+ .filter(static.OAuth2BearerToken.expire_at > datetime.now())
710
+ ).one_or_none()
711
+
712
+ if bearer_token is not None:
713
+ request.user = bearer_token.user
714
+
715
+ LOG.debug("validate_bearer_token => %s", bearer_token is not None)
716
+ return bearer_token is not None
717
+
718
+ def validate_client_id( # pylint: disable=no-self-use,useless-suppression
719
+ self,
720
+ client_id: str,
721
+ request: oauthlib.common.Request,
722
+ *args: Any,
723
+ **kwargs: Any,
724
+ ) -> bool:
725
+ """
726
+ Ensure client_id belong to a valid and active client.
727
+
728
+ Note, while not strictly necessary it can often be very convenient
729
+ to set request.client to the client object associated with the
730
+ given client_id.
731
+
732
+ Arguments:
733
+
734
+ client_id: Unicode client identifier
735
+ request: oauthlib.common.Request
736
+
737
+ Method is used by:
738
+ - Authorization Code Grant
739
+ - Implicit Grant
740
+ """
741
+ del args, kwargs
742
+
743
+ LOG.debug("validate_client_id")
744
+
745
+ from c2cgeoportal_commons.models import DBSession, static # pylint: disable=import-outside-toplevel
746
+
747
+ client = (
748
+ DBSession.query(static.OAuth2Client)
749
+ .filter(static.OAuth2Client.client_id == client_id)
750
+ .one_or_none()
751
+ )
752
+ if client is not None:
753
+ request.client = client
754
+ return client is not None
755
+
756
+ def validate_code( # pylint: disable=no-self-use,useless-suppression
757
+ self,
758
+ client_id: str,
759
+ code: str,
760
+ client: "c2cgeoportal_commons.models.static.OAuth2Client", # noqa: F821
761
+ request: oauthlib.common.Request,
762
+ *args: Any,
763
+ **kwargs: Any,
764
+ ) -> bool:
765
+ """
766
+ Verify that the authorization_code is valid and assigned to the given client.
767
+
768
+ Before returning true, set the following based on the information stored
769
+ with the code in 'save_authorization_code':
770
+
771
+ - request.user
772
+ - request.state (if given)
773
+ - request.scopes
774
+ OBS! The request.user attribute should be set to the resource owner
775
+ associated with this authorization code. Similarly request.scopes
776
+ must also be set.
777
+
778
+ Arguments:
779
+
780
+ client_id: Unicode client identifier
781
+ code: Unicode authorization code
782
+ client: Client object set by you, see authenticate_client.
783
+ request: The HTTP Request
784
+
785
+ Method is used by:
786
+ - Authorization Code Grant
787
+ """
788
+ del args, kwargs
789
+
790
+ LOG.debug("validate_code %s", client_id)
791
+
792
+ from c2cgeoportal_commons.models import DBSession, static # pylint: disable=import-outside-toplevel
793
+
794
+ authorization_code = (
795
+ DBSession.query(static.OAuth2AuthorizationCode)
796
+ .join(static.OAuth2AuthorizationCode.client)
797
+ .filter(static.OAuth2AuthorizationCode.code == code)
798
+ .filter(static.OAuth2Client.client_id == client_id)
799
+ .filter(static.OAuth2AuthorizationCode.expire_at > datetime.now())
800
+ .one_or_none()
801
+ )
802
+ if authorization_code is not None:
803
+ request.user = authorization_code.user
804
+ LOG.debug("validate_code => %s", authorization_code is not None)
805
+ return authorization_code is not None
806
+
807
+ def validate_grant_type( # pylint: disable=no-self-use,useless-suppression
808
+ self,
809
+ client_id: str,
810
+ grant_type: str,
811
+ client: "c2cgeoportal_commons.models.static.OAuth2Client",
812
+ request: oauthlib.common.Request,
813
+ *args: Any,
814
+ **kwargs: Any,
815
+ ) -> bool:
816
+ """
817
+ Ensure client is authorized to use the grant_type requested.
818
+
819
+ Arguments:
820
+
821
+ client_id: Unicode client identifier
822
+ grant_type: Unicode grant type, i.e. authorization_code, password.
823
+ client: Client object set by you, see authenticate_client.
824
+ request: The HTTP Request
825
+
826
+ Method is used by:
827
+ - Authorization Code Grant
828
+ - Resource Owner Password Credentials Grant
829
+ - Client Credentials Grant
830
+ - Refresh Token Grant
831
+ """
832
+ del client, request, args, kwargs
833
+
834
+ LOG.debug(
835
+ "validate_grant_type %s %s => %s",
836
+ client_id,
837
+ grant_type,
838
+ grant_type in ("authorization_code", "refresh_token"),
839
+ )
840
+
841
+ return grant_type in ("authorization_code", "refresh_token")
842
+
843
+ def validate_redirect_uri( # pylint: disable=no-self-use,useless-suppression
844
+ self,
845
+ client_id: str,
846
+ redirect_uri: str,
847
+ request: oauthlib.common.Request,
848
+ *args: Any,
849
+ **kwargs: Any,
850
+ ) -> bool:
851
+ """
852
+ Ensure client is authorized to redirect to the redirect_uri requested.
853
+
854
+ All clients should register the absolute URIs of all URIs they intend
855
+ to redirect to. The registration is outside of the scope of oauthlib.
856
+
857
+ Arguments:
858
+
859
+ client_id: Unicode client identifier
860
+ redirect_uri: Unicode absolute URI
861
+ request: The HTTP Request
862
+
863
+ Method is used by:
864
+ - Authorization Code Grant
865
+ - Implicit Grant
866
+ """
867
+ del request, args, kwargs
868
+
869
+ LOG.debug("validate_redirect_uri %s %s", client_id, redirect_uri)
870
+
871
+ from c2cgeoportal_commons.models import DBSession, static # pylint: disable=import-outside-toplevel
872
+
873
+ client = (
874
+ DBSession.query(static.OAuth2Client)
875
+ .filter(static.OAuth2Client.client_id == client_id)
876
+ .filter(static.OAuth2Client.redirect_uri == redirect_uri)
877
+ .one_or_none()
878
+ )
879
+ LOG.debug("validate_redirect_uri %s", client is not None)
880
+ return client is not None
881
+
882
+ def validate_refresh_token( # pylint: disable=no-self-use,useless-suppression
883
+ self,
884
+ refresh_token: str,
885
+ client: "c2cgeoportal_commons.models.static.OAuth2Client", # noqa: F821
886
+ request: oauthlib.common.Request,
887
+ *args: Any,
888
+ **kwargs: Any,
889
+ ) -> bool:
890
+ """
891
+ Ensure the Bearer token is valid and authorized access to scopes.
892
+
893
+ OBS! The request.user attribute should be set to the resource owner
894
+ associated with this refresh token.
895
+
896
+ Arguments:
897
+
898
+ refresh_token: Unicode refresh token
899
+ client: Client object set by you, see authenticate_client.
900
+ request: The HTTP Request
901
+
902
+ Method is used by:
903
+ - Authorization Code Grant (indirectly by issuing refresh tokens)
904
+ - Resource Owner Password Credentials Grant (also indirectly)
905
+ - Refresh Token Grant
906
+ """
907
+ del args, kwargs
908
+
909
+ LOG.debug("validate_refresh_token %s", client.client_id if client else None)
910
+
911
+ from c2cgeoportal_commons.models import DBSession, static # pylint: disable=import-outside-toplevel
912
+
913
+ bearer_token = (
914
+ DBSession.query(static.OAuth2BearerToken)
915
+ .filter(static.OAuth2BearerToken.refresh_token == refresh_token)
916
+ .filter(static.OAuth2BearerToken.client_id == request.client.id)
917
+ .one_or_none()
918
+ )
919
+
920
+ if bearer_token is not None:
921
+ request.user = bearer_token.user
922
+
923
+ return bearer_token is not None
924
+
925
+ def validate_response_type( # pylint: disable=no-self-use,useless-suppression
926
+ self,
927
+ client_id: str,
928
+ response_type: str,
929
+ client: "c2cgeoportal_commons.models.static.OAuth2Client",
930
+ request: oauthlib.common.Request,
931
+ *args: Any,
932
+ **kwargs: Any,
933
+ ) -> bool:
934
+ """
935
+ Ensure client is authorized to use the response_type requested.
936
+
937
+ Arguments:
938
+
939
+ client_id: Unicode client identifier
940
+ response_type: Unicode response type, i.e. code, token.
941
+ client: Client object set by you, see authenticate_client.
942
+ request: The HTTP Request
943
+
944
+ Method is used by:
945
+ - Authorization Code Grant
946
+ - Implicit Grant
947
+ """
948
+ del client, request, args, kwargs
949
+
950
+ LOG.debug("validate_response_type %s %s", client_id, response_type)
951
+
952
+ return response_type == "code"
953
+
954
+ def validate_scopes( # pylint: disable=no-self-use,useless-suppression
955
+ self,
956
+ client_id: str,
957
+ scopes: List[str],
958
+ client: "c2cgeoportal_commons.models.static.OAuth2Client",
959
+ request: oauthlib.common.Request,
960
+ *args: Any,
961
+ **kwargs: Any,
962
+ ) -> bool:
963
+ """
964
+ Ensure the client is authorized access to requested scopes.
965
+
966
+ Arguments:
967
+
968
+ client_id: Unicode client identifier
969
+ scopes: List of scopes (defined by you)
970
+ client: Client object set by you, see authenticate_client.
971
+ request: The HTTP Request
972
+
973
+ Method is used by all core grant types:
974
+ - Authorization Code Grant
975
+ - Implicit Grant
976
+ - Resource Owner Password Credentials Grant
977
+ - Client Credentials Grant
978
+ """
979
+ del client, request, args, kwargs
980
+
981
+ LOG.debug("validate_scopes %s %s", client_id, scopes)
982
+
983
+ return True
984
+
985
+ def validate_user( # pylint: disable=no-self-use,useless-suppression
986
+ self,
987
+ username: str,
988
+ password: str,
989
+ client: "c2cgeoportal_commons.models.static.OAuth2Client", # noqa: F821
990
+ request: oauthlib.common.Request,
991
+ *args: Any,
992
+ **kwargs: Any,
993
+ ) -> bool:
994
+ """
995
+ Ensure the username and password is valid.
996
+
997
+ OBS! The validation should also set the user attribute of the request
998
+ to a valid resource owner, i.e. request.user = username or similar. If
999
+ not set you will be unable to associate a token with a user in the
1000
+ persistence method used (commonly, save_bearer_token).
1001
+
1002
+ Arguments:
1003
+
1004
+ username: Unicode username
1005
+ password: Unicode password
1006
+ client: Client object set by you, see authenticate_client.
1007
+ request: The HTTP Request
1008
+
1009
+ Method is used by:
1010
+ - Resource Owner Password Credentials Grant
1011
+ """
1012
+ del password, client, request, args, kwargs
1013
+
1014
+ LOG.debug("validate_user %s", username)
1015
+
1016
+ raise NotImplementedError("Not implemented.")
1017
+
1018
+
1019
+ def get_oauth_client(settings: Dict[str, Any]) -> oauthlib.oauth2.WebApplicationServer:
1020
+ """Get the oauth2 client, with a cache."""
1021
+ authentication_settings = settings.get("authentication", {})
1022
+ return _get_oauth_client_cache(
1023
+ authentication_settings.get("oauth2_authorization_expire_minutes", 10),
1024
+ authentication_settings.get("oauth2_token_expire_minutes", 60),
1025
+ )
1026
+
1027
+
1028
+ @OBJECT_CACHE_REGION.cache_on_arguments() # type: ignore
1029
+ def _get_oauth_client_cache(
1030
+ authorization_expire_minutes: int, token_expire_minutes: int
1031
+ ) -> oauthlib.oauth2.WebApplicationServer:
1032
+ """Get the oauth2 client, with a cache."""
1033
+ return oauthlib.oauth2.WebApplicationServer(
1034
+ RequestValidator(authorization_expires_in=authorization_expire_minutes),
1035
+ token_expires_in=token_expire_minutes * 60,
1036
+ )