diff --git a/zerver/webhooks/teamcity/fixtures/slack_non_generic_payload.json b/zerver/webhooks/teamcity/fixtures/slack_non_generic_payload.json new file mode 100644 index 0000000000..a0f343d0e2 --- /dev/null +++ b/zerver/webhooks/teamcity/fixtures/slack_non_generic_payload.json @@ -0,0 +1,42 @@ +{ + "username":"TeamCity", + "icon_url":"https://raw.githubusercontent.com/tcplugins/tcWebHooks/master/docs/icons/teamcity-logo-48x48.png", + "attachments":[ + { + "title":"Failed (broken) : ${buildName} <${buildStatusUrl}|build #${buildNumber}>", + "fallback":"Failed (broken) : ${buildName} build #${buildNumber}", + "color":"danger", + "fields":[ + { + "title":"Status", + "value":"${buildStatus}" + }, + { + "title":"Project Name", + "value":"<${rootUrl}/project.html?projectId=${projectExternalId}|${projectName}>", + "short":true + }, + { + "title":"Build Name", + "value":"<${rootUrl}/viewType.html?buildTypeId=${buildExternalTypeId}|${buildName}>", + "short":true + }, + { + "title":"Commit", + "value":"<${buildStatusUrl}&tab=buildChangesDiv|${substr(build.vcs.number,0,7,32)}>", + "short":true + }, + { + "title":"Triggered By", + "value":"${triggeredBy}", + "short":true + }, + { + "title":"Agent", + "value":"${agentName}", + "short":true + } + ] + } + ] +} diff --git a/zerver/webhooks/teamcity/tests.py b/zerver/webhooks/teamcity/tests.py index b0d926264a..4d4e9a3d34 100644 --- a/zerver/webhooks/teamcity/tests.py +++ b/zerver/webhooks/teamcity/tests.py @@ -2,7 +2,9 @@ import ujson from zerver.lib.test_classes import WebhookTestCase -from zerver.models import Recipient +from zerver.lib.send_email import FromAddress +from zerver.models import Recipient, get_user, get_realm +from zerver.webhooks.teamcity.view import MISCONFIGURED_PAYLOAD_TYPE_ERROR_MESSAGE class TeamcityHookTests(WebhookTestCase): STREAM_NAME = 'teamcity' @@ -39,3 +41,15 @@ class TeamcityHookTests(WebhookTestCase): self.assertEqual(msg.content, expected_message) self.assertEqual(msg.recipient.type, Recipient.PERSONAL) + + def test_non_generic_payload_ignore_pm_notification(self) -> None: + expected_message = MISCONFIGURED_PAYLOAD_TYPE_ERROR_MESSAGE.format( + bot_name=get_user('webhook-bot@zulip.com', get_realm('zulip')).full_name, + support_email=FromAddress.SUPPORT + ).strip() + payload = self.get_body('slack_non_generic_payload') + self.client_post(self.url, payload, content_type="application/json") + msg = self.get_last_message() + + self.assertEqual(msg.content, expected_message) + self.assertEqual(msg.recipient.type, Recipient.PERSONAL) diff --git a/zerver/webhooks/teamcity/view.py b/zerver/webhooks/teamcity/view.py index 36523e8356..f7ecdb70fc 100644 --- a/zerver/webhooks/teamcity/view.py +++ b/zerver/webhooks/teamcity/view.py @@ -8,12 +8,23 @@ from django.db.models import Q from django.http import HttpRequest, HttpResponse from zerver.decorator import api_key_only_webhook_view -from zerver.lib.actions import check_send_private_message +from zerver.lib.actions import check_send_private_message, \ + send_rate_limited_pm_notification_to_bot_owner +from zerver.lib.send_email import FromAddress from zerver.lib.request import REQ, has_request_variables from zerver.lib.response import json_error, json_success from zerver.lib.webhooks.common import check_send_webhook_message from zerver.models import Realm, UserProfile +MISCONFIGURED_PAYLOAD_TYPE_ERROR_MESSAGE = """ +Hi there! Your bot {bot_name} just received a TeamCity payload in a +format that Zulip doesn't recognize. This usually indicates a +configuration issue in your TeamCity webhook settings. Please make sure +that you set the **Payload Format** option to **Legacy Webhook (JSON)** +in your TeamCity webhook configuration. Contact {support_email} if you +need further help! +""" + def guess_zulip_user_from_teamcity(teamcity_username: str, realm: Realm) -> Optional[UserProfile]: try: # Try to find a matching user in Zulip @@ -39,7 +50,18 @@ def get_teamcity_property_value(property_list: List[Dict[str, str]], name: str) @has_request_variables def api_teamcity_webhook(request: HttpRequest, user_profile: UserProfile, payload: Dict[str, Any]=REQ(argument_type='body')) -> HttpResponse: - message = payload['build'] + message = payload.get('build') + if message is None: + # Ignore third-party specific (e.g. Slack/HipChat) payload formats + # and notify the bot owner + message = MISCONFIGURED_PAYLOAD_TYPE_ERROR_MESSAGE.format( + bot_name=user_profile.full_name, + support_email=FromAddress.SUPPORT, + ).strip() + send_rate_limited_pm_notification_to_bot_owner( + user_profile, user_profile.realm, message) + + return json_success() build_name = message['buildFullName'] build_url = message['buildStatusUrl']