MqttMonitor/MqttConnectionProfilesDialog.py

branch
connection_profiles
changeset 26
ad232a5129cc
parent 23
0b23bd856e43
child 30
17ef10819773
--- a/MqttMonitor/MqttConnectionProfilesDialog.py	Sat Sep 08 15:28:48 2018 +0200
+++ b/MqttMonitor/MqttConnectionProfilesDialog.py	Sat Sep 08 15:29:39 2018 +0200
@@ -16,6 +16,7 @@
     QListWidgetItem, QInputDialog, QLineEdit
 
 from E5Gui import E5MessageBox
+from E5Gui.E5PathPicker import E5PathPickerModes
 
 from .Ui_MqttConnectionProfilesDialog import Ui_MqttConnectionProfilesDialog
 
@@ -37,7 +38,8 @@
             connection parameters. Each entry must have the keys
             "BrokerAddress", "BrokerPort", "ClientId",
             "Keepalive", "CleanSession", "Username", "Password", "WillTopic",
-            "WillMessage", "WillQos", "WillRetain".
+            "WillMessage", "WillQos", "WillRetain", "TlsEnable", "TlsCaCert",
+            "TlsClientCert", "TlsClientKey".
         @type dict
         @param parent reference to the parent widget
         @type QWidget
@@ -52,12 +54,30 @@
         self.__profilesChanged = False
         
         self.plusButton.setIcon(UI.PixmapCache.getIcon("plus.png"))
+        self.copyButton.setIcon(UI.PixmapCache.getIcon("editCopy.png"))
         self.minusButton.setIcon(UI.PixmapCache.getIcon("minus.png"))
         
+        self.tlsCertsFilePicker.setMode(E5PathPickerModes.OpenFileMode)
+        self.tlsCertsFilePicker.setFilters(
+            self.tr("Certificate Files (*.crt *.pem);;All Files (*)"))
+        self.tlsSelfSignedCertsFilePicker.setMode(
+            E5PathPickerModes.OpenFileMode)
+        self.tlsSelfSignedCertsFilePicker.setFilters(
+            self.tr("Certificate Files (*.crt *.pem);;All Files (*)"))
+        self.tlsSelfSignedClientCertFilePicker.setMode(
+            E5PathPickerModes.OpenFileMode)
+        self.tlsSelfSignedClientCertFilePicker.setFilters(
+            self.tr("Certificate Files (*.crt *.pem);;All Files (*)"))
+        self.tlsSelfSignedClientKeyFilePicker.setMode(
+            E5PathPickerModes.OpenFileMode)
+        self.tlsSelfSignedClientKeyFilePicker.setFilters(
+            self.tr("Key Files (*.key *.pem);;All Files (*)"))
+        
         self.profileTabWidget.setCurrentIndex(0)
         
         if len(self.__profiles) == 0:
             self.minusButton.setEnabled(False)
+            self.copyButton.setEnabled(False)
         
         self.profileFrame.setEnabled(False)
         self.__populatingProfile = False
