From 15fa46ff856204c6c1c829a2fd6e608c3c0a0656 Mon Sep 17 00:00:00 2001 From: Ted Lawson Date: Tue, 26 Sep 2023 06:22:54 -0700 Subject: [PATCH] Improve SSH key process. By @bigtedde (#1802) --- src/vorta/views/repo_tab.py | 12 ++++++++--- src/vorta/views/ssh_dialog.py | 16 +++++++++------ tests/unit/test_repo.py | 38 ++++++++++++++++++++++++++++++----- 3 files changed, 52 insertions(+), 14 deletions(-) diff --git a/src/vorta/views/repo_tab.py b/src/vorta/views/repo_tab.py index 96b614b0..3b04ffa2 100644 --- a/src/vorta/views/repo_tab.py +++ b/src/vorta/views/repo_tab.py @@ -198,10 +198,17 @@ class RepoTab(RepoBase, RepoUI, BackupProfileMixin): ssh_add_window = SSHAddWindow() self._window = ssh_add_window # For tests ssh_add_window.setParent(self, QtCore.Qt.WindowType.Sheet) - ssh_add_window.accepted.connect(self.init_ssh) - # ssh_add_window.rejected.connect(lambda: self.sshComboBox.setCurrentIndex(0)) + ssh_add_window.rejected.connect(self.init_ssh) + ssh_add_window.failure.connect(self.create_ssh_key_failure) ssh_add_window.open() + def create_ssh_key_failure(self, exit_code): + msg = QMessageBox() + msg.setStandardButtons(QMessageBox.StandardButton.Ok) + msg.setParent(self, QtCore.Qt.WindowType.Sheet) + msg.setText(self.tr(f'Error during key generation. Exited with code {exit_code}.')) + msg.show() + def ssh_copy_to_clipboard_action(self): msg = QMessageBox() msg.setStandardButtons(QMessageBox.StandardButton.Ok) @@ -223,7 +230,6 @@ class RepoTab(RepoBase, RepoUI, BackupProfileMixin): "Use it to set up remote repo permissions." ) ) - else: msg.setText(self.tr("Could not find public key.")) else: diff --git a/src/vorta/views/ssh_dialog.py b/src/vorta/views/ssh_dialog.py index ab310284..baf1c4db 100644 --- a/src/vorta/views/ssh_dialog.py +++ b/src/vorta/views/ssh_dialog.py @@ -1,7 +1,7 @@ import os -from PyQt6 import uic -from PyQt6.QtCore import QProcess, Qt +from PyQt6 import QtCore, uic +from PyQt6.QtCore import QProcess, Qt, pyqtSlot from PyQt6.QtWidgets import QApplication, QDialogButtonBox from ..utils import get_asset @@ -11,6 +11,8 @@ SSHAddUI, SSHAddBase = uic.loadUiType(uifile) class SSHAddWindow(SSHAddBase, SSHAddUI): + failure = QtCore.pyqtSignal(int) + def __init__(self): super().__init__() self.setupUi(self) @@ -68,15 +70,17 @@ class SSHAddWindow(SSHAddBase, SSHAddUI): self.sshproc.finished.connect(self.generate_key_result) self.sshproc.start('ssh-keygen', ['-t', format, '-b', length, '-f', output_path, '-N', '']) - def generate_key_result(self, exitCode, exitStatus): - if exitCode == 0: + @pyqtSlot(int) + def generate_key_result(self, exit_code): + if exit_code == 0: output_path = os.path.expanduser(self.outputFileTextBox.text()) pub_key = open(output_path + '.pub').read().strip() clipboard = QApplication.clipboard() clipboard.setText(pub_key) - self.errors.setText(self.tr('New key was copied to clipboard and written to %s.') % output_path) + self.reject() else: - self.errors.setText(self.tr('Error during key generation.')) + self.reject() + self.failure.emit(exit_code) def get_values(self): return { diff --git a/tests/unit/test_repo.py b/tests/unit/test_repo.py index c20f4be8..8cbb29a3 100644 --- a/tests/unit/test_repo.py +++ b/tests/unit/test_repo.py @@ -131,13 +131,13 @@ def test_repo_add_success(qapp, qtbot, mocker, borg_json_output): assert tab.repoSelector.currentText() == f"{test_repo_name} - {test_repo_url}" -def test_ssh_dialog(qapp, qtbot, tmpdir): +def test_ssh_dialog_success(qapp, qtbot, mocker, tmpdir): main = qapp.main_window tab = main.repoTab qtbot.mouseClick(tab.bAddSSHKey, QtCore.Qt.MouseButton.LeftButton) ssh_dialog = tab._window - + ssh_dialog_closed = mocker.spy(ssh_dialog, 'reject') ssh_dir = tmpdir key_tmpfile = ssh_dir.join("id_rsa-test") pub_tmpfile = ssh_dir.join("id_rsa-test.pub") @@ -145,18 +145,46 @@ def test_ssh_dialog(qapp, qtbot, tmpdir): ssh_dialog.outputFileTextBox.setText(key_tmpfile_full) ssh_dialog.generate_key() - # Ensure new key files exist - qtbot.waitUntil(lambda: ssh_dialog.errors.text().startswith('New key was copied'), **pytest._wait_defaults) + # Ensure new key file was created + qtbot.waitUntil(lambda: ssh_dialog_closed.called, **pytest._wait_defaults) assert len(ssh_dir.listdir()) == 2 + # Ensure new key is populated in SSH combobox + mocker.patch('os.path.expanduser', return_value=str(tmpdir)) + tab.init_ssh() + assert tab.sshComboBox.count() == 2 + # Ensure valid keys were created key_tmpfile_content = key_tmpfile.read() assert key_tmpfile_content.startswith('-----BEGIN OPENSSH PRIVATE KEY-----') pub_tmpfile_content = pub_tmpfile.read() assert pub_tmpfile_content.startswith('ssh-ed25519') + +def test_ssh_dialog_failure(qapp, qtbot, mocker, monkeypatch, tmpdir): + main = qapp.main_window + tab = main.repoTab + monkeypatch.setattr(QMessageBox, "show", lambda *args: True) + failure_message = mocker.spy(tab, "create_ssh_key_failure") + + qtbot.mouseClick(tab.bAddSSHKey, QtCore.Qt.MouseButton.LeftButton) + ssh_dialog = tab._window + ssh_dir = tmpdir + key_tmpfile = ssh_dir.join("invalid///===for_testing") + key_tmpfile_full = os.path.join(key_tmpfile.dirname, key_tmpfile.basename) + ssh_dialog.outputFileTextBox.setText(key_tmpfile_full) ssh_dialog.generate_key() - qtbot.waitUntil(lambda: ssh_dialog.errors.text().startswith('Key file already'), **pytest._wait_defaults) + + qtbot.waitUntil(lambda: failure_message.called, **pytest._wait_defaults) + failure_message.assert_called_once() + + # Ensure no new ney file was created + assert len(ssh_dir.listdir()) == 0 + + # Ensure no new key file in combo box + mocker.patch('os.path.expanduser', return_value=str(tmpdir)) + tab.init_ssh() + assert tab.sshComboBox.count() == 1 def test_create(qapp, borg_json_output, mocker, qtbot):