runtime/WampClient.py
branchnevow_service_rework
changeset 2220 985f234b0d09
parent 2218 7a4deed94eb2
child 2221 e03f7649bfb3
equal deleted inserted replaced
2219:73042b2d8d65 2220:985f234b0d09
    32 from autobahn.twisted.websocket import WampWebSocketClientFactory, connectWS
    32 from autobahn.twisted.websocket import WampWebSocketClientFactory, connectWS
    33 from autobahn.wamp import types, auth
    33 from autobahn.wamp import types, auth
    34 from autobahn.wamp.serializer import MsgPackSerializer
    34 from autobahn.wamp.serializer import MsgPackSerializer
    35 from twisted.internet.defer import inlineCallbacks
    35 from twisted.internet.defer import inlineCallbacks
    36 from twisted.internet.protocol import ReconnectingClientFactory
    36 from twisted.internet.protocol import ReconnectingClientFactory
       
    37 from twisted.python.components import registerAdapter
    37 
    38 
    38 import runtime.NevowServer as NS
    39 import runtime.NevowServer as NS
    39 
    40 
    40 from formless import annotate
    41 from formless import annotate, webform
       
    42 import formless
       
    43 from nevow import tags, url, static
    41 
    44 
    42 mandatoryConfigItems = ["ID", "active", "realm", "url"]
    45 mandatoryConfigItems = ["ID", "active", "realm", "url"]
    43 
    46 
    44 _transportFactory = None
    47 _transportFactory = None
    45 _WampSession = None
    48 _WampSession = None
    46 _PySrv = None
    49 _PySrv = None
       
    50 WorkingDir = None
       
    51 
       
    52 # Find pre-existing project WAMP config file
    47 _WampConf = None
    53 _WampConf = None
    48 _WampSecret = None
    54 _WampSecret = None
    49 
    55 
    50 ExposedCalls = [
    56 ExposedCalls = [
    51     ("StartPLC", {}),
    57     ("StartPLC", {}),
    59     ("RemoteExec", {}),
    65     ("RemoteExec", {}),
    60     ("GetLogMessage", {}),
    66     ("GetLogMessage", {}),
    61     ("ResetLogCount", {})
    67     ("ResetLogCount", {})
    62 ]
    68 ]
    63 
    69 
       
    70 # de-activated dumb wamp config
       
    71 defaultWampConfig = {
       
    72     "ID": "wamptest", 
       
    73     "active": False, 
       
    74     "realm": "Automation", 
       
    75     "url": "ws://127.0.0.1:8888"
       
    76 }
       
    77 
    64 # Those two lists are meant to be filled by customized runtime
    78 # Those two lists are meant to be filled by customized runtime
    65 # or User python code.
    79 # or User python code.
    66 
    80 
    67 """ crossbar Events to register to """
    81 """ crossbar Events to register to """
    68 SubscribedEvents = []
    82 SubscribedEvents = []
   162             super(ReconnectingWampWebSocketClientFactory, self).clientConnectionFailed(connector, reason)
   176             super(ReconnectingWampWebSocketClientFactory, self).clientConnectionFailed(connector, reason)
   163         else:
   177         else:
   164             del connector
   178             del connector
   165 
   179 
   166 
   180 
   167 def CheckConfiguration(WSClientConf):
   181 def CheckConfiguration(WampClientConf):
   168     url = WSClientConf["url"]
   182     url = WampClientConf["url"]
   169     if not IsCorrectUri(url):
   183     if not IsCorrectUri(url):
   170         raise annotate.ValidateError(
   184         raise annotate.ValidateError(
   171             {"url":"Invalid URL: {}".format(url)},
   185             {"url":"Invalid URL: {}".format(url)},
   172             _("WAMP confiuration error:"))
   186             _("WAMP confiuration error:"))
   173 
   187 
   174 def GetConfiguration():
   188 def GetConfiguration():
   175     global lastKnownConfig
   189     global lastKnownConfig
   176 
   190 
   177     WSClientConf = json.load(open(_WampConf))
   191     if os.path.exists(_WampConf):
       
   192         WampClientConf = json.load(open(_WampConf))
       
   193     else: 
       
   194         WampClientConf = defaultWampConfig
       
   195 
   178     for itemName in mandatoryConfigItems:
   196     for itemName in mandatoryConfigItems:
   179         if WSClientConf.get(itemName, None) is None :
   197         if WampClientConf.get(itemName, None) is None :
   180             raise Exception(_("WAMP configuration error : missing '{}' parameter.").format(itemName))
   198             raise Exception(_("WAMP configuration error : missing '{}' parameter.").format(itemName))
   181 
   199 
   182     CheckConfiguration(WSClientConf)
   200     CheckConfiguration(WampClientConf)
   183 
   201 
   184     lastKnownConfig = WSClientConf.copy()
   202     lastKnownConfig = WampClientConf.copy()
   185     return WSClientConf
   203     return WampClientConf
   186 
   204 
   187 
   205 
   188 def SetConfiguration(WSClientConf):
   206 def SetWampSecret(wampSecret):
       
   207     with open(os.path.realpath(_WampSecret), 'w') as f:
       
   208         f.write(wampSecret)
       
   209 
       
   210 def SetConfiguration(WampClientConf):
   189     global lastKnownConfig
   211     global lastKnownConfig
   190 
   212 
   191     CheckConfiguration(WSClientConf)
   213     CheckConfiguration(WampClientConf)
   192 
   214 
   193     lastKnownConfig = WSClientConf.copy()
   215     lastKnownConfig = WampClientConf.copy()
   194     
   216     
   195     with open(os.path.realpath(_WampConf), 'w') as f:
   217     with open(os.path.realpath(_WampConf), 'w') as f:
   196         json.dump(WSClientConf, f, sort_keys=True, indent=4)
   218         json.dump(WampClientConf, f, sort_keys=True, indent=4)
   197     if 'active' in WSClientConf and WSClientConf['active']:
   219     if 'active' in WampClientConf and WampClientConf['active']:
   198         if _transportFactory and _WampSession:
   220         if _transportFactory and _WampSession:
   199             StopReconnectWampClient()
   221             StopReconnectWampClient()
   200         StartReconnectWampClient()
   222         StartReconnectWampClient()
   201     else:
   223     else:
   202         StopReconnectWampClient()
   224         StopReconnectWampClient()
   203 
   225 
   204     return WSClientConf
   226     return WampClientConf
   205 
   227 
   206 
   228 
   207 def LoadWampSecret(secretfname):
   229 def LoadWampSecret(secretfname):
   208     WSClientWampSecret = open(secretfname, 'rb').read()
   230     WSClientWampSecret = open(secretfname, 'rb').read()
   209     if len(WSClientWampSecret) == 0 :
   231     if len(WSClientWampSecret) == 0 :
   215     return re.match(r'wss?://[^\s?:#-]+(:[0-9]+)?(/[^\s]*)?$', uri) is not None
   237     return re.match(r'wss?://[^\s?:#-]+(:[0-9]+)?(/[^\s]*)?$', uri) is not None
   216 
   238 
   217 
   239 
   218 def RegisterWampClient(wampconf=None, wampsecret=None):
   240 def RegisterWampClient(wampconf=None, wampsecret=None):
   219     global _WampConf, _WampSecret
   241     global _WampConf, _WampSecret
   220     if wampsecret:
   242     _WampConfDefault = os.path.join(WorkingDir, "wampconf.json")
       
   243     _WampSecretDefault = os.path.join(WorkingDir, "wamp.secret")
       
   244 
       
   245     # default project's wampconf has precedance over commandline given
       
   246     if os.path.exists(_WampConfDefault) or wampconf is None:
       
   247         _WampConf = _WampConfDefault
       
   248     else :
       
   249         _WampConf = wampconf
       
   250 
       
   251     WampClientConf = GetConfiguration()
       
   252 
       
   253     if wampsecret is not None:
       
   254         WampClientConf["secret"] = LoadWampSecret(wampsecret)
   221         _WampSecret = wampsecret
   255         _WampSecret = wampsecret
   222     if wampconf:
   256     else :
   223         _WampConf = wampconf
   257         if os.path.exists(_WampSecretDefault):
   224 
   258             WampClientConf["secret"] = LoadWampSecret(_WampSecretDefault)
   225     WSClientConf = GetConfiguration()
   259         else :
   226 
   260             print(_("WAMP authentication has no secret configured"))
   227     if not WSClientConf["active"]:
   261         _WampSecret = _WampSecretDefault
       
   262 
       
   263     if not WampClientConf["active"]:
   228         print(_("WAMP deactivated in configuration"))
   264         print(_("WAMP deactivated in configuration"))
   229         return
   265         return
   230 
   266 
   231     if _WampSecret is not None:
       
   232         WSClientConf["secret"] = LoadWampSecret(_WampSecret)
       
   233 
       
   234     # create a WAMP application session factory
   267     # create a WAMP application session factory
   235     component_config = types.ComponentConfig(
   268     component_config = types.ComponentConfig(
   236         realm=WSClientConf["realm"],
   269         realm=WampClientConf["realm"],
   237         extra=WSClientConf)
   270         extra=WampClientConf)
   238     session_factory = wamp.ApplicationSessionFactory(
   271     session_factory = wamp.ApplicationSessionFactory(
   239         config=component_config)
   272         config=component_config)
   240     session_factory.session = WampSession
   273     session_factory.session = WampSession
   241 
   274 
   242     # create a WAMP-over-WebSocket transport client factory
   275     # create a WAMP-over-WebSocket transport client factory
   243     ReconnectingWampWebSocketClientFactory(
   276     ReconnectingWampWebSocketClientFactory(
   244         component_config,
   277         component_config,
   245         session_factory,
   278         session_factory,
   246         url=WSClientConf["url"],
   279         url=WampClientConf["url"],
   247         serializers=[MsgPackSerializer()])
   280         serializers=[MsgPackSerializer()])
   248 
   281 
   249     # start the client from a Twisted endpoint
   282     # start the client from a Twisted endpoint
   250     if _transportFactory:
   283     if _transportFactory:
   251         conn = connectWS(_transportFactory)
   284         conn = connectWS(_transportFactory)
   252         print(_("WAMP client connecting to :"), WSClientConf["url"])
   285         print(_("WAMP client connecting to :"), WampClientConf["url"])
   253         return True
   286         return True
   254     else:
   287     else:
   255         print(_("WAMP client can not connect to :"), WSClientConf["url"])
   288         print(_("WAMP client can not connect to :"), WampClientConf["url"])
   256         return False
   289         return False
   257 
   290 
   258 
   291 
   259 def StopReconnectWampClient():
   292 def StopReconnectWampClient():
   260     if _transportFactory is not None :
   293     if _transportFactory is not None :
   290 def SetServer(pysrv):
   323 def SetServer(pysrv):
   291     _PySrv = pysrv
   324     _PySrv = pysrv
   292 
   325 
   293 
   326 
   294 #### WEB CONFIGURATION INTERFACE ####
   327 #### WEB CONFIGURATION INTERFACE ####
   295 
   328 WAMP_SECRET_URL = "secret"
   296 webExposedConfigItems = ['active', 'url', 'ID']
   329 webExposedConfigItems = ['active', 'url', 'ID']
   297 
   330 
   298 def wampConfigDefault(ctx,argument):
   331 def wampConfigDefault(ctx,argument):
   299     if lastKnownConfig is not None :
   332     if lastKnownConfig is not None :
   300         return lastKnownConfig.get(argument.name, None)
   333         return lastKnownConfig.get(argument.name, None)
   301 
   334 
   302 def wampConfig(**kwargs):
   335 def wampConfig(**kwargs):
       
   336     secretfile_field = kwargs["secretfile"]
       
   337     if secretfile_field is not None:
       
   338         secret = secretfile_field.file.read()
       
   339         SetWampSecret(secret)
       
   340 
   303     newConfig = lastKnownConfig.copy()
   341     newConfig = lastKnownConfig.copy()
   304     for argname in webExposedConfigItems:
   342     for argname in webExposedConfigItems:
   305         newConfig[argname] = kwargs[argname]
   343         newConfig[argname] = kwargs[argname]
   306 
   344 
   307     SetConfiguration(newConfig)
   345     SetConfiguration(newConfig)
       
   346 
       
   347 class FileUploadDownload(annotate.FileUpload):
       
   348     pass
       
   349 
       
   350 
       
   351 class FileUploadDownloadRenderer(webform.FileUploadRenderer):
       
   352     def input(self, context, slot, data, name, value):
       
   353         slot[_("Upload:")]
       
   354         slot = webform.FileUploadRenderer.input(self, context, slot, data, name, value)
       
   355         download_url = data.typedValue.getAttribute('download_url')
       
   356         return slot[tags.a(href=download_url)[_("Download")]]
       
   357 
       
   358 registerAdapter(FileUploadDownloadRenderer, FileUploadDownload, formless.iformless.ITypedRenderer)
       
   359            
       
   360 def getDownloadUrl(ctx, argument):
       
   361     if lastKnownConfig is not None :
       
   362         currentID = lastKnownConfig.get("ID", None)
       
   363         return url.URL.fromContext(ctx).\
       
   364             child(WAMP_SECRET_URL).\
       
   365             child(lastKnownConfig["ID"]+".secret")
   308 
   366 
   309 webFormInterface = [
   367 webFormInterface = [
   310     ("status",
   368     ("status",
   311        annotate.String(label=_("Current status"),
   369        annotate.String(label=_("Current status"),
   312                        immutable = True,
   370                        immutable = True,
   313                        default = lambda *k:getWampStatus())),
   371                        default = lambda *k:getWampStatus())),
   314     ("ID",
   372     ("ID",
   315        annotate.String(label=_("ID"),
   373        annotate.String(label=_("ID"),
   316                        default = wampConfigDefault)),
   374                        default = wampConfigDefault)),
       
   375     ("secretfile",
       
   376        FileUploadDownload(
       
   377            label = _("File containing secret for that ID"),
       
   378            download_url = getDownloadUrl,
       
   379        )),
   317     ("active",
   380     ("active",
   318        annotate.Boolean(label=_("Enable WAMP connection"),
   381        annotate.Boolean(label=_("Enable WAMP connection"),
   319                         default=wampConfigDefault)),
   382                         default=wampConfigDefault)),
   320     ("url",
   383     ("url",
   321        annotate.String(label=_("WAMP Server URL"),
   384        annotate.String(label=_("WAMP Server URL"),
   326     "wamp", 
   389     "wamp", 
   327     _("Wamp Settings"),
   390     _("Wamp Settings"),
   328     webFormInterface,
   391     webFormInterface,
   329     _("Set"),
   392     _("Set"),
   330     wampConfig)
   393     wampConfig)
       
   394 
       
   395 
       
   396 def deliverWampSecret(ctx, segments):
       
   397     filename = segments[1].decode('utf-8')
       
   398     # TODO : SECURITY compare filename to ID and blah...
       
   399     secret = LoadWampSecret(_WampSecret)
       
   400     return static.Data(secret, 'application/octet-stream'),()
       
   401 
       
   402 NS.customSettingsURLs[WAMP_SECRET_URL] = deliverWampSecret
       
   403