Ejemplo n.º 1
0
def test_team():
    _team = Team.get_team_by_name('testteam')
    assert _team.name == 'testteam'
    assert _team.team_id == 'T12345678'

    _team = Team.get_team_by_id('TABCDEF12')
    assert _team.name == 'test2ndteam'
    assert _team.team_id == 'TABCDEF12'

    with pytest.raises(TeamInitializationError):
        _team = Team.get_team_by_name('faketeam')

    with pytest.raises(TeamInitializationError):
        _team = Team.get_team_by_id(team_id='BADTEAMID')
Ejemplo n.º 2
0
def process_slash_command(command):
    """
    Dispatcher for slack slash commands.
    """
    statsd = stats.get_statsd_client()
    team = Team.get_team_by_id(command['team_id'])
    bot = Bot.get_bot_by_bot_id(team, command['omnibot_bot_id'])
    if command['command'].startswith('/'):
        command_name = command['command'][1:]
    else:
        command_name = command['command']
    event_trace = merge_logging_context(
        {
            'trigger_id': command['trigger_id'],
            'command': command_name,
        },
        bot.logging_context,
    )
    statsd.incr('slash_command.process.attempt.{}'.format(command_name))
    try:
        with statsd.timer('process_slash_command'):
            logger.debug('Processing slash_command: {}'.format(
                json.dumps(command, indent=2)),
                         extra=event_trace)
            slash_command = SlashCommand(bot, command, event_trace)
            _process_slash_command_handlers(slash_command)
    except Exception:
        statsd.incr('slash_command.process.failed.{}'.format(command_name))
        logger.exception('Could not process slash command.',
                         exc_info=True,
                         extra=event_trace)
Ejemplo n.º 3
0
def process_interactive_component(component):
    """
    Dispatcher for slack interactive components
    """
    statsd = stats.get_statsd_client()
    team = Team.get_team_by_id(component['team']['id'])
    bot = Bot.get_bot_by_bot_id(team, component['omnibot_bot_id'])
    event_trace = merge_logging_context(
        {
            'callback_id': get_callback_id(component),
            'component_type': component['type'],
        },
        bot.logging_context,
    )
    statsd.incr('interactive_component.process.attempt.{}'.format(
        get_callback_id(component)))
    try:
        with statsd.timer('process_interactive_component'):
            logger.debug('Processing interactive component: {}'.format(
                json.dumps(component, indent=2)),
                         extra=event_trace)
            interactive_component = InteractiveComponent(
                bot, component, event_trace)
            _process_interactive_component(interactive_component)
    except Exception:
        statsd.incr('interactive_component.process.failed.{}'.format(
            get_callback_id(component)))
        logger.exception('Could not process interactive component.',
                         exc_info=True,
                         extra=event_trace)
Ejemplo n.º 4
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')
Ejemplo n.º 5
0
def process_event(event):
    """
    Dispatcher for slack api events.
    """
    statsd = stats.get_statsd_client()
    team = Team.get_team_by_id(event['team_id'])
    bot = Bot.get_bot_by_bot_id(team, event['api_app_id'])
    event_info = event['event']
    event_type = event_info['type']
    event_trace = {
        'event_ts': event_info['event_ts'],
        'event_type': event_type,
        'app_id': event['api_app_id'],
        'team_id': bot.team.team_id,
        'bot_receiver': bot.name
    }
    statsd.incr('event.process.attempt.{}'.format(event_type))
    if event_type == 'message' or event_type == 'app_mention':
        try:
            with statsd.timer('process_event'):
                logger.debug(
                    'Processing message: {}'.format(
                        json.dumps(event, indent=2)
                    ),
                    extra=event_trace
                )
                try:
                    message = Message(bot, event_info, event_trace)
                    _process_message_handlers(message)
                except MessageUnsupportedError:
                    pass
        except Exception:
            statsd.incr('event.process.failed.{}'.format(event_type))
            logger.exception(
                'Could not process message.',
                exc_info=True,
                extra=event_trace
            )
    else:
        logger.debug(
            'Event is not a message type.',
            extra=event_trace
        )
        logger.debug(event)
Ejemplo n.º 6
0
Archivo: api.py Proyecto: lyft/omnibot
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
Ejemplo n.º 7
0
Archivo: api.py Proyecto: lyft/omnibot
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
Ejemplo n.º 8
0
Archivo: api.py Proyecto: lyft/omnibot
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
Ejemplo n.º 9
0
def get_test_bot() -> BotMatcher:
    return BotMatcher(
        Bot.get_bot_by_bot_id(Team.get_team_by_id("TEST_TEAM_ID"),
                              "TEST_OMNIBOT_ID"))