def _handle_client_sync(self, req: Request): """ Handles a client update request from any foreign source :param req: Request containing the client update request :return: None """ try: self._validator.validate(req.get_payload(), "api_client_sync_request") except ValidationError as err: self._respond_with_error(req, "ValidationError", f"Request validation error at '{ApiURIs.sync_client.uri}': '{err.message}'") return client_id = req.get_sender() self._logger.info(f"Syncing client {client_id}") decoder = ApiDecoder() new_client = decoder.decode_client(req.get_payload()["client"], req.get_sender()) gadget_data = req.get_payload()["gadgets"] for gadget in gadget_data: self._update_gadget(client_id, gadget) self._delegate.handle_client_sync(new_client)
def _log_request(self, req: Request): short_json = json.dumps(req.get_payload()) if len(short_json) > 35: short_json = short_json[:35] + f"... + {len(short_json) - 35} bytes" auth_type = "No Auth" if req.get_auth() is None else req.get_auth().__class__.__name__[:-13] self._logger.info(f"Received request from '{req.get_sender()}' at '{req.get_path()}' " f"(Auth type: '{auth_type}'): {short_json}")
def _check_auth(self, req: Request): if self.auth_manager is None: return if req.get_auth() is None: self._respond_with_error(req, "NeAuthError", "The bridge only accepts requests based on privileges") raise AuthError() auth = req.get_auth() try: if isinstance(auth, CredentialsAuthContainer): self.auth_manager.authenticate(auth.username, auth.password) self.auth_manager.check_path_access_level_for_user(auth.username, req.get_path()) elif isinstance(auth, SerialAuthContainer): self.auth_manager.check_path_access_level(ApiAccessLevel.admin, req.get_path()) elif isinstance(auth, MqttAuthContainer): self.auth_manager.check_path_access_level(ApiAccessLevel.mqtt, req.get_path()) else: self._respond_with_error(req, "UnknownAuthError", "Unknown error occurred") raise AuthError() except AuthenticationFailedException: self._respond_with_error(req, "WrongAuthError", "illegal combination of username and password") raise AuthError() except UserDoesNotExistException: self._respond_with_error(req, "UserDoesntExistError", "User does not exist") raise AuthError() except InsufficientAccessPrivilegeException: self._respond_with_error(req, "AccessLevelError", "Insufficient privileges") raise AuthError() except UnknownUriException: self._handle_unknown(req) raise AuthError()
def _handle_check_bridge_for_update(self, req: Request): """ Checks whether the remote, the bridge is currently running on, is an older version :param req: empty request :return: None """ try: self._validator.validate(req.get_payload(), "api_empty_request") except ValidationError: self._respond_with_error(req, "ValidationError", f"Request validation error at {ApiURIs.bridge_update_check.uri}") return encoder = ApiEncoder() try: updater = BridgeUpdateManager(os.getcwd()) bridge_meta = updater.check_for_update() except UpdateNotPossibleException: self._respond_with_error(req, "UpdateNotPossibleException", "bridge could not be updated") except NoUpdateAvailableException: self._respond_with_status(req, True, "Bridge is up to date") else: payload = encoder.encode_bridge_update_info(bridge_meta) req.respond(payload) return
def _handle_client_reboot(self, req: Request): """ Handles a client reboot request :param req: Request containing the client id to reboot :return: None """ try: self._validator.validate(req.get_payload(), "api_client_reboot_request") except ValidationError: self._respond_with_error(req, "ValidationError", f"Request validation error at '{ApiURIs.update_gadget.uri}'") return try: self.send_client_reboot(req.get_payload()["id"]) except UnknownClientException: self._respond_with_error(req, "UnknownClientException", f"Nee client with the id: {req.get_payload()['id']} exists") except NoClientResponseException: self._respond_with_error(req, "NoClientResponseException", f"Client did not respond to reboot request") except ClientRebootError: self._respond_with_error(req, "ClientRebootError", f"Client could not be rebooted for some reason")
def _handle_get_all_configs(self, req: Request): """ Responds with the names and descriptions of all available configs :param req: empty Request :return: None """ try: self._validator.validate(req.get_payload(), "api_empty_request") except ValidationError: self._respond_with_error(req, "ValidationError", f"Request validation error at '{ApiURIs.config_storage_get_all.uri}'") return manager = ClientConfigManager() config_names = manager.get_config_names() all_configs = {} self._logger.info("fetching all configs") for config in config_names: if not config == "Example": try: conf = manager.get_config(config) all_configs[config] = conf["description"] except ConfigDoesNotExistException: self._logger.error("congratz, something went wrong, abort, abort, return to the moon base") pass else: pass payload = {"configs": all_configs} req.respond(payload)
def send_file(self, file): if not self.connected: return False request = Request('get_structure', file.file_id) request_json = request_to_json(request) self.send_bytes_secure(bytes(request_json, encoding='utf-8')) prev_structure = self.receive_bytes_secure() prev_root_hash = get_root_hash(prev_structure, file, False) if self._latest_top_hash and prev_root_hash != self._latest_top_hash: print('Client: Structure incorrectly modified before write.') return False expected_root_hash = get_root_hash(prev_structure, file, True) file_json = file_to_json(file) request = Request('send_file', file_json) request_json = request_to_json(request) self.send_bytes_secure(bytes(request_json, encoding='utf-8')) hash_structure = self.receive_bytes_secure() if not hash_structure or hash_structure.decode('utf-8') == 'error': return False received_root_hash = get_root_hash(hash_structure, file, False) if received_root_hash == expected_root_hash: self._latest_top_hash = received_root_hash else: print('Client: Structure incorrectly modified after write.') self.disconnect() return False print('Client: Calculated top hash:', self._latest_top_hash) return True
def mock_receive(self, path: str, sender: str, payload: dict): buf_req = Request(path, None, sender, self._hostname, payload) buf_req.set_callback_method(self._get_mock_response_function()) self.receive(buf_req)
def receive(self, req: Request): if req.get_receiver() == self._network.get_hostname(): # Normal Request self._logger.info(f"Received Request at '{req.get_path()}'") elif req.get_receiver() is None: # Broadcast self._logger.info(f"Received Broadcast at '{req.get_path()}'") else: return req.respond(req.get_payload())
def _handle_heartbeat(self, req: Request): try: self._validator.validate(req.get_payload(), "bridge_heartbeat_request") except ValidationError: self._respond_with_error(req, "ValidationError", f"Request validation error at '{ApiURIs.heartbeat.uri}'") return rt_id = req.get_payload()["runtime_id"] self._delegate.handle_heartbeat(req.get_sender(), rt_id)
def mock_response_function(req: Request, payload: dict, path: str): if path is None: buf_path = req.get_path() else: buf_path = path out_req = Request(path=buf_path, session_id=req.get_session_id(), sender=req.get_receiver(), receiver=req.get_sender(), payload=payload) self._last_response = out_req
def _create_incoming_request(self, hostname: str, path: str, payload: dict, auth: Optional[AuthContainer]): session_id = random.randint(0, 30000) sender = f"rest_client_{session_id}" self._request = Request(path=path, session_id=session_id, sender=sender, receiver=hostname, payload=payload) if auth is not None: self._request.set_auth(auth) self._request.set_callback_method(self._get_response_function())
def _send(self, req: Request): """Sends a request on the serial port""" json_str = json.dumps(req.get_body()) req_line = "!r_p[{}]_b[{}]_\n".format(req.get_path(), json_str) self._logger.debug("Sending: {}".format(req_line[:-1])) out_data = req_line.encode() bytes_written = self._serial_client.write(out_data) if not bytes_written == len(out_data): self._logger.error( f"Problem sending request: only {bytes_written} of {len(out_data)} bytes written." )
def test_list_tables(self): request = Request('list_tables:me') lobby = Lobby() lobby._create_table('Test table 1', 6) lobby._create_table('Test table 2', 9) client = None result = lobby.handle_request(Request('list_tables:me'), client) self.assertEqual( result, '\nTables in the server:\nTest table 1: 0/6\nTest table 2: 0/9\n\n: ' )
def test_create_same_name_table(self): request = Request('list_tables:me') lobby = Lobby() self.assertEqual(len(lobby.tables), 0) client = None lobby.handle_request(Request('create_table:me:my table:2'), client) result = lobby.handle_request(Request('create_table:me:my table:2'), client) self.assertEqual(result, 'Table "my table" already exists') self.assertEqual(len(lobby.tables), 1)
def respond(req: Request, payload: dict, path: Optional[str]): if req.get_session_id() != self._request.get_session_id(): raise IllegalResponseException( f"Session IDs {req.get_session_id()} and " f"{self._request.get_session_id()} are not matching") if path is not None: res_path = path else: res_path = req.get_path() self._response = Request(path=res_path, session_id=req.get_session_id(), sender=self._request.get_receiver(), receiver=req.get_sender(), payload=payload)
def _respond_to(self, req: Request, payload: dict, path: Optional[str] = None): if path: out_path = path else: out_path = req.get_path() receiver = req.get_sender() out_req = Request(out_path, req.get_session_id(), self._host_name, receiver, payload) self._send(out_req)
def request_file(self, file_id): if not self.connected: return None request = Request('get_file', file_id) request_json = request_to_json(request) self.send_bytes_secure(bytes(request_json, encoding='utf-8')) return self.receive_file()
def _handle_request(self, req: Request): self._log_request(req) try: self._check_auth(req) except AuthError: return switcher = { ApiURIs.heartbeat.uri: self._handle_heartbeat, ApiURIs.sync_client.uri: self._handle_client_sync, ApiURIs.info_bridge.uri: self._handle_info_bridge, ApiURIs.info_gadgets.uri: self._handle_info_gadgets, ApiURIs.info_clients.uri: self._handle_info_clients, ApiURIs.update_gadget.uri: self._handle_update_gadget, ApiURIs.client_reboot.uri: self._handle_client_reboot, ApiURIs.client_config_write.uri: self._handle_client_config_write, ApiURIs.client_config_delete.uri: self._handle_client_config_delete, ApiURIs.config_storage_get_all.uri: self._handle_get_all_configs, ApiURIs.config_storage_get.uri: self._handle_get_config, ApiURIs.config_storage_save.uri: self._handle_save_config, ApiURIs.config_storage_delete.uri: self._handle_delete_config, ApiURIs.bridge_update_check.uri: self._handle_check_bridge_for_update, ApiURIs.bridge_update_execute.uri: self._handle_bridge_update } handler: Callable[[Request], None] = switcher.get(req.get_path(), self._handle_unknown) handler(req)
def exit(self): if not self.connected: return False request = Request('exit', '') request_json = request_to_json(request) self.send_bytes_secure(bytes(request_json, encoding='utf-8')) self.disconnect() return True
def thread_method(): time.sleep(1) out_req = Request(req.get_path(), req.get_session_id(), req.get_receiver(), req.get_sender(), {"ack": self._mock_ack}) self.receive(out_req)
def _handle_update_gadget(self, req: Request): """ Handles a characteristic update request, for a gadget, from any foreign source :param req: Request containing the gadget update request :return: None """ try: self._validator.validate(req.get_payload(), "api_gadget_update_request") except ValidationError: self._respond_with_error(req, "ValidationError", f"Request validation error at '{ApiURIs.update_gadget.uri}'") return try: gadget_update_info = ApiDecoder().decode_gadget_update(req.get_payload()) except GadgetDecodeError: self._respond_with_error(req, "GadgetDecodeError", f"Gadget update decode error at '{ApiURIs.sync_client.uri}'") return client_id = req.get_sender() gadget_info = [x for x in self._delegate.get_gadget_info() if x.get_name() == req.get_payload()["id"]] if not gadget_info: self._respond_with_error(req, "GagdetDoesNeeExist", "Sadly, no gadget with the given id exists") return gadget = gadget_info[0] updated_characteristics = [x.id for x in gadget_update_info.characteristics] buf_characteristics = [x for x in gadget.get_characteristics() if x.get_type() in updated_characteristics] for c in buf_characteristics: value = [x.step_value for x in gadget_update_info.characteristics if x.id == c.get_type()][0] c.set_step_value(value) out_gadget = AnyGadget(gadget_update_info.id, req.get_sender(), buf_characteristics) self._logger.info(f"Updating {len(buf_characteristics)} gadget characteristics from '{client_id}'") self._delegate.handle_gadget_update(out_gadget)
class RestServerRequestManager: _request: Request _response: Optional[Request] def __init__(self, hostname: str, path: str, payload: dict, auth: Optional[AuthContainer]): self._response = None self._create_incoming_request(hostname, path, payload, auth) def __del__(self): pass def _create_incoming_request(self, hostname: str, path: str, payload: dict, auth: Optional[AuthContainer]): session_id = random.randint(0, 30000) sender = f"rest_client_{session_id}" self._request = Request(path=path, session_id=session_id, sender=sender, receiver=hostname, payload=payload) if auth is not None: self._request.set_auth(auth) self._request.set_callback_method(self._get_response_function()) def _get_response_function(self) -> response_callback_type: def respond(req: Request, payload: dict, path: Optional[str]): if req.get_session_id() != self._request.get_session_id(): raise IllegalResponseException( f"Session IDs {req.get_session_id()} and " f"{self._request.get_session_id()} are not matching") if path is not None: res_path = path else: res_path = req.get_path() self._response = Request(path=res_path, session_id=req.get_session_id(), sender=self._request.get_receiver(), receiver=req.get_sender(), payload=payload) return respond def get_request(self) -> Request: return self._request def await_response(self, timeout: int = 2) -> Request: start_time = datetime.now() while self._response is None: time.sleep(0.1) now = datetime.now() if now > (start_time + timedelta(seconds=timeout)): break if self._response is None: raise NoResponseReceivedError(self._request.get_path()) return self._response
def test_list_tables_no_tables(self): lobby = Lobby() self.assertEqual(len(lobby.tables), 0) client = None result = lobby.handle_request(Request('list_tables:me'), client) self.assertEqual(result, 'No tables created')
def _decode_line(self, line) -> Optional[Request]: """Decodes a line and extracts a request if there is any""" if line[:3] == "!r_": elems = re.findall("_([a-z])\[(.+?)\]", line) req_dict = {} for elem_type, val in elems: if elem_type in req_dict: self._logger.warning( "Double key in request: '{}'".format(elem_type)) return None else: req_dict[elem_type] = val for key in ["p", "b"]: if key not in req_dict: self._logger.warning( "Missing key in request: '{}'".format(key)) return None try: json_body = json.loads(req_dict["b"]) try: self._validator.validate(json_body, REQ_VALIDATION_SCHEME_NAME) except ValidationError: self._logger.warning( "Could not decode Request, Schema Validation failed.") return None out_req = Request(path=req_dict["p"], session_id=json_body["session_id"], sender=json_body["sender"], receiver=json_body["receiver"], payload=json_body["payload"], connection_type=f"Serial[{self._address}]") out_req.set_auth(SerialAuthContainer()) return out_req except ValueError: return None return None
def test_create_table(self): request = Request('create_table:me:my table:2') lobby = Lobby() self.assertEqual(len(lobby.tables), 0) client = None lobby.handle_request(request, client) self.assertEqual(len(lobby.tables), 1) self.assertIn('my table', lobby.tables.keys())
def _handle_get_config(self, req: Request): """ Responds with the config for a given name, if there is none an error is returned :param req: Request containing the name of the requested config :return: None """ try: self._validator.validate(req.get_payload(), "api_config_delete_get") except ValidationError: self._respond_with_error(req, "ValidationError", f"Request validation error at '{ApiURIs.config_storage_get.uri}'") return try: name = req.get_payload()["name"] config = ClientConfigManager().get_config(name) payload = {"config": config} req.respond(payload) except ConfigDoesNotExistException as err: self._respond_with_error(req=req, err_type="ConfigDoesNotExistException", message=err.args[0])
def _handle_client_config_delete(self, req: Request): """ Handles a client config delete request :param req: Request containing the client config to delete :return: None """ try: self._validator.validate(req.get_payload(), "api_client_config_delete") except ValidationError: self._respond_with_error(req, "ValidationError", f"Request validation error at '{ApiURIs.update_gadget.uri}'") return
def _handle_delete_config(self, req: Request): """ Deletes the config for a given name, if there is no config, an error is returned :param req: Request containing name of the config :return: None """ try: self._validator.validate(req.get_payload(), "api_config_delete_get") except ValidationError: self._respond_with_error(req, "ValidationError", f"Request validation error at '{ApiURIs.config_storage_delete.uri}'") return name = req.get_payload()["name"] manager = ClientConfigManager() try: manager.delete_config_file(name) except ConfigDoesNotExistException as err: self._respond_with_error(req=req, err_type="ConfigDoesNotExistException", message=err.args[0]) return self._respond_with_status(req, True, "Config was deleted successfully")
def _handle_save_config(self, req: Request): """ Saves the given config or overwrites an already existing config :param req: Request containing the config :return: None """ try: self._validator.validate(req.get_payload(), "api_config_save") except ValidationError: self._respond_with_error(req, "ValidationError", f"Request validation error at '{ApiURIs.config_storage_save.uri}'") return config = req.get_payload()["config"] overwrite = req.get_payload()["overwrite"] manager = ClientConfigManager() try: manager.write_config(config, overwrite=overwrite) except ConfigAlreadyExistsException as err: self._respond_with_error(req=req, err_type="ConfigAlreadyExistsException", message=err.args[0]) return self._respond_with_status(req, True, "Config was saved successfully")