def api_basecamp_webhook( request: HttpRequest, user_profile: UserProfile, payload: Dict[str, Any] = REQ(argument_type='body') ) -> HttpResponse: event = get_event_type(payload) if event not in SUPPORT_EVENTS: raise UnsupportedWebhookEventType(event) subject = get_project_name(payload) if event.startswith('document_'): body = get_document_body(event, payload) elif event.startswith('question_answer_'): body = get_questions_answer_body(event, payload) elif event.startswith('question_'): body = get_questions_body(event, payload) elif event.startswith('message_'): body = get_message_body(event, payload) elif event.startswith('todolist_'): body = get_todo_list_body(event, payload) elif event.startswith('todo_'): body = get_todo_body(event, payload) elif event.startswith('comment_'): body = get_comment_body(event, payload) else: raise UnsupportedWebhookEventType(event) check_send_webhook_message(request, user_profile, subject, body) return json_success()
def api_pagerduty_webhook( request: HttpRequest, user_profile: UserProfile, payload: Dict[str, Iterable[Dict[str, Any]]] = REQ(argument_type="body"), ) -> HttpResponse: for message in payload["messages"]: message_type = message.get("type") # If the message has no "type" key, then this payload came from a # Pagerduty Webhook V2. if message_type is None: break if message_type not in PAGER_DUTY_EVENT_NAMES: raise UnsupportedWebhookEventType(message_type) format_dict = build_pagerduty_formatdict(message) send_formated_pagerduty(request, user_profile, message_type, format_dict) for message in payload["messages"]: event = message.get("event") # If the message has no "event" key, then this payload came from a # Pagerduty Webhook V1. if event is None: break if event not in PAGER_DUTY_EVENT_NAMES_V2: raise UnsupportedWebhookEventType(event) format_dict = build_pagerduty_formatdict_v2(message) send_formated_pagerduty(request, user_profile, event, format_dict) return json_success()
def get_zulip_event_name( header_event: str, payload: Dict[str, Any], branches: Optional[str], ) -> Optional[str]: """ Usually, we return an event name that is a key in EVENT_FUNCTION_MAPPER. We return None for an event that we know we don't want to handle. """ if header_event == "pull_request": action = payload["action"] if action in ("opened", "synchronize", "reopened", "edited"): return "opened_or_update_pull_request" if action in ("assigned", "unassigned"): return "assigned_or_unassigned_pull_request" if action == "closed": return "closed_pull_request" if action == "review_requested": return "pull_request_review_requested" if action == "ready_for_review": return "pull_request_ready_for_review" if action in ("locked", "unlocked"): return "locked_or_unlocked_pull_request" if action in ("auto_merge_enabled", "auto_merge_disabled"): return "pull_request_auto_merge" if action in IGNORED_PULL_REQUEST_ACTIONS: return None elif header_event == "push": if is_commit_push_event(payload): if branches is not None: branch = get_branch_name_from_ref(payload["ref"]) if branches.find(branch) == -1: return None return "push_commits" else: return "push_tags" elif header_event == "check_run": if payload["check_run"]["status"] != "completed": return None return header_event elif header_event == "team": action = payload["action"] if action == "edited": return "team" if action in IGNORED_TEAM_ACTIONS: # no need to spam our logs, we just haven't implemented it yet return None else: # this means GH has actually added new actions since September 2020, # so it's a bit more cause for alarm raise UnsupportedWebhookEventType(f"unsupported team action {action}") elif header_event in list(EVENT_FUNCTION_MAPPER.keys()): return header_event elif header_event in IGNORED_EVENTS: return None complete_event = "{}:{}".format(header_event, payload.get("action", "???")) # nocoverage raise UnsupportedWebhookEventType(complete_event)
def api_pagerduty_webhook( request: HttpRequest, user_profile: UserProfile, payload: Dict[str, Any] = REQ(argument_type="body"), ) -> HttpResponse: messages = payload.get("messages") if messages is not None: for message in messages: message_type = message.get("type") # If the message has no "type" key, then this payload came from a # Pagerduty Webhook V2. if message_type is None: break if message_type not in PAGER_DUTY_EVENT_NAMES: raise UnsupportedWebhookEventType(message_type) format_dict = build_pagerduty_formatdict(message) send_formated_pagerduty(request, user_profile, message_type, format_dict) for message in messages: event = message.get("event") # If the message has no "event" key, then this payload came from a # Pagerduty Webhook V1. if event is None: break if event not in PAGER_DUTY_EVENT_NAMES_V2: raise UnsupportedWebhookEventType(event) format_dict = build_pagerduty_formatdict_v2(message) send_formated_pagerduty(request, user_profile, event, format_dict) else: if "event" in payload: # V3 has no "messages" field, and it has key "event" instead event = payload["event"] event_type = event.get("event_type") if event_type not in PAGER_DUTY_EVENT_NAMES_V3: raise UnsupportedWebhookEventType(event_type) format_dict = build_pagerduty_formatdict_v3(event) send_formated_pagerduty(request, user_profile, event_type, format_dict) return json_success()
def get_event(request: HttpRequest, payload: Dict[str, Any], branches: Optional[str]) -> Optional[str]: event = validate_extract_webhook_http_header(request, 'X_GITLAB_EVENT', 'GitLab') if event == "System Hook": # Convert the event name to a GitLab event title event_name = payload.get('event_name', payload.get('object_kind')) event = event_name.split("__")[0].replace("_", " ").title() event = f"{event} Hook" if event in [ 'Confidential Issue Hook', 'Issue Hook', 'Merge Request Hook', 'Wiki Page Hook' ]: action = payload['object_attributes'].get('action', 'open') event = f"{event} {action}" elif event in ['Confidential Note Hook', 'Note Hook']: action = payload['object_attributes'].get('noteable_type') event = f"{event} {action}" elif event == 'Push Hook': if branches is not None: branch = get_branch_name(payload) if branches.find(branch) == -1: return None if event in list(EVENT_FUNCTION_MAPPER.keys()): return event raise UnsupportedWebhookEventType(event)
def api_clubhouse_webhook( request: HttpRequest, user_profile: UserProfile, payload: Optional[Dict[str, Any]] = REQ(argument_type="body"), ) -> HttpResponse: # Clubhouse has a tendency to send empty POST requests to # third-party endpoints. It is unclear as to which event type # such requests correspond to. So, it is best to ignore such # requests for now. if payload is None: return json_success() event = get_event(payload) if event is None: return json_success() body_func = EVENT_BODY_FUNCTION_MAPPER.get(event) topic_func = get_topic_function_based_on_type(payload) if body_func is None or topic_func is None: raise UnsupportedWebhookEventType(event) topic = topic_func(payload) body = body_func(payload) if topic and body: check_send_webhook_message(request, user_profile, topic, body) return json_success()
def get_event(request: HttpRequest, payload: WildValue, branches: Optional[str]) -> Optional[str]: event = validate_extract_webhook_http_header(request, "X_GITLAB_EVENT", "GitLab") if event == "System Hook": # Convert the event name to a GitLab event title event_name = payload.get("event_name", payload["object_kind"]).tame(check_string) event = event_name.split("__")[0].replace("_", " ").title() event = f"{event} Hook" if event in [ "Confidential Issue Hook", "Issue Hook", "Merge Request Hook", "Wiki Page Hook" ]: action = payload["object_attributes"].get("action", "open").tame(check_string) event = f"{event} {action}" elif event in ["Confidential Note Hook", "Note Hook"]: action = payload["object_attributes"]["noteable_type"].tame( check_string) event = f"{event} {action}" elif event == "Push Hook": if branches is not None: branch = get_branch_name(payload) if branches.find(branch) == -1: return None if event in list(EVENT_FUNCTION_MAPPER.keys()): return event raise UnsupportedWebhookEventType(event)
def get_type(request: HttpRequest, payload: Dict[str, Any]) -> str: if payload.get('push'): return 'push' elif payload.get('fork'): return 'fork' elif payload.get('comment') and payload.get('commit'): return 'commit_comment' elif payload.get('commit_status'): return 'change_commit_status' elif payload.get('issue'): if payload.get('changes'): return "issue_updated" if payload.get('comment'): return 'issue_commented' return "issue_created" elif payload.get('pullrequest'): pull_request_template = 'pull_request_{}' # Note that we only need the HTTP header to determine pullrequest events. # We rely on the payload itself to determine the other ones. event_key = validate_extract_webhook_http_header( request, "X_EVENT_KEY", "BitBucket") assert event_key is not None action = re.match('pullrequest:(?P<action>.*)$', event_key) if action: action_group = action.group('action') if action_group in PULL_REQUEST_SUPPORTED_ACTIONS: return pull_request_template.format(action_group) else: event_key = validate_extract_webhook_http_header( request, "X_EVENT_KEY", "BitBucket") if event_key == 'repo:updated': return event_key raise UnsupportedWebhookEventType(event_key)
def api_jira_webhook( request: HttpRequest, user_profile: UserProfile, payload: Dict[str, Any] = REQ(argument_type="body"), ) -> HttpResponse: event = get_event_type(payload) if event in IGNORED_EVENTS: return json_success(request) if event is None: raise AnomalousWebhookPayload() if event is not None: content_func = JIRA_CONTENT_FUNCTION_MAPPER.get(event) if content_func is None: raise UnsupportedWebhookEventType(event) subject = get_issue_subject(payload) content: str = content_func(payload, user_profile) check_send_webhook_message( request, user_profile, subject, content, event, unquote_url_parameters=True ) return json_success(request)
def get_type(request: HttpRequest, payload: WildValue) -> str: if "push" in payload: return "push" elif "fork" in payload: return "fork" elif "comment" in payload and "commit" in payload: return "commit_comment" elif "commit_status" in payload: return "change_commit_status" elif "issue" in payload: if "changes" in payload: return "issue_updated" if "comment" in payload: return "issue_commented" return "issue_created" elif "pullrequest" in payload: pull_request_template = "pull_request_{}" # Note that we only need the HTTP header to determine pullrequest events. # We rely on the payload itself to determine the other ones. event_key = validate_extract_webhook_http_header( request, "X_EVENT_KEY", "BitBucket") assert event_key is not None action = re.match("pullrequest:(?P<action>.*)$", event_key) if action: action_group = action.group("action") if action_group in PULL_REQUEST_SUPPORTED_ACTIONS: return pull_request_template.format(action_group) else: event_key = validate_extract_webhook_http_header( request, "X_EVENT_KEY", "BitBucket") if event_key == "repo:updated": return event_key raise UnsupportedWebhookEventType(event_key)
def api_harbor_webhook( request: HttpRequest, user_profile: UserProfile, payload: Dict[str, Any] = REQ(argument_type="body"), ) -> HttpResponse: operator_username = "******".format(payload["operator"]) if operator_username != "auto": operator_profile = guess_zulip_user_from_harbor( operator_username, user_profile.realm) if operator_profile: operator_username = f"@**{operator_profile.full_name}**" # nocoverage event = payload["type"] topic = payload["event_data"]["repository"]["repo_full_name"] if event in IGNORED_EVENTS: return json_success(request) content_func = EVENT_FUNCTION_MAPPER.get(event) if content_func is None: raise UnsupportedWebhookEventType(event) content: str = content_func(payload, user_profile, operator_username) check_send_webhook_message(request, user_profile, topic, content, event, unquote_url_parameters=True) return json_success(request)
def api_bitbucket3_webhook( request: HttpRequest, user_profile: UserProfile, payload: WildValue = REQ(argument_type="body", converter=to_wild_value), branches: Optional[str] = REQ(default=None), user_specified_topic: Optional[str] = REQ("topic", default=None), ) -> HttpResponse: eventkey: Optional[str] if "eventKey" in payload: eventkey = payload["eventKey"].tame(check_string) else: eventkey = validate_extract_webhook_http_header( request, "X-Event-Key", "BitBucket", fatal=True ) assert eventkey is not None handler = EVENT_HANDLER_MAP.get(eventkey) if handler is None: raise UnsupportedWebhookEventType(eventkey) data = handler(payload, branches=branches, include_title=user_specified_topic) for element in data: check_send_webhook_message( request, user_profile, element["subject"], element["body"], eventkey, unquote_url_parameters=True, ) return json_success(request)
def get_event_type(event: Dict[str, Any]) -> str: event_type_match = re.match("check.(.*)", event["event"]) if event_type_match: event_type = event_type_match.group(1) if event_type in EVENT_TYPE_BODY_MAPPER: return event_type raise UnsupportedWebhookEventType(event["event"])
def api_github_webhook( request: HttpRequest, user_profile: UserProfile, payload: Dict[str, Any]=REQ(argument_type='body'), branches: Optional[str]=REQ(default=None), user_specified_topic: Optional[str]=REQ("topic", default=None)) -> HttpResponse: """ GitHub sends the event as an HTTP header. We have our own Zulip-specific concept of an event that often maps directly to the X_GITHUB_EVENT header's event, but we sometimes refine it based on the payload. """ header_event = validate_extract_webhook_http_header(request, "X_GITHUB_EVENT", "GitHub") if header_event is None: raise UnsupportedWebhookEventType("no header provided") event = get_zulip_event_name(header_event, payload, branches) if event is None: # This is nothing to worry about--get_event() returns None # for events that are valid but not yet handled by us. # See IGNORED_EVENTS, for example. return json_success() subject = get_subject_based_on_type(payload, event) body_function = EVENT_FUNCTION_MAPPER[event] helper = Helper( payload=payload, include_title=user_specified_topic is not None, ) body = body_function(helper) check_send_webhook_message(request, user_profile, subject, body) return json_success()
def api_raygun_webhook( request: HttpRequest, user_profile: UserProfile, payload: Dict[str, Any] = REQ(argument_type="body"), ) -> HttpResponse: # The payload contains 'event' key. This 'event' key has a value of either # 'error_notification' or 'error_activity'. 'error_notification' happens # when an error is caught in an application, where as 'error_activity' # happens when an action is being taken for the error itself # (ignored/resolved/assigned/etc.). event = payload["event"] # Because we wanted to create a message for all of the payloads, it is best # to handle them separately. This is because some payload keys don't exist # in the other event. if event == "error_notification": message = compose_notification_message(payload) elif event == "error_activity": message = compose_activity_message(payload) else: raise UnsupportedWebhookEventType(event) topic = "test" check_send_webhook_message(request, user_profile, topic, message, event) return json_success(request)
def compose_notification_message(payload: Dict[str, Any]) -> str: """Composes a message that contains information on the error :param payload: Raygun payload :return: Returns a response message """ # Get the event type of the error. This can be "NewErrorOccurred", # "ErrorReoccurred", "OneMinuteFollowUp", "FiveMinuteFollowUp", ..., # "HourlyFollowUp" for notification error. event_type = payload["eventType"] # "NewErrorOccurred" and "ErrorReoccurred" contain error instance # information, meaning that it has payload['error']['instance']. The other # event type (the follow ups) doesn't have this instance. # We now split this main function again into two functions. One is for # "NewErrorOccurred" and "ErrorReoccurred", and one is for the rest. Both # functions will return a text message that is formatted for the chat. if event_type == "NewErrorOccurred" or event_type == "ErrorReoccurred": return notification_message_error_occurred(payload) elif "FollowUp" in event_type: return notification_message_follow_up(payload) else: raise UnsupportedWebhookEventType(event_type)
def api_bitbucket3_webhook( request: HttpRequest, user_profile: UserProfile, payload: Dict[str, Any] = REQ(argument_type="body"), branches: Optional[str] = REQ(default=None), user_specified_topic: Optional[str] = REQ("topic", default=None), ) -> HttpResponse: try: eventkey = payload["eventKey"] except KeyError: eventkey = request.META["HTTP_X_EVENT_KEY"] handler = EVENT_HANDLER_MAP.get(eventkey) if handler is None: raise UnsupportedWebhookEventType(eventkey) if "branches" in signature(handler).parameters: data = handler(payload, branches) elif "include_title" in signature(handler).parameters: data = handler(payload, include_title=user_specified_topic) else: data = handler(payload) for element in data: check_send_webhook_message( request, user_profile, element["subject"], element["body"], eventkey, unquote_url_parameters=True, ) return json_success()
def api_sentry_webhook( request: HttpRequest, user_profile: UserProfile, payload: Dict[str, Any] = REQ(argument_type="body"), ) -> HttpResponse: data = payload.get("data", None) if data is None: data = transform_webhook_payload(payload) # We currently support two types of payloads: events and issues. if data: if "event" in data: subject, body = handle_event_payload(data["event"]) elif "issue" in data: subject, body = handle_issue_payload(payload["action"], data["issue"], payload["actor"]) else: raise UnsupportedWebhookEventType(str(list(data.keys()))) else: subject, body = handle_deprecated_payload(payload) check_send_webhook_message(request, user_profile, subject, body) return json_success()
def repo_push_branch_data(payload: Dict[str, Any], change: Dict[str, Any]) -> Dict[str, str]: event_type = change["type"] repo_name = payload["repository"]["name"] user_name = get_user_name(payload) branch_name = change["ref"]["displayId"] branch_head = change["toHash"] if event_type == "ADD": body = get_create_branch_event_message( user_name=user_name, url=None, branch_name=branch_name, ) elif event_type == "UPDATE": body = BRANCH_UPDATED_MESSAGE_TEMPLATE.format( user_name=user_name, branch_name=branch_name, head=branch_head, ) elif event_type == "DELETE": body = get_remove_branch_event_message(user_name, branch_name) else: message = "{}.{}".format(payload["eventKey"], event_type) # nocoverage raise UnsupportedWebhookEventType(message) subject = TOPIC_WITH_BRANCH_TEMPLATE.format(repo=repo_name, branch=branch_name) return {"subject": subject, "body": body}
def get_subject_and_body(payload: Mapping[str, Any], action_type: str) -> Optional[Tuple[str, str]]: if action_type in SUPPORTED_CARD_ACTIONS: return process_card_action(payload, action_type) if action_type in IGNORED_CARD_ACTIONS: return None if action_type in SUPPORTED_BOARD_ACTIONS: return process_board_action(payload, action_type) raise UnsupportedWebhookEventType(action_type)
def get_proper_action(payload: Mapping[str, Any], action_type: Optional[str]) -> Optional[str]: if action_type == 'updateBoard': data = get_action_data(payload) # we don't support events for when a board's background # is changed if data['old'].get('prefs', {}).get('background') is not None: return None elif data['old']['name']: return CHANGE_NAME raise UnsupportedWebhookEventType(action_type) return action_type
def get_proper_action(payload: Mapping[str, Any], action_type: Optional[str]) -> Optional[str]: if action_type == "updateBoard": data = get_action_data(payload) # we don't support events for when a board's background # is changed if data["old"].get("prefs", {}).get("background") is not None: return None elif data["old"]["name"]: return CHANGE_NAME raise UnsupportedWebhookEventType(action_type) return action_type
def handle_issue_payload( action: str, issue: Dict[str, Any], actor: Dict[str, Any] ) -> Tuple[str, str]: """Handle either an issue type event.""" subject = issue["title"] datetime = issue["lastSeen"].split(".")[0].replace("T", " ") if issue["assignedTo"]: if issue["assignedTo"]["type"] == "team": assignee = "team {}".format(issue["assignedTo"]["name"]) else: assignee = issue["assignedTo"]["name"] else: assignee = "No one" if action == "created": context = { "title": subject, "level": issue["level"], "datetime": datetime, "assignee": assignee, } body = ISSUE_CREATED_MESSAGE_TEMPLATE.format(**context) elif action == "resolved": context = { "title": subject, "actor": actor["name"], } body = ISSUE_RESOLVED_MESSAGE_TEMPLATE.format(**context) elif action == "assigned": context = { "title": subject, "assignee": assignee, "actor": actor["name"], } body = ISSUE_ASSIGNED_MESSAGE_TEMPLATE.format(**context) elif action == "ignored": context = { "title": subject, "actor": actor["name"], } body = ISSUE_IGNORED_MESSAGE_TEMPLATE.format(**context) else: raise UnsupportedWebhookEventType("unknown-issue-action type") return (subject, body)
def api_basecamp_webhook( request: HttpRequest, user_profile: UserProfile, payload: WildValue = REQ(argument_type="body", converter=to_wild_value), ) -> HttpResponse: event = get_event_type(payload) if event not in SUPPORT_EVENTS: raise UnsupportedWebhookEventType(event) subject = get_project_name(payload) if event.startswith("document_"): body = get_document_body(event, payload) event = "document" elif event.startswith("question_answer_"): body = get_questions_answer_body(event, payload) event = "question_answer" elif event.startswith("question_"): body = get_questions_body(event, payload) event = "question" elif event.startswith("message_"): body = get_message_body(event, payload) event = "message" elif event.startswith("todolist_"): body = get_todo_list_body(event, payload) event = "todolist" elif event.startswith("todo_"): body = get_todo_body(event, payload) event = "todo" elif event.startswith("comment_"): body = get_comment_body(event, payload) event = "comment" else: raise UnsupportedWebhookEventType(event) check_send_webhook_message(request, user_profile, subject, body, event) return json_success(request)
def api_pingdom_webhook( request: HttpRequest, user_profile: UserProfile, payload: Dict[str, Any] = REQ(argument_type="body"), ) -> HttpResponse: check_type = get_check_type(payload) if check_type in SUPPORTED_CHECK_TYPES: subject = get_subject_for_http_request(payload) body = get_body_for_http_request(payload) else: raise UnsupportedWebhookEventType(check_type) check_send_webhook_message(request, user_profile, subject, body) return json_success()
def get_body_for_http_request(payload: Dict[str, Any]) -> str: if payload["eventType"] == "Test": return get_setup_webhook_message("Radarr") elif payload["eventType"] == "Health": return get_body_for_health_check_event(payload) elif payload["eventType"] == "Rename": return get_body_for_movie_renamed_event(payload) elif payload["eventType"] == "Download" and "isUpgrade" in payload: if payload["isUpgrade"]: return get_body_for_movie_imported_upgrade_event(payload) else: return get_body_for_movie_imported_event(payload) elif payload["eventType"] == "Grab": return get_body_for_movie_grabbed_event(payload) else: raise UnsupportedWebhookEventType(payload["eventType"])
def repo_push_tag_data(payload: WildValue, change: WildValue) -> Dict[str, str]: event_type = change["type"].tame(check_string) repo_name = payload["repository"]["name"].tame(check_string) tag_name = change["ref"]["displayId"].tame(check_string) if event_type == "ADD": action = "pushed" elif event_type == "DELETE": action = "removed" else: message = "{}.{}".format(payload["eventKey"].tame(check_string), event_type) # nocoverage raise UnsupportedWebhookEventType(message) subject = BITBUCKET_TOPIC_TEMPLATE.format(repository_name=repo_name) body = get_push_tag_event_message(get_user_name(payload), tag_name, action=action) return {"subject": subject, "body": body}
def api_intercom_webhook( request: HttpRequest, user_profile: UserProfile, payload: Dict[str, Any] = REQ(argument_type="body"), ) -> HttpResponse: event_type = payload["topic"] if event_type == "ping": return json_success() handler = EVENT_TO_FUNCTION_MAPPER.get(event_type) if handler is None: raise UnsupportedWebhookEventType(event_type) topic, body = handler(payload) check_send_webhook_message(request, user_profile, topic, body) return json_success()
def get_body_for_http_request(payload: Dict[str, Any]) -> str: if payload["eventType"] == "Test": return get_setup_webhook_message("Lidarr") elif payload["eventType"] == "Grab": return get_body_for_album_grabbed_event(payload) elif payload["eventType"] == "Rename": return get_body_for_tracks_renamed_event(payload) elif payload["eventType"] == "Retag": return get_body_for_tracks_retagged_event(payload) elif payload["eventType"] == "Download" and "isUpgrade" in payload: if payload["isUpgrade"]: return get_body_for_tracks_imported_upgrade_event(payload) else: return get_body_for_tracks_imported_event(payload) else: raise UnsupportedWebhookEventType(payload["eventType"])
def api_statuspage_webhook( request: HttpRequest, user_profile: UserProfile, payload: Dict[str, Any] = REQ(argument_type="body"), ) -> HttpResponse: if "incident" in payload: topic = get_incident_topic(payload) body = get_incident_events_body(payload) elif "component" in payload: topic = get_component_topic(payload) body = get_components_update_body(payload) else: raise UnsupportedWebhookEventType("unknown-event") check_send_webhook_message(request, user_profile, topic, body) return json_success()