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_end_human_interaction_fails_on_update_item_exception():
    # moved blocks of code that can't be tested automatically to here
    # test end_human_interaction() to fail on update item. 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']
    message_id = gen_id()
    date_time = gen_datetimenow()
    response_table = boto3.resource('dynamodb').Table(
        os.environ['SOCLESS_MESSAGE_RESPONSE_TABLE'])
    response_table.put_item(
        Item={
            "message_id": message_id,
            "datetime": date_time,
            "investigation_id": sfn_item_metadata['investigation_id'],
            "message": test_message,
            "fulfilled": False,
            "execution_id": sfn_item_metadata['execution_id'],
            "receiver": sfn_context['sfn_context']['State_Config']['Name'],
            "await_token": sfn_item_metadata['task_token']
        })
    try:
        response_table.update_item(
            Key={"message_id": message_id},
            UpdateExpression=
            "SET fulfilled = :fulfilled, response_payload = :response_payload",
            ExpressionAttributeValues={
                ":fulfilled": True,
                ":response_payload": 1.0
            })
    except Exception as e:
        triggered_exception = True
    assert triggered_exception == True
Example #3
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}
Example #4
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
    }
Example #5
0
def mock_execution_results_table_entry():
    # setup db context for execution results table entry

    random_id = gen_id()
    execution_id = gen_id()
    investigation_id = gen_id()
    date_time = gen_datetimenow()
    context = {
        "datetime": date_time,
        "execution_id": execution_id,
        "investigation_id": investigation_id,
        "results": {
            'artifacts': {
                'event': {
                    'id': random_id,
                    'created_at': date_time,
                    'data_types': {},
                    'details': {
                        "some": "randon text"
                    },
                    'event_type': 'Test integrations',
                    'event_meta': {},
                    'investigation_id': investigation_id,
                    'status_': 'open',
                    'is_duplicate': False
                },
                'execution_id': execution_id
            },
            "errors": {},
            "results": {}
        }
    }
    results_table_name = os.environ['SOCLESS_RESULTS_TABLE']
    client = boto3.client('dynamodb')
    client.put_item(TableName=results_table_name,
                    Item=dict_to_item(context, convert_root=False))
    return {
        'id': random_id,
        "execution_id": execution_id,
        "investigation_id": investigation_id,
        "datetime": date_time,
        'context': context
    }
def test_end_human_interaction_fails_on_execution_results_not_found():
    # test end_human_interaction() fails on execution_results doesn't exist in the saved item. 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']
    message_id = gen_id()
    response_table = boto3.resource('dynamodb').Table(
        os.environ['SOCLESS_MESSAGE_RESPONSE_TABLE'])
    response_table.put_item(
        Item={
            "message_id": message_id,
            "datetime": gen_datetimenow(),
            "investigation_id": sfn_item_metadata['investigation_id'],
            "message": test_message,
            "fulfilled": False,
            "execution_id": gen_id(),
            "receiver": sfn_context['sfn_context']['State_Config']['Name'],
            "await_token": sfn_item_metadata['task_token']
        })
    with pytest.raises(Exception, match='execution_results_not_found'):
        end_human_interaction(message_id, test_response)
Example #7
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
    }
Example #8
0
def mock_sfn_db_context():
    # setup db context for step function

    item_metadata = mock_execution_results_table_entry()
    task_token = gen_id()
    sfn_context = {
        'task_token': task_token,
        'sfn_context': {
            'execution_id': item_metadata['execution_id'],
            'artifacts': {
                'event': {
                    'id': item_metadata['id'],
                    'created_at': item_metadata['datetime'],
                    'data_types': {},
                    'details': {
                        "some": "randon text"
                    },
                    'event_type': 'Test sfn',
                    'event_meta': {},
                    'investigation_id': item_metadata['investigation_id'],
                    'status_': 'open',
                    'is_duplicate': False
                },
                'execution_id': item_metadata['execution_id']
            },
            'State_Config': {
                'Name': "Test state name",
                'Parameters': {}
            }
        }
    }
    return {
        'id': item_metadata['id'],
        'task_token': task_token,
        "execution_id": item_metadata['execution_id'],
        "investigation_id": item_metadata['investigation_id'],
        "datetime": item_metadata['datetime'],
        'sfn_context': sfn_context,
        'db_context': item_metadata['context']
    }
def test_end_human_interaction_fails_on_nonexistent_message_id():
    # test end_human_interaction() on message id that doesn't exist. Expecting it to raise an exception
    test_response = {"response": "Hello, back"}
    with pytest.raises(Exception, match='message_id_not_found'):
        end_human_interaction(gen_id(), test_response)
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
    }
Example #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
    }
Example #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}
Example #13
0
def test_gen_id():
    """Testing the gen_id util"""
    response = gen_id(8)
    assert len(response) == 8
    assert type(response) == str
def test_ExecutionContext_fetch_context_fails_on_execution_id_not_existing():
    # test ExecutionContext fetch_context to fail when execution_id doesn't exist in dynamodb. Expecing it to raise an exception
    execution = ExecutionContext(gen_id())
    with pytest.raises(Exception, match="^Error: Unable to get execution_id"):
        execution.fetch_context()
def test_ExecutionContext_init():
    # test ExecutionContext init to assert the execution_id is the same one as expected
    execution_id = gen_id()
    execution_context = ExecutionContext(execution_id)
    assert execution_context.execution_id == execution_id