def __call__(self, *args, **kwargs): if len(kwargs) > 0 and len(args) > 0: raise JSONRPCException("JSON-RPC does not support positional and keyword arguments at the same time") if not self.__cookieProcessor.has_token(): # get the cookie self.__opener.open(self.__serviceURL).read() if len(kwargs): postdata = dumps({"method": self.__serviceName, 'params': kwargs, 'id': 'jsonrpc'}) else: postdata = dumps({"method": self.__serviceName, 'params': args, 'id': 'jsonrpc'}) try: if self.__mode == 'POST': respdata = self.__opener.open(self.__serviceURL, postdata.encode('utf8')).read() else: respdata = self.__opener.open(self.__serviceURL + "?" + quote(postdata.encode('utf8'))).read() except HTTPError as e: error = loads(e.fp.readline()) raise JSONRPCException(error['error']['message']) resp = loads(respdata) if resp['error'] != None: raise JSONRPCException(resp['error']) return resp['result']
def __call__(self, *args, **kwargs): if len(kwargs) > 0 and len(args) > 0: raise JSONRPCException("JSON-RPC does not support positional and keyword arguments at the same time") # Default to 'core' queue call_id = uuid.uuid4() topic = "%s/%s" % (self.__serviceAddress, call_id) if isinstance(self.__methods, Future): self.__methods = yield self.__methods if self.__methods and self.__serviceName not in self.__methods: raise NameError("name '%s' not defined" % self.__serviceName) # Send if len(kwargs): postdata = dumps({"method": self.__serviceName, 'params': kwargs, 'id': 'jsonrpc'}) else: postdata = dumps({"method": self.__serviceName, 'params': args, 'id': 'jsonrpc'}) response = yield self.__handler.send_sync_message(postdata, topic) resp = loads(response) if 'error' in resp and resp['error'] is not None: raise JSONRPCException(resp['error']) raise gen.Return(response)
def handle_request(self, topic, message): if topic == self.subtopic: # event from proxy received try: data = etree.fromstring(message, PluginRegistry.getEventParser()) event_type = stripNs(data.xpath('/g:Event/*', namespaces={'g': "http://www.gonicus.de/Events"})[0].tag) if event_type == "ClientLeave": proxy_id = str(data.ClientLeave.Id) registry = PluginRegistry.getInstance("BackendRegistry") registry.unregisterBackend(proxy_id) except etree.XMLSyntaxError as e: self.log.error("Event parsing error: %s" % e) elif topic.startswith(self.subtopic): response_topic = "%s/response" % "/".join(topic.split("/")[0:4]) try: id_, res = self.process(topic, message) if is_future(res): res = yield res response = dumps({"result": res, "id": id_}) self.log.debug("MQTT-RPC response: %s on topic %s" % (response, topic)) except Exception as e: err = str(e) self.log.error("MQTT RPC call error: %s" % err) response = dumps({'id': topic.split("/")[-2], 'error': err}) # Get rid of it... self.mqtt.send_message(response, topic=response_topic, qos=2) else: self.log.warning("unhandled topic request received: %s" % topic)
def __call__(self, *args, **kwargs): if len(kwargs) > 0 and len(args) > 0: raise JSONRPCException("JSON-RPC does not support positional and keyword arguments at the same time") if not self.__cookieProcessor.has_token(): # get the cookie self.__opener.open(self.__serviceURL).read() if len(kwargs): postdata = dumps({"method": self.__serviceName, 'params': kwargs, 'id': 'jsonrpc'}) else: postdata = dumps({"method": self.__serviceName, 'params': args, 'id': 'jsonrpc'}) try: if self.__mode == 'POST': respdata = self.__opener.open(self.__serviceURL, postdata.encode('utf8')).read() else: respdata = self.__opener.open(self.__serviceURL + "?" + quote(postdata.encode('utf8'))).read() except HTTPError as e: raise JSONRPCException(e.reason if len(e.reason) else str(e)) resp = loads(respdata) if resp['error'] is not None: raise JSONRPCException(resp['error']) return resp['result']
def post(self): try: resp = self.process(self.request.body) except ValueError as e: self.clear() self.set_status(400) self.finish(str(e)) except tornado.web.HTTPError as e: self.clear() self.set_status(e.status_code) self.finish(e.log_message) raise e except FilterException as e: self.clear() self.set_status(500) error = dict( name='JSONRPCError', code=100, message=str(e), error=str(e) ) self.finish(dumps(dict(result=None, error=error, id=None))) raise e else: if isinstance(resp['result'], Future): resp['result'] = yield resp['result'] self.write(dumps(resp)) self.set_header("Content-Type", "application/json")
def __update_population(self): # collect current attribute values data = {} for prop in self._get_attributes(): data[prop] = getattr(self, prop) changes = {} for key in self.__attribute_map: if self.__attribute_map[key][ 'values_populate'] and self.__attribute_map[key][ 're_populate_on_update'] is True: cr = PluginRegistry.getInstance('CommandRegistry') values = cr.call(self.__attribute_map[key]['values_populate'], data) if self.__attribute_map[key]['values'] != values: changes[key] = values self.__attribute_map[key]['values'] = values if len(changes.keys()) and self.__user is not None: e = EventMaker() changed = list() for key, values in changes.items(): change = e.Change(e.PropertyName(key), e.NewValues(dumps(values))) changed.append(change) ev = e.Event( e.ObjectPropertyValuesChanged(e.UUID(self.uuid), *changed)) event_object = objectify.fromstring( etree.tostring(ev).decode('utf-8')) SseHandler.notify(event_object, channel="user.%s" % self.__user)
def test_getSessionUser(self): self.login() data = dumps({"id": 1, "method": "getSessionUser", "params": []}) response = self.fetch('/rpc', method='POST', body=data) assert response.code == 200 json = loads(response.body) assert json['result'] == "admin"
def setPasswordRecoveryAnswers(self, user, object_dn, data): """ Set the password recovery answers for a user """ data = loads(data) # Do we have read permissions for the requested attribute env = Environment.getInstance() topic = "%s.objects.%s.attributes.%s" % (env.domain, "User", "passwordRecoveryHash") aclresolver = PluginRegistry.getInstance("ACLResolver") if not aclresolver.check(user, topic, "w", base=object_dn): self.__log.debug("user '%s' has insufficient permissions to write %s on %s, required is %s:%s" % ( user, "isLocked", object_dn, topic, "w")) raise ACLException(C.make_error('PERMISSION_ACCESS', topic, target=object_dn)) user = ObjectProxy(object_dn) method = user.passwordMethod # Try to detect the responsible password method-class pwd_o = self.get_method_by_method_type(method) if not pwd_o: raise PasswordException(C.make_error("PASSWORD_UNKNOWN_HASH", type=method)) # hash the new answers for idx, answer in data.items(): data[idx] = pwd_o.generate_password_hash(self.clean_string(answer), method) print("%s encrypted with %s as index %s => %s" % (self.clean_string(answer), method, idx, data[idx])) # Set the password and commit the changes user.passwordRecoveryHash = dumps(data) user.commit()
def will_set(self, topic, message, qos=0, retain=False): """ Set a Will to be sent to the broker. If the client disconnects without calling disconnect(), the broker will publish the message on its behalf. """ payload = {"content": message, "sender_id": self.__sender_id} self.client.will_set(topic, dumps(payload), qos, retain)
def commandReceived(self, topic, message): """ Process incoming commands, coming in with session and message information. ================= ========================== Parameter Description ================= ========================== message Received MQTT message ================= ========================== Incoming messages are coming from an :class:`gosa.common.components.mqtt_proxy.MQTTServiceProxy`. The command result is written to the '<domain>.client.<client-uuid>' queue. """ err = None res = None name = None args = None kwargs = None id_ = '' response_topic = "%s/response" % "/".join(topic.split("/")[0:4]) try: req = loads(message) except Exception as e: err = str(e) self.log.error("ServiceRequestNotTranslatable: %s" % err) req = {'id': topic.split("/")[-2]} if err is None: try: id_ = req['id'] name = req['method'] args = req['params'] kwargs = req['kwparams'] except KeyError as e: self.log.error("KeyError: %s" % e) err = str(BadServiceRequest(message)) self.log.debug("received call [%s] for %s: %s(%s,%s)" % (id_, topic, name, args, kwargs)) # Try to execute if err is None: try: res = self.__cr.dispatch(name, *args, **kwargs) except Exception as e: err = str(e) # Write exception to log exc_type, exc_value, exc_traceback = sys.exc_info() self.log.error(traceback.format_exception(exc_type, exc_value, exc_traceback)) self.log.debug("returning call [%s]: %s / %s" % (id_, res, err)) response = dumps({"result": res, "id": id_}) # Get rid of it... self.client.send_message(response, topic=response_topic)
def test_handle_backend_message(self): e = EventMaker() # send client poll with mock.patch.object(self.service.proxy_mqtt, "send_message") as mps: # send ACLChanged m_resolver = PluginRegistry.getInstance("ACLResolver") self.service._handle_backend_message("%s/proxy" % self.env.domain, etree.tostring(e.Event(e.Trigger(e.Type("ACLChanged"))), pretty_print=True).decode()) assert m_resolver.load_acls.called assert not mps.called m_index = PluginRegistry.getInstance("ObjectIndex") self.service._handle_backend_message("%s/client/broadcast" % self.env.domain, etree.tostring(e.Event(e.ClientPoll()), pretty_print=True).decode()) assert m_index.registerProxy.called assert mps.called mps.reset_mock() # send client RPC payload = dumps({"id": "mqttrpc", "method": "test", "params": []}) topic = "%s/client/client_id/request_id/request" % self.env.domain self.service._handle_backend_message(topic, payload) mps.assert_called_with(payload, topic, qos=1, proxied=True)
def test_handle_backend_message(self): e = EventMaker() # send client poll with mock.patch.object(self.service.proxy_mqtt, "send_message") as mps: # send ACLChanged m_resolver = PluginRegistry.getInstance("ACLResolver") self.service._handle_backend_message( "%s/proxy" % self.env.domain, etree.tostring(e.Event(e.Trigger(e.Type("ACLChanged"))), pretty_print=True).decode()) assert m_resolver.load_acls.called assert not mps.called m_index = PluginRegistry.getInstance("ObjectIndex") self.service._handle_backend_message( "%s/client/broadcast" % self.env.domain, etree.tostring(e.Event(e.ClientPoll()), pretty_print=True).decode()) assert m_index.registerProxy.called assert mps.called mps.reset_mock() # send client RPC payload = dumps({"id": "jsonrpc", "method": "test", "params": []}) topic = "%s/client/client_id/request_id/request" % self.env.domain self.service._handle_backend_message(topic, payload) mps.assert_called_with(payload, topic, qos=1)
def test_handle_proxy_message(self): # send client poll with mock.patch.object(self.service.backend_mqtt, "send_message") as mbs: payload = dumps({"id": "mqttrpc", "result": "test"}) topic = "%s/client/client_id/request_id/response" % self.env.domain self.service._handle_proxy_message(topic, payload) mbs.assert_called_with(payload, topic, qos=1, proxied=True)
def test_unknown(self): self.login() data = dumps({"id": 1, "method": "unknownmethod", "params": []}) response = self.fetch('/rpc', method='POST', body=data) assert response.code == 500 json = loads(response.body) assert json['error']['code'] == 100 assert json['error']['name'] == "JSONRPCError"
def test_xsrf(self): data = dumps({ "id": 3, "method": "login", "params": ["admin", "tester"] }) response = self.fetch('/rpc', method='POST', body=data) # without requesting the xsrf cookie we get the 403 code assert response.code == 403
def test_handle_proxy_message(self): # send client poll with mock.patch.object(self.service.backend_mqtt, "send_message") as mbs: payload = dumps({"id": "jsonrpc", "result": "test"}) topic = "%s/client/client_id/request_id/response" % self.env.domain self.service._handle_proxy_message(topic, payload) mbs.assert_called_with(payload, topic, qos=1)
def login(self): # fetch the xsrf cookie self.fetch('/rpc', method='GET') data = dumps({ "id": 0, "method": "login", "params": ["admin", "tester"] }) # login return self.fetch('/rpc', method='POST', body=data)
def will_set(self, topic, message, qos=0, retain=False): """ Set a Will to be sent to the broker. If the client disconnects without calling disconnect(), the broker will publish the message on its behalf. """ payload = { "content": message, "sender_id": self.__sender_id } self.client.will_set(topic, dumps(payload), qos, retain)
def stop(self): settings_file = self.env.config.get("webhooks.registry-store", "/var/lib/gosa/webhooks") to_save = self.__hooks.copy() for mime_type, sender_name in self.__temporary: if mime_type in to_save and sender_name in to_save[mime_type]: del to_save[mime_type][sender_name] if len(to_save[mime_type].keys()) == 0: del to_save[mime_type] with open(settings_file, 'w') as f: f.write(dumps(to_save))
def publish(self, topic, message, qos=0, retain=False): """ Publish a message on the MQTT bus""" message = { "sender_id": self.__sender_id, "content": message } res, mid = self.client.publish(topic, payload=dumps(message), qos=qos, retain=retain) self.__published_messages[mid] = res if res == mqtt.MQTT_ERR_NO_CONN: self.log.error("mqtt server not reachable, message could not be send to '%s'" % topic)
def test_logout(self): # fetch the xsrf cookie self.fetch('/rpc', method='GET') data = dumps({"id": 3, "method": "logout", "params": []}) response = self.fetch('/rpc', method='POST', body=data) # logging out before beeing logged in is not allowed assert response.code == 401 self.login() response = self.fetch('/rpc', method='POST', body=data) assert response.code == 200 json = loads(response.body) assert json['result'] is True assert json['error'] is None assert json['id'] == 3 # check if we are logged out data = dumps({"id": 3, "method": "getSessionUser", "params": []}) response = self.fetch('/rpc', method='POST', body=data) assert response.code == 401
def test_xsrf(self): data = dumps({ "id": 3, "method": "login", "params": ["admin", "tester"] }) response = self.fetch('/rpc', method='POST', body=data ) # without requesting the xsrf cookie we get the 403 code assert response.code == 403
def test_missingparameter(self): # fetch the xsrf cookie self.fetch('/rpc', method='GET') data = dumps({ "id": 1, "params": [] }) response = self.fetch('/rpc', method='POST', body=data ) assert response.code == 400
def publish(self, topic, message, qos=0, retain=False): """ Publish a message on the MQTT bus""" message = {"sender_id": self.__sender_id, "content": message} res, mid = self.client.publish(topic, payload=dumps(message), qos=qos, retain=retain) self.__published_messages[mid] = res if res == mqtt.MQTT_ERR_NO_CONN: self.log.error( "mqtt server not reachable, message could not be send to '%s'" % topic)
def test_wrong_parameter_format(self): # fetch the xsrf cookie self.fetch('/rpc', method='GET') data = dumps({ "id": 1, "method": "login", "params": 'no list or dict' }) response = self.fetch('/rpc', method='POST', body=data ) assert response.code == 400
def test_bad_method_name(self): # fetch the xsrf cookie self.fetch('/rpc', method='GET') data = dumps({ "id": 1, "method": "_somemethod", "params": [] }) response = self.fetch('/rpc', method='POST', body=data ) assert response.code == 403
def login(self): # fetch the xsrf cookie self.fetch('/rpc', method='GET') data = dumps({ "id": 0, "method": "login", "params": ["admin", "tester"] }) # login return self.fetch('/rpc', method='POST', body=data )
def handle_request(self, request_handler): foreman = PluginRegistry.getInstance("Foreman") self.log.debug(request_handler.request.body) data = loads(request_handler.request.body) if data["action"] in ForemanRealmReceiver.skip_next_event: del ForemanRealmReceiver.skip_next_event[data["action"]] return # TODO disable hook logging to file with open("foreman-log.json", "a") as f: f.write("%s,\n" % dumps(data, indent=4, sort_keys=True)) ForemanBackend.modifier = "foreman" if data['action'] == "create": # new client -> join it try: key = yield foreman.add_host(data['hostname']) # send key as otp to foremans realm proxy request_handler.finish(dumps({ "randompassword": key })) except Exception as e: request_handler.finish(dumps({ "error": "%s" % e })) raise e elif data['action'] == "delete": try: foreman.remove_type("ForemanHost", data['hostname']) except Exception as e: request_handler.finish(dumps({ "error": "%s" % e })) raise e ForemanBackend.modifier = None
def test_logout(self): # fetch the xsrf cookie self.fetch('/rpc', method='GET') data = dumps({ "id": 3, "method": "logout", "params": [] }) response = self.fetch('/rpc', method='POST', body=data ) # logging out before beeing logged in is not allowed assert response.code == 401 self.login() response = self.fetch('/rpc', method='POST', body=data ) assert response.code == 200 json = loads(response.body) assert json['result'] is True assert json['error'] is None assert json['id'] == 3 # check if we are logged out data = dumps({ "id": 3, "method": "getSessionUser", "params": [] }) response = self.fetch('/rpc', method='POST', body=data ) assert response.code == 401
def save(self): settings_file = self.env.config.get("webhooks.registry-store", "/var/lib/gosa/webhooks") to_save = copy.deepcopy(self.__hooks) for mime_type, sender_name in self.__temporary: if mime_type in to_save and sender_name in to_save[mime_type]: del to_save[mime_type][sender_name] if len(to_save[mime_type].keys()) == 0: del to_save[mime_type] # backup old file if os.path.exists(settings_file): shutil.copyfile(settings_file, "%s.backup" % settings_file) with open(settings_file, 'w') as f: f.write(dumps(to_save))
def test_getSessionUser(self): self.login() data = dumps({ "id": 1, "method": "getSessionUser", "params": [] }) response = self.fetch('/rpc', method='POST', body=data ) assert response.code == 200 json = loads(response.body) assert json['result'] == "admin"
def test_exception(self): self.login() data = dumps({ "id": 1, "method": "getSessionUser", "params": { 'test': 'test' } }) response = self.fetch('/rpc', method='POST', body=data) assert response.code == 500 json = loads(response.body) assert json['error']['code'] == 100 assert json['error']['name'] == "JSONRPCError"
def configureUsers(self, client_id, users): users_config = self.__collect_user_configuration(client_id, users) for uid, config in users_config.items(): if "menu" in config: # send to client self.log.debug("sending generated menu for user %s" % uid) self.queuedClientDispatch(client_id, "dbus_configureUserMenu", uid, dumps(config["menu"])) if "printer-setup" in config: self.configureHostPrinters(client_id, config["printer-setup"]) if "resolution" in config and config["resolution"] is not None and len(config["resolution"]): self.log.debug("sending screen resolution: %sx%s for user %s to client %s" % (config["resolution"][0], config["resolution"][1], uid, client_id)) self.queuedClientDispatch(client_id, "dbus_configureUserScreen", uid, config["resolution"][0], config["resolution"][1])
def __call__(self, *args, **kwargs): if len(kwargs) > 0 and len(args) > 0: raise JSONRPCException( "JSON-RPC does not support positional and keyword arguments at the same time" ) # Default to 'core' queue call_id = uuid.uuid4() topic = "%s/%s" % (self.__serviceAddress, call_id) if isinstance(self.__methods, Future): self.__methods = yield self.__methods if self.__methods and self.__serviceName not in self.__methods: raise NameError("name '%s' not defined" % self.__serviceName) # Send if len(kwargs): postdata = dumps({ "method": self.__serviceName, 'params': kwargs, 'id': 'jsonrpc' }) else: postdata = dumps({ "method": self.__serviceName, 'params': args, 'id': 'jsonrpc' }) response = yield self.__handler.send_sync_message(postdata, topic) resp = loads(response) if 'error' in resp and resp['error'] is not None: raise JSONRPCException(resp['error']) raise gen.Return(response)
def send_message(cls, msg, topic=None, channel='broadcast', session_id=None): """ Sends a message to all live connections """ id = str(uuid.uuid4()) if isinstance(msg, dict): msg = dumps(msg) dataString = format( "%s\n" % "\n".join([("data: %s" % x) for x in msg.splitlines() if not x == ''])) if topic is not None: message = format('id: %s\nevent: %s\n%s\n' % (id, topic, dataString)) else: message = format('id: %s\n%s\n' % (id, dataString)) cls._cache.append({ 'id': id, 'channel': channel, 'body': message, }) if len(cls._cache) > cls._cache_size: cls._cache = cls._cache[-cls._cache_size:] if channel == 'broadcast': clients = [] for chan in cls._channels: for client in cls._channels[chan]: clients.append(client) elif session_id is not None: clients = [] channel_clients = cls._channels.get(channel, []) for connection_id in channel_clients: if channel_clients[connection_id] == session_id: clients.append(connection_id) else: clients = cls._channels.get(channel, []) cls.log.info( 'Sending %s "%s" in channel %s (session: %s) to %s clients' % (topic, msg, channel, session_id, len(clients))) for client_id in clients: client = cls._connections[client_id] client.on_message(message)
def test_exception(self): self.login() data = dumps({ "id": 1, "method": "getSessionUser", "params": {'test': 'test'} }) response = self.fetch('/rpc', method='POST', body=data ) assert response.code == 500 json = loads(response.body) assert json['error']['code'] == 500 assert json['error']['name'] == "JSONRPCError"
def handle_request(self, topic, message): if topic == self.subtopic: # event from proxy received try: data = etree.fromstring(message, PluginRegistry.getEventParser()) event_type = stripNs( data.xpath( '/g:Event/*', namespaces={'g': "http://www.gonicus.de/Events"})[0].tag) if event_type == "ClientLeave": proxy_id = str(data.ClientLeave.Id) registry = PluginRegistry.getInstance("BackendRegistry") registry.unregisterBackend(proxy_id) except etree.XMLSyntaxError as e: self.log.error("Event parsing error: %s" % e) elif topic.startswith(self.subtopic): response_topic = "%s/response" % "/".join(topic.split("/")[0:4]) try: id_, res = self.process(topic, message) response = dumps({"result": res, "id": id_}) except Exception as e: err = str(e) self.log.error("MQTT RPC call error: %s" % err) response = dumps({'id': topic.split("/")[-2], 'error': err}) # Get rid of it... self.mqtt.send_message(response, topic=response_topic) else: self.log.warning("unhandled topic request received: %s" % topic)
def __request(self, method_name, object_type, object_id=None, data=None): if self.foreman_host is None: return {} cached = ForemanClientCache.get_cache(method_name, object_type, object_id=object_id) if cached is not None: self.log.debug("using cached response for %s request of %s" % (method_name, object_type)) return cached url = "%s/%s" % (self.foreman_host, object_type) if object_id is not None: url += "/%s" % object_id method = getattr(requests, method_name) data = dumps(data) if data is not None else None self.log.debug("sending %s request with %s to %s" % (method_name, data, url)) kwargs = { "headers": self.headers, "verify": self.env.config.get("foreman.verify", "true") == "true", "data": data, "cookies": self.__cookies, "timeout": 30 } try: if self.__cookies is None: response = self.__authenticate(method, url, kwargs) else: response = method(url, **kwargs) except Exception as e: self.log.error("Error during foreman API request: %s" % str(e)) raise e if response.status_code == 401 and self.__cookies is not None: # try to re-authenticate session might be timed out response = self.__authenticate(method, url, kwargs) if response.ok: data = response.json() self.log.debug("response %s" % data) # check for error if "error" in data: raise ForemanBackendException(response, method=method_name) else: ForemanClientCache.add_to_cache(method_name, object_type, data, object_id=object_id) return data else: self.log.error("%s request with %s to %s failed: %s" % (method_name, data, url, str(response.content))) raise ForemanBackendException(response, method=method_name)
def test_unknown(self): self.login() data = dumps({ "id": 1, "method": "unknownmethod", "params": [] }) response = self.fetch('/rpc', method='POST', body=data ) assert response.code == 500 json = loads(response.body) assert json['error']['code'] == 100 assert json['error']['name'] == "JSONRPCError"
def publish(self, topic, content, qos=0, retain=False, retried=0, proxied=False): """ Publish a message on the MQTT bus""" message = { "sender_id": self.__sender_id, "content": content } if proxied is True: message['proxied_by'] = self.__sender_id res, mid = self.client.publish(topic, payload=dumps(message), qos=qos, retain=retain) self.__published_messages[mid] = res if res == mqtt.MQTT_ERR_NO_CONN: self.log.error("%s: mqtt server not reachable, message could not be send to '%s'" % (self.get_identifier(), topic)) if qos > 0 and retried < 3: # try again yield gen.sleep(0.1) self.publish(topic, content, qos=qos, retain=retain, retried=retried+1)
def __call__(self, *args, **kwargs): data = {} if '__user__' in kwargs: data['user'] = kwargs['__user__'] del kwargs['__user__'] if '__session_id__' in kwargs: data['session_id'] = kwargs['__session_id__'] del kwargs['__session_id__'] if len(kwargs) > 0 and len(args) > 0: raise JSONRPCException( "JSON-RPC does not support positional and keyword arguments at the same time" ) # Default to 'core' queue call_id = uuid.uuid4() topic = "%s/%s" % (self.__serviceAddress, call_id) if isinstance(self.__methods, Future): self.__methods = yield self.__methods if self.__methods and self.__serviceName not in self.__methods: raise NameError("name '%s' not defined" % self.__serviceName) # Send data.update({ "method": self.__serviceName, "id": "jsonrpc", "sender": self.env.uuid }) if len(kwargs): data["params"] = kwargs else: data["params"] = args postdata = dumps(data) response = yield self.__handler.send_sync_message(postdata, topic) resp = loads(response) if 'error' in resp and resp['error'] is not None: raise JSONRPCException(resp['error']) return resp['result']
def test_MqttEventConsumer(self): schema = '<?xml version="1.0"?>' \ '<schema xmlns="http://www.w3.org/2001/XMLSchema" xmlns:e="http://www.gonicus.de/Events" ' \ 'targetNamespace="http://www.gonicus.de/Events" elementFormDefault="qualified">'\ '<include schemaLocation="%s"/>'\ '<complexType name="Event">'\ '<choice maxOccurs="1" minOccurs="1">'\ '<group ref="e:Events"/>'\ '</choice>'\ '</complexType>'\ '<group name="Events">'\ '<choice>'\ '<element name="BackendChange" type="e:BackendChange"/>'\ '</choice>'\ '</group>'\ '<element name="Event" type="e:Event"/>'\ '</schema>' % resource_filename('gosa.backend', 'data/events/BackendChange.xsd') with unittest.mock.patch("gosa.common.events.PluginRegistry.getEventSchema", return_value=schema): e = EventMaker() callback = unittest.mock.Mock() event = e.Event( e.BackendChange( e.DN("dn"), e.ModificationTime("mod_time"), e.ChangeType("type") ) ) mec = MqttEventConsumer(callback=callback, event_type="BackendChange") payload = dumps({ "sender_id": None, "content": etree.tostring(event, pretty_print=True).decode('utf-8') }) message = unittest.mock.MagicMock() message.payload = payload message.topic = "%s/events" % Environment.getInstance().domain mec.mqtt.get_client().client.on_message(None, None, message) args, kwargs = callback.call_args assert etree.tostring(args[0], pretty_print=True).decode('utf-8') == etree.tostring(event, pretty_print=True).decode('utf-8') PluginRegistry._event_parser = None
def send_message(cls, msg, topic=None, channel='broadcast', session_id=None): """ Sends a message to all live connections """ id = str(uuid.uuid4()) if isinstance(msg, dict): msg = dumps(msg) dataString = format("%s\n" % "\n".join([("data: %s" % x) for x in msg.splitlines() if not x == ''])) if topic is not None: message = format('id: %s\nevent: %s\n%s\n' % (id, topic, dataString)) else: message = format('id: %s\n%s\n' % (id, dataString)) cls._cache.append({ 'id': id, 'channel': channel, 'body': message, }) if len(cls._cache) > cls._cache_size: cls._cache = cls._cache[-cls._cache_size:] if channel == 'broadcast': clients = [] for chan in cls._channels: for client in cls._channels[chan]: clients.append(client) elif session_id is not None: clients = [] channel_clients = cls._channels.get(channel, []) for connection_id in channel_clients: if channel_clients[connection_id] == session_id: clients.append(connection_id) else: clients = cls._channels.get(channel, []) logging.info('Sending %s "%s" in channel %s (session: %s) to %s clients' % (topic, msg, channel, session_id, len(clients))) for client_id in clients: client = cls._connections[client_id] client.on_message(message)
def configureUsers(self, client_id, users): users_config = self.__collect_user_configuration(client_id, users) for uid, config in users_config.items(): if "menu" in config: # send to client self.log.debug("sending generated menu for user %s" % uid) self.queuedClientDispatch(client_id, "dbus_configureUserMenu", uid, dumps(config["menu"])) if "printer-setup" in config: self.configureHostPrinters(client_id, config["printer-setup"]) if "resolution" in config and config[ "resolution"] is not None and len(config["resolution"]): self.log.debug( "sending screen resolution: %sx%s for user %s to client %s" % (config["resolution"][0], config["resolution"][1], uid, client_id)) self.queuedClientDispatch(client_id, "dbus_configureUserScreen", uid, config["resolution"][0], config["resolution"][1])
def __request(self, method_name, type, id=None, data=None): if self.foreman_host is None: return {} url = "%s/%s" % (self.foreman_host, type) if id is not None: url += "/%s" % id method = getattr(requests, method_name) data = dumps(data) if data is not None else None self.log.debug("sending %s request with %s to %s" % (method_name, data, url)) kwargs = { "headers": self.headers, "verify": self.env.config.get("foreman.verify", "true") == "true", "data": data, "cookies": self.__cookies } if self.__cookies is None: response = self.__authenticate(method, url, kwargs) else: response = method(url, **kwargs) if response.status_code == 401 and self.__cookies is not None: # try to re-authenticate session might be timed out response = self.__authenticate(method, url, kwargs) if response.ok: data = response.json() self.log.debug("response %s" % data) # check for error if "error" in data: raise ForemanBackendException(response, method=method_name) return data else: self.log.error("%s request with %s to %s failed: %s" % (method_name, data, url, str(response.content))) raise ForemanBackendException(response, method=method_name)
def setPasswordRecoveryAnswers(self, user, object_dn, data): """ Set the password recovery answers for a user """ data = loads(data) # Do we have read permissions for the requested attribute env = Environment.getInstance() topic = "%s.objects.%s.attributes.%s" % (env.domain, "User", "passwordRecoveryHash") aclresolver = PluginRegistry.getInstance("ACLResolver") if not aclresolver.check(user, topic, "w", base=object_dn): self.__log.debug( "user '%s' has insufficient permissions to write %s on %s, required is %s:%s" % (user, "isLocked", object_dn, topic, "w")) raise ACLException( C.make_error('PERMISSION_ACCESS', topic, target=object_dn)) user = ObjectProxy(object_dn) method = user.passwordMethod # Try to detect the responsible password method-class pwd_o = self.get_method_by_method_type(method) if not pwd_o: raise PasswordException( C.make_error("PASSWORD_UNKNOWN_HASH", type=method)) # hash the new answers for idx, answer in data.items(): data[idx] = pwd_o.generate_password_hash(self.clean_string(answer), method) print("%s encrypted with %s as index %s => %s" % (self.clean_string(answer), method, idx, data[idx])) # Set the password and commit the changes user.passwordRecoveryHash = dumps(data) user.commit()
def __call__(self, *args, **kwargs): data = {} if '__user__' in kwargs: data['user'] = kwargs['__user__'] del kwargs['__user__'] if '__session_id__' in kwargs: data['session_id'] = kwargs['__session_id__'] del kwargs['__session_id__'] # Default to 'core' queue call_id = uuid.uuid4() topic = "%s/%s" % (self.__serviceAddress, call_id) if isinstance(self.__methods, Future): self.__methods = yield self.__methods if self.__methods and self.__serviceName not in self.__methods: raise NameError("name '%s' not defined" % self.__serviceName) # Send data.update({ "method": self.__serviceName, "id": "mqttrpc", "sender": self.env.uuid }) data["kwparams"] = kwargs data["params"] = args postdata = dumps(data) response = yield self.__handler.send_sync_message(postdata, topic, qos=2) resp = loads(response) if 'error' in resp and resp['error'] is not None: raise JSONRPCException(resp['error']) return resp['result']
def notify_backend(env, mode, user): """ Main event loop which will process all registered threads in a loop. It will run as long env.active is set to True.""" log = logging.getLogger(__name__) PluginRegistry.modules["ClientCommandRegistry"] = ClientCommandRegistry() PluginRegistry.modules["MQTTClientService"] = MQTTClientService() dbus_proxy = DBUSProxy() dbus_proxy.serve(register_methods=False) url = env.config.get("jsonrpc.url", default=None) sys_id = env.config.get("core.id", default=None) key = env.config.get("jsonrpc.key", default=None) # Prepare URL for login url = urlparse(url) # Try to log in with provided credentials connection = '%s://%s%s' % (url.scheme, url.netloc, url.path) proxy = JSONServiceProxy(connection) # Try to log in try: if not proxy.login(sys_id, key): log.error("connection to GOsa backend failed") print(_("Cannot join client: check user name or password!")) return False else: if mode == "start": config = proxy.preUserSession(sys_id, user) # send config to dbus if "menu" in config: # send to client print("sending generated menu for user '%s'" % user) dbus_proxy.callDBusMethod("dbus_configureUserMenu", user, dumps(config["menu"])) if "printer-setup" in config and "printers" in config[ "printer-setup"]: dbus_proxy.callDBusMethod("dbus_deleteAllPrinters") for p_conf in config["printer-setup"]["printers"]: print("adding printer '%s'" % p_conf["cn"]) p = { key: value if value is not None else "" for (key, value) in p_conf.items() } dbus_proxy.callDBusMethod("dbus_addPrinter", p) if "defaultPrinter" in config["printer-setup"] and config[ "printer-setup"]["defaultPrinter"] is not None: print("setting '%s' as default printer" % config["printer-setup"]["defaultPrinter"]) dbus_proxy.callDBusMethod( "dbus_defaultPrinter", config["printer-setup"]["defaultPrinter"]) if "resolution" in config and config[ "resolution"] is not None and len( config["resolution"]): print("sending screen resolution: %sx%s for user %s" % (config["resolution"][0], config["resolution"][1], user)) dbus_proxy.callDBusMethod("dbus_configureUserScreen", user, config["resolution"][0], config["resolution"][1]) elif mode == "end": proxy.postUserSession(sys_id, user) except HTTPError as e: if e.code == 401: log.error("connection to GOsa backend failed") print(_("Cannot join client: check user name or password!")) return False else: print("Error: %s " % str(e)) raise e except Exception as e: print("Error: %s " % str(e)) raise e
def will_set(self, topic, message, qos=0, retain=False): """ Set a Will to be sent to the broker. If the client disconnects without calling disconnect(), the broker will publish the message on its behalf. """ self.client.will_set(topic, dumps(message), qos, retain)
def __save_settings(self): with open(self.settings_file, "w") as f: f.write(dumps(self.__settings))
def stop(self): settings_file = self.env.config.get("webhooks.registry-store", "/var/lib/gosa/webhooks") with open(settings_file, 'w') as f: f.write(dumps(self.__hooks))
def __save_settings(self): with open(self.settings_file, "w") as f: f.write(dumps(self.__settings))
def process(self, obj, key, valDict): if type(valDict[key]['value'] is not None): valDict[key]['value'] = list(map(lambda x: dumps(x), valDict[key]['value'])) return key, valDict
def stop(self): settings_file = self.env.config.get("webhooks.registry-store", "/var/lib/gosa/webhooks") with open(settings_file, 'w') as f: f.write(dumps(self.__hooks))
def test_rpc(self): service = PluginRegistry.getInstance("MQTTRPCService") request_uuid = uuid.uuid4() topic = "%s/proxy/fake_client_id/%s" % ( Environment.getInstance().domain, request_uuid) with mock.patch.object(PluginRegistry.getInstance('CommandRegistry'), "dispatch") as m, \ mock.patch.object(service.mqtt, "send_message") as mq: m.return_value = "fake_response" # call with wrong json service.handle_request(topic, "this is no json: 'string'") args, kwargs = mq.call_args response = loads(args[0]) assert "error" in response assert kwargs["topic"] == "%s/response" % topic assert not m.called mq.reset_mock() # call without params service.handle_request( topic, dumps({ "id": "jsonrpc", "method": "fakeCall", "user": "******" })) args, kwargs = mq.call_args response = loads(args[0]) assert "error" in response assert kwargs["topic"] == "%s/response" % topic assert not m.called # call with empty params service.handle_request( topic, dumps({ "id": "jsonrpc", "method": "fakeCall", "user": "******", "session_id": "fake_session_id", "params": [] })) m.assert_called_with("admin", "fake_session_id", "fakeCall") args, kwargs = mq.call_args response = loads(args[0]) assert "result" in response assert response["result"] == "fake_response" assert kwargs["topic"] == "%s/response" % topic mq.reset_mock() m.reset_mock() service.handle_request( topic, dumps({ "id": "jsonrpc", "method": "fakeCall", "user": "******", "params": ["param1", "param2"] })) m.assert_called_with("admin", None, "fakeCall", "param1", "param2") args, kwargs = mq.call_args response = loads(args[0]) assert "result" in response assert response["result"] == "fake_response" assert kwargs["topic"] == "%s/response" % topic mq.reset_mock() m.reset_mock() # call without user (client id taken as user) service.handle_request( topic, dumps({ "id": "jsonrpc", "method": "fakeCall", "params": [] })) m.assert_called_with("fake_client_id", None, "fakeCall") args, kwargs = mq.call_args response = loads(args[0]) assert "result" in response assert response["result"] == "fake_response" assert kwargs["topic"] == "%s/response" % topic
def handle_request(self, request_handler): foreman = PluginRegistry.getInstance("Foreman") data = loads(request_handler.request.body) self.log.debug(data) # TODO disable hook logging to file with open("foreman-log.json", "a") as f: f.write("%s,\n" % dumps(data, indent=4, sort_keys=True)) if data["event"] in ForemanHookReceiver.skip_next_event and data["object"] in ForemanHookReceiver.skip_next_event[data["event"]]: ForemanHookReceiver.skip_next_event[data["event"]].remove(data["object"]) self.log.info("skipped '%s' event for object: '%s'" % (data["event"], data["object"])) return data_keys = list(data['data'].keys()) if len(data_keys) == 1: type = data_keys[0] else: # no type given -> skipping this event as other might come with more information self.log.warning("skipping event '%s' for object '%s' as no type information is given in data: '%s'" % (data["event"], data["object"], data["data"])) return # search for real data if len(data['data'][type].keys()) == 1: # something like {data: 'host': {host: {...}}} # or {data: 'discovered_host': {host: {...}}} payload_data = data['data'][type][list(data['data'][type].keys())[0]] else: payload_data = data['data'][type] if type == "operatingsystem": with make_session() as session: foreman.sync_release_name(payload_data, session, event=data['event']) session.commit() return factory = ObjectFactory.getInstance() foreman_type = type if type == "discovered_host": type = "host" object_types = factory.getObjectNamesWithBackendSetting("Foreman", "type", "%ss" % type) object_type = object_types[0] if len(object_types) else None backend_attributes = factory.getObjectBackendProperties(object_type) if object_type is not None else None self.log.debug("Hookevent: '%s' for '%s' (%s)" % (data['event'], data['object'], object_type)) uuid_attribute = None if "Foreman" in backend_attributes: uuid_attribute = backend_attributes["Foreman"]["_uuidSourceAttribute"] \ if '_uuidSourceAttribute' in backend_attributes["Foreman"] else backend_attributes["Foreman"]["_uuidAttribute"] ForemanBackend.modifier = "foreman" update_data = {} if data['event'] in ["update", "create"] and foreman_type == "host": id = payload_data["id"] if "id" in payload_data else None try: foreman.write_parameters(id if id is not None else data['object']) except: foreman.mark_for_parameter_setting(data['object'], { "status": "created", "use_id": id }) if data['event'] == "after_commit" or data['event'] == "update" or data['event'] == "after_create" or data['event'] == "create": host = None if data['event'] == "update" and foreman_type == "host" and "mac" in payload_data and payload_data["mac"] is not None: # check if we have an discovered host for this mac index = PluginRegistry.getInstance("ObjectIndex") res = index.search({ "_type": "Device", "extension": ["ForemanHost", "ieee802Device"], "macAddress": payload_data["mac"], "status": "discovered" }, {"dn": 1}) if len(res): self.log.debug("update received for existing host with dn: %s" % res[0]["dn"]) host = ObjectProxy(res[0]["dn"]) if foreman_type != "discovered_host" and host.is_extended_by("ForemanHost"): host.status = "unknown" foreman_object = foreman.get_object(object_type, payload_data[uuid_attribute], create=host is None) if foreman_object and host: if foreman_object != host: self.log.debug("using known host instead of creating a new one") # host is the formerly discovered host, which might have been changed in GOsa for provisioning # so we want to use this one, foreman_object is the joined one, so copy the credentials from foreman_object to host if not host.is_extended_by("RegisteredDevice"): host.extend("RegisteredDevice") if not host.is_extended_by("simpleSecurityObject"): host.extend("simpleSecurityObject") host.deviceUUID = foreman_object.deviceUUID host.userPassword = foreman_object.userPassword host.otp = foreman_object.otp host.cn = foreman_object.cn # now delete the formerly joined host foreman_object.remove() foreman_object = host elif foreman_object is None and host is not None: foreman_object = host elif foreman_type == "discovered_host": self.log.debug("setting discovered state for %s" % payload_data[uuid_attribute]) if not foreman_object.is_extended_by("ForemanHost"): foreman_object.extend("ForemanHost") foreman_object.status = "discovered" if foreman_type == "host": old_build_state = foreman_object.build foreman.update_type(object_type, foreman_object, payload_data, uuid_attribute, update_data=update_data) if foreman_type == "host" and old_build_state is True and foreman_object.build is False and \ foreman_object.status == "ready": # send notification e = EventMaker() ev = e.Event(e.Notification( e.Title(N_("Host ready")), e.Body(N_("Host '%s' has been successfully build." % foreman_object.cn)), e.Icon("@Ligature/pc"), e.Timeout("10000") )) event_object = objectify.fromstring(etree.tostring(ev, pretty_print=True).decode('utf-8')) SseHandler.notify(event_object) elif data['event'] == "after_destroy": # print("Payload: %s" % payload_data) foreman.remove_type(object_type, payload_data[uuid_attribute]) # because foreman sends the after_commit event after the after_destroy event # we need to skip this event, otherwise the host would be re-created if "after_commit" not in ForemanHookReceiver.skip_next_event: ForemanHookReceiver.skip_next_event["after_commit"] = [data['object']] else: ForemanHookReceiver.skip_next_event["after_commit"].append(data['object']) # add garbage collection for skip sobj = PluginRegistry.getInstance("SchedulerService") sobj.getScheduler().add_date_job(self.cleanup_event_skipper, datetime.datetime.now() + datetime.timedelta(minutes=1), args=("after_commit", data['object']), tag='_internal', jobstore='ram') else: self.log.info("unhandled hook event '%s' received for '%s'" % (data['event'], type)) ForemanBackend.modifier = None
def commandReceived(self, topic, message): """ Process incoming commands, coming in with session and message information. ================= ========================== Parameter Description ================= ========================== message Received MQTT message ================= ========================== Incoming messages are coming from an :class:`gosa.common.components.mqtt_proxy.MQTTServiceProxy`. The command result is written to the '<domain>.client.<client-uuid>' queue. """ err = None res = None name = None args = None id_ = '' response_topic = "%s/to-backend" % "/".join(topic.split("/")[0:4]) try: req = loads(message) except Exception as e: err = str(e) self.log.error("ServiceRequestNotTranslatable: %s" % err) req = {'id': topic.split("/")[-2]} if err is None: try: id_ = req['id'] name = req['method'] args = req['params'] except KeyError as e: self.log.error("KeyError: %s" % e) err = str(BadServiceRequest(message)) self.log.debug("received call [%s] for %s: %s(%s)" % (id_, topic, name, args)) # Try to execute if err is None: try: res = self.__cr.dispatch(name, *args) except Exception as e: err = str(e) # Write exception to log exc_type, exc_value, exc_traceback = sys.exc_info() self.log.error( traceback.format_exception(exc_type, exc_value, exc_traceback)) self.log.debug("returning call [%s]: %s / %s" % (id_, res, err)) response = dumps({"result": res, "id": id_}) # Get rid of it... mqtt = PluginRegistry.getInstance('MQTTClientHandler') mqtt.send_message(response, topic=response_topic)
def requestPasswordReset(self, uid, step, uuid=None, data=None): """ Request a password reset if the submitted password recovery answers match the stored ones for the given user :param uid: user id :param step: 'start' to trigger the password reset process by sending an email with activation link to the user :param uuid: the recovery uuid :param data: optional data required by the current step :return: * """ # check for existing uid and status of the users password settings index = PluginRegistry.getInstance("ObjectIndex") res = index.search({'uid': uid, '_type': 'User'}, {'dn': 1}) if len(res) == 0: raise PasswordException(C.make_error("UID_UNKNOWN", target=uid)) dn = res[0]['dn'] user = ObjectProxy(dn) if user.mail is None: raise PasswordException( C.make_error("PASSWORD_RECOVERY_IMPOSSIBLE")) recovery_state = loads( user.passwordRecoveryState ) if user.passwordRecoveryState is not None else {} if step != "start": # check uuid if 'uuid' not in recovery_state or recovery_state['uuid'] != uuid: # recovery process has not been started raise PasswordException( C.make_error("PASSWORD_RECOVERY_STATE_ERROR")) if step == "start": # start process by generating an unique password recovery link for this user and sending it to him via mail if 'uuid' not in recovery_state: # generate a new id recovery_state['sent_counter'] = 0 recovery_state['uuid'] = str(Uuid.uuid4()) recovery_state['state'] = 'started' # send the link to the user content = N_( "Please open this link to continue your password recovery process." ) + ":" gui = PluginRegistry.getInstance("HTTPService").get_gui_uri() content += "\n\n%s?pwruid=%s&uid=%s\n\n" % ( "/".join(gui), recovery_state['uuid'], uid) mail = PluginRegistry.getInstance("Mail") mail.send(user.mail, N_("Password recovery link"), content) recovery_state["sent_counter"] += 1 user.passwordRecoveryState = dumps(recovery_state) user.commit() return True elif step == "get_questions": # check correct state if recovery_state['state'] is None: raise PasswordException( C.make_error("PASSWORD_RECOVERY_STATE_ERROR")) # return the indices of the questions the user has answered recovery_hashes = loads(user.passwordRecoveryHash) # TODO retrieve minimum amount of correct answers from user policy object return random.sample(recovery_hashes.keys(), 3) elif step == "check_answers": # check correct state if recovery_state['state'] is None: raise PasswordException( C.make_error("PASSWORD_RECOVERY_STATE_ERROR")) data = loads(data) recovery_hashes = loads(user.passwordRecoveryHash) correct_answers = 0 for idx, answer in data.items(): if idx not in recovery_hashes: # the user hasn't answered this question continue # detect method from existing answer pwd_o = self.detect_method_by_hash(recovery_hashes[idx]) if not pwd_o: raise PasswordException( C.make_error("PASSWORD_RECOVERY_IMPOSSIBLE")) # encrypt and compare new answer if pwd_o.compare_hash(self.clean_string(answer), recovery_hashes[idx]): correct_answers += 1 # TODO retrieve minimum amount of correct answers from user policy object if correct_answers >= 3: recovery_state['state'] = 'verified' user.passwordRecoveryState = dumps(recovery_state) user.commit() return True else: return False elif step == "change_password": # check correct state if recovery_state['state'] != 'verified': raise PasswordException( C.make_error("PASSWORD_RECOVERY_STATE_ERROR")) self.setUserPassword(uid, user.dn, data) user.passwordRecoveryState = None user.commit() return True