Example #1
0
    def _perform_http_request_internal(self, url: str, req: Request):
        opener: Optional[OpenerDirector] = None
        # for security (BAN-B310)
        if url.lower().startswith("http"):
            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
        http_resp: Optional[HTTPResponse] = None
        if opener:
            http_resp = opener.open(req, timeout=self.timeout)  # skipcq: BAN-B310
        else:
            http_resp = urlopen(  # skipcq: BAN-B310
                req, context=self.ssl, timeout=self.timeout
            )
        charset: str = http_resp.headers.get_content_charset() or "utf-8"
        response_body: str = http_resp.read().decode(charset)
        resp = WebhookResponse(
            url=url,
            status_code=http_resp.status,
            body=response_body,
            headers=http_resp.headers,
        )
        _debug_log_response(self.logger, resp)
        return resp
Example #2
0
    def _perform_urllib_http_request_internal(
        self,
        url: str,
        req: Request,
    ) -> Dict[str, Any]:
        # 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"):
            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)
            if resp.headers.get_content_type() == "application/gzip":
                # admin.analytics.getFile
                body: bytes = resp.read()
                if self._logger.level <= logging.DEBUG:
                    self._logger.debug("Received the following response - "
                                       f"status: {resp.code}, "
                                       f"headers: {dict(resp.headers)}, "
                                       f"body: (binary)")
                return {
                    "status": resp.code,
                    "headers": resp.headers,
                    "body": body
                }

            charset = resp.headers.get_content_charset() or "utf-8"
            body: str = resp.read().decode(
                charset)  # read the response body here
            if self._logger.level <= logging.DEBUG:
                self._logger.debug("Received the following response - "
                                   f"status: {resp.code}, "
                                   f"headers: {dict(resp.headers)}, "
                                   f"body: {body}")
            return {"status": resp.code, "headers": resp.headers, "body": body}
        raise SlackRequestError(f"Invalid URL detected: {url}")
def execute(config: Configuration, output_all: bool, output_file: str,
            output_slack: bool):
    with open(output_file, "w", newline='') as f:
        writer = csv.DictWriter(
            f,
            fieldnames=["secret", "project", "repository", "file", "commit"])
        writer.writeheader()
        for repo_info, repo_config, secrets in scan(config, output_all):
            for secret in secrets:
                row = {
                    "secret": secret["line"].strip() or secret["file"],
                    "project": repo_info.project,
                    "repository": repo_info.name,
                    "file": secret["file"],
                    "commit": secret["commit"]
                }
                print(row)
                writer.writerow(row)

            if output_slack and repo_config.slack_webhook:
                webhook = WebhookClient(repo_config.slack_webhook)
                for blocks in SlackMessageBuilder(repo_info, secrets).build():
                    response = webhook.send(text="fallback", blocks=blocks)
                    if response.status_code != 200:
                        raise SlackRequestError(
                            f"Error when sending message blocks to slack: {response.body}"
                        )
Example #4
0
def _build_req_args(
    *,
    token: Optional[str],
    http_verb: str,
    files: dict,
    data: dict,
    default_params: dict,
    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 data is not None and isinstance(data, dict):
        data = {k: v for k, v in data.items() if v is not None}
        _set_default_params(data, default_params)
    if files is not None and isinstance(files, dict):
        files = {k: v for k, v in files.items() if v is not None}
        # NOTE: We do not need to all #_set_default_params here
        # because other parameters in binary data requests can exist
        # only in either data or params, not in files.
    if params is not None and isinstance(params, dict):
        params = {k: v for k, v in params.items() if v is not None}
        _set_default_params(params, default_params)
    if json is not None and isinstance(json, dict):
        _set_default_params(json, default_params)

    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
Example #5
0
def _update_call_participants(
    kwargs, users: Union[str, Sequence[Dict[str, str]]]
) -> None:
    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 Sequence[Dict[str, str]]")
Example #6
0
    def _perform_http_request(
        self,
        *,
        http_verb: str = "GET",
        url: str,
        body_params: Optional[Dict[str, any]] = None,
        headers: Dict[str, str],
    ) -> AuditLogsResponse:
        if body_params is not None:
            body_params = json.dumps(body_params)
        headers["Content-Type"] = "application/json;charset=utf-8"

        if self.logger.level <= logging.DEBUG:
            headers_for_logging = {
                k: "(redacted)" if k.lower() == "authorization" else v
                for k, v in headers.items()
            }
            self.logger.debug(
                f"Sending a request - url: {url}, body: {body_params}, headers: {headers_for_logging}"
            )
        try:
            opener: Optional[OpenerDirector] = None
            # for security (BAN-B310)
            if url.lower().startswith("http"):
                req = Request(
                    method=http_verb,
                    url=url,
                    data=body_params.encode("utf-8")
                    if body_params is not None else None,
                    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 = AuditLogsResponse(
                url=url,
                status_code=resp.status,
                raw_body=response_body,
                headers=resp.headers,
            )
            _debug_log_response(self.logger, resp)
            return resp

        except HTTPError as e:
            # read the response body here
            charset = e.headers.get_content_charset() or "utf-8"
            body_params: str = e.read().decode(charset)
            resp = AuditLogsResponse(
                url=url,
                status_code=e.code,
                raw_body=body_params,
                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
    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)
                if resp.headers.get_content_type() == "application/gzip":
                    # admin.analytics.getFile
                    body: bytes = resp.read()
                    return {
                        "status": resp.code,
                        "headers": resp.headers,
                        "body": body
                    }

                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"]

            # read the response body here
            charset = e.headers.get_content_charset() or "utf-8"
            body: str = e.read().decode(charset)
            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
Example #8
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:
            # read the response body here
            charset = e.headers.get_content_charset() or "utf-8"
            body: str = e.read().decode(charset)
            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