Skip to content

Commit

Permalink
Add support for encryption
Browse files Browse the repository at this point in the history
  • Loading branch information
oroulet committed Dec 8, 2016
1 parent 70d6df9 commit ac5e608
Show file tree
Hide file tree
Showing 5 changed files with 289 additions and 8 deletions.
69 changes: 69 additions & 0 deletions uaclient/connection_dialog.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
from PyQt5.QtWidgets import QDialog, QFileDialog

from uaclient.connection_ui import Ui_ConnectionDialog


class ConnectionDialog(QDialog):
def __init__(self, parent, uri):
QDialog.__init__(self)
self.ui = Ui_ConnectionDialog()
self.ui.setupUi(self)

self.uaclient = parent.uaclient
self.uri = uri

self.ui.modeComboBox.addItem("None")
self.ui.modeComboBox.addItem("Sign")
self.ui.modeComboBox.addItem("SignAndEncrypt")

self.ui.policyComboBox.addItem("None")
self.ui.policyComboBox.addItem("Basic128Rsa15")
self.ui.policyComboBox.addItem("Basic256")

self.ui.closeButton.clicked.connect(self.accept)
self.ui.certificateButton.clicked.connect(self.get_certificate)
self.ui.privateKeyButton.clicked.connect(self.get_private_key)

@property
def security_mode(self):
return self.ui.modeComboBox.currentText()

@security_mode.setter
def security_mode(self, value):
self.ui.modeComboBox.setCurrentText(value)

@property
def security_policy(self):
return self.ui.policyComboBox.currentText()

@security_policy.setter
def security_policy(self, value):
self.ui.policyComboBox.setCurrentText(value)

@property
def certificate_path(self):
return self.ui.certificateLabel.text()

@certificate_path.setter
def certificate_path(self, value):
self.ui.certificateLabel.setText(value)

@property
def private_key_path(self):
return self.ui.privateKeyLabel.text()

@private_key_path.setter
def private_key_path(self, value):
self.ui.privateKeyLabel.setText(value)

def get_certificate(self):
path, ok = QFileDialog.getOpenFileName(self, "Select certificate", self.certificate_path, "Certificate (*.der)")
if ok:
self.ui.certificateLabel.setText(path)

def get_private_key(self):
path, ok = QFileDialog.getOpenFileName(self, "Select private key", self.private_key_path, "Private key (*.pem)")
if ok:
self.ui.privateKeyLabel.setText(path)


64 changes: 64 additions & 0 deletions uaclient/connection_ui.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'uaclient/connection_ui.ui'
#
# Created by: PyQt5 UI code generator 5.7
#
# WARNING! All changes made in this file will be lost!

from PyQt5 import QtCore, QtGui, QtWidgets

class Ui_ConnectionDialog(object):
def setupUi(self, ConnectionDialog):
ConnectionDialog.setObjectName("ConnectionDialog")
ConnectionDialog.resize(400, 300)
self.gridLayout = QtWidgets.QGridLayout(ConnectionDialog)
self.gridLayout.setContentsMargins(11, 11, 11, 11)
self.gridLayout.setSpacing(6)
self.gridLayout.setObjectName("gridLayout")
self.queryButton = QtWidgets.QPushButton(ConnectionDialog)
self.queryButton.setObjectName("queryButton")
self.gridLayout.addWidget(self.queryButton, 0, 0, 1, 2)
self.label = QtWidgets.QLabel(ConnectionDialog)
self.label.setObjectName("label")
self.gridLayout.addWidget(self.label, 1, 0, 1, 1)
self.policyComboBox = QtWidgets.QComboBox(ConnectionDialog)
self.policyComboBox.setObjectName("policyComboBox")
self.gridLayout.addWidget(self.policyComboBox, 1, 1, 1, 2)
self.label_2 = QtWidgets.QLabel(ConnectionDialog)
self.label_2.setObjectName("label_2")
self.gridLayout.addWidget(self.label_2, 2, 0, 1, 1)
self.modeComboBox = QtWidgets.QComboBox(ConnectionDialog)
self.modeComboBox.setObjectName("modeComboBox")
self.gridLayout.addWidget(self.modeComboBox, 2, 1, 1, 2)
self.certificateLabel = QtWidgets.QLabel(ConnectionDialog)
self.certificateLabel.setObjectName("certificateLabel")
self.gridLayout.addWidget(self.certificateLabel, 3, 0, 1, 1)
self.certificateButton = QtWidgets.QPushButton(ConnectionDialog)
self.certificateButton.setObjectName("certificateButton")
self.gridLayout.addWidget(self.certificateButton, 3, 1, 1, 2)
self.privateKeyLabel = QtWidgets.QLabel(ConnectionDialog)
self.privateKeyLabel.setObjectName("privateKeyLabel")
self.gridLayout.addWidget(self.privateKeyLabel, 4, 0, 1, 1)
self.privateKeyButton = QtWidgets.QPushButton(ConnectionDialog)
self.privateKeyButton.setObjectName("privateKeyButton")
self.gridLayout.addWidget(self.privateKeyButton, 4, 1, 1, 2)
self.closeButton = QtWidgets.QPushButton(ConnectionDialog)
self.closeButton.setObjectName("closeButton")
self.gridLayout.addWidget(self.closeButton, 5, 2, 1, 1)

