Beispiel #1
0
async def handle_slack_event(*, client, event, background_tasks):
    """Handles slack event message."""
    user_id = event.event.user
    channel_id = get_channel_id_from_event(event)

    if user_id and channel_id:
        db_session = get_organization_from_channel_id(channel_id=channel_id)
        if not db_session:
            log.info(
                f"Unable to determine organization associated with channel id. ChannelId: {channel_id}"
            )
            return {"ok": ""}

        conversation = conversation_service.get_by_channel_id_ignoring_channel_type(
            db_session=db_session, channel_id=channel_id)

        if conversation and dispatch_slack_service.is_user(user_id):
            # We resolve the user's email
            user_email = await dispatch_slack_service.get_user_email_async(
                client, user_id)

            # Dispatch event functions to be executed in the background
            for f in event_functions(event):
                background_tasks.add_task(
                    f,
                    user_id,
                    user_email,
                    channel_id,
                    conversation.incident_id,
                    event=event,
                )

    return {"ok": ""}
Beispiel #2
0
def handle_dialog_action(config: SlackConversationConfiguration, action: dict,
                         background_tasks: BackgroundTasks):
    """Handles all dialog actions."""
    channel_id = action["channel"]["id"]
    db_session = get_organization_scope_from_channel_id(channel_id=channel_id)

    conversation = conversation_service.get_by_channel_id_ignoring_channel_type(
        db_session=db_session, channel_id=channel_id)
    incident_id = conversation.incident_id

    user_id = action["user"]["id"]
    user_email = action["user"]["email"]

    action_id = action["callback_id"]

    for f in dialog_action_functions(config, action_id):
        background_tasks.add_task(
            f,
            user_id=user_id,
            user_email=user_email,
            config=config,
            channel_id=channel_id,
            incident_id=incident_id,
            action=action,
        )
Beispiel #3
0
async def handle_slack_command(*, db_session, client, request,
                               background_tasks):
    """Handles slack command message."""
    # We fetch conversation by channel id
    channel_id = request.get("channel_id")
    conversation = conversation_service.get_by_channel_id_ignoring_channel_type(
        db_session=db_session, channel_id=channel_id)

    # We get the name of command that was run
    command = request.get("command")

    incident_id = 0
    if conversation:
        incident_id = conversation.incident_id
    else:
        if command not in [
                SLACK_COMMAND_REPORT_INCIDENT_SLUG,
                SLACK_COMMAND_LIST_INCIDENTS_SLUG
        ]:
            # We let the user know that incident-specific commands
            # can only be run in incident conversations
            return create_command_run_in_nonincident_conversation_message(
                command)

        # We get the list of public and private conversations the Slack bot is a member of
        (
            public_conversations,
            private_conversations,
        ) = await dispatch_slack_service.get_conversations_by_user_id_async(
            client, SLACK_APP_USER_SLUG)

        # We get the name of conversation where the command was run
        conversation_id = request.get("channel_id")
        conversation_name = await dispatch_slack_service.get_conversation_name_by_id_async(
            client, conversation_id)

        if (not conversation_name or conversation_name
                not in public_conversations + private_conversations):
            # We let the user know in which public conversations they can run the command
            return create_command_run_in_conversation_where_bot_not_present_message(
                command, public_conversations)

    user_id = request.get("user_id")
    user_email = await dispatch_slack_service.get_user_email_async(
        client, user_id)

    # some commands are sensitive and we only let non-participants execute them
    allowed = check_command_restrictions(command=command,
                                         user_email=user_email,
                                         incident_id=incident_id,
                                         db_session=db_session)
    if not allowed:
        return create_command_run_by_non_privileged_user_message(command)

    for f in command_functions(command):
        background_tasks.add_task(f, incident_id, command=request)

    return INCIDENT_CONVERSATION_COMMAND_MESSAGE.get(
        command, f"Running... Command: {command}")
