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.
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/CHANGELOG.md +1 -1
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/PKG-INFO +1 -1
- rdiffweb-7.2.0b2/SECURITY.md +33 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_controller.py +5 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_history.py +32 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_login.py +1 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/librdiff.py +13 -3
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/model/_message.py +85 -20
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/model/_user.py +32 -13
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/notification.py +10 -17
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/admin_users.html +1 -1
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb.egg-info/PKG-INFO +1 -1
- rdiffweb-7.2.0b2/rdiffweb.egg-info/scm_version.json +8 -0
- rdiffweb-7.2.0b1/SECURITY.md +0 -7
- rdiffweb-7.2.0b1/rdiffweb.egg-info/scm_version.json +0 -8
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/FUNDING.yml +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/LICENSE +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/MANIFEST.in +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/README.md +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/babel.cfg +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/pyproject.toml +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/__init__.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/Breadcrumb.jinja +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/ButtonConfirm.jinja +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/Heatmap.css +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/Heatmap.jinja +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/HeatmapLegend.jinja +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/HeatmapYear.jinja +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/KpiCard.css +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/KpiCard.jinja +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/MfaCurrentBadgeWidget.jinja +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/ModalConfirm.jinja +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/ModalConfirm.js +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/ModalDialog.jinja +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/ModalDialog.js +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/NavItem.jinja +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/NavItemRepo.jinja +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/RdwChart.jinja +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/RdwChart.js +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/RdwDateSelector.css +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/RdwDateSelector.jinja +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/RdwDateSelector.js +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/RdwEmpty.jinja +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/RdwFooter.jinja +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/RdwIcon.css +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/RdwIcon.jinja +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/RdwLog.css +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/RdwLog.jinja +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/RdwMessages.jinja +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/RdwModal.jinja +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/RdwTable.css +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/RdwTable.jinja +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/RdwTable.js +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/RdwTime.jinja +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/RepoCard.jinja +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/RepoStatus.jinja +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/RepoWarning.css +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/RepoWarning.jinja +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/SelectMultipleWidget.css +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/SelectMultipleWidget.jinja +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/components/StorageUsage.jinja +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/__init__.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/api.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/api_currentuser.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/api_openapi.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/dispatch.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/filter_authorization.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/formdb.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_admin.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_admin_activity.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_admin_logs.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_admin_repos.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_admin_session.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_admin_sysinfo.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_admin_users.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_browse.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_delete.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_graphs.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_history.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_home.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_login.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_logs.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_mfa.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_pref_general.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_pref_mfa.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_pref_notification.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_pref_session.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_pref_sshkeys.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_pref_tokens.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_prefs.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_repo_activity.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_restore.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_settings.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_stats.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/static.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/__init__.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_api.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_check_links.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_openapi.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_admin.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_admin_activity.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_admin_logs.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_admin_repos.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_admin_session.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_admin_sysinfo.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_admin_users.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_browse.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_delete.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_error.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_graphs.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_home.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_logs.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_mfa.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_prefs_general.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_prefs_mfa.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_prefs_notification.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_prefs_session.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_prefs_ssh.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_prefs_tokens.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_repo_activity.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_restore.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_settings.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_stats.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_secure_headers.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/controller/widgets.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/__init__.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/authorizedkeys.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/config.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/dircache.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/diskusage.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/model/__init__.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/model/_callbacks.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/model/_diskusage.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/model/_repo.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/model/_session.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/model/_sshkey.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/model/_timestamp.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/model/_token.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/model/_update.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/model/tests/__init__.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/model/tests/test_db_after_create.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/model/tests/test_repo.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/model/tests/test_token.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/model/tests/test_user.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/quota.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/remove_older.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/restore.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/tests/__init__.py +0 -0
- {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
- {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
- {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
- {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
- {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
- {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
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/tests/session_statistics.2014-11-02T09:16:43-05:00.data +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/tests/test_authorized_keys +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/tests/test_authorizedkeys.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/tests/test_config.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/tests/test_diskusage.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/tests/test_librdiff.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/tests/test_login.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/tests/test_notification.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/tests/test_publickey_ssh_dsa.pub +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/tests/test_publickey_ssh_rsa.pub +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/tests/test_quota.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/tests/test_remove_older.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/core/tests/test_restore.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/locales/ca/LC_MESSAGES/messages.mo +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/locales/ca/LC_MESSAGES/messages.po +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/locales/de/LC_MESSAGES/messages.mo +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/locales/de/LC_MESSAGES/messages.po +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/locales/en/LC_MESSAGES/messages.mo +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/locales/en/LC_MESSAGES/messages.po +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/locales/es/LC_MESSAGES/messages.mo +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/locales/es/LC_MESSAGES/messages.po +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/locales/fr/LC_MESSAGES/messages.mo +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/locales/fr/LC_MESSAGES/messages.po +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/locales/messages.pot +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/locales/pt/LC_MESSAGES/messages.mo +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/locales/pt/LC_MESSAGES/messages.po +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/locales/ru/LC_MESSAGES/messages.mo +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/locales/ru/LC_MESSAGES/messages.po +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/main.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/plugins/__init__.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/plugins/restapi.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/rdw_app.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/static/favicon.ico +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/static/favicon.svg +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/static/header-logo.png +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/static/header-logo.svg +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/static/login-bg.png +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/static/main.js +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/static/robots.txt +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/admin_activity.html +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/admin_logs.html +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/admin_repos.html +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/admin_session.html +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/admin_sysinfo.html +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/admin_user_edit.html +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/browse.html +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/email_access_token_added.html +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/email_authorizedkey_added.html +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/email_changed.html +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/email_latest.html +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/email_layout.html +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/email_mfa.html +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/email_notification.html +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/email_password_changed.html +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/email_repo_added.html +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/email_repo_deleted.html +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/email_report.html +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/email_storage_usage.html +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/email_verification_code.html +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/error_page.html +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/graphs.html +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/history.html +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/home.html +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/include/email_kpi_cards.html +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/include/email_repo_card.html +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/include/email_storage_usage_card.html +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/include/session.html +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/include/timerange.html +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/layout.html +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/login.html +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/logs.html +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/main.css +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/mfa.html +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/prefs_general.html +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/prefs_mfa.html +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/prefs_notification.html +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/prefs_session.html +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/prefs_sshkeys.html +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/prefs_tokens.html +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/repo_activity.html +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/restore.html +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/settings.html +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/sidebar.html +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/templates/stats.html +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/test.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/tests/__init__.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/tests/rdw.conf +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/tests/rdw.db +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/tests/test_app.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/tests/test_main.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/tests/testcases.tar.gz +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/tools/__init__.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/tools/enrich_session.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/tools/errors.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/tools/poppath.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb/tools/required_scope.py +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb.egg-info/SOURCES.txt +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb.egg-info/dependency_links.txt +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb.egg-info/entry_points.txt +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb.egg-info/requires.txt +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb.egg-info/scm_file_list.json +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdiffweb.egg-info/top_level.txt +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/rdw.conf +0 -0
- {rdiffweb-7.2.0b1 → rdiffweb-7.2.0b2}/setup.cfg +0 -0
- {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
|
|
|
@@ -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.
|
|
1000
|
-
increment_path = os.path.
|
|
1001
|
-
if not 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.
|
|
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
|
|
168
|
-
"""
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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-
|
|
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">
|
rdiffweb-7.2.0b1/SECURITY.md
DELETED
|
@@ -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.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|