self.retranslateUi(ConnectionDialog)
QtCore.QMetaObject.connectSlotsByName(ConnectionDialog)

def retranslateUi(self, ConnectionDialog):
_translate = QtCore.QCoreApplication.translate
ConnectionDialog.setWindowTitle(_translate("ConnectionDialog", "ConnectionDialog"))
self.queryButton.setText(_translate("ConnectionDialog", "Query server capability"))
self.label.setText(_translate("ConnectionDialog", "Security Policy"))
self.label_2.setText(_translate("ConnectionDialog", "Message Security Mode"))
self.certificateLabel.setText(_translate("ConnectionDialog", "None"))
self.certificateButton.setText(_translate("ConnectionDialog", "Select certificate"))
self.privateKeyLabel.setText(_translate("ConnectionDialog", "None"))
self.privateKeyButton.setText(_translate("ConnectionDialog", "Select private key"))
self.closeButton.setText(_translate("ConnectionDialog", "Close"))

84 changes: 84 additions & 0 deletions uaclient/connection_ui.ui
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ConnectionDialog</class>
<widget class="QDialog" name="ConnectionDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>ConnectionDialog</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0" colspan="2">
<widget class="QPushButton" name="queryButton">
<property name="text">
<string>Query server capability</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Security Policy</string>
</property>
</widget>
</item>
<item row="1" column="1" colspan="2">
<widget class="QComboBox" name="policyComboBox"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Message Security Mode</string>
</property>
</widget>
</item>
<item row="2" column="1" colspan="2">
<widget class="QComboBox" name="modeComboBox"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="certificateLabel">
<property name="text">
<string>None</string>
</property>
</widget>
</item>
<item row="3" column="1" colspan="2">
<widget class="QPushButton" name="certificateButton">
<property name="text">
<string>Select certificate</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="privateKeyLabel">
<property name="text">
<string>None</string>
</property>
</widget>
</item>
<item row="4" column="1" colspan="2">
<widget class="QPushButton" name="privateKeyButton">
<property name="text">
<string>Select private key</string>
</property>
</widget>
</item>
<item row="5" column="2">
<widget class="QPushButton" name="closeButton">
<property name="text">
<string>Close</string>
</property>
</widget>
</item>
</layout>
</widget>
<layoutdefault spacing="6" margin="11"/>
<resources/>
<connections/>
</ui>
24 changes: 21 additions & 3 deletions uaclient/mainwindow.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,9 @@ def __init__(self):
self.datachange_ui = DataChangeUI(self, self.uaclient)
self.event_ui = EventUI(self, self.uaclient)

self.ui.addrComboBox.currentTextChanged.connect(self._uri_changed)
self._uri_changed(self.ui.addrComboBox.currentText()) # force update for current value at startup

self.ui.treeView.activated.connect(self.show_refs)
self.ui.treeView.clicked.connect(self.show_refs)
self.ui.actionCopyPath.triggered.connect(self.tree_ui.copy_path)
Expand All @@ -269,8 +272,23 @@ def __init__(self):
self.ui.actionConnect.triggered.connect(self.connect)
self.ui.actionDisconnect.triggered.connect(self.disconnect)

self.connection_dialog = ConnectionDialog(self)
self.ui.connectOptionButton.clicked.connect(self.connection_dialog.show)
self.ui.connectOptionButton.clicked.connect(self.show_connection_dialog)

