settings: Add web_public_streams_enabled server setting.

This new setting both serves as a guard to allow us to merge API
support for web public streams to main before we're ready for this
feature to be available on Zulip Cloud, and also long term will
protect self-hosted servers from accidentally enabling web-public
streams (which could be a scary possibility for the administrators of
a corporate Zulip server).
This commit is contained in:
Tim Abbott 2021-09-21 10:49:12 -07:00
parent 8c50a08066
commit 8fe61674d4
4 changed files with 37 additions and 4 deletions

View File

@ -852,15 +852,27 @@ class Realm(models.Model):
def presence_disabled(self) -> bool: def presence_disabled(self) -> bool:
return self.is_zephyr_mirror_realm return self.is_zephyr_mirror_realm
def web_public_streams_enabled(self) -> bool:
if not settings.WEB_PUBLIC_STREAMS_ENABLED:
# To help protect against accidentally web-public streams in
# self-hosted servers, we require the feature to be enabled at
# the server level before it is available to users.
return False
if self.plan_type == Realm.LIMITED:
# In Zulip Cloud, we also require a paid or sponsored
# plan, to protect against the spam/abuse attacks that
# target every open Internet service that can host files.
return False
return True
def has_web_public_streams(self) -> bool: def has_web_public_streams(self) -> bool:
""" """
If any of the streams in the realm is web If any of the streams in the realm is web
public, then the realm is web public. public, then the realm is web public.
""" """
if self.plan_type == Realm.LIMITED: if not self.web_public_streams_enabled():
# We don't enable web public streams feature
# for realms on LIMITED plan to avoid spamming
# behaviour.
return False return False
return Stream.objects.filter(realm=self, is_web_public=True).exists() return Stream.objects.filter(realm=self, is_web_public=True).exists()

View File

@ -741,23 +741,40 @@ class RealmTest(ZulipTestCase):
rome = Stream.objects.get(name="Rome") rome = Stream.objects.get(name="Rome")
self.assertEqual(rome.is_web_public, True) self.assertEqual(rome.is_web_public, True)
self.assertEqual(realm.has_web_public_streams(), True) self.assertEqual(realm.has_web_public_streams(), True)
self.assertEqual(realm.web_public_streams_enabled(), True)
with self.settings(WEB_PUBLIC_STREAMS_ENABLED=False):
self.assertEqual(realm.has_web_public_streams(), False)
self.assertEqual(realm.web_public_streams_enabled(), False)
# Convert Rome to a public stream # Convert Rome to a public stream
rome.is_web_public = False rome.is_web_public = False
rome.save() rome.save()
self.assertEqual(Stream.objects.filter(realm=realm, is_web_public=True).count(), 0) self.assertEqual(Stream.objects.filter(realm=realm, is_web_public=True).count(), 0)
self.assertEqual(realm.web_public_streams_enabled(), True)
self.assertEqual(realm.has_web_public_streams(), False) self.assertEqual(realm.has_web_public_streams(), False)
with self.settings(WEB_PUBLIC_STREAMS_ENABLED=False):
self.assertEqual(realm.web_public_streams_enabled(), False)
self.assertEqual(realm.has_web_public_streams(), False)
# Restore state # Restore state
rome.is_web_public = True rome.is_web_public = True
rome.save() rome.save()
self.assertEqual(Stream.objects.filter(realm=realm, is_web_public=True).count(), 1) self.assertEqual(Stream.objects.filter(realm=realm, is_web_public=True).count(), 1)
self.assertEqual(realm.has_web_public_streams(), True) self.assertEqual(realm.has_web_public_streams(), True)
self.assertEqual(realm.web_public_streams_enabled(), True)
with self.settings(WEB_PUBLIC_STREAMS_ENABLED=False):
self.assertEqual(realm.web_public_streams_enabled(), False)
self.assertEqual(realm.has_web_public_streams(), False)
realm.plan_type = Realm.LIMITED realm.plan_type = Realm.LIMITED
realm.save() realm.save()
self.assertEqual(Stream.objects.filter(realm=realm, is_web_public=True).count(), 1) self.assertEqual(Stream.objects.filter(realm=realm, is_web_public=True).count(), 1)
self.assertEqual(realm.web_public_streams_enabled(), False)
self.assertEqual(realm.has_web_public_streams(), False) self.assertEqual(realm.has_web_public_streams(), False)
with self.settings(WEB_PUBLIC_STREAMS_ENABLED=False):
self.assertEqual(realm.web_public_streams_enabled(), False)
self.assertEqual(realm.has_web_public_streams(), False)
class RealmAPITest(ZulipTestCase): class RealmAPITest(ZulipTestCase):

View File

@ -262,6 +262,9 @@ EXTERNAL_URI_SCHEME = "https://"
# Whether anyone can create a new organization on the Zulip server. # Whether anyone can create a new organization on the Zulip server.
OPEN_REALM_CREATION = False OPEN_REALM_CREATION = False
# Whether it's possible to create web-public streams on this server.
WEB_PUBLIC_STREAMS_ENABLED = False
# Setting for where the system bot users are. Likely has no # Setting for where the system bot users are. Likely has no
# purpose now that the REALMS_HAVE_SUBDOMAINS migration is finished. # purpose now that the REALMS_HAVE_SUBDOMAINS migration is finished.
SYSTEM_ONLY_REALMS = {"zulip"} SYSTEM_ONLY_REALMS = {"zulip"}

View File

@ -74,6 +74,7 @@ CAMO_URI = ""
TORNADO_PORTS = [9993] TORNADO_PORTS = [9993]
OPEN_REALM_CREATION = True OPEN_REALM_CREATION = True
WEB_PUBLIC_STREAMS_ENABLED = True
INVITES_MIN_USER_AGE_DAYS = 0 INVITES_MIN_USER_AGE_DAYS = 0
EMBEDDED_BOTS_ENABLED = True EMBEDDED_BOTS_ENABLED = True