예제 #1
0
    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)
예제 #2
0
 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}")
예제 #3
0
    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()
예제 #4
0
    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
예제 #5
0
    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")
예제 #6
0
    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)
예제 #7
0
    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
예제 #8
0
 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())
예제 #10
0
    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)
예제 #11
0
 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
예제 #12
0
 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())
예제 #13
0
    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."
            )
예제 #14
0
    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: '
        )
예제 #15
0
    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)
예제 #16
0
        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)
예제 #17
0
    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)
예제 #18
0
 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()
예제 #19
0
    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)
예제 #20
0
 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
예제 #21
0
 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)
예제 #22
0
    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)
예제 #23
0
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
예제 #24
0
    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')
예제 #25
0
    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
예제 #26
0
    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())
예제 #27
0
    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])
예제 #28
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
예제 #29
0
    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")
예제 #30
0
    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")