예제 #1
0
    async def _execute_call(
        self,
        kwargs: Dict[str, str],
        http_method: str,
        config: common.UrlMethod,
        message: Dict[str, Any],
        request_headers: Mapping[str, str],
        auth: Optional[MutableMapping[str, str]] = None,
    ) -> common.Response:
        try:
            if kwargs is None or config is None:
                raise Exception("This method is unknown! This should not occur!")

            # create message that contains all arguments
            message.update(kwargs)

            if config.properties.validate_sid:
                if "sid" not in message:
                    raise exceptions.BadRequest("this is an agent to server call, it should contain an agent session id")

                elif not self.validate_sid(uuid.UUID(message["sid"])):
                    raise exceptions.BadRequest("the sid %s is not valid." % message["sid"])

            arguments = CallArguments(config.properties, message, request_headers)
            await arguments.process()
            authorize_request(auth, arguments.metadata, arguments.call_args, config)

            # rename arguments if handler requests this
            call_args = arguments.call_args
            if hasattr(config.handler, "__protocol_mapping__"):
                for k, v in config.handler.__protocol_mapping__.items():
                    if v in call_args:
                        call_args[k] = call_args[v]
                        del call_args[v]

            LOGGER.debug(
                "Calling method %s(%s)",
                config.handler,
                ", ".join(["%s='%s'" % (name, common.shorten(str(value))) for name, value in arguments.call_args.items()]),
            )

            result = await config.handler(**arguments.call_args)
            return await arguments.process_return(config, result)
        except pydantic.ValidationError:
            LOGGER.exception(f"The handler {config.handler} caused a validation error in a data model (pydantic).")
            raise exceptions.ServerError("data validation error.")

        except exceptions.BaseHttpException:
            LOGGER.debug("An HTTP Error occurred", exc_info=True)
            raise

        except Exception as e:
            LOGGER.exception("An exception occured during the request.")
            raise exceptions.ServerError(str(e.args))
예제 #2
0
    def _validate_union_return(self, arg_type: Type, value: Any) -> None:
        """Validate a return with a union type
        :see: protocol.common.MethodProperties._validate_function_types
        """
        matching_type = None
        for t in typing_inspect.get_args(arg_type, evaluate=True):
            instanceof_type = t
            if typing_inspect.is_generic_type(t):
                instanceof_type = typing_inspect.get_origin(t)

            if isinstance(value, instanceof_type):
                if matching_type is not None:
                    raise exceptions.ServerError(
                        f"Return type is defined as a union {arg_type} for which multiple "
                        f"types match the provided value {value}"
                    )
                matching_type = t

        if matching_type is None:
            raise exceptions.BadRequest(
                f"Invalid return value, no matching type found in union {arg_type} for value type {type(value)}"
            )

        if typing_inspect.is_generic_type(matching_type):
            self._validate_generic_return(arg_type, matching_type)
예제 #3
0
    def _validate_generic_return(self, arg_type: Type, value: Any) -> None:
        """Validate List or Dict types.

        :note: we return any here because the calling function also returns any.
        """
        if issubclass(typing_inspect.get_origin(arg_type), list):
            if not isinstance(value, list):
                raise exceptions.ServerError(
                    f"Invalid return value, type needs to be a list. Argument type should be {arg_type}"
                )

            el_type = typing_inspect.get_args(arg_type, evaluate=True)[0]
            if el_type is Any:
                return
            for el in value:
                if typing_inspect.is_union_type(el_type):
                    self._validate_union_return(el_type, el)
                elif not isinstance(el, el_type):
                    raise exceptions.ServerError(f"Element {el} of returned list is not of type {el_type}.")

        elif issubclass(typing_inspect.get_origin(arg_type), dict):
            if not isinstance(value, dict):
                raise exceptions.ServerError(
                    f"Invalid return value, type needs to be a dict. Argument type should be {arg_type}"
                )

            el_type = typing_inspect.get_args(arg_type, evaluate=True)[1]
            if el_type is Any:
                return
            for k, v in value.items():
                if not isinstance(k, str):
                    raise exceptions.ServerError("Keys of return dict need to be strings.")

                if typing_inspect.is_union_type(el_type):
                    self._validate_union_return(el_type, v)
                elif not isinstance(v, el_type):
                    raise exceptions.ServerError(f"Element {v} of returned list is not of type {el_type}.")

        else:
            # This should not happen because of MethodProperties validation
            raise exceptions.BadRequest(
                f"Failed to validate generic type {arg_type} of return value, only List and Dict are supported"
            )
