コード例 #1
0
ファイル: user_settings.py プロジェクト: zy964c/zulip
def update_display_settings_backend(
    request: HttpRequest,
    user_profile: UserProfile,
    twenty_four_hour_time: Optional[bool] = REQ(validator=check_bool,
                                                default=None),
    high_contrast_mode: Optional[bool] = REQ(validator=check_bool,
                                             default=None),
    night_mode: Optional[bool] = REQ(validator=check_bool, default=None),
    default_language: Optional[bool] = REQ(validator=check_string,
                                           default=None),
    left_side_userlist: Optional[bool] = REQ(validator=check_bool,
                                             default=None),
    emoji_alt_code: Optional[bool] = REQ(validator=check_bool, default=None),
    emojiset: Optional[str] = REQ(validator=check_string, default=None),
    timezone: Optional[str] = REQ(validator=check_string, default=None)
) -> HttpResponse:

    if (default_language is not None
            and default_language not in get_available_language_codes()):
        raise JsonableError(_("Invalid language '%s'" % (default_language, )))

    if (timezone is not None and timezone not in get_all_timezones()):
        raise JsonableError(_("Invalid timezone '%s'" % (timezone, )))

    if (emojiset is not None
            and emojiset not in UserProfile.emojiset_choices()):
        raise JsonableError(_("Invalid emojiset '%s'" % (emojiset, )))

    request_settings = {
        k: v
        for k, v in list(locals().items()) if k in user_profile.property_types
    }
    result = {}  # type: Dict[str, Any]
    for k, v in list(request_settings.items()):
        if v is not None and getattr(user_profile, k) != v:
            do_set_user_display_setting(user_profile, k, v)
            result[k] = v

    return json_success(result)
コード例 #2
0
ファイル: message_edit.py プロジェクト: vishwasmodi/zulip
def get_message_edit_history(
    request: HttpRequest,
    user_profile: UserProfile,
    message_id: int = REQ(converter=to_non_negative_int, path_only=True)
) -> HttpResponse:
    if not user_profile.realm.allow_edit_history:
        return json_error(
            _("Message edit history is disabled in this organization"))
    message, ignored_user_message = access_message(user_profile, message_id)

    # Extract the message edit history from the message
    if message.edit_history is not None:
        message_edit_history = orjson.loads(message.edit_history)
    else:
        message_edit_history = []

    # Fill in all the extra data that will make it usable
    fill_edit_history_entries(message_edit_history, message)
    return json_success(
        {"message_history": list(reversed(message_edit_history))})
コード例 #3
0
ファイル: view.py プロジェクト: zmuhammad261/zulip
def api_pivotal_webhook(request, user_profile, client, stream=REQ()):
    # type: (HttpRequest, UserProfile, Client, Text) -> HttpResponse
    subject = content = None
    try:
        subject, content = api_pivotal_webhook_v3(request, user_profile, stream)
    except AttributeError:
        return json_error(_("Failed to extract data from Pivotal XML response"))
    except Exception:
        # Attempt to parse v5 JSON payload
        try:
            subject, content = api_pivotal_webhook_v5(request, user_profile, stream)
        except AttributeError:
            return json_error(_("Failed to extract data from Pivotal V5 JSON response"))

    if subject is None or content is None:
        return json_error(_("Unable to handle Pivotal payload"))

    check_send_message(user_profile, client, "stream",
                       [stream], subject, content)
    return json_success()
コード例 #4
0
ファイル: view.py プロジェクト: vfseanm/zulip
def api_heroku_webhook(request,
                       user_profile,
                       stream=REQ(default="heroku"),
                       head=REQ(),
                       app=REQ(),
                       user=REQ(),
                       url=REQ(),
                       git_log=REQ()):
    # type: (HttpRequest, UserProfile, Text, Text, Text, Text, Text, Text) -> HttpResponse
    template = "{} deployed version {} of [{}]({})\n> {}"
    content = template.format(user, head, app, url, git_log)

    check_send_message(user_profile, request.client, "stream", [stream], app,
                       content)
    return json_success()
コード例 #5
0
def api_transifex_webhook(request, user_profile, client,
                          project=REQ(), resource=REQ(),
                          language=REQ(), translated=REQ(default=None),
                          reviewed=REQ(default=None),
                          stream=REQ(default='transifex')):
                          # type: (HttpRequest, UserProfile, Client, str, str, str, Optional[int], Optional[int], str) -> HttpResponse
    subject = "{} in {}".format(project, language)
    if translated:
        body = "Resource {} fully translated.".format(resource)
    elif reviewed:
        body = "Resource {} fully reviewed.".format(resource)
    else:
        return json_error(_("Transifex wrong request"))
    check_send_message(user_profile, client, 'stream', [stream], subject, body)
    return json_success()
コード例 #6
0
def update_user_custom_profile_data(
    request,
    user_profile,
    data=REQ(validator=check_list(check_dict([('id', check_int)])))):
    # type: (HttpRequest, UserProfile, List[Dict[str, Union[int, Text]]]) -> HttpResponse
    for item in data:
        field_id = item['id']
        try:
            field = CustomProfileField.objects.get(id=field_id)
        except CustomProfileField.DoesNotExist:
            return json_error(
                _('Field id {id} not found.').format(id=field_id))

        validator = CustomProfileField.FIELD_VALIDATORS[field.field_type]
        result = validator('value[{}]'.format(field_id), item['value'])
        if result is not None:
            return json_error(result)

    do_update_user_custom_profile_data(user_profile, data)
    # We need to call this explicitly otherwise constraints are not check
    return json_success()
