diff --git a/ui/opensnitch/dialogs/preferences.py b/ui/opensnitch/dialogs/preferences.py index 360230fb..c9df05ec 100644 --- a/ui/opensnitch/dialogs/preferences.py +++ b/ui/opensnitch/dialogs/preferences.py @@ -2,6 +2,7 @@ import sys import time import os import json +import stat from PyQt5 import QtCore, QtGui, uic, QtWidgets from PyQt5.QtCore import QCoreApplication as QC @@ -13,7 +14,7 @@ from opensnitch.utils import Message, QuickHelp, Themes, Icons, languages from opensnitch.utils.xdg import Autostart from opensnitch.notifications import DesktopNotifications -from opensnitch import ui_pb2 +from opensnitch import ui_pb2, auth DIALOG_UI_PATH = "%s/../res/preferences.ui" % os.path.dirname(sys.modules[__name__].__file__) class PreferencesDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]): @@ -37,6 +38,7 @@ class PreferencesDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]): self._themes = Themes.instance() self._saved_theme = "" self._restart_msg = QC.translate("preferences", "Restart the GUI in order changes to take effect") + self._changes_needs_restart = None self._cfg = Config.get() self._nodes = Nodes.instance() @@ -72,6 +74,13 @@ class PreferencesDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]): self.radioSysNotifs.clicked.connect(self._cb_radio_system_notifications) self.helpButton.setToolTipDuration(30 * 1000) + self.comboAuthType.currentIndexChanged.connect(self._cb_combo_auth_type_changed) + self.comboAuthType.setItemData(0, auth.Simple) + self.comboAuthType.setItemData(1, auth.TLSSimple) + self.comboAuthType.setItemData(2, auth.TLSMutual) + self.lineCertFile.textChanged.connect(self._cb_line_certs_changed) + self.lineCertKeyFile.textChanged.connect(self._cb_line_certs_changed) + self.comboUIRules.currentIndexChanged.connect(self._cb_combo_uirules_changed) if QtGui.QIcon.hasThemeIcon("emblem-default"): @@ -109,6 +118,7 @@ class PreferencesDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]): super(PreferencesDialog, self).showEvent(event) try: + self._changes_needs_restart = None self._settingsSaved = False self._reset_status_message() self._hide_status_label() @@ -207,13 +217,22 @@ class PreferencesDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]): self.comboUIDuration.setCurrentIndex(self._default_duration) self.comboUIDialogPos.setCurrentIndex(self._cfg.getInt(self._cfg.DEFAULT_POPUP_POSITION)) + self.checkAutostart.setChecked(self._autostart.isEnabled()) + maxmsgsize = self._cfg.getSettings(Config.DEFAULT_SERVER_MAX_MESSAGE_LENGTH) if maxmsgsize: self.comboGrpcMsgSize.setCurrentText(maxmsgsize) else: self.comboGrpcMsgSize.setCurrentIndex(0) - self.checkAutostart.setChecked(self._autostart.isEnabled()) + self.lineCertFile.setText(self._cfg.getSettings(Config.AUTH_CERT)) + self.lineCertKeyFile.setText(self._cfg.getSettings(Config.AUTH_CERTKEY)) + authtype_idx = self.comboAuthType.findData(self._cfg.getSettings(Config.AUTH_TYPE)) + if authtype_idx <= 0: + authtype_idx = 0 + self.lineCertFile.setEnabled(False) + self.lineCertKeyFile.setEnabled(False) + self.comboAuthType.setCurrentIndex(authtype_idx) saved_lang = self._cfg.getSettings(Config.DEFAULT_LANGUAGE) if saved_lang: @@ -378,6 +397,7 @@ class PreferencesDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]): self.labelNodeVersion.setText("") def _save_settings(self): + self._reset_status_message() self._save_ui_config() if not self._save_db_config(): return @@ -414,6 +434,7 @@ class PreferencesDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]): self.saved.emit() self._settingsSaved = True + self._needs_restart() def _save_db_config(self): dbtype = self.comboDBType.currentIndex() @@ -421,10 +442,7 @@ class PreferencesDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]): if self.dbLabel.text() != "" and \ (self.comboDBType.currentIndex() != self.dbType or db_name != self.dbLabel.text()): - Message.ok( - QC.translate("preferences", "DB type changed"), - self._restart_msg, - QtWidgets.QMessageBox.Warning) + self._changes_needs_restart = QC.translate("preferences", "DB type changed") if self.comboDBType.currentIndex() != Database.DB_TYPE_MEMORY: if self.dbLabel.text() != "": @@ -452,6 +470,20 @@ class PreferencesDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]): if maxmsgsize is not "": self._cfg.setSettings(Config.DEFAULT_SERVER_MAX_MESSAGE_LENGTH, maxmsgsize.replace(" ", "")) + savedauthtype = self._cfg.getSettings(Config.AUTH_TYPE) + authtype = self.comboAuthType.itemData(self.comboAuthType.currentIndex()) + cert = self._cfg.getSettings(Config.AUTH_CERT) + certkey = self._cfg.getSettings(Config.AUTH_CERTKEY) + if not self._validate_certs(): + return + + if savedauthtype != authtype or self.lineCertFile.text() != cert or self.lineCertKeyFile.text() != certkey: + self._changes_needs_restart = QC.translate("preferences", "Certificates changed") + self._cfg.setSettings(Config.AUTH_TYPE, authtype) + self._cfg.setSettings(Config.AUTH_CERT, self.lineCertFile.text()) + self._cfg.setSettings(Config.AUTH_CERTKEY, self.lineCertKeyFile.text()) + + self._autostart.enable(self.checkAutostart.isChecked()) selected_lang = self.comboUILang.itemData(self.comboUILang.currentIndex()) @@ -459,10 +491,7 @@ class PreferencesDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]): saved_lang = "" if saved_lang is None else saved_lang if saved_lang != selected_lang: languages.save(self._cfg, selected_lang) - Message.ok( - QC.translate("preferences", "Language changed"), - self._restart_msg, - QtWidgets.QMessageBox.Warning) + self._changes_needs_restart = QC.translate("preferences", "Language changed") self._cfg.setSettings(self._cfg.DEFAULT_IGNORE_TEMPORARY_RULES, int(self.comboUIRules.currentIndex())) self._cfg.setSettings(self._cfg.DEFAULT_IGNORE_RULES, bool(self.checkUIRules.isChecked())) @@ -493,10 +522,7 @@ class PreferencesDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]): self._themes.save_theme(self.comboUITheme.currentIndex(), self.comboUITheme.currentText()) if self._themes.available() and self._saved_theme != "" and self.comboUITheme.currentText() == QC.translate("preferences", "System"): - Message.ok( - QC.translate("preferences", "UI theme changed"), - QC.translate("preferences", "Restart the GUI in order to apply the new theme"), - QtWidgets.QMessageBox.Warning) + self._changes_needs_restart = QC.translate("preferences", "UI theme changed") # this is a workaround for not display pop-ups. # see #79 for more information. @@ -534,10 +560,7 @@ class PreferencesDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]): savedAddr = self._cfg.getSettings(Config.DEFAULT_SERVER_ADDR) # exclude this message if there're more than one node connected if self.comboNodes.count() == 1 and savedAddr != None and savedAddr != self.comboNodeAddress.currentText(): - Message.ok( - QC.translate("preferences", "Ok"), - self._restart_msg, - QtWidgets.QMessageBox.Information) + self._changes_needs_restart = QC.translate("preferences", "Ok") self._cfg.setSettings(Config.DEFAULT_SERVER_ADDR, self.comboNodeAddress.currentText()) @@ -552,6 +575,36 @@ class PreferencesDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]): return None + def _validate_certs(self): + try: + if self.comboAuthType.currentIndex() == 0: + return True + + if self.comboAuthType.currentIndex() > 0 and (self.lineCertFile.text() == "" or self.lineCertKeyFile.text() == ""): + raise ValueError(QC.translate("preferences", "Certs fields cannot be empty.")) + + if oct(stat.S_IMODE(os.lstat(self.lineCertFile.text()).st_mode)) != "0o600": + self._set_status_message( + QC.translate("preferences", "cert file has excessive permissions, it should have 0600") + ) + if oct(stat.S_IMODE(os.lstat(self.lineCertFile.text()).st_mode)) != "0o600": + self._set_status_message( + QC.translate("preferences", "cert key file has excessive permissions, it should have 0600") + ) + + return True + except Exception as e: + self._changes_needs_restart = None + self._set_status_error(str(e)) + return False + + def _needs_restart(self): + if self._changes_needs_restart: + Message.ok(self._changes_needs_restart, + self._restart_msg, + QtWidgets.QMessageBox.Warning) + self._changes_needs_restart = None + def _hide_status_label(self): self.statusLabel.hide() @@ -601,6 +654,9 @@ class PreferencesDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]): del self._notifications_sent[reply.id] + def _cb_line_certs_changed(self, text): + self._changes_needs_restart = QC.translate("preferences", "Certs changed") + def _cb_file_db_clicked(self): options = QtWidgets.QFileDialog.Options() fileName, _ = QtWidgets.QFileDialog.getSaveFileName(self, "", "","All Files (*)", options=options) @@ -628,6 +684,7 @@ class PreferencesDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]): self._save_settings() def _cb_apply_button_clicked(self): + self._reset_status_message() self._save_settings() def _cb_cancel_button_clicked(self): @@ -657,6 +714,14 @@ class PreferencesDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]): def _cb_combo_themes_changed(self, index): self._themes.change_theme(self, self.comboUITheme.currentText()) + def _cb_combo_auth_type_changed(self, index): + curtype = self.comboAuthType.itemData(self.comboAuthType.currentIndex()) + savedtype = self._cfg.getSettings(Config.AUTH_TYPE) + if curtype != savedtype: + self._changes_needs_restart = QC.translate("preferences", "Auth type changed") + self.lineCertFile.setEnabled(index > 0) + self.lineCertKeyFile.setEnabled(index > 0) + def _cb_db_max_days_toggled(self, state): self._enable_db_cleaner_options(state, 1) diff --git a/ui/opensnitch/res/preferences.ui b/ui/opensnitch/res/preferences.ui index 05676d8c..6f7692ac 100644 --- a/ui/opensnitch/res/preferences.ui +++ b/ui/opensnitch/res/preferences.ui @@ -523,7 +523,52 @@ - + + + + + + + + System + + + + + + + + + 0 + 0 + + + + Theme + + + + + + + By default the GUI is started when login + + + Autostart the GUI upon login + + + true + + + + + + + + Server + + + true @@ -553,51 +598,56 @@ - - - - - + + - System + Simple + + + + + Simple TLS + + + + + Mutual TLS - - - - 0 - 0 - - - - Theme - - - - - Maximum size for receiving messages from nodes. Default 4MB + Maximum size of each message from nodes. Default 4MB Max gRPC channel size - - + + + + Absolute path to the cert key file + + + + + - By default the GUI is started when login + <p>Simple: no authentication, TLS simple/mutual: use SSL certificates to authenticate nodes.</p><p>Visit the wiki for more information.</p> - Autostart the GUI upon login + Authentication type - - true + + + + + + Absolute path to the cert file @@ -609,7 +659,7 @@ 0 0 586 - 238 + 209