Example #1
0
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()
Example #2
0
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()
Example #3
0
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()
Example #4
0
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()
Example #5
0
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()
Example #6
0
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()
Example #7
0
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()
Example #8
0
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()
Example #9
0
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()
Example #10
0
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()
Example #11
0
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()
Example #12
0
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()
Example #13
0
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()
Example #14
0
File: view.py Project: jdherg/zulip
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()
Example #15
0
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()
Example #16
0
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()
Example #17
0
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()
Example #18
0
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()
Example #19
0
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()
Example #20
0
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()
Example #21
0
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))
Example #22
0
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()
Example #23
0
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()
Example #24
0
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()
Example #25
0
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()
Example #26
0
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})
Example #27
0
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()
Example #28
0
File: yo.py Project: Frouk/zulip
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()
Example #29
0
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()
Example #30
0
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})
Example #31
0
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)
Example #32
0
def confirmation_key(request: HttpRequest) -> HttpResponse:
    return json_success(request.session.get('confirmation_key'))
Example #33
0
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})
Example #34
0
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()
Example #35
0
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()
Example #36
0
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()
Example #37
0
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})
Example #38
0
def get_statuses_for_realm(request: HttpRequest, user_profile: UserProfile) -> HttpResponse:
    return json_success(get_status_list(user_profile))
Example #39
0
def list_realm_domains(request: HttpRequest, user_profile: UserProfile) -> HttpResponse:
    domains = get_realm_domains(user_profile.realm)
    return json_success(request, data={"domains": domains})
Example #40
0
def _deactivate_user_profile_backend(request: HttpRequest, user_profile: UserProfile,
                                     target: UserProfile) -> HttpResponse:
    do_deactivate_user(target, acting_user=user_profile)
    return json_success()
Example #41
0
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)
Example #42
0
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)
Example #43
0
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]})
Example #44
0
 def _wrapped_view_func(request: HttpRequest, *args: object,
                        **kwargs: object) -> HttpResponse:
     if request.method == "HEAD":
         return json_success()
     return view_func(request, *args, **kwargs)
Example #45
0
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()
Example #46
0
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))
Example #47
0
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()
Example #48
0
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"])
Example #49
0
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)
Example #50
0
def notify(request: HttpRequest) -> HttpResponse:
    process_notification(orjson.loads(request.POST["data"]))
    return json_success()
Example #51
0
def get_realm_exports(request: HttpRequest, user: UserProfile) -> HttpResponse:
    realm_exports = get_realm_exports_serialized(user)
    return json_success(request, data={"exports": realm_exports})
Example #52
0
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()
Example #53
0
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()
Example #54
0
def list_subscriptions_backend(request, user_profile):
    # type: (HttpRequest, UserProfile) -> HttpResponse
    return json_success(
        {"subscriptions": gather_subscriptions(user_profile)[0]})
Example #55
0
File: auth.py Project: sonu96/zulip
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})
Example #56
0
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()
Example #57
0
File: auth.py Project: sonu96/zulip
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)
Example #58
0
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)
Example #59
0
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)