5 # Copyright 2012 Intel Corporation.
7 # Licensed under the Apache License, Version 2.0 (the "License");
8 # you may not use this file except in compliance with the License.
9 # You may obtain a copy of the License at
11 # http://www.apache.org/licenses/LICENSE-2.0
13 # Unless required by applicable law or agreed to in writing, software
14 # distributed under the License is distributed on an "AS IS" BASIS,
15 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 # See the License for the specific language governing permissions and
17 # limitations under the License.
19 # Luc Yriarte <luc.yriarte@intel.com>
20 # Christophe Guiraud <christophe.guiraud@intel.com>
24 import argparse, dbus, io, json, sys
26 from twisted.internet import glib2reactor
27 # Configure the twisted mainloop to be run inside the glib mainloop.
28 # This must be done before importing the other twisted modules
29 glib2reactor.install()
30 from twisted.internet import reactor, defer
32 from autobahn.websocket import listenWS
33 from autobahn.wamp import exportRpc, WampServerFactory, WampCraServerProtocol
35 from dbus.mainloop.glib import DBusGMainLoop
38 gobject.threads_init()
44 from twisted.python import log
48 ###############################################################################
54 ###############################################################################
58 for item in list[1:len(list)]:
62 ###############################################################################
70 self.dbusConnexions = {}
72 self.signalHandlers = {}
75 def dbusConnexion(self, busName):
76 if not self.dbusConnexions.has_key(busName):
77 if busName == "session":
78 self.dbusConnexions[busName] = dbus.SessionBus()
79 elif busName == "system":
80 self.dbusConnexions[busName] = dbus.SystemBus()
82 raise Exception("Error: invalid bus: %s" % busName)
83 return self.dbusConnexions[busName]
87 ###############################################################################
88 class DbusSignalHandler:
89 def __init__(self, object, senderName, objectName, interfaceName, signalName):
91 self.id = hashId([senderName, objectName, interfaceName, signalName])
92 # connect dbus proxy object to signal
93 self.proxyObject = object
94 self.proxyObject.connect_to_signal(signalName, self.handleSignal, interfaceName)
97 def handleSignal(self, *args):
98 # publish dbus args under topic hash id
99 factory.dispatch(self.id, json.dumps(args))
103 ###############################################################################
104 class DbusCallHandler:
105 def __init__(self, method, args):
106 # deferred reply to return dbus results
108 self.request = defer.Deferred()
113 def callMethod(self):
114 # dbus method async call
116 self.method(*self.args, reply_handler=self.dbusSuccess, error_handler=self.dbusError)
120 def dbusSuccess(self, *result):
121 # return JSON string result array
122 self.request.callback(json.dumps(result))
126 def dbusError(self, error):
127 # return dbus error message
128 self.request.errback(error.get_dbus_message())
133 ###############################################################################
134 class CloudeebusService:
135 def __init__(self, permissions):
136 self.permissions = permissions;
138 self.proxyObjects = {}
140 self.proxyMethods = {}
142 self.pendingCalls = []
145 def proxyObject(self, busName, serviceName, objectName):
146 id = hashId([serviceName, objectName])
147 if not self.proxyObjects.has_key(id):
149 # check permissions, array.index throws exception
150 self.permissions.index(serviceName)
151 bus = cache.dbusConnexion(busName)
152 self.proxyObjects[id] = bus.get_object(serviceName, objectName)
153 return self.proxyObjects[id]
156 def proxyMethod(self, busName, serviceName, objectName, interfaceName, methodName):
157 id = hashId([serviceName, objectName, interfaceName, methodName])
158 if not self.proxyMethods.has_key(id):
159 obj = self.proxyObject(busName, serviceName, objectName)
160 self.proxyMethods[id] = obj.get_dbus_method(methodName, interfaceName)
161 return self.proxyMethods[id]
165 def dbusRegister(self, list):
166 # read arguments list by position
168 raise Exception("Error: expected arguments: bus, sender, object, interface, signal)")
170 # check if a handler exists
171 sigId = hashId(list[1:5])
172 if cache.signalHandlers.has_key(sigId):
175 # get dbus proxy object
176 object = self.proxyObject(list[0], list[1], list[2])
178 # create a handler that will publish the signal
179 dbusSignalHandler = DbusSignalHandler(object, *list[1:5])
180 cache.signalHandlers[sigId] = dbusSignalHandler
182 return dbusSignalHandler.id
186 def dbusSend(self, list):
187 # clear pending calls
188 for call in self.pendingCalls:
190 self.pendingCalls.remove(call)
192 # read arguments list by position
194 raise Exception("Error: expected arguments: bus, destination, object, interface, message, [args])")
196 # parse JSON arg list
199 args = json.loads(list[5])
201 # get dbus proxy method
202 method = self.proxyMethod(list[0], *list[1:5])
204 # use a deferred call handler to manage dbus results
205 dbusCallHandler = DbusCallHandler(method, args)
206 self.pendingCalls.append(dbusCallHandler)
207 return dbusCallHandler.callMethod()
211 ###############################################################################
212 class CloudeebusServerProtocol(WampCraServerProtocol):
214 def onSessionOpen(self):
215 # CRA authentication options
216 self.clientAuthTimeout = 0
217 self.clientAuthAllowAnonymous = OPENDOOR
218 # CRA authentication init
219 WampCraServerProtocol.onSessionOpen(self)
222 def getAuthPermissions(self, key, extra):
223 return json.loads(extra.get("permissions", "[]"))
226 def getAuthSecret(self, key):
227 secret = CREDENTIALS.get(key, None)
230 # secret must be of str type to be hashed
231 return secret.encode('utf-8')
234 def onAuthenticated(self, key, permissions):
236 # check authentication key
238 raise Exception("Authentication failed")
239 # check permissions, array.index throws exception
240 for req in permissions:
242 # create cloudeebus service instance
243 self.cloudeebusService = CloudeebusService(permissions)
244 # register it for RPC
245 self.registerForRpc(self.cloudeebusService)
246 # register for Publish / Subscribe
247 self.registerForPubSub("", True)
250 def connectionLost(self, reason):
251 WampCraServerProtocol.connectionLost(self, reason)
252 if factory.getConnectionCount() == 0:
257 ###############################################################################
259 if __name__ == '__main__':
263 parser = argparse.ArgumentParser(description='Javascript DBus bridge.')
264 parser.add_argument('-d', '--debug', action='store_true')
265 parser.add_argument('-o', '--opendoor', action='store_true')
266 parser.add_argument('-p', '--port', default='9000')
267 parser.add_argument('-c', '--credentials')
268 parser.add_argument('-w', '--whitelist')
270 args = parser.parse_args(sys.argv[1:])
273 log.startLogging(sys.stdout)
275 OPENDOOR = args.opendoor
278 jfile = open(args.credentials)
279 CREDENTIALS = json.load(jfile)
283 jfile = open(args.whitelist)
284 WHITELIST = json.load(jfile)
287 uri = "ws://localhost:" + args.port
289 factory = WampServerFactory(uri, debugWamp = args.debug)
290 factory.protocol = CloudeebusServerProtocol
291 factory.setProtocolOptions(allowHixie76 = True)
295 DBusGMainLoop(set_as_default=True)