From d41aa5a31019467775e2f2f5eb261a58f3227d25 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Fri, 17 Mar 2023 17:08:43 +0530 Subject: [PATCH 01/11] Added change passphrase feature Signed-off-by: Chirag Aggarwal --- src/vorta/assets/UI/changeborgpass.ui | 132 ++++++++++++++++++ src/vorta/assets/UI/repotab.ui | 7 + src/vorta/assets/icons/lock.svg | 1 + src/vorta/borg/_compatibility.py | 1 + src/vorta/borg/change_passphrase.py | 56 ++++++++ .../views/change_borg_passphrase_dialog.py | 104 ++++++++++++++ src/vorta/views/repo_tab.py | 17 +++ 7 files changed, 318 insertions(+) create mode 100644 src/vorta/assets/UI/changeborgpass.ui create mode 100644 src/vorta/assets/icons/lock.svg create mode 100644 src/vorta/borg/change_passphrase.py create mode 100644 src/vorta/views/change_borg_passphrase_dialog.py diff --git a/src/vorta/assets/UI/changeborgpass.ui b/src/vorta/assets/UI/changeborgpass.ui new file mode 100644 index 000000000..4536c11a3 --- /dev/null +++ b/src/vorta/assets/UI/changeborgpass.ui @@ -0,0 +1,132 @@ + + + ChangeBorgPassphrase + + + + 0 + 0 + 466 + 274 + + + + true + + + + 0 + + + + + + true + + + + Change Borg Passphrase + + + + Qt::AlignCenter + + + + + + + + + QFormLayout::ExpandingFieldsGrow + + + 5 + + + 5 + + + 5 + + + 30 + + + + + + + Old Passphrase: + + + + + + + true + + + QLineEdit::Password + + + + + + + + New Passphrase: + + + + + + + true + + + QLineEdit::Password + + + + + + + true + + + QLineEdit::Password + + + + + + + Confirm Passphrase: + + + + + + + + + + + + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + diff --git a/src/vorta/assets/UI/repotab.ui b/src/vorta/assets/UI/repotab.ui index ae083ebef..ff40ff15a 100644 --- a/src/vorta/assets/UI/repotab.ui +++ b/src/vorta/assets/UI/repotab.ui @@ -106,6 +106,13 @@ + + + + Change Borg Passphrase + + + diff --git a/src/vorta/assets/icons/lock.svg b/src/vorta/assets/icons/lock.svg new file mode 100644 index 000000000..4ab58d4eb --- /dev/null +++ b/src/vorta/assets/icons/lock.svg @@ -0,0 +1 @@ + diff --git a/src/vorta/borg/_compatibility.py b/src/vorta/borg/_compatibility.py index bf2a01b0b..1672b2bef 100644 --- a/src/vorta/borg/_compatibility.py +++ b/src/vorta/borg/_compatibility.py @@ -8,6 +8,7 @@ 'COMPACT_SUBCOMMAND': parse_version('1.2.0a1'), 'V122': parse_version('1.2.2'), 'V2': parse_version('2.0.0b1'), + 'CHANGE_PASSPHRASE': parse_version('1.1.0'), # add new version-checks here. } diff --git a/src/vorta/borg/change_passphrase.py b/src/vorta/borg/change_passphrase.py new file mode 100644 index 000000000..8f2df7dcf --- /dev/null +++ b/src/vorta/borg/change_passphrase.py @@ -0,0 +1,56 @@ +from typing import Any, Dict +from vorta.config import LOG_DIR +from vorta.i18n import trans_late, translate +from vorta.utils import borg_compat +from .borg_job import BorgJob + + +class BorgChangePassJob(BorgJob): + def started_event(self): + self.app.backup_started_event.emit() + self.app.backup_progress_event.emit(self.tr('Changing Borg passphrase...')) + + def finished_event(self, result: Dict[str, Any]): + """ + Process that the job terminated with the given results. + + Parameters + ---------- + result : Dict[str, Any] + The (json-like) dictionary containing the job results. + """ + self.app.backup_finished_event.emit(result) + self.result.emit(result) + if result['returncode'] != 0: + self.app.backup_progress_event.emit( + translate( + 'BorgChangePassJob', + 'Errors during changing passphrase. See the logs for details.', + ).format(LOG_DIR.as_uri()) + ) + else: + self.app.backup_progress_event.emit(self.tr('Borg passphrase changed.')) + + @classmethod + def prepare(cls, profile, oldPass, newPass): + ret = super().prepare(profile) + if not ret['ok']: + return ret + else: + ret['ok'] = False # Set back to false, so we can do our own checks here. + + if not borg_compat.check('CHANGE_PASSPHRASE'): + ret['ok'] = False + ret['message'] = trans_late('messages', 'This feature needs Borg 1.1.0 or higher.') + return ret + + cmd = ['borg', '--info', '--log-json', 'key', 'change-passphrase'] + cmd.append(f'{profile.repo.url}') + + ret['password'] = oldPass + ret['additional_env'] = {'BORG_NEW_PASSPHRASE': newPass} + + ret['ok'] = True + ret['cmd'] = cmd + + return ret diff --git a/src/vorta/views/change_borg_passphrase_dialog.py b/src/vorta/views/change_borg_passphrase_dialog.py new file mode 100644 index 000000000..b606d1a42 --- /dev/null +++ b/src/vorta/views/change_borg_passphrase_dialog.py @@ -0,0 +1,104 @@ +from PyQt5 import QtCore, uic +from PyQt5.QtWidgets import QAction, QApplication, QDialogButtonBox, QLineEdit +from vorta.borg.change_passphrase import BorgChangePassJob +from vorta.i18n import translate +from vorta.utils import get_asset, validate_passwords +from vorta.views.utils import get_colored_icon + +uifile = get_asset('UI/changeborgpass.ui') +ChangeBorgPassUI, ChangeBorgPassBase = uic.loadUiType(uifile) + + +class ChangeBorgPassphraseWindow(ChangeBorgPassBase, ChangeBorgPassUI): + change_borg_passphrase = QtCore.pyqtSignal(dict) + + def __init__(self, profile): + super().__init__() + self.setupUi(self) + self.setAttribute(QtCore.Qt.WA_DeleteOnClose) + self.result = None + self.profile = profile + + # dialogButtonBox + self.saveButton = self.buttonBox.button(QDialogButtonBox.StandardButton.Ok) + self.saveButton.setText(self.tr("Update")) + + self.buttonBox.rejected.connect(self.close) + self.buttonBox.accepted.connect(self.run) + self.passwordLineEdit.textChanged.connect(self.password_listener) + self.confirmLineEdit.textChanged.connect(self.password_listener) + + # Add clickable icon to toggle password visibility to end of box + self.showHideAction = QAction(self.tr("Show my passwords"), self) + self.showHideAction.setCheckable(True) + self.showHideAction.toggled.connect(self.set_visibility) + + self.passwordLineEdit.addAction(self.showHideAction, QLineEdit.TrailingPosition) + + self.set_icons() + + def retranslateUi(self, dialog): + """Retranslate strings in ui.""" + super().retranslateUi(dialog) + + # setupUi calls retranslateUi + if hasattr(self, 'saveButton'): + self.saveButton.setText(self.tr("Update")) + + def set_icons(self): + self.showHideAction.setIcon(get_colored_icon("eye")) + + def set_visibility(self, visible): + visibility = QLineEdit.Normal if visible else QLineEdit.Password + self.passwordLineEdit.setEchoMode(visibility) + self.confirmLineEdit.setEchoMode(visibility) + + if visible: + self.showHideAction.setIcon(get_colored_icon("eye-slash")) + self.showHideAction.setText(self.tr("Hide my passwords")) + else: + self.showHideAction.setIcon(get_colored_icon("eye")) + self.showHideAction.setText(self.tr("Show my passwords")) + + # No need to add this function to JobsManager because repo is set for the first time + def run(self): + if self.password_listener(): + oldPass = self.oldPasswordLineEdit.text() + newPass = self.passwordLineEdit.text() + + params = BorgChangePassJob.prepare(self.profile, oldPass, newPass) + if params['ok']: + self.saveButton.setEnabled(False) + job = BorgChangePassJob(params['cmd'], params) + job.updated.connect(self._set_status) + job.result.connect(self.run_result) + QApplication.instance().jobs_manager.add_job(job) + else: + self._set_status(params['message']) + + def _set_status(self, text): + self.errorText.setText(text) + self.errorText.repaint() + + def run_result(self, result): + self.saveButton.setEnabled(True) + if result['returncode'] == 0: + self.change_borg_passphrase.emit(result) + self.accept() + else: + self._set_status(self.tr('Unable to change Borg passphrase.')) + + def password_listener(self): + '''Validates passwords only if its going to be used''' + oldPass = self.oldPasswordLineEdit.text() + firstPass = self.passwordLineEdit.text() + secondPass = self.confirmLineEdit.text() + + # Since borg originally does not have minimum character requirement + if len(oldPass) < 1: + self.errorText.setText(translate('utils', 'Old password is required.')) + return False + + msg = validate_passwords(firstPass, secondPass) + self.errorText.setText(translate('utils', msg)) + return not bool(msg) diff --git a/src/vorta/views/repo_tab.py b/src/vorta/views/repo_tab.py index f531f2fa0..494ff8ee8 100644 --- a/src/vorta/views/repo_tab.py +++ b/src/vorta/views/repo_tab.py @@ -5,6 +5,7 @@ from PyQt5.QtWidgets import QApplication, QLayout, QMenu, QMessageBox from vorta.store.models import ArchiveModel, BackupProfileMixin, RepoModel from vorta.utils import borg_compat, get_asset, get_private_keys, pretty_bytes +from .change_borg_passphrase_dialog import ChangeBorgPassphraseWindow from .repo_add_dialog import AddRepoWindow, ExistingRepoWindow from .ssh_dialog import SSHAddWindow from .utils import get_colored_icon @@ -25,6 +26,9 @@ def __init__(self, parent=None): self.repoRemoveToolbutton.clicked.connect(self.repo_unlink_action) self.copyURLbutton.clicked.connect(self.copy_URL_action) + # passphrase change button + self.changePassbutton.clicked.connect(self.change_borg_passphrase) + # init repo add button self.menuAddRepo = QMenu(self.bAddRepo) @@ -72,6 +76,7 @@ def set_icons(self): self.repoRemoveToolbutton.setIcon(get_colored_icon('unlink')) self.sshKeyToClipboardButton.setIcon(get_colored_icon('copy')) self.copyURLbutton.setIcon(get_colored_icon('copy')) + self.changePassbutton.setIcon(get_colored_icon('lock')) def set_repos(self): self.repoSelector.clear() @@ -124,6 +129,12 @@ def init_repo_stats(self): self.sshComboBox.setEnabled(ssh_enabled) self.sshKeyToClipboardButton.setEnabled(ssh_enabled) + # Disable the change passphrase button if encryption type is not repokey + if repo.encryption in ['repokey', 'repokey-blake2']: + self.changePassbutton.setEnabled(True) + else: + self.changePassbutton.setEnabled(False) + # update stats if repo.unique_csize is not None: self.sizeCompressed.setText(pretty_bytes(repo.unique_csize)) @@ -248,6 +259,12 @@ def add_existing_repo(self): # window.rejected.connect(lambda: self.repoSelector.setCurrentIndex(0)) window.open() + def change_borg_passphrase(self): + window = ChangeBorgPassphraseWindow(self.profile()) + self._window = window # For tests + window.setParent(self, QtCore.Qt.Sheet) + window.open() + def repo_select_action(self): profile = self.profile() profile.repo = self.repoSelector.currentData() From b5c3772256180fcd4b1dd0f6ab4de247f6247d2e Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Sat, 18 Mar 2023 17:13:16 +0530 Subject: [PATCH 02/11] Added tests for passphrase change Signed-off-by: Chirag Aggarwal --- .../views/change_borg_passphrase_dialog.py | 8 +++ .../change_passphrase_stderr.json | 2 + .../change_passphrase_stdout.json | 0 tests/test_repo.py | 55 +++++++++++++++++++ 4 files changed, 65 insertions(+) create mode 100644 tests/borg_json_output/change_passphrase_stderr.json create mode 100644 tests/borg_json_output/change_passphrase_stdout.json diff --git a/src/vorta/views/change_borg_passphrase_dialog.py b/src/vorta/views/change_borg_passphrase_dialog.py index b606d1a42..77ef2614f 100644 --- a/src/vorta/views/change_borg_passphrase_dialog.py +++ b/src/vorta/views/change_borg_passphrase_dialog.py @@ -62,6 +62,7 @@ def set_visibility(self, visible): # No need to add this function to JobsManager because repo is set for the first time def run(self): + # if self.password_listener() and self.validate(): if self.password_listener(): oldPass = self.oldPasswordLineEdit.text() newPass = self.passwordLineEdit.text() @@ -88,6 +89,13 @@ def run_result(self, result): else: self._set_status(self.tr('Unable to change Borg passphrase.')) + def validate(self): + """Check encryption type""" + if self.profile.repo.encryption in ['repokey', 'repokey-blake2']: + return True + self.errorText.setText(translate('utils', 'Encryption type must be repokey.')) + return False + def password_listener(self): '''Validates passwords only if its going to be used''' oldPass = self.oldPasswordLineEdit.text() diff --git a/tests/borg_json_output/change_passphrase_stderr.json b/tests/borg_json_output/change_passphrase_stderr.json new file mode 100644 index 000000000..d73afe5bd --- /dev/null +++ b/tests/borg_json_output/change_passphrase_stderr.json @@ -0,0 +1,2 @@ +{"type": "log_message", "time": 1679134475.3384268, "message": "Key updated", "levelname": "INFO", "name": "borg.archiver"} +{"type": "log_message", "time": 1679134475.338515, "message": "Key location: /Users/chirag/Projects/vorta/repo2", "levelname": "INFO", "name": "borg.archiver"} diff --git a/tests/borg_json_output/change_passphrase_stdout.json b/tests/borg_json_output/change_passphrase_stdout.json new file mode 100644 index 000000000..e69de29bb diff --git a/tests/test_repo.py b/tests/test_repo.py index 5807c8cda..78dace78d 100644 --- a/tests/test_repo.py +++ b/tests/test_repo.py @@ -63,6 +63,41 @@ def test_repo_unlink(qapp, qtbot): assert main.progressText.text() == 'Select a backup repository first.' +def test_passphrase_change_failures(qapp, qtbot): + # Add new repo window + main = qapp.main_window + main.repoTab.change_borg_passphrase() + change_pass_window = main.repoTab._window + qtbot.addWidget(change_pass_window) + + qtbot.keyClicks(change_pass_window.passwordLineEdit, LONG_PASSWORD) + qtbot.keyClicks(change_pass_window.confirmLineEdit, LONG_PASSWORD) + qtbot.mouseClick(change_pass_window.saveButton, QtCore.Qt.LeftButton) + assert change_pass_window.errorText.text().startswith('Old password is required') + + change_pass_window.passwordLineEdit.clear() + change_pass_window.confirmLineEdit.clear() + qtbot.keyClicks(change_pass_window.passwordLineEdit, SHORT_PASSWORD) + qtbot.keyClicks(change_pass_window.confirmLineEdit, SHORT_PASSWORD) + qtbot.keyClicks(change_pass_window.oldPasswordLineEdit, 'a') + qtbot.mouseClick(change_pass_window.saveButton, QtCore.Qt.LeftButton) + assert change_pass_window.errorText.text() == 'Passwords must be greater than 8 characters long.' + + change_pass_window.passwordLineEdit.clear() + change_pass_window.confirmLineEdit.clear() + qtbot.keyClicks(change_pass_window.passwordLineEdit, SHORT_PASSWORD + "1") + qtbot.keyClicks(change_pass_window.confirmLineEdit, SHORT_PASSWORD) + qtbot.mouseClick(change_pass_window.saveButton, QtCore.Qt.LeftButton) + assert change_pass_window.errorText.text() == 'Passwords must be identical and greater than 8 characters long.' + + change_pass_window.passwordLineEdit.clear() + change_pass_window.confirmLineEdit.clear() + qtbot.keyClicks(change_pass_window.passwordLineEdit, LONG_PASSWORD) + qtbot.keyClicks(change_pass_window.confirmLineEdit, SHORT_PASSWORD) + qtbot.mouseClick(change_pass_window.saveButton, QtCore.Qt.LeftButton) + assert change_pass_window.errorText.text() == 'Passwords must be identical.' + + def test_password_autofill(qapp, qtbot): main = qapp.main_window main.repoTab.new_repo() # couldn't click menu @@ -146,3 +181,23 @@ def test_create(qapp, borg_json_output, mocker, qtbot): assert main.createStartBtn.isEnabled() assert main.archiveTab.archiveTable.rowCount() == 3 assert main.scheduleTab.logTableWidget.rowCount() == 1 + + +def test_passphrase_change(qapp, qtbot, mocker, borg_json_output): + main = qapp.main_window + main.repoTab.change_borg_passphrase() + change_pass_window = main.repoTab._window + + qtbot.keyClicks(change_pass_window.oldPasswordLineEdit, LONG_PASSWORD) + qtbot.keyClicks(change_pass_window.passwordLineEdit, LONG_PASSWORD) + qtbot.keyClicks(change_pass_window.confirmLineEdit, LONG_PASSWORD) + + stdout, stderr = borg_json_output('change_passphrase') + print(stdout, stderr) + popen_result = mocker.MagicMock(stdout=stdout, stderr=stderr, returncode=0) + mocker.patch.object(vorta.borg.borg_job, 'Popen', return_value=popen_result) + + change_pass_window.run() + + qtbot.waitUntil(lambda: main.progressText.text().startswith('Borg passphrase changed.'), **pytest._wait_defaults) + assert main.progressText.text() == 'Borg passphrase changed.' From 16d5c6b714585c0fe24153c8e5aa1790e847f15e Mon Sep 17 00:00:00 2001 From: jetchirag Date: Tue, 21 Mar 2023 18:20:39 +0530 Subject: [PATCH 03/11] Update src/vorta/assets/icons/lock.svg Co-authored-by: yfprojects <62463991+real-yfprojects@users.noreply.github.com> --- src/vorta/assets/icons/lock.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vorta/assets/icons/lock.svg b/src/vorta/assets/icons/lock.svg index 4ab58d4eb..bc685d525 100644 --- a/src/vorta/assets/icons/lock.svg +++ b/src/vorta/assets/icons/lock.svg @@ -1 +1 @@ - + From 40be85f8301932bce4afa1ea4e12f5dc167d8a5b Mon Sep 17 00:00:00 2001 From: jetchirag Date: Tue, 21 Mar 2023 18:32:17 +0530 Subject: [PATCH 04/11] Update src/vorta/views/repo_tab.py Co-authored-by: yfprojects <62463991+real-yfprojects@users.noreply.github.com> --- src/vorta/views/repo_tab.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vorta/views/repo_tab.py b/src/vorta/views/repo_tab.py index 494ff8ee8..1f4c18aea 100644 --- a/src/vorta/views/repo_tab.py +++ b/src/vorta/views/repo_tab.py @@ -130,7 +130,7 @@ def init_repo_stats(self): self.sshKeyToClipboardButton.setEnabled(ssh_enabled) # Disable the change passphrase button if encryption type is not repokey - if repo.encryption in ['repokey', 'repokey-blake2']: + if repo.encryption.startswith('repokey'): self.changePassbutton.setEnabled(True) else: self.changePassbutton.setEnabled(False) From 85ff48081c80af67f244f4145fb3d0ac30955846 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Tue, 21 Mar 2023 18:38:26 +0530 Subject: [PATCH 05/11] Added change passphrase button tooltip for disabled state Signed-off-by: Chirag Aggarwal --- src/vorta/views/change_borg_passphrase_dialog.py | 1 - src/vorta/views/repo_tab.py | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vorta/views/change_borg_passphrase_dialog.py b/src/vorta/views/change_borg_passphrase_dialog.py index 77ef2614f..6e1edd5d3 100644 --- a/src/vorta/views/change_borg_passphrase_dialog.py +++ b/src/vorta/views/change_borg_passphrase_dialog.py @@ -60,7 +60,6 @@ def set_visibility(self, visible): self.showHideAction.setIcon(get_colored_icon("eye")) self.showHideAction.setText(self.tr("Show my passwords")) - # No need to add this function to JobsManager because repo is set for the first time def run(self): # if self.password_listener() and self.validate(): if self.password_listener(): diff --git a/src/vorta/views/repo_tab.py b/src/vorta/views/repo_tab.py index 1f4c18aea..f863b8b53 100644 --- a/src/vorta/views/repo_tab.py +++ b/src/vorta/views/repo_tab.py @@ -111,6 +111,7 @@ def init_repo_stats(self): # prepare translations na = self.tr('N/A', "Not available.") no_repo_selected = self.tr("Select a repository first.") + no_repokey_encryption = self.tr("Change Borg Passphrase (Disabled: No Repokey Encryption)") refresh = self.tr("Try refreshing the metadata of any archive.") # set labels @@ -134,6 +135,7 @@ def init_repo_stats(self): self.changePassbutton.setEnabled(True) else: self.changePassbutton.setEnabled(False) + self.changePassbutton.setToolTip(no_repokey_encryption) # update stats if repo.unique_csize is not None: From ddf3ecf724d4ef740e9be64711e3481acd54be6d Mon Sep 17 00:00:00 2001 From: jetchirag Date: Thu, 23 Mar 2023 23:10:59 +0530 Subject: [PATCH 06/11] minor changes; edited validate function to use startwith instead of list Signed-off-by: jetchirag --- src/vorta/views/change_borg_passphrase_dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vorta/views/change_borg_passphrase_dialog.py b/src/vorta/views/change_borg_passphrase_dialog.py index 6e1edd5d3..fe45c18b3 100644 --- a/src/vorta/views/change_borg_passphrase_dialog.py +++ b/src/vorta/views/change_borg_passphrase_dialog.py @@ -90,7 +90,7 @@ def run_result(self, result): def validate(self): """Check encryption type""" - if self.profile.repo.encryption in ['repokey', 'repokey-blake2']: + if self.profile.repo.encryption.startswith('repokey'): return True self.errorText.setText(translate('utils', 'Encryption type must be repokey.')) return False From f96ca0f1d6ff5a9a08b56c61de6543796f602f93 Mon Sep 17 00:00:00 2001 From: jetchirag Date: Fri, 24 Mar 2023 22:56:23 +0530 Subject: [PATCH 07/11] Dynamically get version requirement in message for change-passphrase Signed-off-by: jetchirag --- src/vorta/borg/change_passphrase.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/vorta/borg/change_passphrase.py b/src/vorta/borg/change_passphrase.py index 8f2df7dcf..04f440ead 100644 --- a/src/vorta/borg/change_passphrase.py +++ b/src/vorta/borg/change_passphrase.py @@ -1,4 +1,5 @@ from typing import Any, Dict +from vorta.borg._compatibility import MIN_BORG_FOR_FEATURE from vorta.config import LOG_DIR from vorta.i18n import trans_late, translate from vorta.utils import borg_compat @@ -41,7 +42,9 @@ def prepare(cls, profile, oldPass, newPass): if not borg_compat.check('CHANGE_PASSPHRASE'): ret['ok'] = False - ret['message'] = trans_late('messages', 'This feature needs Borg 1.1.0 or higher.') + ret['message'] = trans_late( + 'messages', 'This feature needs Borg {} or higher.'.format(MIN_BORG_FOR_FEATURE['CHANGE_PASSPHRASE']) + ) return ret cmd = ['borg', '--info', '--log-json', 'key', 'change-passphrase'] From a58db2d703b5b8e6b429802785c13e147f96081e Mon Sep 17 00:00:00 2001 From: jetchirag Date: Fri, 31 Mar 2023 23:45:04 +0530 Subject: [PATCH 08/11] Changed Disabled Change Passphrase tooltip Co-authored-by: yfprojects <62463991+real-yfprojects@users.noreply.github.com> --- src/vorta/views/repo_tab.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vorta/views/repo_tab.py b/src/vorta/views/repo_tab.py index c5b620327..6ef22465a 100644 --- a/src/vorta/views/repo_tab.py +++ b/src/vorta/views/repo_tab.py @@ -111,7 +111,7 @@ def init_repo_stats(self): # prepare translations na = self.tr('N/A', "Not available.") no_repo_selected = self.tr("Select a repository first.") - no_repokey_encryption = self.tr("Change Borg Passphrase (Disabled: No Repokey Encryption)") + no_repokey_encryption = self.tr("Change Borg Passphrase (Repokey encryption needed)") refresh = self.tr("Try refreshing the metadata of any archive.") # set labels From 8649f4dc91ab4462523c1a897594aa3f929107bf Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Fri, 31 Mar 2023 14:48:55 -0400 Subject: [PATCH 09/11] Save password to keyring, remove old password field Signed-off-by: Chirag Aggarwal --- src/vorta/assets/UI/changeborgpass.ui | 23 ++----------------- src/vorta/borg/change_passphrase.py | 14 +++++++++-- .../views/change_borg_passphrase_dialog.py | 9 +------- tests/test_repo.py | 7 ------ 4 files changed, 15 insertions(+), 38 deletions(-) diff --git a/src/vorta/assets/UI/changeborgpass.ui b/src/vorta/assets/UI/changeborgpass.ui index 4536c11a3..794483de1 100644 --- a/src/vorta/assets/UI/changeborgpass.ui +++ b/src/vorta/assets/UI/changeborgpass.ui @@ -7,7 +7,7 @@ 0 0 466 - 274 + 174 @@ -44,7 +44,7 @@ 5 - 5 + 30 5 @@ -53,25 +53,6 @@ 30 - - - - - Old Passphrase: - - - - - - - true - - - QLineEdit::Password - - - - diff --git a/src/vorta/borg/change_passphrase.py b/src/vorta/borg/change_passphrase.py index 04f440ead..88bef1ede 100644 --- a/src/vorta/borg/change_passphrase.py +++ b/src/vorta/borg/change_passphrase.py @@ -2,6 +2,7 @@ from vorta.borg._compatibility import MIN_BORG_FOR_FEATURE from vorta.config import LOG_DIR from vorta.i18n import trans_late, translate +from vorta.store.models import RepoModel from vorta.utils import borg_compat from .borg_job import BorgJob @@ -33,7 +34,7 @@ def finished_event(self, result: Dict[str, Any]): self.app.backup_progress_event.emit(self.tr('Borg passphrase changed.')) @classmethod - def prepare(cls, profile, oldPass, newPass): + def prepare(cls, profile, newPass): ret = super().prepare(profile) if not ret['ok']: return ret @@ -50,10 +51,19 @@ def prepare(cls, profile, oldPass, newPass): cmd = ['borg', '--info', '--log-json', 'key', 'change-passphrase'] cmd.append(f'{profile.repo.url}') - ret['password'] = oldPass ret['additional_env'] = {'BORG_NEW_PASSPHRASE': newPass} ret['ok'] = True ret['cmd'] = cmd return ret + + def process_result(self, result): + if result['returncode'] == 0: + # Change passphrase in keyring + repo = RepoModel.get(url=result['params']['repo_url']) + if repo.encryption != 'none': + self.keyring.set_password( + "vorta-repo", repo.url, result['params']['additional_env']['BORG_NEW_PASSPHRASE'] + ) + repo.save() diff --git a/src/vorta/views/change_borg_passphrase_dialog.py b/src/vorta/views/change_borg_passphrase_dialog.py index fe45c18b3..8ea6fc549 100644 --- a/src/vorta/views/change_borg_passphrase_dialog.py +++ b/src/vorta/views/change_borg_passphrase_dialog.py @@ -63,10 +63,9 @@ def set_visibility(self, visible): def run(self): # if self.password_listener() and self.validate(): if self.password_listener(): - oldPass = self.oldPasswordLineEdit.text() newPass = self.passwordLineEdit.text() - params = BorgChangePassJob.prepare(self.profile, oldPass, newPass) + params = BorgChangePassJob.prepare(self.profile, newPass) if params['ok']: self.saveButton.setEnabled(False) job = BorgChangePassJob(params['cmd'], params) @@ -97,15 +96,9 @@ def validate(self): def password_listener(self): '''Validates passwords only if its going to be used''' - oldPass = self.oldPasswordLineEdit.text() firstPass = self.passwordLineEdit.text() secondPass = self.confirmLineEdit.text() - # Since borg originally does not have minimum character requirement - if len(oldPass) < 1: - self.errorText.setText(translate('utils', 'Old password is required.')) - return False - msg = validate_passwords(firstPass, secondPass) self.errorText.setText(translate('utils', msg)) return not bool(msg) diff --git a/tests/test_repo.py b/tests/test_repo.py index d91b81949..17bce6884 100644 --- a/tests/test_repo.py +++ b/tests/test_repo.py @@ -68,16 +68,10 @@ def test_passphrase_change_failures(qapp, qtbot): change_pass_window = main.repoTab._window qtbot.addWidget(change_pass_window) - qtbot.keyClicks(change_pass_window.passwordLineEdit, LONG_PASSWORD) - qtbot.keyClicks(change_pass_window.confirmLineEdit, LONG_PASSWORD) - qtbot.mouseClick(change_pass_window.saveButton, QtCore.Qt.LeftButton) - assert change_pass_window.errorText.text().startswith('Old password is required') - change_pass_window.passwordLineEdit.clear() change_pass_window.confirmLineEdit.clear() qtbot.keyClicks(change_pass_window.passwordLineEdit, SHORT_PASSWORD) qtbot.keyClicks(change_pass_window.confirmLineEdit, SHORT_PASSWORD) - qtbot.keyClicks(change_pass_window.oldPasswordLineEdit, 'a') qtbot.mouseClick(change_pass_window.saveButton, QtCore.Qt.LeftButton) assert change_pass_window.errorText.text() == 'Passwords must be greater than 8 characters long.' @@ -186,7 +180,6 @@ def test_passphrase_change(qapp, qtbot, mocker, borg_json_output): main.repoTab.change_borg_passphrase() change_pass_window = main.repoTab._window - qtbot.keyClicks(change_pass_window.oldPasswordLineEdit, LONG_PASSWORD) qtbot.keyClicks(change_pass_window.passwordLineEdit, LONG_PASSWORD) qtbot.keyClicks(change_pass_window.confirmLineEdit, LONG_PASSWORD) From 8c3344e438eb82092c1117a5d0700278c71d3f57 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Thu, 3 Aug 2023 19:52:07 -0400 Subject: [PATCH 10/11] Lint fix Signed-off-by: Chirag Aggarwal --- src/vorta/borg/change_passphrase.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vorta/borg/change_passphrase.py b/src/vorta/borg/change_passphrase.py index 88bef1ede..4b15c4180 100644 --- a/src/vorta/borg/change_passphrase.py +++ b/src/vorta/borg/change_passphrase.py @@ -1,9 +1,11 @@ from typing import Any, Dict + from vorta.borg._compatibility import MIN_BORG_FOR_FEATURE from vorta.config import LOG_DIR from vorta.i18n import trans_late, translate from vorta.store.models import RepoModel from vorta.utils import borg_compat + from .borg_job import BorgJob From d9571d2d2847b7473254bf76c6a46fd162aef27d Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Tue, 8 Aug 2023 14:03:50 -0400 Subject: [PATCH 11/11] Set window title Signed-off-by: Chirag Aggarwal --- src/vorta/assets/UI/changeborgpass.ui | 17 +---------------- src/vorta/views/repo_tab.py | 1 + 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/src/vorta/assets/UI/changeborgpass.ui b/src/vorta/assets/UI/changeborgpass.ui index 859f4d481..efac139ae 100644 --- a/src/vorta/assets/UI/changeborgpass.ui +++ b/src/vorta/assets/UI/changeborgpass.ui @@ -9,22 +9,7 @@ 0 - - - - - true - - - - Change Borg Passphrase - - - - Qt::AlignCenter - - - + diff --git a/src/vorta/views/repo_tab.py b/src/vorta/views/repo_tab.py index 78b928278..5037dd5a4 100644 --- a/src/vorta/views/repo_tab.py +++ b/src/vorta/views/repo_tab.py @@ -269,6 +269,7 @@ def add_existing_repo(self): def change_borg_passphrase(self): window = ChangeBorgPassphraseWindow(self.profile()) self._window = window # For tests + window.setWindowTitle(self.tr("Change Borg Passphrase")) window.setParent(self, QtCore.Qt.WindowType.Sheet) window.open()