Exemplo n.º 1
0
 def validate_slack_signature(self, request: GladosRequest):
     valid = self.client.validate_slack_signature(
         signing_secret=self.signing_secret, **request.slack_verify.json
     )
     logging.info(f"valid payload signature from slack: {valid}")
     if not valid:
         raise SlackRequestError("Signature of request is not valid")
Exemplo n.º 2
0
    def _perform_http_request(self, *, url: str, body: Dict[str, any],
                              headers: Dict[str, str]) -> WebhookResponse:
        """Performs an HTTP request and parses the response.
        :param url: a complete URL to send data (e.g., https://hooks.slack.com/XXX)
        :param body: request body data
        :param headers: complete set of request headers
        :return: API response
        """
        body = json.dumps(body)
        headers["Content-Type"] = "application/json;charset=utf-8"

        if self.logger.level <= logging.DEBUG:
            self.logger.debug(
                f"Sending a request - url: {self.url}, body: {body}, headers: {headers}"
            )
        try:
            # for security
            if url.lower().startswith("http"):
                req = Request(method="POST",
                              url=url,
                              data=body.encode("utf-8"),
                              headers=headers)
            else:
                raise SlackRequestError(f"Invalid URL detected: {url}")

            resp: HTTPResponse = urlopen(req)
            charset: str = resp.headers.get_content_charset() or "utf-8"
            response_body: str = resp.read().decode(charset)
            resp = WebhookResponse(
                url=self.url,
                status_code=resp.status,
                body=response_body,
                headers=resp.headers,
            )
            self._debug_log_response(resp)
            return resp

        except HTTPError as e:
            charset: str = e.headers.get_content_charset() or "utf-8"
            response_body: str = resp.read().decode(charset)
            resp = WebhookResponse(
                url=self.url,
                status_code=e.code,
                body=response_body,
                headers=e.headers,
            )
            if e.code == 429:
                # for backward-compatibility with WebClient (v.2.5.0 or older)
                resp.headers["Retry-After"] = resp.headers["retry-after"]
            self._debug_log_response(resp)
            return resp

        except Exception as err:
            self.logger.error(
                f"Failed to send a request to Slack API server: {err}")
            raise err
Exemplo n.º 3
0
    def _update_call_participants(kwargs, users: Union[str, List[Dict[str, str]]]):
        if users is None:
            return

        if isinstance(users, list):
            kwargs.update({"users": json.dumps(users)})
        elif isinstance(users, str):
            kwargs.update({"users": users})
        else:
            raise SlackRequestError("users must be either str or List[Dict[str, str]]")
Exemplo n.º 4
0
def _build_req_args(
    *,
    token: Optional[str],
    http_verb: str,
    files: dict,
    data: Union[dict, FormData],
    params: dict,
    json: dict,  # skipcq: PYL-W0621
    headers: dict,
    auth: dict,
    ssl: Optional[SSLContext],
    proxy: Optional[str],
) -> dict:
    has_json = json is not None
    has_files = files is not None
    if has_json and http_verb != "POST":
        msg = "Json data can only be submitted as POST requests. GET requests should use the 'params' argument."
        raise SlackRequestError(msg)

    if auth:
        auth = BasicAuth(auth["client_id"], auth["client_secret"])

    if data is not None and isinstance(data, dict):
        data = {k: v for k, v in data.items() if v is not None}
    if files is not None and isinstance(files, dict):
        files = {k: v for k, v in files.items() if v is not None}
    if params is not None and isinstance(params, dict):
        params = {k: v for k, v in params.items() if v is not None}

    token: Optional[str] = token
    if params is not None and "token" in params:
        token = params.pop("token")
    if json is not None and "token" in json:
        token = json.pop("token")
    req_args = {
        "headers": _get_headers(
            headers=headers,
            token=token,
            has_json=has_json,
            has_files=has_files,
            request_specific_headers=headers,
        ),
        "data": data,
        "files": files,
        "params": params,
        "json": json,
        "ssl": ssl,
        "proxy": proxy,
        "auth": auth,
    }
    return req_args
Exemplo n.º 5
0
    def _perform_http_request(self, *, body: Dict[str, any],
                              headers: Dict[str, str]) -> WebhookResponse:
        """Performs an HTTP request and parses the response.
        :param url: a complete URL to send data (e.g., https://hooks.slack.com/XXX)
        :param body: request body data
        :param headers: complete set of request headers
        :return: API response
        """
        body = json.dumps(body)
        headers["Content-Type"] = "application/json;charset=utf-8"

        if self.logger.level <= logging.DEBUG:
            self.logger.debug(
                f"Sending a request - url: {self.url}, body: {body}, headers: {headers}"
            )
        try:
            url = self.url
            opener: Optional[OpenerDirector] = None
            # for security (BAN-B310)
            if url.lower().startswith("http"):
                req = Request(method="POST",
                              url=url,
                              data=body.encode("utf-8"),
                              headers=headers)
                if self.proxy is not None:
                    if isinstance(self.proxy, str):
                        opener = urllib.request.build_opener(
                            ProxyHandler({
                                "http": self.proxy,
                                "https": self.proxy
                            }),
                            HTTPSHandler(context=self.ssl),
                        )
                    else:
                        raise SlackRequestError(
                            f"Invalid proxy detected: {self.proxy} must be a str value"
                        )
            else:
                raise SlackRequestError(f"Invalid URL detected: {url}")

            # NOTE: BAN-B310 is already checked above
            resp: Optional[HTTPResponse] = None
            if opener:
                resp = opener.open(req,
                                   timeout=self.timeout)  # skipcq: BAN-B310
            else:
                resp = urlopen(  # skipcq: BAN-B310
                    req,
                    context=self.ssl,
                    timeout=self.timeout)
            charset: str = resp.headers.get_content_charset() or "utf-8"
            response_body: str = resp.read().decode(charset)
            resp = WebhookResponse(
                url=url,
                status_code=resp.status,
                body=response_body,
                headers=resp.headers,
            )
            _debug_log_response(self.logger, resp)
            return resp

        except HTTPError as e:
            charset = e.headers.get_content_charset() or "utf-8"
            body: str = e.read().decode(charset)  # read the response body here
            resp = WebhookResponse(
                url=url,
                status_code=e.code,
                body=body,
                headers=e.headers,
            )
            if e.code == 429:
                # for backward-compatibility with WebClient (v.2.5.0 or older)
                resp.headers["Retry-After"] = resp.headers["retry-after"]
            _debug_log_response(self.logger, resp)
            return resp

        except Exception as err:
            self.logger.error(
                f"Failed to send a request to Slack API server: {err}")
            raise err
