Esempio n. 1
0
def handle_state(context,
                 trigger_id,
                 title,
                 elements,
                 receiver='',
                 submit_label='Submit',
                 state='n/a'):
    """Send a dialog into Slack

    Args:
        context (dict): The state context object. This is included automatically by Socless Core and SHOULD NOT be supplied by the user when creating a playbook
        receiver (str): The name of the State in the playbook that will receive the dialog response
        trigger_id (str): trigger_id required to open a Dialog
        title (str): Dialog title
        elements (list): Dialog elements
        submit_label (str): Label for Dialog's submit button
        state (str): this string simply echoes back what your app passed to dialog.open. Use it as a pointer that references sensitive data stored elsewhere.

    Note:
        - See https://api.slack.com/dialogs for more details on to create elements for a slack dialog
        - This integration starts a Human Response workflow. When used in a playbook, it needs to be followed by a Task state that uses the _socless_outbound_message_response Activity to receive the response from a user
        - A user can respond to a dialog by either submitting it or cancelling it. The response payload contains a key named `type` that can be either `dialog_submission` or `dialog_cancellation`. In your playboks,
          be sure to check what type of response a user provided before acting on it.
    """
    USE_NEW_INTERACTION = 'task_token' in context

    message_id = gen_id()

    dialog = {
        'title': title,
        'elements': elements,
        'submit_label': submit_label,
        'notify_on_cancel': True,
        'callback_id': message_id,
        'state': state
    }

    payload = {'trigger_id': trigger_id, 'dialog': dialog}

    url = "https://slack.com/api/dialog.open"
    headers = {
        'content-type': 'application/json',
        'Authorization': "Bearer {}".format(SLACK_BOT_TOKEN)
    }
    if USE_NEW_INTERACTION:
        init_human_interaction(context, payload, message_id)

    resp = requests.post(url, json=payload, headers=headers)
    json_resp = resp.json()

    if not json_resp["ok"]:
        raise Exception(json_resp['error'])

    if not USE_NEW_INTERACTION:
        investigation_id = context['artifacts']['event']['investigation_id']
        execution_id = context.get('execution_id')
        socless_dispatch_outbound_message(receiver, message_id,
                                          investigation_id, execution_id,
                                          payload)
    return {'response': json_resp, "message_id": message_id}
Esempio n. 2
0
def handle_state(
    context,
    target: str,
    target_type: str,
    message_template: str,
    receiver: str = "",
    response_desc: str = "[response]",
    as_user: bool = True,
    token: str = "",
):
    """Send a Slack Message and store the message id for the message.
    Args:
        target         : the username or slack id to send this message to
        target_type    : "slack_id" | "user" | "channel"
        token          : you can pass an alternate token via Jinja template in playbook.json (ssm, environment, etc)
    Returns:

    """
    helper = SlackHelper(token)

    if not message_template:
        raise Exception("No text was supplied to Slack message")

    USE_NEW_INTERACTION = "task_token" in context

    message_id = gen_id(6)

    context["_message_id"] = message_id
    extended_template = "{message_template}\n```{slash_command} {context[_message_id]} {response_desc}```\n".format(
        message_template=message_template,
        slash_command=SLACK_SLASH_COMMAND,
        context=context,
        response_desc=response_desc,
    )
    message = socless_template_string(extended_template, context)

    if USE_NEW_INTERACTION:
        init_human_interaction(context, message, message_id)

    resp = helper.slack_post_msg_wrapper(target,
                                         target_type,
                                         text=message,
                                         as_user=as_user)

    if not USE_NEW_INTERACTION:
        investigation_id = context["artifacts"]["event"]["investigation_id"]
        execution_id = context.get("execution_id")
        socless_dispatch_outbound_message(receiver, message_id,
                                          investigation_id, execution_id,
                                          message)

    return {
        "response": resp.data,  # type: ignore
        "message_id": message_id,
        "slack_id": resp["channel"],  # type: ignore
    }
