--- a/runtime/WampClient.py Tue Feb 18 16:19:23 2025 +0100
+++ b/runtime/WampClient.py Tue Feb 18 16:22:54 2025 +0100
@@ -26,12 +26,16 @@
from autobahn.twisted import wamp
from autobahn.twisted.websocket import WampWebSocketClientFactory, connectWS
from autobahn.wamp import types, auth
from autobahn.wamp.serializer import MsgPackSerializer
from twisted.internet.protocol import ReconnectingClientFactory
from twisted.python.components import registerAdapter
+from twisted.internet.ssl import optionsForClientTLS, CertificateOptions +from twisted.internet._sslverify import OpenSSLCertificateAuthorities +from OpenSSL import crypto from formless import annotate, webform
@@ -47,6 +51,7 @@
# Find pre-existing project WAMP config file
@@ -78,7 +83,8 @@
# Those two lists are meant to be filled by customized runtime
@@ -220,7 +226,7 @@
if os.path.exists(_WampConf):
WampClientConf = json.load(open(_WampConf))
UpdateWithDefault(WampClientConf, defaultWampConfig)
@@ -240,11 +246,6 @@
-def SetWampSecret(wampSecret):
- with open(os.path.realpath(_WampSecret), 'wb') as f:
def SetConfiguration(WampClientConf):
@@ -272,10 +273,16 @@
return re.match(r'wss?://[^\s?:#-]+(:[0-9]+)?(/[^\s]*)?$', uri) is not None
-def RegisterWampClient(wampconf=None, wampsecret=None):
- global _WampConf, _WampSecret
- _WampConfDefault = os.path.join(WorkingDir, "wampconf.json")
- _WampSecretDefault = os.path.join(WorkingDir, "wamp.secret")
+def RegisterWampClient(wampconf=None, wampsecret=None, ConfDir=None, KeyStore=None): + global _WampConf, _WampSecret, _WampTrust + ConfDir = ConfDir if ConfDir else WorkingDir + KeyStore = KeyStore if KeyStore else WorkingDir + _WampConfDefault = os.path.join(ConfDir, "wampconf.json") + _WampSecretDefault = os.path.join(KeyStore, "wamp.secret") + _WampTrust = os.path.join(KeyStore, "wampTrustStore.crt") # set config file path only if not already set
@@ -323,13 +330,28 @@
# start the client from a Twisted endpoint
- connectWS(_transportFactory)
+ if _transportFactory.isSecure: + contextFactory = MakeSecureContextFactory(WampClientConf["verifyHostname"]) + connectWS(_transportFactory, contextFactory) print(_("WAMP client connecting to :"), WampClientConf["url"])
print(_("WAMP client can not connect to :"), WampClientConf["url"])
+def MakeSecureContextFactory(verifyHostname): + if os.path.exists(_WampTrust): + cert = crypto.load_certificate( + six.u(open(_WampTrust, 'r').read()) + trustRoot=OpenSSLCertificateAuthorities([cert]) + return optionsForClientTLS(_transportFactory.host, trustRoot=trustRoot) def StopReconnectWampClient():
if _transportFactory is not None:
@@ -377,11 +399,13 @@
# WEB CONFIGURATION INTERFACE
WAMP_SECRET_URL = "secret"
+WAMP_DELETE_TRUSTSTORE_URL = "delete_truststore" webExposedConfigItems = [
"clientFactoryOptions.maxDelay",
"protocolOptions.autoPingInterval",
- "protocolOptions.autoPingTimeout"
+ "protocolOptions.autoPingTimeout", @@ -403,8 +427,17 @@
if secretfile_field is not None:
secretfile = getattr(secretfile_field, "file", None)
if secretfile is not None:
- secret = secretfile_field.file.read()
+ with open(os.path.realpath(_WampSecret), 'w') as destfd: + shutil.copyfileobj(secretfile,destfd) + trustStore_field = kwargs["trustStore"] + if trustStore_field is not None: + trustStore_file = getattr(trustStore_field, "file", None) + if trustStore_file is not None: + with open(os.path.realpath(_WampTrust), 'w') as destfd: + trustStore_file.seek(0) + shutil.copyfileobj(trustStore_file,destfd) newConfig = lastKnownConfig.copy()
for argname in webExposedConfigItems:
@@ -448,6 +481,43 @@
child(lastKnownConfig["ID"] + ".secret")
+class FileUploadDelete(annotate.FileUpload): +class FileUploadDeleteRenderer(webform.FileUploadRenderer): + def input(self, context, slot, data, name, value): + # pylint: disable=expression-not-assigned + slot = webform.FileUploadRenderer.input( + self, context, slot, data, name, value) + file_exists = data.typedValue.getAttribute('file_exists') + if file_exists and file_exists(): + file_delete = data.typedValue.getAttribute('file_delete') + tags.a(href=file_delete, target=unique)[_("Delete")], + tags.iframe(srcdoc="File exists", name=unique, + height="20", width="150", + marginheight="5", marginwidth="5", + scrolling="no", frameborder="0") +registerAdapter(FileUploadDeleteRenderer, FileUploadDelete, + formless.iformless.ITypedRenderer) +def getTrustStorePresence(): + return os.path.exists(_WampTrust) +def getTrustStoreDeleteUrl(ctx, argument): + return url.URL.fromContext(ctx).child(WAMP_DELETE_TRUSTSTORE_URL) annotate.String(label=_("Current status"),
@@ -473,7 +543,14 @@
default=wampConfigDefault)),
("protocolOptions.autoPingTimeout",
annotate.Integer(label=_("Auto ping timeout (s)"),
- default=wampConfigDefault))
+ default=wampConfigDefault)), + FileUploadDelete(label=_("File containing server certificate"), + file_exists=getTrustStorePresence, + file_delete=getTrustStoreDeleteUrl)), + annotate.Boolean(label=_("Verify hostname matches certificate hostname"), + default=wampConfigDefault)), def deliverWampSecret(ctx, segments):
@@ -487,6 +564,10 @@
secret = LoadWampSecret(_WampSecret)
return static.Data(secret, 'application/octet-stream'), ()
+def deleteTrustStore(ctx, segments): + if os.path.exists(_WampTrust): + return static.Data("TrustStore deleted", 'text/html'), () def RegisterWebSettings(NS):
@@ -499,4 +580,5 @@
WebSettings.addCustomURL(WAMP_SECRET_URL, deliverWampSecret)
+ WebSettings.addCustomURL(WAMP_DELETE_TRUSTSTORE_URL, deleteTrustStore)