From 166d9282b896bdeecedca7ae4e01ba2369ca4407 Mon Sep 17 00:00:00 2001 From: Sahil Batra Date: Mon, 24 Feb 2025 20:14:11 +0530 Subject: [PATCH] streams: Use can_subscribe_group setting for checking permission. This commit adds code to use can_subscribe_group setting in webapp. Fixes part of #33417. --- tools/lib/capitalization.py | 2 + web/src/group_permission_settings.ts | 1 + web/src/settings_config.ts | 3 + web/src/stream_create.ts | 2 + web/src/stream_data.ts | 64 +++++++++---- web/src/stream_edit.ts | 5 + web/src/stream_events.ts | 3 + web/src/stream_settings_components.ts | 6 +- web/src/stream_settings_ui.ts | 12 +++ web/src/stream_types.ts | 2 + web/src/stream_ui_updates.ts | 10 ++ .../group_setting_value_pill_input.hbs | 8 +- .../stream_can_subscribe_group_label.hbs | 4 + .../stream_settings/stream_types.hbs | 5 + web/tests/compose_validate.test.cjs | 3 + web/tests/composebox_typeahead.test.cjs | 5 + web/tests/lib/example_settings.cjs | 8 ++ web/tests/message_view.test.cjs | 1 + web/tests/peer_data.test.cjs | 1 + web/tests/stream_data.test.cjs | 94 ++++++++++++++++++- web/tests/stream_events.test.cjs | 21 +++++ web/tests/stream_pill.test.cjs | 3 + web/tests/stream_settings_ui.test.cjs | 8 ++ 23 files changed, 244 insertions(+), 27 deletions(-) create mode 100644 web/templates/stream_settings/stream_can_subscribe_group_label.hbs diff --git a/tools/lib/capitalization.py b/tools/lib/capitalization.py index 491d639c68..6ca84803f4 100644 --- a/tools/lib/capitalization.py +++ b/tools/lib/capitalization.py @@ -180,6 +180,8 @@ IGNORED_PHRASES = [ r"weeks", # Used in "Who can subscribe others to this channel" label. r"must be subscribed", + # Used in "Who can subscribe to this channel" label. + r"everyone except guests can subscribe to any public channel", ] # Sort regexes in descending order of their lengths. As a result, the diff --git a/web/src/group_permission_settings.ts b/web/src/group_permission_settings.ts index f08e594455..39bc6c4f74 100644 --- a/web/src/group_permission_settings.ts +++ b/web/src/group_permission_settings.ts @@ -69,6 +69,7 @@ export const stream_group_setting_name_schema = z.enum([ "can_administer_channel_group", "can_remove_subscribers_group", "can_send_message_group", + "can_subscribe_group", ]); export type StreamGroupSettingName = z.infer; diff --git a/web/src/settings_config.ts b/web/src/settings_config.ts index 494f0b5032..27f448fe23 100644 --- a/web/src/settings_config.ts +++ b/web/src/settings_config.ts @@ -662,6 +662,7 @@ export const all_group_setting_labels = { can_add_subscribers_group: $t({defaultMessage: "Who can subscribe anyone to this channel"}), can_send_message_group: $t({defaultMessage: "Who can post to this channel"}), can_administer_channel_group: $t({defaultMessage: "Who can administer this channel"}), + can_subscribe_group: $t({defaultMessage: "Who can subscribe to this channel"}), can_remove_subscribers_group: $t({ defaultMessage: "Who can unsubscribe anyone from this channel", }), @@ -752,12 +753,14 @@ export const owner_editable_realm_group_permission_settings = new Set([ export const stream_group_permission_settings: StreamGroupSettingName[] = [ "can_send_message_group", "can_administer_channel_group", + "can_subscribe_group", "can_add_subscribers_group", "can_remove_subscribers_group", ]; export const stream_group_permission_settings_requiring_content_access: StreamGroupSettingName[] = [ "can_add_subscribers_group", + "can_subscribe_group", ]; // Order of settings is important, as this list is used to diff --git a/web/src/stream_create.ts b/web/src/stream_create.ts index e1596962be..c9a0e26456 100644 --- a/web/src/stream_create.ts +++ b/web/src/stream_create.ts @@ -515,6 +515,7 @@ export function show_new_stream_modal(): void { $("#stream_creation_form .default-stream input").prop("checked", false); update_announce_stream_state(); stream_ui_updates.update_can_add_subscribers_group_label($("#stream-creation")); + stream_ui_updates.update_can_subscribe_group_label($("#stream-creation")); stream_ui_updates.update_default_stream_and_stream_privacy_state($("#stream-creation")); clear_error_display(); } @@ -545,6 +546,7 @@ export function set_up_handlers(): void { $container.on("change", ".stream-privacy-values input", () => { update_announce_stream_state(); stream_ui_updates.update_default_stream_and_stream_privacy_state($container); + stream_ui_updates.update_can_subscribe_group_label($container); // We update the label on `can_add_subscribers_groups` in the // listener attached to `.stream-privacy-values input` on // `#channels_overlay_container` which covers both stream diff --git a/web/src/stream_data.ts b/web/src/stream_data.ts index 297cde89b3..42bf7d5aae 100644 --- a/web/src/stream_data.ts +++ b/web/src/stream_data.ts @@ -518,6 +518,37 @@ export function has_metadata_access(sub: StreamSubscription): boolean { return true; } + const can_subscribe = settings_data.user_has_permission_for_group_setting( + sub.can_subscribe_group, + "can_subscribe_group", + "stream", + ); + if (can_subscribe) { + return true; + } + + return false; +} + +export function has_content_access_via_group_permissions(sub: StreamSubscription): boolean { + const can_add_subscribers = settings_data.user_has_permission_for_group_setting( + sub.can_add_subscribers_group, + "can_add_subscribers_group", + "stream", + ); + if (can_add_subscribers) { + return true; + } + + const can_subscribe = settings_data.user_has_permission_for_group_setting( + sub.can_subscribe_group, + "can_subscribe_group", + "stream", + ); + if (can_subscribe) { + return true; + } + return false; } @@ -543,12 +574,7 @@ export let has_content_access = (sub: StreamSubscription): boolean => { return false; } - const can_add_subscribers = settings_data.user_has_permission_for_group_setting( - sub.can_add_subscribers_group, - "can_add_subscribers_group", - "stream", - ); - if (can_add_subscribers) { + if (has_content_access_via_group_permissions(sub)) { return true; } @@ -584,18 +610,20 @@ function can_administer_channel(sub: StreamSubscription): boolean { } export function can_toggle_subscription(sub: StreamSubscription): boolean { - // You can always remove your subscription if you're subscribed. - // - // One can only join a stream if it is public (!invite_only) and - // your role is Member or above (!is_guest). - // Spectators cannot subscribe to any streams. - // - // Note that the correctness of this logic relies on the fact that - // one cannot be subscribed to a deactivated stream. - return ( - (sub.subscribed || (!current_user.is_guest && !(sub.invite_only || sub.is_archived))) && - !page_params.is_spectator - ); + if (page_params.is_spectator) { + return false; + } + + // Currently, you can always remove your subscription if you're subscribed. + if (sub.subscribed) { + return true; + } + + if (has_content_access(sub)) { + return true; + } + + return false; } export function get_current_user_and_their_bots_with_post_messages_permission( diff --git a/web/src/stream_edit.ts b/web/src/stream_edit.ts index 0b1b46f39d..15e6690c5a 100644 --- a/web/src/stream_edit.ts +++ b/web/src/stream_edit.ts @@ -287,6 +287,7 @@ export function show_settings_for(node: HTMLElement): void { stream_ui_updates.enable_or_disable_permission_settings_in_edit_panel(sub); setup_group_setting_widgets(slim_sub); stream_ui_updates.update_can_add_subscribers_group_label($edit_container); + stream_ui_updates.update_can_subscribe_group_label($edit_container); $("#channels_overlay_container").on( "click", @@ -804,6 +805,8 @@ export function initialize(): void { ); if (sub && $subsection.attr("id") === "stream_permission_settings") { stream_ui_updates.update_default_stream_and_stream_privacy_state($subsection); + const $edit_container = stream_settings_containers.get_edit_container(sub); + stream_ui_updates.update_can_subscribe_group_label($edit_container); } return true; }, @@ -871,6 +874,8 @@ export function initialize(): void { settings_org.discard_stream_settings_subsection_changes($subsection, sub); if ($subsection.attr("id") === "stream_permission_settings") { stream_ui_updates.update_default_stream_and_stream_privacy_state($subsection); + const $edit_container = stream_settings_containers.get_edit_container(sub); + stream_ui_updates.update_can_subscribe_group_label($edit_container); } }, ); diff --git a/web/src/stream_events.ts b/web/src/stream_events.ts index 0080a7e46a..4a84801eab 100644 --- a/web/src/stream_events.ts +++ b/web/src/stream_events.ts @@ -104,6 +104,9 @@ export function update_property

