rdiffweb 7.1.1__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.1.1 → rdiffweb-7.2.0b2}/CHANGELOG.md +1 -1
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/PKG-INFO +5 -5
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/README.md +3 -3
- rdiffweb-7.2.0b2/SECURITY.md +33 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/pyproject.toml +1 -1
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/components/RdwMessages.jinja +6 -4
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/components/RdwTable.css +9 -1
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/components/RepoCard.jinja +12 -15
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_admin_repos.py +3 -1
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_home.py +3 -2
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_controller.py +5 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_history.py +32 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_login.py +1 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/core/librdiff.py +13 -3
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/core/model/_callbacks.py +2 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/core/model/_message.py +85 -20
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/core/model/_repo.py +23 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/core/model/_user.py +36 -14
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/core/notification.py +13 -19
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/templates/admin_repos.html +15 -12
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/templates/admin_session.html +4 -3
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/templates/admin_users.html +6 -5
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/templates/graphs.html +0 -6
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/templates/home.html +2 -2
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/templates/include/email_kpi_cards.html +2 -2
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/templates/include/email_repo_card.html +5 -8
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/templates/login.html +2 -2
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/templates/main.css +2 -2
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/templates/stats.html +2 -1
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/test.py +14 -11
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb.egg-info/PKG-INFO +5 -5
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb.egg-info/requires.txt +1 -1
- rdiffweb-7.2.0b2/rdiffweb.egg-info/scm_version.json +8 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/tox.ini +6 -6
- rdiffweb-7.1.1/SECURITY.md +0 -7
- rdiffweb-7.1.1/rdiffweb.egg-info/scm_version.json +0 -8
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/FUNDING.yml +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/LICENSE +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/MANIFEST.in +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/babel.cfg +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/__init__.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/components/Breadcrumb.jinja +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/components/ButtonConfirm.jinja +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/components/Heatmap.css +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/components/Heatmap.jinja +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/components/HeatmapLegend.jinja +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/components/HeatmapYear.jinja +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/components/KpiCard.css +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/components/KpiCard.jinja +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/components/MfaCurrentBadgeWidget.jinja +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/components/ModalConfirm.jinja +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/components/ModalConfirm.js +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/components/ModalDialog.jinja +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/components/ModalDialog.js +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/components/NavItem.jinja +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/components/NavItemRepo.jinja +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/components/RdwChart.jinja +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/components/RdwChart.js +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/components/RdwDateSelector.css +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/components/RdwDateSelector.jinja +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/components/RdwDateSelector.js +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/components/RdwEmpty.jinja +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/components/RdwFooter.jinja +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/components/RdwIcon.css +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/components/RdwIcon.jinja +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/components/RdwLog.css +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/components/RdwLog.jinja +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/components/RdwModal.jinja +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/components/RdwTable.jinja +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/components/RdwTable.js +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/components/RdwTime.jinja +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/components/RepoStatus.jinja +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/components/RepoWarning.css +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/components/RepoWarning.jinja +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/components/SelectMultipleWidget.css +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/components/SelectMultipleWidget.jinja +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/components/StorageUsage.jinja +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/__init__.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/api.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/api_currentuser.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/api_openapi.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/dispatch.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/filter_authorization.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/formdb.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_admin.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_admin_activity.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_admin_logs.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_admin_session.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_admin_sysinfo.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_admin_users.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_browse.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_delete.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_graphs.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_history.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_login.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_logs.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_mfa.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_pref_general.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_pref_mfa.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_pref_notification.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_pref_session.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_pref_sshkeys.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_pref_tokens.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_prefs.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_repo_activity.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_restore.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_settings.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/page_stats.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/static.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/__init__.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_api.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_check_links.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_openapi.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_admin.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_admin_activity.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_admin_logs.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_admin_repos.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_admin_session.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_admin_sysinfo.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_admin_users.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_browse.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_delete.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_error.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_graphs.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_home.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_logs.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_mfa.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_prefs_general.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_prefs_mfa.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_prefs_notification.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_prefs_session.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_prefs_ssh.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_prefs_tokens.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_repo_activity.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_restore.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_settings.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_page_stats.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/tests/test_secure_headers.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/controller/widgets.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/core/__init__.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/core/authorizedkeys.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/core/config.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/core/dircache.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/core/diskusage.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/core/model/__init__.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/core/model/_diskusage.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/core/model/_session.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/core/model/_sshkey.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/core/model/_timestamp.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/core/model/_token.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/core/model/_update.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/core/model/tests/__init__.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/core/model/tests/test_db_after_create.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/core/model/tests/test_repo.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/core/model/tests/test_token.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/core/model/tests/test_user.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/core/quota.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/core/remove_older.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/core/restore.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/core/tests/__init__.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/core/tests/rdiff-backup-data/error_log.2015-11-19T07:27:46-05:00.data +0 -0
- {rdiffweb-7.1.1 → 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.1.1 → 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.1.1 → rdiffweb-7.2.0b2}/rdiffweb/core/tests/rdiff-backup-data/file_statistics.2014-11-05T16:05:07-05:00.data +0 -0
- {rdiffweb-7.1.1 → 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.1.1 → rdiffweb-7.2.0b2}/rdiffweb/core/tests/rdiff-backup-data/session_statistics.2014-11-02T09:16:43-05:00.data +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/core/tests/session_statistics.2014-11-02T09:16:43-05:00.data +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/core/tests/test_authorized_keys +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/core/tests/test_authorizedkeys.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/core/tests/test_config.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/core/tests/test_diskusage.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/core/tests/test_librdiff.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/core/tests/test_login.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/core/tests/test_notification.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/core/tests/test_publickey_ssh_dsa.pub +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/core/tests/test_publickey_ssh_rsa.pub +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/core/tests/test_quota.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/core/tests/test_remove_older.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/core/tests/test_restore.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/locales/ca/LC_MESSAGES/messages.mo +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/locales/ca/LC_MESSAGES/messages.po +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/locales/de/LC_MESSAGES/messages.mo +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/locales/de/LC_MESSAGES/messages.po +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/locales/en/LC_MESSAGES/messages.mo +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/locales/en/LC_MESSAGES/messages.po +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/locales/es/LC_MESSAGES/messages.mo +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/locales/es/LC_MESSAGES/messages.po +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/locales/fr/LC_MESSAGES/messages.mo +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/locales/fr/LC_MESSAGES/messages.po +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/locales/messages.pot +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/locales/pt/LC_MESSAGES/messages.mo +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/locales/pt/LC_MESSAGES/messages.po +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/locales/ru/LC_MESSAGES/messages.mo +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/locales/ru/LC_MESSAGES/messages.po +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/main.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/plugins/__init__.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/plugins/restapi.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/rdw_app.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/static/favicon.ico +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/static/favicon.svg +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/static/header-logo.png +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/static/header-logo.svg +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/static/login-bg.png +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/static/main.js +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/static/robots.txt +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/templates/admin_activity.html +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/templates/admin_logs.html +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/templates/admin_sysinfo.html +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/templates/admin_user_edit.html +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/templates/browse.html +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/templates/email_access_token_added.html +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/templates/email_authorizedkey_added.html +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/templates/email_changed.html +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/templates/email_latest.html +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/templates/email_layout.html +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/templates/email_mfa.html +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/templates/email_notification.html +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/templates/email_password_changed.html +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/templates/email_repo_added.html +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/templates/email_repo_deleted.html +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/templates/email_report.html +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/templates/email_storage_usage.html +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/templates/email_verification_code.html +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/templates/error_page.html +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/templates/history.html +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/templates/include/email_storage_usage_card.html +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/templates/include/session.html +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/templates/include/timerange.html +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/templates/layout.html +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/templates/logs.html +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/templates/mfa.html +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/templates/prefs_general.html +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/templates/prefs_mfa.html +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/templates/prefs_notification.html +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/templates/prefs_session.html +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/templates/prefs_sshkeys.html +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/templates/prefs_tokens.html +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/templates/repo_activity.html +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/templates/restore.html +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/templates/settings.html +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/templates/sidebar.html +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/tests/__init__.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/tests/rdw.conf +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/tests/rdw.db +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/tests/test_app.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/tests/test_main.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/tests/testcases.tar.gz +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/tools/__init__.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/tools/enrich_session.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/tools/errors.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/tools/poppath.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb/tools/required_scope.py +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb.egg-info/SOURCES.txt +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb.egg-info/dependency_links.txt +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb.egg-info/entry_points.txt +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb.egg-info/scm_file_list.json +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdiffweb.egg-info/top_level.txt +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/rdw.conf +0 -0
- {rdiffweb-7.1.1 → rdiffweb-7.2.0b2}/setup.cfg +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.
|
|
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
|
|
@@ -691,7 +691,7 @@ Classifier: Framework :: CherryPy
|
|
|
691
691
|
Requires-Python: <4,>=3.8
|
|
692
692
|
Description-Content-Type: text/markdown
|
|
693
693
|
License-File: LICENSE
|
|
694
|
-
Requires-Dist: cherrypy-foundation<2,>=1.
|
|
694
|
+
Requires-Dist: cherrypy-foundation<2,>=1.18.0
|
|
695
695
|
Requires-Dist: cherrypy-foundation[ldap]
|
|
696
696
|
Requires-Dist: cherrypy-foundation[oauth]
|
|
697
697
|
Requires-Dist: cherrypy-foundation[passwd]
|
|
@@ -727,7 +727,7 @@ Dynamic: license-file
|
|
|
727
727
|
<strong>
|
|
728
728
|
<a href="https://www.rdiffweb.org">website</a>
|
|
729
729
|
• <a href="https://www.ikus-soft.com/archive/rdiffweb/doc/latest/html/">docs</a>
|
|
730
|
-
• <a href="https://
|
|
730
|
+
• <a href="https://minarca.discourse.group/">community</a>
|
|
731
731
|
• <a href="https://rdiffweb-demo.ikus-soft.com/">demo</a>
|
|
732
732
|
</strong>
|
|
733
733
|
</p>
|
|
@@ -823,11 +823,11 @@ By signing up for, you will receive regular updates and notifications about new
|
|
|
823
823
|
|
|
824
824
|
[Rdiffweb Newsletter](https://rdiffweb.org/contactus).
|
|
825
825
|
|
|
826
|
-
##
|
|
826
|
+
## Community
|
|
827
827
|
|
|
828
828
|
Join our growing community to get answers to your technical questions.
|
|
829
829
|
|
|
830
|
-
Rdiffweb users should use the [
|
|
830
|
+
Rdiffweb users should use the [Discourse Group](https://minarca.discourse.group/).
|
|
831
831
|
|
|
832
832
|
## Documentation
|
|
833
833
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<strong>
|
|
5
5
|
<a href="https://www.rdiffweb.org">website</a>
|
|
6
6
|
• <a href="https://www.ikus-soft.com/archive/rdiffweb/doc/latest/html/">docs</a>
|
|
7
|
-
• <a href="https://
|
|
7
|
+
• <a href="https://minarca.discourse.group/">community</a>
|
|
8
8
|
• <a href="https://rdiffweb-demo.ikus-soft.com/">demo</a>
|
|
9
9
|
</strong>
|
|
10
10
|
</p>
|
|
@@ -100,11 +100,11 @@ By signing up for, you will receive regular updates and notifications about new
|
|
|
100
100
|
|
|
101
101
|
[Rdiffweb Newsletter](https://rdiffweb.org/contactus).
|
|
102
102
|
|
|
103
|
-
##
|
|
103
|
+
## Community
|
|
104
104
|
|
|
105
105
|
Join our growing community to get answers to your technical questions.
|
|
106
106
|
|
|
107
|
-
Rdiffweb users should use the [
|
|
107
|
+
Rdiffweb users should use the [Discourse Group](https://minarca.discourse.group/).
|
|
108
108
|
|
|
109
109
|
## Documentation
|
|
110
110
|
|
|
@@ -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.
|
|
@@ -42,14 +42,15 @@
|
|
|
42
42
|
{% trans %}Event{% endtrans %}
|
|
43
43
|
</span>
|
|
44
44
|
{% endset %}
|
|
45
|
-
{% set
|
|
45
|
+
{% set spacer_btn = {"extend": "spacer"} %}
|
|
46
|
+
{% set reset_btn = {'text': _('Reset Filters'), 'extend': 'reset', 'className':'btn-secondary'} %}
|
|
46
47
|
{% set new_type = {'text': _('Creations'), 'extend': 'filter', 'column': 'type:name', 'search':'new', 'regex': False, 'className':'btn-outline-secondary'} %}
|
|
47
48
|
{% set dirty_type = {'text': _('Modifications'), 'extend': 'filter', 'column': 'type:name', 'search':'dirty', 'className':'btn-outline-secondary'} %}
|
|
48
49
|
{% set comment_type = {'text': _('Comments'), 'extend': 'filter', 'column': 'type:name', 'search':'comment', 'className':'btn-outline-secondary'} %}
|
|
49
50
|
{% set event_type = {'text': _('Events'), 'extend': 'filter', 'column': 'type:name', 'search':'event', 'className':'btn-outline-secondary'} %}
|
|
50
51
|
{% set delete_type = {'text': _('Deletions'), 'extend': 'filter', 'column': 'type:name', 'search':'deleted', 'className':'btn-outline-secondary'} %}
|
|
51
52
|
{% if show in ['repo', 'user'] %}
|
|
52
|
-
{% set buttons = [new_type, dirty_type, comment_type, event_type, delete_type, reset_btn] %}
|
|
53
|
+
{% set buttons = [new_type, dirty_type, comment_type, event_type, delete_type, spacer_btn, reset_btn] %}
|
|
53
54
|
{% set columns = [
|
|
54
55
|
{'name':'date', 'data':'date', 'title':_('Date'), 'render':'datetime'},
|
|
55
56
|
{'name':'author', 'data':'author_username', 'title':_('Actor'), 'orderable':True, 'render':'actor'},
|
|
@@ -58,8 +59,9 @@
|
|
|
58
59
|
] %}
|
|
59
60
|
{% else %}
|
|
60
61
|
{% set buttons = [new_type, dirty_type, comment_type, event_type, delete_type,
|
|
61
|
-
{'text': _('Users'), 'extend': 'filter', 'column': 'model_name:name', 'search':'user', 'className':'ms-3 btn-outline-
|
|
62
|
-
{'text': _('Repositories'), 'extend': 'filter', 'column': 'model_name:name', 'search':'repo', 'className':'btn-outline-
|
|
62
|
+
{'text': _('Users'), 'extend': 'filter', 'column': 'model_name:name', 'search':'user', 'className':'ms-3 btn-outline-secondary'},
|
|
63
|
+
{'text': _('Repositories'), 'extend': 'filter', 'column': 'model_name:name', 'search':'repo', 'className':'btn-outline-secondary'},
|
|
64
|
+
spacer_btn,
|
|
63
65
|
reset_btn
|
|
64
66
|
] %}
|
|
65
67
|
{% set columns = [
|
|
@@ -15,9 +15,17 @@
|
|
|
15
15
|
width: 100%;
|
|
16
16
|
color: var(--bs-body-color) !important;
|
|
17
17
|
background-color: var(--bs-body-bg) !important;
|
|
18
|
-
min-width: 25vw;
|
|
19
18
|
padding:0 !important;
|
|
20
19
|
}
|
|
20
|
+
/* If the row is too long, make it wrap. */
|
|
21
|
+
.rdw-table .dt-layout-row {
|
|
22
|
+
flex-wrap: wrap;
|
|
23
|
+
}
|
|
24
|
+
/* Add margin arround buttons when content is wrapping. */
|
|
25
|
+
.rdw-table .dt-buttons .btn.btn-sm {
|
|
26
|
+
margin-top: 3px;
|
|
27
|
+
margin-bottom: 3px;
|
|
28
|
+
}
|
|
21
29
|
|
|
22
30
|
/* Hide search label */
|
|
23
31
|
.rdw-table .dt-search label {
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
{# Backup count #}
|
|
29
29
|
<div class="col-6">
|
|
30
30
|
<div class="bg-light rounded-3 p-2">
|
|
31
|
-
<div class="fw-bold text-truncate">{{ repo.
|
|
31
|
+
<div class="fw-bold text-truncate">{{ repo.backup_count or mdash }}</div>
|
|
32
32
|
<div class="text-muted" style="font-size:.72rem">{% trans %}Restore points{% endtrans %}</div>
|
|
33
33
|
</div>
|
|
34
34
|
</div>
|
|
@@ -39,25 +39,22 @@
|
|
|
39
39
|
<RdwIcon value="bi-calendar-range" class="me-1" />
|
|
40
40
|
{%- trans %}Oldest:{% endtrans %}
|
|
41
41
|
<strong>
|
|
42
|
-
<RdwTime :value="repo.
|
|
42
|
+
<RdwTime :value="repo.first_backup_date" format="short" />
|
|
43
43
|
</strong>
|
|
44
44
|
</span>
|
|
45
45
|
{# Error count #}
|
|
46
46
|
<span class="text-muted small">
|
|
47
|
-
{% if repo.
|
|
48
|
-
{% set error_count = repo.session_statistics[-1].errors %}
|
|
49
|
-
{% if error_count > 0 %}
|
|
50
|
-
<a class="text-danger-emphasis fw-semibold"
|
|
51
|
-
href="{{ url_for('logs', repo, date=repo.session_statistics[-1].date) }}">
|
|
52
|
-
<RdwIcon value="bi-exclamation-circle" class="me-1" />
|
|
53
|
-
{{- _('%(count)s errors', count=error_count) }}
|
|
54
|
-
</a>
|
|
55
|
-
{% else %}
|
|
56
|
-
<RdwIcon value="bi-check-circle" class="text-success" class="me-1" />
|
|
57
|
-
{{- _('%(count)s errors', count=error_count) }}
|
|
58
|
-
{% endif %}
|
|
59
|
-
{% else %}
|
|
47
|
+
{% if repo.last_error_count is none %}
|
|
60
48
|
{{ mdash }}
|
|
49
|
+
{% elif repo.last_error_date %}
|
|
50
|
+
<a class="text-danger-emphasis fw-semibold"
|
|
51
|
+
href="{{ url_for('logs', repo, date=repo.last_error_date) }}">
|
|
52
|
+
<RdwIcon value="bi-exclamation-circle" class="me-1" />
|
|
53
|
+
{{- _('%(count)s errors', count=repo.last_error_count) }}
|
|
54
|
+
</a>
|
|
55
|
+
{% else %}
|
|
56
|
+
<RdwIcon value="bi-check-circle" class="text-success" class="me-1" />
|
|
57
|
+
{{- _('%(count)s errors', count=repo.last_error_count) }}
|
|
61
58
|
{% endif %}
|
|
62
59
|
</span>
|
|
63
60
|
</div>
|
|
@@ -23,7 +23,7 @@ from cherrypy_foundation.tools.i18n import gettext_lazy as _
|
|
|
23
23
|
from cherrypy_foundation.url import url_for
|
|
24
24
|
|
|
25
25
|
from rdiffweb.core.librdiff import RdiffTime
|
|
26
|
-
from rdiffweb.core.model import DiskUsage, UserObject
|
|
26
|
+
from rdiffweb.core.model import DiskUsage, RepoObject, UserObject
|
|
27
27
|
|
|
28
28
|
# Define the logger
|
|
29
29
|
logger = logging.getLogger(__name__)
|
|
@@ -68,6 +68,7 @@ class HomePage:
|
|
|
68
68
|
|
|
69
69
|
# Add disk usage to repos.
|
|
70
70
|
repo_objs = DiskUsage.attach_disk_usage(repo_objs)
|
|
71
|
+
repo_objs = RepoObject.attach_statistics_info(repo_objs)
|
|
71
72
|
|
|
72
73
|
activity_end = RdiffTime()
|
|
73
74
|
activity_start = RdiffTime() - timedelta(days=30)
|
|
@@ -82,7 +83,7 @@ class HomePage:
|
|
|
82
83
|
"total_interrupted": status_counts["interrupted"],
|
|
83
84
|
"total_in_progress": status_counts["in_progress"],
|
|
84
85
|
# Errors
|
|
85
|
-
"
|
|
86
|
+
"total_error_count": sum(r.last_error_count for r in repo_objs if r.last_error_count),
|
|
86
87
|
# last_backup
|
|
87
88
|
"last_backup_date": last_backup.last_backup_date if last_backup else None,
|
|
88
89
|
"last_backup_repo": last_backup.display_name if last_backup else None,
|
|
@@ -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):
|
|
@@ -19,6 +19,8 @@ from sqlalchemy.orm import Session
|
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
def add_post_commit_tasks(session, *args):
|
|
22
|
+
if getattr(session, '_trans_context_manager', False):
|
|
23
|
+
raise RuntimeError('cannot use add_post_commit_tasks with sql context manager')
|
|
22
24
|
# Add task
|
|
23
25
|
session.info.setdefault("_after_commit_tasks", []).append(args)
|
|
24
26
|
|
|
@@ -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:
|
|
@@ -192,6 +192,29 @@ class RepoObject(MessageMixin, Base, RdiffRepo):
|
|
|
192
192
|
except ValueError:
|
|
193
193
|
raise DoesNotExistError(path)
|
|
194
194
|
|
|
195
|
+
@classmethod
|
|
196
|
+
def attach_statistics_info(cls, repo_objs):
|
|
197
|
+
for repo in repo_objs:
|
|
198
|
+
try:
|
|
199
|
+
stats = repo.session_statistics
|
|
200
|
+
except Exception:
|
|
201
|
+
cherrypy.log('unexpected error trying to get session_statistics', traceback=True)
|
|
202
|
+
stats = None
|
|
203
|
+
|
|
204
|
+
if stats:
|
|
205
|
+
repo.last_error_count = stats[-1].errors
|
|
206
|
+
repo.last_error_date = stats[-1].date
|
|
207
|
+
repo.last_filesize = stats[-1].sourcefilesize
|
|
208
|
+
repo.first_backup_date = stats[0].date
|
|
209
|
+
repo.backup_count = len(stats)
|
|
210
|
+
else:
|
|
211
|
+
repo.last_error_count = None
|
|
212
|
+
repo.last_error_date = None
|
|
213
|
+
repo.last_filesize = None
|
|
214
|
+
repo.first_backup_date = None
|
|
215
|
+
repo.backup_count = 0
|
|
216
|
+
return repo_objs
|
|
217
|
+
|
|
195
218
|
def __init__(self, *args, **kwargs):
|
|
196
219
|
Base.__init__(self, *args, **kwargs)
|
|
197
220
|
self.__init_on_load__()
|
|
@@ -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,22 +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
|
-
with cherrypy.db.session.begin():
|
|
90
|
-
# Let start by deleting all repositories from disk.
|
|
89
|
+
try:
|
|
91
90
|
userobj = UserObject.get_user(userid)
|
|
92
91
|
username = userobj.username
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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)
|
|
111
|
+
UserObject.session.rollback()
|
|
112
|
+
userobj.add_message(Message(body=gettext("Deletion of user failed"), type=Message.TYPE_EVENT))
|
|
113
|
+
userobj.commit()
|
|
114
|
+
raise
|
|
115
|
+
finally:
|
|
116
|
+
UserObject.session.rollback()
|
|
100
117
|
|
|
101
118
|
|
|
102
119
|
class UserObject(MessageMixin, Base):
|
|
@@ -362,11 +379,16 @@ class UserObject(MessageMixin, Base):
|
|
|
362
379
|
raise ValueError(_("can't delete admin user"))
|
|
363
380
|
if delete_data:
|
|
364
381
|
self.status = UserObject.STATUS_DELETING
|
|
382
|
+
self.add_message(Message(body=gettext("Deletion of user has been scheduled."), type=Message.TYPE_EVENT))
|
|
365
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
|
+
)
|
|
366
387
|
repoobj._status = RepoObject.STATUS_DELETING
|
|
367
|
-
|
|
388
|
+
ctx = Message.capture_context()
|
|
389
|
+
add_post_commit_tasks(self.session, 'scheduler:add_job_now', delete_user_with_data, self.id, ctx)
|
|
368
390
|
else:
|
|
369
|
-
# Otherwise, let delete
|
|
391
|
+
# Otherwise, let delete user directly.
|
|
370
392
|
return Base.delete(self)
|
|
371
393
|
|
|
372
394
|
def delete(self):
|