Sat, 08 Sep 2018 15:29:39 +0200
MqttConnectionProfilesDialog: added support for TLS and added a button to copy the current profile.
MqttMonitor/MqttConnectionProfilesDialog.py | file | annotate | diff | comparison | revisions | |
MqttMonitor/MqttConnectionProfilesDialog.ui | file | annotate | diff | comparison | revisions |
--- 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()
--- a/MqttMonitor/MqttConnectionProfilesDialog.ui Sat Sep 08 15:28:48 2018 +0200 +++ b/MqttMonitor/MqttConnectionProfilesDialog.ui Sat Sep 08 15:29:39 2018 +0200 @@ -53,6 +53,13 @@ </widget> </item> <item> + <widget class="QToolButton" name="copyButton"> + <property name="toolTip"> + <string>Press to copy the selected profile</string> + </property> + </widget> + </item> + <item> <widget class="QToolButton" name="minusButton"> <property name="toolTip"> <string>Press to delete the selected profile</string> @@ -94,9 +101,9 @@ </widget> </item> <item row="0" column="1"> - <widget class="E5ClearableLineEdit" name="profileEdit"> - <property name="toolTip"> - <string>Enter the name of the profile</string> + <widget class="QLineEdit" name="profileEdit"> + <property name="readOnly"> + <bool>true</bool> </property> </widget> </item> @@ -380,6 +387,243 @@ </item> </layout> </widget> + <widget class="QWidget" name="tlsTab"> + <attribute name="title"> + <string>SSL/TLS</string> + </attribute> + <layout class="QVBoxLayout" name="verticalLayout_5"> + <item> + <widget class="QGroupBox" name="tlsGroupBox"> + <property name="toolTip"> + <string>Select to enable SSL/TLS connections</string> + </property> + <property name="title"> + <string>SSL/TLS Enabled</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="checked"> + <bool>false</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout_4"> + <item> + <widget class="QRadioButton" name="tlsDefaultCertsButton"> + <property name="toolTip"> + <string>Select to use the default certificate file of the client</string> + </property> + <property name="text"> + <string>CA signed server certificate</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QRadioButton" name="tlsCertsFileButton"> + <property name="toolTip"> + <string>Select to use a specific certificate file</string> + </property> + <property name="text"> + <string>CA certificate file</string> + </property> + </widget> + </item> + <item> + <widget class="QWidget" name="tlsCertsFileWidget" native="true"> + <property name="enabled"> + <bool>false</bool> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_6"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <spacer name="horizontalSpacer_4"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>25</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="label_8"> + <property name="text"> + <string>CA File:</string> + </property> + </widget> + </item> + <item> + <widget class="E5PathPicker" name="tlsCertsFilePicker" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="focusPolicy"> + <enum>Qt::StrongFocus</enum> + </property> + <property name="toolTip"> + <string>Enter the full path to the CA certificate file</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QRadioButton" name="tlsSelfSignedCertsButton"> + <property name="toolTip"> + <string>Select to use a self signed client certificate</string> + </property> + <property name="text"> + <string>Self signed certificates</string> + </property> + </widget> + </item> + <item> + <widget class="QWidget" name="tlsSelfSignedFilesWidget" native="true"> + <property name="enabled"> + <bool>false</bool> + </property> + <layout class="QGridLayout" name="gridLayout_5"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item row="0" column="0"> + <spacer name="horizontalSpacer_5"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Fixed</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>25</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="0" column="1"> + <widget class="QLabel" name="label_9"> + <property name="text"> + <string>CA File:</string> + </property> + </widget> + </item> + <item row="0" column="2"> + <widget class="E5PathPicker" name="tlsSelfSignedCertsFilePicker" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="focusPolicy"> + <enum>Qt::StrongFocus</enum> + </property> + <property name="toolTip"> + <string>Enter the full path to the CA certificate file</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLabel" name="label_10"> + <property name="text"> + <string>Client Certificate File:</string> + </property> + </widget> + </item> + <item row="1" column="2"> + <widget class="E5PathPicker" name="tlsSelfSignedClientCertFilePicker" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="focusPolicy"> + <enum>Qt::StrongFocus</enum> + </property> + <property name="toolTip"> + <string>Enter the full path to the client certificate file</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QLabel" name="label_11"> + <property name="text"> + <string>Client Key File:</string> + </property> + </widget> + </item> + <item row="2" column="2"> + <widget class="E5PathPicker" name="tlsSelfSignedClientKeyFilePicker" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="focusPolicy"> + <enum>Qt::StrongFocus</enum> + </property> + <property name="toolTip"> + <string>Enter the full path to the client key file</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <spacer name="verticalSpacer_3"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>128</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> </widget> </item> <item> @@ -411,10 +655,17 @@ <extends>QLineEdit</extends> <header>E5Gui/E5LineEdit.h</header> </customwidget> + <customwidget> + <class>E5PathPicker</class> + <extends>QWidget</extends> + <header>E5Gui/E5PathPicker.h</header> + <container>1</container> + </customwidget> </customwidgets> <tabstops> <tabstop>profilesList</tabstop> <tabstop>plusButton</tabstop> + <tabstop>copyButton</tabstop> <tabstop>minusButton</tabstop> <tabstop>profileEdit</tabstop> <tabstop>brokerAddressEdit</tabstop> @@ -430,6 +681,14 @@ <tabstop>willMessageEdit</tabstop> <tabstop>willQosSpinBox</tabstop> <tabstop>willRetainCheckBox</tabstop> + <tabstop>tlsGroupBox</tabstop> + <tabstop>tlsDefaultCertsButton</tabstop> + <tabstop>tlsCertsFileButton</tabstop> + <tabstop>tlsCertsFilePicker</tabstop> + <tabstop>tlsSelfSignedCertsButton</tabstop> + <tabstop>tlsSelfSignedCertsFilePicker</tabstop> + <tabstop>tlsSelfSignedClientCertFilePicker</tabstop> + <tabstop>tlsSelfSignedClientKeyFilePicker</tabstop> </tabstops> <resources/> <connections> @@ -465,5 +724,37 @@ </hint> </hints> </connection> + <connection> + <sender>tlsCertsFileButton</sender> + <signal>toggled(bool)</signal> + <receiver>tlsCertsFileWidget</receiver> + <slot>setEnabled(bool)</slot> + <hints> + <hint type="sourcelabel"> + <x>367</x> + <y>238</y> + </hint> + <hint type="destinationlabel"> + <x>357</x> + <y>252</y> + </hint> + </hints> + </connection> + <connection> + <sender>tlsSelfSignedCertsButton</sender> + <signal>toggled(bool)</signal> + <receiver>tlsSelfSignedFilesWidget</receiver> + <slot>setEnabled(bool)</slot> + <hints> + <hint type="sourcelabel"> + <x>387</x> + <y>287</y> + </hint> + <hint type="destinationlabel"> + <x>466</x> + <y>305</y> + </hint> + </hints> + </connection> </connections> </ui>