# HG changeset patch # User Edouard Tisserant # Date 1531220045 -7200 # Node ID 985f234b0d092397a9f23770f47793c290dc7993 # Parent 73042b2d8d6554eb7344cc90f8ad50bffa8a3b20 Moved Wamp configuration file selection logic into runtime/wampclient.py. Added Wamp CRA secret file upload and download. Slightly reworked code for config and secret file loading and saving. diff -r 73042b2d8d65 -r 985f234b0d09 Beremiz_service.py --- a/Beremiz_service.py Tue Jul 10 12:51:28 2018 +0200 +++ b/Beremiz_service.py Tue Jul 10 12:54:05 2018 +0200 @@ -592,19 +592,11 @@ webport = None NS.WorkingDir = WorkingDir - # Find pre-existing project WAMP config file - _wampconf = os.path.join(WorkingDir, "wampconf.json") - - # If project's WAMP config file exits, override default (-c) - if os.path.exists(_wampconf): - wampconf = _wampconf - - if wampconf is not None: - try: - import runtime.WampClient as WC # pylint: disable=ungrouped-imports - except Exception: - LogMessageAndException(_("WAMP import failed :")) - wampconf = None + try: + import runtime.WampClient as WC # pylint: disable=ungrouped-imports + WC.WorkingDir = WorkingDir + except Exception: + LogMessageAndException(_("WAMP import failed :")) # Load extensions for extention_file, extension_folder in extensions: @@ -621,12 +613,11 @@ except Exception: LogMessageAndException(_("Nevow Web service failed. ")) - if wampconf is not None: - try: - WC.SetServer(pyroserver) - WC.RegisterWampClient(wampconf, wampsecret) - except Exception: - LogMessageAndException(_("WAMP client startup failed. ")) + try: + WC.SetServer(pyroserver) + WC.RegisterWampClient(wampconf, wampsecret) + except Exception: + LogMessageAndException(_("WAMP client startup failed. ")) pyro_thread_started = Lock() pyro_thread_started.acquire() diff -r 73042b2d8d65 -r 985f234b0d09 runtime/WampClient.py --- a/runtime/WampClient.py Tue Jul 10 12:51:28 2018 +0200 +++ b/runtime/WampClient.py Tue Jul 10 12:54:05 2018 +0200 @@ -34,16 +34,22 @@ from autobahn.wamp.serializer import MsgPackSerializer from twisted.internet.defer import inlineCallbacks from twisted.internet.protocol import ReconnectingClientFactory +from twisted.python.components import registerAdapter import runtime.NevowServer as NS -from formless import annotate +from formless import annotate, webform +import formless +from nevow import tags, url, static mandatoryConfigItems = ["ID", "active", "realm", "url"] _transportFactory = None _WampSession = None _PySrv = None +WorkingDir = None + +# Find pre-existing project WAMP config file _WampConf = None _WampSecret = None @@ -61,6 +67,14 @@ ("ResetLogCount", {}) ] +# de-activated dumb wamp config +defaultWampConfig = { + "ID": "wamptest", + "active": False, + "realm": "Automation", + "url": "ws://127.0.0.1:8888" +} + # Those two lists are meant to be filled by customized runtime # or User python code. @@ -164,8 +178,8 @@ del connector -def CheckConfiguration(WSClientConf): - url = WSClientConf["url"] +def CheckConfiguration(WampClientConf): + url = WampClientConf["url"] if not IsCorrectUri(url): raise annotate.ValidateError( {"url":"Invalid URL: {}".format(url)}, @@ -174,34 +188,42 @@ def GetConfiguration(): global lastKnownConfig - WSClientConf = json.load(open(_WampConf)) + if os.path.exists(_WampConf): + WampClientConf = json.load(open(_WampConf)) + else: + WampClientConf = defaultWampConfig + for itemName in mandatoryConfigItems: - if WSClientConf.get(itemName, None) is None : + if WampClientConf.get(itemName, None) is None : raise Exception(_("WAMP configuration error : missing '{}' parameter.").format(itemName)) - CheckConfiguration(WSClientConf) - - lastKnownConfig = WSClientConf.copy() - return WSClientConf - - -def SetConfiguration(WSClientConf): + CheckConfiguration(WampClientConf) + + lastKnownConfig = WampClientConf.copy() + return WampClientConf + + +def SetWampSecret(wampSecret): + with open(os.path.realpath(_WampSecret), 'w') as f: + f.write(wampSecret) + +def SetConfiguration(WampClientConf): global lastKnownConfig - CheckConfiguration(WSClientConf) - - lastKnownConfig = WSClientConf.copy() + CheckConfiguration(WampClientConf) + + lastKnownConfig = WampClientConf.copy() with open(os.path.realpath(_WampConf), 'w') as f: - json.dump(WSClientConf, f, sort_keys=True, indent=4) - if 'active' in WSClientConf and WSClientConf['active']: + json.dump(WampClientConf, f, sort_keys=True, indent=4) + if 'active' in WampClientConf and WampClientConf['active']: if _transportFactory and _WampSession: StopReconnectWampClient() StartReconnectWampClient() else: StopReconnectWampClient() - return WSClientConf + return WampClientConf def LoadWampSecret(secretfname): @@ -217,24 +239,35 @@ def RegisterWampClient(wampconf=None, wampsecret=None): global _WampConf, _WampSecret - if wampsecret: + _WampConfDefault = os.path.join(WorkingDir, "wampconf.json") + _WampSecretDefault = os.path.join(WorkingDir, "wamp.secret") + + # default project's wampconf has precedance over commandline given + if os.path.exists(_WampConfDefault) or wampconf is None: + _WampConf = _WampConfDefault + else : + _WampConf = wampconf + + WampClientConf = GetConfiguration() + + if wampsecret is not None: + WampClientConf["secret"] = LoadWampSecret(wampsecret) _WampSecret = wampsecret - if wampconf: - _WampConf = wampconf - - WSClientConf = GetConfiguration() - - if not WSClientConf["active"]: + else : + if os.path.exists(_WampSecretDefault): + WampClientConf["secret"] = LoadWampSecret(_WampSecretDefault) + else : + print(_("WAMP authentication has no secret configured")) + _WampSecret = _WampSecretDefault + + if not WampClientConf["active"]: print(_("WAMP deactivated in configuration")) return - if _WampSecret is not None: - WSClientConf["secret"] = LoadWampSecret(_WampSecret) - # create a WAMP application session factory component_config = types.ComponentConfig( - realm=WSClientConf["realm"], - extra=WSClientConf) + realm=WampClientConf["realm"], + extra=WampClientConf) session_factory = wamp.ApplicationSessionFactory( config=component_config) session_factory.session = WampSession @@ -243,16 +276,16 @@ ReconnectingWampWebSocketClientFactory( component_config, session_factory, - url=WSClientConf["url"], + url=WampClientConf["url"], serializers=[MsgPackSerializer()]) # start the client from a Twisted endpoint if _transportFactory: conn = connectWS(_transportFactory) - print(_("WAMP client connecting to :"), WSClientConf["url"]) + print(_("WAMP client connecting to :"), WampClientConf["url"]) return True else: - print(_("WAMP client can not connect to :"), WSClientConf["url"]) + print(_("WAMP client can not connect to :"), WampClientConf["url"]) return False @@ -292,7 +325,7 @@ #### WEB CONFIGURATION INTERFACE #### - +WAMP_SECRET_URL = "secret" webExposedConfigItems = ['active', 'url', 'ID'] def wampConfigDefault(ctx,argument): @@ -300,12 +333,37 @@ return lastKnownConfig.get(argument.name, None) def wampConfig(**kwargs): + secretfile_field = kwargs["secretfile"] + if secretfile_field is not None: + secret = secretfile_field.file.read() + SetWampSecret(secret) + newConfig = lastKnownConfig.copy() for argname in webExposedConfigItems: newConfig[argname] = kwargs[argname] SetConfiguration(newConfig) +class FileUploadDownload(annotate.FileUpload): + pass + + +class FileUploadDownloadRenderer(webform.FileUploadRenderer): + def input(self, context, slot, data, name, value): + slot[_("Upload:")] + slot = webform.FileUploadRenderer.input(self, context, slot, data, name, value) + download_url = data.typedValue.getAttribute('download_url') + return slot[tags.a(href=download_url)[_("Download")]] + +registerAdapter(FileUploadDownloadRenderer, FileUploadDownload, formless.iformless.ITypedRenderer) + +def getDownloadUrl(ctx, argument): + if lastKnownConfig is not None : + currentID = lastKnownConfig.get("ID", None) + return url.URL.fromContext(ctx).\ + child(WAMP_SECRET_URL).\ + child(lastKnownConfig["ID"]+".secret") + webFormInterface = [ ("status", annotate.String(label=_("Current status"), @@ -314,6 +372,11 @@ ("ID", annotate.String(label=_("ID"), default = wampConfigDefault)), + ("secretfile", + FileUploadDownload( + label = _("File containing secret for that ID"), + download_url = getDownloadUrl, + )), ("active", annotate.Boolean(label=_("Enable WAMP connection"), default=wampConfigDefault)), @@ -328,3 +391,13 @@ webFormInterface, _("Set"), wampConfig) + + +def deliverWampSecret(ctx, segments): + filename = segments[1].decode('utf-8') + # TODO : SECURITY compare filename to ID and blah... + secret = LoadWampSecret(_WampSecret) + return static.Data(secret, 'application/octet-stream'),() + +NS.customSettingsURLs[WAMP_SECRET_URL] = deliverWampSecret +