コード例 #7
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, Text, bool, Text, Text, Text, Dict[str, Any]) -> 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 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()
コード例 #8
0
ファイル: view.py プロジェクト: shikharsrivastava23/zulip
def api_zabbix_webhook(
        request: HttpRequest,
        user_profile: UserProfile,
        payload: Dict[str, Any] = REQ(argument_type="body"),
) -> HttpResponse:

    try:
        body = get_body_for_http_request(payload)
        subject = get_subject_for_http_request(payload)
    except KeyError:
        message = MISCONFIGURED_PAYLOAD_ERROR_MESSAGE.format(
            bot_name=user_profile.full_name,
            support_email=FromAddress.SUPPORT,
        ).strip()
        send_rate_limited_pm_notification_to_bot_owner(user_profile,
                                                       user_profile.realm,
                                                       message)

        raise JsonableError(_("Invalid payload"))

    check_send_webhook_message(request, user_profile, subject, body)
    return json_success()
コード例 #9
0
ファイル: report.py プロジェクト: pxhanus/zulip
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)):

    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"])
    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()
コード例 #10
0
ファイル: messages.py プロジェクト: tied/zulip
def update_message_flags(request,
                         user_profile,
                         messages=REQ(validator=check_list(check_int)),
                         operation=REQ('op'),
                         flag=REQ(),
                         all=REQ(validator=check_bool, default=False),
                         stream_name=REQ(default=None),
                         topic_name=REQ(default=None)):
    # type: (HttpRequest, UserProfile, List[int], text_type, text_type, bool, Optional[text_type], Optional[text_type]) -> HttpResponse
    if all:
        target_count_str = "all"
    else:
        target_count_str = str(len(messages))
    log_data_str = "[%s %s/%s]" % (operation, flag, target_count_str)
    request._log_data["extra"] = log_data_str
    stream = None
    if stream_name is not None:
        stream = get_stream(stream_name, user_profile.realm)
        if not stream:
            raise JsonableError(_('No such stream \'%s\'') % (stream_name, ))
        if topic_name:
            topic_exists = UserMessage.objects.filter(
                user_profile=user_profile,
                message__recipient__type_id=stream.id,
                message__recipient__type=Recipient.STREAM,
                message__subject__iexact=topic_name).exists()
            if not topic_exists:
                raise JsonableError(_('No such topic \'%s\'') % (topic_name, ))
    count = do_update_message_flags(user_profile, operation, flag, messages,
                                    all, stream, topic_name)

    # If we succeed, update log data str with the actual count for how
    # many messages were updated.
    if count != len(messages):
        log_data_str = "[%s %s/%s] actually %s" % (operation, flag,
                                                   target_count_str, count)
    request._log_data["extra"] = log_data_str

    return json_success({'result': 'success', 'messages': messages, 'msg': ''})
コード例 #11
0
def update_message_flags(request, user_profile,
                         messages=REQ('messages', validator=check_list(check_int)),
                         operation=REQ('op'), flag=REQ('flag'),
                         all=REQ('all', validator=check_bool, default=False),
                         stream_name=REQ('stream_name', default=None),
                         topic_name=REQ('topic_name', default=None)):

    request._log_data["extra"] = "[%s %s]" % (operation, flag)
    stream = None
    if stream_name is not None:
        stream = get_stream(stream_name, user_profile.realm)
        if not stream:
            raise JsonableError(_('No such stream \'%s\'') % (stream_name,))
        if topic_name:
            topic_exists = UserMessage.objects.filter(user_profile=user_profile,
                                                      message__recipient__type_id=stream.id,
                                                      message__recipient__type=Recipient.STREAM,
                                                      message__subject__iexact=topic_name).exists()
            if not topic_exists:
                raise JsonableError(_('No such topic \'%s\'') % (topic_name,))
    do_update_message_flags(user_profile, operation, flag, messages, all, stream, topic_name)
    return json_success({'result': 'success',
                         'messages': messages,
                         'msg': ''})
コード例 #12
0
ファイル: user_settings.py プロジェクト: waveyuk/zulip
def change_enter_sends(request, user_profile,
                       enter_sends=REQ(validator=check_bool)):
    # type: (HttpRequest, UserProfile, bool) -> HttpResponse
    do_change_enter_sends(user_profile, enter_sends)
    return json_success()
コード例 #13
0
def json_change_settings(
        request: HttpRequest,
        user_profile: UserProfile,
        full_name: str = REQ(default=""),
        email: str = REQ(default=""),
        old_password: str = REQ(default=""),
        new_password: str = REQ(default=""),
) -> HttpResponse:
    if not (full_name or new_password or email):
        return json_error(_("Please fill out all fields."))

    if new_password != "":
        return_data: Dict[str, Any] = {}
        if email_belongs_to_ldap(user_profile.realm,
                                 user_profile.delivery_email):
            return json_error(_("Your Zulip password is managed in LDAP"))

        try:
            if not authenticate(
                    request,
                    username=user_profile.delivery_email,
                    password=old_password,
                    realm=user_profile.realm,
                    return_data=return_data,
            ):
                return json_error(_("Wrong password!"))
        except RateLimited as e:
            assert e.secs_to_freedom is not None
            secs_to_freedom = int(e.secs_to_freedom)
            return json_error(
                _("You're making too many attempts! Try again in {} seconds.").
                format(secs_to_freedom), )

        if not check_password_strength(new_password):
            return json_error(_("New password is too weak!"))

        do_change_password(user_profile, new_password)
        # In Django 1.10, password changes invalidates sessions, see
        # https://docs.djangoproject.com/en/1.10/topics/auth/default/#session-invalidation-on-password-change
        # for details. To avoid this logging the user out of their own
        # session (which would provide a confusing UX at best), we
        # update the session hash here.
        update_session_auth_hash(request, user_profile)
        # We also save the session to the DB immediately to mitigate
        # race conditions. In theory, there is still a race condition
        # and to completely avoid it we will have to use some kind of
        # mutex lock in `django.contrib.auth.get_user` where session
        # is verified. To make that lock work we will have to control
        # the AuthenticationMiddleware which is currently controlled
        # by Django,
        request.session.save()

    result: Dict[str, Any] = {}
    new_email = email.strip()
    if user_profile.delivery_email != new_email and new_email != "":
        if user_profile.realm.email_changes_disabled and not user_profile.is_realm_admin:
            return json_error(
                _("Email address changes are disabled in this organization."))

        error = validate_email_is_valid(
            new_email,
            get_realm_email_validator(user_profile.realm),
        )
        if error:
            return json_error(error)

        try:
            validate_email_not_already_in_realm(
                user_profile.realm,
                new_email,
                verbose=False,
            )
        except ValidationError as e:
            return json_error(e.message)

        do_start_email_change_process(user_profile, new_email)
        result["account_email"] = _(
            "Check your email for a confirmation link. ")

    if user_profile.full_name != full_name and full_name.strip() != "":
        if name_changes_disabled(
                user_profile.realm) and not user_profile.is_realm_admin:
            # Failingly silently is fine -- they can't do it through the UI, so
            # they'd have to be trying to break the rules.
            pass
        else:
            # Note that check_change_full_name strips the passed name automatically
            result["full_name"] = check_change_full_name(
                user_profile, full_name, user_profile)

    return json_success(result)
