2019-04-14 15:28:19 +02:00
|
|
|
import os
|
2023-01-18 05:25:49 +01:00
|
|
|
from contextlib import suppress
|
2024-07-12 02:30:23 +02:00
|
|
|
from typing import TYPE_CHECKING, Any
|
2019-04-14 15:28:19 +02:00
|
|
|
|
2020-08-07 01:09:47 +02:00
|
|
|
import orjson
|
2019-04-14 15:28:19 +02:00
|
|
|
from django.http import HttpRequest, HttpResponse
|
2022-07-28 18:26:13 +02:00
|
|
|
from django.http.response import HttpResponseBase
|
2019-04-14 15:28:19 +02:00
|
|
|
from django.shortcuts import render
|
|
|
|
from django.test import Client
|
2024-06-28 21:02:09 +02:00
|
|
|
from pydantic import Json
|
2019-04-14 15:28:19 +02:00
|
|
|
|
2021-07-04 08:52:23 +02:00
|
|
|
from zerver.lib.exceptions import JsonableError, ResourceNotFoundError
|
2019-04-14 15:28:19 +02:00
|
|
|
from zerver.lib.integrations import WEBHOOK_INTEGRATIONS
|
2021-07-04 08:52:23 +02:00
|
|
|
from zerver.lib.response import json_success
|
2024-06-28 21:02:09 +02:00
|
|
|
from zerver.lib.typed_endpoint import PathOnly, typed_endpoint
|
2020-06-11 00:54:34 +02:00
|
|
|
from zerver.lib.webhooks.common import get_fixture_http_headers, standardize_headers
|
2023-12-15 02:14:24 +01:00
|
|
|
from zerver.models import UserProfile
|
|
|
|
from zerver.models.realms import get_realm
|
2019-04-14 15:28:19 +02:00
|
|
|
|
2022-07-21 19:54:20 +02:00
|
|
|
if TYPE_CHECKING:
|
|
|
|
from django.test.client import _MonkeyPatchedWSGIResponse as TestHttpResponse
|
|
|
|
|
2021-02-12 08:20:45 +01:00
|
|
|
ZULIP_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../../")
|
2019-04-14 15:28:19 +02:00
|
|
|
|
|
|
|
|
2024-07-12 02:30:17 +02:00
|
|
|
def get_webhook_integrations() -> list[str]:
|
2019-04-14 15:28:19 +02:00
|
|
|
return [integration.name for integration in WEBHOOK_INTEGRATIONS]
|
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2024-07-12 02:30:23 +02:00
|
|
|
def get_valid_integration_name(name: str) -> str | None:
|
2020-02-28 23:14:47 +01:00
|
|
|
for integration_name in get_webhook_integrations():
|
|
|
|
if name == integration_name:
|
|
|
|
return integration_name
|
|
|
|
return None
|
2019-04-14 15:28:19 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
2019-04-14 15:28:19 +02:00
|
|
|
def dev_panel(request: HttpRequest) -> HttpResponse:
|
|
|
|
integrations = get_webhook_integrations()
|
|
|
|
bots = UserProfile.objects.filter(is_bot=True, bot_type=UserProfile.INCOMING_WEBHOOK_BOT)
|
2021-03-26 17:33:41 +01:00
|
|
|
context = {
|
|
|
|
"integrations": integrations,
|
|
|
|
"bots": bots,
|
|
|
|
# We set isolated_page to avoid clutter from footer/header.
|
|
|
|
"isolated_page": True,
|
|
|
|
}
|
2021-04-17 15:18:15 +02:00
|
|
|
return render(request, "zerver/development/integrations_dev_panel.html", context)
|
2019-04-14 15:28:19 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
|
|
|
|
def send_webhook_fixture_message(
|
2024-07-12 02:30:17 +02:00
|
|
|
url: str, body: str, is_json: bool, custom_headers: dict[str, Any]
|
2022-07-21 19:54:20 +02:00
|
|
|
) -> "TestHttpResponse":
|
2019-05-16 20:29:18 +02:00
|
|
|
client = Client()
|
|
|
|
realm = get_realm("zulip")
|
2019-06-21 04:41:30 +02:00
|
|
|
standardized_headers = standardize_headers(custom_headers)
|
|
|
|
http_host = standardized_headers.pop("HTTP_HOST", realm.host)
|
2019-05-16 20:29:18 +02:00
|
|
|
if is_json:
|
2019-06-21 04:41:30 +02:00
|
|
|
content_type = standardized_headers.pop("HTTP_CONTENT_TYPE", "application/json")
|
2019-05-16 20:29:18 +02:00
|
|
|
else:
|
2019-06-21 04:41:30 +02:00
|
|
|
content_type = standardized_headers.pop("HTTP_CONTENT_TYPE", "text/plain")
|
2021-02-12 08:19:30 +01:00
|
|
|
return client.post(
|
2022-07-21 19:54:20 +02:00
|
|
|
url,
|
|
|
|
body,
|
|
|
|
content_type=content_type,
|
|
|
|
follow=False,
|
|
|
|
secure=False,
|
2023-07-19 22:57:31 +02:00
|
|
|
headers=None,
|
2022-07-21 19:54:20 +02:00
|
|
|
HTTP_HOST=http_host,
|
|
|
|
**standardized_headers,
|
2021-02-12 08:19:30 +01:00
|
|
|
)
|
|
|
|
|
2019-05-16 20:29:18 +02:00
|
|
|
|
2024-06-28 21:02:09 +02:00
|
|
|
@typed_endpoint
|
|
|
|
def get_fixtures(request: HttpRequest, *, integration_name: PathOnly[str]) -> HttpResponse:
|
2020-02-28 23:14:47 +01:00
|
|
|
valid_integration_name = get_valid_integration_name(integration_name)
|
|
|
|
if not valid_integration_name:
|
2021-07-04 08:52:23 +02:00
|
|
|
raise ResourceNotFoundError(f'"{integration_name}" is not a valid webhook integration.')
|
2019-04-14 15:28:19 +02:00
|
|
|
|
|
|
|
fixtures = {}
|
2020-06-10 06:40:53 +02:00
|
|
|
fixtures_dir = os.path.join(ZULIP_PATH, f"zerver/webhooks/{valid_integration_name}/fixtures")
|
2019-04-14 15:28:19 +02:00
|
|
|
if not os.path.exists(fixtures_dir):
|
2023-07-19 23:06:38 +02:00
|
|
|
msg = f'The integration "{valid_integration_name}" does not have fixtures.'
|
2021-07-04 08:52:23 +02:00
|
|
|
raise ResourceNotFoundError(msg)
|
2019-04-14 15:28:19 +02:00
|
|
|
|
|
|
|
for fixture in os.listdir(fixtures_dir):
|
|
|
|
fixture_path = os.path.join(fixtures_dir, fixture)
|
2020-04-09 21:51:58 +02:00
|
|
|
with open(fixture_path) as f:
|
2019-07-14 21:37:08 +02:00
|
|
|
body = f.read()
|
2023-01-18 05:25:49 +01:00
|
|
|
# The file extension will be used to determine the type.
|
|
|
|
with suppress(orjson.JSONDecodeError):
|
2020-08-07 01:09:47 +02:00
|
|
|
body = orjson.loads(body)
|
2019-06-07 20:06:06 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
headers_raw = get_fixture_http_headers(
|
|
|
|
valid_integration_name, "".join(fixture.split(".")[:-1])
|
|
|
|
)
|
2020-03-20 18:16:51 +01:00
|
|
|
|
2023-05-30 00:01:44 +02:00
|
|
|
def fix_name(header: str) -> str: # nocoverage
|
2024-09-03 19:42:14 +02:00
|
|
|
return header.removeprefix("HTTP_") # HTTP_ is a prefix intended for Django.
|
2019-06-07 20:06:06 +02:00
|
|
|
|
2020-03-20 18:16:51 +01:00
|
|
|
headers = {fix_name(k): v for k, v in headers_raw.items()}
|
2019-06-07 20:06:06 +02:00
|
|
|
fixtures[fixture] = {"body": body, "headers": headers}
|
2019-04-14 15:28:19 +02:00
|
|
|
|
2022-01-31 13:44:02 +01:00
|
|
|
return json_success(request, data={"fixtures": fixtures})
|
2019-04-14 15:28:19 +02:00
|
|
|
|
|
|
|
|
2024-06-28 21:02:09 +02:00
|
|
|
@typed_endpoint
|
2021-02-12 08:19:30 +01:00
|
|
|
def check_send_webhook_fixture_message(
|
|
|
|
request: HttpRequest,
|
2024-06-28 21:02:09 +02:00
|
|
|
*,
|
|
|
|
url: str,
|
|
|
|
body: str,
|
|
|
|
is_json: Json[bool],
|
|
|
|
custom_headers: str,
|
2022-07-28 18:26:13 +02:00
|
|
|
) -> HttpResponseBase:
|
2019-06-25 08:17:10 +02:00
|
|
|
try:
|
2020-08-07 01:09:47 +02:00
|
|
|
custom_headers_dict = orjson.loads(custom_headers)
|
2023-05-30 00:01:44 +02:00
|
|
|
except orjson.JSONDecodeError as ve: # nocoverage
|
2021-06-30 18:35:50 +02:00
|
|
|
raise JsonableError(f"Custom HTTP headers are not in a valid JSON format. {ve}") # nolint
|
2019-06-25 08:17:10 +02:00
|
|
|
|
2021-02-12 08:19:30 +01:00
|
|
|
response = send_webhook_fixture_message(url, body, is_json, custom_headers_dict)
|
2019-04-14 15:28:19 +02:00
|
|
|
if response.status_code == 200:
|
2021-02-12 08:19:30 +01:00
|
|
|
responses = [{"status_code": response.status_code, "message": response.content.decode()}]
|
2022-01-31 13:44:02 +01:00
|
|
|
return json_success(request, data={"responses": responses})
|
2023-10-04 20:45:34 +02:00
|
|
|
else: # nocoverage
|
2019-04-14 15:28:19 +02:00
|
|
|
return response
|
2019-05-16 20:29:18 +02:00
|
|
|
|
|
|
|
|
2024-06-28 21:02:09 +02:00
|
|
|
@typed_endpoint
|
2021-02-12 08:19:30 +01:00
|
|
|
def send_all_webhook_fixture_messages(
|
2024-06-28 21:02:09 +02:00
|
|
|
request: HttpRequest, *, url: str, integration_name: str
|
2021-02-12 08:19:30 +01:00
|
|
|
) -> HttpResponse:
|
2020-02-28 23:14:47 +01:00
|
|
|
valid_integration_name = get_valid_integration_name(integration_name)
|
2023-05-30 00:01:44 +02:00
|
|
|
if not valid_integration_name: # nocoverage
|
2021-07-04 08:52:23 +02:00
|
|
|
raise ResourceNotFoundError(f'"{integration_name}" is not a valid webhook integration.')
|
2020-02-28 23:14:47 +01:00
|
|
|
|
2020-06-10 06:40:53 +02:00
|
|
|
fixtures_dir = os.path.join(ZULIP_PATH, f"zerver/webhooks/{valid_integration_name}/fixtures")
|
2019-05-16 20:29:18 +02:00
|
|
|
if not os.path.exists(fixtures_dir):
|
2023-07-19 23:06:38 +02:00
|
|
|
msg = f'The integration "{valid_integration_name}" does not have fixtures.'
|
2021-07-04 08:52:23 +02:00
|
|
|
raise ResourceNotFoundError(msg)
|
2019-05-16 20:29:18 +02:00
|
|
|
|
|
|
|
responses = []
|
|
|
|
for fixture in os.listdir(fixtures_dir):
|
|
|
|
fixture_path = os.path.join(fixtures_dir, fixture)
|
2020-04-09 21:51:58 +02:00
|
|
|
with open(fixture_path) as f:
|
2019-07-14 21:37:08 +02:00
|
|
|
content = f.read()
|
2019-06-25 08:17:10 +02:00
|
|
|
x = fixture.split(".")
|
|
|
|
fixture_name, fixture_format = "".join(_ for _ in x[:-1]), x[-1]
|
2020-02-28 23:14:47 +01:00
|
|
|
headers = get_fixture_http_headers(valid_integration_name, fixture_name)
|
2023-01-18 03:28:19 +01:00
|
|
|
is_json = fixture_format == "json"
|
2019-06-25 08:17:10 +02:00
|
|
|
response = send_webhook_fixture_message(url, content, is_json, headers)
|
2021-02-12 08:19:30 +01:00
|
|
|
responses.append(
|
|
|
|
{
|
|
|
|
"status_code": response.status_code,
|
|
|
|
"fixture_name": fixture,
|
|
|
|
"message": response.content.decode(),
|
|
|
|
}
|
|
|
|
)
|
2022-01-31 13:44:02 +01:00
|
|
|
return json_success(request, data={"responses": responses})
|