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.
This commit is contained in:
Sahil Batra 2023-11-22 17:03:48 +05:30 committed by Tim Abbott
parent 244b150920
commit 3697df1971
7 changed files with 85 additions and 3 deletions

View File

@ -49,6 +49,7 @@ IGNORED_PHRASES = [
r"Zulip Security", r"Zulip Security",
r"Zulip Cloud", r"Zulip Cloud",
r"Zulip Cloud Standard", r"Zulip Cloud Standard",
r"Zulip Cloud Plus",
r"BigBlueButton", r"BigBlueButton",
# Code things # Code things
r"\.zuliprc", r"\.zuliprc",

View File

@ -1,4 +1,5 @@
import $ from "jquery"; import $ from "jquery";
import tippy from "tippy.js";
import render_admin_tab from "../templates/settings/admin_tab.hbs"; import render_admin_tab from "../templates/settings/admin_tab.hbs";
import render_settings_organization_settings_tip from "../templates/settings/organization_settings_tip.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_bot_creation_policy").val(page_params.realm_bot_creation_policy);
$("#id_realm_digest_weekday").val(options.realm_digest_weekday); $("#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) { export function launch(section) {

View File

@ -32,6 +32,7 @@ from zerver.models import (
ScheduledEmail, ScheduledEmail,
Stream, Stream,
Subscription, Subscription,
SystemGroups,
UserGroup, UserGroup,
UserProfile, UserProfile,
active_user_ids, active_user_ids,
@ -147,7 +148,7 @@ def do_set_push_notifications_enabled_end_timestamp(
send_event(realm, event, active_user_ids(realm.id)) send_event(realm, event, active_user_ids(realm.id))
@transaction.atomic(durable=True) @transaction.atomic(savepoint=False)
def do_change_realm_permission_group_setting( def do_change_realm_permission_group_setting(
realm: Realm, setting_name: str, user_group: UserGroup, *, acting_user: Optional[UserProfile] realm: Realm, setting_name: str, user_group: UserGroup, *, acting_user: Optional[UserProfile]
) -> None: ) -> None:
@ -520,6 +521,21 @@ def do_change_realm_plan_type(
# We do not allow public access on limited plans. # We do not allow public access on limited plans.
do_set_realm_property(realm, "enable_spectator_access", False, acting_user=acting_user) 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.plan_type = plan_type
realm.save(update_fields=["plan_type"]) realm.save(update_fields=["plan_type"])
RealmAuditLog.objects.create( RealmAuditLog.objects.create(

View File

@ -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_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 # plan_type controls various features around resource/feature
# limitations for a Zulip organization on multi-tenant installations # limitations for a Zulip organization on multi-tenant installations
# like Zulip Cloud. # 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: if self.plan_type == Realm.PLAN_TYPE_LIMITED:
raise JsonableError(str(self.UPGRADE_TEXT_STANDARD)) 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 @property
def subdomain(self) -> str: def subdomain(self) -> str:
return self.string_id return self.string_id

View File

@ -2628,6 +2628,10 @@ class NormalActionsTest(BaseAction):
def test_realm_update_plan_type(self) -> None: def test_realm_update_plan_type(self) -> None:
realm = self.user_profile.realm 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) state_data = fetch_initial_state_data(self.user_profile)
self.assertEqual(state_data["realm_plan_type"], Realm.PLAN_TYPE_SELF_HOSTED) 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( lambda: do_change_realm_plan_type(
realm, Realm.PLAN_TYPE_LIMITED, acting_user=self.user_profile 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[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) state_data = fetch_initial_state_data(self.user_profile)
self.assertEqual(state_data["realm_plan_type"], Realm.PLAN_TYPE_LIMITED) self.assertEqual(state_data["realm_plan_type"], Realm.PLAN_TYPE_LIMITED)

View File

@ -21,6 +21,7 @@ from zerver.actions.message_send import (
from zerver.actions.realm_settings import ( from zerver.actions.realm_settings import (
do_add_deactivated_redirect, do_add_deactivated_redirect,
do_change_realm_org_type, do_change_realm_org_type,
do_change_realm_permission_group_setting,
do_change_realm_plan_type, do_change_realm_plan_type,
do_deactivate_realm, do_deactivate_realm,
do_delete_all_realm_attachments, do_delete_all_realm_attachments,
@ -798,6 +799,12 @@ class RealmTest(ZulipTestCase):
self.assertEqual(realm.message_visibility_limit, None) self.assertEqual(realm.message_visibility_limit, None)
self.assertEqual(realm.upload_quota_gb, 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) do_change_realm_plan_type(realm, Realm.PLAN_TYPE_STANDARD, acting_user=iago)
realm = get_realm("zulip") realm = get_realm("zulip")
realm_audit_log = RealmAuditLog.objects.filter( 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.max_invites, Realm.INVITES_STANDARD_REALM_DAILY_MAX)
self.assertEqual(realm.message_visibility_limit, None) self.assertEqual(realm.message_visibility_limit, None)
self.assertEqual(realm.upload_quota_gb, Realm.UPLOAD_QUOTA_STANDARD) 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_set_realm_property(realm, "enable_spectator_access", True, acting_user=None)
do_change_realm_plan_type(realm, Realm.PLAN_TYPE_LIMITED, acting_user=iago) 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.message_visibility_limit, None)
self.assertEqual(realm.upload_quota_gb, Realm.UPLOAD_QUOTA_STANDARD) 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) 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.plan_type, Realm.PLAN_TYPE_SELF_HOSTED)
self.assertEqual(realm.max_invites, settings.INVITES_DEFAULT_REALM_DAILY_MAX) 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) result = self.client_patch("/json/realm", req)
self.assert_json_error(result, "Available on Zulip Cloud Standard. Upgrade to access.") 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): class ScrubRealmTest(ZulipTestCase):
def test_do_delete_all_realm_attachments(self) -> None: def test_do_delete_all_realm_attachments(self) -> None:

View File

@ -239,6 +239,8 @@ def update_realm(
# Remove this when the feature is ready for production. # Remove this when the feature is ready for production.
assert settings.DEVELOPMENT assert settings.DEVELOPMENT
realm.can_enable_restricted_user_access_for_guests()
data: Dict[str, Any] = {} data: Dict[str, Any] = {}
message_content_delete_limit_seconds: Optional[int] = None message_content_delete_limit_seconds: Optional[int] = None