update(chat): add option to disable private chat (#21459)

* update(chat): add option to disable private chat

- Adds `public.chat.private` to settings.yml, defaults to true
- Adds `privateChat` to disabledFeatures

* backend validation for disabledFeatures=privateChat

* refactor(settings): remove chat.private from settings.yml

Will be kept only in disabledFeatures

---------

Co-authored-by: Gustavo Trott <gustavo@trott.com.br>
This commit is contained in:
germanocaumo 2024-10-25 17:40:10 +00:00 committed by GitHub
parent 82774b9a08
commit e992862e40
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 76 additions and 48 deletions

View File

@ -20,8 +20,13 @@ trait CreateGroupChatReqMsgHdlr extends SystemConfiguration {
liveMeeting: LiveMeeting, bus: MessageBus): MeetingState2x = { liveMeeting: LiveMeeting, bus: MessageBus): MeetingState2x = {
log.debug("RECEIVED CREATE CHAT REQ MESSAGE") log.debug("RECEIVED CREATE CHAT REQ MESSAGE")
var privateChatDisabled: Boolean = false
var chatLocked: Boolean = false var chatLocked: Boolean = false
if (msg.body.access == GroupChatAccess.PRIVATE) {
privateChatDisabled = liveMeeting.props.meetingProp.disabledFeatures.contains("privateChat")
}
for { for {
user <- Users2x.findWithIntId(liveMeeting.users2x, msg.header.userId) user <- Users2x.findWithIntId(liveMeeting.users2x, msg.header.userId)
} yield { } yield {
@ -45,7 +50,12 @@ trait CreateGroupChatReqMsgHdlr extends SystemConfiguration {
// Check if this message was sent while the lock settings was being changed. // Check if this message was sent while the lock settings was being changed.
val isDelayedMessage = System.currentTimeMillis() - MeetingStatus2x.getPermissionsChangedOn(liveMeeting.status) < 5000 val isDelayedMessage = System.currentTimeMillis() - MeetingStatus2x.getPermissionsChangedOn(liveMeeting.status) < 5000
if (applyPermissionCheck && chatLocked && !isDelayedMessage) { if (privateChatDisabled ||
(
applyPermissionCheck &&
chatLocked &&
!isDelayedMessage
)) {
val meetingId = liveMeeting.props.meetingProp.intId val meetingId = liveMeeting.props.meetingProp.intId
val reason = "No permission to create a new group chat." val reason = "No permission to create a new group chat."
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting) PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)

View File

@ -25,6 +25,7 @@ trait SendGroupChatMessageMsgHdlr extends HandlerHelpers {
} }
val chatDisabled: Boolean = liveMeeting.props.meetingProp.disabledFeatures.contains("chat") val chatDisabled: Boolean = liveMeeting.props.meetingProp.disabledFeatures.contains("chat")
var privateChatDisabled: Boolean = false
val replyChatMessageDisabled: Boolean = liveMeeting.props.meetingProp.disabledFeatures.contains("replyChatMessage") val replyChatMessageDisabled: Boolean = liveMeeting.props.meetingProp.disabledFeatures.contains("replyChatMessage")
var chatLocked: Boolean = false var chatLocked: Boolean = false
var chatLockedForUser: Boolean = false var chatLockedForUser: Boolean = false
@ -33,6 +34,10 @@ trait SendGroupChatMessageMsgHdlr extends HandlerHelpers {
user <- Users2x.findWithIntId(liveMeeting.users2x, msg.header.userId) user <- Users2x.findWithIntId(liveMeeting.users2x, msg.header.userId)
groupChat <- state.groupChats.find(msg.body.chatId) groupChat <- state.groupChats.find(msg.body.chatId)
} yield { } yield {
if (groupChat.access == GroupChatAccess.PRIVATE) {
privateChatDisabled = liveMeeting.props.meetingProp.disabledFeatures.contains("privateChat")
}
if (groupChat.access == GroupChatAccess.PUBLIC && user.userLockSettings.disablePublicChat && user.role != Roles.MODERATOR_ROLE) { if (groupChat.access == GroupChatAccess.PUBLIC && user.userLockSettings.disablePublicChat && user.role != Roles.MODERATOR_ROLE) {
chatLockedForUser = true chatLockedForUser = true
} }
@ -54,7 +59,10 @@ trait SendGroupChatMessageMsgHdlr extends HandlerHelpers {
} }
} }
if (!chatDisabled && !(applyPermissionCheck && chatLocked) && !chatLockedForUser) { if (!chatDisabled &&
!privateChatDisabled &&
!(applyPermissionCheck && chatLocked) &&
!chatLockedForUser) {
val newState = for { val newState = for {
sender <- GroupChatApp.findGroupChatUser(msg.header.userId, liveMeeting.users2x) sender <- GroupChatApp.findGroupChatUser(msg.header.userId, liveMeeting.users2x)
chat <- state.groupChats.find(msg.body.chatId) chat <- state.groupChats.find(msg.body.chatId)

View File

@ -74,13 +74,13 @@ const intlMessages = defineMessages({
description: 'locked element label', description: 'locked element label',
}, },
hideCursorsLabel: { hideCursorsLabel: {
id: "app.lock-viewers.hideViewersCursor", id: 'app.lock-viewers.hideViewersCursor',
description: 'label for other viewers cursor', description: 'label for other viewers cursor',
}, },
hideAnnotationsLabel: { hideAnnotationsLabel: {
id: "app.lock-viewers.hideAnnotationsLabel", id: 'app.lock-viewers.hideAnnotationsLabel',
description: 'label for other viewers annotation', description: 'label for other viewers annotation',
} },
}); });
const propTypes = { const propTypes = {
@ -88,7 +88,7 @@ const propTypes = {
intl: PropTypes.shape({ intl: PropTypes.shape({
formatMessage: PropTypes.func.isRequired, formatMessage: PropTypes.func.isRequired,
}).isRequired, }).isRequired,
meeting: PropTypes.object.isRequired, meeting: PropTypes.shape({}).isRequired,
showToggleLabel: PropTypes.bool.isRequired, showToggleLabel: PropTypes.bool.isRequired,
updateLockSettings: PropTypes.func.isRequired, updateLockSettings: PropTypes.func.isRequired,
updateWebcamsOnlyForModerator: PropTypes.func.isRequired, updateWebcamsOnlyForModerator: PropTypes.func.isRequired,
@ -106,6 +106,12 @@ class LockViewersComponent extends Component {
}; };
} }
componentWillUnmount() {
const { closeModal } = this.props;
closeModal();
}
toggleLockSettings(property) { toggleLockSettings(property) {
const { lockSettingsProps } = this.state; const { lockSettingsProps } = this.state;
@ -129,18 +135,14 @@ class LockViewersComponent extends Component {
displayLockStatus(status) { displayLockStatus(status) {
const { intl } = this.props; const { intl } = this.props;
return ( return (
status && <Styled.ToggleLabel> status && (
<Styled.ToggleLabel>
{intl.formatMessage(intlMessages.lockedLabel)} {intl.formatMessage(intlMessages.lockedLabel)}
</Styled.ToggleLabel> </Styled.ToggleLabel>
)
); );
} }
componentWillUnmount() {
const { closeModal } = this.props;
closeModal();
}
render() { render() {
const { const {
closeModal, closeModal,
@ -152,6 +154,7 @@ class LockViewersComponent extends Component {
onRequestClose, onRequestClose,
priority, priority,
isChatEnabled, isChatEnabled,
isPrivateChatEnabled,
isSharedNotesEnabled, isSharedNotesEnabled,
} = this.props; } = this.props;
@ -257,7 +260,7 @@ class LockViewersComponent extends Component {
</Styled.Row> </Styled.Row>
{isChatEnabled ? ( {isChatEnabled ? (
<Fragment> <>
<Styled.Row data-test="lockPublicChatItem"> <Styled.Row data-test="lockPublicChatItem">
<Styled.Col aria-hidden="true"> <Styled.Col aria-hidden="true">
<Styled.FormElement> <Styled.FormElement>
@ -283,34 +286,35 @@ class LockViewersComponent extends Component {
</Styled.FormElementRight> </Styled.FormElementRight>
</Styled.Col> </Styled.Col>
</Styled.Row> </Styled.Row>
<Styled.Row data-test="lockPrivateChatItem"> {isPrivateChatEnabled ? (
<Styled.Col aria-hidden="true"> <Styled.Row data-test="lockPrivateChatItem">
<Styled.FormElement> <Styled.Col aria-hidden="true">
<Styled.Label> <Styled.FormElement>
{intl.formatMessage(intlMessages.privateChatLable)} <Styled.Label>
</Styled.Label> {intl.formatMessage(intlMessages.privateChatLable)}
</Styled.FormElement> </Styled.Label>
</Styled.Col> </Styled.FormElement>
<Styled.Col> </Styled.Col>
<Styled.FormElementRight> <Styled.Col>
{this.displayLockStatus(lockSettingsProps.disablePrivateChat)} <Styled.FormElementRight>
<Toggle {this.displayLockStatus(lockSettingsProps.disablePrivateChat)}
icons={false} <Toggle
defaultChecked={lockSettingsProps.disablePrivateChat} icons={false}
onChange={() => { defaultChecked={lockSettingsProps.disablePrivateChat}
this.toggleLockSettings('disablePrivateChat'); onChange={() => {
}} this.toggleLockSettings('disablePrivateChat');
ariaLabel={intl.formatMessage(intlMessages.privateChatLable)} }}
showToggleLabel={showToggleLabel} ariaLabel={intl.formatMessage(intlMessages.privateChatLable)}
invertColors={invertColors} showToggleLabel={showToggleLabel}
data-test="lockPrivateChat" invertColors={invertColors}
/> data-test="lockPrivateChat"
</Styled.FormElementRight> />
</Styled.Col> </Styled.FormElementRight>
</Styled.Row> </Styled.Col>
</Fragment> </Styled.Row>
) : null ) : null}
} </>
) : null}
{isSharedNotesEnabled {isSharedNotesEnabled
? ( ? (
<Styled.Row data-test="lockEditSharedNotesItem"> <Styled.Row data-test="lockEditSharedNotesItem">
@ -339,8 +343,7 @@ class LockViewersComponent extends Component {
</Styled.Col> </Styled.Col>
</Styled.Row> </Styled.Row>
) )
: null : null}
}
<Styled.Row data-test="lockUserListItem"> <Styled.Row data-test="lockUserListItem">
<Styled.Col aria-hidden="true"> <Styled.Col aria-hidden="true">
<Styled.FormElement> <Styled.FormElement>

View File

@ -4,7 +4,7 @@ import LockViewersComponent from './component';
import useCurrentUser from '/imports/ui/core/hooks/useCurrentUser'; import useCurrentUser from '/imports/ui/core/hooks/useCurrentUser';
import { SET_LOCK_SETTINGS_PROPS, SET_WEBCAM_ONLY_FOR_MODERATOR } from './mutations'; import { SET_LOCK_SETTINGS_PROPS, SET_WEBCAM_ONLY_FOR_MODERATOR } from './mutations';
import useMeeting from '../../core/hooks/useMeeting'; import useMeeting from '../../core/hooks/useMeeting';
import { useIsChatEnabled, useIsSharedNotesEnabled } from '../../services/features'; import { useIsChatEnabled, useIsPrivateChatEnabled, useIsSharedNotesEnabled } from '../../services/features';
const LockViewersContainer = (props) => { const LockViewersContainer = (props) => {
const { data: currentUserData } = useCurrentUser((user) => ({ const { data: currentUserData } = useCurrentUser((user) => ({
@ -47,6 +47,7 @@ const LockViewersContainer = (props) => {
usersPolicies: m.usersPolicies, usersPolicies: m.usersPolicies,
})); }));
const isChatEnabled = useIsChatEnabled(); const isChatEnabled = useIsChatEnabled();
const isPrivateChatEnabled = useIsPrivateChatEnabled();
const isSharedNotesEnabled = useIsSharedNotesEnabled(); const isSharedNotesEnabled = useIsSharedNotesEnabled();
return amIModerator && meeting && ( return amIModerator && meeting && (
@ -57,6 +58,7 @@ const LockViewersContainer = (props) => {
showToggleLabel={false} showToggleLabel={false}
meeting={meeting} meeting={meeting}
isChatEnabled={isChatEnabled} isChatEnabled={isChatEnabled}
isPrivateChatEnabled={isPrivateChatEnabled}
isSharedNotesEnabled={isSharedNotesEnabled} isSharedNotesEnabled={isSharedNotesEnabled}
{...props} {...props}
/> />

View File

@ -6,6 +6,7 @@ import {
import Auth from '/imports/ui/services/auth'; import Auth from '/imports/ui/services/auth';
import logger from '/imports/startup/client/logger'; import logger from '/imports/startup/client/logger';
import { toggleMuteMicrophone } from '/imports/ui/components/audio/audio-graphql/audio-controls/input-stream-live-selector/service'; import { toggleMuteMicrophone } from '/imports/ui/components/audio/audio-graphql/audio-controls/input-stream-live-selector/service';
import { useIsPrivateChatEnabled } from '/imports/ui/services/features';
import getFromUserSettings from '/imports/ui/services/users-settings'; import getFromUserSettings from '/imports/ui/services/users-settings';
export const isVoiceOnlyUser = (userId: string) => userId.toString().startsWith('v_'); export const isVoiceOnlyUser = (userId: string) => userId.toString().startsWith('v_');
@ -31,7 +32,7 @@ export const generateActionsPermissions = (
const parentRoomModerator = getFromUserSettings('bbb_parent_room_moderator', false); const parentRoomModerator = getFromUserSettings('bbb_parent_room_moderator', false);
const isSubjectUserGuest = subjectUser.guest; const isSubjectUserGuest = subjectUser.guest;
const hasAuthority = currentUser.isModerator || amISubjectUser; const hasAuthority = currentUser.isModerator || amISubjectUser;
const allowedToChatPrivately = !amISubjectUser && !isDialInUser; const allowedToChatPrivately = !amISubjectUser && !isDialInUser && useIsPrivateChatEnabled();
const allowedToMuteAudio = hasAuthority const allowedToMuteAudio = hasAuthority
&& subjectUserVoice?.joined && subjectUserVoice?.joined
&& !isMuted && !isMuted

View File

@ -131,3 +131,7 @@ export function useIsChatMessageReactionsEnabled() {
&& window.meetingClientSettings.public.chat.toolbar.includes('reactions') && window.meetingClientSettings.public.chat.toolbar.includes('reactions')
); );
} }
export function useIsPrivateChatEnabled() {
return useDisabledFeatures().indexOf('privateChat') === -1;
}

View File

@ -479,7 +479,7 @@ endWhenNoModeratorDelayInMinutes=1
# List of features to disable (comma-separated) # List of features to disable (comma-separated)
# https://docs.bigbluebutton.org/3.0/development/api/#create # https://docs.bigbluebutton.org/3.0/development/api/#create
# Available options: # Available options:
# chat, sharedNotes, polls, screenshare, externalVideos, layouts, captions, liveTranscription, # chat, privateChat, sharedNotes, polls, screenshare, externalVideos, layouts, captions, liveTranscription,
# breakoutRooms, importSharedNotesFromBreakoutRooms, importPresentationWithAnnotationsFromBreakoutRooms, # breakoutRooms, importSharedNotesFromBreakoutRooms, importPresentationWithAnnotationsFromBreakoutRooms,
# presentation, downloadPresentationWithAnnotations, downloadPresentationOriginalFile, downloadPresentationConvertedToPdf, # presentation, downloadPresentationWithAnnotations, downloadPresentationOriginalFile, downloadPresentationConvertedToPdf,
# learningDashboard, learningDashboardDownloadSessionData, # learningDashboard, learningDashboardDownloadSessionData,

View File

@ -350,7 +350,7 @@ const createEndpointTableData = [
"name": "disabledFeatures", "name": "disabledFeatures",
"required": false, "required": false,
"type": "String", "type": "String",
"description": (<>List (comma-separated) of features to disable in a particular meeting. (added 2.5)<br /><br />Available options to disable:<br /><ul><li><code className="language-plaintext highlighter-rouge">breakoutRooms</code>- <b>Breakout Rooms</b> </li><li><code className="language-plaintext highlighter-rouge">captions</code>- <b>Closed Caption</b> </li><li><code className="language-plaintext highlighter-rouge">chat</code>- <b>Chat</b></li><li><code className="language-plaintext highlighter-rouge">downloadPresentationWithAnnotations</code>- <b>Annotated presentation download</b></li><li><code className="language-plaintext highlighter-rouge">snapshotOfCurrentSlide</code>- <b>Allow snapshot of the current slide</b></li><li><code className="language-plaintext highlighter-rouge">externalVideos</code>- <b>Share an external video</b> </li><li><code className="language-plaintext highlighter-rouge">importPresentationWithAnnotationsFromBreakoutRooms</code>- <b>Capture breakout presentation</b></li><li><code className="language-plaintext highlighter-rouge">importSharedNotesFromBreakoutRooms</code>- <b>Capture breakout shared notes</b></li><li><code className="language-plaintext highlighter-rouge">layouts</code>- <b>Layouts</b> (allow only default layout)</li><li><code className="language-plaintext highlighter-rouge">learningDashboard</code>- <b>Learning Analytics Dashboard</b></li><li><code className="language-plaintext highlighter-rouge">learningDashboardDownloadSessionData</code>- <b>Learning Analytics Dashboard Download Session Data (prevents the option to download)</b></li><li><code className="language-plaintext highlighter-rouge">polls</code>- <b>Polls</b> </li><li><code className="language-plaintext highlighter-rouge">screenshare</code>- <b>Screen Sharing</b></li><li><code className="language-plaintext highlighter-rouge">sharedNotes</code>- <b>Shared Notes</b></li><li><code className="language-plaintext highlighter-rouge">virtualBackgrounds</code>- <b>Virtual Backgrounds</b></li><li><code className="language-plaintext highlighter-rouge">customVirtualBackgrounds</code>- <b>Virtual Backgrounds Upload</b></li><li><code className="language-plaintext highlighter-rouge">liveTranscription</code>- <b>Live Transcription</b></li><li><code className="language-plaintext highlighter-rouge">presentation</code>- <b>Presentation</b></li><li><code className="language-plaintext highlighter-rouge">cameraAsContent</code>-<b>Enables/Disables camera as a content</b></li><li><code className="language-plaintext highlighter-rouge">timer</code>- <b>disables timer</b></li><li><code className="language-plaintext highlighter-rouge">infiniteWhiteboard</code>- <b>Infinite Whiteboard (added in BigBlueButton 3.0)</b></li></ul></>) "description": (<>List (comma-separated) of features to disable in a particular meeting. (added 2.5)<br /><br />Available options to disable:<br /><ul><li><code className="language-plaintext highlighter-rouge">breakoutRooms</code>- <b>Breakout Rooms</b> </li><li><code className="language-plaintext highlighter-rouge">captions</code>- <b>Closed Caption</b> </li><li><code className="language-plaintext highlighter-rouge">chat</code>- <b>Chat</b></li><li><code className="language-plaintext highlighter-rouge">privateChat</code>- <b>Private Chat</b></li><li><code className="language-plaintext highlighter-rouge">downloadPresentationWithAnnotations</code>- <b>Annotated presentation download</b></li><li><code className="language-plaintext highlighter-rouge">snapshotOfCurrentSlide</code>- <b>Allow snapshot of the current slide</b></li><li><code className="language-plaintext highlighter-rouge">externalVideos</code>- <b>Share an external video</b> </li><li><code className="language-plaintext highlighter-rouge">importPresentationWithAnnotationsFromBreakoutRooms</code>- <b>Capture breakout presentation</b></li><li><code className="language-plaintext highlighter-rouge">importSharedNotesFromBreakoutRooms</code>- <b>Capture breakout shared notes</b></li><li><code className="language-plaintext highlighter-rouge">layouts</code>- <b>Layouts</b> (allow only default layout)</li><li><code className="language-plaintext highlighter-rouge">learningDashboard</code>- <b>Learning Analytics Dashboard</b></li><li><code className="language-plaintext highlighter-rouge">learningDashboardDownloadSessionData</code>- <b>Learning Analytics Dashboard Download Session Data (prevents the option to download)</b></li><li><code className="language-plaintext highlighter-rouge">polls</code>- <b>Polls</b> </li><li><code className="language-plaintext highlighter-rouge">screenshare</code>- <b>Screen Sharing</b></li><li><code className="language-plaintext highlighter-rouge">sharedNotes</code>- <b>Shared Notes</b></li><li><code className="language-plaintext highlighter-rouge">virtualBackgrounds</code>- <b>Virtual Backgrounds</b></li><li><code className="language-plaintext highlighter-rouge">customVirtualBackgrounds</code>- <b>Virtual Backgrounds Upload</b></li><li><code className="language-plaintext highlighter-rouge">liveTranscription</code>- <b>Live Transcription</b></li><li><code className="language-plaintext highlighter-rouge">presentation</code>- <b>Presentation</b></li><li><code className="language-plaintext highlighter-rouge">cameraAsContent</code>-<b>Enables/Disables camera as a content</b></li><li><code className="language-plaintext highlighter-rouge">timer</code>- <b>disables timer</b></li><li><code className="language-plaintext highlighter-rouge">infiniteWhiteboard</code>- <b>Infinite Whiteboard (added in BigBlueButton 3.0)</b></li></ul></>)
}, },
{ {
"name": "disabledFeaturesExclude", "name": "disabledFeaturesExclude",