コード例 #14
0
def update_display_settings_backend(
    request: HttpRequest,
    user_profile: UserProfile,
    twenty_four_hour_time: Optional[bool] = REQ(json_validator=check_bool,
                                                default=None),
    dense_mode: Optional[bool] = REQ(json_validator=check_bool, default=None),
    starred_message_counts: Optional[bool] = REQ(json_validator=check_bool,
                                                 default=None),
    fluid_layout_width: Optional[bool] = REQ(json_validator=check_bool,
                                             default=None),
    high_contrast_mode: Optional[bool] = REQ(json_validator=check_bool,
                                             default=None),
    color_scheme: Optional[int] = REQ(json_validator=check_int_in(
        UserProfile.COLOR_SCHEME_CHOICES),
                                      default=None),
    translate_emoticons: Optional[bool] = REQ(json_validator=check_bool,
                                              default=None),
    default_language: Optional[str] = REQ(default=None),
    default_view: Optional[str] = REQ(
        str_validator=check_string_in(default_view_options), default=None),
    left_side_userlist: Optional[bool] = REQ(json_validator=check_bool,
                                             default=None),
    emojiset: Optional[str] = REQ(
        str_validator=check_string_in(emojiset_choices), default=None),
    demote_inactive_streams: Optional[int] = REQ(json_validator=check_int_in(
        UserProfile.DEMOTE_STREAMS_CHOICES),
                                                 default=None),
    timezone: Optional[str] = REQ(str_validator=check_string_in(
        pytz.all_timezones_set),
                                  default=None),
) -> HttpResponse:

    # We can't use REQ for this widget because
    # get_available_language_codes requires provisioning to be
    # complete.
    if default_language is not None and default_language not in get_available_language_codes(
    ):
        raise JsonableError(_("Invalid default_language"))

    request_settings = {
        k: v
        for k, v in list(locals().items()) if k in user_profile.property_types
    }
    result: Dict[str, Any] = {}
    for k, v in list(request_settings.items()):
        if v is not None and getattr(user_profile, k) != v:
            do_set_user_display_setting(user_profile, k, v)
            result[k] = v

    return json_success(result)
コード例 #15
0
ファイル: views.py プロジェクト: yurakozlovskiy/zulip
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,
                               validator=check_bool,
                               intentionally_undocumented=True),
    client_gravatar: bool = REQ(default=False,
                                validator=check_bool,
                                intentionally_undocumented=True),
    slim_presence: bool = REQ(default=False,
                              validator=check_bool,
                              intentionally_undocumented=True),
    all_public_streams: bool = REQ(default=False,
                                   validator=check_bool,
                                   intentionally_undocumented=True),
    event_types: Optional[Sequence[str]] = REQ(
        default=None,
        validator=check_list(check_string),
        intentionally_undocumented=True),
    dont_block: bool = REQ(default=False, validator=check_bool),
    narrow: Iterable[Sequence[str]] = REQ(default=[],
                                          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,
                                      validator=check_bool,
                                      intentionally_undocumented=True),
) -> HttpResponse:
    # Extract the Tornado handler from the request
    handler: AsyncDjangoHandler = request._tornado_handler

    if user_client is None:
        valid_user_client = request.client
    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,
        )

    result = fetch_events(events_query)
    if "extra_log_data" in result:
        request._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"])
コード例 #16
0
ファイル: users.py プロジェクト: wetlife/zulip
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 = '%s@%s' % (short_name, user_profile.realm.get_bot_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:
        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)
    if len(request.FILES) == 1:
        user_file = list(request.FILES.values())[0]
        upload_avatar_image(user_file, user_profile, bot_profile)
    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)
コード例 #17
0
def json_change_notify_settings(
        request: HttpRequest, user_profile: UserProfile,
        enable_stream_desktop_notifications: Optional[bool]=REQ(validator=check_bool, default=None),
        enable_stream_email_notifications: Optional[bool]=REQ(validator=check_bool, default=None),
        enable_stream_push_notifications: Optional[bool]=REQ(validator=check_bool, default=None),
        enable_stream_sounds: Optional[bool]=REQ(validator=check_bool, default=None),
        enable_desktop_notifications: Optional[bool]=REQ(validator=check_bool, default=None),
        enable_sounds: Optional[bool]=REQ(validator=check_bool, default=None),
        enable_offline_email_notifications: Optional[bool]=REQ(validator=check_bool, default=None),
        enable_offline_push_notifications: Optional[bool]=REQ(validator=check_bool, default=None),
        enable_online_push_notifications: Optional[bool]=REQ(validator=check_bool, default=None),
        enable_digest_emails: Optional[bool]=REQ(validator=check_bool, default=None),
        pm_content_in_desktop_notifications: Optional[bool]=REQ(validator=check_bool, default=None),
        realm_name_in_notifications: Optional[bool]=REQ(validator=check_bool, default=None)) \
        -> HttpResponse:
    result = {}

    # Stream notification settings.

    req_vars = {
        k: v
        for k, v in list(locals().items())
        if k in user_profile.notification_setting_types
    }

    for k, v in list(req_vars.items()):
        if v is not None and getattr(user_profile, k) != v:
            do_change_notification_settings(user_profile, k, v)
            result[k] = v

    return json_success(result)
