mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-04 05:53:43 +00:00 
			
		
		
		
	integrations: Update Slack webhook to use check_send_webhook_message.
Due to the channel_map_to_topics URL parameter in the Slack webhook, it was not migrated to use the check_send_webhook_message. By using check_send_webhook_message, any topic parameter in the webhook URL will be prioritized over mapping Slack channels to topics, e.g. when channel_map_to_topics is true. This is because the default behaviour for incoming webhooks is to send a default topic as a parameter to check_send_webhook_message in case there is no topic specified in the URL. In contrast, we can override the stream passed in the URL when channel_map_to_topics is false by passing the Slack channel name to check_send_webhook_message. The default behaviour for incoming webhooks is to send a direct message if there is no specified stream in the URL, so a default stream is not generally passed to check_send_webhook_message. Fixes #27601.
This commit is contained in:
		
				
					committed by
					
						
						Tim Abbott
					
				
			
			
				
	
			
			
			
						parent
						
							069680a7d1
						
					
				
				
					commit
					3a03e14938
				
			@@ -7,20 +7,20 @@ See also the [Slack-compatible webhook](/integrations/doc/slack_incoming).
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
1. {!create-an-incoming-webhook.md!}
 | 
					1. {!create-an-incoming-webhook.md!}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Construct the URL for the {{ integration_display_name }}
 | 
					1. {!generate-integration-url.md!}
 | 
				
			||||||
    bot using the bot's API key:
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    `{{ api_url }}{{ integration_url }}?api_key=abcdefgh`
 | 
					    If you'd like to map Slack channels to different topics within the same
 | 
				
			||||||
 | 
					    stream, add `&channels_map_to_topics=1` to the end of the URL. Note that
 | 
				
			||||||
    If you'd like to map Slack channels to different topics within the
 | 
					    this should be used instead of specifying a topic in the URL. If a topic
 | 
				
			||||||
    same stream, add `&channels_map_to_topics=1` to the end of the URL.
 | 
					    is specified in the URL, then it will be prioritized over the channel to
 | 
				
			||||||
    This is the default behavior of the integration.
 | 
					    topic mapping.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    If you'd like to map Slack channels to different streams, add
 | 
					    If you'd like to map Slack channels to different streams, add
 | 
				
			||||||
    `&channels_map_to_topics=0` to the end of the URL. Make sure you
 | 
					    `&channels_map_to_topics=0` to the end of the URL. Make sure you create
 | 
				
			||||||
    create streams for all your public Slack channels, and that the name
 | 
					    streams for all your public Slack channels *(see step 1)*, and that the
 | 
				
			||||||
    of each stream is the same as the name of the Slack channel it maps
 | 
					    name of each stream is the same as the name of the Slack channel it maps
 | 
				
			||||||
    to.
 | 
					    to. Note that in this case, the channel to stream mapping will be
 | 
				
			||||||
 | 
					    prioritized over the stream specified in the URL.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
