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. nevow_service_rework
authorEdouard Tisserant
Tue, 10 Jul 2018 12:54:05 +0200
branchnevow_service_rework
changeset 2220 985f234b0d09
parent 2219 73042b2d8d65
child 2221 e03f7649bfb3
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.
Beremiz_service.py
runtime/WampClient.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()
--- 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
+