71 |
94 |
72 |
95 |
73 class WampSession(wamp.ApplicationSession): |
96 class WampSession(wamp.ApplicationSession): |
74 def onConnect(self): |
97 def onConnect(self): |
75 if "secret" in self.config.extra: |
98 if "secret" in self.config.extra: |
76 user = self.config.extra["ID"].encode('utf8') |
99 user = self.config.extra["ID"] |
77 self.join(u"Automation", [u"wampcra"], user) |
100 self.join(u"Automation", [u"wampcra"], user) |
78 else: |
101 else: |
79 self.join(u"Automation") |
102 self.join(u"Automation") |
80 |
103 |
81 def onChallenge(self, challenge): |
104 def onChallenge(self, challenge): |
82 if challenge.method == u"wampcra": |
105 if challenge.method == u"wampcra": |
83 secret = self.config.extra["secret"].encode('utf8') |
106 if "secret" in self.config.extra: |
84 signature = auth.compute_wcs(secret, challenge.extra['challenge'].encode('utf8')) |
107 secret = self.config.extra["secret"].encode('utf8') |
85 return signature.decode("ascii") |
108 signature = auth.compute_wcs(secret, challenge.extra['challenge'].encode('utf8')) |
|
109 return signature.decode("ascii") |
|
110 else: |
|
111 raise Exception("no secret given for authentication") |
86 else: |
112 else: |
87 raise Exception("don't know how to handle authmethod {}".format(challenge.method)) |
113 raise Exception("don't know how to handle authmethod {}".format(challenge.method)) |
88 |
114 |
89 @inlineCallbacks |
115 @inlineCallbacks |
90 def onJoin(self, details): |
116 def onJoin(self, details): |
91 global _WampSession |
117 global _WampSession |
92 _WampSession = self |
118 _WampSession = self |
93 ID = self.config.extra["ID"] |
119 ID = self.config.extra["ID"] |
94 print('WAMP session joined by :', ID) |
120 |
95 for name in ExposedCalls: |
121 for name, kwargs in ExposedCalls: |
96 regoption = types.RegisterOptions(u'exact', u'last') |
122 try: |
97 yield self.register(GetCallee(name), u'.'.join((ID, name)), regoption) |
123 registerOptions = types.RegisterOptions(**kwargs) |
|
124 except TypeError as e: |
|
125 registerOptions = None |
|
126 print(_("TypeError register option: {}".format(e))) |
|
127 |
|
128 yield self.register(GetCallee(name), u'.'.join((ID, name)), registerOptions) |
98 |
129 |
99 for name in SubscribedEvents: |
130 for name in SubscribedEvents: |
100 yield self.subscribe(GetCallee(name), unicode(name)) |
131 yield self.subscribe(GetCallee(name), unicode(name)) |
101 |
132 |
102 for func in DoOnJoin: |
133 for func in DoOnJoin: |
103 yield func(self) |
134 yield func(self) |
104 |
135 |
|
136 print(_('WAMP session joined (%s) by:' % time.ctime()), ID) |
|
137 |
105 def onLeave(self, details): |
138 def onLeave(self, details): |
106 global _WampSession |
139 global _WampSession, _transportFactory |
|
140 super(WampSession, self).onLeave(details) |
107 _WampSession = None |
141 _WampSession = None |
|
142 _transportFactory = None |
108 print(_('WAMP session left')) |
143 print(_('WAMP session left')) |
109 |
144 |
110 |
145 |
111 class ReconnectingWampWebSocketClientFactory(WampWebSocketClientFactory, ReconnectingClientFactory): |
146 class ReconnectingWampWebSocketClientFactory(WampWebSocketClientFactory, ReconnectingClientFactory): |
|
147 def __init__(self, config, *args, **kwargs): |
|
148 global _transportFactory |
|
149 WampWebSocketClientFactory.__init__(self, *args, **kwargs) |
|
150 |
|
151 try: |
|
152 protocolOptions = config.extra.get('protocolOptions', None) |
|
153 if protocolOptions: |
|
154 self.setProtocolOptions(**protocolOptions) |
|
155 _transportFactory = self |
|
156 except Exception, e: |
|
157 print(_("Custom protocol options failed :"), e) |
|
158 _transportFactory = None |
|
159 |
|
160 def buildProtocol(self, addr): |
|
161 self.resetDelay() |
|
162 return ReconnectingClientFactory.buildProtocol(self, addr) |
|
163 |
112 def clientConnectionFailed(self, connector, reason): |
164 def clientConnectionFailed(self, connector, reason): |
113 print(_("WAMP Client connection failed (%s) .. retrying .." % time.ctime())) |
165 if self.continueTrying: |
114 ReconnectingClientFactory.clientConnectionFailed(self, connector, reason) |
166 print(_("WAMP Client connection failed (%s) .. retrying ..") % time.ctime()) |
|
167 super(ReconnectingWampWebSocketClientFactory, self).clientConnectionFailed(connector, reason) |
|
168 else: |
|
169 del connector |
115 |
170 |
116 def clientConnectionLost(self, connector, reason): |
171 def clientConnectionLost(self, connector, reason): |
117 print(_("WAMP Client connection lost (%s) .. retrying .." % time.ctime())) |
172 if self.continueTrying: |
118 ReconnectingClientFactory.clientConnectionFailed(self, connector, reason) |
173 print(_("WAMP Client connection lost (%s) .. retrying ..") % time.ctime()) |
119 |
174 super(ReconnectingWampWebSocketClientFactory, self).clientConnectionFailed(connector, reason) |
120 |
175 else: |
121 def LoadWampClientConf(wampconf): |
176 del connector |
122 try: |
177 |
123 WSClientConf = json.load(open(wampconf)) |
178 |
124 return WSClientConf |
179 def CheckConfiguration(WampClientConf): |
125 except ValueError, ve: |
180 url = WampClientConf["url"] |
126 print(_("WAMP load error: "), ve) |
181 if not IsCorrectUri(url): |
127 return None |
182 raise annotate.ValidateError( |
128 except Exception: |
183 {"url":"Invalid URL: {}".format(url)}, |
129 return None |
184 _("WAMP configuration error:")) |
|
185 |
|
186 def GetConfiguration(): |
|
187 global lastKnownConfig |
|
188 |
|
189 if os.path.exists(_WampConf): |
|
190 WampClientConf = json.load(open(_WampConf)) |
|
191 else: |
|
192 WampClientConf = defaultWampConfig.copy() |
|
193 |
|
194 for itemName in mandatoryConfigItems: |
|
195 if WampClientConf.get(itemName, None) is None : |
|
196 raise Exception(_("WAMP configuration error : missing '{}' parameter.").format(itemName)) |
|
197 |
|
198 CheckConfiguration(WampClientConf) |
|
199 |
|
200 lastKnownConfig = WampClientConf.copy() |
|
201 return WampClientConf |
|
202 |
|
203 |
|
204 def SetWampSecret(wampSecret): |
|
205 with open(os.path.realpath(_WampSecret), 'w') as f: |
|
206 f.write(wampSecret) |
|
207 |
|
208 def SetConfiguration(WampClientConf): |
|
209 global lastKnownConfig |
|
210 |
|
211 CheckConfiguration(WampClientConf) |
|
212 |
|
213 lastKnownConfig = WampClientConf.copy() |
|
214 |
|
215 with open(os.path.realpath(_WampConf), 'w') as f: |
|
216 json.dump(WampClientConf, f, sort_keys=True, indent=4) |
|
217 if 'active' in WampClientConf and WampClientConf['active']: |
|
218 if _transportFactory and _WampSession: |
|
219 StopReconnectWampClient() |
|
220 StartReconnectWampClient() |
|
221 else: |
|
222 StopReconnectWampClient() |
|
223 |
|
224 return WampClientConf |
130 |
225 |
131 |
226 |
132 def LoadWampSecret(secretfname): |
227 def LoadWampSecret(secretfname): |
133 try: |
228 WSClientWampSecret = open(secretfname, 'rb').read() |
134 WSClientWampSecret = open(secretfname, 'rb').read() |
229 if len(WSClientWampSecret) == 0 : |
135 return WSClientWampSecret |
230 raise Exception(_("WAMP secret empty")) |
136 except ValueError, ve: |
231 return WSClientWampSecret |
137 print(_("Wamp secret load error:"), ve) |
232 |
138 return None |
233 |
139 except Exception: |
234 def IsCorrectUri(uri): |
140 return None |
235 return re.match(r'wss?://[^\s?:#-]+(:[0-9]+)?(/[^\s]*)?$', uri) is not None |
141 |
236 |
142 |
237 |
143 def RegisterWampClient(wampconf, secretfname): |
238 def RegisterWampClient(wampconf=None, wampsecret=None): |
144 |
239 global _WampConf, _WampSecret |
145 WSClientConf = LoadWampClientConf(wampconf) |
240 _WampConfDefault = os.path.join(WorkingDir, "wampconf.json") |
146 |
241 _WampSecretDefault = os.path.join(WorkingDir, "wamp.secret") |
147 if not WSClientConf: |
242 |
148 print(_("WAMP client connection not established!")) |
243 # set config file path only if not already set |
|
244 if _WampConf is None: |
|
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 # set secret file path only if not already set |
|
254 if _WampSecret is None: |
|
255 # default project's wamp secret also has precedance over commandline given |
|
256 if os.path.exists(_WampSecretDefault): |
|
257 _WampSecret = _WampSecretDefault |
|
258 else: |
|
259 _WampSecret = wampsecret |
|
260 |
|
261 if _WampSecret is not None: |
|
262 WampClientConf["secret"] = LoadWampSecret(_WampSecret) |
|
263 else : |
|
264 print(_("WAMP authentication has no secret configured")) |
|
265 _WampSecret = _WampSecretDefault |
|
266 |
|
267 if not WampClientConf["active"]: |
|
268 print(_("WAMP deactivated in configuration")) |
149 return |
269 return |
150 |
|
151 WampSecret = LoadWampSecret(secretfname) |
|
152 |
|
153 if WampSecret is not None: |
|
154 WSClientConf["secret"] = WampSecret |
|
155 |
270 |
156 # create a WAMP application session factory |
271 # create a WAMP application session factory |
157 component_config = types.ComponentConfig( |
272 component_config = types.ComponentConfig( |
158 realm=WSClientConf["realm"], |
273 realm=WampClientConf["realm"], |
159 extra=WSClientConf) |
274 extra=WampClientConf) |
160 session_factory = wamp.ApplicationSessionFactory( |
275 session_factory = wamp.ApplicationSessionFactory( |
161 config=component_config) |
276 config=component_config) |
162 session_factory.session = WampSession |
277 session_factory.session = WampSession |
163 |
278 |
164 # create a WAMP-over-WebSocket transport client factory |
279 # create a WAMP-over-WebSocket transport client factory |
165 transport_factory = ReconnectingWampWebSocketClientFactory( |
280 ReconnectingWampWebSocketClientFactory( |
|
281 component_config, |
166 session_factory, |
282 session_factory, |
167 url=WSClientConf["url"], |
283 url=WampClientConf["url"], |
168 serializers=[MsgPackSerializer()]) |
284 serializers=[MsgPackSerializer()]) |
169 |
285 |
170 # start the client from a Twisted endpoint |
286 # start the client from a Twisted endpoint |
171 conn = connectWS(transport_factory) |
287 if _transportFactory: |
172 print(_("WAMP client connecting to :"), WSClientConf["url"]) |
288 conn = connectWS(_transportFactory) |
173 return conn |
289 print(_("WAMP client connecting to :"), WampClientConf["url"]) |
|
290 return True |
|
291 else: |
|
292 print(_("WAMP client can not connect to :"), WampClientConf["url"]) |
|
293 return False |
|
294 |
|
295 |
|
296 def StopReconnectWampClient(): |
|
297 if _transportFactory is not None : |
|
298 _transportFactory.stopTrying() |
|
299 if _WampSession is not None : |
|
300 _WampSession.leave() |
|
301 |
|
302 |
|
303 def StartReconnectWampClient(): |
|
304 if _WampSession: |
|
305 # do reconnect |
|
306 _WampSession.disconnect() |
|
307 return True |
|
308 else: |
|
309 # do connect |
|
310 RegisterWampClient() |
|
311 return True |
174 |
312 |
175 |
313 |
176 def GetSession(): |
314 def GetSession(): |
177 return _WampSession |
315 return _WampSession |
178 |
316 |
|
317 def getWampStatus(): |
|
318 if _transportFactory is not None : |
|
319 if _WampSession is not None : |
|
320 if _WampSession.is_attached() : |
|
321 return "Attached" |
|
322 return "Established" |
|
323 return "Connecting" |
|
324 return "Disconnected" |
|
325 |
179 |
326 |
180 def SetServer(pysrv): |
327 def SetServer(pysrv): |
181 global _PySrv |
|
182 _PySrv = pysrv |
328 _PySrv = pysrv |
|
329 |
|
330 |
|
331 #### WEB CONFIGURATION INTERFACE #### |
|
332 WAMP_SECRET_URL = "secret" |
|
333 webExposedConfigItems = ['active', 'url', 'ID'] |
|
334 |
|
335 def wampConfigDefault(ctx,argument): |
|
336 if lastKnownConfig is not None : |
|
337 return lastKnownConfig.get(argument.name, None) |
|
338 |
|
339 def wampConfig(**kwargs): |
|
340 secretfile_field = kwargs["secretfile"] |
|
341 if secretfile_field is not None: |
|
342 secretfile = getattr(secretfile_field, "file", None) |
|
343 if secretfile is not None: |
|
344 secret = secretfile_field.file.read() |
|
345 SetWampSecret(secret) |
|
346 |
|
347 newConfig = lastKnownConfig.copy() |
|
348 for argname in webExposedConfigItems: |
|
349 arg = kwargs.get(argname, None) |
|
350 if arg is not None : |
|
351 newConfig[argname] = arg |
|
352 |
|
353 SetConfiguration(newConfig) |
|
354 |
|
355 class FileUploadDownload(annotate.FileUpload): |
|
356 pass |
|
357 |
|
358 |
|
359 class FileUploadDownloadRenderer(webform.FileUploadRenderer): |
|
360 def input(self, context, slot, data, name, value): |
|
361 slot[_("Upload:")] |
|
362 slot = webform.FileUploadRenderer.input(self, context, slot, data, name, value) |
|
363 download_url = data.typedValue.getAttribute('download_url') |
|
364 return slot[tags.a(href=download_url)[_("Download")]] |
|
365 |
|
366 registerAdapter(FileUploadDownloadRenderer, FileUploadDownload, formless.iformless.ITypedRenderer) |
|
367 |
|
368 def getDownloadUrl(ctx, argument): |
|
369 if lastKnownConfig is not None : |
|
370 return url.URL.fromContext(ctx).\ |
|
371 child(WAMP_SECRET_URL).\ |
|
372 child(lastKnownConfig["ID"]+".secret") |
|
373 |
|
374 webFormInterface = [ |
|
375 ("status", |
|
376 annotate.String(label=_("Current status"), |
|
377 immutable = True, |
|
378 default = lambda *k:getWampStatus())), |
|
379 ("ID", |
|
380 annotate.String(label=_("ID"), |
|
381 default = wampConfigDefault)), |
|
382 ("secretfile", |
|
383 FileUploadDownload( |
|
384 label = _("File containing secret for that ID"), |
|
385 download_url = getDownloadUrl, |
|
386 )), |
|
387 ("active", |
|
388 annotate.Boolean(label=_("Enable WAMP connection"), |
|
389 default=wampConfigDefault)), |
|
390 ("url", |
|
391 annotate.String(label=_("WAMP Server URL"), |
|
392 default=wampConfigDefault))] |
|
393 |
|
394 |
|
395 def deliverWampSecret(ctx, segments): |
|
396 filename = segments[1].decode('utf-8') |
|
397 # FIXME: compare filename to ID+".secret" |
|
398 # for now all url under /secret returns the secret |
|
399 |
|
400 # TODO: make beutifull message in case of exception |
|
401 # while loading secret (if empty or dont exist) |
|
402 secret = LoadWampSecret(_WampSecret) |
|
403 return static.Data(secret, 'application/octet-stream'),() |
|
404 |
|
405 def RegisterWebSettings(NS): |
|
406 NS.ConfigurableSettings.addExtension( |
|
407 "wamp", |
|
408 _("Wamp Settings"), |
|
409 webFormInterface, |
|
410 _("Set"), |
|
411 wampConfig) |
|
412 |
|
413 |
|
414 NS.customSettingsURLs[WAMP_SECRET_URL] = deliverWampSecret |
|
415 |