Esempio n. 3
0
def handle_state(context,
                 target,
                 target_type,
                 message_template,
                 receiver="",
                 response_desc='[response]'):
    """
    Send a Slack Message and store the message id for the message
    """

    if not message_template:
        raise Exception("No text was supplied to Slack message")

    USE_NEW_INTERACTION = 'task_token' in context

    message_id = gen_id(6)

    context['_message_id'] = message_id
    extended_template = "{message_template}\n```{slash_command} {context[_message_id]} {response_desc}```\n".format(
        message_template=message_template,
        slash_command=SLACK_SLASH_COMMAND,
        context=context,
        response_desc=response_desc)
    message = socless_template_string(extended_template, context)
    target_id = get_channel_id(target, target_type)

    if USE_NEW_INTERACTION:
        init_human_interaction(context, message, message_id)

    r = slack_client.chat_postMessage(channel=target_id,
                                      text=message,
                                      as_user=True)

    if not r.data['ok']:
        raise Exception(
            f"Human Reponse workflow failed to initiate because slack_client failed to send message: {r.data}"
        )

    if not USE_NEW_INTERACTION:
        investigation_id = context['artifacts']['event']['investigation_id']
        execution_id = context.get('execution_id')
        socless_dispatch_outbound_message(receiver, message_id,
                                          investigation_id, execution_id,
                                          message)

    return {
        "response": r.data,
        "message_id": message_id,
        "slack_id": target_id
    }
def test_init_human_interaction_fails_on_generic_exceptions():
    # test init_human_interaction() to make it fail on exceptions other than KeyError
    bad_context = {
        'task_token':
        1.0,  #It expects a string, and a float number should make it fail
        'execution_id': gen_id(),
        'artifacts': {
            'event': {
                'id': gen_id(),
                'created_at': gen_datetimenow(),
                'data_types': {},
                'details': {
                    "some": "randon text"
                },
                'event_type': 'Test sfn',
                'event_meta': {},
                'investigation_id': gen_id(),
                'status_': 'open',
                'is_duplicate': False
            },
            'execution_id': gen_id()
        },
        'state_name': gen_id(),
        'Parameters': {}
    }
    test_message = {"greeting": "Hello, World"}

    with pytest.raises(Exception):
        message_id = init_human_interaction(bad_context, test_message)
def test_init_human_interaction_fails_on_invalid_execute_context_key():
    # test init_human_interaction() to make it fail on invalid execute context key
    # it's expected to raise an exception for KeyError
    bad_context = {'hello': 'world'}
    test_message = {"greeting": "Hello, World"}
    with pytest.raises(Exception):
        message_id = init_human_interaction(bad_context, test_message)
def test_end_human_interaction_fails_on_response_delivery_failed():
    # moto step function send_task_success is not implemented yet. Therefore, this is the last line of test that interacts with humaninteraction.py can be written
    # test end_human_interaction() fails on response_deliver_failed. Expecting it to raise an exception

    test_response = {"response": "Hello, back"}
    test_message = {"greeting": "Hello, World"}
    sfn_item_metadata = mock_sfn_db_context()
    sfn_context = sfn_item_metadata['sfn_context']
    state_handler = StateHandler(sfn_context, MockLambdaContext(),
                                 mock_integration_handler)
    message_id = init_human_interaction(state_handler.context, test_message)
    with pytest.raises(Exception, match='^response_delivery_failed'):
        end_human_interaction(message_id, test_response)
def test_end_human_interaction_fails_on_used_message_id():
    # test end_human_interaction() fails on message id that has already been used. Expecting it to raise an exception

    test_response = {"response": "Hello, back"}
    test_message = {"greeting": "Hello, World"}
    sfn_item_metadata = mock_sfn_db_context()
    sfn_context = sfn_item_metadata['sfn_context']
    state_handler = StateHandler(sfn_context, MockLambdaContext(),
                                 mock_integration_handler)
    message_id = init_human_interaction(state_handler.context, test_message)
    response_table = boto3.resource('dynamodb').Table(
        os.environ['SOCLESS_MESSAGE_RESPONSE_TABLE'])
    response_table.update_item(Key={"message_id": message_id},
                               UpdateExpression="SET fulfilled = :fulfilled",
                               ExpressionAttributeValues={":fulfilled": True})
    with pytest.raises(Exception, match='message_id_used'):
        end_human_interaction(message_id, test_response)
def test_end_human_interaction():
    # moto step function send_task_success is not implemented yet
    # test end_human_interaction normally to assert it works as expected
    test_response = {"response": "Hello, back"}
    test_message = {"greeting": "Hello, World"}
    sfn_item_metadata = mock_sfn_db_context()
    sfn_context = sfn_item_metadata['sfn_context']
    db_context = sfn_item_metadata['db_context']
    state_name = sfn_context['sfn_context']['State_Config']['Name']
    state_handler = StateHandler(sfn_context, MockLambdaContext(),
                                 mock_integration_handler)
    message_id = init_human_interaction(state_handler.context, test_message)
    with pytest.raises(Exception, match='^response_delivery_failed'):
        end_human_interaction(message_id, test_response)
    client = boto3.client('dynamodb')
    updated_db_context = client.get_item(
        TableName=os.environ['SOCLESS_RESULTS_TABLE'],
        Key={'execution_id':
             dict_to_item(sfn_item_metadata['execution_id'])})['Item']

    db_context['results']['results'][state_name] = test_response
    db_context['results']['results']['_Last_Saved_Results'] = test_response
    assert dict_to_item(db_context, convert_root=False) == updated_db_context
