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 watch_channels(): try: redis_client = omniredis.get_redis_client(decode_responses=False) last_run_key = "watch:channels:last_run_datetime" if not _is_allowed_to_run(redis_client, last_run_key): return statsd = stats.get_statsd_client() with redis_lock.Lock(redis_client, 'watch_channels', expire=LOCK_EXPIRATION, auto_renewal=True): with statsd.timer('watch.channels'): for team_name, bot_name in settings.PRIMARY_SLACK_BOT.items(): logger.info( 'Updating slack channel list.', extra={ 'team': team_name, 'bot': bot_name }, ) team = Team.get_team_by_name(team_name) bot = Bot.get_bot_by_name(team, bot_name) slack.update_channels(bot) redis_client.set(last_run_key, datetime.now().isoformat()) except Exception: logger.exception('Failed to update slack channel list.', exc_info=True) finally: return gevent.spawn_later(settings.WATCHER_SPAWN_WAIT_TIME_IN_SEC, watch_channels)
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)
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)
def get_bot_ims(team_name, bot_name): """ Returns list of IMs (DMs with a bot), for the provided `bot_name` and `team_name`. .. :quickref: Get a list of IMs for a bot within a team **Example request**: .. sourcecode:: http GET /api/v1/slack/get_ims/myteam/mybot **Example response**: .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json {"ims": [ { "id": 'D1234567', "created": 1518129625, "is_im": true, "is_org_shared": true, "user": "******" "is_user_deleted": false, "priority": 0.01234567 } ] } :param team_name: The team to search for this user, as configured in omnibot. :type team_name: str :param bot_name: The bot to use for the request, as configured in omnibot. :type bot_name: str """ try: team = Team.get_team_by_name(team_name) except TeamInitializationError: return jsonify({'error': 'provided team name was not found.'}), 404 try: bot = Bot.get_bot_by_name(team, bot_name) except BotInitializationError: return jsonify({'error': 'provided bot name was not found.'}), 404 raw_ims = slack.get_ims(bot) ims = [] for im in raw_ims: # each im is a tuple where im[0] is the channel id and im[1] is the im object ims.append(json.loads(im[1])) return jsonify({'ims': ims})
def decorated(*args, **kwargs): bot_name = request.view_args.get('bot_name') team_name = request.view_args.get('team_name') try: team = Team.get_team_by_name(team_name) except TeamInitializationError: logger.warning('Failed to validate bot', extra={ 'bot': bot_name, 'team': team_name, }) return abort(404) try: Bot.get_bot_by_name(team, bot_name) except BotInitializationError: logger.warning('Failed to validate bot', extra={ 'bot': bot_name, 'team': team_name, }) return abort(404) return f(*args, **kwargs)
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)
def test_interactive_block_component(mocker): _team = Team.get_team_by_name('testteam') _bot = Bot.get_bot_by_name(_team, 'echobot') component = { 'type': 'block_actions', 'response_url': 'https://hooks.slack.com/app/T999999/375455994771/iMW9hNKFI739hGOw9FCXMlf4', # noqa:E501 'actions': [{ 'block_id': 'echobot_action_test', 'action_ts': '1561559117.130541' }] } event_trace = { 'callback_id': 'echobot_action_test', 'app_id': _bot.bot_id, 'team_id': _bot.team.team_id, 'bot_receiver': _bot.name, 'component_type': 'message_action' } _component = InteractiveComponent(_bot, component, event_trace) assert _component.callback_id == 'echobot_action_test'
def help_callback(container): """ Callback for omnibot help info. """ payload = container.payload logger.debug('Help callback text: {}'.format(payload['text'])) logger.debug('Help callback payload: {}'.format( json.dumps(payload, indent=2))) ret_action = {'action': 'chat.postMessage', 'kwargs': {'attachments': []}} ret = {'actions': [ret_action]} command_fields = [] regex_fields = [] team = Team.get_team_by_name(payload['team']['name']) bot = Bot.get_bot_by_name(team, payload['bot']['name']) for handler in bot.message_handlers: if handler['match_type'] == 'command': command_fields.append({ 'title': handler['match'], 'value': handler.get('description', ''), 'short': False }) if handler['match_type'] == 'regex': regex_fields.append({ 'title': handler['match'], 'value': handler.get('description', ''), 'short': False }) if command_fields: ret_action['kwargs']['attachments'].append({ 'title': 'Commands:', 'fields': command_fields }) if regex_fields: ret_action['kwargs']['attachments'].append({ 'title': 'Regex matches:', 'fields': regex_fields }) return ret
def test_interactive_component(mocker): _team = Team.get_team_by_name('testteam') _bot = Bot.get_bot_by_name(_team, 'echobot') component = { 'type': 'message_action', 'callback_id': 'echobot_action_test', 'action_ts': '1234567.12', 'trigger_id': '376604117319.165116859648.515402022613c2893a80d6268c463e54', # noqa:E501 'response_url': 'https://hooks.slack.com/app/T999999/375455994771/iMW9hNKFI739hGOw9FCXMlf4', # noqa:E501 'user': { 'id': 'A12345678', 'name': 'echobot' }, 'team': { 'id': 'T999999', 'domain': 'omnibot-test-domain' }, 'channel': { 'id': 'C123456AB', 'name': 'channel-channel' }, 'message': { 'type': 'message', 'user': '******', 'text': '<@A12345678> echo I am <!here|here> in <#C123456AB|channel-channel>. See: <http://example.com> :simple_smile:', # noqa:E501 'ts': '1230000.00' } } event_trace = { 'callback_id': 'echobot_action_test', 'app_id': _bot.bot_id, 'team_id': _bot.team.team_id, 'bot_receiver': _bot.name, 'component_type': 'message_action' } get_user_mock = mocker.patch('omnibot.services.slack.get_user') user_ret = {'A12345678': {}} get_user_mock.return_value = user_ret get_channel_mock = mocker.patch('omnibot.services.slack.get_channel') channel_ret = {'C123456AB': {}} get_channel_mock.return_value = channel_ret extract_users_mock = mocker.patch( 'omnibot.services.slack.parser.extract_users') users_ret = {'<@A12345678>': 'echobot'} extract_users_mock.return_value = users_ret replace_users_mock = mocker.patch( 'omnibot.services.slack.parser.replace_users') replace_users_mock.return_value = '@echobot echo I am <!here|here> in <#C123456AB|channel-channel>. See: <http://example.com> :simple_smile:' # noqa:E501 extract_channels_mock = mocker.patch( 'omnibot.services.slack.parser.extract_channels') channels_ret = {'<#C123456AB|channel-channel>': 'channel-channel'} extract_channels_mock.return_value = channels_ret replace_channels_mock = mocker.patch( 'omnibot.services.slack.parser.replace_channels') replace_channels_mock.return_value = '@echobot echo I am <!here|here> in #channel-channel. See: <http://example.com> :simple_smile:' # noqa:E501 extract_subteams_mock = mocker.patch( 'omnibot.services.slack.parser.extract_subteams') extract_subteams_mock.return_value = {} extract_specials_mock = mocker.patch( 'omnibot.services.slack.parser.extract_specials') special_ret = {'<!here|here>': '@here'} extract_specials_mock.return_value = special_ret replace_specials_mock = mocker.patch( 'omnibot.services.slack.parser.replace_specials') replace_specials_mock.return_value = '@echobot echo I am @here in #channel-channel. See: <http://example.com> :simple_smile:' # noqa:E501 extract_emojis_mock = mocker.patch( 'omnibot.services.slack.parser.extract_emojis') emoji_ret = {':simple-smile': 'simple_smile'} extract_emojis_mock.return_value = emoji_ret extract_emails_mock = mocker.patch( 'omnibot.services.slack.parser.extract_emails') extract_emails_mock.return_value = {} replace_emails_mock = mocker.patch( 'omnibot.services.slack.parser.replace_emails') replace_emails_mock.return_value = '@echobot echo I am @here in #channel-channel. See: <http://example.com> :simple_smile:' # noqa:E501 extract_urls_mock = mocker.patch( 'omnibot.services.slack.parser.extract_urls') url_ret = {'<http://example.com>': 'http://example.com'} extract_urls_mock.return_value = url_ret replace_urls_mock = mocker.patch( 'omnibot.services.slack.parser.replace_urls') replace_urls_mock.return_value = '@echobot echo I am @here in #channel-channel. See: http://example.com :simple_smile:' # noqa:E501 extract_mentions_mock = mocker.patch( 'omnibot.services.slack.parser.extract_mentions') extract_mentions_mock.return_value = True extract_command_mock = mocker.patch( 'omnibot.services.slack.parser.extract_command') extract_command_mock.return_value = 'echo I am @here in #channel-channel. See: http://example.com :simple_smile:' # noqa:E501 _component = InteractiveComponent(_bot, component, event_trace) assert _component.event_trace == event_trace assert _component.bot == _bot assert _component.component_type == 'message_action' assert _component.callback_id == 'echobot_action_test' assert _component.action_ts == '1234567.12' assert _component.trigger_id == '376604117319.165116859648.515402022613c2893a80d6268c463e54' # noqa:E501 assert _component.response_url == 'https://hooks.slack.com/app/T999999/375455994771/iMW9hNKFI739hGOw9FCXMlf4' # noqa:E501 assert _component.submission is None assert _component.channel['id'] == 'C123456AB' assert _component.parsed_channel == channel_ret assert _component.user == component['user'] assert _component.team == { 'name': _bot.team.name, 'team_id': _bot.team.team_id } assert _component.message['parsed_user'] == user_ret assert _component.message['users'] == users_ret assert _component.message[ 'parsed_text'] == '@echobot echo I am @here in #channel-channel. See: http://example.com :simple_smile:' # noqa:E501 assert _component.message.get('bot_id') is None assert _component.message['channels'] == channels_ret assert _component.message['specials'] == special_ret assert _component.message['emails'] == {} assert _component.message['urls'] == url_ret
def send_bot_im(team_name, bot_name, email): """ Sends a message as a bot user to an IM (direct message) channel between a team member and a bot user, for the provided `team_name`, `bot_name`, and `email`. .. :quickref: Send an IM message between bot and user **Example request**: .. sourcecode:: http GET /api/v1/slack/send_im/myteam/mybot/[email protected] **Example response**: .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json { "channel": "DC1234567", "message": { "bot_id": "BC1234567", "subtype": "bot_message", "text": "HI!", "ts": "1538593287.000100", "type": "message", "username": "******" }, "ok": true, "ts": "1538593287.000100" } :param team_name: The team to search for the given bot, as configured in omnibot. :type team_name: str :param bot_name: The bot sending the IM to the user, as configured in omnibot. :type bot_name: str :param email: The email address of user to send message to :type email: str :resheader Content-Type: application/json :statuscode 200: success :statuscode 400: slack call returned a non-OK status :statuscode 404: team, bot, IM unable to be found, or user deleted from slack team """ data = request.json try: team = Team.get_team_by_name(team_name) bot = Bot.get_bot_by_name(team, bot_name) except TeamInitializationError: return jsonify({ 'error': 'provided team name was not found.', 'team_name': team_name, 'bot_name': bot_name, 'email': email }), 404 except BotInitializationError: return jsonify({ 'error': 'provided bot name was not found.', 'team_name': team_name, 'bot_name': bot_name, 'email': email }), 404 user = slack.get_user_by_email(bot, email) if not user: return jsonify({ 'error': 'unable to find slack user for given email.', 'team_name': team_name, 'bot_name': bot_name, 'email': email }), 404 im_id = slack.get_im_channel_id(bot, user['id']) if im_id is None: return jsonify({ 'error': 'unable to find IM channel.', 'team_name': team_name, 'bot_name': bot_name, 'email': email }), 404 data['kwargs']['channel'] = im_id ret = _perform_action(bot, data) if ret['ok']: return jsonify(ret), 200 else: return jsonify(ret), 400
def get_test_bot() -> BotMatcher: return BotMatcher( Bot.get_bot_by_bot_id(Team.get_team_by_id("TEST_TEAM_ID"), "TEST_OMNIBOT_ID"))
def slack_action_v2(team_name, bot_name): """ Perform an action against slack, as the provided `bot_name` in the provided `team_name`. .. :quickref: SlackAction; Perform a slack action in a specified team as a specified bot. **Example request**: .. sourcecode:: http POST /api/v1/slack/action/myteam/mybot HTTP/1.1 Content-Type: application/json { "action": "chat.postMessage", "kwargs": { "channel": "test-omnibot", "text": "@example see #general and use @here", "as_user": true, "omnibot_parse": { "text": ["channels", "users", "specials"] } } } :<json string action: slack api action to perform. example: `chat.postMessage` :<json dict kwargs: keyword arguments you'd pass into the associated slack api action. example: `"text": "@example see #general and use @here"` :<json dict omnibot_parse: The keyword argument you'd like omnibot to parse, with a list of things to parse. example: {"text": ["channels", "users", "specials"]} **Example response**: .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json { "channel": "C12345", "message": { "bot_id": "B456123", "subtype": "bot_message", "text": "<@UABC123|example> see <#C12345|general> and use <!here|here>", "ts": "1523557397.000335", "type": "message", "username": "******" }, "ok": true, "ts": "1523557397.000335" } :param team_name: The team to perform this action against, as configured in omnibot. :type team_name: str :param bot_name: The bot to use for the request, as configured in omnibot. :type bot_name: str :reqheader x-envoy-internal: Header that indicates whether or not this request is coming from an internal service or not. This is auto-set by envoy and doesn't need to be explicitly set. :resheader Content-Type: application/json :statuscode 200: success :statuscode 400: slack call returned a non-OK status """ data = request.json try: team = Team.get_team_by_name(team_name) except TeamInitializationError: return jsonify({'error': 'provided team name was not found.'}), 404 try: bot = Bot.get_bot_by_name(team, bot_name) except BotInitializationError: return jsonify({'error': 'provided bot name was not found.'}), 404 ret = _perform_action(bot, data) if ret['ok']: return jsonify(ret), 200 else: return jsonify(ret), 400
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 get_channel_by_name(team_name, bot_name, channel_name): """ Returns a channel object from slack, for the provided `channel_name` in the `team_name` using the specified `bot_name`. .. :quickref: Channel; Get a channel from a team, via the channel_name **Example request**: .. sourcecode:: http GET /api/v1/slack/get_channel/myteam/mybot/general HTTP/1.1 **Example response**: .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json { "channel": { "id": "C4VQ6NUNN", "name": "general", "is_channel": true, "created": 1491515285, "creator": "U4WF56QGP", "is_archived": false, "is_general": true, "unlinked": 0, "name_normalized": "general", "is_shared": false, "is_org_shared": false, "is_member": false, "is_private": false, "is_mpim": false, "members": [ "U4WF56QGP", "U6HQQ19EC", "U6J3LTKSQ", "U6J4EGP44", "U6JDF1JBU", "U6JEGTFDZ", "U6JERPMJ7", "U6JG691MJ", "U6JGEQ0J0", "U6SAVUK44", "U750C7B37", "U7DH0H802" ], "topic": { "value": "test123", "creator": "U6J3LTKSQ", "last_set": 1507156612 }, "purpose": { "value": "This channel is for team-wide communication.", "creator": "", "last_set": 0 }, "previous_names": [], "num_members": 9 } } :param team_name: The team to search for this channel, as configured in omnibot. :type team_name: str :param bot_name: The bot to use for the request, as configured in omnibot. :type bot_name: str :param channel_name: The name of the channel to get. :type channel_name: str :reqheader x-envoy-internal: Header that indicates whether or not this request is coming from an internal service or not. This is auto-set by envoy and doesn't need to be explicitly set. :resheader Content-Type: application/json :statuscode 200: success :statuscode 404: channel with specified channel_name could not be found in the specified team using the specified bot. """ logger.debug( 'Getting channel for team={} bot={} channel={}.', extra={ 'team': team_name, 'bot': bot_name, 'channel': channel_name, }, ) try: team = Team.get_team_by_name(team_name) except TeamInitializationError: return jsonify({'error': 'provided team name was not found.'}), 404 try: bot = Bot.get_bot_by_name(team, bot_name) except BotInitializationError: return jsonify({'error': 'provided bot name was not found.'}), 404 channel = slack.get_channel_by_name(bot, channel_name) if channel is None: logger.debug( 'Failed to get channel', extra=merge_logging_context( {'channel': channel_name}, bot.logging_context, ), ) return jsonify({'error': 'provided channel_name was not found.'}), 404 return jsonify(channel)
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
def get_user_v2(team_name, bot_name, email): """ Returns basic user information, for the provided `email` in the `team_name` using the specified `bot_name`. .. :quickref: User; Get a user from a team, via their email **Example request**: .. sourcecode:: http GET /api/v1/slack/get_user/myteam/mybot/[email protected] HTTP/1.1 **Example response**: .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json {"email": "*****@*****.**", "name": "Test User", "team_id": "T123456", "user_id": "U123ABC"} :param team_name: The team to search for this user, as configured in omnibot. :type team_name: str :param bot_name: The bot to use for the request, as configured in omnibot. :type bot_name: str :param email: The email address of the user to get. :type email: str :reqheader x-envoy-internal: Header that indicates whether or not this request is coming from an internal service or not. This is auto-set by envoy and doesn't need to be explicitly set. :resheader Content-Type: application/json :statuscode 200: success :statuscode 404: user with specified email could not be found using the specified bot. """ logger.debug('Getting user team={} bot={} email={}.', extra={ 'team': team_name, 'bot': bot_name, 'email': email, }) try: team = Team.get_team_by_name(team_name) except TeamInitializationError: return jsonify({'error': 'provided team name was not found.'}), 404 try: bot = Bot.get_bot_by_name(team, bot_name) except BotInitializationError: return jsonify({'error': 'provided bot name was not found.'}), 404 user = slack.get_user_by_email(bot, email) if not user: return jsonify({'error': 'user not found'}, ), 404 name = slack.get_name_from_user(user) return jsonify({ 'user': { 'email': email, 'name': name, 'team_id': team.team_id, 'user_id': user['id'] } })
def test_message(mocker): _team = Team.get_team_by_name('testteam') _bot = Bot.get_bot_by_name(_team, 'echobot') event = { 'ts': '1234567.12', 'thread_ts': None, 'user': '******', 'text': '<@A12345678> echo I am <!here|here> in <#C123456AB|channel-channel>. See: <http://example.com> :simple_smile:', # noqa:E501 'channel': 'C123456AB', } event_trace = { 'event_ts': '1234567.12', 'event_type': 'message', 'app_id': _bot.bot_id, 'team_id': _bot.team.team_id, 'bot_receiver': _bot.name } get_user_mock = mocker.patch('omnibot.services.slack.get_user') user_ret = {'A12345678': {}} get_user_mock.return_value = user_ret get_channel_mock = mocker.patch('omnibot.services.slack.get_channel') channel_ret = {'C123456AB': {}} get_channel_mock.return_value = channel_ret extract_users_mock = mocker.patch( 'omnibot.services.slack.parser.extract_users') user_ret = {'<@A12345678>': 'echobot'} extract_users_mock.return_value = user_ret replace_users_mock = mocker.patch( 'omnibot.services.slack.parser.replace_users') replace_users_mock.return_value = '@echobot echo I am <!here|here> in <#C123456AB|channel-channel>. See: <http://example.com> :simple_smile:' # noqa:E501 extract_channels_mock = mocker.patch( 'omnibot.services.slack.parser.extract_channels') channels_ret = {'<#C123456AB|channel-channel>': 'channel-channel'} extract_channels_mock.return_value = channels_ret replace_channels_mock = mocker.patch( 'omnibot.services.slack.parser.replace_channels') replace_channels_mock.return_value = '@echobot echo I am <!here|here> in #channel-channel. See: <http://example.com> :simple_smile:' # noqa:E501 extract_subteams_mock = mocker.patch( 'omnibot.services.slack.parser.extract_subteams') extract_subteams_mock.return_value = {} extract_specials_mock = mocker.patch( 'omnibot.services.slack.parser.extract_specials') special_ret = {'<!here|here>': '@here'} extract_specials_mock.return_value = special_ret replace_specials_mock = mocker.patch( 'omnibot.services.slack.parser.replace_specials') replace_specials_mock.return_value = '@echobot echo I am @here in #channel-channel. See: <http://example.com> :simple_smile:' # noqa:E501 extract_emojis_mock = mocker.patch( 'omnibot.services.slack.parser.extract_emojis') emoji_ret = {':simple-smile': 'simple_smile'} extract_emojis_mock.return_value = emoji_ret extract_emails_mock = mocker.patch( 'omnibot.services.slack.parser.extract_emails') extract_emails_mock.return_value = {} replace_emails_mock = mocker.patch( 'omnibot.services.slack.parser.replace_emails') replace_emails_mock.return_value = '@echobot echo I am @here in #channel-channel. See: <http://example.com> :simple_smile:' # noqa:E501 extract_urls_mock = mocker.patch( 'omnibot.services.slack.parser.extract_urls') url_ret = {'<http://example.com>': 'http://example.com'} extract_urls_mock.return_value = url_ret replace_urls_mock = mocker.patch( 'omnibot.services.slack.parser.replace_urls') replace_urls_mock.return_value = '@echobot echo I am @here in #channel-channel. See: http://example.com :simple_smile:' # noqa:E501 extract_mentions_mock = mocker.patch( 'omnibot.services.slack.parser.extract_mentions') extract_mentions_mock.return_value = True extract_command_mock = mocker.patch( 'omnibot.services.slack.parser.extract_command') extract_command_mock.return_value = 'echo I am @here in #channel-channel. See: http://example.com :simple_smile:' # noqa:E501 _message = Message(_bot, event, event_trace) assert _message.event == event assert _message.event_trace == event_trace assert _message.bot == _bot assert _message.subtype is None assert _message.text == event['text'] assert _message.parsed_text == '@echobot echo I am @here in #channel-channel. See: http://example.com :simple_smile:' # noqa:E501 assert _message.command_text == 'echo I am @here in #channel-channel. See: http://example.com :simple_smile:' # noqa:E501 assert _message.directed is True assert _message.mentioned is True assert _message.channel_id == 'C123456AB' assert _message.channel == channel_ret assert _message.user == event['user'] assert _message.ts == event['ts'] assert _message.thread_ts == event['thread_ts'] assert _message.team == { 'name': _bot.team.name, 'team_id': _bot.team.team_id } assert _message.bot_id is None assert _message.channels == channels_ret assert _message.users == user_ret assert _message.specials == special_ret assert _message.emails == {} assert _message.urls == url_ret assert _message.match_type is None assert _message.match is None assert _message.event_trace == event_trace _message.set_match('command', 'echo') assert _message.match_type == 'command' assert _message.match == 'echo' assert _message.payload['command'] == 'echo' assert _message.payload[ 'args'] == 'I am @here in #channel-channel. See: http://example.com :simple_smile:' # noqa:E501 _message.set_match('regex', None) assert _message.match_type == 'regex' event_copy = copy.deepcopy(event) event_copy['bot_id'] = 'A12345678' with pytest.raises(MessageUnsupportedError): _message = Message(_bot, event_copy, event_trace) event_copy = copy.deepcopy(event) event_copy['thread_ts'] = '1234568.00' with pytest.raises(MessageUnsupportedError): _message = Message(_bot, event_copy, event_trace) event_copy = copy.deepcopy(event) event_copy['subtype'] = 'some_subtype' with pytest.raises(MessageUnsupportedError): _message = Message(_bot, event_copy, event_trace)