def _uri_changed(self, uri):
self.uaclient.load_security_settings(uri)

def show_connection_dialog(self):
dia = ConnectionDialog(self, self.ui.addrComboBox.currentText())
dia.security_mode = self.uaclient.security_mode
dia.security_policy = self.uaclient.security_policy
dia.certificate_path = self.uaclient.certificate_path
dia.private_key_path = self.uaclient.private_key_path
ret = dia.exec_()
if ret:
self.uaclient.security_mode = dia.security_mode
self.uaclient.security_policy = dia.security_policy
self.uaclient.certificate_path = dia.certificate_path
self.uaclient.private_key_path = dia.private_key_path

@trycatchslot
def show_refs(self, idx):
Expand Down Expand Up @@ -299,6 +317,7 @@ def get_current_node(self, idx=None):
def get_uaclient(self):
return self.uaclient

@trycatchslot
def connect(self):
uri = self.ui.addrComboBox.currentText()
try:
Expand Down Expand Up @@ -339,7 +358,6 @@ def closeEvent(self, event):
self.settings.setValue("main_window_height", self.size().height())
self.settings.setValue("main_window_state", self.saveState())
self.settings.setValue("address_list", self._address_list)
self.connection_dialog.save_state()
self.disconnect()
event.accept()

Expand Down
56 changes: 51 additions & 5 deletions uaclient/uaclient.py
Original file line number Diff line number Diff line change
@@ -1,40 +1,85 @@
import logging

from PyQt5.QtCore import QSettings

from opcua import ua
from opcua import Client
from opcua import Node
from opcua import crypto


logger = logging.getLogger(__name__)


class UaClient(object):
"""
OPC-Ua client specialized for the need of GUI client
return exactly whant GUI needs, no customization possible
return exactly what GUI needs, no customization possible
"""

def __init__(self):
self.settings = QSettings()
self.client = None
self._connected = False
self._datachange_sub = None
self._event_sub = None
self._subs_dc = {}
self._subs_ev = {}
self.security_mode = None
self.security_policy = None
self.certificate_path = None
self.private_key_path = None

def load_security_settings(self, uri):
self.security_mode = None
self.security_policy = None
self.certificate_path = None
self.private_key_path = None

mysettings = self.settings.value("security_settings", None)
if mysettings is None:
return
if uri in mysettings:
mode, policy, cert, key = mysettings[uri]
self.security_mode = mode
self.security_policy = policy
self.certificate_path = cert
self.private_key_path = key

def save_security_settings(self, uri):
mysettings = self.settings.value("security_settings", None)
if mysettings is None:
mysettings = {}
mysettings[uri] = [self.security_mode,
self.security_policy,
self.certificate_path,
self.private_key_path]
self.settings.setValue("security_settings", mysettings)

def get_node(self, nodeid):
return self.client.get_node(nodeid)

def connect(self, uri):
self.disconnect()
print("Connecting to ", uri)
logger.info("Connecting to %s with parameters %s, %s, %s, %s", uri, self.security_mode, self.security_policy, self.certificate_path, self.private_key_path)
self.client = Client(uri)
if self.security_mode is not None and self.security_policy is not None:
self.client.set_security(
getattr(crypto.security_policies, 'SecurityPolicy' + self.security_policy),
self.certificate_path,
self.private_key_path,
mode=getattr(ua.MessageSecurityMode, self.security_mode)
)
self.client.connect()
self._connected = True
self.save_security_settings(uri)

def disconnect(self):
if self._connected:
print("Disconnecting from server")
self._subs_dc = {}
self._subs_ev = {}
self._connected = False
self._subscription = None
self.client.disconnect()
self.client = None

Expand Down Expand Up @@ -65,7 +110,8 @@ def get_node_attrs(self, node):
attrs = node.get_attributes([ua.AttributeIds.DisplayName, ua.AttributeIds.BrowseName, ua.AttributeIds.NodeId])
return node, [attr.Value.Value.to_string() for attr in attrs]

def get_children(self, node):
@staticmethod
def get_children(node):
descs = node.get_children_descriptions()
descs.sort(key=lambda x: x.BrowseName)
return descs
Expand Down

0 comments on commit ac5e608

Please sign in to comment.