def test_init_human_interaction():
    # test init_human_interaction() normally to assert the item gets saved is the same as expected
    test_message = {"greeting": "Hello, World"}
    sfn_item_metadata = mock_sfn_db_context()
    sfn_context = sfn_item_metadata['sfn_context']
    state_handler = StateHandler(sfn_context, MockLambdaContext(),
                                 mock_integration_handler)
    message_id = init_human_interaction(state_handler.context, test_message)

    mock_message_response_entry = dict_to_item(
        {
            "await_token":
            sfn_context['task_token'],
            "execution_id":
            sfn_context['sfn_context']['execution_id'],
            "fulfilled":
            False,
            "investigation_id":
            sfn_context['sfn_context']['artifacts']['event']
            ['investigation_id'],
            "message":
            test_message,
            "receiver":
            sfn_context['sfn_context']['State_Config']['Name'],
        },
        convert_root=False)

    client = boto3.client('dynamodb')
    init_message_response_entry = client.get_item(
        TableName=os.environ['SOCLESS_MESSAGE_RESPONSE_TABLE'],
        Key={'message_id': dict_to_item(message_id)})['Item']
    mock_message_response_entry['datetime'] = init_message_response_entry[
        'datetime']
    mock_message_response_entry['message_id'] = dict_to_item(message_id)

    assert mock_message_response_entry == init_message_response_entry
Esempio n. 10
0
def handle_state(
    context,
    target_type: str,
    target: str,
    text: str,
    receiver: str = "",
    prompt_text: str = "",
    yes_text: str = "Yes",
    no_text: str = "No",
    as_user: bool = True,
    token: str = "",
):
    """Send a Slack Message and store the message id for the message.
    Args:
        target         : the username or slack id to send this message to
        target_type    : "slack_id" | "user" | "channel"
        token          : you can pass an alternate token via Jinja template in playbook.json (ssm, environment, etc)
    Returns:

    """
    helper = SlackHelper(token)
    USE_NEW_INTERACTION = "task_token" in context

    if not all([target_type, target, text]):
        raise Exception(
            "Incomplete inputs: target, target_type and text must be supplied")

    ATTACHMENT_YES_ACTION = {
        "name": "yes_text",
        "style": "default",
        "text": "",
        "type": "button",
        "value": "true",
    }
    ATTACHMENT_NO_ACTION = {
        "name": "no_text",
        "style": "danger",
        "text": "",
        "type": "button",
        "value": "false",
    }

    ATTACHMENT_TEMPLATE = {
        "text": "",
        "mrkdwn_in": ["text"],
        "fallback": "New message",
        "callback_id": "",
        "color": "#3AA3E3",
        "attachment_type": "default",
        "actions": [],
    }

    message_id = gen_id(6)
    context["_message_id"] = message_id
    text = socless_template_string(text, context)
    prompt_text = socless_template_string(prompt_text, context)

    ATTACHMENT_TEMPLATE["text"] = "*{}*".format(prompt_text)
    ATTACHMENT_TEMPLATE["callback_id"] = message_id
    ATTACHMENT_YES_ACTION["text"] = yes_text
    ATTACHMENT_NO_ACTION["text"] = no_text
    ATTACHMENT_TEMPLATE["actions"] = [
        ATTACHMENT_YES_ACTION, ATTACHMENT_NO_ACTION
    ]

    payload = {"text": text, "ATTACHMENT_TEMPLATE": ATTACHMENT_TEMPLATE}

    if USE_NEW_INTERACTION:
        init_human_interaction(context, payload, message_id)

    resp = helper.slack_post_msg_wrapper(
        target,
        target_type,
        text=text,
        attachments=[ATTACHMENT_TEMPLATE],
        as_user=as_user,
    )

    if not USE_NEW_INTERACTION:
        investigation_id = context["artifacts"]["event"]["investigation_id"]
        execution_id = context.get("execution_id")
        socless_dispatch_outbound_message(receiver, message_id,
                                          investigation_id, execution_id,
                                          payload)

    return {
        "response": resp.data,  # type: ignore
        "message_id": message_id,
        "slack_id": resp["channel"],  # type: ignore
    }