コード例 #18
0
def remove_alert_words(request,
                       user_profile,
                       alert_words=REQ(validator=check_list(check_string),
                                       default=[])):
    do_remove_alert_words(user_profile, alert_words)
    return json_success()
コード例 #19
0
def get_events_internal(request: HttpRequest, handler: BaseHandler,
                        user_profile_id: int=REQ()) -> Union[HttpResponse, _RespondAsynchronously]:
    user_profile = get_user_profile_by_id(user_profile_id)
    request._email = user_profile.email
    process_client(request, user_profile, client_name="internal")
    return get_events_backend(request, user_profile, handler)
コード例 #20
0
def get_events_backend(request: HttpRequest, user_profile: UserProfile, handler: BaseHandler,
                       # 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, validator=check_bool,
                                                intentionally_undocumented=True),
                       client_gravatar: bool=REQ(default=False, validator=check_bool,
                                                 intentionally_undocumented=True),
                       all_public_streams: bool=REQ(default=False, validator=check_bool,
                                                    intentionally_undocumented=True),
                       event_types: Optional[str]=REQ(default=None, validator=check_list(check_string),
                                                      intentionally_undocumented=True),
                       dont_block: bool=REQ(default=False, validator=check_bool),
                       narrow: Iterable[Sequence[str]]=REQ(default=[], validator=check_list(None),
                                                           intentionally_undocumented=True),
                       lifespan_secs: int=REQ(default=0, converter=to_non_negative_int,
                                              intentionally_undocumented=True)
                       ) -> Union[HttpResponse, _RespondAsynchronously]:
    if user_client is None:
        valid_user_client = request.client
    else:
        valid_user_client = user_client

    events_query = dict(
        user_profile_id = user_profile.id,
        user_profile_email = user_profile.email,
        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,
            user_profile_email = user_profile.email,
            event_types = event_types,
            client_type_name = valid_user_client.name,
            apply_markdown = apply_markdown,
            client_gravatar = client_gravatar,
            all_public_streams = all_public_streams,
            queue_timeout = lifespan_secs,
            last_connection_time = time.time(),
            narrow = narrow)

    result = fetch_events(events_query)
    if "extra_log_data" in result:
        request._log_data['extra'] = result["extra_log_data"]

    if result["type"] == "async":
        handler._request = request
        return RespondAsynchronously
    if result["type"] == "error":
        raise result["exception"]
    return json_success(result["response"])
コード例 #21
0
def json_change_notify_settings(
    request,
    user_profile,
    enable_stream_desktop_notifications=REQ(validator=check_bool,
                                            default=None),
    enable_stream_sounds=REQ(validator=check_bool, default=None),
    enable_desktop_notifications=REQ(validator=check_bool, default=None),
    enable_sounds=REQ(validator=check_bool, default=None),
    enable_offline_email_notifications=REQ(validator=check_bool, default=None),
    enable_offline_push_notifications=REQ(validator=check_bool, default=None),
    enable_online_push_notifications=REQ(validator=check_bool, default=None),
    enable_digest_emails=REQ(validator=check_bool, default=None),
    pm_content_in_desktop_notifications=REQ(validator=check_bool,
                                            default=None)):
    # type: (HttpRequest, UserProfile, Optional[bool], Optional[bool], Optional[bool], Optional[bool], Optional[bool], Optional[bool], Optional[bool], Optional[bool], Optional[bool]) -> HttpResponse
    result = {}

    # Stream notification settings.

    if enable_stream_desktop_notifications is not None and \
            user_profile.enable_stream_desktop_notifications != enable_stream_desktop_notifications:
        do_change_enable_stream_desktop_notifications(
            user_profile, enable_stream_desktop_notifications)
        result[
            'enable_stream_desktop_notifications'] = enable_stream_desktop_notifications

    if enable_stream_sounds is not None and \
            user_profile.enable_stream_sounds != enable_stream_sounds:
        do_change_enable_stream_sounds(user_profile, enable_stream_sounds)
        result['enable_stream_sounds'] = enable_stream_sounds

    # PM and @-mention settings.

    if enable_desktop_notifications is not None and \
            user_profile.enable_desktop_notifications != enable_desktop_notifications:
        do_change_enable_desktop_notifications(user_profile,
                                               enable_desktop_notifications)
        result['enable_desktop_notifications'] = enable_desktop_notifications

    if enable_sounds is not None and \
            user_profile.enable_sounds != enable_sounds:
        do_change_enable_sounds(user_profile, enable_sounds)
        result['enable_sounds'] = enable_sounds

    if enable_offline_email_notifications is not None and \
            user_profile.enable_offline_email_notifications != enable_offline_email_notifications:
        do_change_enable_offline_email_notifications(
            user_profile, enable_offline_email_notifications)
        result[
            'enable_offline_email_notifications'] = enable_offline_email_notifications

    if enable_offline_push_notifications is not None and \
            user_profile.enable_offline_push_notifications != enable_offline_push_notifications:
        do_change_enable_offline_push_notifications(
            user_profile, enable_offline_push_notifications)
        result[
            'enable_offline_push_notifications'] = enable_offline_push_notifications

    if enable_online_push_notifications is not None and \
            user_profile.enable_online_push_notifications != enable_online_push_notifications:
        do_change_enable_online_push_notifications(
            user_profile, enable_online_push_notifications)
        result[
            'enable_online_push_notifications'] = enable_online_push_notifications

    if enable_digest_emails is not None and \
            user_profile.enable_digest_emails != enable_digest_emails:
        do_change_enable_digest_emails(user_profile, enable_digest_emails)
        result['enable_digest_emails'] = enable_digest_emails

    if pm_content_in_desktop_notifications is not None and \
            user_profile.pm_content_in_desktop_notifications != pm_content_in_desktop_notifications:
        do_change_pm_content_in_desktop_notifications(
            user_profile, pm_content_in_desktop_notifications)
        result[
            'pm_content_in_desktop_notifications'] = pm_content_in_desktop_notifications

    return json_success(result)
