예제 #1
0
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}")
예제 #2
0
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)
예제 #3
0
 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
예제 #4
0
 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
예제 #5
0
 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
예제 #6
0
 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 ""
예제 #7
0
 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"))
예제 #8
0
 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)
예제 #9
0
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 ""
예제 #10
0
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"
예제 #11
0
 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
예제 #12
0
 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()
예제 #13
0
    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
예제 #14
0
 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 []
예제 #15
0
    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
예제 #16
0
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
예제 #17
0
    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"
            }
예제 #18
0
 def set_oncall(self, user_id):
     """Sets the oncall in the database"""
     return DynamoUtils.save_oncall(self.team_id, user_id)
예제 #19
0
 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")
예제 #20
0
 def get_ongoing_incidents(self) -> List[Incident]:
     """Returns all the incidents that are ongoing"""
     return DynamoUtils.get_ongoing_incidents(self.team_id)
예제 #21
0
 def get_today_incidents(self) -> List[Incident]:
     """Returns all incidents from today that are ongoing"""
     return DynamoUtils.get_today_incidents(self.team_id)
예제 #22
0
 def close_incident(self, incident_id):
     """Closes incident"""
     incident = Incident(self.team_id, incident_id)
     return DynamoUtils.update_incident_status(incident,
                                               IncidentStatus.CLOSED)
예제 #23
0
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)
예제 #24
0
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",
        )
예제 #25
0
 def get_oncall(self, team_id):
     """Gets current oncall"""
     return DynamoUtils.get_oncall(team_id)