Repository: zulip/zulip

Issue:
streams: Fixes issue in stream creation.
This PR fixes issue in stream creation.
<!-- Describe your pull request here.-->

[CZO discussion](https://chat.zulip.org/#narrow/stream/9-issues/topic/Issue.20in.20stream.20creation)

<!-- If the PR makes UI changes, always include one or more still screenshots to demonstrate your changes. If it seems helpful, add a screen capture of the new functionality as well.

Tooling tips: https://zulip.readthedocs.io/en/latest/tutorials/screenshot-and-gif-software.html
-->

**Screenshots and screen captures:**


Stream Creation form when user cannot subscribe other users.
![Screenshot from 2023-05-11 13-23-55](https://github.com/zulip/zulip/assets/99073049/d290be46-455c-4176-aa69-87ec2916cc2d)
![Screenshot from 2023-05-11 13-23-52](https://github.com/zulip/zulip/assets/99073049/0585cdaa-e60a-4314-bdf7-bf641123c601)


Stream edit when user cannot subscribe other users.
![Screenshot from 2023-05-11 13-24-11](https://github.com/zulip/zulip/assets/99073049/f954d24c-af36-4ea7-9ecb-ea1ee30b8e84)


<details>
<summary>Self-review checklist</summary>

<!-- Prior to submitting a PR, follow our step-by-step guide to review your own code:
https://zulip.readthedocs.io/en/latest/contributing/code-reviewing.html#how-to-review-code -->

<!-- Once you create the PR, check off all the steps below that you have completed.
If any of these steps are not relevant or you have not completed, leave them unchecked.-->

- [x] [Self-reviewed](https://zulip.readthedocs.io/en/latest/contributing/code-reviewing.html#how-to-review-code) the changes for clarity and maintainability
      (variable names, code reuse, readability, etc.).

Communicate decisions, questions, and potential concerns.

- [x] Explains differences from previous plans (e.g., issue description).
- [x] Highlights technical choices and bugs encountered.
- [x] Calls out remaining decisions and concerns.
- [x] Automated tests verify logic where appropriate.

Individual commits are ready for review (see [commit discipline](https://zulip.readthedocs.io/en/latest/contributing/commit-discipline.html)).

- [x] Each commit is a coherent idea.
- [x] Commit message(s) explain reasoning and motivation for changes.

Completed manual review and testing of the following:

- [x] Visual appearance of the changes.
- [x] Responsiveness and internationalization.
- [x] Strings and tooltips.
- [x] End-to-end functionality of buttons, interactions and flows.
- [x] Corner cases, error conditions, and easily imagined bugs.
</details>



Discussion:
@sahil839 please review this PR. 
> I am not sure we discussed about hiding or disabling the add subscribers widget in stream creation form. I like the idea of hiding it, but am not sure what is the best solution. @alya FYI.

Hm, I think we need some way to communicate to the user that they don't have permissions to add subscribers. It's probably fine to disable the user input field, "Add" button, and the "Add all users" button, with the same tooltip as on the the "Edit stream" panel. That might be simplest, and then we won't have to adjust the filter location?
@Ujjawal3 can you update the PR to fix the tests and post a comment mentioning the updates when it is ready for review? Would be good to fix this issue soon.
> @Ujjawal3 can you update the PR to fix the tests and post a comment mentioning the updates when it is ready for review? Would be good to fix this issue soon.

I was busy with my college exams since last week so could not do much contributions. But tomorrow they will finish so will work after that.
@sahil839 I have updated the PR and addressed all your comments. Also now add subscriber section is disabled in stream creation form. Please have a look.
@sahil839 I have updated the PR and addressed your comments. Please have a look.

Resolving patch:
diff --git a/zerver/views/streams.py b/zerver/views/streams.py
--- a/zerver/views/streams.py
+++ b/zerver/views/streams.py
@@ -619,6 +619,21 @@ def add_subscriptions_backend(
 
         stream_dicts.append(stream_dict_copy)
 
+    is_subscribing_other_users = False
+    if len(principals) > 0 and not all(user_id == user_profile.id for user_id in principals):
+        is_subscribing_other_users = True
+
+    if is_subscribing_other_users:
+        if not user_profile.can_subscribe_other_users():
+            # Guest users case will not be handled here as it will
+            # be handled by the decorator above.
+            raise JsonableError(_("Insufficient permission"))
+        subscribers = {
+            principal_to_user_profile(user_profile, principal) for principal in principals
+        }
+    else:
+        subscribers = {user_profile}
+
     # Validation of the streams arguments, including enforcement of
     # can_create_streams policy and check_stream_name policy is inside
     # list_to_streams.
@@ -635,20 +650,14 @@ def add_subscriptions_backend(
     # Newly created streams are also authorized for the creator
     streams = authorized_streams + created_streams
 
-    if len(principals) > 0:
-        if realm.is_zephyr_mirror_realm and not all(stream.invite_only for stream in streams):
-            raise JsonableError(
-                _("You can only invite other Zephyr mirroring users to private streams.")
-            )
-        if not user_profile.can_subscribe_other_users():
-            # Guest users case will not be handled here as it will
-            # be handled by the decorator above.
-            raise JsonableError(_("Insufficient permission"))
-        subscribers = {
-            principal_to_user_profile(user_profile, principal) for principal in principals
-        }
-    else:
-        subscribers = {user_profile}
+    if (
+        is_subscribing_other_users
+        and realm.is_zephyr_mirror_realm
+        and not all(stream.invite_only for stream in streams)
+    ):
+        raise JsonableError(
+            _("You can only invite other Zephyr mirroring users to private streams.")
+        )
 
     (subscribed, already_subscribed) = bulk_add_subscriptions(
         realm, streams, subscribers, acting_user=user_profile, color_map=color_map


Test patch:
diff --git a/zerver/tests/test_subs.py b/zerver/tests/test_subs.py
--- a/zerver/tests/test_subs.py
+++ b/zerver/tests/test_subs.py
@@ -4422,7 +4422,7 @@ def test_multi_user_subscription(self) -> None:
 
         # Now add ourselves
         with self.capture_send_event_calls(expected_num_events=2) as events:
-            with self.assert_database_query_count(13):
+            with self.assert_database_query_count(12):
                 self.common_subscribe_to_streams(
                     self.test_user,
                     streams_to_sub,
@@ -4731,28 +4731,36 @@ def test_users_getting_remove_peer_event(self) -> None:
 
     def test_bulk_subscribe_MIT(self) -> None:
         mit_user = self.mit_user("starnine")
+        num_streams = 15
 
         realm = get_realm("zephyr")
-        stream_names = [f"stream_{i}" for i in range(40)]
+        stream_names = [f"stream_{i}" for i in range(num_streams)]
         streams = [self.make_stream(stream_name, realm=realm) for stream_name in stream_names]
 
         for stream in streams:
             stream.is_in_zephyr_realm = True
             stream.save()
 
-        # Make sure Zephyr mirroring realms such as MIT do not get
-        # any tornado subscription events
-        with self.capture_send_event_calls(expected_num_events=0):
-            with self.assert_database_query_count(5):
+        # Verify that peer_event events are never sent in Zephyr
+        # realm. This does generate stream creation events from
+        # send_stream_creation_events_for_private_streams.
+        with self.capture_send_event_calls(expected_num_events=num_streams + 1) as events:
+            with self.assert_database_query_count(num_streams + 12):
                 self.common_subscribe_to_streams(
                     mit_user,
                     stream_names,
                     dict(principals=orjson.dumps([mit_user.id]).decode()),
                     subdomain="zephyr",
-                    allow_fail=True,
                 )
+            # num_streams stream creation events:
+            self.assertEqual(
+                {(event["event"]["type"], event["event"]["op"]) for event in events[0:num_streams]},
+                {("stream", "create")},
+            )
+            # Followed by one subscription event:
+            self.assertEqual(events[num_streams]["event"]["type"], "subscription")
 
-        with self.capture_send_event_calls(expected_num_events=0):
+        with self.capture_send_event_calls(expected_num_events=1):
             bulk_remove_subscriptions(
                 realm,
                 users=[mit_user],
@@ -4760,6 +4768,31 @@ def test_bulk_subscribe_MIT(self) -> None:
                 acting_user=None,
             )
 
+    def test_subscribe_others_to_public_stream_in_zephyr_realm(self) -> None:
+        """
+        Users cannot be subscribed to public streams by other users in zephyr realm.
+        """
+        starnine = self.mit_user("starnine")
+        espuser = self.mit_user("espuser")
+
+        realm = get_realm("zephyr")
+        stream = self.make_stream("stream_1", realm=realm)
+        stream.is_in_zephyr_realm = True
+        stream.save()
+
+        result = self.common_subscribe_to_streams(
+            starnine,
+            ["stream_1"],
+            dict(principals=orjson.dumps([starnine.id, espuser.id]).decode()),
+            subdomain="zephyr",
+            allow_fail=True,
+        )
+        self.assert_json_error(
+            result,
+            "You can only invite other Zephyr mirroring users to private streams.",
+            status_code=400,
+        )
+
     def test_bulk_subscribe_many(self) -> None:
         # Create a whole bunch of streams
         streams = [f"stream_{i}" for i in range(30)]