Пример #1
0
def test_team():
    _team = Team.get_team_by_name('testteam')
    _bot = Bot.get_bot_by_name(_team, 'echobot')
    assert _bot.name == 'echobot'
    assert _bot.bot_id == 'A12345678'
    assert _bot.team == _team
    assert _bot.oauth_user_token == '1234'
    assert _bot.oauth_bot_token == '1234'
    assert _bot.verification_token == '1234'

    _team = Team.get_team_by_id(team_id='TABCDEF12')
    _bot = Bot.get_bot_by_bot_id(_team, 'A98765432')
    assert _bot.name == 'echobot'
    assert _bot.bot_id == 'A98765432'
    assert _bot.team == _team
    assert _bot.oauth_user_token == '1234'
    assert _bot.oauth_bot_token == ''
    assert _bot.verification_token == '1234'

    _team = Team.get_team_by_name('testteam')
    _bot = Bot.get_bot_by_verification_token('5555')
    assert _bot.name == 'pingbot'
    assert _bot.bot_id == 'AABCDEF12'
    assert _bot.team == _team
    assert _bot.oauth_user_token == '5555'
    assert _bot.oauth_bot_token == '5555'
    assert _bot.verification_token == '5555'

    with pytest.raises(BotInitializationError):
        _bot = Bot.get_bot_by_name(_team, 'fakebot')

    with pytest.raises(BotInitializationError):
        _bot = Bot.get_bot_by_bot_id(_team, 'BADBOTID')
Пример #2
0
def slack_event():
    """
    Handle event subscription API webhooks from slack.
    """
    event = request.json
    logger.debug('Event received in API slack_event: {}'.format(event))
    # Every event should have a validation token
    if 'token' not in event:
        msg = 'No verification token in event.'
        logger.error(msg)
        return jsonify({'status': 'failure', 'error': msg}), 403
    # url_verification events don't contain info about team_id or api_app_id,
    # annoyingly, so we need to special case this to validate the token
    # against all configured apps.
    if event.get('type') == 'url_verification':
        try:
            Bot.get_bot_by_verification_token(event['token'])
        except BotInitializationError:
            msg = 'url_verification failed.'
            logger.error(msg)
            return jsonify({'status': 'failure', 'error': msg}), 403
        return jsonify({'challenge': event['challenge']})
    api_app_id = event.get('api_app_id')
    if api_app_id is None:
        msg = 'No api_app_id in event.'
        logger.error(msg)
        return jsonify({'status': 'failure', 'error': msg}), 403
    team_id = event.get('team_id')
    if team_id is None:
        msg = 'No team_id in event.'
        logger.error(
            msg,
            extra={'bot_id': api_app_id},
        )
        return jsonify({'status': 'failure', 'error': msg}), 403
    try:
        team = Team.get_team_by_id(team_id)
    except TeamInitializationError:
        msg = 'Unsupported team'
        logger.warning(msg, extra={'team_id': team_id, 'bot_id': api_app_id})
        return jsonify({'status': 'failure', 'error': msg}), 403
    try:
        bot = Bot.get_bot_by_bot_id(team, api_app_id)
    except BotInitializationError:
        msg = 'Unsupported bot'
        logger.info(msg, extra={'team_id': team_id, 'bot_id': api_app_id})
        return jsonify({'status': 'ignored', 'warning': msg}), 200
    if event['token'] != bot.verification_token:
        msg = 'Incorrect verification token in event for bot'
        logger.error(
            msg,
            extra=bot.logging_context,
        )
        return jsonify({'status': 'failure', 'error': msg}), 403
    if 'event' not in event:
        msg = 'Request does not have an event. Processing will not proceed!'
        logger.error(
            msg,
            extra=bot.logging_context,
        )
        return jsonify({'status': 'failure', 'error': msg}), 403
    try:
        instrument_event(bot, event)
    except Exception:
        logger.exception(
            'Could not instrument request',
            extra=bot.logging_context,
        )
    try:
        queue_event(bot, event, 'event')
    except Exception:
        logger.exception(
            'Could not queue request.',
            extra=bot.logging_context,
        )
        return jsonify({'status': 'failure'}), 500
    return jsonify({'status': 'success'}), 200