コード例 #22
0
def send_message_backend(
    request: HttpRequest,
    user_profile: UserProfile,
    message_type_name: str = REQ("type"),
    req_to: Optional[str] = REQ("to", default=None),
    forged_str: Optional[str] = REQ("forged",
                                    default=None,
                                    documentation_pending=True),
    topic_name: Optional[str] = REQ_topic(),
    message_content: str = REQ("content"),
    widget_content: Optional[str] = REQ(default=None,
                                        documentation_pending=True),
    realm_str: Optional[str] = REQ("realm_str",
                                   default=None,
                                   documentation_pending=True),
    local_id: Optional[str] = REQ(default=None),
    queue_id: Optional[str] = REQ(default=None),
    delivery_type: str = REQ("delivery_type",
                             default="send_now",
                             documentation_pending=True),
    defer_until: Optional[str] = REQ("deliver_at",
                                     default=None,
                                     documentation_pending=True),
    tz_guess: Optional[str] = REQ("tz_guess",
                                  default=None,
                                  documentation_pending=True),
) -> HttpResponse:

    # If req_to is None, then we default to an
    # empty list of recipients.
    message_to: Union[Sequence[int], Sequence[str]] = []

    if req_to is not None:
        if message_type_name == "stream":
            stream_indicator = extract_stream_indicator(req_to)

            # For legacy reasons check_send_message expects
            # a list of streams, instead of a single stream.
            #
            # Also, mypy can't detect that a single-item
            # list populated from a Union[int, str] is actually
            # a Union[Sequence[int], Sequence[str]].
            if isinstance(stream_indicator, int):
                message_to = [stream_indicator]
            else:
                message_to = [stream_indicator]
        else:
            message_to = extract_private_recipients(req_to)

    # Temporary hack: We're transitioning `forged` from accepting
    # `yes` to accepting `true` like all of our normal booleans.
    forged = forged_str is not None and forged_str in ["yes", "true"]

    client = request.client
    can_forge_sender = request.user.can_forge_sender
    if forged and not can_forge_sender:
        return json_error(_("User not authorized for this query"))

    realm = None
    if realm_str and realm_str != user_profile.realm.string_id:
        # The realm_str parameter does nothing, because it has to match
        # the user's realm - but we keep it around for backward compatibility.
        return json_error(_("User not authorized for this query"))

    if client.name in [
            "zephyr_mirror", "irc_mirror", "jabber_mirror", "JabberMirror"
    ]:
        # Here's how security works for mirroring:
        #
        # For private messages, the message must be (1) both sent and
        # received exclusively by users in your realm, and (2)
        # received by the forwarding user.
        #
        # For stream messages, the message must be (1) being forwarded
        # by an API superuser for your realm and (2) being sent to a
        # mirrored stream.
        #
        # The most important security checks are in
        # `create_mirrored_message_users` below, which checks the
        # same-realm constraint.
        if "sender" not in request.POST:
            return json_error(_("Missing sender"))
        if message_type_name != "private" and not can_forge_sender:
            return json_error(_("User not authorized for this query"))

        # For now, mirroring only works with recipient emails, not for
        # recipient user IDs.
        if not all(isinstance(to_item, str) for to_item in message_to):
            return json_error(
                _("Mirroring not allowed with recipient user IDs"))

        # We need this manual cast so that mypy doesn't complain about
        # create_mirrored_message_users not being able to accept a Sequence[int]
        # type parameter.
        message_to = cast(Sequence[str], message_to)

        try:
            mirror_sender = create_mirrored_message_users(
                request, user_profile, message_to)
        except InvalidMirrorInput:
            return json_error(_("Invalid mirrored message"))

        if client.name == "zephyr_mirror" and not user_profile.realm.is_zephyr_mirror_realm:
            return json_error(
                _("Zephyr mirroring is not allowed in this organization"))
        sender = mirror_sender
    else:
        if "sender" in request.POST:
            return json_error(_("Invalid mirrored message"))
        sender = user_profile

    if (delivery_type == "send_later"
            or delivery_type == "remind") and defer_until is None:
        return json_error(
            _("Missing deliver_at in a request for delayed message delivery"))

    if (delivery_type == "send_later"
            or delivery_type == "remind") and defer_until is not None:
        return handle_deferred_message(
            sender,
            client,
            message_type_name,
            message_to,
            topic_name,
            message_content,
            delivery_type,
            defer_until,
            tz_guess,
            forwarder_user_profile=user_profile,
            realm=realm,
        )

    ret = check_send_message(
        sender,
        client,
        message_type_name,
        message_to,
        topic_name,
        message_content,
        forged=forged,
        forged_timestamp=request.POST.get("time"),
        forwarder_user_profile=user_profile,
        realm=realm,
        local_id=local_id,
        sender_queue_id=queue_id,
        widget_content=widget_content,
    )
    return json_success({"id": ret})