@@ -105,6 +125,7 @@
         @type QListWidgetItem
         """
         self.minusButton.setEnabled(current is not None)
+        self.copyButton.setEnabled(current is not None)
         
         if current is not previous:
             if not self.__deletingProfile and self.__isChangedProfile():
@@ -135,10 +156,48 @@
             self.tr("New Connection Profile"),
             self.tr("Enter name for the new Connection Profile:"),
             QLineEdit.Normal)
-        if ok and bool(profileName) and profileName not in self.__profiles:
-            itm = QListWidgetItem(profileName, self.profilesList)
-            self.profilesList.setCurrentItem(itm)
-            self.brokerAddressEdit.setFocus(Qt.OtherFocusReason)
+        if ok and bool(profileName):
+            if profileName in self.__profiles:
+                E5MessageBox.warning(
+                    self,
+                    self.tr("New Connection Profile"),
+                    self.tr("""<p>A connection named <b>{0}</b> exists"""
+                            """ already. Aborting...</p>""").format(
+                        profileName))
+            else:
+                itm = QListWidgetItem(profileName, self.profilesList)
+                self.profilesList.setCurrentItem(itm)
+                self.brokerAddressEdit.setFocus(Qt.OtherFocusReason)
+    
+    @pyqtSlot()
+    def on_copyButton_clicked(self):
+        """
+        Private slot to copy the selected profile entry.
+        """
+        itm = self.profilesList.currentItem()
+        if itm:
+            profileName = itm.text()
+            newProfileName, ok = QInputDialog.getText(
+                self,
+                self.tr("Copy Connection Profile"),
+                self.tr("Enter name for the copied Connection Profile:"),
+                QLineEdit.Normal)
+            if ok and bool(newProfileName):
+                if newProfileName in self.__profiles:
+                    E5MessageBox.warning(
+                        self,
+                        self.tr("Copy Connection Profile"),
+                        self.tr("""<p>A connection named <b>{0}</b> exists"""
+                                """ already. Aborting...</p>""").format(
+                            newProfileName))
+                else:
+                    profile = self.__defaultProfile()
+                    profile.update(self.__profiles[profileName])
+                    self.__profiles[newProfileName] = profile
+                    
+                    itm = QListWidgetItem(newProfileName, self.profilesList)
+                    self.profilesList.setCurrentItem(itm)
+                    self.brokerAddressEdit.setFocus(Qt.OtherFocusReason)
     
     @pyqtSlot()
     def on_minusButton_clicked(self):
@@ -170,7 +229,8 @@
         @return dictionary containing dictionaries containing the defined
             connection profiles. Each entry have the keys "BrokerAddress",
             "BrokerPort", "ClientId", "Keepalive", "CleanSession", "Username",
-            "Password", "WillTopic", "WillMessage", "WillQos", "WillRetain".
+            "Password", "WillTopic", "WillMessage", "WillQos", "WillRetain",
+            "TlsEnable", "TlsCaCert", "TlsClientCert", "TlsClientKey".
         @rtype dict
         """
         profilesDict = {}
@@ -197,7 +257,22 @@
             "WillMessage": self.willMessageEdit.toPlainText(),
             "WillQos": self.willQosSpinBox.value(),
             "WillRetain": self.willRetainCheckBox.isChecked(),
+            "TlsEnable": self.tlsGroupBox.isChecked(),
+            "TlsCaCert": "",
+            "TlsClientCert": "",
+            "TlsClientKey": "",
         }
+        if profile["TlsEnable"]:
+            if self.tlsCertsFileButton.isChecked():
+                profile["TlsCaCert"] = self.tlsCertsFilePicker.text()
+            elif self.tlsSelfSignedCertsButton.isChecked():
+                profile["TlsCaCert"] = \
+                    self.tlsSelfSignedCertsFilePicker.text()
+                profile["TlsClientCert"] = \
+                    self.tlsSelfSignedClientCertFilePicker.text()
+                profile["TlsClientKey"] = \
+                    self.tlsSelfSignedClientKeyFilePicker.text()
+        
         self.__profiles[profileName] = profile
         self.__profilesChanged = True
         
@@ -212,7 +287,10 @@
         """
         defaultProfile = self.__client.defaultConnectionOptions()
         defaultProfile["BrokerAddress"] = ""
-        defaultProfile["BrokerPort"] = 1883
+        if defaultProfile["TlsEnable"]:
+            defaultProfile["BrokerPort"] = 8883
+        else:
+            defaultProfile["BrokerPort"] = 1883
         
         return defaultProfile
     
@@ -247,10 +325,9 @@
         @param profileName name of the profile to get data from
         @type str
         """
+        profile = self.__defaultProfile()
         if profileName:
-            profile = self.__profiles[profileName]
-        else:
-            profile = self.__defaultProfile()
+            profile.update(self.__profiles[profileName])
         
         self.__populatingProfile = True
         if profileName is not None:
@@ -266,6 +343,19 @@
         self.willMessageEdit.setPlainText(profile["WillMessage"])
         self.willQosSpinBox.setValue(profile["WillQos"])
         self.willRetainCheckBox.setChecked(profile["WillRetain"])
+        self.tlsGroupBox.setChecked(profile["TlsEnable"])
+        if profile["TlsCaCert"] and profile["TlsClientCert"]:
+            self.tlsSelfSignedCertsButton.setChecked(True)
+            self.tlsSelfSignedCertsFilePicker.setText(profile["TlsCaCert"])
+            self.tlsSelfSignedClientCertFilePicker.setText(
+                profile["TlsClientCert"])
+            self.tlsSelfSignedClientKeyFilePicker.setText(
+                profile["TlsClientKey"])
+        elif profile["TlsCaCert"]:
+            self.tlsCertsFileButton.setChecked(True)
+            self.tlsCertsFilePicker.setText(profile["TlsCaCert"])
+        else:
+            self.tlsDefaultCertsButton.setChecked(True)
         self.__populatingProfile = False
         
         self.profileFrame.setEnabled(True)