Пример #3
0
def slack_interactive_component():
    # Slack sends interactive components as application/x-www-form-urlencoded,
    # json encoded inside of the payload field. What a whacky API.
    component = json.loads(request.form.to_dict().get('payload', {}))
    logger.debug(
        'component received in API slack_slash_command: {}'.format(component))
    if (component.get('type') not in [
            'interactive_message',
            'message_action',
            'dialog_submission',
            'block_actions',
    ]):
        msg = ('Unsupported type={} in interactive'
               ' component.'.format(component.get('type')))
        logger.warning(msg)
        return jsonify({'status': 'failure', 'error': msg}), 400
    # Every event should have a validation token
    if 'token' not in component:
        msg = 'No verification token in interactive component.'
        logger.warning(msg)
        return jsonify({'status': 'failure', 'error': msg}), 403
    if not component.get('team', {}).get('id'):
        msg = 'No team id in interactive component.'
        logger.warning(msg)
        return jsonify({'status': 'failure', 'error': msg}), 403
    try:
        team = Team.get_team_by_id(component['team']['id'])
    except TeamInitializationError:
        msg = 'Unsupported team'
        logger.warning(
            msg,
            extra={'team_id': component['team']['id']},
        )
        return jsonify({'status': 'failure', 'error': msg}), 403
    # interactive components annoyingly don't send an app id, so we need
    # to verify
    try:
        bot = Bot.get_bot_by_verification_token(component['token'])
    except BotInitializationError:
        msg = ('Token sent with interactive component does not match any'
               ' configured app.')
        logger.error(
            msg,
            extra=team.logging_context,
        )
        return jsonify({'status': 'failure', 'error': msg}), 403
    if team.team_id != bot.team.team_id:
        # This should never happen, but let's be paranoid.
        msg = ('Token sent with slash command does not match team in event.')
        logger.error(
            msg,
            extra=merge_logging_context(
                {'expected_team_id': team.team_id},
                bot.logging_context,
            ),
        )
        return jsonify({'status': 'failure', 'error': msg}), 403
    handler_found = None
    for handler in bot.interactive_component_handlers:
        if get_callback_id(component) == handler.get('callback_id'):
            handler_found = handler
            break
    if not handler_found:
        msg = ('This interactive component does not have any omnibot handler'
               ' associated with it.')
        logger.error(
            msg,
            extra=bot.logging_context,
        )
        return jsonify({'response_type': 'ephemeral', 'text': msg}), 200
    # To avoid needing to look the bot up from its token when the dequeue this
    # command,:let's extend the payload with the bot id
    component['omnibot_bot_id'] = bot.bot_id
    # TODO: Use action_ts to instrument event
    try:
        # If there's no callbacks defined for this interactive component, we
        # can skip enqueuing it, since the workers will just discard it.
        if handler_found.get('callbacks'):
            queue_event(bot, component, 'interactive_component')
    except Exception:
        msg = 'Could not queue interactive component.'
        logger.exception(
            msg,
            extra=bot.logging_context,
        )
        return jsonify({'status': 'failure', 'error': msg}), 500
    # Open a dialog, if we have a trigger ID, and a dialog is defined for this
    # handler. Not all interactive components have a trigger ID.
    if component.get('trigger_id') and handler_found.get('dialog'):
        _perform_action(
            bot, {
                'action': 'dialog.open',
                'kwargs': {
                    'dialog': handler_found['dialog'],
                    'trigger_id': component['trigger_id']
                }
            })
    if component['type'] in ['dialog_submission']:
        return '', 200
    elif handler_found.get('no_message_response'):
        return '', 200
    else:
        return _get_write_message_response(handler_found), 200
Пример #4
0
def slack_slash_command():
    # Slack sends slash commands as application/x-www-form-urlencoded
    command = request.form.to_dict()
    logger.debug(
        'command received in API slack_slash_command: {}'.format(command))
    # Every event should have a validation token
    if 'token' not in command:
        msg = 'No verification token in slash command.'
        logger.error(msg)
        return jsonify({'status': 'failure', 'error': msg}), 403
    if 'team_id' not in command:
        msg = 'No team_id in slash command.'
        logger.error(msg)
        return jsonify({'status': 'failure', 'error': msg}), 403
    try:
        team = Team.get_team_by_id(command['team_id'])
    except TeamInitializationError:
        msg = 'Unsupported team'
        logger.warning(
            msg,
            extra={'team_id': command['team_id']},
        )
        return jsonify({'status': 'failure', 'error': msg}), 403
    # Slash commands annoyingly don't send an app id, so we need to verify
    try:
        bot = Bot.get_bot_by_verification_token(command['token'])
    except BotInitializationError:
        msg = (
            'Token sent with slash command does not match any configured app.')
        logger.error(
            msg,
            extra=team.logging_context,
        )
        return jsonify({'status': 'failure', 'error': msg}), 403
    if team.team_id != bot.team.team_id:
        # This should never happen, but let's be paranoid.
        msg = ('Token sent with slash command does not match team in event.')
        logger.error(msg,
                     extra=merge_logging_context(
                         {'expected_team_id': team.team_id},
                         bot.logging_context,
                     ))
        return jsonify({'status': 'failure', 'error': msg}), 403
    handler_found = None
    for slash_handler in bot.slash_command_handlers:
        if command['command'] == slash_handler.get('command'):
            handler_found = slash_handler
            break
    if not handler_found:
        msg = ('This slash command does not have any omnibot handler'
               ' associated with it.')
        logger.error(
            msg,
            extra=bot.logging_context,
        )
        return jsonify({'response_type': 'ephemeral', 'text': msg}), 200
    # To avoid needing to look the bot up from its token when the dequeue this
    # command,:let's extend the payload with the bot id
    command['omnibot_bot_id'] = bot.bot_id
    # We can't instrument slash commands, because they don't have ts info.
    # TODO: investigate if we can parse the trigger ID; it's possible part
    # of that is a timestamp
    try:
        # If there's no callbacks defined for this slash command, we
        # can skip enqueuing it, since the workers will just discard it.
        if handler_found.get('callbacks'):
            queue_event(bot, command, 'slash_command')
    except Exception:
        msg = 'Could not queue slash command.'
        logger.exception(
            msg,
            extra={
                'team': team.team_id,
                'app': bot.bot_id,
                'bot': bot.name
            },
        )
        return jsonify({'status': 'failure', 'error': msg}), 500
    if handler_found.get('dialog'):
        _perform_action(
            bot, {
                'action': 'dialog.open',
                'kwargs': {
                    'dialog': handler_found['dialog'],
                    'trigger_id': command['trigger_id']
                }
            })
    return _get_write_message_response(handler_found), 200