Esempio n. 11
0
def handle_state(context,
                 target_type,
                 target,
                 text,
                 receiver='',
                 prompt_text='',
                 yes_text='Yes',
                 no_text='No'):
    """
    Send a Slack Message and store the message id for the message
    """
    USE_NEW_INTERACTION = 'task_token' in context

    if not all([target_type, target, text]):
        raise Exception(
            "Incomplete inputs: target, target_type and text must be supplied")
    target_id = get_channel_id(target, target_type)

    ATTACHMENT_YES_ACTION = {
        "name": "yes_text",
        "style": "default",
        "text": "",
        "type": "button",
        "value": "true"
    }
    ATTACHMENT_NO_ACTION = {
        "name": "no_text",
        "style": "danger",
        "text": "",
        "type": "button",
        "value": "false"
    }

    ATTACHMENT_TEMPLATE = {
        "text": "",
        "mrkdwn_in": ["text"],
        "fallback": "New message",
        "callback_id": "",
        "color": "#3AA3E3",
        "attachment_type": "default",
        "actions": []
    }

    message_id = gen_id(6)
    context['_message_id'] = message_id
    text = socless_template_string(text, context)
    prompt_text = socless_template_string(prompt_text, context)

    ATTACHMENT_TEMPLATE['text'] = "*{}*".format(prompt_text)
    ATTACHMENT_TEMPLATE['callback_id'] = message_id
    ATTACHMENT_YES_ACTION['text'] = yes_text
    ATTACHMENT_NO_ACTION['text'] = no_text
    ATTACHMENT_TEMPLATE['actions'] = [
        ATTACHMENT_YES_ACTION, ATTACHMENT_NO_ACTION
    ]

    payload = {"text": text, "ATTACHMENT_TEMPLATE": ATTACHMENT_TEMPLATE}

    if USE_NEW_INTERACTION:
        init_human_interaction(context, payload, message_id)

    resp = slack_client.chat_postMessage(channel=target_id,
                                         text=text,
                                         attachments=[ATTACHMENT_TEMPLATE],
                                         as_user=True)

    if not resp.data['ok']:
        raise Exception(resp.data['error'])

    if not USE_NEW_INTERACTION:
        investigation_id = context['artifacts']['event']['investigation_id']
        execution_id = context.get('execution_id')
        socless_dispatch_outbound_message(receiver, message_id,
                                          investigation_id, execution_id,
                                          payload)

    return {
        'response': resp.data,
        "message_id": message_id,
        "slack_id": target_id
    }
Esempio n. 12
0
def handle_state(
    context,
    trigger_id: str,
    title: str,
    elements: list,
    receiver: str = "",
    submit_label: str = "Submit",
    state: str = "n/a",
    token: str = "",
):
    """Send a dialog into Slack

    Args:
        receiver     : The name of the State in the playbook that will receive the dialog response
        trigger_id   : trigger_id required to open a Dialog
        title        : Dialog title
        elements     : Dialog elements
        submit_label : Label for Dialog's submit button
        state        : this string simply echoes back what your app passed to dialog.open. Use it as a pointer that references sensitive data stored elsewhere.
        token        : you can pass an alternate token via Jinja template in playbook.json (ssm, environment, etc)

    Note:
        - See https://api.slack.com/dialogs for more details on to create elements for a slack dialog
        - This integration starts a Human Response workflow. When used in a playbook, it needs to be followed by a Task state that uses the _socless_outbound_message_response Activity to receive the response from a user
        - A user can respond to a dialog by either submitting it or cancelling it. The response payload contains a key named `type` that can be either `dialog_submission` or `dialog_cancellation`. In your playboks,
            be sure to check what type of response a user provided before acting on it.
    """
    if not token:
        token = SOCLESS_BOT_TOKEN

    USE_NEW_INTERACTION = "task_token" in context

    message_id = gen_id()

    dialog = {
        "title": title,
        "elements": elements,
        "submit_label": submit_label,
        "notify_on_cancel": True,
        "callback_id": message_id,
        "state": state,
    }

    payload = {"trigger_id": trigger_id, "dialog": dialog}

    url = "https://slack.com/api/dialog.open"
    headers = {
        "content-type": "application/json",
        "Authorization": "Bearer {}".format(token),
    }
    if USE_NEW_INTERACTION:
        init_human_interaction(context, payload, message_id)

    resp = requests.post(url, json=payload, headers=headers)
    json_resp = resp.json()

    if not json_resp["ok"]:
        raise Exception(json_resp["error"])

    if not USE_NEW_INTERACTION:
        investigation_id = context["artifacts"]["event"]["investigation_id"]
        execution_id = context.get("execution_id")
        socless_dispatch_outbound_message(receiver, message_id,
                                          investigation_id, execution_id,
                                          payload)
    return {"response": json_resp, "message_id": message_id}