Ejemplo n.º 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')
Ejemplo n.º 2
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.º 3
0
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)
Ejemplo n.º 4
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.º 5
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.º 6
0
Archivo: api.py Proyecto: lyft/omnibot
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})
Ejemplo n.º 7
0
 def get_bot_by_verification_token(cls, verification_token):
     name = None
     _bot_data = {}
     for team_name, bots in settings.SLACK_BOT_TOKENS.items():
         for bot_name, bot_data in bots.items():
             if verification_token == bot_data['verification_token']:
                 name = bot_name
                 _bot_data = bot_data
                 break
     if not _bot_data:
         raise BotInitializationError('Invalid bot')
     team = Team.get_team_by_name(team_name)
     return cls(team, name, _bot_data)
Ejemplo n.º 8
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.º 9
0
Archivo: api.py Proyecto: lyft/omnibot
 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)
Ejemplo n.º 10
0
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'
Ejemplo n.º 11
0
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
Ejemplo n.º 12
0
Archivo: api.py Proyecto: lyft/omnibot
def get_team_id_by_name(team_name):
    """
    Get a team_id, from its `team_name`.

    .. :quickref: Team ID; Get team_id from team_name

    **Example request**:

    .. sourcecode:: http

       GET /api/v1/slack/get_team/myteam HTTP/1.1

    **Example response**:

    .. sourcecode:: http

       HTTP/1.1 200 OK
       Content-Type: application/json

       {"team_id": "T123456"}

    :param team_name: The team to search for this user, as configured in
                      omnibot.
    :type team_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: team is not configured
    """
    logger.debug('Getting team id', extra={'team': team_name})
    try:
        team = Team.get_team_by_name(team_name)
        return jsonify({'team_id': team.team_id})
    except TeamInitializationError:
        return jsonify({'error': 'provided team_name is not configured.'}), 404
Ejemplo n.º 13
0
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)
Ejemplo n.º 14
0
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
Ejemplo n.º 15
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"))
Ejemplo n.º 16
0
Archivo: api.py Proyecto: lyft/omnibot
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
Ejemplo n.º 17
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.º 18
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.º 19
0
Archivo: api.py Proyecto: lyft/omnibot
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
Ejemplo n.º 20
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.º 21
0
Archivo: api.py Proyecto: lyft/omnibot
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)
Ejemplo n.º 22
0
Archivo: api.py Proyecto: lyft/omnibot
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']
        }
    })