Exemplo n.º 6
0
    def _perform_urllib_http_request(
            self, *, url: str, args: Dict[str, Dict[str,
                                                    any]]) -> Dict[str, any]:
        """Performs an HTTP request and parses the response.

        :param url: a complete URL (e.g., https://www.slack.com/api/chat.postMessage)
        :param args: args has "headers", "data", "params", and "json"
            "headers": Dict[str, str]
            "data": Dict[str, any]
            "params": Dict[str, str],
            "json": Dict[str, any],
        :return: dict {status: int, headers: Headers, body: str}
        """
        headers = args["headers"]
        if args["json"]:
            body = json.dumps(args["json"])
            headers["Content-Type"] = "application/json;charset=utf-8"
        elif args["data"]:
            boundary = f"--------------{uuid.uuid4()}"
            sep_boundary = b"\r\n--" + boundary.encode("ascii")
            end_boundary = sep_boundary + b"--\r\n"
            body = io.BytesIO()
            data = args["data"]
            for key, value in data.items():
                readable = getattr(value, "readable", None)
                if readable and value.readable():
                    filename = "Uploaded file"
                    name_attr = getattr(value, "name", None)
                    if name_attr:
                        filename = (name_attr.decode("utf-8") if isinstance(
                            name_attr, bytes) else name_attr)
                    if "filename" in data:
                        filename = data["filename"]
                    mimetype = (mimetypes.guess_type(filename)[0]
                                or "application/octet-stream")
                    title = (
                        f'\r\nContent-Disposition: form-data; name="{key}"; filename="{filename}"\r\n'
                        + f"Content-Type: {mimetype}\r\n")
                    value = value.read()
                else:
                    title = f'\r\nContent-Disposition: form-data; name="{key}"\r\n'
                    value = str(value).encode("utf-8")
                body.write(sep_boundary)
                body.write(title.encode("utf-8"))
                body.write(b"\r\n")
                body.write(value)

            body.write(end_boundary)
            body = body.getvalue()
            headers[
                "Content-Type"] = f"multipart/form-data; boundary={boundary}"
            headers["Content-Length"] = len(body)
        elif args["params"]:
            body = urlencode(args["params"])
            headers["Content-Type"] = "application/x-www-form-urlencoded"
        else:
            body = None

        if isinstance(body, str):
            body = body.encode("utf-8")

        # NOTE: Intentionally ignore the `http_verb` here
        # Slack APIs accepts any API method requests with POST methods
        try:
            # urllib not only opens http:// or https:// URLs, but also ftp:// and file://.
            # With this it might be possible to open local files on the executing machine
            # which might be a security risk if the URL to open can be manipulated by an external user.
            # (BAN-B310)
            if url.lower().startswith("http"):
                req = Request(method="POST",
                              url=url,
                              data=body,
                              headers=headers)
                opener: Optional[OpenerDirector] = None
                if self.proxy is not None:
                    if isinstance(self.proxy, str):
                        opener = urllib.request.build_opener(
                            ProxyHandler({
                                "http": self.proxy,
                                "https": self.proxy
                            }),
                            HTTPSHandler(context=self.ssl),
                        )
                    else:
                        raise SlackRequestError(
                            f"Invalid proxy detected: {self.proxy} must be a str value"
                        )

                # NOTE: BAN-B310 is already checked above
                resp: Optional[HTTPResponse] = None
                if opener:
                    resp = opener.open(
                        req, timeout=self.timeout)  # skipcq: BAN-B310
                else:
                    resp = urlopen(  # skipcq: BAN-B310
                        req,
                        context=self.ssl,
                        timeout=self.timeout)
                charset = resp.headers.get_content_charset() or "utf-8"
                body: str = resp.read().decode(
                    charset)  # read the response body here
                return {
                    "status": resp.code,
                    "headers": resp.headers,
                    "body": body
                }
            raise SlackRequestError(f"Invalid URL detected: {url}")
        except HTTPError as e:
            resp = {"status": e.code, "headers": e.headers}
            if e.code == 429:
                # for compatibility with aiohttp
                resp["headers"]["Retry-After"] = resp["headers"]["retry-after"]

            charset = e.headers.get_content_charset() or "utf-8"
            body: str = e.read().decode(charset)  # read the response body here
            resp["body"] = body
            return resp

        except Exception as err:
            self._logger.error(
                f"Failed to send a request to Slack API server: {err}")
            raise err