def slack_auth(): """Endpoint to slack oauth flow""" code = request.args.get("code") or "no code!" try: team_id, access_token = SlackOauthService.get_access_token(code) DynamoUtils.save_slack_access_token(team_id, access_token) return redirect( f"https://slack.com/app_redirect?app=A01H45TA509&team={team_id}", code=302) except Exception as e: logger.error(f"ERROR: slack/oauth {e}")
def zoom_auth(): """Endpoint to zoom oauth flow""" code = request.args.get("code") or "no code!" state = request.args.get("state") decoded_state = base64.b64decode(state).decode() team_id, _user_id = decoded_state.split(":") token_data: TokenData = ZoomOauthService.get_token_data(code) zoom = Zoom(token_data) DynamoUtils.save_zoom_data(team_id, zoom) return redirect( f"https://slack.com/app_redirect?app=A01H45TA509&team={team_id}", code=302)
def __get_access_token(self) -> str: """ Gets access token from self.token_data.access_token if not expired. If access_token is expired, refreshes and saves the new token data """ if (self.jira.token_data.expiry_date is not None and self.jira.token_data.is_access_token_expired() is False): return self.jira.token_data.access_token else: logger.info(f"Token expired, refreshing... {self.team_id}") self.jira.token_data = JiraOauthService.refresh_access_token( self.jira.token_data) DynamoUtils.save_jira_data(self.team_id, self.jira) logger.info(f"Token refreshed and saved {self.team_id}") return self.jira.token_data.access_token
def list(cls, team_id: str) -> List[SlackResponder]: """ Returns the list of responders from the database. """ responder_id_list = list(DynamoUtils.get_responders(team_id)) responders = cls.__build_responders_list(responder_id_list) return responders
def __init__(self, team_id) -> None: self.team_id = team_id self.jira: Jira = DynamoUtils.get_jira_data(team_id) if self.jira is None or not self.jira.is_valid(): raise Exception("No ticket integration for team") self.BASE_URL = self.BASE_URL % self.jira.account_id self.JIRA_TICKET_BASE_URL = self.JIRA_TICKET_BASE_URL % self.jira.account_id
def get_call(self, incident_id) -> str: """Returns the call link for the given incident""" incident: Incident = DynamoUtils.get_incident(self.team_id, incident_id) if incident is not None and incident.has_call(): return incident.call.get_link() return ""
def show_close_incident_modal(self, team_id, channel_id, trigger_id): """Show a slack modal with an input to add incident resolution text""" slack_access_token = DynamoUtils.get_slack_access_token(team_id) client = WebClient(token=slack_access_token) formatter = CloseIncidentFormatter() client.views_open(trigger_id=trigger_id, view=formatter.format(channel_id).get("view"))
def format(self, team_id, user_id): services_section = [] authorized_apps = DynamoUtils.get_authorized_apps(team_id) for service in self.services: services_section.append( service.build_oauth_entry(team_id, user_id, authorized_apps)) response = self.__build_response(services_section) return self.__build_response(services_section)
def jira_oauth(): """Endpoint to jira oauth flow""" code = request.args.get("code") or "no code!" state = request.args.get("state") decoded_state = base64.b64decode(state).decode() team_id, user_id = decoded_state.split(":") token_data: TokenData = JiraOauthService.get_token_data(code) try: account_id = JiraOauthService.get_jira_id(token_data.access_token) jira = Jira(token_data, account_id) DynamoUtils.save_jira_data(team_id, jira) return redirect( f"https://slack.com/app_redirect?app=A01H45TA509&team={team_id}", code=302) except Exception as e: logger.error( f"Could not authenticate jira app for team: {team_id} and user: {user_id} - {e}" ) return ""
def interaction_handler(message, _context): """ Handles interactive events from Slack elements like buttons """ response_url = message.get("response_url") interaction_type = message.get("type") team = message.get("team") team_id = team.get("id") slack_access_token = DynamoUtils.get_slack_access_token(team_id) client = WebClient(token=slack_access_token) if interaction_type == "view_submission": view = message.get("view") block_id = view.get("blocks")[0].get("block_id") action_id = view.get("blocks")[0].get("element").get("action_id") if action_id == "incident_resolution": incident_id = "" try: resolution_text = (view.get("state").get("values").get( block_id).get("incident_resolution").get("value")) incident_id = view.get("private_metadata") slack_events_handler = SlackEventsHandler(client, team_id) slack_events_handler.close_incident_and_add_resolution( incident_id, resolution_text) return "ok" except Exception as ex: logger.error( f"error closing incident for team {team_id} and incident {incident_id} {ex}" ) client.chat_postMessage( channel=incident_id, text= "There was an error closing the incident. Please contact support", ) return "failed" else: action = message.get("actions")[0] action_id = action.get("action_id") if action_id == "cancel": __update_original_message(response_url, "Incident creation cancelled") elif action_id == "create_incident": if "team" in message and "id" in message.get("team"): try: container = message.get("container") channel = container.get("channel_id") incident_name = action.get("value") slack_events_handler = SlackEventsHandler(client, team_id) slack_events_handler.create_new_incident( channel, incident_name) __delete_original_message(response_url) except Exception as e: logger.error(f"exception during incident creation {e}") return "error" return "ok"
def get_integrated_services(user_id) -> List[IntegratedService]: """ Get services the user has integrated with """ integrated_services: List[IntegratedService] = [] apps = DynamoUtils.get_authorized_apps(user_id) if "jira" in apps: integrated_services.append(JiraApiService(user_id)) if "zoom" in apps: integrated_services.append(ZoomApiService(user_id)) return integrated_services
def __get_access_token(self) -> str: """ Gets access token from self.token_data.access_token if not expired. If access_token is expired, refreshes and saves the new token data """ if ( self.zoom.token_data.expiry_date is not None and self.zoom.token_data.is_access_token_expired() is False ): return self.zoom.token_data.access_token else: if self.zoom.token_data.refresh_token is not None: logger.info("Token expired, refreshing...") self.zoom.token_data = ZoomOauthService.refresh_access_token( self.zoom.token_data ) DynamoUtils.save_zoom_data(self.team_id, self.zoom) logger.info("Token refreshed and saved") return self.zoom.token_data.access_token else: raise Exception()
def create_incident(self, incident_id, incident_name) -> Incident: """ Creates an incident in the database Parameters: incident_id: In the slackbot context, this is the channel id incident_name: Name that will be set as a channel name and also saved in the db """ # Create ticket ticket: Integration = self.create_ticket() # Create call call: Integration = self.create_call() incident = Incident(self.team_id, incident_id, incident_name) if ticket is not None: incident.set_ticket(ticket) if call is not None: incident.set_call(call) # Save incident DynamoUtils.create_incident(incident) return incident
def remove(cls, team_id: str, responders_list: List[str]) -> List[SlackResponder]: """ Removes one or more responders from the responders list in the database. Parameters: team_id(str): Team id responders_list (List[str]): List of slack user ids. """ response = DynamoUtils.remove_responders(team_id, responders_list) if cls.has_responders(response): responders_complete = response.get("Attributes").get("responders") responders = cls.__build_responders_list(responders_complete) return responders return []
def get_responders_with_oncall( self, ) -> List[ SlackResponder]: # TODO - use interface instead of SlackResponder """ Returns a list that is a combination of responders and oncall. An oncall is treated a responder """ responders: List[SlackResponder] = RespondersList.list(self.team_id) oncall = DynamoUtils.get_oncall(self.team_id) oncall_responder: SlackResponder = SlackResponder(oncall) if oncall_responder.id is not None and not any( responder.id == oncall_responder.id for responder in responders): responders.append(oncall_responder) return responders
def handle_message(msg, _context): """ Handles and reacts to messages sent in slack channels """ team_id = msg.get("team") slack_access_token = DynamoUtils.get_slack_access_token(team_id) client = WebClient(token=slack_access_token) channel = msg["channel"] slack_events_handler = SlackEventsHandler(client, team_id) if msg.get("subtype") is None and "parca" in msg.get("text"): response = "You probably meant Lisandro, <@%s>! :tada:" % msg["user"] client.chat_postMessage(channel=channel, text=response) elif (msg.get("subtype") is None and re.search("^log:", msg.get("text"), re.IGNORECASE) is not None): text_to_log = msg.get("text").replace("LOG: ", "") slack_events_handler.log_comment(channel, text_to_log) return
def log_comment(self, incident_id: str, text: str) -> dict: """ Adds a comment to the ticket associated to the incident_id received in the arguments """ try: incident: Incident = DynamoUtils.get_incident( self.team_id, incident_id) if incident is None: return { "status": "failure", "failure_description": "Not ongoing incident in this channel, " "please run this command in an incident channel", } if incident.has_ticket(): issue_id = incident.ticket.get_link().split("browse/")[1] response = self.ticket_service.add_comment(issue_id, text) if (response.get("success") is not None and response.get("success") is True): return {"status": "ok"} else: return { "status": "failure", "failure_description": response.get("descriptive_error"), } else: return { "status": "failure", "failure_description": "No ticket associated to this incident", } except Exception as e: logger.error( f"could not log comment for team {self.team_id} - {e}") return { "status": "failure", "failure_description": "Could not log comment" }
def set_oncall(self, user_id): """Sets the oncall in the database""" return DynamoUtils.save_oncall(self.team_id, user_id)
def __init__(self, team_id): self.team_id = team_id self.zoom: Zoom = DynamoUtils.get_zoom_data(team_id) if not self.zoom.is_valid(): raise Exception("No call integration for team")
def get_ongoing_incidents(self) -> List[Incident]: """Returns all the incidents that are ongoing""" return DynamoUtils.get_ongoing_incidents(self.team_id)
def get_today_incidents(self) -> List[Incident]: """Returns all incidents from today that are ongoing""" return DynamoUtils.get_today_incidents(self.team_id)
def close_incident(self, incident_id): """Closes incident""" incident = Incident(self.team_id, incident_id) return DynamoUtils.update_incident_status(incident, IncidentStatus.CLOSED)
def handle_mention(message, _context): """ Handle bots mentions """ team_id = message.get("team") slack_access_token = DynamoUtils.get_slack_access_token(team_id) client = WebClient(token=slack_access_token) channel = message["channel"] slack_events_handler = SlackEventsHandler(client, team_id) if message.get("subtype") is None and "alive" in message.get("text"): message = "Yes, <@%s>! I am!" % message["user"] client.chat_postMessage(channel=channel, text=message) elif message.get("subtype") is None and "create ticket" in message.get( "text"): ticket: Integration = slack_events_handler.create_ticket() if ticket is not None: client.chat_postMessage(channel=channel, text="ticket created: " + ticket.get_link()) else: client.chat_postMessage( channel=channel, text="Ticket not created. Do you have a ticket integration?", ) elif message.get("subtype") is None and "create call" in message.get( "text"): call: Integration = slack_events_handler.create_call() if call is not None: client.chat_postMessage(channel=channel, text="call created: " + call.get_link()) else: client.chat_postMessage( channel=channel, text="Call not created. Do you have a call integration?", ) elif message.get("subtype") is None and "get call" in message.get("text"): call_link = slack_events_handler.get_call(channel) if bool(call_link): client.chat_postMessage(channel=channel, text="Call: " + call_link) else: client.chat_postMessage(channel=channel, text="No call available in this channel") elif message.get("subtype") is None and ( "new incident" in message.get("text") or "create incident" in message.get("text")): incident_name = sanitise_incident_name(message.get("text")) slack_events_handler.handle_new_incident_creation( channel, incident_name) elif message.get("subtype") is None and ( "who is oncall" in message.get("text") or "who’s oncall" in message.get("text")): user_id = slack_events_handler.get_oncall(team_id) if user_id is None: client.chat_postMessage( channel=channel, text= "An oncall hasn't been set yet.```/sereno set oncall <user>```", ) return client.chat_postMessage(channel=channel, text=f"<@{user_id}> is oncall") elif message.get("subtype") is None and ( "ongoing" in message.get("text") and ("incident" in message.get("text") or "incidents" in message.get("text"))): response = slack_events_handler.get_ongoing_incidents_from_today( team_id) client.chat_postMessage(channel=channel, blocks=response)
def command_handler(message, _context): """ Handles commands sent to slackbot """ team_id = message["team_id"] user_id = message["user_id"] command = message["text"] trigger_id = message.get("trigger_id") channel_id = message.get("channel_id") slack_access_token = DynamoUtils.get_slack_access_token(team_id) client = WebClient(token=slack_access_token) slack_commands_handler = SlackCommandsHandler(team_id) if "register" in command: client.chat_postEphemeral( channel=channel_id, user=user_id, blocks=slack_commands_handler.build_register_response(user_id).get( "blocks"), ) elif "responders" in command: if "add" in command: responders_list_users = re.findall(USER_ID_REGEX, command) responders_list_channels = re.findall(CHANNEL_ID_REGEX, command) responders_list = responders_list_users + responders_list_channels if len(responders_list) == 0: client.chat_postEphemeral( channel=channel_id, user=user_id, text="List of responders cannot be empty!", ) response = slack_commands_handler.add_responders(responders_list) client.chat_postEphemeral(channel=channel_id, user=user_id, text=response) elif "remove" in command: responders_list_users = re.findall(USER_ID_REGEX, command) responders_list_channels = re.findall(CHANNEL_ID_REGEX, command) responders_list = responders_list_users + responders_list_channels response = slack_commands_handler.remove_responders( responders_list) client.chat_postEphemeral(channel=channel_id, user=user_id, text=response) elif "list" in command: response = slack_commands_handler.list_responders() client.chat_postEphemeral(channel=channel_id, user=user_id, text=response) else: client.chat_postEphemeral( channel=channel_id, user=user_id, text= "Sorry I did not understand the command: options are add, remove, list", ) elif "oncall" in command: if "set" in command: user_matched = re.search(USER_ID_REGEX, command) if user_matched is not None: user_id_to_set = user_matched.group(1) slack_commands_handler.set_oncall(user_id_to_set) client.chat_postEphemeral( channel=channel_id, user=user_id, text="<@%s> set as oncall" % user_id_to_set, ) else: client.chat_postEphemeral( channel=channel_id, user=user_id, text="Sorry, wrong format. Do `/sereno set oncall <user>`", ) elif "help" in command: formatter = HelpFormatter() client.chat_postEphemeral(channel=channel_id, user=user_id, blocks=formatter.format().get("blocks")) elif "close incident" in command: slack_commands_handler.show_close_incident_modal( team_id, channel_id, trigger_id) else: client.chat_postEphemeral( channel=channel_id, user=user_id, text="Sorry I did not understand the command. " "Type `/sereno help` to see a list of available commands", )
def get_oncall(self, team_id): """Gets current oncall""" return DynamoUtils.get_oncall(team_id)