async def handle_menu( request: Request, response: Response, x_slack_request_timestamp: int = Header(None), x_slack_signature: str = Header(None), db_session: Session = Depends(get_db), ): """Handle all incoming Slack actions.""" raw_request_body = bytes.decode(await request.body()) request_body_form = await request.form() request = json.loads(request_body_form.get("payload")) # 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 create an async Slack client slack_async_client = dispatch_slack_service.create_slack_client( run_async=True) return await handle_slack_menu( db_session=db_session, client=slack_async_client, request=request, )
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 incoming 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) # We add the user-agent string to the response headers response.headers["X-Slack-Powered-By"] = create_ua_string() # Echo the URL verification challenge code back to Slack if event.challenge: return {"challenge": event.challenge} slack_async_client = dispatch_slack_service.create_slack_client( run_async=True) return await handle_slack_event( db_session=db_session, client=slack_async_client, event=event, background_tasks=background_tasks, )
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 incoming Slack commands.""" raw_request_body = bytes.decode(await request.body()) request_body_form = await request.form() request = 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() slack_async_client = dispatch_slack_service.create_slack_client( run_async=True) return await handle_slack_command( db_session=db_session, client=slack_async_client, request=request, background_tasks=background_tasks, )
async def handle_command( request: Request, response: Response, organization: str, background_tasks: BackgroundTasks, x_slack_request_timestamp: int = Header(...), x_slack_signature: str = Header(...), ): """Handle all incoming Slack commands.""" raw_request_body = bytes.decode(await request.body()) request_body_form = await request.form() request = request_body_form._dict # We verify the timestamp verify_timestamp(x_slack_request_timestamp) # We verify the signature current_configuration = verify_signature(organization, 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() slack_async_client = dispatch_slack_service.create_slack_client( config=current_configuration, run_async=True) body = await handle_slack_command( config=current_configuration, client=slack_async_client, request=request, background_tasks=background_tasks, ) return JSONResponse(content=body)
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 if (event_body.type == "message" and event_body.subtype): # We ignore messages that have a subtype # Parse the Event payload and emit the event to the event listener response.headers["X-Slack-Powered-By"] = create_ua_string() return {"ok"} user_id = event_body.user # Fetch conversation by channel id channel_id = ( # We ensure channel_id always has a value event_body.channel_id if event_body.channel_id else event_body.channel) conversation = get_by_channel_id(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) # We add the user-agent string to the response headers response.headers["X-Slack-Powered-By"] = create_ua_string() return {"ok"}
async def handle_action( 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 actions.""" raw_request_body = bytes.decode(await request.body()) request_body_form = await request.form() action = json.loads(request_body_form.get("payload")) # 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 create an async Slack client slack_async_client = dispatch_slack_service.create_slack_client( run_async=True) # We resolve the user's email user_id = action["user"]["id"] user_email = await dispatch_slack_service.get_user_email_async( slack_async_client, user_id) # We resolve the action name based on the type action_name = get_action_name_by_action_type(action) # if the request was made as a form submission from slack then we skip getting the incident_id # the incident will be created in in the next step incident_id = 0 if action_name != NewIncidentSubmission.form_slack_view: # we resolve the incident id based on the action type incident_id = get_incident_id_by_action_type(action, db_session) # Dispatch action functions to be executed in the background for f in action_functions(action_name): background_tasks.add_task(f, user_id, user_email, incident_id, action) # We add the user-agent string to the response headers response.headers["X-Slack-Powered-By"] = create_ua_string() # When there are no exceptions within the dialog submission, your app must respond with 200 OK with an empty body. response_body = {} if action_name == NewIncidentSubmission.form_slack_view: # For modals we set "response_action" to "clear" to close all views in the modal. # An empty body is currently not working. response_body = {"response_action": "clear"} return response_body
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"}
async def handle_action( 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 actions.""" raw_request_body = bytes.decode(await request.body()) request_body_form = await request.form() action = json.loads(request_body_form.get("payload")) # 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 create an async Slack client slack_async_client = dispatch_slack_service.create_slack_client( run_async=True) # We resolve the user's email user_id = action["user"]["id"] user_email = await dispatch_slack_service.get_user_email_async( slack_async_client, user_id) action["user"]["email"] = user_email # We add the user-agent string to the response headers # NOTE: I don't think this header ever gets sent? (kglisson) response.headers["X-Slack-Powered-By"] = create_ua_string() # When there are no exceptions within the dialog submission, your app must respond with 200 OK with an empty body. response_body = {} if action.get("view"): handle_modal_action(action, background_tasks) if action["type"] == "view_submission": # For modals we set "response_action" to "clear" to close all views in the modal. # An empty body is currently not working. response_body = {"response_action": "clear"} elif action["type"] == "dialog_submission": handle_dialog_action(action, background_tasks, db_session=db_session) elif action["type"] == "block_actions": handle_block_action(action, background_tasks) return response_body
def wrapper(*args, **kwargs): background = False if not kwargs.get("db_session"): db_session = SessionLocal() background = True kwargs["db_session"] = 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)) # notify the user the interaction failed user_id = args[0] channel_id = args[2] message = f"Sorry, we've run into an unexpected error. For help, please reach out to the incident commander and provide them with the following token: {slack_interaction_guid}." dispatch_slack_service.send_ephemeral_message( kwargs["slack_client"], channel_id, user_id, message) finally: if background: kwargs["db_session"].close()
async def handle_action( 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 actions.""" raw_request_body = bytes.decode(await request.body()) request_body_form = await request.form() action = json.loads(request_body_form.get("payload")) # 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 create an async Slack client slack_async_client = dispatch_slack_service.create_slack_client( run_async=True) # We resolve the user's email user_id = action["user"]["id"] user_email = await dispatch_slack_service.get_user_email_async( slack_async_client, user_id) # We resolve the action name based on the type action_name = get_action_name_by_action_type(action) # we resolve the incident id based on the action type incident_id = get_incident_id_by_action_type(action, db_session) # Dispatch action functions to be executed in the background for f in action_functions(action_name): background_tasks.add_task(f, user_email, incident_id, action) # We add the user-agent string to the response headers response.headers["X-Slack-Powered-By"] = create_ua_string() # When there are no exceptions within the dialog submission, your app must respond with 200 OK with an empty body. # This will complete the dialog. (https://api.slack.com/dialogs#validation) return {}
async def check_command_restrictions(command: str, user_id: str, incident_id: int, db_session: Session) -> bool: """Checks the current user's role to determine what commands they are allowed to run.""" # some commands are sensitive and we only let non-participants execute them command_permissons = { SLACK_COMMAND_UPDATE_INCIDENT_SLUG: [ ParticipantRoleType.incident_commander, ParticipantRoleType.scribe, ], SLACK_COMMAND_ASSIGN_ROLE_SLUG: [ ParticipantRoleType.incident_commander, ParticipantRoleType.reporter, ParticipantRoleType.liaison, ParticipantRoleType.scribe, ], SLACK_COMMAND_REPORT_EXECUTIVE_SLUG: [ ParticipantRoleType.incident_commander, ParticipantRoleType.scribe, ], SLACK_COMMAND_REPORT_TACTICAL_SLUG: [ ParticipantRoleType.incident_commander, ParticipantRoleType.scribe, ], } # no permissions have been defined if command not in command_permissons.keys(): return True slack_async_client = dispatch_slack_service.create_slack_client( run_async=True) user_email = await dispatch_slack_service.get_user_email_async( slack_async_client, user_id) participant = participant_service.get_by_incident_id_and_email( db_session=db_session, incident_id=incident_id, email=user_email) # if any required role is active, allow command for current_role in participant.current_roles: for allowed_role in command_permissons[command]: if current_role.role == allowed_role: return True
async def handle_event( event: EventEnvelope, request: Request, response: Response, organization: str, background_tasks: BackgroundTasks, x_slack_request_timestamp: int = Header(...), x_slack_signature: str = Header(...), ): """Handle all incoming Slack events.""" raw_request_body = bytes.decode(await request.body()) # We verify the timestamp verify_timestamp(x_slack_request_timestamp) # We verify the signature current_configuration = verify_signature(organization, 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() # Echo the URL verification challenge code back to Slack if event.challenge: return JSONResponse(content={"challenge": event.challenge}) slack_async_client = dispatch_slack_service.create_slack_client( config=current_configuration, run_async=True) body = await handle_slack_event( config=current_configuration, client=slack_async_client, event=event, background_tasks=background_tasks, ) return JSONResponse(content=body)
async def handle_menu( request: Request, response: Response, organization: str, x_slack_request_timestamp: int = Header(...), x_slack_signature: str = Header(...), ): """Handle all incoming Slack actions.""" raw_request_body = bytes.decode(await request.body()) request_body_form = await request.form() try: request = json.loads(request_body_form.get("payload")) except Exception: raise HTTPException(status_code=400, detail=[{"msg": "Bad Request"}]) # We verify the timestamp verify_timestamp(x_slack_request_timestamp) # We verify the signature verify_signature(organization, raw_request_body, x_slack_request_timestamp, x_slack_signature) current_configuration = verify_signature(organization, 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 create an async Slack client slack_async_client = dispatch_slack_service.create_slack_client( config=current_configuration, run_async=True) body = await handle_slack_menu( config=current_configuration, client=slack_async_client, request=request, ) return JSONResponse(content=body)
from dispatch.incident.enums import IncidentStatus from dispatch.incident.models import IncidentUpdate, IncidentRead from dispatch.plugins.dispatch_slack import service as dispatch_slack_service from dispatch.report import flows as report_flows from .config import ( SLACK_COMMAND_ASSIGN_ROLE_SLUG, SLACK_COMMAND_ENGAGE_ONCALL_SLUG, SLACK_COMMAND_EXECUTIVE_REPORT_SLUG, SLACK_COMMAND_TACTICAL_REPORT_SLUG, SLACK_COMMAND_UPDATE_INCIDENT_SLUG, ) from .service import get_user_email slack_client = dispatch_slack_service.create_slack_client() @background_task def add_user_to_conversation(user_id: str, user_email: str, incident_id: int, action: dict, db_session=None): """Adds a user to a conversation.""" incident = incident_service.get(db_session=db_session, incident_id=incident_id) if incident.status == IncidentStatus.closed: message = f"Sorry, we cannot add you to a closed incident. Please reach out to the incident commander ({incident.commander.name}) for details." dispatch_slack_service.send_ephemeral_message(
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()
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}")
def wrapper(*args, **kwargs): background = False metrics_provider.counter("function.call.counter", tags={ "function": fullname(func), "slack": True }) channel_id = kwargs["channel_id"] user_id = kwargs["user_id"] if not kwargs.get("db_session"): # 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_scope_from_channel_id( channel_id=channel_id) if not scoped_db_session: scoped_db_session = get_default_organization_scope() else: scoped_db_session = get_organization_scope_from_slug( organization_slug) background = True kwargs["db_session"] = scoped_db_session if not kwargs.get("slack_client"): slack_client = dispatch_slack_service.create_slack_client( config=kwargs["config"]) kwargs["slack_client"] = slack_client try: 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 ValidationError as e: log.exception(e) message = f"Command Error: {e.errors()[0]['msg']}" dispatch_slack_service.send_ephemeral_message( client=kwargs["slack_client"], conversation_id=channel_id, user_id=user_id, text=message, ) 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)) # we notify the user that the interaction failed message = f"""Sorry, we've run into an unexpected error. For help please reach out to your Dispatch admins \ and provide them with the following token: '{slack_interaction_guid}'.""" dispatch_slack_service.send_ephemeral_message( client=kwargs["slack_client"], conversation_id=channel_id, user_id=user_id, text=message, ) finally: if background: kwargs["db_session"].close()