コード例 #1
0
def list_to_streams(
    streams_raw: Collection[StreamDict],
    user_profile: UserProfile,
    autocreate: bool = False,
    admin_access_required: bool = False,
) -> Tuple[List[Stream], List[Stream]]:
    """Converts list of dicts to a list of Streams, validating input in the process

    For each stream name, we validate it to ensure it meets our
    requirements for a proper stream name using check_stream_name.

    This function in autocreate mode should be atomic: either an exception will be raised
    during a precheck, or all the streams specified will have been created if applicable.

    @param streams_raw The list of stream dictionaries to process;
      names should already be stripped of whitespace by the caller.
    @param user_profile The user for whom we are retrieving the streams
    @param autocreate Whether we should create streams if they don't already exist
    """
    # Validate all streams, getting extant ones, then get-or-creating the rest.

    stream_set = {stream_dict["name"] for stream_dict in streams_raw}

    for stream_name in stream_set:
        # Stream names should already have been stripped by the
        # caller, but it makes sense to verify anyway.
        assert stream_name == stream_name.strip()
        check_stream_name(stream_name)

    existing_streams: List[Stream] = []
    missing_stream_dicts: List[StreamDict] = []
    existing_stream_map = bulk_get_streams(user_profile.realm, stream_set)

    if admin_access_required:
        existing_recipient_ids = [
            stream.recipient_id for stream in existing_stream_map.values()
        ]
        subs = Subscription.objects.filter(
            user_profile=user_profile,
            recipient_id__in=existing_recipient_ids,
            active=True)
        sub_map = {sub.recipient_id: sub for sub in subs}
        for stream in existing_stream_map.values():
            sub = sub_map.get(stream.recipient_id, None)
            check_stream_access_for_delete_or_update(user_profile, stream, sub)

    message_retention_days_not_none = False
    web_public_stream_requested = False
    for stream_dict in streams_raw:
        stream_name = stream_dict["name"]
        stream = existing_stream_map.get(stream_name.lower())
        if stream is None:
            if stream_dict.get("message_retention_days", None) is not None:
                message_retention_days_not_none = True
            missing_stream_dicts.append(stream_dict)

            if autocreate and stream_dict["is_web_public"]:
                web_public_stream_requested = True
        else:
            existing_streams.append(stream)

    if len(missing_stream_dicts) == 0:
        # This is the happy path for callers who expected all of these
        # streams to exist already.
        created_streams: List[Stream] = []
    else:
        # autocreate=True path starts here
        for stream_dict in missing_stream_dicts:
            invite_only = stream_dict.get("invite_only", False)
            if invite_only and not user_profile.can_create_private_streams():
                raise JsonableError(_("Insufficient permission"))
            if not invite_only and not user_profile.can_create_public_streams(
            ):
                raise JsonableError(_("Insufficient permission"))

        if not autocreate:
            raise JsonableError(
                _("Stream(s) ({}) do not exist").format(
                    ", ".join(stream_dict["name"]
                              for stream_dict in missing_stream_dicts), ))

        if web_public_stream_requested:
            if not user_profile.realm.web_public_streams_enabled():
                raise JsonableError(_("Web public streams are not enabled."))
            if not user_profile.can_create_web_public_streams():
                # We set create_web_public_stream_policy to allow only organization owners
                # to create web-public streams, because of their sensitive nature.
                raise JsonableError(_("Insufficient permission"))

        if message_retention_days_not_none:
            if not user_profile.is_realm_owner:
                raise OrganizationOwnerRequired()

            user_profile.realm.ensure_not_on_limited_plan()

        # We already filtered out existing streams, so dup_streams
        # will normally be an empty list below, but we protect against somebody
        # else racing to create the same stream.  (This is not an entirely
        # paranoid approach, since often on Zulip two people will discuss
        # creating a new stream, and both people eagerly do it.)
        created_streams, dup_streams = create_streams_if_needed(
            realm=user_profile.realm,
            stream_dicts=missing_stream_dicts,
            acting_user=user_profile)
        existing_streams += dup_streams

    return existing_streams, created_streams