Beispiel #4
0
def get_plugin_configuration_from_channel_id(db_session: SessionLocal,
                                             channel_id: str) -> Plugin:
    """Fetches the currently slack plugin configuration for this incident channel."""
    conversation = conversation_service.get_by_channel_id_ignoring_channel_type(
        db_session, channel_id)
    if conversation:
        plugin_instance = plugin_service.get_active_instance(
            db_session=db_session,
            plugin_type="conversation",
            project_id=conversation.incident.project.id,
        )
        return plugin_instance.configuration
Beispiel #5
0
async def handle_event(
        event: EventEnvelope,
        request: Request,
        response: Response,
        background_tasks: BackgroundTasks,
        x_slack_request_timestamp: int = Header(None),
        x_slack_signature: str = Header(None),
        db_session: Session = Depends(get_db),
):
    """Handle all incomming Slack events."""
    raw_request_body = bytes.decode(await request.body())

    # We verify the timestamp
    verify_timestamp(x_slack_request_timestamp)

    # We verify the signature
    verify_signature(raw_request_body, x_slack_request_timestamp,
                     x_slack_signature)

    # Echo the URL verification challenge code back to Slack
    if event.challenge:
        return {"challenge": event.challenge}

    event_body = event.event

    user_id = event_body.user
    channel_id = get_channel_id_from_event(event_body)

    if user_id and channel_id:
        conversation = conversation_service.get_by_channel_id_ignoring_channel_type(
            db_session=db_session, channel_id=channel_id)

        if conversation and dispatch_slack_service.is_user(user_id):
            # We create an async Slack client
            slack_async_client = dispatch_slack_service.create_slack_client(
                run_async=True)

            # We resolve the user's email
            user_email = await dispatch_slack_service.get_user_email_async(
                slack_async_client, user_id)

            # Dispatch event functions to be executed in the background
            for f in event_functions(event):
                background_tasks.add_task(f,
                                          user_email,
                                          conversation.incident_id,
                                          event=event)

    # We add the user-agent string to the response headers
    response.headers["X-Slack-Powered-By"] = create_ua_string()
    return {"ok"}
Beispiel #6
0
def handle_dialog_action(action: dict, background_tasks: BackgroundTasks, db_session: SessionLocal):
    """Handles all dialog actions."""
    channel_id = action["channel"]["id"]
    conversation = conversation_service.get_by_channel_id_ignoring_channel_type(
        db_session=db_session, channel_id=channel_id
    )
    incident_id = conversation.incident_id

    user_id = action["user"]["id"]
    user_email = action["user"]["email"]

    action_id = action["callback_id"]

    for f in dialog_action_functions(action_id):
        background_tasks.add_task(f, user_id, user_email, incident_id, action)
Beispiel #7
0
async def handle_incident_conversation_commands(config, client, request, background_tasks):
    """Handles all commands that are issued from an incident conversation."""
    channel_id = request.get("channel_id")
    command = request.get("command")
    db_session = get_organization_scope_from_channel_id(channel_id=channel_id)

    if not db_session:
        # We let the user know that incident-specific commands
        # can only be run in incident conversations
        return create_command_run_in_nonincident_conversation_message(command)

    conversation = conversation_service.get_by_channel_id_ignoring_channel_type(
        db_session=db_session, channel_id=channel_id
    )

    user_id = request.get("user_id")
    user_email = await dispatch_slack_service.get_user_email_async(client, user_id)

    # some commands are sensitive and we only let non-participants execute them
    allowed = check_command_restrictions(
        config=config,
        command=command,
        user_email=user_email,
        incident_id=conversation.incident.id,
        db_session=db_session,
    )
    if not allowed:
        return create_command_run_by_non_privileged_user_message(command)

    for f in command_functions(config, command):
        background_tasks.add_task(
            f,
            user_id=user_id,
            user_email=user_email,
            channel_id=channel_id,
            config=config,
            incident_id=conversation.incident.id,
            command=request,
        )

    return get_incident_conversation_command_message(config, command)
