SlackAskV2

Sends a message (question) to either user (in a direct message) or to a channel. The message includes predefined reply options. The response can also close a task (might be conditional) in a playbook. Note: a message maximum length is 3000 characters enforced by Slack API.

python · Slack

Details

IDSlackAskV2
Languagepython
From Version5.5.0
Docker Imagedemisto/python3:3.12.13.10116658
Tagsslack

README

Sends a message (question) to either user (in a direct message) or to a channel. The message includes predefined reply options. The response can also close a task (might be conditional) in a playbook.

SlackAskV2 was added to support the release of SlackV3 is only compatible with SlackV3.

Script Data


Name Description
Script Type python3
Tags slack
Demisto Version 5.5.0

Use Case


This automation allows you to ask users in Slack (including external to Cortex XSOAR) questions, have them respond and
reflect the answer back to Cortex XSOAR.

Dependencies


Requires an instance of the SlackV3 integration.

This script uses the following commands and scripts.

  • send-notification

Inputs


Argument Name Description
user The Slack user to which to send the message. Can be either an email address or a Slack user name.
channel The Slack channel to which to send the message.
message The message to send to the user or channel.
option1 The first reply option. The default is “Yes” with a green button. To change the color of the button, add the pound sign (#) followed by the name of the new color (green, red, or black). The default color is “green”. For example, “Yes#green”. Options cannot contain whitespaces.
option2 The second reply option. The default is “No” with a red button. To change the button color, add the pound sign (#) followed by the name of the new color (green, red, or black). The default color is “red”. For example, “No#red”. Options cannot contain whitespaces.
task The task to close with the reply. If empty, then no playbook tasks will be closed.
replyEntriesTag Tag to add to email reply entries.
persistent Indicates whether to use one-time entitlement or persistent entitlement.
responseType How the user should respond to the question.
additionalOptions A comma-separated list of additional options in the format of “option#color”, for example, “maybe#red”. The default color is “black”. Options cannot contain whitespaces.
reply The reply to send to the user. Use the templates {user} and {response} to incorporate these in the reply. (i.e. “Thank you {user}. You have answered {response}.”)
lifetime Time until the question expires. For example - 1 day. When it expires, a default response is sent.
defaultResponse Default response in case the question expires.
slackInstance The instance of SlackV3 this script should use.
slackVersion The version of Slack to use. SlackV3 is configured by default.

Outputs


There are no outputs for this script.

Guide


The automation is most useful in a playbook to determine the outcome of a conditional task - which will be one of the provided options.
It uses a mechanism that allows external users to respond in Cortex XSOAR (per investigation) with entitlement strings embedded within the message contents.
SlackAsk

The automation can utilize the interactive capabilities of Slack to send a form with buttons -
this requires the external endpoint for interactive responses to be available for connection (See the SlackV3 integration documentation for more information).
You can also utilize threads instead, simply by specifying the responseType argument.

To use SlackAskV2 via playbook:

  1. Add the SlackAskV2 script to a playbook as a task.
  2. In the message argument, specify the message to be sent.
  3. Configure the response options by filling out the option1 and option2 arguments (default values are ‘Yes’ and ‘No’).
  4. Either a user or a channel_id or channel_name must be specified.
  5. In the SlackAskV2 task, pass a tag value to the task argument.

All other inputs are optional.
At some point at the playbook, after running SlackAskV2, add a manual conditional task, which holds up the playbook execution until the response is received from Slack.
The condition names must match the response options you passed in to SlackAskV2.
In order to tie the conditional task back to SlackAskV2, add the same tag from the fifth step to the conditional task (under the “Details” tab of the task). The conditional task will be marked as completed when a user responds to the SlackAskV2 form.

Notes


  • When using the replyEntriesTag argument, the persistent argument must be set to True.
  • SlackAskV2 will not work when run in the playbook debugger. This is because the debugger does not generate entitlements, since they must be tied to an investigation. Entitlements are needed to track the response.
  • Whitespaces are not supported in custom options and will not work. (i.e. setting a button to I Agree)
import datetime
import json

import dateparser
import demistomock as demisto
import SlackAskV2
from CommonServerPython import entryTypes

BLOCKS = [
    {"type": "section", "text": {"type": "mrkdwn", "text": "wat up"}},
    {
        "type": "actions",
        "elements": [
            {
                "type": "button",
                "text": {"type": "plain_text", "emoji": True, "text": "yes"},
                "value": '{"entitlement": "4404dae8-2d45-46bd-85fa-64779c12abe8@22", "reply": "Thank you brother."}',
                "style": "danger",
            },
            {
                "type": "button",
                "text": {"type": "plain_text", "emoji": True, "text": "no"},
                "value": '{"entitlement": "4404dae8-2d45-46bd-85fa-64779c12abe8@22", "reply": "Thank you brother."}',
                "style": "danger",
            },
        ],
    },
]

BLOCKS_ADDITIONAL = [
    {"type": "section", "text": {"type": "mrkdwn", "text": "wat up"}},
    {
        "type": "actions",
        "elements": [
            {
                "type": "button",
                "text": {"type": "plain_text", "emoji": True, "text": "yes"},
                "value": '{"entitlement": "4404dae8-2d45-46bd-85fa-64779c12abe8@22", "reply": "Thank you brother."}',
                "style": "danger",
            },
            {
                "type": "button",
                "text": {"type": "plain_text", "emoji": True, "text": "no"},
                "value": '{"entitlement": "4404dae8-2d45-46bd-85fa-64779c12abe8@22", "reply": "Thank you brother."}',
                "style": "danger",
            },
            {
                "type": "button",
                "text": {"type": "plain_text", "emoji": True, "text": "maybe"},
                "value": '{"entitlement": "4404dae8-2d45-46bd-85fa-64779c12abe8@22", "reply": "Thank you brother."}',
            },
        ],
    },
]


def execute_command(command, args):
    if command == "addEntitlement":
        return [{"Type": entryTypes["note"], "Contents": "4404dae8-2d45-46bd-85fa-64779c12abe8"}]

    return []


def test_slack_ask_user(mocker):
    # Set
    mocker.patch.object(demisto, "executeCommand", side_effect=execute_command)
    mocker.patch.object(demisto, "investigation", return_value={"id": "22"})
    mocker.patch.object(
        demisto,
        "args",
        return_value={
            "user": "alexios",
            "message": "wat up",
            "option1": "yes#red",
            "option2": "no#red",
            "reply": "Thank you brother.",
            "lifetime": "24 hours",
            "defaultResponse": "NoResponse",
            "using-brand": "SlackV3",
            "slackInstance": "TestingInstance1",
        },
    )
    mocker.patch.object(demisto, "results")
    mocker.patch.object(dateparser, "parse", return_value=datetime.datetime(2019, 9, 26, 18, 38, 25))

    # Arrange
    SlackAskV2.main()
    call_args = demisto.executeCommand.call_args[0]

    # Assert
    assert call_args[1] == {
        "ignoreAddURL": "true",
        "using-brand": "SlackV3",
        "using": "TestingInstance1",
        "blocks": json.dumps(
            {
                "blocks": json.dumps(BLOCKS),
                "entitlement": "4404dae8-2d45-46bd-85fa-64779c12abe8@22",
                "reply": "Thank you brother.",
                "expiry": "2019-09-26 18:38:25",
                "default_response": "NoResponse",
            }
        ),
        "message": "wat up",
        "to": "alexios",
    }


def test_slack_ask_user_additional(mocker):
    # Set
    mocker.patch.object(demisto, "executeCommand", side_effect=execute_command)
    mocker.patch.object(demisto, "investigation", return_value={"id": "22"})
    mocker.patch.object(
        demisto,
        "args",
        return_value={
            "user": "alexios",
            "message": "wat up",
            "option1": "yes#red",
            "option2": "no#red",
            "additionalOptions": "maybe",
            "reply": "Thank you brother.",
            "lifetime": "24 hours",
            "defaultResponse": "NoResponse",
            "slackInstance": "TestingInstance1",
            "slackVersion": "SlackV3",
        },
    )
    mocker.patch.object(demisto, "results")
    mocker.patch.object(dateparser, "parse", return_value=datetime.datetime(2019, 9, 26, 18, 38, 25))

    # Arrange
    SlackAskV2.main()
    call_args = demisto.executeCommand.call_args[0]

    # Assert
    assert call_args[1] == {
        "ignoreAddURL": "true",
        "using-brand": "SlackV3",
        "using": "TestingInstance1",
        "blocks": json.dumps(
            {
                "blocks": json.dumps(BLOCKS_ADDITIONAL),
                "entitlement": "4404dae8-2d45-46bd-85fa-64779c12abe8@22",
                "reply": "Thank you brother.",
                "expiry": "2019-09-26 18:38:25",
                "default_response": "NoResponse",
            }
        ),
        "message": "wat up",
        "to": "alexios",
    }


def test_slack_ask_channel(mocker):
    # Set
    mocker.patch.object(demisto, "executeCommand", side_effect=execute_command)
    mocker.patch.object(demisto, "investigation", return_value={"id": "22"})
    mocker.patch.object(
        demisto,
        "args",
        return_value={
            "channel": "general",
            "message": "wat up",
            "option1": "yes#red",
            "option2": "no#red",
            "reply": "Thank you brother.",
            "lifetime": "24 hours",
            "defaultResponse": "NoResponse",
            "using-brand": "SlackV3",
        },
    )
    mocker.patch.object(demisto, "results")
    mocker.patch.object(dateparser, "parse", return_value=datetime.datetime(2019, 9, 26, 18, 38, 25))

    # Arrange
    SlackAskV2.main()
    call_args = demisto.executeCommand.call_args[0]

    # Assert
    assert call_args[1] == {
        "ignoreAddURL": "true",
        "using-brand": "SlackV3",
        "blocks": json.dumps(
            {
                "blocks": json.dumps(BLOCKS),
                "entitlement": "4404dae8-2d45-46bd-85fa-64779c12abe8@22",
                "reply": "Thank you brother.",
                "expiry": "2019-09-26 18:38:25",
                "default_response": "NoResponse",
            }
        ),
        "message": "wat up",
        "channel": "general",
    }


def test_slack_ask_user_threads(mocker):
    # Set
    mocker.patch.object(demisto, "executeCommand", side_effect=execute_command)
    mocker.patch.object(demisto, "investigation", return_value={"id": "22"})
    mocker.patch.object(
        demisto,
        "args",
        return_value={
            "user": "alexios",
            "message": "wat up",
            "responseType": "thread",
            "option1": "yes#red",
            "option2": "no#red",
            "reply": "Thank you brother.",
            "lifetime": "24 hours",
            "defaultResponse": "NoResponse",
            "using-brand": "SlackV3",
        },
    )
    mocker.patch.object(demisto, "results")
    mocker.patch.object(dateparser, "parse", return_value=datetime.datetime(2019, 9, 26, 18, 38, 25))

    # Arrange
    SlackAskV2.main()
    call_args = demisto.executeCommand.call_args[0]

    # Assert
    assert call_args[1] == {
        "message": json.dumps(
            {
                "message": "wat up - Please reply to this thread with `yes` or `no`.",
                "entitlement": "4404dae8-2d45-46bd-85fa-64779c12abe8@22",
                "reply": "Thank you brother.",
                "expiry": "2019-09-26 18:38:25",
                "default_response": "NoResponse",
            }
        ),
        "ignoreAddURL": "true",
        "using-brand": "SlackV3",
        "to": "alexios",
    }


def test_slack_ask_user_threads_additional(mocker):
    # Set
    mocker.patch.object(demisto, "executeCommand", side_effect=execute_command)
    mocker.patch.object(demisto, "investigation", return_value={"id": "22"})
    mocker.patch.object(
        demisto,
        "args",
        return_value={
            "user": "alexios",
            "message": "wat up",
            "option1": "yes#red",
            "option2": "no#red",
            "additionalOptions": "maybe",
            "responseType": "thread",
            "reply": "Thank you brother.",
            "lifetime": "24 hours",
            "defaultResponse": "NoResponse",
            "using-brand": "SlackV3",
        },
    )
    mocker.patch.object(demisto, "results")
    mocker.patch.object(dateparser, "parse", return_value=datetime.datetime(2019, 9, 26, 18, 38, 25))

    # Arrange
    SlackAskV2.main()
    call_args = demisto.executeCommand.call_args[0]

    # Assert
    assert call_args[1] == {
        "message": json.dumps(
            {
                "message": "wat up - Please reply to this thread with `yes` or `no` or `maybe`.",
                "entitlement": "4404dae8-2d45-46bd-85fa-64779c12abe8@22",
                "reply": "Thank you brother.",
                "expiry": "2019-09-26 18:38:25",
                "default_response": "NoResponse",
            }
        ),
        "ignoreAddURL": "true",
        "using-brand": "SlackV3",
        "to": "alexios",
    }


def test_slack_ask_channel_threads(mocker):
    # Set
    mocker.patch.object(demisto, "executeCommand", side_effect=execute_command)
    mocker.patch.object(demisto, "investigation", return_value={"id": "22"})
    mocker.patch.object(
        demisto,
        "args",
        return_value={
            "channel": "general",
            "message": "wat up",
            "responseType": "thread",
            "option1": "yes#red",
            "option2": "no#red",
            "reply": "Thank you brother.",
            "lifetime": "24 hours",
            "defaultResponse": "NoResponse",
            "using-brand": "SlackV3",
        },
    )
    mocker.patch.object(demisto, "results")
    mocker.patch.object(dateparser, "parse", return_value=datetime.datetime(2019, 9, 26, 18, 38, 25))

    # Arrange
    SlackAskV2.main()
    call_args = demisto.executeCommand.call_args[0]

    # Assert
    assert call_args[1] == {
        "message": json.dumps(
            {
                "message": "wat up - Please reply to this thread with `yes` or `no`.",
                "entitlement": "4404dae8-2d45-46bd-85fa-64779c12abe8@22",
                "reply": "Thank you brother.",
                "expiry": "2019-09-26 18:38:25",
                "default_response": "NoResponse",
            }
        ),
        "ignoreAddURL": "true",
        "using-brand": "SlackV3",
        "channel": "general",
    }