Exemplo n.º 1
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)
Exemplo n.º 2
0
 def __init__(self, bot, component, event_trace):
     self._event_trace = event_trace
     self._payload = {}
     self._payload['omnibot_payload_type'] = 'interactive_component'
     self._bot = bot
     # The bot object has data we don't want to pass to downstreams, so
     # in the payload, we just store specific bot data.
     self._payload['bot'] = {'name': bot.name, 'bot_id': bot.bot_id}
     # For future safety sake, we'll do the same for the team.
     self._payload['team'] = {
         'name': bot.team.name,
         'team_id': bot.team.team_id
     }
     self._payload['type'] = component['type']
     self._payload['callback_id'] = get_callback_id(component)
     self._payload['action_ts'] = component.get('action_ts')
     self._payload['message_ts'] = component.get('message_ts')
     self._payload['trigger_id'] = component.get('trigger_id')
     self._payload['response_url'] = component['response_url']
     self._payload['original_message'] = component.get('original_message')
     self._payload['state'] = component.get('state')
     self._payload['user'] = component.get('user')
     if self.user:
         self._payload['parsed_user'] = slack.get_user(
             self.bot, self.user['id'])
     self._payload['channel'] = component.get('channel')
     if self.channel:
         self._event_trace['channel_id'] = self.channel['id']
         self._payload['parsed_channel'] = slack.get_channel(
             self.bot, self.channel['id'])
     self._payload['message'] = component.get('message')
     if self.message:
         self._parse_message()
     self._payload['submission'] = component.get('submission')
     self._payload['actions'] = component.get('actions')
Exemplo n.º 3
0
Arquivo: api.py Projeto: 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