Beispiel #8
0
async def handle_command(
    request: Request,
    response: Response,
    background_tasks: BackgroundTasks,
    x_slack_request_timestamp: int = Header(None),
    x_slack_signature: str = Header(None),
    db_session: Session = Depends(get_db),
):
    """Handle all incomming Slack commands."""
    raw_request_body = bytes.decode(await request.body())
    request_body_form = await request.form()
    command = request_body_form._dict

    # We verify the timestamp
    verify_timestamp(x_slack_request_timestamp)

    # We verify the signature
    verify_signature(raw_request_body, x_slack_request_timestamp, x_slack_signature)

    # We add the user-agent string to the response headers
    response.headers["X-Slack-Powered-By"] = create_ua_string()

    # Fetch conversation by channel id
    channel_id = command.get("channel_id")
    conversation = conversation_service.get_by_channel_id_ignoring_channel_type(
        db_session=db_session, channel_id=channel_id
    )

    incident_id = 0
    if conversation:
        incident_id = conversation.incident_id
    else:
        if command.get("command") != SLACK_COMMAND_REPORT_INCIDENT_SLUG:
            return render_non_incident_conversation_command_error_message(command.get("command"))

    for f in command_functions(command.get("command")):
        background_tasks.add_task(f, incident_id, command=command)

    return INCIDENT_CONVERSATION_COMMAND_MESSAGE.get(
        command.get("command"), f"Running... Command: {command.get('command')}"
    )
Beispiel #9
0
def get_organization_scope_from_channel_id(channel_id: str) -> SessionLocal:
    """Iterate all organizations looking for a relevant channel_id."""
    db_session = SessionLocal()
    organization_slugs = [
        o.slug for o in organization_service.get_all(db_session=db_session)
    ]
    db_session.close()

    for slug in organization_slugs:
        schema_engine = engine.execution_options(
            schema_translate_map={
                None: f"dispatch_organization_{slug}",
            })

        scoped_db_session = sessionmaker(bind=schema_engine)()
        conversation = conversation_service.get_by_channel_id_ignoring_channel_type(
            db_session=scoped_db_session, channel_id=channel_id)
        if conversation:
            return scoped_db_session

        scoped_db_session.close()
Beispiel #10
0
async def handle_slack_event(*, db_session, client, event, background_tasks):
    """Handles slack event message."""
    user_id = event.event.user
    channel_id = get_channel_id_from_event(event)

    if user_id and channel_id:
        conversation = conversation_service.get_by_channel_id_ignoring_channel_type(
            db_session=db_session, channel_id=channel_id)

        if conversation and dispatch_slack_service.is_user(user_id):
            # We resolve the user's email
            user_email = await dispatch_slack_service.get_user_email_async(
                client, user_id)

            # Dispatch event functions to be executed in the background
            for f in event_functions(event):
                background_tasks.add_task(f,
                                          user_email,
                                          conversation.incident_id,
                                          event=event)

    return {"ok": ""}