コード例 #23
0
def json_change_notify_settings(
    request: HttpRequest,
    user_profile: UserProfile,
    enable_stream_desktop_notifications: Optional[bool] = REQ(
        json_validator=check_bool, default=None),
    enable_stream_email_notifications: Optional[bool] = REQ(
        json_validator=check_bool, default=None),
    enable_stream_push_notifications: Optional[bool] = REQ(
        json_validator=check_bool, default=None),
    enable_stream_audible_notifications: Optional[bool] = REQ(
        json_validator=check_bool, default=None),
    wildcard_mentions_notify: Optional[bool] = REQ(json_validator=check_bool,
                                                   default=None),
    notification_sound: Optional[str] = REQ(default=None),
    enable_desktop_notifications: Optional[bool] = REQ(
        json_validator=check_bool, default=None),
    enable_sounds: Optional[bool] = REQ(json_validator=check_bool,
                                        default=None),
    enable_offline_email_notifications: Optional[bool] = REQ(
        json_validator=check_bool, default=None),
    enable_offline_push_notifications: Optional[bool] = REQ(
        json_validator=check_bool, default=None),
    enable_online_push_notifications: Optional[bool] = REQ(
        json_validator=check_bool, default=None),
    enable_digest_emails: Optional[bool] = REQ(json_validator=check_bool,
                                               default=None),
    enable_login_emails: Optional[bool] = REQ(json_validator=check_bool,
                                              default=None),
    enable_marketing_emails: Optional[bool] = REQ(json_validator=check_bool,
                                                  default=None),
    message_content_in_email_notifications: Optional[bool] = REQ(
        json_validator=check_bool, default=None),
    pm_content_in_desktop_notifications: Optional[bool] = REQ(
        json_validator=check_bool, default=None),
    desktop_icon_count_display: Optional[int] = REQ(json_validator=check_int,
                                                    default=None),
    realm_name_in_notifications: Optional[bool] = REQ(
        json_validator=check_bool, default=None),
    presence_enabled: Optional[bool] = REQ(json_validator=check_bool,
                                           default=None),
) -> HttpResponse:
    result = {}

    # Stream notification settings.

    if (notification_sound is not None
            and notification_sound not in get_available_notification_sounds()
            and notification_sound != "none"):
        raise JsonableError(
            _("Invalid notification sound '{}'").format(notification_sound))

    req_vars = {
        k: v
        for k, v in list(locals().items())
        if k in user_profile.notification_setting_types
    }

    for k, v in list(req_vars.items()):
        if v is not None and getattr(user_profile, k) != v:
            do_change_notification_settings(user_profile,
                                            k,
                                            v,
                                            acting_user=user_profile)
            result[k] = v

    return json_success(result)
コード例 #24
0
def zcommand_backend(
    request: HttpRequest,
    user_profile: UserProfile,
    command: str = REQ("command")) -> HttpResponse:
    return json_success(process_zcommands(command, user_profile))
コード例 #25
0
ファイル: message_edit.py プロジェクト: thedeveloperr/zulip
def update_message_backend(
    request: HttpRequest,
    user_profile: UserMessage,
    message_id: int = REQ(converter=to_non_negative_int, path_only=True),
    stream_id: Optional[int] = REQ(converter=to_non_negative_int,
                                   default=None),
    topic_name: Optional[str] = REQ_topic(),
    propagate_mode: Optional[str] = REQ(
        default="change_one",
        str_validator=check_string_in(PROPAGATE_MODE_VALUES)),
    send_notification_to_old_thread: bool = REQ(default=True,
                                                validator=check_bool),
    send_notification_to_new_thread: bool = REQ(default=True,
                                                validator=check_bool),
    content: Optional[str] = REQ(default=None)
) -> HttpResponse:
    if not user_profile.realm.allow_message_editing:
        return json_error(
            _("Your organization has turned off message editing"))

    if propagate_mode != "change_one" and topic_name is None and stream_id is None:
        return json_error(_("Invalid propagate_mode without topic edit"))

    message, ignored_user_message = access_message(user_profile, message_id)
    is_no_topic_msg = (message.topic_name() == "(no topic)")

    # You only have permission to edit a message if:
    # you change this value also change those two parameters in message_edit.js.
    # 1. You sent it, OR:
    # 2. This is a topic-only edit for a (no topic) message, OR:
    # 3. This is a topic-only edit and you are an admin, OR:
    # 4. This is a topic-only edit and your realm allows users to edit topics.
    if message.sender == user_profile:
        pass
    elif (content is
          None) and (is_no_topic_msg or user_profile.is_realm_admin
                     or user_profile.realm.allow_community_topic_editing):
        pass
    else:
        raise JsonableError(
            _("You don't have permission to edit this message"))

    # If there is a change to the content, check that it hasn't been too long
    # Allow an extra 20 seconds since we potentially allow editing 15 seconds
    # past the limit, and in case there are network issues, etc. The 15 comes
    # from (min_seconds_to_edit + seconds_left_buffer) in message_edit.js; if
    # you change this value also change those two parameters in message_edit.js.
    edit_limit_buffer = 20
    if content is not None and user_profile.realm.message_content_edit_limit_seconds > 0:
        deadline_seconds = user_profile.realm.message_content_edit_limit_seconds + edit_limit_buffer
        if (timezone_now() - message.date_sent) > datetime.timedelta(
                seconds=deadline_seconds):
            raise JsonableError(
                _("The time limit for editing this message has passed"))

    # If there is a change to the topic, check that the user is allowed to
    # edit it and that it has not been too long. If this is not the user who
    # sent the message, they are not the admin, and the time limit for editing
    # topics is passed, raise an error.
    if content is None and message.sender != user_profile and not user_profile.is_realm_admin and \
            not is_no_topic_msg:
        deadline_seconds = Realm.DEFAULT_COMMUNITY_TOPIC_EDITING_LIMIT_SECONDS + edit_limit_buffer
        if (timezone_now() - message.date_sent) > datetime.timedelta(
                seconds=deadline_seconds):
            raise JsonableError(
                _("The time limit for editing this message has passed"))

    if topic_name is None and content is None and stream_id is None:
        return json_error(_("Nothing to change"))
    if topic_name is not None:
        topic_name = topic_name.strip()
        if topic_name == "":
            raise JsonableError(_("Topic can't be empty"))
    rendered_content = None
    links_for_embed: Set[str] = set()
    prior_mention_user_ids: Set[int] = set()
    mention_user_ids: Set[int] = set()
    mention_data: Optional[bugdown.MentionData] = None
    if content is not None:
        content = content.strip()
        if content == "":
            content = "(deleted)"
        content = truncate_body(content)

        mention_data = bugdown.MentionData(
            realm_id=user_profile.realm.id,
            content=content,
        )
        user_info = get_user_info_for_message_updates(message.id)
        prior_mention_user_ids = user_info['mention_user_ids']

        # We render the message using the current user's realm; since
        # the cross-realm bots never edit messages, this should be
        # always correct.
        # Note: If rendering fails, the called code will raise a JsonableError.
        rendered_content = render_incoming_message(
            message,
            content,
            user_info['message_user_ids'],
            user_profile.realm,
            mention_data=mention_data)
        links_for_embed |= message.links_for_preview

        mention_user_ids = message.mentions_user_ids

    new_stream = None
    old_stream = None
    number_changed = 0

    if stream_id is not None:
        if not user_profile.is_realm_admin:
            raise JsonableError(
                _("You don't have permission to move this message"))
        if content is not None:
            raise JsonableError(
                _("Cannot change message content while changing stream"))

        old_stream = get_stream_by_id(message.recipient.type_id)
        new_stream = get_stream_by_id(stream_id)

        if not (old_stream.is_public() and new_stream.is_public()):
            # We'll likely decide to relax this condition in the
            # future; it just requires more care with details like the
            # breadcrumb messages.
            raise JsonableError(_("Streams must be public"))

    number_changed = do_update_message(
        user_profile, message, new_stream, topic_name, propagate_mode,
        send_notification_to_old_thread, send_notification_to_new_thread,
        content, rendered_content, prior_mention_user_ids, mention_user_ids,
        mention_data)

    # Include the number of messages changed in the logs
    request._log_data['extra'] = f"[{number_changed}]"
    if links_for_embed:
        event_data = {
            'message_id': message.id,
            'message_content': message.content,
            # The choice of `user_profile.realm_id` rather than
            # `sender.realm_id` must match the decision made in the
            # `render_incoming_message` call earlier in this function.
            'message_realm_id': user_profile.realm_id,
            'urls': links_for_embed
        }
        queue_json_publish('embed_links', event_data)
    return json_success()
