runtime/WampClient.py
changeset 2248 d9353e440887
parent 2223 909216419e45
child 2260 74205edac761
equal deleted inserted replaced
2247:921ba5658183 2248:d9353e440887
    65     ("ResetLogCount", {})
    65     ("ResetLogCount", {})
    66 ]
    66 ]
    67 
    67 
    68 # de-activated dumb wamp config
    68 # de-activated dumb wamp config
    69 defaultWampConfig = {
    69 defaultWampConfig = {
    70     "ID": "wamptest", 
    70     "ID": "wamptest",
    71     "active": False, 
    71     "active": False,
    72     "realm": "Automation", 
    72     "realm": "Automation",
    73     "url": "ws://127.0.0.1:8888"
    73     "url": "ws://127.0.0.1:8888"
    74 }
    74 }
    75 
    75 
    76 # Those two lists are meant to be filled by customized runtime
    76 # Those two lists are meant to be filled by customized runtime
    77 # or User python code.
    77 # or User python code.
    81 
    81 
    82 """ things to do on join (callables) """
    82 """ things to do on join (callables) """
    83 DoOnJoin = []
    83 DoOnJoin = []
    84 
    84 
    85 lastKnownConfig = None
    85 lastKnownConfig = None
       
    86 
    86 
    87 
    87 def GetCallee(name):
    88 def GetCallee(name):
    88     """ Get Callee or Subscriber corresponding to '.' spearated object path """
    89     """ Get Callee or Subscriber corresponding to '.' spearated object path """
    89     names = name.split('.')
    90     names = name.split('.')
    90     obj = _PySrv.plcobj
    91     obj = _PySrv.plcobj
    92         obj = getattr(obj, names.pop(0))
    93         obj = getattr(obj, names.pop(0))
    93     return obj
    94     return obj
    94 
    95 
    95 
    96 
    96 class WampSession(wamp.ApplicationSession):
    97 class WampSession(wamp.ApplicationSession):
       
    98 
    97     def onConnect(self):
    99     def onConnect(self):
    98         if "secret" in self.config.extra:
   100         if "secret" in self.config.extra:
    99             user = self.config.extra["ID"]
   101             user = self.config.extra["ID"]
   100             self.join(u"Automation", [u"wampcra"], user)
   102             self.join(u"Automation", [u"wampcra"], user)
   101         else:
   103         else:
   103 
   105 
   104     def onChallenge(self, challenge):
   106     def onChallenge(self, challenge):
   105         if challenge.method == u"wampcra":
   107         if challenge.method == u"wampcra":
   106             if "secret" in self.config.extra:
   108             if "secret" in self.config.extra:
   107                 secret = self.config.extra["secret"].encode('utf8')
   109                 secret = self.config.extra["secret"].encode('utf8')
   108                 signature = auth.compute_wcs(secret, challenge.extra['challenge'].encode('utf8'))
   110                 signature = auth.compute_wcs(
       
   111                     secret, challenge.extra['challenge'].encode('utf8'))
   109                 return signature.decode("ascii")
   112                 return signature.decode("ascii")
   110             else:
   113             else:
   111                 raise Exception("no secret given for authentication")
   114                 raise Exception("no secret given for authentication")
   112         else:
   115         else:
   113             raise Exception("don't know how to handle authmethod {}".format(challenge.method))
   116             raise Exception(
       
   117                 "don't know how to handle authmethod {}".format(challenge.method))
   114 
   118 
   115     @inlineCallbacks
   119     @inlineCallbacks
   116     def onJoin(self, details):
   120     def onJoin(self, details):
   117         global _WampSession
   121         global _WampSession
   118         _WampSession = self
   122         _WampSession = self
   142         _transportFactory = None
   146         _transportFactory = None
   143         print(_('WAMP session left'))
   147         print(_('WAMP session left'))
   144 
   148 
   145 
   149 
   146 class ReconnectingWampWebSocketClientFactory(WampWebSocketClientFactory, ReconnectingClientFactory):
   150 class ReconnectingWampWebSocketClientFactory(WampWebSocketClientFactory, ReconnectingClientFactory):
       
   151 
   147     def __init__(self, config, *args, **kwargs):
   152     def __init__(self, config, *args, **kwargs):
   148         global _transportFactory
   153         global _transportFactory
   149         WampWebSocketClientFactory.__init__(self, *args, **kwargs)
   154         WampWebSocketClientFactory.__init__(self, *args, **kwargs)
   150 
   155 
   151         try:
   156         try:
   161         self.resetDelay()
   166         self.resetDelay()
   162         return ReconnectingClientFactory.buildProtocol(self, addr)
   167         return ReconnectingClientFactory.buildProtocol(self, addr)
   163 
   168 
   164     def clientConnectionFailed(self, connector, reason):
   169     def clientConnectionFailed(self, connector, reason):
   165         if self.continueTrying:
   170         if self.continueTrying:
   166             print(_("WAMP Client connection failed (%s) .. retrying ..") % time.ctime())
   171             print(_("WAMP Client connection failed (%s) .. retrying ..") %
   167             super(ReconnectingWampWebSocketClientFactory, self).clientConnectionFailed(connector, reason)
   172                   time.ctime())
       
   173             super(ReconnectingWampWebSocketClientFactory,
       
   174                   self).clientConnectionFailed(connector, reason)
   168         else:
   175         else:
   169             del connector
   176             del connector
   170 
   177 
   171     def clientConnectionLost(self, connector, reason):
   178     def clientConnectionLost(self, connector, reason):
   172         if self.continueTrying:
   179         if self.continueTrying:
   173             print(_("WAMP Client connection lost (%s) .. retrying ..") % time.ctime())
   180             print(_("WAMP Client connection lost (%s) .. retrying ..") %
   174             super(ReconnectingWampWebSocketClientFactory, self).clientConnectionFailed(connector, reason)
   181                   time.ctime())
       
   182             super(ReconnectingWampWebSocketClientFactory,
       
   183                   self).clientConnectionFailed(connector, reason)
   175         else:
   184         else:
   176             del connector
   185             del connector
   177 
   186 
   178 
   187 
   179 def CheckConfiguration(WampClientConf):
   188 def CheckConfiguration(WampClientConf):
   180     url = WampClientConf["url"]
   189     url = WampClientConf["url"]
   181     if not IsCorrectUri(url):
   190     if not IsCorrectUri(url):
   182         raise annotate.ValidateError(
   191         raise annotate.ValidateError(
   183             {"url":"Invalid URL: {}".format(url)},
   192             {"url": "Invalid URL: {}".format(url)},
   184             _("WAMP configuration error:"))
   193             _("WAMP configuration error:"))
       
   194 
   185 
   195 
   186 def GetConfiguration():
   196 def GetConfiguration():
   187     global lastKnownConfig
   197     global lastKnownConfig
   188 
   198 
   189     if os.path.exists(_WampConf):
   199     if os.path.exists(_WampConf):
   190         WampClientConf = json.load(open(_WampConf))
   200         WampClientConf = json.load(open(_WampConf))
   191     else: 
   201     else:
   192         WampClientConf = defaultWampConfig.copy()
   202         WampClientConf = defaultWampConfig.copy()
   193 
   203 
   194     for itemName in mandatoryConfigItems:
   204     for itemName in mandatoryConfigItems:
   195         if WampClientConf.get(itemName, None) is None :
   205         if WampClientConf.get(itemName, None) is None:
   196             raise Exception(_("WAMP configuration error : missing '{}' parameter.").format(itemName))
   206             raise Exception(
       
   207                 _("WAMP configuration error : missing '{}' parameter.").format(itemName))
   197 
   208 
   198     CheckConfiguration(WampClientConf)
   209     CheckConfiguration(WampClientConf)
   199 
   210 
   200     lastKnownConfig = WampClientConf.copy()
   211     lastKnownConfig = WampClientConf.copy()
   201     return WampClientConf
   212     return WampClientConf
   203 
   214 
   204 def SetWampSecret(wampSecret):
   215 def SetWampSecret(wampSecret):
   205     with open(os.path.realpath(_WampSecret), 'w') as f:
   216     with open(os.path.realpath(_WampSecret), 'w') as f:
   206         f.write(wampSecret)
   217         f.write(wampSecret)
   207 
   218 
       
   219 
   208 def SetConfiguration(WampClientConf):
   220 def SetConfiguration(WampClientConf):
   209     global lastKnownConfig
   221     global lastKnownConfig
   210 
   222 
   211     CheckConfiguration(WampClientConf)
   223     CheckConfiguration(WampClientConf)
   212 
   224 
   213     lastKnownConfig = WampClientConf.copy()
   225     lastKnownConfig = WampClientConf.copy()
   214     
   226 
   215     with open(os.path.realpath(_WampConf), 'w') as f:
   227     with open(os.path.realpath(_WampConf), 'w') as f:
   216         json.dump(WampClientConf, f, sort_keys=True, indent=4)
   228         json.dump(WampClientConf, f, sort_keys=True, indent=4)
   217     if 'active' in WampClientConf and WampClientConf['active']:
   229     if 'active' in WampClientConf and WampClientConf['active']:
   218         if _transportFactory and _WampSession:
   230         if _transportFactory and _WampSession:
   219             StopReconnectWampClient()
   231             StopReconnectWampClient()
   224     return WampClientConf
   236     return WampClientConf
   225 
   237 
   226 
   238 
   227 def LoadWampSecret(secretfname):
   239 def LoadWampSecret(secretfname):
   228     WSClientWampSecret = open(secretfname, 'rb').read()
   240     WSClientWampSecret = open(secretfname, 'rb').read()
   229     if len(WSClientWampSecret) == 0 :
   241     if len(WSClientWampSecret) == 0:
   230         raise Exception(_("WAMP secret empty"))
   242         raise Exception(_("WAMP secret empty"))
   231     return WSClientWampSecret
   243     return WSClientWampSecret
   232 
   244 
   233 
   245 
   234 def IsCorrectUri(uri):
   246 def IsCorrectUri(uri):
   250 
   262 
   251     WampClientConf = GetConfiguration()
   263     WampClientConf = GetConfiguration()
   252 
   264 
   253     # set secret file path only if not already set
   265     # set secret file path only if not already set
   254     if _WampSecret is None:
   266     if _WampSecret is None:
   255         # default project's wamp secret also has precedance over commandline given
   267         # default project's wamp secret also
       
   268         # has precedance over commandline given
   256         if os.path.exists(_WampSecretDefault):
   269         if os.path.exists(_WampSecretDefault):
   257             _WampSecret = _WampSecretDefault
   270             _WampSecret = _WampSecretDefault
   258         else:
   271         else:
   259             _WampSecret = wampsecret
   272             _WampSecret = wampsecret
   260 
   273 
   261     if _WampSecret is not None:
   274     if _WampSecret is not None:
   262         WampClientConf["secret"] = LoadWampSecret(_WampSecret)
   275         WampClientConf["secret"] = LoadWampSecret(_WampSecret)
   263     else :
   276     else:
   264         print(_("WAMP authentication has no secret configured"))
   277         print(_("WAMP authentication has no secret configured"))
   265         _WampSecret = _WampSecretDefault
   278         _WampSecret = _WampSecretDefault
   266 
   279 
   267     if not WampClientConf["active"]:
   280     if not WampClientConf["active"]:
   268         print(_("WAMP deactivated in configuration"))
   281         print(_("WAMP deactivated in configuration"))
   283         url=WampClientConf["url"],
   296         url=WampClientConf["url"],
   284         serializers=[MsgPackSerializer()])
   297         serializers=[MsgPackSerializer()])
   285 
   298 
   286     # start the client from a Twisted endpoint
   299     # start the client from a Twisted endpoint
   287     if _transportFactory:
   300     if _transportFactory:
   288         conn = connectWS(_transportFactory)
   301         connectWS(_transportFactory)
   289         print(_("WAMP client connecting to :"), WampClientConf["url"])
   302         print(_("WAMP client connecting to :"), WampClientConf["url"])
   290         return True
   303         return True
   291     else:
   304     else:
   292         print(_("WAMP client can not connect to :"), WampClientConf["url"])
   305         print(_("WAMP client can not connect to :"), WampClientConf["url"])
   293         return False
   306         return False
   294 
   307 
   295 
   308 
   296 def StopReconnectWampClient():
   309 def StopReconnectWampClient():
   297     if _transportFactory is not None :
   310     if _transportFactory is not None:
   298         _transportFactory.stopTrying()
   311         _transportFactory.stopTrying()
   299     if _WampSession is not None :
   312     if _WampSession is not None:
   300         _WampSession.leave()
   313         _WampSession.leave()
   301 
   314 
   302 
   315 
   303 def StartReconnectWampClient():
   316 def StartReconnectWampClient():
   304     if _WampSession:
   317     if _WampSession:
   312 
   325 
   313 
   326 
   314 def GetSession():
   327 def GetSession():
   315     return _WampSession
   328     return _WampSession
   316 
   329 
       
   330 
   317 def getWampStatus():
   331 def getWampStatus():
   318     if _transportFactory is not None :
   332     if _transportFactory is not None:
   319         if _WampSession is not None :
   333         if _WampSession is not None:
   320             if _WampSession.is_attached() :
   334             if _WampSession.is_attached():
   321                 return "Attached"
   335                 return "Attached"
   322             return "Established"
   336             return "Established"
   323         return "Connecting"
   337         return "Connecting"
   324     return "Disconnected"
   338     return "Disconnected"
   325 
   339 
   326 
   340 
   327 def SetServer(pysrv):
   341 def SetServer(pysrv):
   328     _PySrv = pysrv
   342     _PySrv = pysrv
   329 
   343 
   330 
   344 
   331 #### WEB CONFIGURATION INTERFACE ####
   345 # WEB CONFIGURATION INTERFACE
   332 WAMP_SECRET_URL = "secret"
   346 WAMP_SECRET_URL = "secret"
   333 webExposedConfigItems = ['active', 'url', 'ID']
   347 webExposedConfigItems = ['active', 'url', 'ID']
   334 
   348 
   335 def wampConfigDefault(ctx,argument):
   349 
   336     if lastKnownConfig is not None :
   350 def wampConfigDefault(ctx, argument):
       
   351     if lastKnownConfig is not None:
   337         return lastKnownConfig.get(argument.name, None)
   352         return lastKnownConfig.get(argument.name, None)
       
   353 
   338 
   354 
   339 def wampConfig(**kwargs):
   355 def wampConfig(**kwargs):
   340     secretfile_field = kwargs["secretfile"]
   356     secretfile_field = kwargs["secretfile"]
   341     if secretfile_field is not None:
   357     if secretfile_field is not None:
   342         secretfile = getattr(secretfile_field, "file", None)
   358         secretfile = getattr(secretfile_field, "file", None)
   345             SetWampSecret(secret)
   361             SetWampSecret(secret)
   346 
   362 
   347     newConfig = lastKnownConfig.copy()
   363     newConfig = lastKnownConfig.copy()
   348     for argname in webExposedConfigItems:
   364     for argname in webExposedConfigItems:
   349         arg = kwargs.get(argname, None)
   365         arg = kwargs.get(argname, None)
   350         if arg is not None :
   366         if arg is not None:
   351             newConfig[argname] = arg
   367             newConfig[argname] = arg
   352 
   368 
   353     SetConfiguration(newConfig)
   369     SetConfiguration(newConfig)
       
   370 
   354 
   371 
   355 class FileUploadDownload(annotate.FileUpload):
   372 class FileUploadDownload(annotate.FileUpload):
   356     pass
   373     pass
   357 
   374 
   358 
   375 
   359 class FileUploadDownloadRenderer(webform.FileUploadRenderer):
   376 class FileUploadDownloadRenderer(webform.FileUploadRenderer):
       
   377 
   360     def input(self, context, slot, data, name, value):
   378     def input(self, context, slot, data, name, value):
       
   379         # pylint: disable=expression-not-assigned
   361         slot[_("Upload:")]
   380         slot[_("Upload:")]
   362         slot = webform.FileUploadRenderer.input(self, context, slot, data, name, value)
   381         slot = webform.FileUploadRenderer.input(
       
   382             self, context, slot, data, name, value)
   363         download_url = data.typedValue.getAttribute('download_url')
   383         download_url = data.typedValue.getAttribute('download_url')
   364         return slot[tags.a(href=download_url)[_("Download")]]
   384         return slot[tags.a(href=download_url)[_("Download")]]
   365 
   385 
   366 registerAdapter(FileUploadDownloadRenderer, FileUploadDownload, formless.iformless.ITypedRenderer)
   386 registerAdapter(FileUploadDownloadRenderer, FileUploadDownload,
   367            
   387                 formless.iformless.ITypedRenderer)
       
   388 
       
   389 
   368 def getDownloadUrl(ctx, argument):
   390 def getDownloadUrl(ctx, argument):
   369     if lastKnownConfig is not None :
   391     if lastKnownConfig is not None:
   370         return url.URL.fromContext(ctx).\
   392         return url.URL.fromContext(ctx).\
   371             child(WAMP_SECRET_URL).\
   393             child(WAMP_SECRET_URL).\
   372             child(lastKnownConfig["ID"]+".secret")
   394             child(lastKnownConfig["ID"] + ".secret")
   373 
   395 
   374 webFormInterface = [
   396 webFormInterface = [
   375     ("status",
   397     ("status",
   376        annotate.String(label=_("Current status"),
   398      annotate.String(label=_("Current status"),
   377                        immutable = True,
   399                      immutable=True,
   378                        default = lambda *k:getWampStatus())),
   400                      default=lambda *k:getWampStatus())),
   379     ("ID",
   401     ("ID",
   380        annotate.String(label=_("ID"),
   402      annotate.String(label=_("ID"),
   381                        default = wampConfigDefault)),
   403                      default=wampConfigDefault)),
   382     ("secretfile",
   404     ("secretfile",
   383        FileUploadDownload(
   405      FileUploadDownload(label=_("File containing secret for that ID"),
   384            label = _("File containing secret for that ID"),
   406                         download_url=getDownloadUrl)),
   385            download_url = getDownloadUrl,
       
   386        )),
       
   387     ("active",
   407     ("active",
   388        annotate.Boolean(label=_("Enable WAMP connection"),
   408      annotate.Boolean(label=_("Enable WAMP connection"),
   389                         default=wampConfigDefault)),
   409                       default=wampConfigDefault)),
   390     ("url",
   410     ("url",
   391        annotate.String(label=_("WAMP Server URL"),
   411      annotate.String(label=_("WAMP Server URL"),
   392                        default=wampConfigDefault))]
   412                      default=wampConfigDefault))]
   393 
   413 
   394 
   414 
   395 def deliverWampSecret(ctx, segments):
   415 def deliverWampSecret(ctx, segments):
   396     filename = segments[1].decode('utf-8')
   416     # filename = segments[1].decode('utf-8')
       
   417 
   397     # FIXME: compare filename to ID+".secret"
   418     # FIXME: compare filename to ID+".secret"
   398     # for now all url under /secret returns the secret
   419     # for now all url under /secret returns the secret
   399 
   420 
   400     # TODO: make beutifull message in case of exception 
   421     # TODO: make beautifull message in case of exception
   401     # while loading secret (if empty or dont exist)
   422     # while loading secret (if empty or dont exist)
   402     secret = LoadWampSecret(_WampSecret)
   423     secret = LoadWampSecret(_WampSecret)
   403     return static.Data(secret, 'application/octet-stream'),()
   424     return static.Data(secret, 'application/octet-stream'), ()
       
   425 
   404 
   426 
   405 def RegisterWebSettings(NS):
   427 def RegisterWebSettings(NS):
   406     NS.ConfigurableSettings.addExtension(
   428     NS.ConfigurableSettings.addExtension(
   407         "wamp", 
   429         "wamp",
   408         _("Wamp Settings"),
   430         _("Wamp Settings"),
   409         webFormInterface,
   431         webFormInterface,
   410         _("Set"),
   432         _("Set"),
   411         wampConfig)
   433         wampConfig)
   412 
   434 
   413 
       
   414     NS.customSettingsURLs[WAMP_SECRET_URL] = deliverWampSecret
   435     NS.customSettingsURLs[WAMP_SECRET_URL] = deliverWampSecret
   415