コード例 #2
0
def update_stream_backend(
    request: HttpRequest,
    user_profile: UserProfile,
    stream_id: int,
    description: Optional[str] = REQ(str_validator=check_capped_string(
        Stream.MAX_DESCRIPTION_LENGTH),
                                     default=None),
    is_private: Optional[bool] = REQ(json_validator=check_bool, default=None),
    is_announcement_only: Optional[bool] = REQ(json_validator=check_bool,
                                               default=None),
    stream_post_policy: Optional[int] = REQ(json_validator=check_int_in(
        Stream.STREAM_POST_POLICY_TYPES),
                                            default=None),
    history_public_to_subscribers: Optional[bool] = REQ(
        json_validator=check_bool, default=None),
    is_web_public: Optional[bool] = REQ(json_validator=check_bool,
                                        default=None),
    new_name: Optional[str] = REQ(default=None),
    message_retention_days: Optional[Union[int, str]] = REQ(
        json_validator=check_string_or_int, default=None),
) -> HttpResponse:
    # We allow realm administrators to to update the stream name and
    # description even for private streams.
    (stream, sub) = access_stream_for_delete_or_update(user_profile, stream_id)

    if message_retention_days is not None:
        if not user_profile.is_realm_owner:
            raise OrganizationOwnerRequired()
        user_profile.realm.ensure_not_on_limited_plan()
        new_message_retention_days_value = parse_message_retention_days(
            message_retention_days,
            Stream.MESSAGE_RETENTION_SPECIAL_VALUES_MAP)
        do_change_stream_message_retention_days(
            stream, user_profile, new_message_retention_days_value)

    if description is not None:
        if "\n" in description:
            # We don't allow newline characters in stream descriptions.
            description = description.replace("\n", " ")
        do_change_stream_description(stream,
                                     description,
                                     acting_user=user_profile)
    if new_name is not None:
        new_name = new_name.strip()
        if stream.name == new_name:
            raise JsonableError(_("Stream already has that name!"))
        if stream.name.lower() != new_name.lower():
            # Check that the stream name is available (unless we are
            # are only changing the casing of the stream name).
            check_stream_name_available(user_profile.realm, new_name)
        do_rename_stream(stream, new_name, user_profile)
    if is_announcement_only is not None:
        # is_announcement_only is a legacy way to specify
        # stream_post_policy.  We can probably just delete this code,
        # since we're not aware of clients that used it, but we're
        # keeping it for backwards-compatibility for now.
        stream_post_policy = Stream.STREAM_POST_POLICY_EVERYONE
        if is_announcement_only:
            stream_post_policy = Stream.STREAM_POST_POLICY_ADMINS
    if stream_post_policy is not None:
        do_change_stream_post_policy(stream,
                                     stream_post_policy,
                                     acting_user=user_profile)

    # But we require even realm administrators to be actually
    # subscribed to make a private stream public.
    if is_private is not None:
        default_stream_ids = {
            s.id
            for s in get_default_streams_for_realm(stream.realm_id)
        }
        (stream, sub) = access_stream_by_id(user_profile, stream_id)
        if is_private and stream.id in default_stream_ids:
            raise JsonableError(_("Default streams cannot be made private."))

    if is_web_public:
        # Enforce restrictions on creating web-public streams.
        if not user_profile.realm.web_public_streams_enabled():
            raise JsonableError(_("Web-public streams are not enabled."))
        if not user_profile.can_create_web_public_streams():
            raise JsonableError(_("Insufficient permission"))
        # Forbid parameter combinations that are inconsistent
        if is_private or history_public_to_subscribers is False:
            raise JsonableError(_("Invalid parameters"))

    if is_private is not None or is_web_public is not None:
        do_change_stream_permission(
            stream,
            invite_only=is_private,
            history_public_to_subscribers=history_public_to_subscribers,
            is_web_public=is_web_public,
            acting_user=user_profile,
        )
    return json_success(request)