Beispiel #11
0
async def handle_command(
        request: Request,
        response: Response,
        background_tasks: BackgroundTasks,
        x_slack_request_timestamp: int = Header(None),
        x_slack_signature: str = Header(None),
        db_session: Session = Depends(get_db),
):
    """Handle all incomming Slack commands."""
    raw_request_body = bytes.decode(await request.body())
    request_body_form = await request.form()
    command_details = request_body_form._dict

    # We verify the timestamp
    verify_timestamp(x_slack_request_timestamp)

    # We verify the signature
    verify_signature(raw_request_body, x_slack_request_timestamp,
                     x_slack_signature)

    # We add the user-agent string to the response headers
    response.headers["X-Slack-Powered-By"] = create_ua_string()

    # We fetch conversation by channel id
    channel_id = command_details.get("channel_id")
    conversation = conversation_service.get_by_channel_id_ignoring_channel_type(
        db_session=db_session, channel_id=channel_id)

    # We get the name of command that was run
    command = command_details.get("command")

    incident_id = 0
    if conversation:
        incident_id = conversation.incident_id
    else:
        if command not in [
                SLACK_COMMAND_REPORT_INCIDENT_SLUG,
                SLACK_COMMAND_LIST_INCIDENTS_SLUG
        ]:
            # We let the user know that incident-specific commands
            # can only be run in incident conversations
            return create_command_run_in_nonincident_conversation_message(
                command)

        # We create an async Slack client
        slack_async_client = dispatch_slack_service.create_slack_client(
            run_async=True)

        # We get the list of public and private conversations the Slack bot is a member of
        (
            public_conversations,
            private_conversations,
        ) = await dispatch_slack_service.get_conversations_by_user_id_async(
            slack_async_client, SLACK_APP_USER_SLUG)

        # We get the name of conversation where the command was run
        conversation_name = command_details.get("channel_name")

        if conversation_name not in public_conversations + private_conversations:
            # We let the user know in which public conversations they can run the command
            return create_command_run_in_conversation_where_bot_not_present_message(
                command, public_conversations)

    for f in command_functions(command):
        background_tasks.add_task(f, incident_id, command=command_details)

    return INCIDENT_CONVERSATION_COMMAND_MESSAGE.get(
        command, f"Running... Command: {command}")
Beispiel #12
0
    def wrapper(*args, **kwargs):
        background = False

        if not kwargs.get("db_session"):
            channel_id = args[2]

            # slug passed directly is prefered over just having a channel_id
            organization_slug = kwargs.pop("organization_slug", None)
            if not organization_slug:
                scoped_db_session = get_organization_from_channel_id(
                    channel_id=channel_id)
                if not scoped_db_session:
                    raise Exception(
                        f"Could not find an organization associated with channel_id. ChannelId: {channel_id}"
                    )
            else:
                schema_engine = engine.execution_options(
                    schema_translate_map={
                        None: f"dispatch_organization_{organization_slug}",
                    })

                scoped_db_session = sessionmaker(bind=schema_engine)()

            background = True
            kwargs["db_session"] = scoped_db_session

        if not kwargs.get("slack_client"):
            slack_client = dispatch_slack_service.create_slack_client()
            kwargs["slack_client"] = slack_client

        try:
            metrics_provider.counter("function.call.counter",
                                     tags={
                                         "function": fullname(func),
                                         "slack": True
                                     })
            start = time.perf_counter()
            result = func(*args, **kwargs)
            elapsed_time = time.perf_counter() - start
            metrics_provider.timer(
                "function.elapsed.time",
                value=elapsed_time,
                tags={
                    "function": fullname(func),
                    "slack": True
                },
            )
            return result
        except Exception as e:
            # we generate our own guid for now, maybe slack provides us something we can use?
            slack_interaction_guid = str(uuid.uuid4())
            log.exception(
                e, extra=dict(slack_interaction_guid=slack_interaction_guid))

            user_id = args[0]
            channel_id = args[2]

            conversation = conversation_service.get_by_channel_id_ignoring_channel_type(
                db_session=kwargs["db_session"], channel_id=channel_id)

            # we notify the user that the interaction failed
            message = (
                f"Sorry, we've run into an unexpected error. For help, please reach out to {conversation.incident.commander.individual.name}",
                f" and provide them with the following token: {slack_interaction_guid}.",
            )
            if conversation.incident.status != IncidentStatus.closed:
                dispatch_slack_service.send_ephemeral_message(
                    kwargs["slack_client"], channel_id, user_id, message)
            else:
                dispatch_slack_service.send_message(
                    client=kwargs["slack_client"],
                    conversation_id=user_id,
                    text=message,
                )
        finally:
            if background:
                kwargs["db_session"].close()