( sub, group_setting_value_schema.parse(value), ); + if (property === "can_subscribe_group" || property === "can_add_subscribers_group") { + stream_settings_ui.update_subscription_elements(sub); + } user_group_edit.update_stream_setting_in_permissions_panel( stream_permission_group_settings_schema.parse(property), group_setting_value_schema.parse(value), diff --git a/web/src/stream_settings_components.ts b/web/src/stream_settings_components.ts index 775f427a20..10c12fddc0 100644 --- a/web/src/stream_settings_components.ts +++ b/web/src/stream_settings_components.ts @@ -15,6 +15,7 @@ import * as peer_data from "./peer_data.ts"; import * as settings_config from "./settings_config.ts"; import * as settings_data from "./settings_data.ts"; import {current_user} from "./state_data.ts"; +import * as stream_data from "./stream_data.ts"; import * as stream_ui_updates from "./stream_ui_updates.ts"; import type {StreamSubscription} from "./sub_store.ts"; import * as ui_report from "./ui_report.ts"; @@ -255,7 +256,10 @@ export function sub_or_unsub( ): void { if (sub.subscribed) { // TODO: This next line should allow guests to access web-public streams. - if (sub.invite_only || current_user.is_guest) { + if ( + (sub.invite_only && !stream_data.has_content_access_via_group_permissions(sub)) || + current_user.is_guest + ) { unsubscribe_from_private_stream(sub); return; } diff --git a/web/src/stream_settings_ui.ts b/web/src/stream_settings_ui.ts index f3115a4d95..aae8379f3b 100644 --- a/web/src/stream_settings_ui.ts +++ b/web/src/stream_settings_ui.ts @@ -39,6 +39,7 @@ import * as stream_edit_toggler from "./stream_edit_toggler.ts"; import * as stream_list from "./stream_list.ts"; import * as stream_settings_api from "./stream_settings_api.ts"; import * as stream_settings_components from "./stream_settings_components.ts"; +import * as stream_settings_containers from "./stream_settings_containers.ts"; import * as stream_settings_data from "./stream_settings_data.ts"; import type {StreamPermissionGroupSetting} from "./stream_types.ts"; import * as stream_ui_updates from "./stream_ui_updates.ts"; @@ -192,6 +193,8 @@ export function update_stream_privacy( const active_data = stream_settings_components.get_active_data(); if (active_data.id === sub.stream_id) { stream_settings_components.set_right_panel_title(sub); + const $edit_container = stream_settings_containers.get_edit_container(sub); + stream_ui_updates.update_can_subscribe_group_label($edit_container); } // Update navbar if needed @@ -231,6 +234,15 @@ export function update_subscribers_ui(sub: StreamSubscription): void { message_view_header.maybe_rerender_title_area_for_stream(sub.stream_id); } +export function update_subscription_elements(sub: StreamSubscription): void { + if (!overlays.streams_open()) { + return; + } + + update_left_panel_row(sub); + stream_ui_updates.update_settings_button_for_sub(sub); +} + export function add_sub_to_table(sub: StreamSubscription): void { if (is_sub_already_present(sub)) { // If a stream is already listed/added in subscription modal, diff --git a/web/src/stream_types.ts b/web/src/stream_types.ts index 0eabe8a389..08386ae90b 100644 --- a/web/src/stream_types.ts +++ b/web/src/stream_types.ts @@ -14,6 +14,7 @@ export const stream_permission_group_settings_schema = z.enum([ "can_administer_channel_group", "can_remove_subscribers_group", "can_send_message_group", + "can_subscribe_group", ]); export type StreamPermissionGroupSetting = z.infer; @@ -42,6 +43,7 @@ export const stream_schema = z.object({ can_administer_channel_group: group_setting_value_schema, can_remove_subscribers_group: group_setting_value_schema, can_send_message_group: group_setting_value_schema, + can_subscribe_group: group_setting_value_schema, is_recently_active: z.boolean(), }); diff --git a/web/src/stream_ui_updates.ts b/web/src/stream_ui_updates.ts index b1377f5a21..2f318c9bc5 100644 --- a/web/src/stream_ui_updates.ts +++ b/web/src/stream_ui_updates.ts @@ -4,6 +4,7 @@ import type * as tippy from "tippy.js"; import render_announce_stream_checkbox from "../templates/stream_settings/announce_stream_checkbox.hbs"; import render_stream_can_add_subscribers_group_label from "../templates/stream_settings/stream_can_add_subscribers_group_label.hbs"; +import render_stream_can_subscribe_group_label from "../templates/stream_settings/stream_can_subscribe_group_label.hbs"; import render_stream_privacy_icon from "../templates/stream_settings/stream_privacy_icon.hbs"; import render_stream_settings_tip from "../templates/stream_settings/stream_settings_tip.hbs"; @@ -269,6 +270,15 @@ export function update_can_add_subscribers_group_label($container: JQuery): void ); } +export function update_can_subscribe_group_label($container: JQuery): void { + const privacy_type = $container.find("input[type=radio][name=privacy]:checked").val(); + const is_invite_only = + privacy_type === "invite-only" || privacy_type === "invite-only-public-history"; + + const $can_subscribe_group_label = $container.find(".can_subscribe_group_label"); + $can_subscribe_group_label.html(render_stream_can_subscribe_group_label({is_invite_only})); +} + export function enable_or_disable_permission_settings_in_edit_panel( sub: SettingsSubscription, ): void { diff --git a/web/templates/settings/group_setting_value_pill_input.hbs b/web/templates/settings/group_setting_value_pill_input.hbs index 6f41c5e83a..370d6aadc6 100644 --- a/web/templates/settings/group_setting_value_pill_input.hbs +++ b/web/templates/settings/group_setting_value_pill_input.hbs @@ -1,8 +1,8 @@

- {{!-- We are using snake case for the id since setting_name is always - in snake case and it would be weird to have the resultant id be a mix - of two types of cases. --}} -