コード例 #26
0
def update_display_settings_backend(
        request: HttpRequest, user_profile: UserProfile,
        twenty_four_hour_time: Optional[bool]=REQ(validator=check_bool, default=None),
        dense_mode: Optional[bool]=REQ(validator=check_bool, default=None),
        starred_message_counts: Optional[bool]=REQ(validator=check_bool, default=None),
        fluid_layout_width: Optional[bool]=REQ(validator=check_bool, default=None),
        high_contrast_mode: Optional[bool]=REQ(validator=check_bool, default=None),
        night_mode: Optional[bool]=REQ(validator=check_bool, default=None),
        translate_emoticons: Optional[bool]=REQ(validator=check_bool, default=None),
        default_language: Optional[bool]=REQ(validator=check_string, default=None),
        left_side_userlist: Optional[bool]=REQ(validator=check_bool, default=None),
        emojiset: Optional[str]=REQ(validator=check_string, default=None),
        demote_inactive_streams: Optional[int]=REQ(validator=check_int, default=None),
        timezone: Optional[str]=REQ(validator=check_string, default=None)) -> HttpResponse:

    if (default_language is not None and
            default_language not in get_available_language_codes()):
        raise JsonableError(_("Invalid language '%s'") % (default_language,))

    if (timezone is not None and
            timezone not in get_all_timezones()):
        raise JsonableError(_("Invalid timezone '%s'") % (timezone,))

    if (emojiset is not None and
            emojiset not in [emojiset_choice['key'] for emojiset_choice in UserProfile.emojiset_choices()]):
        raise JsonableError(_("Invalid emojiset '%s'") % (emojiset,))

    if (demote_inactive_streams is not None and
            demote_inactive_streams not in UserProfile.DEMOTE_STREAMS_CHOICES):
        raise JsonableError(_("Invalid setting value '%s'") % (demote_inactive_streams,))

    request_settings = {k: v for k, v in list(locals().items()) if k in user_profile.property_types}
    result = {}  # type: Dict[str, Any]
    for k, v in list(request_settings.items()):
        if v is not None and getattr(user_profile, k) != v:
            do_set_user_display_setting(user_profile, k, v)
            result[k] = v

    return json_success(result)
コード例 #27
0
ファイル: users.py プロジェクト: shekhirin/zulip
def add_bot_backend(request,
                    user_profile,
                    full_name=REQ(),
                    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_type, text_type, Optional[text_type], Optional[text_type], Optional[bool]) -> HttpResponse
    short_name += "-bot"
    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 = stream_or_none(default_sending_stream_name,
                                                user_profile.realm)
    if (default_sending_stream and not default_sending_stream.is_public() and
            not subscribed_to_stream(user_profile, default_sending_stream)):
        return json_error(_('Insufficient permission'))

    default_events_register_stream = None
    if default_events_register_stream_name is not None:
        default_events_register_stream = stream_or_none(
            default_events_register_stream_name, user_profile.realm)
    if (default_events_register_stream
            and not default_events_register_stream.is_public()
            and not subscribed_to_stream(user_profile,
                                         default_events_register_stream)):
        return json_error(_('Insufficient permission'))

    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)
コード例 #28
0
def change_enter_sends(request: HttpRequest, user_profile: UserProfile,
                       enter_sends: bool=REQ(validator=check_bool)) -> HttpResponse:
    do_change_enter_sends(user_profile, enter_sends)
    return json_success()
