bots/incident-response-slackbot/incident_response_slackbot/handlers.py (254 lines of code) (raw):
import os
import pickle
import typing as t
from enum import Enum
from logging import getLogger
from incident_response_slackbot.config import load_config, get_config
from incident_response_slackbot.db.database import Database
from incident_response_slackbot.openai_utils import (
create_greeting,
generate_awareness_question,
get_thread_summary,
get_user_awareness,
messages_to_string,
)
from openai_slackbot.handlers import BaseActionHandler, BaseMessageHandler
logger = getLogger(__name__)
DATABASE = Database()
class InboundDirectMessageHandler(BaseMessageHandler):
"""
Handles Direct Messages for incident response use cases
"""
def __init__(self, slack_client):
super().__init__(slack_client)
self.config = get_config()
async def should_handle(self, args):
return True
async def handle(self, args):
event = args.event
user_id = event.get("user")
if not DATABASE.user_exists(user_id):
# If the user_id does not exist, they're not part of an active chat
return
message_ts = DATABASE.get_ts(user_id)
await self.send_message_to_channel(event, message_ts)
user_awareness = await get_user_awareness(event["text"])
logger.info(f"User awareness decision: {user_awareness}")
if user_awareness["has_answered"]:
await self.handle_user_response(user_id, message_ts)
else:
await self.nudge_user(user_id, message_ts)
async def send_message_to_channel(self, event, message_ts):
# Send the received message to the monitoring channel
await self._slack_client.post_message(
channel=self.config.feed_channel_id,
text=f"Received message from <@{event['user']}>:\n> {event['text']}",
thread_ts=message_ts,
)
async def handle_user_response(self, user_id, message_ts):
# User has answered the question
messages = await self._slack_client.get_thread_messages(
channel=self.config.feed_channel_id,
thread_ts=message_ts,
)
# Send the end message to the user
thank_you = "Thanks for your time!"
await self._slack_client.post_message(
channel=user_id,
text=thank_you,
)
# Send message to the channel
await self._slack_client.post_message(
channel=self.config.feed_channel_id,
text=f"Sent message to <@{user_id}>:\n> {thank_you}",
thread_ts=message_ts,
)
summary = await get_thread_summary(messages)
# Send message to the channel
await self._slack_client.post_message(
channel=self.config.feed_channel_id,
text=f"Here is the summary of the chat:\n> {summary}",
thread_ts=message_ts,
)
DATABASE.delete(user_id)
await self.end_chat(message_ts)
async def end_chat(self, message_ts):
original_blocks = await self._slack_client.get_original_blocks(
message_ts, self.config.feed_channel_id
)
# Remove action buttons and add "Chat has ended" text
new_blocks = [block for block in original_blocks if block.get("type") != "actions"]
# Add the "Chat has ended" text
new_blocks.append(
{
"type": "section",
"block_id": "end_chat_automatically",
"text": {
"type": "mrkdwn",
"text": f"The chat was automatically ended from SecurityBot review. :done_:",
"verbatim": True,
},
}
)
await self._slack_client.update_message(
channel=self.config.feed_channel_id,
blocks=new_blocks,
ts=message_ts,
text="Ended chat automatically",
)
async def nudge_user(self, user_id, message_ts):
# User has not answered the question
nudge_message = await generate_awareness_question()
# Send the greeting message to the user
await self._slack_client.post_message(
channel=user_id,
text=nudge_message,
)
# Send message to the channel
await self._slack_client.post_message(
channel=self.config.feed_channel_id,
text=f"Sent message to <@{user_id}>:\n> {nudge_message}",
thread_ts=message_ts,
)
class InboundIncidentStartChatHandler(BaseActionHandler):
def __init__(self, slack_client):
super().__init__(slack_client)
self.config = get_config()
@property
def action_id(self):
return "start_chat_submit_action"
async def handle(self, args):
body = args.body
original_message = body["container"]
original_message_ts = original_message["message_ts"]
alert_user_id = DATABASE.get_user_id(original_message_ts)
user = body["user"]
name = user["name"]
first_name = name.split(".")[1]
logger.info(f"Handling inbound incident start chat action from {user['name']}")
# Update the blocks and elements
blocks = self.update_blocks(body, alert_user_id)
# Add the "Started a chat" text
blocks.append(self.create_chat_start_section(user["id"]))
messages = await self._slack_client.get_thread_messages(
channel=self.config.feed_channel_id,
thread_ts=original_message_ts,
)
message = await self._slack_client.update_message(
channel=self.config.feed_channel_id,
blocks=blocks,
ts=original_message_ts,
text=messages[0]["text"],
)
text_messages = messages_to_string(messages)
logger.info(f"Alert and detail: {text_messages}")
username = await self._slack_client.get_user_display_name(alert_user_id)
greeting_message = await create_greeting(first_name, text_messages)
logger.info(f"generated greeting message: {greeting_message}")
# Send the greeting message to the user and to the channel
await self.send_greeting_message(alert_user_id, greeting_message, original_message_ts)
logger.info(f"Succesfully started chat with user: {username}")
return message
def update_blocks(self, body, alert_user_id):
body_copy = body.copy()
new_elements = []
for block in body_copy.get("message", {}).get("blocks", []):
if block.get("type") == "actions":
for element in block.get("elements", []):
if element.get("action_id") == "do_nothing_submit_action":
element["action_id"] = "end_chat_submit_action"
element["text"]["text"] = "End Chat"
element["value"] = alert_user_id
new_elements.append(element)
block["elements"] = new_elements
return body_copy.get("message", {}).get("blocks", [])
def create_chat_start_section(self, user_id):
return {
"type": "section",
"block_id": "started_chat",
"text": {
"type": "mrkdwn",
"text": f"<@{user_id}> started a chat.",
"verbatim": True,
},
}
async def send_greeting_message(self, alert_user_id, greeting_message, original_message_ts):
# Send the greeting message to the user
await self._slack_client.post_message(
channel=alert_user_id,
text=greeting_message,
)
# Send message to the channel
await self._slack_client.post_message(
channel=self.config.feed_channel_id,
text=f"Sent message to <@{alert_user_id}>:\n> {greeting_message}",
thread_ts=original_message_ts,
)
class InboundIncidentDoNothingHandler(BaseActionHandler):
"""
Handles incoming alerts and decides whether to take no action.
This will close the alert and mark the status as complete.
"""
def __init__(self, slack_client):
super().__init__(slack_client)
self.config = get_config()
@property
def action_id(self):
return "do_nothing_submit_action"
async def handle(self, args):
body = args.body
user_id = body["user"]["id"]
original_message_ts = body["message"]["ts"]
# Remove action buttons and add "Chat has ended" text
new_blocks = [
block
for block in body.get("message", {}).get("blocks", [])
if block.get("type") != "actions"
]
# Add the "Chat has ended" text
new_blocks.append(
{
"type": "section",
"block_id": "do_nothing",
"text": {
"type": "mrkdwn",
"text": f"<@{user_id}> decided that no action was necessary :done_:",
"verbatim": True,
},
}
)
await self._slack_client.update_message(
channel=self.config.feed_channel_id,
blocks=new_blocks,
ts=original_message_ts,
text="Do Nothing action selected",
)
class InboundIncidentEndChatHandler(BaseActionHandler):
"""
Ends the chat manually
"""
def __init__(self, slack_client):
super().__init__(slack_client)
self.config = get_config()
@property
def action_id(self):
return "end_chat_submit_action"
async def handle(self, args):
body = args.body
user_id = body["user"]["id"]
message_ts = body["message"]["ts"]
alert_user_id = DATABASE.get_user_id(message_ts)
original_blocks = await self._slack_client.get_original_blocks(
message_ts, self.config.feed_channel_id
)
# Remove action buttons and add "Chat has ended" text
new_blocks = [block for block in original_blocks if block.get("type") != "actions"]
# Add the "Chat has ended" text
new_blocks.append(
{
"type": "section",
"block_id": "end_chat_manually",
"text": {
"type": "mrkdwn",
"text": f"<@{user_id}> has ended the chat :done_:",
"verbatim": True,
},
}
)
await self._slack_client.update_message(
channel=self.config.feed_channel_id,
blocks=new_blocks,
ts=message_ts,
text="Ended chat automatically",
)
# User has answered the question
messages = await self._slack_client.get_thread_messages(
channel=self.config.feed_channel_id,
thread_ts=message_ts,
)
thank_you = "Thanks for your time!"
await self._slack_client.post_message(
channel=alert_user_id,
text=thank_you,
)
# Send message to the channel
await self._slack_client.post_message(
channel=self.config.feed_channel_id,
text=f"Sent message to <@{alert_user_id}>:\n> {thank_you}",
thread_ts=message_ts,
)
summary = await get_thread_summary(messages)
# Send message to the channel
await self._slack_client.post_message(
channel=self.config.feed_channel_id,
text=f"Here is the summary of the chat:\n> {summary}",
thread_ts=message_ts,
)
DATABASE.delete(user_id)