Ejemplo n.º 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