コード例 #29
0
def get_messages_backend(request: HttpRequest, user_profile: UserProfile,
                         anchor_val: Optional[str]=REQ(
                             'anchor', str_validator=check_string, default=None),
                         num_before: int=REQ(converter=to_non_negative_int),
                         num_after: int=REQ(converter=to_non_negative_int),
                         narrow: OptionalNarrowListT=REQ('narrow', converter=narrow_parameter, default=None),
                         use_first_unread_anchor_val: bool=REQ('use_first_unread_anchor',
                                                               validator=check_bool, default=False),
                         client_gravatar: bool=REQ(validator=check_bool, default=False),
                         apply_markdown: bool=REQ(validator=check_bool, default=True)) -> HttpResponse:
    anchor = parse_anchor_value(anchor_val, use_first_unread_anchor_val)
    if num_before + num_after > MAX_MESSAGES_PER_FETCH:
        return json_error(_("Too many messages requested (maximum {}).").format(
            MAX_MESSAGES_PER_FETCH,
        ))

    if user_profile.realm.email_address_visibility != Realm.EMAIL_ADDRESS_VISIBILITY_EVERYONE:
        # If email addresses are only available to administrators,
        # clients cannot compute gravatars, so we force-set it to false.
        client_gravatar = False

    include_history = ok_to_include_history(narrow, user_profile)
    if include_history:
        # The initial query in this case doesn't use `zerver_usermessage`,
        # and isn't yet limited to messages the user is entitled to see!
        #
        # This is OK only because we've made sure this is a narrow that
        # will cause us to limit the query appropriately later.
        # See `ok_to_include_history` for details.
        need_message = True
        need_user_message = False
    elif narrow is None:
        # We need to limit to messages the user has received, but we don't actually
        # need any fields from Message
        need_message = False
        need_user_message = True
    else:
        need_message = True
        need_user_message = True

    query, inner_msg_id_col = get_base_query_for_search(
        user_profile=user_profile,
        need_message=need_message,
        need_user_message=need_user_message,
    )

    query, is_search = add_narrow_conditions(
        user_profile=user_profile,
        inner_msg_id_col=inner_msg_id_col,
        query=query,
        narrow=narrow,
    )

    if narrow is not None:
        # Add some metadata to our logging data for narrows
        verbose_operators = []
        for term in narrow:
            if term['operator'] == "is":
                verbose_operators.append("is:" + term['operand'])
            else:
                verbose_operators.append(term['operator'])
        request._log_data['extra'] = "[{}]".format(",".join(verbose_operators))

    sa_conn = get_sqlalchemy_connection()

    if anchor is None:
        # The use_first_unread_anchor code path
        anchor = find_first_unread_anchor(
            sa_conn,
            user_profile,
            narrow,
        )

    anchored_to_left = (anchor == 0)

    # Set value that will be used to short circuit the after_query
    # altogether and avoid needless conditions in the before_query.
    anchored_to_right = (anchor >= LARGER_THAN_MAX_MESSAGE_ID)
    if anchored_to_right:
        num_after = 0

    first_visible_message_id = get_first_visible_message_id(user_profile.realm)
    query = limit_query_to_range(
        query=query,
        num_before=num_before,
        num_after=num_after,
        anchor=anchor,
        anchored_to_left=anchored_to_left,
        anchored_to_right=anchored_to_right,
        id_col=inner_msg_id_col,
        first_visible_message_id=first_visible_message_id,
    )

    main_query = alias(query)
    query = select(main_query.c, None, main_query).order_by(column("message_id").asc())
    # This is a hack to tag the query we use for testing
    query = query.prefix_with("/* get_messages */")
    rows = list(sa_conn.execute(query).fetchall())

    query_info = post_process_limited_query(
        rows=rows,
        num_before=num_before,
        num_after=num_after,
        anchor=anchor,
        anchored_to_left=anchored_to_left,
        anchored_to_right=anchored_to_right,
        first_visible_message_id=first_visible_message_id,
    )

    rows = query_info['rows']

    # The following is a little messy, but ensures that the code paths
    # are similar regardless of the value of include_history.  The
    # 'user_messages' dictionary maps each message to the user's
    # UserMessage object for that message, which we will attach to the
    # rendered message dict before returning it.  We attempt to
    # bulk-fetch rendered message dicts from remote cache using the
    # 'messages' list.
    message_ids: List[int] = []
    user_message_flags: Dict[int, List[str]] = {}
    if include_history:
        message_ids = [row[0] for row in rows]

        # TODO: This could be done with an outer join instead of two queries
        um_rows = UserMessage.objects.filter(user_profile=user_profile,
                                             message__id__in=message_ids)
        user_message_flags = {um.message_id: um.flags_list() for um in um_rows}

        for message_id in message_ids:
            if message_id not in user_message_flags:
                user_message_flags[message_id] = ["read", "historical"]
    else:
        for row in rows:
            message_id = row[0]
            flags = row[1]
            user_message_flags[message_id] = UserMessage.flags_list_for_flags(flags)
            message_ids.append(message_id)

    search_fields: Dict[int, Dict[str, str]] = dict()
    if is_search:
        for row in rows:
            message_id = row[0]
            (topic_name, rendered_content, content_matches, topic_matches) = row[-4:]

            try:
                search_fields[message_id] = get_search_fields(rendered_content, topic_name,
                                                              content_matches, topic_matches)
            except UnicodeDecodeError as err:  # nocoverage
                # No coverage for this block since it should be
                # impossible, and we plan to remove it once we've
                # debugged the case that makes it happen.
                raise Exception(str(err), message_id, narrow)

    message_list = messages_for_ids(
        message_ids=message_ids,
        user_message_flags=user_message_flags,
        search_fields=search_fields,
        apply_markdown=apply_markdown,
        client_gravatar=client_gravatar,
        allow_edit_history=user_profile.realm.allow_edit_history,
    )

    statsd.incr('loaded_old_messages', len(message_list))

    ret = dict(
        messages=message_list,
        result='success',
        msg='',
        found_anchor=query_info['found_anchor'],
        found_oldest=query_info['found_oldest'],
        found_newest=query_info['found_newest'],
        history_limited=query_info['history_limited'],
        anchor=anchor,
    )
    return json_success(ret)
コード例 #30
0
ファイル: users.py プロジェクト: wetlife/zulip
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 UserProfile.DoesNotExist:
        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, user_profile)
    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)
        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)