rdiffweb 7.2.0b1__tar.gz → 7.2.0b2__tar.gz

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 (259) hide show
  1. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/CHANGELOG.md +1 -1
  2. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/PKG-INFO +1 -1
  3. rdiffweb-7.2.0b2/SECURITY.md +33 -0
  4. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_controller.py +5 -0
  5. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_history.py +32 -0
  6. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_login.py +1 -0
  7. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/librdiff.py +13 -3
  8. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/model/_message.py +85 -20
  9. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/model/_user.py +32 -13
  10. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/notification.py +10 -17
  11. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/admin_users.html +1 -1
  12. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb.egg-info/PKG-INFO +1 -1
  13. rdiffweb-7.2.0b2/rdiffweb.egg-info/scm_version.json +8 -0
  14. rdiffweb-7.2.0b1/SECURITY.md +0 -7
  15. rdiffweb-7.2.0b1/rdiffweb.egg-info/scm_version.json +0 -8
  16. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/FUNDING.yml +0 -0
  17. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/LICENSE +0 -0
  18. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/MANIFEST.in +0 -0
  19. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/README.md +0 -0
  20. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/babel.cfg +0 -0
  21. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/pyproject.toml +0 -0
  22. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/__init__.py +0 -0
  23. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/Breadcrumb.jinja +0 -0
  24. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/ButtonConfirm.jinja +0 -0
  25. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/Heatmap.css +0 -0
  26. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/Heatmap.jinja +0 -0
  27. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/HeatmapLegend.jinja +0 -0
  28. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/HeatmapYear.jinja +0 -0
  29. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/KpiCard.css +0 -0
  30. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/KpiCard.jinja +0 -0
  31. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/MfaCurrentBadgeWidget.jinja +0 -0
  32. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/ModalConfirm.jinja +0 -0
  33. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/ModalConfirm.js +0 -0
  34. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/ModalDialog.jinja +0 -0
  35. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/ModalDialog.js +0 -0
  36. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/NavItem.jinja +0 -0
  37. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/NavItemRepo.jinja +0 -0
  38. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/RdwChart.jinja +0 -0
  39. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/RdwChart.js +0 -0
  40. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/RdwDateSelector.css +0 -0
  41. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/RdwDateSelector.jinja +0 -0
  42. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/RdwDateSelector.js +0 -0
  43. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/RdwEmpty.jinja +0 -0
  44. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/RdwFooter.jinja +0 -0
  45. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/RdwIcon.css +0 -0
  46. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/RdwIcon.jinja +0 -0
  47. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/RdwLog.css +0 -0
  48. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/RdwLog.jinja +0 -0
  49. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/RdwMessages.jinja +0 -0
  50. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/RdwModal.jinja +0 -0
  51. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/RdwTable.css +0 -0
  52. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/RdwTable.jinja +0 -0
  53. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/RdwTable.js +0 -0
  54. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/RdwTime.jinja +0 -0
  55. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/RepoCard.jinja +0 -0
  56. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/RepoStatus.jinja +0 -0
  57. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/RepoWarning.css +0 -0
  58. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/RepoWarning.jinja +0 -0
  59. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/SelectMultipleWidget.css +0 -0
  60. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/SelectMultipleWidget.jinja +0 -0
  61. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/StorageUsage.jinja +0 -0
  62. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/__init__.py +0 -0
  63. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/api.py +0 -0
  64. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/api_currentuser.py +0 -0
  65. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/api_openapi.py +0 -0
  66. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/dispatch.py +0 -0
  67. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/filter_authorization.py +0 -0
  68. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/formdb.py +0 -0
  69. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_admin.py +0 -0
  70. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_admin_activity.py +0 -0
  71. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_admin_logs.py +0 -0
  72. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_admin_repos.py +0 -0
  73. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_admin_session.py +0 -0
  74. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_admin_sysinfo.py +0 -0
  75. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_admin_users.py +0 -0
  76. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_browse.py +0 -0
  77. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_delete.py +0 -0
  78. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_graphs.py +0 -0
  79. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_history.py +0 -0
  80. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_home.py +0 -0
  81. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_login.py +0 -0
  82. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_logs.py +0 -0
  83. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_mfa.py +0 -0
  84. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_pref_general.py +0 -0
  85. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_pref_mfa.py +0 -0
  86. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_pref_notification.py +0 -0
  87. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_pref_session.py +0 -0
  88. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_pref_sshkeys.py +0 -0
  89. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_pref_tokens.py +0 -0
  90. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_prefs.py +0 -0
  91. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_repo_activity.py +0 -0
  92. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_restore.py +0 -0
  93. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_settings.py +0 -0
  94. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_stats.py +0 -0
  95. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/static.py +0 -0
  96. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/__init__.py +0 -0
  97. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_api.py +0 -0
  98. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_check_links.py +0 -0
  99. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_openapi.py +0 -0
  100. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_admin.py +0 -0
  101. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_admin_activity.py +0 -0
  102. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_admin_logs.py +0 -0
  103. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_admin_repos.py +0 -0
  104. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_admin_session.py +0 -0
  105. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_admin_sysinfo.py +0 -0
  106. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_admin_users.py +0 -0
  107. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_browse.py +0 -0
  108. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_delete.py +0 -0
  109. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_error.py +0 -0
  110. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_graphs.py +0 -0
  111. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_home.py +0 -0
  112. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_logs.py +0 -0
  113. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_mfa.py +0 -0
  114. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_prefs_general.py +0 -0
  115. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_prefs_mfa.py +0 -0
  116. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_prefs_notification.py +0 -0
  117. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_prefs_session.py +0 -0
  118. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_prefs_ssh.py +0 -0
  119. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_prefs_tokens.py +0 -0
  120. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_repo_activity.py +0 -0
  121. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_restore.py +0 -0
  122. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_settings.py +0 -0
  123. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_stats.py +0 -0
  124. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_secure_headers.py +0 -0
  125. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/widgets.py +0 -0
  126. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/__init__.py +0 -0
  127. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/authorizedkeys.py +0 -0
  128. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/config.py +0 -0
  129. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/dircache.py +0 -0
  130. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/diskusage.py +0 -0
  131. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/model/__init__.py +0 -0
  132. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/model/_callbacks.py +0 -0
  133. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/model/_diskusage.py +0 -0
  134. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/model/_repo.py +0 -0
  135. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/model/_session.py +0 -0
  136. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/model/_sshkey.py +0 -0
  137. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/model/_timestamp.py +0 -0
  138. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/model/_token.py +0 -0
  139. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/model/_update.py +0 -0
  140. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/model/tests/__init__.py +0 -0
  141. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/model/tests/test_db_after_create.py +0 -0
  142. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/model/tests/test_repo.py +0 -0
  143. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/model/tests/test_token.py +0 -0
  144. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/model/tests/test_user.py +0 -0
  145. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/quota.py +0 -0
  146. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/remove_older.py +0 -0
  147. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/restore.py +0 -0
  148. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/tests/__init__.py +0 -0
  149. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/tests/rdiff-backup-data/error_log.2015-11-19T07:27:46-05:00.data +0 -0
  150. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/tests/rdiff-backup-data/error_log.2015-11-20T07:27:46-05:00.data.gz +0 -0
  151. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/tests/rdiff-backup-data/error_log.2019-05-22T09:19:09-04:00.data.gz +0 -0
  152. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/tests/rdiff-backup-data/file_statistics.2014-11-05T16:05:07-05:00.data +0 -0
  153. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/tests/rdiff-backup-data/file_statistics.2014-11-05T16:05:07-05:00.data.gz +0 -0
  154. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/tests/rdiff-backup-data/session_statistics.2014-11-02T09:16:43-05:00.data +0 -0
  155. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/tests/session_statistics.2014-11-02T09:16:43-05:00.data +0 -0
  156. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/tests/test_authorized_keys +0 -0
  157. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/tests/test_authorizedkeys.py +0 -0
  158. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/tests/test_config.py +0 -0
  159. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/tests/test_diskusage.py +0 -0
  160. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/tests/test_librdiff.py +0 -0
  161. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/tests/test_login.py +0 -0
  162. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/tests/test_notification.py +0 -0
  163. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/tests/test_publickey_ssh_dsa.pub +0 -0
  164. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/tests/test_publickey_ssh_rsa.pub +0 -0
  165. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/tests/test_quota.py +0 -0
  166. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/tests/test_remove_older.py +0 -0
  167. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/tests/test_restore.py +0 -0
  168. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/locales/ca/LC_MESSAGES/messages.mo +0 -0
  169. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/locales/ca/LC_MESSAGES/messages.po +0 -0
  170. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/locales/de/LC_MESSAGES/messages.mo +0 -0
  171. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/locales/de/LC_MESSAGES/messages.po +0 -0
  172. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/locales/en/LC_MESSAGES/messages.mo +0 -0
  173. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/locales/en/LC_MESSAGES/messages.po +0 -0
  174. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/locales/es/LC_MESSAGES/messages.mo +0 -0
  175. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/locales/es/LC_MESSAGES/messages.po +0 -0
  176. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/locales/fr/LC_MESSAGES/messages.mo +0 -0
  177. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/locales/fr/LC_MESSAGES/messages.po +0 -0
  178. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/locales/messages.pot +0 -0
  179. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/locales/pt/LC_MESSAGES/messages.mo +0 -0
  180. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/locales/pt/LC_MESSAGES/messages.po +0 -0
  181. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/locales/ru/LC_MESSAGES/messages.mo +0 -0
  182. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/locales/ru/LC_MESSAGES/messages.po +0 -0
  183. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/main.py +0 -0
  184. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/plugins/__init__.py +0 -0
  185. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/plugins/restapi.py +0 -0
  186. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/rdw_app.py +0 -0
  187. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/static/favicon.ico +0 -0
  188. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/static/favicon.svg +0 -0
  189. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/static/header-logo.png +0 -0
  190. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/static/header-logo.svg +0 -0
  191. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/static/login-bg.png +0 -0
  192. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/static/main.js +0 -0
  193. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/static/robots.txt +0 -0
  194. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/admin_activity.html +0 -0
  195. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/admin_logs.html +0 -0
  196. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/admin_repos.html +0 -0
  197. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/admin_session.html +0 -0
  198. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/admin_sysinfo.html +0 -0
  199. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/admin_user_edit.html +0 -0
  200. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/browse.html +0 -0
  201. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/email_access_token_added.html +0 -0
  202. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/email_authorizedkey_added.html +0 -0
  203. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/email_changed.html +0 -0
  204. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/email_latest.html +0 -0
  205. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/email_layout.html +0 -0
  206. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/email_mfa.html +0 -0
  207. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/email_notification.html +0 -0
  208. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/email_password_changed.html +0 -0
  209. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/email_repo_added.html +0 -0
  210. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/email_repo_deleted.html +0 -0
  211. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/email_report.html +0 -0
  212. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/email_storage_usage.html +0 -0
  213. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/email_verification_code.html +0 -0
  214. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/error_page.html +0 -0
  215. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/graphs.html +0 -0
  216. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/history.html +0 -0
  217. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/home.html +0 -0
  218. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/include/email_kpi_cards.html +0 -0
  219. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/include/email_repo_card.html +0 -0
  220. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/include/email_storage_usage_card.html +0 -0
  221. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/include/session.html +0 -0
  222. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/include/timerange.html +0 -0
  223. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/layout.html +0 -0
  224. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/login.html +0 -0
  225. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/logs.html +0 -0
  226. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/main.css +0 -0
  227. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/mfa.html +0 -0
  228. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/prefs_general.html +0 -0
  229. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/prefs_mfa.html +0 -0
  230. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/prefs_notification.html +0 -0
  231. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/prefs_session.html +0 -0
  232. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/prefs_sshkeys.html +0 -0
  233. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/prefs_tokens.html +0 -0
  234. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/repo_activity.html +0 -0
  235. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/restore.html +0 -0
  236. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/settings.html +0 -0
  237. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/sidebar.html +0 -0
  238. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/stats.html +0 -0
  239. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/test.py +0 -0
  240. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/tests/__init__.py +0 -0
  241. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/tests/rdw.conf +0 -0
  242. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/tests/rdw.db +0 -0
  243. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/tests/test_app.py +0 -0
  244. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/tests/test_main.py +0 -0
  245. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/tests/testcases.tar.gz +0 -0
  246. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/tools/__init__.py +0 -0
  247. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/tools/enrich_session.py +0 -0
  248. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/tools/errors.py +0 -0
  249. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/tools/poppath.py +0 -0
  250. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/tools/required_scope.py +0 -0
  251. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb.egg-info/SOURCES.txt +0 -0
  252. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb.egg-info/dependency_links.txt +0 -0
  253. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb.egg-info/entry_points.txt +0 -0
  254. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb.egg-info/requires.txt +0 -0
  255. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb.egg-info/scm_file_list.json +0 -0
  256. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb.egg-info/top_level.txt +0 -0
  257. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdw.conf +0 -0
  258. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/setup.cfg +0 -0
  259. {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/tox.ini +0 -0
@@ -445,7 +445,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
445
445
  ### Fixed
446
446
 
447
447
  - Fix exception raised for invalid locale identifier ([17c6d10](https://gitlab.com/ikus-soft/rdiffweb/commit/17c6d10a5a50d46218649c708a030e6f43d6620e) by Patrik Dufresne).
448
- - Fix token impersonation vulnerability ([4ce35fc](https://gitlab.com/ikus-soft/rdiffweb/commit/4ce35fc2d23ac1cc789186f32bf76603b2eddeb3) by Patrik Dufresne).
448
+ - Fix token impersonation vulnerability CVE-2025-67796 ([4ce35fc](https://gitlab.com/ikus-soft/rdiffweb/commit/4ce35fc2d23ac1cc789186f32bf76603b2eddeb3) by Patrik Dufresne).
449
449
  - Fix selenium execution with bookworm ([1fd8db3](https://gitlab.com/ikus-soft/rdiffweb/commit/1fd8db32b9eb738168f5b1b126f020652674be9d) by Patrik Dufresne).
450
450
  - Fix doc upload #337 ([6d7219b](https://gitlab.com/ikus-soft/rdiffweb/commit/6d7219bd4348ad28e1704a4d708111751151fddc) by Patrik Dufresne).
451
451
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rdiffweb
3
- Version: 7.2.0b1
3
+ Version: 7.2.0b2
4
4
  Summary: A web interface to rdiff-backup repositories.
5
5
  Author-email: Patrik Dufresne <patrik@ikus-soft.com>
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -0,0 +1,33 @@
1
+ # Reporting a Vulnerability
2
+
3
+ If you discover a security vulnerability in Rdiffweb, please report it privately through GitLab:
4
+
5
+ 1. Open a **[New Confidential Issue](https://gitlab.com/ikus-soft/rdiffweb/-/issues/new?issue%5Bconfidential%5D=true)**.
6
+ 2. Make sure the **"This issue is confidential"** checkbox is checked.
7
+ 3. Include the affected version, description, and steps to reproduce (PoC).
8
+
9
+ Thank you for improving the security of Rdiffweb.
10
+
11
+ ## Non-qualifying reports
12
+
13
+ The following are generally **not** considered security vulnerabilities. Please do not report them as such;
14
+ open a regular (public) issue or merge request instead if you believe they are worth improving.
15
+
16
+ - **Content Security Policy (CSP) configuration**: Use of `unsafe-inline` or `unsafe-eval`, or any other
17
+ permissive or missing CSP directive, without a working exploit that does not depend on the CSP itself.
18
+ CSP is a defense-in-depth layer, not Rdiffweb's primary protection against XSS. Rdiffweb's web interface
19
+ currently relies on inline scripts and styles.
20
+ - **Weak TLS / SSL configuration**: TLS is expected to be terminated by the reverse proxy.
21
+ - **Clickjacking** without a demonstrated impact on a sensitive action.
22
+ - **Self-XSS**, or XSS requiring the victim to paste code into their own browser console.
23
+ - **XSS or content injection by administrators**: administrators are trusted by design.
24
+ - **Unsupported browsers**: issues that only affect outdated browsers or require relaxed browser security settings.
25
+ - **Vulnerabilities in third-party dependencies** without a demonstrated exploit path in Rdiffweb.
26
+ - **Findings from automated scanners** submitted without manual verification and a working proof of concept.
27
+ - **Misconfiguration of a deployment** (reverse proxy, OS, container) outside Rdiffweb's default configuration.
28
+ - **Code paths unreachable in a supported installation**: weaknesses that only occur when a declared
29
+ dependency is missing, removed, or deliberately bypassed (e.g. `--no-deps`), or in an environment that
30
+ Rdiffweb does not support. A report must demonstrate that the issue is reachable in a default,
31
+ supported installation.
32
+
33
+ Reports in these categories may be closed without a detailed response.
@@ -86,6 +86,11 @@ class ControllerTest(rdiffweb.test.WebCase):
86
86
  self.getPage('/static//../../test.js')
87
87
  self.assertStatus(403)
88
88
 
89
+ def test_dt_language(self):
90
+ for locale in ['en', 'fr', 'fr_CA']:
91
+ self.getPage(f'/static/dt-language/{locale}')
92
+ self.assertStatus(200)
93
+
89
94
 
90
95
  class ControllerExternalUrlTest(rdiffweb.test.WebCase):
91
96
  default_config = {'external_url': 'https://www.example.com'}
@@ -15,6 +15,7 @@
15
15
  # along with this program. If not, see <https://www.gnu.org/licenses/>.
16
16
 
17
17
  import os
18
+ from pathlib import Path
18
19
 
19
20
  import cherrypy
20
21
  from selenium.webdriver.common.action_chains import ActionChains
@@ -35,6 +36,37 @@ class HistoryPageTest(rdiffweb.test.WebCase):
35
36
  self.getPage(url)
36
37
  self.assertStatus('200 OK')
37
38
 
39
+ def test_history_path_traversal(self):
40
+ # Given a user with single repo
41
+ user = UserObject.get_user(self.USERNAME)
42
+ (Path(self.testcases) / 'broker-repo').rename(Path(self.testcases) / 'testcases-secret')
43
+ user.refresh_repos(delete=True)
44
+ user.commit()
45
+ self.assertEqual(['testcases', 'testcases-secret'], [r.name for r in user.repo_objs])
46
+ # When browsing /history/user/test/../testing
47
+ self.getPage('/history/admin/testcases/%2e%2e/testcases-secret')
48
+ # Then an error should be raised.
49
+ self.assertStatus(403)
50
+
51
+ def test_history_with_single_repo(self):
52
+ """
53
+ Verify if browsing '/history/' for a single repository is working.
54
+ """
55
+ # Change the user setting to match single repo.
56
+ user = UserObject.get_user(self.USERNAME)
57
+ user.user_root = os.path.join(self.testcases, 'testcases')
58
+ user.refresh_repos(delete=True)
59
+ user.commit()
60
+ self.assertEqual([''], [r.name for r in user.repo_objs])
61
+ # Check if browsing is working.
62
+ self.getPage('/history/admin')
63
+ self.assertStatus('200 OK')
64
+ self.assertInBody('Files')
65
+ # Check sub directory browsing
66
+ self.getPage('/history/admin/Revisions/')
67
+ self.assertStatus('200 OK')
68
+ self.assertInBody('Files')
69
+
38
70
  def test_history_with_root(self):
39
71
  self._history(self.USERNAME, self.REPO, limit=10)
40
72
  # Check revisions
@@ -90,6 +90,7 @@ class LoginPageTest(rdiffweb.test.WebCase):
90
90
  cherrypy.scheduler.wait_for_jobs()
91
91
  user = UserObject.get_user(self.USERNAME)
92
92
  self.assertEqual("User login to web application", user.messages[-1].body)
93
+ self.assertEqual(userobj.id, user.messages[-1].author_id)
93
94
 
94
95
  def test_login_case_insensitive(self):
95
96
  # When authenticating with valid credentials with all uppercase username
@@ -53,6 +53,16 @@ STDOUT_ENCODING = 'utf-8'
53
53
  LANG = "en_US." + STDOUT_ENCODING
54
54
 
55
55
 
56
+ def _is_child_of(child, parent):
57
+ """
58
+ Return True if the child is relative to parent path or False.
59
+ This method is string-based.
60
+ """
61
+ child = os.path.normpath(child)
62
+ parent = os.path.normpath(parent)
63
+ return child == parent or child.startswith(parent.rstrip(b'/') + b'/')
64
+
65
+
56
66
  def rdiff_backup_version():
57
67
  """
58
68
  Get rdiff-backup version
@@ -996,9 +1006,9 @@ class RdiffRepo(object):
996
1006
  """Return a new instance of DirEntry to represent the given path."""
997
1007
  # Compute increment directory location.
998
1008
  assert isinstance(path, bytes)
999
- full_path = os.path.normpath(os.path.join(self.full_path, path.strip(b'/')))
1000
- increment_path = os.path.normpath(os.path.join(self._increment_path, path.strip(b'/'), b'..'))
1001
- if not full_path.startswith(self.full_path) or not increment_path.startswith(self.full_path):
1009
+ full_path = os.path.join(self.full_path, path.strip(b'/'))
1010
+ increment_path = os.path.dirname(os.path.join(self._increment_path, path.strip(b'/')))
1011
+ if not _is_child_of(full_path, self.full_path) or not _is_child_of(increment_path, self.full_path):
1002
1012
  raise AccessDeniedError('%s make reference outside the repository' % self._decode(path))
1003
1013
  relative_path = os.path.relpath(full_path, self.full_path)
1004
1014
  if relative_path.startswith(RDIFF_BACKUP_DATA):
@@ -14,9 +14,12 @@
14
14
  # You should have received a copy of the GNU General Public License
15
15
  # along with this program. If not, see <https://www.gnu.org/licenses/>.
16
16
 
17
+ import contextvars
17
18
  import itertools
18
19
  import json
19
20
  import logging
21
+ from collections import namedtuple
22
+ from contextlib import contextmanager
20
23
 
21
24
  import cherrypy
22
25
  import cherrypy_foundation.plugins.db # noqa
@@ -64,6 +67,35 @@ class JSONString(TypeDecorator):
64
67
  return None
65
68
 
66
69
 
70
+ class _MessageContext(
71
+ namedtuple('_MessageContext', ['author', 'author_id', 'author_username', 'ip_address', 'user_agent'])
72
+ ):
73
+
74
+ def __new__(cls, author=None, author_id=None, author_username='', ip_address='', user_agent=''):
75
+ # Derive id/username from the author when one is provided.
76
+ if author is not None:
77
+ author_id = author.id
78
+ author_username = author.username
79
+ return super().__new__(cls, author, author_id, author_username, ip_address, user_agent)
80
+
81
+ def _replace(self, **kwds):
82
+ if 'author' in kwds and kwds['author'] is not None:
83
+ kwds.setdefault('author_id', kwds['author'].id)
84
+ kwds.setdefault('author_username', kwds['author'].username)
85
+ return super()._replace(**kwds)
86
+
87
+ @contextmanager
88
+ def use(self):
89
+ ctx = (
90
+ self._replace(author=None) if self.author and inspect(self.author).session != cherrypy.db.session else self
91
+ )
92
+ token = Message._context_var.set(ctx)
93
+ try:
94
+ yield self
95
+ finally:
96
+ Message._context_var.reset(token)
97
+
98
+
67
99
  def get_model_changes(model):
68
100
  """
69
101
  Return a dictionary containing changes made to the model since it was
@@ -142,12 +174,14 @@ class Message(Base):
142
174
  changes = Column('changes', JSONString, nullable=True)
143
175
  date = Column(Timestamp(timezone=True), default=func.now())
144
176
 
177
+ _context_var = contextvars.ContextVar('message_context', default=None)
178
+
145
179
  def __init__(self, **kwargs):
146
180
  """
147
181
  Called only when creating a NEW instance, not when loading from DB.
148
182
  """
149
183
  super().__init__(**kwargs)
150
- self._initialize_from_session()
184
+ self._initialize_from_context()
151
185
 
152
186
  @property
153
187
  def model_object(self):
@@ -164,26 +198,57 @@ class Message(Base):
164
198
  self.author_username = value.username
165
199
  return value
166
200
 
167
- def _initialize_from_session(self):
168
- """Populate fields from the current CherryPy session/request."""
169
- request = cherrypy.request
170
- try:
171
- if not self.author:
172
- self.author = request.currentuser
173
- except AttributeError:
174
- pass # No active request/user
175
-
176
- try:
177
- if not self.ip_address:
178
- self.ip_address = request.remote.ip or ''
179
- except AttributeError:
180
- pass
201
+ def _initialize_from_context(self):
202
+ """
203
+ Populate fields from the current execution context: either the live
204
+ CherryPy request (the common case), or a propagated ExecutionContext
205
+ installed via `copy_context()` in a background thread.
206
+ """
207
+ ctx = Message._current_context()
208
+ if not self.author and ctx.author is not None:
209
+ self.author = ctx.author
210
+ if not self.author_id and ctx.author_id:
211
+ self.author_id = ctx.author_id
212
+ if not self.author_username and ctx.author_username:
213
+ self.author_username = ctx.author_username
214
+ if not self.ip_address and ctx.ip_address:
215
+ self.ip_address = ctx.ip_address
216
+ if not self.user_agent and ctx.user_agent:
217
+ self.user_agent = ctx.user_agent
218
+
219
+ @staticmethod
220
+ def capture_context():
221
+ """
222
+ Capture author/ip/user-agent from the current CherryPy request.
223
+ Call this from the HTTP thread before scheduling background work.
224
+ Returns a context-manager-capable object; use it with `.use()`.
225
+ """
226
+ request = getattr(cherrypy, 'request', None)
227
+ author = getattr(request, 'currentuser', None) if request else None
228
+ author_id = getattr(author, 'id', None) if author else None
229
+ author_username = getattr(author, 'username', None) if author else None
230
+ ip_address = ''
231
+ user_agent = ''
232
+ if request is not None:
233
+ try:
234
+ ip_address = request.remote.ip or ''
235
+ except AttributeError:
236
+ pass
237
+ try:
238
+ user_agent = request.headers.get('User-Agent', '')
239
+ except AttributeError:
240
+ pass
241
+ return _MessageContext(
242
+ author=author,
243
+ author_id=author_id,
244
+ author_username=author_username,
245
+ ip_address=ip_address,
246
+ user_agent=user_agent,
247
+ )
181
248
 
182
- try:
183
- if not self.user_agent:
184
- self.user_agent = request.headers.get('User-Agent', '')
185
- except AttributeError:
186
- pass
249
+ @classmethod
250
+ def _current_context(cls):
251
+ return cls._context_var.get() or cls.capture_context()
187
252
 
188
253
 
189
254
  class MessageMixin:
@@ -21,6 +21,7 @@ import sys
21
21
 
22
22
  import cherrypy
23
23
  from cherrypy_foundation.passwd import check_password, hash_password
24
+ from cherrypy_foundation.tools.i18n import gettext
24
25
  from cherrypy_foundation.tools.i18n import gettext_lazy as _
25
26
  from sqlalchemy import (
26
27
  Boolean,
@@ -42,7 +43,7 @@ from sqlalchemy.ext.hybrid import hybrid_property
42
43
  from sqlalchemy.orm import Session, relationship, validates
43
44
 
44
45
  from ._callbacks import add_post_commit_tasks
45
- from ._message import AUDIT_IGNORE, MessageMixin, get_model_changes
46
+ from ._message import AUDIT_IGNORE, Message, MessageMixin, get_model_changes
46
47
  from ._repo import RepoObject
47
48
  from ._session import SessionObject
48
49
  from ._sshkey import SshKey
@@ -81,25 +82,38 @@ Base = cherrypy.db.base
81
82
  SEP = b'/'
82
83
 
83
84
 
84
- def delete_user_with_data(userid):
85
+ def delete_user_with_data(userid, ctx):
85
86
  """
86
87
  Job to delete user with all data.
87
88
  """
88
89
  try:
89
- # Let start by deleting all repositories from disk.
90
90
  userobj = UserObject.get_user(userid)
91
91
  username = userobj.username
92
- logger.info('deleting user [%s] with data', username)
93
- for repoobj in userobj.repo_objs:
94
- logger.info('deleting repository [%s]', repoobj.display_name)
95
- repoobj.delete_repo()
96
- # Finish by deleting the user it self.
97
- userobj.delete()
98
- userobj.commit()
99
- logger.info('user [%s] deleted', username)
100
92
  except Exception:
93
+ logger.warning("deleting user id [%s] failed", userid, exc_info=True)
94
+ UserObject.session.rollback()
95
+ raise
96
+
97
+ try:
98
+ with ctx.use():
99
+ # Let start by deleting all repositories from disk.
100
+ logger.info('deleting user [%s] with data', username)
101
+ for repoobj in userobj.repo_objs:
102
+ logger.info('deleting repository [%s]', repoobj.display_name)
103
+ repoobj.delete_repo()
104
+ repoobj.commit()
105
+ # Finish by deleting the user it self.
106
+ userobj.delete()
107
+ userobj.commit()
108
+ logger.info('user [%s] deleted', username)
109
+ except Exception:
110
+ logger.warning("deleting user [%s] failed", username, exc_info=True)
101
111
  UserObject.session.rollback()
112
+ userobj.add_message(Message(body=gettext("Deletion of user failed"), type=Message.TYPE_EVENT))
113
+ userobj.commit()
102
114
  raise
115
+ finally:
116
+ UserObject.session.rollback()
103
117
 
104
118
 
105
119
  class UserObject(MessageMixin, Base):
@@ -365,11 +379,16 @@ class UserObject(MessageMixin, Base):
365
379
  raise ValueError(_("can't delete admin user"))
366
380
  if delete_data:
367
381
  self.status = UserObject.STATUS_DELETING
382
+ self.add_message(Message(body=gettext("Deletion of user has been scheduled."), type=Message.TYPE_EVENT))
368
383
  for repoobj in self.repo_objs:
384
+ repoobj.add_message(
385
+ Message(body=gettext("Deletion of repository has been scheduled."), type=Message.TYPE_EVENT)
386
+ )
369
387
  repoobj._status = RepoObject.STATUS_DELETING
370
- add_post_commit_tasks(self.session, 'scheduler:add_job_now', delete_user_with_data, self.id)
388
+ ctx = Message.capture_context()
389
+ add_post_commit_tasks(self.session, 'scheduler:add_job_now', delete_user_with_data, self.id, ctx)
371
390
  else:
372
- # Otherwise, let delete use directly.
391
+ # Otherwise, let delete user directly.
373
392
  return Base.delete(self)
374
393
 
375
394
  def delete(self):
@@ -39,23 +39,20 @@ from rdiffweb.core.model import Message, RepoObject, UserObject
39
39
  CONTEXT = 'NOTIFICATION'
40
40
 
41
41
 
42
- def _log_user_login(user_id, date, ip_address, user_agent):
42
+ def _log_user_login(ctx):
43
43
  """Used to log user loging asynchronously."""
44
44
  # Make sure to start from a clean session.
45
45
  cherrypy.db.clear_sessions()
46
46
  # Log event
47
47
  with cherrypy.db.session.begin():
48
- user = UserObject.get_user(user_id)
49
- user.add_message(
50
- Message(
51
- body=_("User login to web application"),
52
- type=Message.TYPE_EVENT,
53
- date=date,
54
- ip_address=ip_address,
55
- user_agent=user_agent,
56
- author=user,
48
+ author = UserObject.get_user(ctx.author_id)
49
+ with ctx.use():
50
+ author.add_message(
51
+ Message(
52
+ body=_("User login to web application"),
53
+ type=Message.TYPE_EVENT,
54
+ )
57
55
  )
58
- )
59
56
 
60
57
 
61
58
  class NotificationPlugin(SimplePlugin):
@@ -333,15 +330,11 @@ class NotificationPlugin(SimplePlugin):
333
330
  )
334
331
  # Log user_login in a different thread.
335
332
  if hasattr(userobj, 'add_message'):
336
- request = cherrypy.serving.request
337
- now = datetime.now(timezone.utc)
333
+ ctx = Message.capture_context()._replace(author=userobj)
338
334
  self.bus.publish(
339
335
  'scheduler:add_job_now',
340
336
  _log_user_login,
341
- user_id=userobj.id,
342
- date=now,
343
- ip_address=request.remote.ip,
344
- user_agent=request.headers.get('User-Agent', ''),
337
+ ctx=ctx,
345
338
  )
346
339
 
347
340
  def notification_job(self):
@@ -35,7 +35,7 @@
35
35
  {% set role_choices = dict(edit_form.role.choices) %}
36
36
  {% set mfa_class = {0: 'text-bg-light border', 1: 'bg-success border'} %}
37
37
  {% set mfa_choices = dict(edit_form.mfa.choices) %}
38
- {% set status_class = {'':'bg-success bg-opacity-10 text-success', currentuser.STATUS_DISABLED:'bg-secondary bg-opacity-10 text-secondary-emphasis', currentuser.STATUS_DELETING:'bg-danger'} %}
38
+ {% set status_class = {'':'bg-success bg-opacity-10 text-success', currentuser.STATUS_DISABLED:'bg-secondary bg-opacity-10 text-secondary-emphasis', currentuser.STATUS_DELETING:'bg-warning-subtle text-warning-emphasis border-warning-subtle'} %}
39
39
  {% set status_label = {'':_('Active'), currentuser.STATUS_DISABLED: _('Disabled'), currentuser.STATUS_DELETING: _('Deleting')} %}
40
40
  {% set report_time_range_choices = dict(edit_form.report_time_range.choices) %}
41
41
  <RdwTable :buttons="{{ buttons }}" :paging="False" class="border rounded-2">
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rdiffweb
3
- Version: 7.2.0b1
3
+ Version: 7.2.0b2
4
4
  Summary: A web interface to rdiff-backup repositories.
5
5
  Author-email: Patrik Dufresne <patrik@ikus-soft.com>
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "7.2.0b2",
3
+ "distance": 0,
4
+ "node": "ga4d62f5b86b9c09723dc0229dd0572b5bc8f194d",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-10-09"
8
+ }
@@ -1,7 +0,0 @@
1
- # Reporting a Vulnerability
2
-
3
- If you discover a security vulnerability in Rdiffweb, please disclose it directly by email to [info@ikus-soft.com](mailto:info@ikus-soft.com).
4
-
5
- We will review your report promptly and work with you to address the issue responsibly.
6
-
7
- Thank you for improving the security of Rdiffweb.
@@ -1,8 +0,0 @@
1
- {
2
- "tag": "7.2.0b1",
3
- "distance": 0,
4
- "node": "g3fc88732c47a298467c7e0c0e4e774e1f9a7e123",
5
- "dirty": false,
6
- "branch": "HEAD",
7
- "node_date": "2026-09-30"
8
- }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes