subscribe: Allow web public stream creation via the API.

User can now create web public stream via the /subscribe API.
So, when a web public stream present in the API request does not
exist, it will be created now by specifying the is_web_public
parameter. The parameter would have been ignored without this
commit.
This commit is contained in:
Aman Agrawal 2021-05-20 08:50:17 +00:00 committed by Tim Abbott
parent eb62693d26
commit 6a78112940
6 changed files with 98 additions and 4 deletions

View File

@ -11,6 +11,11 @@ below features are supported.
## Changes in Zulip 5.0
**Feature level 98**
* [`POST /subscribe`](/api/subscribe): Added `is_web_public` parameter
for requesting the creation of a web-public stream.
**Feature level 97**
* `GET /realm/emoji`, `POST /realm/emoji/{emoji_name}`, [`GET

View File

@ -33,7 +33,7 @@ DESKTOP_WARNING_VERSION = "5.4.3"
# Changes should be accompanied by documentation explaining what the
# new level means in templates/zerver/api/changelog.md, as well as
# "**Changes**" entries in the endpoint's documentation in `zulip.yaml`.
API_FEATURE_LEVEL = 97
API_FEATURE_LEVEL = 98
# Bump the minor PROVISION_VERSION to indicate that folks should provision
# only when going from an old version of the code to a newer version. Bump

View File

@ -153,6 +153,7 @@ def create_streams_if_needed(
realm,
stream_dict["name"],
invite_only=stream_dict.get("invite_only", False),
is_web_public=stream_dict.get("is_web_public", False),
stream_post_policy=stream_dict.get(
"stream_post_policy", Stream.STREAM_POST_POLICY_EVERYONE
),
@ -645,6 +646,7 @@ def list_to_streams(
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())
@ -652,6 +654,9 @@ def list_to_streams(
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)
@ -673,6 +678,14 @@ def list_to_streams(
)
)
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.is_realm_owner:
# We only allow organization owners to create web-public streams,
# because of their sensitive nature.
raise OrganizationOwnerRequired()
if message_retention_days_not_none:
if not user_profile.is_realm_owner:
raise OrganizationOwnerRequired()

View File

@ -6688,6 +6688,23 @@ paths:
type: boolean
default: false
example: true
- name: is_web_public
in: query
description: |
This parameter determines whether any newly created streams will be
web public streams.
Note that creating web public streams requires the
`WEB_PUBLIC_STREAMS_ENABLED` [server setting][server-settings]
to be enabled on the Zulip server in question.
[server-settings]: https://zulip.readthedocs.io/en/stable/production/settings.html
**Changes**: New in Zulip 5.0 (feature level 98).
schema:
type: boolean
default: false
example: true
- $ref: "#/components/parameters/HistoryPublicToSubscribers"
- $ref: "#/components/parameters/StreamPostPolicy"
- $ref: "#/components/parameters/MessageRetentionDays"

View File

@ -463,6 +463,49 @@ class StreamAdminTest(ZulipTestCase):
self.assertTrue(stream.invite_only)
self.assertFalse(stream.history_public_to_subscribers)
def test_create_web_public_stream(self) -> None:
user_profile = self.example_user("hamlet")
owner = self.example_user("desdemona")
stream_names = ["new1", "new2", "new3"]
stream_descriptions = ["des1", "des2", "des3"]
streams_raw: List[StreamDict] = [
{"name": stream_name, "description": stream_description, "is_web_public": True}
for (stream_name, stream_description) in zip(stream_names, stream_descriptions)
]
# Normal user cannot create web-public streams
with self.assertRaisesRegex(JsonableError, "Must be an organization owner"):
list_to_streams(
streams_raw,
user_profile,
autocreate=True,
)
with self.settings(WEB_PUBLIC_STREAMS_ENABLED=False):
with self.assertRaisesRegex(JsonableError, "Web public streams are not enabled."):
list_to_streams(
streams_raw,
owner,
autocreate=True,
)
existing_streams, new_streams = list_to_streams(
streams_raw,
owner,
autocreate=True,
)
self.assert_length(new_streams, 3)
self.assert_length(existing_streams, 0)
actual_stream_names = {stream.name for stream in new_streams}
self.assertEqual(actual_stream_names, set(stream_names))
actual_stream_descriptions = {stream.description for stream in new_streams}
self.assertEqual(actual_stream_descriptions, set(stream_descriptions))
for stream in new_streams:
self.assertTrue(stream.is_web_public)
def test_make_stream_public_zephyr_mirror(self) -> None:
user_profile = self.mit_user("starnine")
self.login_user(user_profile)
@ -1289,6 +1332,7 @@ class StreamAdminTest(ZulipTestCase):
{
"name": "new_stream",
"message_retention_days": 10,
"is_web_public": False,
}
]
with self.assertRaisesRegex(JsonableError, "Must be an organization owner"):
@ -1298,6 +1342,7 @@ class StreamAdminTest(ZulipTestCase):
{
"name": "new_stream",
"message_retention_days": -1,
"is_web_public": False,
}
]
with self.assertRaisesRegex(JsonableError, "Must be an organization owner"):
@ -1307,6 +1352,7 @@ class StreamAdminTest(ZulipTestCase):
{
"name": "new_stream",
"message_retention_days": None,
"is_web_public": False,
}
]
result = list_to_streams(streams_raw, admin, autocreate=True)
@ -1318,9 +1364,20 @@ class StreamAdminTest(ZulipTestCase):
owner = self.example_user("desdemona")
realm = owner.realm
streams_raw = [
{"name": "new_stream1", "message_retention_days": 10},
{"name": "new_stream2", "message_retention_days": -1},
{"name": "new_stream3"},
{
"name": "new_stream1",
"message_retention_days": 10,
"is_web_public": False,
},
{
"name": "new_stream2",
"message_retention_days": -1,
"is_web_public": False,
},
{
"name": "new_stream3",
"is_web_public": False,
},
]
do_change_plan_type(realm, Realm.LIMITED, acting_user=admin)

View File

@ -451,6 +451,7 @@ def add_subscriptions_backend(
"subscriptions", json_validator=add_subscriptions_schema
),
invite_only: bool = REQ(json_validator=check_bool, default=False),
is_web_public: bool = REQ(json_validator=check_bool, default=False),
stream_post_policy: int = REQ(
json_validator=check_int_in(Stream.STREAM_POST_POLICY_TYPES),
default=Stream.STREAM_POST_POLICY_EVERYONE,
@ -483,6 +484,7 @@ def add_subscriptions_backend(
stream_dict_copy["description"] = stream_dict["description"].replace("\n", " ")
stream_dict_copy["invite_only"] = invite_only
stream_dict_copy["is_web_public"] = is_web_public
stream_dict_copy["stream_post_policy"] = stream_post_policy
stream_dict_copy["history_public_to_subscribers"] = history_public_to_subscribers
stream_dict_copy["message_retention_days"] = parse_message_retention_days(