예제 #4
0
 def _encode_body(self, body: ReturnTypes,
                  content_type: str) -> Union[str, bytes]:
     if content_type == common.JSON_CONTENT:
         return common.json_encode(body)
     if content_type == common.HTML_CONTENT:
         assert isinstance(body, str)
         return body.encode(common.HTML_ENCODING)
     if content_type == common.HTML_CONTENT_WITH_UTF8_CHARSET:
         assert isinstance(body, str)
         return body.encode(common.UTF8_ENCODING)
     elif not isinstance(body, (str, bytes)):
         raise exceptions.ServerError(
             f"Body should be str or bytes and not {type(body)}."
             " For dict make sure content type is set to {common.JSON_CONTENT}"
         )
     return body
예제 #5
0
    async def get_server_status(self) -> StatusResponse:
        product_metadata = self.feature_manager.get_product_metadata()
        if product_metadata.version is None:
            raise exceptions.ServerError(
                "Could not find version number for the inmanta compiler."
                "Is inmanta installed? Use setuptools install or setuptools dev to install."
            )

        slices = []
        for slice_name, slice in self._server.get_slices().items():
            try:
                slices.append(
                    SliceStatus(name=slice_name,
                                status=await slice.get_status()))
            except Exception:
                LOGGER.error(
                    f"The following error occured while trying to determine the status of slice {slice_name}",
                    exc_info=True,
                )

        response = StatusResponse(
            product=product_metadata.product,
            edition=product_metadata.edition,
            version=product_metadata.version,
            license=product_metadata.license,
            extensions=self.get_extension_statuses(
                list(self._server.get_slices().values())),
            slices=slices,
            features=[
                FeatureStatus(slice=feature.slice,
                              name=feature.name,
                              value=self.feature_manager.get_value(feature))
                for feature in self.feature_manager.get_features()
            ],
        )

        return response
예제 #6
0
    async def process_return(self, config: common.UrlMethod, result: Apireturn) -> common.Response:
        """A handler can return ApiReturn, so lets handle all possible return types and convert it to a Response

        Apireturn = Union[int, Tuple[int, Optional[JsonType]], "ReturnValue", "BaseModel"]
        """
        if "return" in self._argspec.annotations:  # new style with return type
            return_type = self._argspec.annotations["return"]

            if return_type is None:
                if result is not None:
                    raise exceptions.ServerError(f"Method {config.method_name} returned a result but is defined as -> None")

                return common.Response.create(ReturnValue(status_code=200, response=None), envelope=False)

            # There is no obvious method to check if the return_type is a specific version of the generic ReturnValue
            # The way this is implemented in typing is different for python 3.6 and 3.7. In this code we "trust" that the
            # signature of the handler and the method definition matches and the returned value matches this return value
            # Both isubclass and isinstance fail on this type
            # This check needs to be first because isinstance fails on generic types.
            # TODO: also validate the value inside a ReturnValue
            if return_type is Any:
                return common.Response.create(
                    ReturnValue(response=result), config.properties.envelope, config.properties.envelope_key
                )

            elif typing_inspect.is_union_type(return_type):
                self._validate_union_return(return_type, result)
                return common.Response.create(
                    ReturnValue(response=result), config.properties.envelope, config.properties.envelope_key
                )

            if typing_inspect.is_generic_type(return_type):
                if isinstance(result, ReturnValue):
                    return common.Response.create(result, config.properties.envelope, config.properties.envelope_key)
                else:
                    self._validate_generic_return(return_type, result)
                    return common.Response.create(
                        ReturnValue(response=result), config.properties.envelope, config.properties.envelope_key
                    )

            elif isinstance(result, BaseModel):
                return common.Response.create(
                    ReturnValue(response=result), config.properties.envelope, config.properties.envelope_key
                )

            elif isinstance(result, common.VALID_SIMPLE_ARG_TYPES):
                return common.Response.create(
                    ReturnValue(response=result), config.properties.envelope, config.properties.envelope_key
                )

            else:
                raise exceptions.ServerError(
                    f"Method {config.method_name} returned an invalid result {result} instead of a BaseModel or ReturnValue"
                )

        else:  # "old" style method definition
            if isinstance(result, tuple):
                if len(result) == 2:
                    code, body = result
                else:
                    raise exceptions.ServerError("Handlers for method call can only return a status code and a reply")

            elif isinstance(result, int):
                code = result
                body = None

            else:
                raise exceptions.ServerError(
                    f"Method {config.method_name} returned an invalid result {result} instead of a status code or tupple"
                )

            if body is not None:
                if config.properties.reply:
                    return common.Response.create(
                        ReturnValue(status_code=code, response=body),
                        config.properties.envelope,
                        config.properties.envelope_key,
                    )
                else:
                    LOGGER.warning("Method %s returned a result although it has no reply!")

            return common.Response.create(ReturnValue(status_code=code, response=None), envelope=False)