@@ -288,6 +378,14 @@
         self.willMessageEdit.setPlainText("")
         self.willQosSpinBox.setValue(0)
         self.willRetainCheckBox.setChecked(False)
+        self.tlsGroupBox.setChecked(False)
+        self.tlsDefaultCertsButton.setChecked(True)
+        self.tlsCertsFileButton.setChecked(True)
+        self.tlsCertsFilePicker.setText("")
+        self.tlsSelfSignedCertsButton.setChecked(False)
+        self.tlsSelfSignedCertsFilePicker.setText("")
+        self.tlsSelfSignedClientCertFilePicker.setText("")
+        self.tlsSelfSignedClientKeyFilePicker.setText("")
         self.__populatingProfile = False
         
         self.profileFrame.setEnabled(False)
@@ -322,8 +420,9 @@
             return False
         
         elif profileName in self.__profiles:
-            profile = self.__profiles[profileName]
-            return (
+            profile = self.__defaultProfile()
+            profile.update(self.__profiles[profileName])
+            changed = (
                 self.brokerAddressEdit.text() != profile["BrokerAddress"] or
                 self.brokerPortSpinBox.value() != profile["BrokerPort"] or
                 self.clientIdEdit.text() != profile["ClientId"] or
@@ -336,8 +435,25 @@
                 self.willTopicEdit.text() != profile["WillTopic"] or
                 self.willMessageEdit.toPlainText() != profile["WillMessage"] or
                 self.willQosSpinBox.value() != profile["WillQos"] or
-                self.willRetainCheckBox.isChecked() != profile["WillRetain"]
+                self.willRetainCheckBox.isChecked() != profile["WillRetain"] or
+                self.tlsGroupBox.isChecked() != profile["TlsEnable"]
             )
+            # check TLS stuff only, if not yet changed
+            if not changed:
+                if self.tlsCertsFileButton.isChecked():
+                    changed |= (
+                        self.tlsCertsFilePicker.text() != profile["TlsCaCert"]
+                    )
+                elif self.tlsSelfSignedCertsButton.isChecked():
+                    changed |= (
+                        self.tlsSelfSignedCertsFilePicker.text() !=
+                        profile["TlsCaCert"] or
+                        self.tlsSelfSignedClientCertFilePicker.text() !=
+                        profile["TlsClientCert"] or
+                        self.tlsSelfSignedClientKeyFilePicker.text() !=
+                        profile["TlsClientKey"]
+                    )
+            return changed
         
         else:
             return True
@@ -360,6 +476,20 @@
                     self.tr("Invalid Connection Parameters"),
                     self.tr("An empty Client ID requires a clean session."))
         
+        if self.tlsGroupBox.isChecked():
+            if self.tlsCertsFileButton.isChecked():
+                # condition 3a: if CA certificates file shall be used, it must
+                # be given
+                enable &= bool(self.tlsCertsFilePicker.text())
+            elif self.tlsSelfSignedCertsButton.isChecked():
+                # condition 3b: if client certificates shall be used, all files
+                # must be given
+                enable &= (
+                    bool(self.tlsSelfSignedCertsFilePicker.text()) and
+                    bool(self.tlsSelfSignedClientCertFilePicker.text()) and
+                    bool(self.tlsSelfSignedClientKeyFilePicker.text())
+                )
+        
         self.profileButtonBox.button(QDialogButtonBox.Apply).setEnabled(enable)
     
     @pyqtSlot(str)
@@ -380,6 +510,138 @@
         uuid = QUuid.createUuid()
         self.clientIdEdit.setText(uuid.toString(QUuid.WithoutBraces))
     