1. Go to <https://my.slack.com/services/new/outgoing-webhook>
 | 
					1. Go to <https://my.slack.com/services/new/outgoing-webhook>
 | 
				
			||||||
   and click **Add Outgoing WebHooks integration**.
 | 
					   and click **Add Outgoing WebHooks integration**.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,31 +2,71 @@ from typing_extensions import override
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
from zerver.lib.test_classes import WebhookTestCase
 | 
					from zerver.lib.test_classes import WebhookTestCase
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					EXPECTED_TOPIC = "Message from Slack"
 | 
				
			||||||
 | 
					EXPECTED_MESSAGE = "**slack_user**: test"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class SlackWebhookTests(WebhookTestCase):
 | 
					class SlackWebhookTests(WebhookTestCase):
 | 
				
			||||||
    STREAM_NAME = "slack"
 | 
					    STREAM_NAME = "slack"
 | 
				
			||||||
    URL_TEMPLATE = "/api/v1/external/slack?stream={stream}&api_key={api_key}"
 | 
					    URL_TEMPLATE = "/api/v1/external/slack?stream={stream}&api_key={api_key}"
 | 
				
			||||||
    WEBHOOK_DIR_NAME = "slack"
 | 
					    WEBHOOK_DIR_NAME = "slack"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_slack_channel_to_topic(self) -> None:
 | 
					    def test_slack_only_stream_parameter(self) -> None:
 | 
				
			||||||
        expected_topic_name = "channel: general"
 | 
					 | 
				
			||||||
        expected_message = "**slack_user**: test"
 | 
					 | 
				
			||||||
        self.check_webhook(
 | 
					        self.check_webhook(
 | 
				
			||||||
            "message_info",
 | 
					            "message_info",
 | 
				
			||||||
            expected_topic_name,
 | 
					            EXPECTED_TOPIC,
 | 
				
			||||||
            expected_message,
 | 
					            EXPECTED_MESSAGE,
 | 
				
			||||||
            content_type="application/x-www-form-urlencoded",
 | 
					            content_type="application/x-www-form-urlencoded",
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_slack_channel_to_stream(self) -> None:
 | 
					    def test_slack_with_user_specified_topic(self) -> None:
 | 
				
			||||||
        self.STREAM_NAME = "general"
 | 
					        self.url = self.build_webhook_url(topic="test")
 | 
				
			||||||
        self.url = "{}{}".format(self.url, "&channels_map_to_topics=0")
 | 
					        expected_topic_name = "test"
 | 
				
			||||||
        expected_topic_name = "Message from Slack"
 | 
					 | 
				
			||||||
        expected_message = "**slack_user**: test"
 | 
					 | 
				
			||||||
        self.check_webhook(
 | 
					        self.check_webhook(
 | 
				
			||||||
            "message_info",
 | 
					            "message_info",
 | 
				
			||||||
            expected_topic_name,
 | 
					            expected_topic_name,
 | 
				
			||||||
            expected_message,
 | 
					            EXPECTED_MESSAGE,
 | 
				
			||||||
 | 
					            content_type="application/x-www-form-urlencoded",
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_slack_channels_map_to_topics_true(self) -> None:
 | 
				
			||||||
 | 
					        self.url = self.build_webhook_url(channels_map_to_topics="1")
 | 
				
			||||||
 | 
					        expected_topic_name = "channel: general"
 | 
				
			||||||
 | 
					        self.check_webhook(
 | 
				
			||||||
 | 
					            "message_info",
 | 
				
			||||||
 | 
					            expected_topic_name,
 | 
				
			||||||
 | 
					            EXPECTED_MESSAGE,
 | 
				
			||||||
 | 
					            content_type="application/x-www-form-urlencoded",
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_slack_channels_map_to_topics_true_and_user_specified_topic(self) -> None:
 | 
				
			||||||
 | 
					        self.url = self.build_webhook_url(topic="test", channels_map_to_topics="1")
 | 
				
			||||||
 | 
					        expected_topic_name = "test"
 | 
				
			||||||
 | 
					        self.check_webhook(
 | 
				
			||||||
 | 
					            "message_info",
 | 
				
			||||||
 | 
					            expected_topic_name,
 | 
				
			||||||
 | 
					            EXPECTED_MESSAGE,
 | 
				
			||||||
 | 
					            content_type="application/x-www-form-urlencoded",
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_slack_channels_map_to_topics_false(self) -> None:
 | 
				
			||||||
 | 
					        self.STREAM_NAME = "general"
 | 
				
			||||||
 | 
					        self.url = self.build_webhook_url(channels_map_to_topics="0")
 | 
				
			||||||
 | 
					        self.check_webhook(
 | 
				
			||||||
 | 
					            "message_info",
 | 
				
			||||||
 | 
					            EXPECTED_TOPIC,
 | 
				
			||||||
 | 
					            EXPECTED_MESSAGE,
 | 
				
			||||||
 | 
					            content_type="application/x-www-form-urlencoded",
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_slack_channels_map_to_topics_false_and_user_specified_topic(self) -> None:
 | 
				
			||||||
 | 
					        self.STREAM_NAME = "general"
 | 
				
			||||||
 | 
					        self.url = self.build_webhook_url(topic="test", channels_map_to_topics="0")
 | 
				
			||||||
 | 
					        expected_topic_name = "test"
 | 
				
			||||||
 | 
					        self.check_webhook(
 | 
				
			||||||
 | 
					            "message_info",
 | 
				
			||||||
 | 
					            expected_topic_name,
 | 
				
			||||||
 | 
					            EXPECTED_MESSAGE,
 | 
				
			||||||
            content_type="application/x-www-form-urlencoded",
 | 
					            content_type="application/x-www-form-urlencoded",
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -50,7 +90,7 @@ class SlackWebhookTests(WebhookTestCase):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    def test_invalid_channels_map_to_topics(self) -> None:
 | 
					    def test_invalid_channels_map_to_topics(self) -> None:
 | 
				
			||||||
        payload = self.get_body("message_info")
 | 
					        payload = self.get_body("message_info")
 | 
				
			||||||
        url = "{}{}".format(self.url, "&channels_map_to_topics=abc")
 | 
					        url = self.build_webhook_url(channels_map_to_topics="abc")
 | 
				
			||||||
        result = self.client_post(url, payload, content_type="application/x-www-form-urlencoded")
 | 
					        result = self.client_post(url, payload, content_type="application/x-www-form-urlencoded")
 | 
				
			||||||
        self.assert_json_error(result, "Error: channels_map_to_topics parameter other than 0 or 1")
 | 
					        self.assert_json_error(result, "Error: channels_map_to_topics parameter other than 0 or 1")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,13 +1,14 @@
 | 
				
			|||||||
 | 
					from typing import Optional
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from django.http import HttpRequest
 | 
					from django.http import HttpRequest
 | 
				
			||||||
from django.http.response import HttpResponse
 | 
					from django.http.response import HttpResponse
 | 
				
			||||||
from django.utils.translation import gettext as _
 | 
					from django.utils.translation import gettext as _
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from zerver.actions.message_send import check_send_stream_message
 | 
					 | 
				
			||||||
from zerver.decorator import webhook_view
 | 
					from zerver.decorator import webhook_view
 | 
				
			||||||
from zerver.lib.exceptions import JsonableError
 | 
					from zerver.lib.exceptions import JsonableError
 | 
				
			||||||
from zerver.lib.request import RequestNotes
 | 
					 | 
				
			||||||
from zerver.lib.response import json_success
 | 
					from zerver.lib.response import json_success
 | 
				
			||||||
from zerver.lib.typed_endpoint import typed_endpoint
 | 
					from zerver.lib.typed_endpoint import typed_endpoint
 | 
				
			||||||
 | 
					from zerver.lib.webhooks.common import check_send_webhook_message
 | 
				
			||||||
from zerver.models import UserProfile
 | 
					from zerver.models import UserProfile
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ZULIP_MESSAGE_TEMPLATE = "**{message_sender}**: {text}"
 | 
					ZULIP_MESSAGE_TEMPLATE = "**{message_sender}**: {text}"
 | 
				
			||||||
@@ -23,20 +24,39 @@ def api_slack_webhook(
 | 
				
			|||||||
    user_name: str,
 | 
					    user_name: str,
 | 
				
			||||||
    text: str,
 | 
					    text: str,
 | 
				
			||||||
    channel_name: str,
 | 
					    channel_name: str,
 | 
				
			||||||
    stream: str = "slack",
 | 
					    channels_map_to_topics: Optional[str] = None,
 | 
				
			||||||
    channels_map_to_topics: str = "1",
 | 
					 | 
				
			||||||
) -> HttpResponse:
 | 
					) -> HttpResponse:
 | 
				
			||||||
    if channels_map_to_topics not in VALID_OPTIONS.values():
 | 
					    content = ZULIP_MESSAGE_TEMPLATE.format(message_sender=user_name, text=text)
 | 
				
			||||||
 | 
					    topic_name = "Message from Slack"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if channels_map_to_topics is None:
 | 
				
			||||||
 | 
					        check_send_webhook_message(
 | 
				
			||||||
 | 
					            request,
 | 
				
			||||||
 | 
					            user_profile,
 | 
				
			||||||
 | 
					            topic_name,
 | 
				
			||||||
 | 
					            content,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    elif channels_map_to_topics == VALID_OPTIONS["SHOULD_BE_MAPPED"]:
 | 
				
			||||||
 | 
					        # If the webhook URL has a user_specified_topic,
 | 
				
			||||||
 | 
					        # then this topic-channel mapping will not be used.
 | 
				
			||||||
 | 
					        topic_name = f"channel: {channel_name}"
 | 
				
			||||||
 | 
					        check_send_webhook_message(
 | 
				
			||||||
 | 
					            request,
 | 
				
			||||||
 | 
					            user_profile,
 | 
				
			||||||
 | 
					            topic_name,
 | 
				
			||||||
 | 
					            content,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    elif channels_map_to_topics == VALID_OPTIONS["SHOULD_NOT_BE_MAPPED"]:
 | 
				
			||||||
 | 
					        # This stream-channel mapping will be used even if
 | 
				
			||||||
 | 
					        # there is a stream specified in the webhook URL.
 | 
				
			||||||
 | 
					        check_send_webhook_message(
 | 
				
			||||||
 | 
					            request,
 | 
				
			||||||
 | 
					            user_profile,
 | 
				
			||||||
 | 
					            topic_name,
 | 
				
			||||||
 | 
					            content,
 | 
				
			||||||
 | 
					            stream=channel_name,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
        raise JsonableError(_("Error: channels_map_to_topics parameter other than 0 or 1"))
 | 
					        raise JsonableError(_("Error: channels_map_to_topics parameter other than 0 or 1"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if channels_map_to_topics == VALID_OPTIONS["SHOULD_BE_MAPPED"]:
 | 
					 | 
				
			||||||
        topic_name = f"channel: {channel_name}"
 | 
					 | 
				
			||||||
    else:
 | 
					 | 
				
			||||||
        stream = channel_name
 | 
					 | 
				
			||||||
        topic_name = _("Message from Slack")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    content = ZULIP_MESSAGE_TEMPLATE.format(message_sender=user_name, text=text)
 | 
					 | 
				
			||||||
    client = RequestNotes.get_notes(request).client
 | 
					 | 
				
			||||||
    assert client is not None
 | 
					 | 
				
			||||||
    check_send_stream_message(user_profile, client, stream, topic_name, content)
 | 
					 | 
				
			||||||
    return json_success(request)
 | 
					    return json_success(request)
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user