diff --git a/.idea/revpicommander.iml b/.idea/revpicommander.iml
index bcd0f63..0d0b203 100644
--- a/.idea/revpicommander.iml
+++ b/.idea/revpicommander.iml
@@ -4,6 +4,7 @@
+
diff --git a/MANIFEST.in b/MANIFEST.in
index 979d6ef..53cd4a6 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -9,5 +9,4 @@ include README.md
include requirements.txt
include setup.iss
include setup.py
-include stdeb.cfg
include translate.pro
diff --git a/requirements.txt b/requirements.txt
index 5b11c16..571a1be 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -4,4 +4,5 @@ revpimodio2>=2.5.6
zeroconf>=0.24.4
setuptools>=65.6.3
wheel
-paramiko>=2.12.0
\ No newline at end of file
+paramiko>=2.12.0
+keyring>=23.13.1
\ No newline at end of file
diff --git a/setup.py b/setup.py
index 1292d52..d4263f7 100644
--- a/setup.py
+++ b/setup.py
@@ -15,6 +15,7 @@ setup(
include_package_data=True,
install_requires=[
+ "keyring",
"PyQt5",
"revpimodio2",
"zeroconf"
diff --git a/src/revpicommander/helper.py b/src/revpicommander/helper.py
index ce7a02e..2808edc 100644
--- a/src/revpicommander/helper.py
+++ b/src/revpicommander/helper.py
@@ -20,7 +20,7 @@ from paramiko.ssh_exception import AuthenticationException
from . import proginit as pi
from .ssh_tunneling.server import SSHLocalTunnel
-from .sshauth import SSHAuth, SSHAuthType
+from .sshauth import SSHAuth
settings = QtCore.QSettings("revpimodio.org", "RevPiCommander")
"""Global application settings."""
@@ -304,11 +304,11 @@ class ConnectionManager(QtCore.QThread):
self.xml_funcs.clear()
self.xml_mode = -1
- def pyload_connect(self, settings: RevPiSettings, parent=None) -> bool:
+ def pyload_connect(self, revpi_settings: RevPiSettings, parent=None) -> bool:
"""
Create a new connection from settings object.
- :param settings: Revolution Pi saved connection settings
+ :param revpi_settings: Revolution Pi saved connection settings
:param parent: Qt parent window for dialog positioning
:return: True, if the connection was successfully established
"""
@@ -322,10 +322,16 @@ class ConnectionManager(QtCore.QThread):
socket.setdefaulttimeout(2)
- if settings.ssh_use_tunnel:
+ if revpi_settings.ssh_use_tunnel:
while True:
- diag_ssh_auth = SSHAuth(SSHAuthType.PASS, parent)
- diag_ssh_auth.username = settings.ssh_user
+ diag_ssh_auth = SSHAuth(
+ revpi_settings.ssh_user,
+ "{0}.{1}_{2}".format(
+ settings.applicationName(),
+ settings.organizationName(),
+ revpi_settings.internal_id),
+ parent,
+ )
if not diag_ssh_auth.exec() == QtWidgets.QDialog.Accepted:
self._clear_settings()
return False
@@ -333,14 +339,15 @@ class ConnectionManager(QtCore.QThread):
ssh_user = diag_ssh_auth.username
ssh_pass = diag_ssh_auth.password
ssh_tunnel_server = SSHLocalTunnel(
- settings.port,
- settings.address,
- settings.ssh_port
+ revpi_settings.port,
+ revpi_settings.address,
+ revpi_settings.ssh_port
)
try:
ssh_tunnel_port = ssh_tunnel_server.connect_by_credentials(ssh_user, ssh_pass)
break
except AuthenticationException:
+ diag_ssh_auth.remove_saved_password()
QtWidgets.QMessageBox.critical(
parent, self.tr("Error"), self.tr(
"The combination of username and password was rejected from the SSH server.\n\n"
@@ -360,7 +367,7 @@ class ConnectionManager(QtCore.QThread):
sp = ServerProxy("http://127.0.0.1:{0}".format(ssh_tunnel_port))
else:
- sp = ServerProxy("http://{0}:{1}".format(settings.address, settings.port))
+ sp = ServerProxy("http://{0}:{1}".format(revpi_settings.address, revpi_settings.port))
# Load values and test connection to Revolution Pi
try:
@@ -371,7 +378,7 @@ class ConnectionManager(QtCore.QThread):
pi.logger.exception(e)
self.connection_error_observed.emit(str(e))
- if not settings.ssh_use_tunnel:
+ if not revpi_settings.ssh_use_tunnel:
# todo: Change message, that user can use ssh
QtWidgets.QMessageBox.critical(
parent, self.tr("Error"), self.tr(
@@ -386,19 +393,19 @@ class ConnectionManager(QtCore.QThread):
return False
- self.settings = settings
+ self.settings = revpi_settings
self.ssh_pass = ssh_pass
self.pyload_version = pyload_version
self.xml_funcs = xml_funcs
self.xml_mode = xml_mode
with self._lck_cli:
- socket.setdefaulttimeout(settings.timeout)
+ socket.setdefaulttimeout(revpi_settings.timeout)
self.ssh_tunnel_server = ssh_tunnel_server
self._cli = sp
self._cli_connect.put_nowait((
- "127.0.0.1" if settings.ssh_use_tunnel else settings.address,
- ssh_tunnel_port if settings.ssh_use_tunnel else settings.port
+ "127.0.0.1" if revpi_settings.ssh_use_tunnel else revpi_settings.address,
+ ssh_tunnel_port if revpi_settings.ssh_use_tunnel else revpi_settings.port
))
self.connection_established.emit()
diff --git a/src/revpicommander/locale/revpicommander_de.qm b/src/revpicommander/locale/revpicommander_de.qm
index 77e78b0..a8afc7e 100644
Binary files a/src/revpicommander/locale/revpicommander_de.qm and b/src/revpicommander/locale/revpicommander_de.qm differ
diff --git a/src/revpicommander/locale/revpicommander_de.ts b/src/revpicommander/locale/revpicommander_de.ts
index f26bef3..a154800 100644
--- a/src/revpicommander/locale/revpicommander_de.ts
+++ b/src/revpicommander/locale/revpicommander_de.ts
@@ -101,62 +101,62 @@ Nicht gespeicherte Änderunen gehen verloren
ConnectionManager
-
+ SIMULATINGSIMULATION
-
+ NOT CONNECTEDNICHT VERBUNDEN
-
+ SERVER ERRORSERVER FEHLER
-
+ RUNNINGLÄUFT
-
+ PLC FILE NOT FOUNDSPS PROGRAMM NICHT GEFUNDEN
-
+ NOT RUNNING (NO STATUS)LÄUFT NICHT (KEIN STATUS)
-
+ PROGRAM KILLEDPROGRAMM GETÖTET
-
+ PROGRAM TERMEDPROGRAMM BEENDET
-
+ NOT RUNNINGLÄUFT NICHT
-
+ FINISHED WITH CODE {0}BEENDET MIT CODE {0}
-
+ ErrorFehler
-
+ The combination of username and password was rejected from the SSH server.
Try again.
@@ -165,7 +165,7 @@ Try again.
Bitte erneut versuchen.
-
+ Could not establish a SSH connection to server:
{0}
@@ -174,7 +174,7 @@ Bitte erneut versuchen.
{0}
-
+ Can not connect to RevPi XML-RPC Service!
This could have the following reasons: The RevPi is not online, the XML-RPC service is not running / bind to localhost or the ACL permission is not set for your IP!!!
@@ -401,7 +401,7 @@ Dies kann aus der Textbox oben kopiert werden.
Can not start the simulator! Maybe the piCtory file is corrupt or you have no write permissions for '{0}'.
- Kann Simulator nicht starten! Vielleicht ist die piCtory Datei defekt oder es gibt keine Schreibberechtigung für '{0}`.
+ Kann Simulator nicht starten! Vielleicht ist die piCtory Datei defekt oder es gibt keine Schreibberechtigung für '{0}'.
@@ -952,6 +952,27 @@ Dies ist kein Fehler, wenn das SPS Startprogramm bereits auf dem Rev Pi ist. Pr
{0}.
+
+ SSHAuth
+
+
+ Could not save password
+ Konnte Kennwort nicht speichern
+
+
+
+ Could not save password to operating systems password save.
+
+Maybe your operating system does not support saving passwords. This could be due to missing libraries or programs.
+
+This is not an error of RevPi Commander.
+ Konnte das Kennwort nicht im Kennwortspeicher des Betriebssystems speichern.
+
+Vielleicht untersützt das Betriebssystem keine Kennwortspeicherung. Dies könnte an fehlenden Bibliotheken oder Programmen liegen.
+
+Dies ist kein Fehler von RevPi Commander.
+
+Simulator
@@ -1672,6 +1693,16 @@ applicable law.
SSH password:SSH Passwort:
+
+
+ Username and password will be saved in secured operating systems's password storage.
+ Benutzername und Kennwort werden im Passwortspeicher vom Betriebssystem gesichert.
+
+
+
+ Save username and password
+ Benutzername und Kennwort merken
+ wid_debugcontrol
diff --git a/src/revpicommander/locale/revpicommander_en.qm b/src/revpicommander/locale/revpicommander_en.qm
new file mode 100644
index 0000000..937ea3e
Binary files /dev/null and b/src/revpicommander/locale/revpicommander_en.qm differ
diff --git a/src/revpicommander/sshauth.py b/src/revpicommander/sshauth.py
index fd0f531..23baaa0 100644
--- a/src/revpicommander/sshauth.py
+++ b/src/revpicommander/sshauth.py
@@ -4,36 +4,88 @@ __author__ = "Sven Sager"
__copyright__ = "Copyright (C) 2023 Sven Sager"
__license__ = "GPLv3"
-from enum import Enum
+from logging import getLogger
+import keyring
from PyQt5 import QtWidgets
+from keyring.errors import KeyringError
-from revpicommander.ui.sshauth_ui import Ui_diag_sshauth
+from .ui.sshauth_ui import Ui_diag_sshauth
-
-class SSHAuthType(Enum):
- PASS = "pass"
- KEYS = "keys"
+log = getLogger()
class SSHAuth(QtWidgets.QDialog, Ui_diag_sshauth):
- """Version information window."""
- def __init__(self, auth_type: SSHAuthType, parent=None):
+ def __init__(self, user_name="", service_name: str = None, parent=None):
+ """
+ Ask the user for username and password or use saved entries.
+
+ If you want to use the operating system's password storage, you have
+ to set a 'service_name'. The value must be unique for your application
+ or for each user, if the username is the same.
+
+ :param user_name: Preset username, also used to check password save
+ :param service_name: Identity to save passwords in os's password save
+ :param parent: Qt parent for this dialog
+ """
+ log.debug("SSHAuth.__init__")
+
super(SSHAuth, self).__init__(parent)
self.setupUi(self)
- self.wid_password.setVisible(auth_type is SSHAuthType.PASS)
- self.wid_keys.setVisible(auth_type is SSHAuthType.KEYS)
+ self._service_name = service_name
+ self.cbx_save_password.setVisible(bool(service_name))
+ self.txt_username.setText(user_name)
+
+ def accept(self) -> None:
+ log.debug("SSHAuth.accept")
+
+ if self._service_name and self.cbx_save_password.isChecked():
+ try:
+ keyring.set_password(self._service_name, self.username, self.password)
+ except KeyringError as e:
+ log.error(e)
+ QtWidgets.QMessageBox.warning(
+ self, self.tr("Could not save password"), self.tr(
+ "Could not save password to operating systems password save.\n\n"
+ "Maybe your operating system does not support saving passwords. "
+ "This could be due to missing libraries or programs.\n\n"
+ "This is not an error of RevPi Commander."
+ )
+ )
+ super().accept()
+
+ def exec(self) -> int:
+ log.debug("SSHAuth.exec")
+
+ if self._service_name:
+ try:
+ saved_password = keyring.get_password(self._service_name, self.username)
+ if saved_password:
+ self.txt_password.setText(saved_password)
+ return QtWidgets.QDialog.Accepted
+ except KeyringError as e:
+ log.error(e)
+
+ return super().exec()
+
+ def remove_saved_password(self) -> None:
+ """Remove saved password."""
+ log.debug("SSHAuth.remove_saved_password")
+
+ if self._service_name:
+ try:
+ keyring.delete_password(self._service_name, self.username)
+ except KeyringError as e:
+ log.error(e)
@property
def password(self) -> str:
+ """Get the saved or entered password."""
return self.txt_password.text()
@property
def username(self) -> str:
+ """Get the entered username."""
return self.txt_username.text()
-
- @username.setter
- def username(self, value: str):
- self.txt_username.setText(value)
diff --git a/src/revpicommander/ui/sshauth_ui.py b/src/revpicommander/ui/sshauth_ui.py
index 4df10c6..5e1f17f 100644
--- a/src/revpicommander/ui/sshauth_ui.py
+++ b/src/revpicommander/ui/sshauth_ui.py
@@ -15,7 +15,7 @@ class Ui_diag_sshauth(object):
def setupUi(self, diag_sshauth):
diag_sshauth.setObjectName("diag_sshauth")
diag_sshauth.setWindowModality(QtCore.Qt.ApplicationModal)
- diag_sshauth.resize(275, 170)
+ diag_sshauth.resize(363, 163)
self.verticalLayout = QtWidgets.QVBoxLayout(diag_sshauth)
self.verticalLayout.setObjectName("verticalLayout")
self.wid_password = QtWidgets.QWidget(diag_sshauth)
@@ -36,18 +36,18 @@ class Ui_diag_sshauth(object):
self.txt_username.setObjectName("txt_username")
self.formLayout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.txt_username)
self.verticalLayout.addWidget(self.wid_password)
- self.wid_keys = QtWidgets.QWidget(diag_sshauth)
- self.wid_keys.setObjectName("wid_keys")
- self.verticalLayout.addWidget(self.wid_keys)
- self.buttonBox = QtWidgets.QDialogButtonBox(diag_sshauth)
- self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
- self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
- self.buttonBox.setObjectName("buttonBox")
- self.verticalLayout.addWidget(self.buttonBox)
+ self.cbx_save_password = QtWidgets.QCheckBox(diag_sshauth)
+ self.cbx_save_password.setObjectName("cbx_save_password")
+ self.verticalLayout.addWidget(self.cbx_save_password)
+ self.btn_box = QtWidgets.QDialogButtonBox(diag_sshauth)
+ self.btn_box.setOrientation(QtCore.Qt.Horizontal)
+ self.btn_box.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
+ self.btn_box.setObjectName("btn_box")
+ self.verticalLayout.addWidget(self.btn_box)
self.retranslateUi(diag_sshauth)
- self.buttonBox.accepted.connect(diag_sshauth.accept) # type: ignore
- self.buttonBox.rejected.connect(diag_sshauth.reject) # type: ignore
+ self.btn_box.accepted.connect(diag_sshauth.accept) # type: ignore
+ self.btn_box.rejected.connect(diag_sshauth.reject) # type: ignore
QtCore.QMetaObject.connectSlotsByName(diag_sshauth)
def retranslateUi(self, diag_sshauth):
@@ -55,6 +55,8 @@ class Ui_diag_sshauth(object):
diag_sshauth.setWindowTitle(_translate("diag_sshauth", "SSH authentication"))
self.lbl_username.setText(_translate("diag_sshauth", "SSH username:"))
self.lbl_password.setText(_translate("diag_sshauth", "SSH password:"))
+ self.cbx_save_password.setToolTip(_translate("diag_sshauth", "Username and password will be saved in secured operating systems\'s password storage."))
+ self.cbx_save_password.setText(_translate("diag_sshauth", "Save username and password"))
if __name__ == "__main__":
diff --git a/stdeb.cfg b/stdeb.cfg
deleted file mode 100644
index abae509..0000000
--- a/stdeb.cfg
+++ /dev/null
@@ -1,6 +0,0 @@
-[DEFAULT]
-Debian-Version=1
-Depends3=python3-pyqt5, python3-revpimodio2 (>= 2.5.0), python3-zeroconf (>= 0.24.4)
-Section=universe/x11
-Suite=stable
-X-Python3-Version: >=3.4
diff --git a/translate.pro b/translate.pro
index 8f935a0..2f7a861 100644
--- a/translate.pro
+++ b/translate.pro
@@ -11,6 +11,7 @@ SOURCES = src/revpicommander/aclmanager.py \
src/revpicommander/revpiplclist.py \
src/revpicommander/revpiprogram.py \
src/revpicommander/simulator.py \
+ src/revpicommander/sshauth.py \
src/revpicommander/revpicommander.py
FORMS = ui_dev/aclmanager.ui \
diff --git a/ui_dev/sshauth.ui b/ui_dev/sshauth.ui
index e690765..3b821e0 100644
--- a/ui_dev/sshauth.ui
+++ b/ui_dev/sshauth.ui
@@ -9,8 +9,8 @@
00
- 275
- 170
+ 363
+ 163
@@ -48,10 +48,17 @@
-
+
+
+ Username and password will be saved in secured operating systems's password storage.
+
+
+ Save username and password
+
+
-
+ Qt::Horizontal
@@ -65,7 +72,7 @@
- buttonBox
+ btn_boxaccepted()diag_sshauthaccept()
@@ -81,7 +88,7 @@
- buttonBox
+ btn_boxrejected()diag_sshauthreject()