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')
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
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
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