def api_gogs_webhook(request: HttpRequest, user_profile: UserProfile, payload: Dict[str, Any]=REQ(argument_type='body'), branches: Optional[str]=REQ(default=None)) -> HttpResponse: repo = payload['repository']['name'] event = validate_extract_webhook_http_header(request, 'X_GOGS_EVENT', 'Gogs') if event == 'push': branch = payload['ref'].replace('refs/heads/', '') if branches is not None and branches.find(branch) == -1: return json_success() body = format_push_event(payload) topic = SUBJECT_WITH_BRANCH_TEMPLATE.format( repo=repo, branch=branch ) elif event == 'create': body = format_new_branch_event(payload) topic = SUBJECT_WITH_BRANCH_TEMPLATE.format( repo=repo, branch=payload['ref'] ) elif event == 'pull_request': body = format_pull_request_event(payload) topic = SUBJECT_WITH_PR_OR_ISSUE_INFO_TEMPLATE.format( repo=repo, type='PR', id=payload['pull_request']['id'], title=payload['pull_request']['title'] ) else: raise UnexpectedWebhookEventType('Gogs', event) check_send_webhook_message(request, user_profile, topic, body) return json_success()
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 == 'jira:issue_created': subject = get_issue_subject(payload) content = handle_created_issue_event(payload) elif event == 'jira:issue_deleted': subject = get_issue_subject(payload) content = handle_deleted_issue_event(payload) elif event == 'jira:issue_updated': subject = get_issue_subject(payload) content = handle_updated_issue_event(payload, user_profile) elif event == 'comment_created': subject = get_issue_subject(payload) content = handle_updated_issue_event(payload, user_profile) elif event in IGNORED_EVENTS: return json_success() else: raise UnexpectedWebhookEventType('Jira', event) check_send_webhook_message(request, user_profile, subject, content, unquote_url_parameters=True) return json_success()
def report_error(request: HttpRequest, user_profile: UserProfile, message: str=REQ(), stacktrace: str=REQ(), ui_message: bool=REQ(validator=check_bool), user_agent: str=REQ(), href: str=REQ(), log: str=REQ(), more_info: Optional[Dict[str, Any]]=REQ(validator=check_dict([]), default=None) ) -> HttpResponse: """Accepts an error report and stores in a queue for processing. The actual error reports are later handled by do_report_error (below)""" if not settings.BROWSER_ERROR_REPORTING: return json_success() if more_info is None: more_info = {} js_source_map = get_js_source_map() if js_source_map: stacktrace = js_source_map.annotate_stacktrace(stacktrace) try: version = subprocess.check_output(["git", "log", "HEAD^..HEAD", "--oneline"], universal_newlines=True) # type: Optional[str] except Exception: version = None # Get the IP address of the request remote_ip = request.META.get('HTTP_X_REAL_IP') if remote_ip is None: remote_ip = request.META['REMOTE_ADDR'] # For the privacy of our users, we remove any actual text content # in draft_content (from drafts rendering exceptions). See the # comment on privacy_clean_markdown for more details. if more_info.get('draft_content'): more_info['draft_content'] = privacy_clean_markdown(more_info['draft_content']) if user_profile.is_authenticated: email = user_profile.delivery_email full_name = user_profile.full_name else: email = "*****@*****.**" full_name = "Anonymous User" queue_json_publish('error_reports', dict( type = "browser", report = dict( host = request.get_host().split(":")[0], ip_address = remote_ip, user_email = email, user_full_name = full_name, user_visible = ui_message, server_path = settings.DEPLOY_ROOT, version = version, user_agent = user_agent, href = href, message = message, stacktrace = stacktrace, log = log, more_info = more_info, ) )) return json_success()
def api_teamcity_webhook(request, user_profile, payload=REQ(argument_type='body'), stream=REQ(default='teamcity')): # type: (HttpRequest, UserProfile, Dict[str, Any], str) -> HttpResponse message = payload['build'] build_name = message['buildFullName'] build_url = message['buildStatusUrl'] changes_url = build_url + '&tab=buildChangesDiv' build_number = message['buildNumber'] build_result = message['buildResult'] build_result_delta = message['buildResultDelta'] build_status = message['buildStatus'] if build_result == 'success': if build_result_delta == 'fixed': status = 'has been fixed! :thumbsup:' else: status = 'was successful! :thumbsup:' elif build_result == 'failure': if build_result_delta == 'broken': status = 'is broken with status %s! :thumbsdown:' % (build_status,) else: status = 'is still broken with status %s! :thumbsdown:' % (build_status,) elif build_result == 'running': status = 'has started.' else: status = '(has no message specified for status %s)' % (build_status,) template = ( u'%s build %s %s\n' u'Details: [changes](%s), [build log](%s)') body = template % (build_name, build_number, status, changes_url, build_url) topic = build_name # Check if this is a personal build, and if so try to private message the user who triggered it. if get_teamcity_property_value(message['teamcityProperties'], 'env.BUILD_IS_PERSONAL') == 'true': # The triggeredBy field gives us the teamcity user full name, and the "teamcity.build.triggeredBy.username" # property gives us the teamcity username. Let's try finding the user email from both. teamcity_fullname = message['triggeredBy'].split(';')[0] teamcity_user = guess_zulip_user_from_teamcity(teamcity_fullname, user_profile.realm) if teamcity_user is None: teamcity_shortname = get_teamcity_property_value(message['teamcityProperties'], 'teamcity.build.triggeredBy.username') if teamcity_shortname is not None: teamcity_user = guess_zulip_user_from_teamcity(teamcity_shortname, user_profile.realm) if teamcity_user is None: # We can't figure out who started this build - there's nothing we can do here. logging.info("Teamcity webhook couldn't find a matching Zulip user for Teamcity user '%s' or '%s'" % ( teamcity_fullname, teamcity_shortname)) return json_success() body = "Your personal build of " + body check_send_message(user_profile, request.client, 'private', [teamcity_user.email], topic, body) return json_success() check_send_message(user_profile, request.client, 'stream', [stream], topic, body) return json_success()
def json_report_error(request, user_profile, message=REQ(), stacktrace=REQ(), ui_message=REQ(validator=check_bool), user_agent=REQ(), href=REQ(), log=REQ(), more_info=REQ(validator=check_dict([]), default=None)): # type: (HttpRequest, UserProfile, text_type, text_type, bool, text_type, text_type, text_type, Dict[str, Any]) -> HttpResponse if not settings.ERROR_REPORTING: return json_success() if js_source_map: stacktrace = js_source_map.annotate_stacktrace(stacktrace) try: version = subprocess.check_output(["git", "log", "HEAD^..HEAD", "--oneline"], universal_newlines=True) except Exception: version = None queue_json_publish('error_reports', dict( type = "browser", report = dict( user_email = user_profile.email, user_full_name = user_profile.full_name, user_visible = ui_message, server_path = settings.DEPLOY_ROOT, version = version, user_agent = user_agent, href = href, message = message, stacktrace = stacktrace, log = log, more_info = more_info, ) ), lambda x: None) return json_success()
def api_beanstalk_webhook(request, user_profile, payload=REQ(validator=check_dict([])), branches=REQ(default=None)): # type: (HttpRequest, UserProfile, Dict[str, Any], Optional[Text]) -> HttpResponse # Beanstalk supports both SVN and git repositories # We distinguish between the two by checking for a # 'uri' key that is only present for git repos git_repo = 'uri' in payload if git_repo: if branches is not None and branches.find(payload['branch']) == -1: return json_success() # To get a linkable url, for commit in payload['commits']: commit['author'] = {'username': commit['author']['name']} subject, content = build_message_from_gitlog(user_profile, payload['repository']['name'], payload['ref'], payload['commits'], payload['before'], payload['after'], payload['repository']['url'], payload['pusher_name']) else: author = payload.get('author_full_name') url = payload.get('changeset_url') revision = payload.get('revision') (short_commit_msg, _, _) = payload.get('message').partition("\n") subject = "svn r%s" % (revision,) content = "%s pushed [revision %s](%s):\n\n> %s" % (author, revision, url, short_commit_msg) check_send_message(user_profile, get_client("ZulipBeanstalkWebhook"), "stream", ["commits"], subject, content) return json_success()
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: logging.warning("Basecamp {} event is not supported".format(event)) return json_success() 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: logging.warning("Basecamp handling of {} event is not implemented".format(event)) return json_success() check_send_webhook_message(request, user_profile, subject, body) return json_success()
def api_freshdesk_webhook(request: HttpRequest, user_profile: UserProfile, payload: Dict[str, Any]=REQ(argument_type='body')) -> HttpResponse: ticket_data = payload["freshdesk_webhook"] required_keys = [ "triggered_event", "ticket_id", "ticket_url", "ticket_type", "ticket_subject", "ticket_description", "ticket_status", "ticket_priority", "requester_name", "requester_email", ] for key in required_keys: if ticket_data.get(key) is None: logging.warning("Freshdesk webhook error. Payload was:") logging.warning(request.body) return json_error(_("Missing key %s in JSON") % (key,)) ticket = TicketDict(ticket_data) subject = "#%s: %s" % (ticket.id, ticket.subject) event_info = parse_freshdesk_event(ticket.triggered_event) if event_info[1] == "created": content = format_freshdesk_ticket_creation_message(ticket) elif event_info[0] == "note_type": content = format_freshdesk_note_message(ticket, event_info) elif event_info[0] in ("status", "priority"): content = format_freshdesk_property_change_message(ticket, event_info) else: # Not an event we know handle; do nothing. return json_success() check_send_webhook_message(request, user_profile, subject, content) return json_success()
def api_bitbucket_webhook(request, user_profile, payload=REQ(validator=check_dict([])), stream=REQ(default='commits'), branches=REQ(default=None)): # type: (HttpRequest, UserProfile, Mapping[Text, Any], Text, Optional[Text]) -> HttpResponse repository = payload['repository'] commits = [ { 'name': payload.get('user'), 'sha': commit.get('raw_node'), 'message': commit.get('message'), 'url': u'{}{}commits/{}'.format( payload.get('canon_url'), repository.get('absolute_url'), commit.get('raw_node')) } for commit in payload['commits'] ] if len(commits) == 0: # Bitbucket doesn't give us enough information to really give # a useful message :/ subject = repository['name'] content = (u"%s [force pushed](%s)" % (payload['user'], payload['canon_url'] + repository['absolute_url'])) else: branch = payload['commits'][-1]['branch'] if branches is not None and branches.find(branch) == -1: return json_success() content = get_push_commits_event_message(payload['user'], None, branch, commits) subject = SUBJECT_WITH_BRANCH_TEMPLATE.format(repo=repository['name'], branch=branch) check_send_message(user_profile, get_client("ZulipBitBucketWebhook"), "stream", [stream], subject, content) return json_success()
def api_gogs_webhook(request: HttpRequest, user_profile: UserProfile, payload: Dict[str, Any]=REQ(argument_type='body'), stream: Text=REQ(default='commits'), branches: Optional[Text]=REQ(default=None)) -> HttpResponse: repo = payload['repository']['name'] event = request.META['HTTP_X_GOGS_EVENT'] if event == 'push': branch = payload['ref'].replace('refs/heads/', '') if branches is not None and branches.find(branch) == -1: return json_success() body = format_push_event(payload) topic = SUBJECT_WITH_BRANCH_TEMPLATE.format( repo=repo, branch=branch ) elif event == 'create': body = format_new_branch_event(payload) topic = SUBJECT_WITH_BRANCH_TEMPLATE.format( repo=repo, branch=payload['ref'] ) elif event == 'pull_request': body = format_pull_request_event(payload) topic = SUBJECT_WITH_PR_OR_ISSUE_INFO_TEMPLATE.format( repo=repo, type='PR', id=payload['pull_request']['id'], title=payload['pull_request']['title'] ) else: return json_error(_('Invalid event "{}" in request headers').format(event)) check_send_stream_message(user_profile, request.client, stream, topic, body) return json_success()
def api_bitbucket2_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: type = get_type(request, payload) if type == 'push': # ignore push events with no changes if not payload['push']['changes']: return json_success() branch = get_branch_name_for_push_event(payload) if branch and branches: if branches.find(branch) == -1: return json_success() subject = get_subject_based_on_type(payload, type) body_function = get_body_based_on_type(type) if 'include_title' in signature(body_function).parameters: body = body_function( payload, include_title=user_specified_topic is not None ) else: body = body_function(payload) if type != 'push': check_send_webhook_message(request, user_profile, subject, body) else: for b, s in zip(body, subject): check_send_webhook_message(request, user_profile, s, b) return json_success()
def api_travis_webhook(request: HttpRequest, user_profile: UserProfile, ignore_pull_requests: bool = REQ(validator=check_bool, default=True), message: Dict[str, str]=REQ('payload', validator=check_dict([ ('author_name', check_string), ('status_message', check_string), ('compare_url', check_string), ]))) -> HttpResponse: message_status = message['status_message'] if ignore_pull_requests and message['type'] == 'pull_request': return json_success() if message_status in GOOD_STATUSES: emoji = ':thumbs_up:' elif message_status in BAD_STATUSES: emoji = ':thumbs_down:' else: emoji = "(No emoji specified for status '{}'.)".format(message_status) body = MESSAGE_TEMPLATE.format( message['author_name'], message_status, emoji, message['compare_url'], message['build_url'] ) topic = 'builds' check_send_webhook_message(request, user_profile, topic, body) return json_success()
def api_jira_webhook(request, user_profile, payload=REQ(argument_type='body'), stream=REQ(default='jira')): # type: (HttpRequest, UserProfile, Dict[str, Any], Text) -> HttpResponse event = get_event_type(payload) if event == 'jira:issue_created': subject = get_issue_subject(payload) content = handle_created_issue_event(payload) elif event == 'jira:issue_deleted': subject = get_issue_subject(payload) content = handle_deleted_issue_event(payload) elif event == 'jira:issue_updated': subject = get_issue_subject(payload) content = handle_updated_issue_event(payload, user_profile) elif event in IGNORED_EVENTS: return json_success() else: if event is None: if not settings.TEST_SUITE: message = u"Got JIRA event with None event type: {}".format(payload) logging.warning(message) return json_error(_("Event is not given by JIRA")) else: if not settings.TEST_SUITE: logging.warning("Got JIRA event type we don't support: {}".format(event)) return json_success() check_send_stream_message(user_profile, request.client, stream, subject, content) return json_success()
def api_zapier_webhook(request: HttpRequest, user_profile: UserProfile, payload: Dict[str, Any]=REQ(argument_type='body')) -> HttpResponse: if payload.get('type') == 'auth': # The bot's details are used by our Zapier app to format a connection # label for users to be able to distinguish between different Zulip # bots and API keys in their UI return json_success({ 'full_name': user_profile.full_name, 'email': user_profile.email, 'id': user_profile.id }) topic = payload.get('topic') content = payload.get('content') if topic is None: topic = payload.get('subject') # Backwards-compatibility if topic is None: return json_error(_("Topic can't be empty")) if content is None: return json_error(_("Content can't be empty")) check_send_webhook_message(request, user_profile, topic, content) return json_success()
def api_travis_webhook(request, user_profile, client, stream=REQ(default='travis'), topic=REQ(default=None), ignore_pull_requests=REQ(validator=check_bool, default=True), message=REQ('payload', validator=check_dict([ ('author_name', check_string), ('status_message', check_string), ('compare_url', check_string), ]))): # type: (HttpRequest, UserProfile, Client, str, str, str, Dict[str, str]) -> HttpResponse message_status = message['status_message'] if ignore_pull_requests and message['type'] == 'pull_request': return json_success() if message_status in GOOD_STATUSES: emoji = ':thumbsup:' elif message_status in BAD_STATUSES: emoji = ':thumbsdown:' else: emoji = "(No emoji specified for status '{}'.)".format(message_status) body = MESSAGE_TEMPLATE.format( message['author_name'], message_status, emoji, message['compare_url'], message['build_url'] ) check_send_message(user_profile, client, 'stream', [stream], topic, body) return json_success()
def api_bitbucket_webhook(request: HttpRequest, user_profile: UserProfile, payload: Mapping[str, Any]=REQ(validator=check_dict([])), branches: Optional[str]=REQ(default=None)) -> HttpResponse: repository = payload['repository'] commits = [ { 'name': payload.get('user'), 'sha': commit.get('raw_node'), 'message': commit.get('message'), 'url': u'{}{}commits/{}'.format( payload.get('canon_url'), repository.get('absolute_url'), commit.get('raw_node')) } for commit in payload['commits'] ] if len(commits) == 0: # Bitbucket doesn't give us enough information to really give # a useful message :/ subject = repository['name'] content = (u"%s [force pushed](%s)" % (payload['user'], payload['canon_url'] + repository['absolute_url'])) else: branch = payload['commits'][-1]['branch'] if branches is not None and branches.find(branch) == -1: return json_success() content = get_push_commits_event_message(payload['user'], None, branch, commits) subject = TOPIC_WITH_BRANCH_TEMPLATE.format(repo=repository['name'], branch=branch) check_send_webhook_message(request, user_profile, subject, content, unquote_url_parameters=True) return json_success()
def api_stripe_webhook(request: HttpRequest, user_profile: UserProfile, payload: Dict[str, Any]=REQ(argument_type='body'), stream: str=REQ(default='test')) -> HttpResponse: try: topic, body = topic_and_body(payload) except SuppressedEvent: # nocoverage return json_success() check_send_webhook_message(request, user_profile, topic, body) return json_success()
def remove_members_from_group_backend(request: HttpRequest, user_profile: UserProfile, user_group_id: int, members: List[int]) -> HttpResponse: if not members: return json_success() user_profiles = user_ids_to_users(members, user_profile.realm) user_group = access_user_group_by_id(user_group_id, user_profile.realm) remove_members_from_user_group(user_group, user_profiles) return json_success()
def update_pointer_backend(request: HttpRequest, user_profile: UserProfile, pointer: int=REQ(converter=to_non_negative_int)) -> HttpResponse: if pointer <= user_profile.pointer: return json_success() if get_usermessage_by_message_id(user_profile, pointer) is None: raise JsonableError(_("Invalid message ID")) request._log_data["extra"] = "[%s]" % (pointer,) update_flags = (request.client.name.lower() in ['android', "zulipandroid"]) do_update_pointer(user_profile, request.client, pointer, update_flags=update_flags) return json_success()
def remove_members_from_group_backend(request: HttpRequest, user_profile: UserProfile, user_group_id: int, members: List[int]) -> HttpResponse: if not members: return json_success() user_profiles = user_ids_to_users(members, user_profile.realm) user_group = access_user_group_by_id(user_group_id, user_profile) group_member_ids = get_user_group_members(user_group) for member in members: if (member not in group_member_ids): raise JsonableError(_("There is no member '%s' in this user group" % (member,))) remove_members_from_user_group(user_group, user_profiles) return json_success()
def get_web_public_topics_backend(request: HttpRequest, stream_id: int) -> HttpResponse: try: stream = get_stream_by_id(stream_id) except JsonableError: return json_success(dict(topics=[])) if not stream.is_web_public: return json_success(dict(topics=[])) recipient = get_stream_recipient(stream.id) result = get_topic_history_for_web_public_stream(recipient=recipient) return json_success(dict(topics=result))
def api_trello_webhook(request, user_profile, payload=REQ(argument_type='body'), stream=REQ(default='trello')): # type: (HttpRequest, UserProfile, Mapping[str, Any], Text) -> HttpResponse payload = ujson.loads(request.body) action_type = payload['action'].get('type') try: message = get_subject_and_body(payload, action_type) if message is None: return json_success() else: subject, body = message except UnsupportedAction: return json_error(_('Unsupported action_type: {action_type}'.format(action_type=action_type))) check_send_message(user_profile, request.client, 'stream', [stream], subject, body) return json_success()
def update_pointer_backend(request, user_profile, pointer=REQ(converter=to_non_negative_int)): # type: (HttpRequest, UserProfile, int) -> HttpResponse if pointer <= user_profile.pointer: return json_success() try: UserMessage.objects.get(user_profile=user_profile, message__id=pointer) except UserMessage.DoesNotExist: raise JsonableError(_("Invalid message ID")) request._log_data["extra"] = "[%s]" % (pointer,) update_flags = request.client.name.lower() in ["android", "zulipandroid"] do_update_pointer(user_profile, pointer, update_flags=update_flags) return json_success()
def add_members_to_group_backend(request: HttpRequest, user_profile: UserProfile, user_group_id: int, members: List[int]) -> HttpResponse: if not members: return json_success() user_group = access_user_group_by_id(user_group_id, user_profile) user_profiles = user_ids_to_users(members, user_profile.realm) existing_member_ids = set(get_memberships_of_users(user_group, user_profiles)) for user_profile in user_profiles: if user_profile.id in existing_member_ids: raise JsonableError(_("User %s is already a member of this group" % (user_profile.id,))) bulk_add_members_to_user_group(user_group, user_profiles) return json_success()
def remote_server_notify_push(request: HttpRequest, entity: Union[UserProfile, RemoteZulipServer], payload: Dict[str, Any]=REQ(argument_type='body')) -> HttpResponse: validate_entity(entity) server = cast(RemoteZulipServer, entity) user_id = payload['user_id'] gcm_payload = payload['gcm_payload'] apns_payload = payload['apns_payload'] android_devices = list(RemotePushDeviceToken.objects.filter( user_id=user_id, kind=RemotePushDeviceToken.GCM, server=server )) apple_devices = list(RemotePushDeviceToken.objects.filter( user_id=user_id, kind=RemotePushDeviceToken.APNS, server=server )) if android_devices: send_android_push_notification(android_devices, gcm_payload, remote=True) if apple_devices: send_apple_push_notification(user_id, apple_devices, apns_payload, remote=True) return json_success()
def json_fetch_api_key(request, user_profile, password=REQ(default='')): # type: (HttpRequest, UserProfile, str) -> HttpResponse if password_auth_enabled(user_profile.realm): if not authenticate(username=user_profile.email, password=password, realm_subdomain=get_subdomain(request)): return json_error(_("Your username or password is incorrect.")) return json_success({"api_key": user_profile.api_key})
def report_send_times(request: HttpRequest, user_profile: UserProfile, time: int=REQ(converter=to_non_negative_int), received: int=REQ(converter=to_non_negative_int, default=-1), displayed: int=REQ(converter=to_non_negative_int, default=-1), locally_echoed: bool=REQ(validator=check_bool, default=False), rendered_content_disparity: bool=REQ(validator=check_bool, default=False)) -> HttpResponse: received_str = "(unknown)" if received > 0: received_str = str(received) displayed_str = "(unknown)" if displayed > 0: displayed_str = str(displayed) request._log_data["extra"] = "[%sms/%sms/%sms/echo:%s/diff:%s]" \ % (time, received_str, displayed_str, locally_echoed, rendered_content_disparity) base_key = statsd_key(user_profile.realm.string_id, clean_periods=True) statsd.timing("endtoend.send_time.%s" % (base_key,), time) if received > 0: statsd.timing("endtoend.receive_time.%s" % (base_key,), received) if displayed > 0: statsd.timing("endtoend.displayed_time.%s" % (base_key,), displayed) if locally_echoed: statsd.incr('locally_echoed') if rendered_content_disparity: statsd.incr('render_disparity') return json_success()
def api_yo_app_webhook(request, user_profile, email=REQ(default=None), username=REQ(default='Yo Bot'), topic=REQ(default='None'), user_ip=REQ(default='None')): body = ('Yo from %s') % (username,) check_send_message(user_profile, get_client('ZulipYoWebhook'), 'private', [email], topic, body) return json_success()
def api_stash_webhook(request, user_profile, payload=REQ(argument_type='body'), stream=REQ(default='commits')): # type: (HttpRequest, UserProfile, Dict[str, Any], text_type) -> HttpResponse # We don't get who did the push, or we'd try to report that. try: repo_name = payload["repository"]["name"] project_name = payload["repository"]["project"]["name"] branch_name = payload["refChanges"][0]["refId"].split("/")[-1] commit_entries = payload["changesets"]["values"] commits = [(entry["toCommit"]["displayId"], entry["toCommit"]["message"].split("\n")[0]) for \ entry in commit_entries] head_ref = commit_entries[-1]["toCommit"]["displayId"] except KeyError as e: return json_error(_("Missing key %s in JSON") % (e.message,)) subject = "%s/%s: %s" % (project_name, repo_name, branch_name) content = "`%s` was pushed to **%s** in **%s/%s** with:\n\n" % ( head_ref, branch_name, project_name, repo_name) content += "\n".join("* `%s`: %s" % ( commit[0], commit[1]) for commit in commits) check_send_message(user_profile, get_client("ZulipStashWebhook"), "stream", [stream], subject, content) return json_success()
def api_fetch_api_key(request, username=REQ(), password=REQ()): # type: (HttpRequest, str, str) -> HttpResponse return_data = {} # type: Dict[str, bool] if username == "google-oauth2-token": user_profile = authenticate(google_oauth2_token=password, realm_subdomain=get_subdomain(request), return_data=return_data) else: user_profile = authenticate(username=username, password=password, realm_subdomain=get_subdomain(request), return_data=return_data) if return_data.get("inactive_user") == True: return json_error(_("Your account has been disabled."), data={"reason": "user disable"}, status=403) if return_data.get("inactive_realm") == True: return json_error(_("Your realm has been deactivated."), data={"reason": "realm deactivated"}, status=403) if return_data.get("password_auth_disabled") == True: return json_error(_("Password auth is disabled in your team."), data={"reason": "password auth disabled"}, status=403) if user_profile is None: if return_data.get("valid_attestation") == True: # We can leak that the user is unregistered iff they present a valid authentication string for the user. return json_error(_("This user is not registered; do so from a browser."), data={"reason": "unregistered"}, status=403) return json_error(_("Your username or password is incorrect."), data={"reason": "incorrect_creds"}, status=403) return json_success({"api_key": user_profile.api_key, "email": user_profile.email})
def patch_bot_backend(request, user_profile, email, full_name=REQ(default=None), bot_owner=REQ(default=None), default_sending_stream=REQ(default=None), default_events_register_stream=REQ(default=None), default_all_public_streams=REQ(default=None, validator=check_bool)): # type: (HttpRequest, UserProfile, Text, Optional[Text], Optional[Text], Optional[Text], Optional[Text], Optional[bool]) -> HttpResponse try: bot = get_user_profile_by_email(email) except: return json_error(_('No such user')) if not user_profile.can_admin_user(bot): return json_error(_('Insufficient permission')) if full_name is not None: check_change_full_name(bot, full_name) if bot_owner is not None: owner = get_user_profile_by_email(bot_owner) do_change_bot_owner(bot, owner) if default_sending_stream is not None: if default_sending_stream == "": stream = None # type: Optional[Stream] else: (stream, recipient, sub) = access_stream_by_name(user_profile, default_sending_stream) do_change_default_sending_stream(bot, stream) if default_events_register_stream is not None: if default_events_register_stream == "": stream = None else: (stream, recipient, sub) = access_stream_by_name(user_profile, default_events_register_stream) do_change_default_events_register_stream(bot, stream) if default_all_public_streams is not None: do_change_default_all_public_streams(bot, default_all_public_streams) if len(request.FILES) == 0: pass elif len(request.FILES) == 1: user_file = list(request.FILES.values())[0] upload_avatar_image(user_file, user_profile, bot.email) avatar_source = UserProfile.AVATAR_FROM_USER do_change_avatar_fields(bot, avatar_source) else: return json_error(_("You may only upload one file at a time")) json_result = dict( full_name=bot.full_name, avatar_url=avatar_url(bot), default_sending_stream=get_stream_name(bot.default_sending_stream), default_events_register_stream=get_stream_name( bot.default_events_register_stream), default_all_public_streams=bot.default_all_public_streams, ) # Don't include the bot owner in case it is not set. # Default bots have no owner. if bot.bot_owner is not None: json_result['bot_owner'] = bot.bot_owner.email return json_success(json_result)
def confirmation_key(request: HttpRequest) -> HttpResponse: return json_success(request.session.get('confirmation_key'))
def json_get_stream_id(request, user_profile, stream_name=REQ('stream')): # type: (HttpRequest, UserProfile, Text) -> HttpResponse (stream, recipient, sub) = access_stream_by_name(user_profile, stream_name) return json_success({'stream_id': stream.id})
def remove_default_stream(request, user_profile, stream_name=REQ()): # type: (HttpRequest, UserProfile, Text) -> HttpResponse (stream, recipient, sub) = access_stream_by_name(user_profile, stream_name) do_remove_default_stream(stream) return json_success()
def api_stripe_webhook(request: HttpRequest, user_profile: UserProfile, payload: Dict[str, Any]=REQ(argument_type='body'), stream: str=REQ(default='test')) -> HttpResponse: body = None event_type = payload["type"] data_object = payload["data"]["object"] if event_type.startswith('charge'): charge_url = "https://dashboard.stripe.com/payments/{}" amount_string = amount(payload["data"]["object"]["amount"], payload["data"]["object"]["currency"]) if event_type.startswith('charge.dispute'): charge_id = data_object["charge"] link = charge_url.format(charge_id) body_template = "A charge dispute for **{amount}** has been {rest}.\n"\ "The charge in dispute {verb} **[{charge}]({link})**." if event_type == "charge.dispute.closed": rest = "closed as **{}**".format(data_object['status']) verb = 'was' else: rest = "created" verb = 'is' body = body_template.format(amount=amount_string, rest=rest, verb=verb, charge=charge_id, link=link) else: charge_id = data_object["id"] link = charge_url.format(charge_id) body_template = "A charge with id **[{charge_id}]({link})** for **{amount}** has {verb}." if event_type == "charge.failed": verb = "failed" else: verb = "succeeded" body = body_template.format(charge_id=charge_id, link=link, amount=amount_string, verb=verb) topic = "Charge {}".format(charge_id) elif event_type.startswith('customer'): object_id = data_object["id"] if event_type.startswith('customer.subscription'): link = "https://dashboard.stripe.com/subscriptions/{}".format(object_id) if event_type == "customer.subscription.created": amount_string = amount(data_object["plan"]["amount"], data_object["plan"]["currency"]) body_template = "A new customer subscription for **{amount}** " \ "every **{interval}** has been created.\n" \ "The subscription has id **[{id}]({link})**." body = body_template.format( amount=amount_string, interval=data_object['plan']['interval'], id=object_id, link=link ) elif event_type == "customer.subscription.deleted": body_template = "The customer subscription with id **[{id}]({link})** was deleted." body = body_template.format(id=object_id, link=link) else: # customer.subscription.trial_will_end DAY = 60 * 60 * 24 # seconds in a day # days_left should always be three according to # https://stripe.com/docs/api/python#event_types, but do the # computation just to be safe. days_left = int((data_object["trial_end"] - time.time() + DAY//2) // DAY) body_template = ("The customer subscription trial with id" " **[{id}]({link})** will end in {days} days.") body = body_template.format(id=object_id, link=link, days=days_left) else: link = "https://dashboard.stripe.com/customers/{}".format(object_id) body_template = "{beginning} customer with id **[{id}]({link})** {rest}." if event_type == "customer.created": beginning = "A new" if data_object["email"] is None: rest = "has been created" else: rest = "and email **{}** has been created".format(data_object['email']) else: beginning = "A" rest = "has been deleted" body = body_template.format(beginning=beginning, id=object_id, link=link, rest=rest) topic = "Customer {}".format(object_id) elif event_type == "invoice.payment_failed": object_id = data_object['id'] link = "https://dashboard.stripe.com/invoices/{}".format(object_id) amount_string = amount(data_object["amount_due"], data_object["currency"]) body_template = "An invoice payment on invoice with id **[{id}]({link})** and "\ "with **{amount}** due has failed." body = body_template.format(id=object_id, amount=amount_string, link=link) topic = "Invoice {}".format(object_id) elif event_type.startswith('order'): object_id = data_object['id'] link = "https://dashboard.stripe.com/orders/{}".format(object_id) amount_string = amount(data_object["amount"], data_object["currency"]) body_template = "{beginning} order with id **[{id}]({link})** for **{amount}** has {end}." if event_type == "order.payment_failed": beginning = "An order payment on" end = "failed" elif event_type == "order.payment_succeeded": beginning = "An order payment on" end = "succeeded" else: beginning = "The" end = "been updated" body = body_template.format(beginning=beginning, id=object_id, link=link, amount=amount_string, end=end) topic = "Order {}".format(object_id) elif event_type.startswith('transfer'): object_id = data_object['id'] link = "https://dashboard.stripe.com/transfers/{}".format(object_id) amount_string = amount(data_object["amount"], data_object["currency"]) body_template = "The transfer with description **{description}** and id **[{id}]({link})** " \ "for amount **{amount}** has {end}." if event_type == "transfer.failed": end = 'failed' else: end = "been paid" body = body_template.format( description=data_object['description'], id=object_id, link=link, amount=amount_string, end=end ) topic = "Transfer {}".format(object_id) if body is None: return json_error(_("We don't support {} event".format(event_type))) check_send_webhook_message(request, user_profile, topic, body) return json_success()
def remove_android_reg_id(request: HttpRequest, user_profile: UserProfile, token: bytes=REQ()) -> HttpResponse: validate_token(token, PushDeviceToken.GCM) remove_push_device_token(user_profile, token, PushDeviceToken.GCM) return json_success()
def get_user_invites(request: HttpRequest, user_profile: UserProfile) -> HttpResponse: all_users = do_get_invites_controlled_by_user(user_profile) return json_success(request, data={"invites": all_users})
def get_statuses_for_realm(request: HttpRequest, user_profile: UserProfile) -> HttpResponse: return json_success(get_status_list(user_profile))
def list_realm_domains(request: HttpRequest, user_profile: UserProfile) -> HttpResponse: domains = get_realm_domains(user_profile.realm) return json_success(request, data={"domains": domains})
def _deactivate_user_profile_backend(request: HttpRequest, user_profile: UserProfile, target: UserProfile) -> HttpResponse: do_deactivate_user(target, acting_user=user_profile) return json_success()
def patch_bot_backend( request: HttpRequest, user_profile: UserProfile, bot_id: int, full_name: Optional[str]=REQ(default=None), bot_owner_id: Optional[int]=REQ(validator=check_int, default=None), config_data: Optional[Dict[str, str]]=REQ(default=None, validator=check_dict(value_validator=check_string)), service_payload_url: Optional[str]=REQ(validator=check_url, default=None), service_interface: Optional[int]=REQ(validator=check_int, default=1), default_sending_stream: Optional[str]=REQ(default=None), default_events_register_stream: Optional[str]=REQ(default=None), default_all_public_streams: Optional[bool]=REQ(default=None, validator=check_bool), ) -> HttpResponse: bot = access_bot_by_id(user_profile, bot_id) if full_name is not None: check_change_bot_full_name(bot, full_name, user_profile) if bot_owner_id is not None: try: owner = get_user_profile_by_id_in_realm(bot_owner_id, user_profile.realm) except UserProfile.DoesNotExist: return json_error(_('Failed to change owner, no such user')) if not owner.is_active: return json_error(_('Failed to change owner, user is deactivated')) if owner.is_bot: return json_error(_("Failed to change owner, bots can't own other bots")) previous_owner = bot.bot_owner if previous_owner != owner: do_change_bot_owner(bot, owner, user_profile) if default_sending_stream is not None: if default_sending_stream == "": stream: Optional[Stream] = None else: (stream, recipient, sub) = access_stream_by_name( user_profile, default_sending_stream) do_change_default_sending_stream(bot, stream) if default_events_register_stream is not None: if default_events_register_stream == "": stream = None else: (stream, recipient, sub) = access_stream_by_name( user_profile, default_events_register_stream) do_change_default_events_register_stream(bot, stream) if default_all_public_streams is not None: do_change_default_all_public_streams(bot, default_all_public_streams) if service_payload_url is not None: check_valid_interface_type(service_interface) assert service_interface is not None do_update_outgoing_webhook_service(bot, service_interface, service_payload_url) if config_data is not None: do_update_bot_config_data(bot, config_data) if len(request.FILES) == 0: pass elif len(request.FILES) == 1: user_file = list(request.FILES.values())[0] upload_avatar_image(user_file, user_profile, bot) avatar_source = UserProfile.AVATAR_FROM_USER do_change_avatar_fields(bot, avatar_source) else: return json_error(_("You may only upload one file at a time")) json_result = dict( full_name=bot.full_name, avatar_url=avatar_url(bot), service_interface = service_interface, service_payload_url = service_payload_url, config_data = config_data, default_sending_stream=get_stream_name(bot.default_sending_stream), default_events_register_stream=get_stream_name(bot.default_events_register_stream), default_all_public_streams=bot.default_all_public_streams, ) # Don't include the bot owner in case it is not set. # Default bots have no owner. if bot.bot_owner is not None: json_result['bot_owner'] = bot.bot_owner.email return json_success(json_result)
def add_bot_backend( request: HttpRequest, user_profile: UserProfile, full_name_raw: str=REQ("full_name"), short_name_raw: str=REQ("short_name"), bot_type: int=REQ(validator=check_int, default=UserProfile.DEFAULT_BOT), payload_url: Optional[str]=REQ(validator=check_url, default=""), service_name: Optional[str]=REQ(default=None), config_data: Dict[str, str]=REQ(default={}, validator=check_dict(value_validator=check_string)), interface_type: int=REQ(validator=check_int, default=Service.GENERIC), default_sending_stream_name: Optional[str]=REQ('default_sending_stream', default=None), default_events_register_stream_name: Optional[str]=REQ('default_events_register_stream', default=None), default_all_public_streams: Optional[bool]=REQ(validator=check_bool, default=None), ) -> HttpResponse: short_name = check_short_name(short_name_raw) if bot_type != UserProfile.INCOMING_WEBHOOK_BOT: service_name = service_name or short_name short_name += "-bot" full_name = check_full_name(full_name_raw) try: email = f'{short_name}@{user_profile.realm.get_bot_domain()}' except InvalidFakeEmailDomain: return json_error(_("Can't create bots until FAKE_EMAIL_DOMAIN is correctly configured.\n" "Please contact your server administrator.")) form = CreateUserForm({'full_name': full_name, 'email': email}) if bot_type == UserProfile.EMBEDDED_BOT: if not settings.EMBEDDED_BOTS_ENABLED: return json_error(_("Embedded bots are not enabled.")) if service_name not in [bot.name for bot in EMBEDDED_BOTS]: return json_error(_("Invalid embedded bot name.")) if not form.is_valid(): # We validate client-side as well return json_error(_('Bad name or username')) try: get_user_by_delivery_email(email, user_profile.realm) return json_error(_("Username already in use")) except UserProfile.DoesNotExist: pass check_bot_name_available( realm_id=user_profile.realm_id, full_name=full_name, ) check_bot_creation_policy(user_profile, bot_type) check_valid_bot_type(user_profile, bot_type) check_valid_interface_type(interface_type) if len(request.FILES) == 0: avatar_source = UserProfile.AVATAR_FROM_GRAVATAR elif len(request.FILES) != 1: return json_error(_("You may only upload one file at a time")) else: avatar_source = UserProfile.AVATAR_FROM_USER default_sending_stream = None if default_sending_stream_name is not None: (default_sending_stream, ignored_rec, ignored_sub) = access_stream_by_name( user_profile, default_sending_stream_name) default_events_register_stream = None if default_events_register_stream_name is not None: (default_events_register_stream, ignored_rec, ignored_sub) = access_stream_by_name( user_profile, default_events_register_stream_name) if bot_type in (UserProfile.INCOMING_WEBHOOK_BOT, UserProfile.EMBEDDED_BOT) and service_name: check_valid_bot_config(bot_type, service_name, config_data) bot_profile = do_create_user(email=email, password=None, realm=user_profile.realm, full_name=full_name, short_name=short_name, bot_type=bot_type, bot_owner=user_profile, avatar_source=avatar_source, default_sending_stream=default_sending_stream, default_events_register_stream=default_events_register_stream, default_all_public_streams=default_all_public_streams) if len(request.FILES) == 1: user_file = list(request.FILES.values())[0] upload_avatar_image(user_file, user_profile, bot_profile) if bot_type in (UserProfile.OUTGOING_WEBHOOK_BOT, UserProfile.EMBEDDED_BOT): assert(isinstance(service_name, str)) add_service(name=service_name, user_profile=bot_profile, base_url=payload_url, interface=interface_type, token=generate_api_key()) if bot_type == UserProfile.INCOMING_WEBHOOK_BOT and service_name: set_bot_config(bot_profile, "integration_id", service_name) if bot_type in (UserProfile.INCOMING_WEBHOOK_BOT, UserProfile.EMBEDDED_BOT): for key, value in config_data.items(): set_bot_config(bot_profile, key, value) notify_created_bot(bot_profile) api_key = get_api_key(bot_profile) json_result = dict( api_key=api_key, avatar_url=avatar_url(bot_profile), default_sending_stream=get_stream_name(bot_profile.default_sending_stream), default_events_register_stream=get_stream_name(bot_profile.default_events_register_stream), default_all_public_streams=bot_profile.default_all_public_streams, ) return json_success(json_result)
def list_realm_custom_profile_fields( request: HttpRequest, user_profile: UserProfile) -> HttpResponse: fields = custom_profile_fields_for_realm(user_profile.realm_id) return json_success(request, data={"custom_fields": [f.as_dict() for f in fields]})
def _wrapped_view_func(request: HttpRequest, *args: object, **kwargs: object) -> HttpResponse: if request.method == "HEAD": return json_success() return view_func(request, *args, **kwargs)
def update_user_backend( request: HttpRequest, user_profile: UserProfile, user_id: int, full_name: Optional[str] = REQ(default=None, json_validator=check_string), role: Optional[int] = REQ( default=None, json_validator=check_int_in(UserProfile.ROLE_TYPES, ), ), profile_data: Optional[List[Dict[str, Optional[Union[ int, str, List[int]]]]]] = REQ( default=None, json_validator=check_profile_data, ), ) -> HttpResponse: target = access_user_by_id(user_profile, user_id, allow_deactivated=True, allow_bots=True, for_admin=True) if role is not None and target.role != role: # Require that the current user has permissions to # grant/remove the role in question. access_user_by_id has # already verified we're an administrator; here we enforce # that only owners can toggle the is_realm_owner flag. if UserProfile.ROLE_REALM_OWNER in [ role, target.role ] and not user_profile.is_realm_owner: raise OrganizationOwnerRequired() if target.role == UserProfile.ROLE_REALM_OWNER and check_last_owner( user_profile): return json_error( _("The owner permission cannot be removed from the only organization owner." )) do_change_user_role(target, role, acting_user=user_profile) if full_name is not None and target.full_name != full_name and full_name.strip( ) != "": # We don't respect `name_changes_disabled` here because the request # is on behalf of the administrator. check_change_full_name(target, full_name, user_profile) if profile_data is not None: clean_profile_data = [] for entry in profile_data: assert isinstance(entry["id"], int) if entry["value"] is None or not entry["value"]: field_id = entry["id"] check_remove_custom_profile_field_value(target, field_id) else: clean_profile_data.append({ "id": entry["id"], "value": entry["value"], }) validate_user_custom_profile_data(target.realm.id, clean_profile_data) do_update_user_custom_profile_data_if_changed(target, clean_profile_data) return json_success()
def get_statuses_for_realm(request: HttpRequest, user_profile: UserProfile) -> HttpResponse: # This isn't used by the webapp; it's available for API use by # bots and other clients. We may want to add slim_presence # support for it (or just migrate its API wholesale) later. return json_success(get_presence_response(user_profile, slim_presence=False))
def report_error( request: HttpRequest, user_profile: UserProfile, message: str = REQ(), stacktrace: str = REQ(), ui_message: bool = REQ(json_validator=check_bool), user_agent: str = REQ(), href: str = REQ(), log: str = REQ(), more_info: Mapping[str, Any] = REQ(json_validator=check_dict([]), default={}), ) -> HttpResponse: """Accepts an error report and stores in a queue for processing. The actual error reports are later handled by do_report_error""" if not settings.BROWSER_ERROR_REPORTING: return json_success() more_info = dict(more_info) js_source_map = get_js_source_map() if js_source_map: stacktrace = js_source_map.annotate_stacktrace(stacktrace) try: version: Optional[str] = subprocess.check_output( ["git", "show", "-s", "--oneline"], universal_newlines=True, ) except (FileNotFoundError, subprocess.CalledProcessError): version = None # Get the IP address of the request remote_ip = request.META["REMOTE_ADDR"] # For the privacy of our users, we remove any actual text content # in draft_content (from drafts rendering exceptions). See the # comment on privacy_clean_markdown for more details. if more_info.get("draft_content"): more_info["draft_content"] = privacy_clean_markdown( more_info["draft_content"]) if user_profile.is_authenticated: email = user_profile.delivery_email full_name = user_profile.full_name else: email = "*****@*****.**" full_name = "Anonymous User" queue_json_publish( "error_reports", dict( type="browser", report=dict( host=SplitResult("", request.get_host(), "", "", "").hostname, ip_address=remote_ip, user_email=email, user_full_name=full_name, user_visible=ui_message, server_path=settings.DEPLOY_ROOT, version=version, user_agent=user_agent, href=href, message=message, stacktrace=stacktrace, log=log, more_info=more_info, ), ), ) return json_success()
def get_events_backend( request: HttpRequest, user_profile: UserProfile, # user_client is intended only for internal Django=>Tornado requests # and thus shouldn't be documented for external use. user_client: Optional[Client] = REQ(converter=get_client, default=None, intentionally_undocumented=True), last_event_id: Optional[int] = REQ(converter=int, default=None), queue_id: Optional[str] = REQ(default=None), # apply_markdown, client_gravatar, all_public_streams, and various # other parameters are only used when registering a new queue via this # endpoint. This is a feature used primarily by get_events_internal # and not expected to be used by third-party clients. apply_markdown: bool = REQ(default=False, json_validator=check_bool, intentionally_undocumented=True), client_gravatar: bool = REQ(default=False, json_validator=check_bool, intentionally_undocumented=True), slim_presence: bool = REQ(default=False, json_validator=check_bool, intentionally_undocumented=True), all_public_streams: bool = REQ(default=False, json_validator=check_bool, intentionally_undocumented=True), event_types: Optional[Sequence[str]] = REQ( default=None, json_validator=check_list(check_string), intentionally_undocumented=True), dont_block: bool = REQ(default=False, json_validator=check_bool), narrow: Sequence[Sequence[str]] = REQ( default=[], json_validator=check_list(check_list(check_string)), intentionally_undocumented=True, ), lifespan_secs: int = REQ(default=0, converter=to_non_negative_int, intentionally_undocumented=True), bulk_message_deletion: bool = REQ(default=False, json_validator=check_bool, intentionally_undocumented=True), stream_typing_notifications: bool = REQ(default=False, json_validator=check_bool, intentionally_undocumented=True), ) -> HttpResponse: if all_public_streams and not user_profile.can_access_public_streams(): raise JsonableError(_("User not authorized for this query")) # Extract the Tornado handler from the request handler: AsyncDjangoHandler = request._tornado_handler if user_client is None: valid_user_client = get_request_notes(request).client assert valid_user_client is not None else: valid_user_client = user_client events_query = dict( user_profile_id=user_profile.id, queue_id=queue_id, last_event_id=last_event_id, event_types=event_types, client_type_name=valid_user_client.name, all_public_streams=all_public_streams, lifespan_secs=lifespan_secs, narrow=narrow, dont_block=dont_block, handler_id=handler.handler_id, ) if queue_id is None: events_query["new_queue_data"] = dict( user_profile_id=user_profile.id, realm_id=user_profile.realm_id, event_types=event_types, client_type_name=valid_user_client.name, apply_markdown=apply_markdown, client_gravatar=client_gravatar, slim_presence=slim_presence, all_public_streams=all_public_streams, queue_timeout=lifespan_secs, last_connection_time=time.time(), narrow=narrow, bulk_message_deletion=bulk_message_deletion, stream_typing_notifications=stream_typing_notifications, ) result = fetch_events(events_query) if "extra_log_data" in result: log_data = get_request_notes(request).log_data assert log_data is not None log_data["extra"] = result["extra_log_data"] if result["type"] == "async": # Mark this response with .asynchronous; this will result in # Tornado discarding the response and instead long-polling the # request. See zulip_finish for more design details. handler._request = request response = json_success() response.asynchronous = True return response if result["type"] == "error": raise result["exception"] return json_success(result["response"])
def update_realm( request, user_profile, name=REQ(validator=check_string, default=None), description=REQ(validator=check_string, default=None), restricted_to_domain=REQ(validator=check_bool, default=None), invite_required=REQ(validator=check_bool, default=None), invite_by_admins_only=REQ(validator=check_bool, default=None), name_changes_disabled=REQ(validator=check_bool, default=None), email_changes_disabled=REQ(validator=check_bool, default=None), inline_image_preview=REQ(validator=check_bool, default=None), inline_url_embed_preview=REQ(validator=check_bool, default=None), create_stream_by_admins_only=REQ(validator=check_bool, default=None), add_emoji_by_admins_only=REQ(validator=check_bool, default=None), allow_message_editing=REQ(validator=check_bool, default=None), mandatory_topics=REQ(validator=check_bool, default=None), message_content_edit_limit_seconds=REQ(converter=to_non_negative_int, default=None), allow_edit_history=REQ(validator=check_bool, default=None), default_language=REQ(validator=check_string, default=None), waiting_period_threshold=REQ(converter=to_non_negative_int, default=None), authentication_methods=REQ(validator=check_dict([]), default=None), notifications_stream_id=REQ(validator=check_int, default=None), message_retention_days=REQ(converter=to_not_negative_int_or_none, default=None)): # type: (HttpRequest, UserProfile, Optional[str], Optional[str], Optional[bool], Optional[bool], Optional[bool], Optional[bool], Optional[bool], Optional[bool], Optional[bool], Optional[bool], Optional[bool], Optional[bool], Optional[bool], Optional[int], Optional[bool], Optional[str], Optional[int], Optional[dict], Optional[int], Optional[int]) -> HttpResponse realm = user_profile.realm # Additional validation/error checking beyond types go here, so # the entire request can succeed or fail atomically. if default_language is not None and default_language not in get_available_language_codes( ): raise JsonableError(_("Invalid language '%s'" % (default_language, ))) if description is not None and len(description) > 1000: return json_error(_("Realm description is too long.")) if authentication_methods is not None and True not in list( authentication_methods.values()): return json_error( _("At least one authentication method must be enabled."), data={"reason": "no authentication"}, status=403) # The user of `locals()` here is a bit of a code smell, but it's # restricted to the elements present in realm.property_types. # # TODO: It should be possible to deduplicate this function up # further by some more advanced usage of the # `REQ/has_request_variables` extraction. req_vars = { k: v for k, v in list(locals().items()) if k in realm.property_types } data = {} # type: Dict[str, Any] for k, v in list(req_vars.items()): if v is not None and getattr(realm, k) != v: do_set_realm_property(realm, k, v) if isinstance(v, Text): data[k] = 'updated' else: data[k] = v # The following realm properties do not fit the pattern above # authentication_methods is not supported by the do_set_realm_property # framework because of its bitfield. if authentication_methods is not None and realm.authentication_methods_dict( ) != authentication_methods: do_set_realm_authentication_methods(realm, authentication_methods) data['authentication_methods'] = authentication_methods # The message_editing settings are coupled to each other, and thus don't fit # into the do_set_realm_property framework. if (allow_message_editing is not None and realm.allow_message_editing != allow_message_editing) or \ (message_content_edit_limit_seconds is not None and realm.message_content_edit_limit_seconds != message_content_edit_limit_seconds): if allow_message_editing is None: allow_message_editing = realm.allow_message_editing if message_content_edit_limit_seconds is None: message_content_edit_limit_seconds = realm.message_content_edit_limit_seconds do_set_realm_message_editing(realm, allow_message_editing, message_content_edit_limit_seconds) data['allow_message_editing'] = allow_message_editing data[ 'message_content_edit_limit_seconds'] = message_content_edit_limit_seconds # Realm.notifications_stream is not a boolean, Text or integer field, and thus doesn't fit # into the do_set_realm_property framework. if notifications_stream_id is not None: if realm.notifications_stream is None or realm.notifications_stream.id != notifications_stream_id: new_notifications_stream = None if notifications_stream_id >= 0: (new_notifications_stream, recipient, sub) = access_stream_by_id(user_profile, notifications_stream_id) do_set_realm_notifications_stream(realm, new_notifications_stream, notifications_stream_id) data['notifications_stream_id'] = notifications_stream_id return json_success(data)
def notify(request: HttpRequest) -> HttpResponse: process_notification(orjson.loads(request.POST["data"])) return json_success()
def get_realm_exports(request: HttpRequest, user: UserProfile) -> HttpResponse: realm_exports = get_realm_exports_serialized(user) return json_success(request, data={"exports": realm_exports})
def json_make_stream_private(request, user_profile, stream_name=REQ()): # type: (HttpRequest, UserProfile, text_type) -> HttpResponse do_make_stream_private(user_profile.realm, stream_name) return json_success()
def deactivate_stream_backend(request, user_profile, stream_id): # type: (HttpRequest, UserProfile, int) -> HttpResponse (stream, recipient, sub) = access_stream_by_id(user_profile, stream_id) do_deactivate_stream(stream) return json_success()
def list_subscriptions_backend(request, user_profile): # type: (HttpRequest, UserProfile) -> HttpResponse return json_success( {"subscriptions": gather_subscriptions(user_profile)[0]})
def api_fetch_google_client_id(request): # type: (HttpRequest) -> HttpResponse if not settings.GOOGLE_CLIENT_ID: return json_error(_("GOOGLE_CLIENT_ID is not configured"), status=400) return json_success({"google_client_id": settings.GOOGLE_CLIENT_ID})
def remove_default_stream(request, user_profile, stream_name=REQ()): # type: (HttpRequest, UserProfile, text_type) -> HttpResponse do_remove_default_stream(user_profile.realm, stream_name) return json_success()
def api_get_auth_backends(request): # type: (HttpRequest) -> HttpResponse """Deprecated route; this is to be replaced by api_get_server_settings""" auth_backends = get_auth_backends_data(request) auth_backends['zulip_version'] = ZULIP_VERSION return json_success(auth_backends)
def add_bot_backend(request, user_profile, full_name_raw=REQ("full_name"), short_name=REQ(), default_sending_stream_name=REQ('default_sending_stream', default=None), default_events_register_stream_name=REQ( 'default_events_register_stream', default=None), default_all_public_streams=REQ(validator=check_bool, default=None)): # type: (HttpRequest, UserProfile, Text, Text, Optional[Text], Optional[Text], Optional[bool]) -> HttpResponse short_name += "-bot" full_name = check_full_name(full_name_raw) email = short_name + "@" + user_profile.realm.domain form = CreateUserForm({'full_name': full_name, 'email': email}) if not form.is_valid(): # We validate client-side as well return json_error(_('Bad name or username')) try: get_user_profile_by_email(email) return json_error(_("Username already in use")) except UserProfile.DoesNotExist: pass if len(request.FILES) == 0: avatar_source = UserProfile.AVATAR_FROM_GRAVATAR elif len(request.FILES) != 1: return json_error(_("You may only upload one file at a time")) else: user_file = list(request.FILES.values())[0] upload_avatar_image(user_file, user_profile, email) avatar_source = UserProfile.AVATAR_FROM_USER default_sending_stream = None if default_sending_stream_name is not None: (default_sending_stream, ignored_rec, ignored_sub) = access_stream_by_name(user_profile, default_sending_stream_name) default_events_register_stream = None if default_events_register_stream_name is not None: (default_events_register_stream, ignored_rec, ignored_sub) = access_stream_by_name( user_profile, default_events_register_stream_name) bot_profile = do_create_user( email=email, password='', realm=user_profile.realm, full_name=full_name, short_name=short_name, active=True, bot_type=UserProfile.DEFAULT_BOT, bot_owner=user_profile, avatar_source=avatar_source, default_sending_stream=default_sending_stream, default_events_register_stream=default_events_register_stream, default_all_public_streams=default_all_public_streams) json_result = dict( api_key=bot_profile.api_key, avatar_url=avatar_url(bot_profile), default_sending_stream=get_stream_name( bot_profile.default_sending_stream), default_events_register_stream=get_stream_name( bot_profile.default_events_register_stream), default_all_public_streams=bot_profile.default_all_public_streams, ) return json_success(json_result)
def add_subscriptions_backend( request, user_profile, streams_raw=REQ("subscriptions", validator=check_list(check_dict([('name', check_string) ]))), invite_only=REQ(validator=check_bool, default=False), announce=REQ(validator=check_bool, default=False), principals=REQ(validator=check_list(check_string), default=[]), authorization_errors_fatal=REQ(validator=check_bool, default=True)): # type: (HttpRequest, UserProfile, Iterable[Mapping[str, Text]], bool, bool, List[Text], bool) -> HttpResponse stream_dicts = [] for stream_dict in streams_raw: stream_dict_copy = {} # type: Dict[str, Any] for field in stream_dict: stream_dict_copy[field] = stream_dict[field] # Strip the stream name here. stream_dict_copy['name'] = stream_dict_copy['name'].strip() stream_dict_copy["invite_only"] = invite_only stream_dicts.append(stream_dict_copy) # Validation of the streams arguments, including enforcement of # can_create_streams policy and check_stream_name policy is inside # list_to_streams. existing_streams, created_streams = \ list_to_streams(stream_dicts, user_profile, autocreate=True) authorized_streams, unauthorized_streams = \ filter_stream_authorization(user_profile, existing_streams) if len(unauthorized_streams) > 0 and authorization_errors_fatal: return json_error( _("Unable to access stream (%s).") % unauthorized_streams[0].name) # Newly created streams are also authorized for the creator streams = authorized_streams + created_streams if len(principals) > 0: if user_profile.realm.is_zephyr_mirror_realm and not all( stream.invite_only for stream in streams): return json_error( _("You can only invite other Zephyr mirroring users to invite-only streams." )) subscribers = set( principal_to_user_profile(user_profile, principal) for principal in principals) else: subscribers = set([user_profile]) (subscribed, already_subscribed) = bulk_add_subscriptions(streams, subscribers) result = dict(subscribed=defaultdict(list), already_subscribed=defaultdict(list)) # type: Dict[str, Any] for (subscriber, stream) in subscribed: result["subscribed"][subscriber.email].append(stream.name) for (subscriber, stream) in already_subscribed: result["already_subscribed"][subscriber.email].append(stream.name) bots = dict( (subscriber.email, subscriber.is_bot) for subscriber in subscribers) newly_created_stream_names = {stream.name for stream in created_streams} private_stream_names = { stream.name for stream in streams if stream.invite_only } # Inform the user if someone else subscribed them to stuff, # or if a new stream was created with the "announce" option. notifications = [] if len(principals) > 0 and result["subscribed"]: for email, subscribed_stream_names in six.iteritems( result["subscribed"]): if email == user_profile.email: # Don't send a Zulip if you invited yourself. continue if bots[email]: # Don't send invitation Zulips to bots continue # For each user, we notify them about newly subscribed streams, except for # streams that were newly created. notify_stream_names = set( subscribed_stream_names) - newly_created_stream_names if not notify_stream_names: continue msg = you_were_just_subscribed_message( acting_user=user_profile, stream_names=notify_stream_names, private_stream_names=private_stream_names) sender = get_system_bot(settings.NOTIFICATION_BOT) notifications.append( internal_prep_private_message(realm=user_profile.realm, sender=sender, recipient_email=email, content=msg)) if announce and len(created_streams) > 0: notifications_stream = user_profile.realm.notifications_stream # type: Optional[Stream] if notifications_stream is not None: if len(created_streams) > 1: stream_msg = "the following streams: %s" % (", ".join( '#**%s**' % s.name for s in created_streams)) else: stream_msg = "a new stream #**%s**." % created_streams[0].name msg = ("%s just created %s" % (user_profile.full_name, stream_msg)) sender = get_system_bot(settings.NOTIFICATION_BOT) stream_name = notifications_stream.name topic = 'Streams' notifications.append( internal_prep_stream_message(realm=user_profile.realm, sender=sender, stream_name=stream_name, topic=topic, content=msg)) if not user_profile.realm.is_zephyr_mirror_realm: for stream in created_streams: notifications.append(prep_stream_welcome_message(stream)) if len(notifications) > 0: do_send_messages(notifications) result["subscribed"] = dict(result["subscribed"]) result["already_subscribed"] = dict(result["already_subscribed"]) if not authorization_errors_fatal: result["unauthorized"] = [ stream.name for stream in unauthorized_streams ] return json_success(result)
def api_github_landing( request, user_profile, event=REQ, payload=REQ(validator=check_dict([])), branches=REQ(default=''), stream=REQ(default=''), version=REQ(converter=to_non_negative_int, default=1), commit_stream=REQ(default=''), issue_stream=REQ(default=''), exclude_pull_requests=REQ(converter=flexible_boolean, default=False), exclude_issues=REQ(converter=flexible_boolean, default=False), exclude_commits=REQ(converter=flexible_boolean, default=False), emphasize_branch_in_topic=REQ(converter=flexible_boolean, default=False), ): repository = payload['repository'] # Special hook for capturing event data. If we see our special test repo, log the payload from github. try: if is_test_repository(repository) and settings.PRODUCTION: with open('/var/log/zulip/github-payloads', 'a') as f: f.write( ujson.dumps({ 'event': event, 'payload': payload, 'branches': branches, 'stream': stream, 'version': version, 'commit_stream': commit_stream, 'issue_stream': issue_stream, 'exclude_pull_requests': exclude_pull_requests, 'exclude_issues': exclude_issues, 'exclude_commits': exclude_commits, 'emphasize_branch_in_topic': emphasize_branch_in_topic, })) f.write('\n') except Exception: logging.exception('Error while capturing Github event') if not stream: stream = 'commits' short_ref = re.sub(r'^refs/heads/', '', payload.get('ref', '')) kwargs = dict() if emphasize_branch_in_topic and short_ref: kwargs['topic_focus'] = short_ref allowed_events = set() if not exclude_pull_requests: allowed_events.add('pull_request') if not exclude_issues: allowed_events.add('issues') allowed_events.add('issue_comment') if not exclude_commits: allowed_events.add('push') allowed_events.add('commit_comment') if event not in allowed_events: return json_success() # We filter issue_comment events for issue creation events if event == 'issue_comment' and payload['action'] != 'created': return json_success() if event == 'push': # If we are given a whitelist of branches, then we silently ignore # any push notification on a branch that is not in our whitelist. if branches and short_ref not in re.split('[\s,;|]+', branches): return json_success() # Map payload to the handler with the right version if version == 2: target_stream, subject, content = api_github_v2( user_profile, event, payload, branches, stream, commit_stream, issue_stream, **kwargs) else: target_stream, subject, content = api_github_v1( user_profile, event, payload, branches, stream, **kwargs) request.client = get_client('ZulipGitHubWebhook') return send_message_backend(request, user_profile, message_type_name='stream', message_to=[target_stream], forged=False, subject_name=subject, message_content=content)