From 3697df19712586ced7117b15e6a063a505b3b7fd Mon Sep 17 00:00:00 2001 From: Sahil Batra Date: Wed, 22 Nov 2023 17:03:48 +0530 Subject: [PATCH] realm: Allow enabling restricted user access for guests only on plus plans. This commit adds code to not allow Zulip Cloud organizations that are not on the Plus plan to change the "can_access_all_users_group" setting. Fixes #27877. --- tools/lib/capitalization.py | 1 + web/src/admin.js | 18 ++++++++++++++++ zerver/actions/realm_settings.py | 18 +++++++++++++++- zerver/models.py | 5 +++++ zerver/tests/test_events.py | 9 ++++++-- zerver/tests/test_realm.py | 35 ++++++++++++++++++++++++++++++++ zerver/views/realm.py | 2 ++ 7 files changed, 85 insertions(+), 3 deletions(-) diff --git a/tools/lib/capitalization.py b/tools/lib/capitalization.py index 1f909e10aa..f228e24524 100644 --- a/tools/lib/capitalization.py +++ b/tools/lib/capitalization.py @@ -49,6 +49,7 @@ IGNORED_PHRASES = [ r"Zulip Security", r"Zulip Cloud", r"Zulip Cloud Standard", + r"Zulip Cloud Plus", r"BigBlueButton", # Code things r"\.zuliprc", diff --git a/web/src/admin.js b/web/src/admin.js index e7190b37dd..b3b7e911ca 100644 --- a/web/src/admin.js +++ b/web/src/admin.js @@ -1,4 +1,5 @@ import $ from "jquery"; +import tippy from "tippy.js"; import render_admin_tab from "../templates/settings/admin_tab.hbs"; import render_settings_organization_settings_tip from "../templates/settings/organization_settings_tip.hbs"; @@ -254,6 +255,23 @@ export function build_page() { $("#id_realm_bot_creation_policy").val(page_params.realm_bot_creation_policy); $("#id_realm_digest_weekday").val(options.realm_digest_weekday); + + const is_plan_plus = page_params.realm_plan_type === 10; + const is_plan_self_hosted = page_params.realm_plan_type === 1; + if ( + page_params.development_environment && + page_params.is_admin && + !(is_plan_plus || is_plan_self_hosted) + ) { + $("#realm_can_access_all_users_group_widget").prop("disabled", true); + const opts = { + content: $t({ + defaultMessage: "This feature is available on Zulip Cloud Plus. Upgrade to access.", + }), + }; + + tippy($("#realm_can_access_all_users_group_widget_container")[0], opts); + } } export function launch(section) { diff --git a/zerver/actions/realm_settings.py b/zerver/actions/realm_settings.py index 31eb1bdfa5..06e568e6ff 100644 --- a/zerver/actions/realm_settings.py +++ b/zerver/actions/realm_settings.py @@ -32,6 +32,7 @@ from zerver.models import ( ScheduledEmail, Stream, Subscription, + SystemGroups, UserGroup, UserProfile, active_user_ids, @@ -147,7 +148,7 @@ def do_set_push_notifications_enabled_end_timestamp( send_event(realm, event, active_user_ids(realm.id)) -@transaction.atomic(durable=True) +@transaction.atomic(savepoint=False) def do_change_realm_permission_group_setting( realm: Realm, setting_name: str, user_group: UserGroup, *, acting_user: Optional[UserProfile] ) -> None: @@ -520,6 +521,21 @@ def do_change_realm_plan_type( # We do not allow public access on limited plans. do_set_realm_property(realm, "enable_spectator_access", False, acting_user=acting_user) + if old_value in [Realm.PLAN_TYPE_PLUS, Realm.PLAN_TYPE_SELF_HOSTED] and plan_type not in [ + Realm.PLAN_TYPE_PLUS, + Realm.PLAN_TYPE_SELF_HOSTED, + ]: + # If downgrading to a plan that no longer has access to change + # can_access_all_users_group, set it back to the default + # value. + everyone_system_group = UserGroup.objects.get( + name=SystemGroups.EVERYONE, realm=realm, is_system_group=True + ) + if realm.can_access_all_users_group_id != everyone_system_group.id: + do_change_realm_permission_group_setting( + realm, "can_access_all_users_group", everyone_system_group, acting_user=acting_user + ) + realm.plan_type = plan_type realm.save(update_fields=["plan_type"]) RealmAuditLog.objects.create( diff --git a/zerver/models.py b/zerver/models.py index 05ff6c20cb..9b3126bdc4 100644 --- a/zerver/models.py +++ b/zerver/models.py @@ -685,6 +685,7 @@ class Realm(models.Model): # type: ignore[django-manager-missing] # django-stub ) UPGRADE_TEXT_STANDARD = gettext_lazy("Available on Zulip Cloud Standard. Upgrade to access.") + UPGRADE_TEXT_PLUS = gettext_lazy("Available on Zulip Cloud Plus. Upgrade to access.") # plan_type controls various features around resource/feature # limitations for a Zulip organization on multi-tenant installations # like Zulip Cloud. @@ -1047,6 +1048,10 @@ class Realm(models.Model): # type: ignore[django-manager-missing] # django-stub if self.plan_type == Realm.PLAN_TYPE_LIMITED: raise JsonableError(str(self.UPGRADE_TEXT_STANDARD)) + def can_enable_restricted_user_access_for_guests(self) -> None: + if self.plan_type not in [Realm.PLAN_TYPE_PLUS, Realm.PLAN_TYPE_SELF_HOSTED]: + raise JsonableError(str(self.UPGRADE_TEXT_PLUS)) + @property def subdomain(self) -> str: return self.string_id diff --git a/zerver/tests/test_events.py b/zerver/tests/test_events.py index 0a5b771b16..c26f11b83b 100644 --- a/zerver/tests/test_events.py +++ b/zerver/tests/test_events.py @@ -2628,6 +2628,10 @@ class NormalActionsTest(BaseAction): def test_realm_update_plan_type(self) -> None: realm = self.user_profile.realm + members_group = UserGroup.objects.get(name=SystemGroups.MEMBERS, realm=realm) + do_change_realm_permission_group_setting( + realm, "can_access_all_users_group", members_group, acting_user=None + ) state_data = fetch_initial_state_data(self.user_profile) self.assertEqual(state_data["realm_plan_type"], Realm.PLAN_TYPE_SELF_HOSTED) @@ -2637,10 +2641,11 @@ class NormalActionsTest(BaseAction): lambda: do_change_realm_plan_type( realm, Realm.PLAN_TYPE_LIMITED, acting_user=self.user_profile ), - num_events=2, + num_events=3, ) check_realm_update("events[0]", events[0], "enable_spectator_access") - check_realm_update("events[1]", events[1], "plan_type") + check_realm_update_dict("events[1]", events[1]) + check_realm_update("events[2]", events[2], "plan_type") state_data = fetch_initial_state_data(self.user_profile) self.assertEqual(state_data["realm_plan_type"], Realm.PLAN_TYPE_LIMITED) diff --git a/zerver/tests/test_realm.py b/zerver/tests/test_realm.py index 82f132900a..420bb92de4 100644 --- a/zerver/tests/test_realm.py +++ b/zerver/tests/test_realm.py @@ -21,6 +21,7 @@ from zerver.actions.message_send import ( from zerver.actions.realm_settings import ( do_add_deactivated_redirect, do_change_realm_org_type, + do_change_realm_permission_group_setting, do_change_realm_plan_type, do_deactivate_realm, do_delete_all_realm_attachments, @@ -798,6 +799,12 @@ class RealmTest(ZulipTestCase): self.assertEqual(realm.message_visibility_limit, None) self.assertEqual(realm.upload_quota_gb, None) + members_system_group = UserGroup.objects.get(name=SystemGroups.MEMBERS, realm=realm) + do_change_realm_permission_group_setting( + realm, "can_access_all_users_group", members_system_group, acting_user=None + ) + self.assertEqual(realm.can_access_all_users_group_id, members_system_group.id) + do_change_realm_plan_type(realm, Realm.PLAN_TYPE_STANDARD, acting_user=iago) realm = get_realm("zulip") realm_audit_log = RealmAuditLog.objects.filter( @@ -814,6 +821,8 @@ class RealmTest(ZulipTestCase): self.assertEqual(realm.max_invites, Realm.INVITES_STANDARD_REALM_DAILY_MAX) self.assertEqual(realm.message_visibility_limit, None) self.assertEqual(realm.upload_quota_gb, Realm.UPLOAD_QUOTA_STANDARD) + everyone_system_group = UserGroup.objects.get(name=SystemGroups.EVERYONE, realm=realm) + self.assertEqual(realm.can_access_all_users_group_id, everyone_system_group.id) do_set_realm_property(realm, "enable_spectator_access", True, acting_user=None) do_change_realm_plan_type(realm, Realm.PLAN_TYPE_LIMITED, acting_user=iago) @@ -839,6 +848,17 @@ class RealmTest(ZulipTestCase): self.assertEqual(realm.message_visibility_limit, None) self.assertEqual(realm.upload_quota_gb, Realm.UPLOAD_QUOTA_STANDARD) + do_change_realm_permission_group_setting( + realm, "can_access_all_users_group", members_system_group, acting_user=None + ) + do_change_realm_plan_type(realm, Realm.PLAN_TYPE_STANDARD, acting_user=iago) + realm = get_realm("zulip") + self.assertEqual(realm.plan_type, Realm.PLAN_TYPE_STANDARD) + self.assertEqual(realm.max_invites, Realm.INVITES_STANDARD_REALM_DAILY_MAX) + self.assertEqual(realm.message_visibility_limit, None) + self.assertEqual(realm.upload_quota_gb, Realm.UPLOAD_QUOTA_STANDARD) + self.assertEqual(realm.can_access_all_users_group_id, everyone_system_group.id) + do_change_realm_plan_type(realm, Realm.PLAN_TYPE_SELF_HOSTED, acting_user=iago) self.assertEqual(realm.plan_type, Realm.PLAN_TYPE_SELF_HOSTED) self.assertEqual(realm.max_invites, settings.INVITES_DEFAULT_REALM_DAILY_MAX) @@ -1601,6 +1621,21 @@ class RealmAPITest(ZulipTestCase): result = self.client_patch("/json/realm", req) self.assert_json_error(result, "Available on Zulip Cloud Standard. Upgrade to access.") + def test_changing_can_access_all_users_group_based_on_plan_type(self) -> None: + realm = get_realm("zulip") + do_change_realm_plan_type(realm, Realm.PLAN_TYPE_LIMITED, acting_user=None) + self.login("iago") + + members_group = UserGroup.objects.get(name="role:members", realm=realm) + req = {"can_access_all_users_group": orjson.dumps(members_group.id).decode()} + result = self.client_patch("/json/realm", req) + self.assert_json_error(result, "Available on Zulip Cloud Plus. Upgrade to access.") + + do_change_realm_plan_type(realm, Realm.PLAN_TYPE_STANDARD, acting_user=None) + req = {"can_access_all_users_group": orjson.dumps(members_group.id).decode()} + result = self.client_patch("/json/realm", req) + self.assert_json_error(result, "Available on Zulip Cloud Plus. Upgrade to access.") + class ScrubRealmTest(ZulipTestCase): def test_do_delete_all_realm_attachments(self) -> None: diff --git a/zerver/views/realm.py b/zerver/views/realm.py index 6bdca491a3..1dbf127805 100644 --- a/zerver/views/realm.py +++ b/zerver/views/realm.py @@ -239,6 +239,8 @@ def update_realm( # Remove this when the feature is ready for production. assert settings.DEVELOPMENT + realm.can_enable_restricted_user_access_for_guests() + data: Dict[str, Any] = {} message_content_delete_limit_seconds: Optional[int] = None