+    @pyqtSlot(str)
+    def on_clientIdEdit_textChanged(self, clientId):
+        """
+        Private slot handling a change of the client ID string.
+        
+        @param clientId client ID
+        @type str
+        """
+        self.__updateApplyButton()
+    
+    @pyqtSlot(bool)
+    def on_cleanSessionCheckBox_clicked(self, checked):
+        """
+        Private slot to handle a change of the clean session selection.
+        
+        @param checked current state of the clean session selection
+        @type bool
+        """
+        self.__updateApplyButton()
+    
+    @pyqtSlot(str)
+    def on_tlsCertsFilePicker_textChanged(self, path):
+        """
+        Private slot handling a change of the TLS CA certificates file.
+        
+        @param path file path
+        @type str
+        """
+        self.__updateApplyButton()
+    
+    @pyqtSlot(str)
+    def on_tlsSelfSignedCertsFilePicker_textChanged(self, path):
+        """
+        Private slot handling a change of the TLS CA certificates file.
+        
+        @param path file path
+        @type str
+        """
+        self.__updateApplyButton()
+    
+    @pyqtSlot(str)
+    def on_tlsSelfSignedClientCertFilePicker_textChanged(self, path):
+        """
+        Private slot handling a change of the TLS client certificate file.
+        
+        @param path file path
+        @type str
+        """
+        self.__updateApplyButton()
+    
+    @pyqtSlot(str)
+    def on_tlsSelfSignedClientKeyFilePicker_textChanged(self, path):
+        """
+        Private slot handling a change of the TLS client key file.
+        
+        @param path file path
+        @type str
+        """
+        self.__updateApplyButton()
+    
+    @pyqtSlot(bool)
+    def on_tlsGroupBox_toggled(self, checked):
+        """
+        Private slot handling the selection of TLS mode.
+        
+        @param checked state of the selection
+        @type bool
+        """
+        if checked and self.brokerPortSpinBox.value() == 1883:
+            # port is still standard non-TLS port
+            yes = E5MessageBox.yesNo(
+                self,
+                self.tr("SSL/TLS Enabled"),
+                self.tr(
+                    """Encrypted connection using SSL/TLS has been enabled."""
+                    """ However, the broker port is still the default"""
+                    """ unencrypted port (port 1883). Shall this be"""
+                    """ changed?"""),
+                icon=E5MessageBox.Warning,
+                yesDefault=True)
+            if yes:
+                self.brokerPortSpinBox.setValue(8883)
+        elif not checked and self.brokerPortSpinBox.value() == 8883:
+            # port is still standard TLS port
+            yes = E5MessageBox.yesNo(
+                self,
+                self.tr("SSL/TLS Disabled"),
+                self.tr(
+                    """Encrypted connection using SSL/TLS has been disabled."""
+                    """ However, the broker port is still the default"""
+                    """ encrypted port (port 8883). Shall this be"""
+                    """ changed?"""),
+                icon=E5MessageBox.Warning,
+                yesDefault=True)
+            if yes:
+                self.brokerPortSpinBox.setValue(1883)
+        
+        self.__updateApplyButton()
+    
+    @pyqtSlot(bool)
+    def on_tlsDefaultCertsButton_toggled(self, checked):
+        """
+        Private slot handling the selection of using the default
+        certificates file.
+        
+        @param checked state of the selection
+        @type bool
+        """
+        self.__updateApplyButton()
+    
+    @pyqtSlot(bool)
+    def on_tlsCertsFileButton_toggled(self, checked):
+        """
+        Private slot handling the selection of using a non-default
+        certificates file.
+        
+        @param checked state of the selection
+        @type bool
+        """
+        self.__updateApplyButton()
+    
+    @pyqtSlot(bool)
+    def on_tlsSelfSignedCertsButton_toggled(self, checked):
+        """
+        Private slot handling the selection of using self signed
+        client certificate and key files.
+        
+        @param checked state of the selection
+        @type bool
+        """
+        self.__updateApplyButton()
+    
     @pyqtSlot()
     def reject(self):
         """
@@ -435,23 +697,3 @@
                 self.__applyProfile()
         
         super(MqttConnectionProfilesDialog, self).accept()
-    
-    @pyqtSlot(str)
-    def on_clientIdEdit_textChanged(self, clientId):
-        """
-        Private slot handling a change of the client ID string.
-        
-        @param clientId client ID
-        @type str
-        """
-        self.__updateApplyButton()
-    
-    @pyqtSlot(bool)
-    def on_cleanSessionCheckBox_clicked(self, checked):
-        """
-        Private slot to handle a change of the clean session selection.
-        
-        @param checked current state of the clean session selection
-        @type bool
-        """
-        self.__updateApplyButton()

eric ide

mercurial