feat(bbb-html5): Ban specific users from the public chat (#20585)
* Initial user lock changes * Show lock icon
This commit is contained in:
parent
15525a6936
commit
fa0ad14c35
@ -0,0 +1,36 @@
|
||||
package org.bigbluebutton.core.apps.users
|
||||
|
||||
import org.bigbluebutton.LockSettingsUtil
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.models.{ Users2x, VoiceUsers }
|
||||
import org.bigbluebutton.core.running.{ MeetingActor, OutMsgRouter }
|
||||
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
|
||||
|
||||
trait LockUserChatInMeetingCmdMsgHdlr extends RightsManagementTrait {
|
||||
this: MeetingActor =>
|
||||
|
||||
val outGW: OutMsgRouter
|
||||
|
||||
def handleLockUserChatInMeetingCmdMsg(msg: LockUserChatInMeetingCmdMsg) {
|
||||
|
||||
def build(meetingId: String, userId: String, isLocked: Boolean): BbbCommonEnvCoreMsg = {
|
||||
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, meetingId, userId)
|
||||
val envelope = BbbCoreEnvelope(LockUserChatInMeetingEvtMsg.NAME, routing)
|
||||
val body = LockUserChatInMeetingEvtMsgBody(userId, isLocked)
|
||||
val header = BbbClientMsgHeader(LockUserChatInMeetingEvtMsg.NAME, meetingId, userId)
|
||||
val event = LockUserChatInMeetingEvtMsg(header, body)
|
||||
|
||||
BbbCommonEnvCoreMsg(envelope, event)
|
||||
}
|
||||
|
||||
if (permissionFailed(PermissionCheck.MOD_LEVEL, PermissionCheck.VIEWER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
val reason = "No permission to lock user chat in meeting."
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, outGW, liveMeeting)
|
||||
} else {
|
||||
log.info("Lock user chat. meetingId=" + props.meetingProp.intId + " userId=" + msg.body.userId + " isLocked=" + msg.body.isLocked)
|
||||
val event = build(props.meetingProp.intId, msg.body.userId, msg.body.isLocked)
|
||||
outGW.send(event)
|
||||
}
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ import org.bigbluebutton.core.running.MeetingActor
|
||||
trait UsersApp2x
|
||||
extends UserLeaveReqMsgHdlr
|
||||
with LockUserInMeetingCmdMsgHdlr
|
||||
with LockUserChatInMeetingCmdMsgHdlr
|
||||
with LockUsersInMeetingCmdMsgHdlr
|
||||
with GetLockSettingsReqMsgHdlr
|
||||
with ChangeUserEmojiCmdMsgHdlr
|
||||
|
@ -386,6 +386,8 @@ class ReceivedJsonMsgHandlerActor(
|
||||
// Lock settings
|
||||
case LockUserInMeetingCmdMsg.NAME =>
|
||||
routeGenericMsg[LockUserInMeetingCmdMsg](envelope, jsonNode)
|
||||
case LockUserChatInMeetingCmdMsg.NAME =>
|
||||
routeGenericMsg[LockUserChatInMeetingCmdMsg](envelope, jsonNode)
|
||||
case ChangeLockSettingsInMeetingCmdMsg.NAME =>
|
||||
routeGenericMsg[ChangeLockSettingsInMeetingCmdMsg](envelope, jsonNode)
|
||||
case LockUsersInMeetingCmdMsg.NAME =>
|
||||
|
@ -514,6 +514,7 @@ class MeetingActor(
|
||||
handleSetLockSettings(m)
|
||||
updateUserLastActivity(m.body.setBy)
|
||||
case m: LockUserInMeetingCmdMsg => handleLockUserInMeetingCmdMsg(m)
|
||||
case m: LockUserChatInMeetingCmdMsg => handleLockUserChatInMeetingCmdMsg(m)
|
||||
case m: LockUsersInMeetingCmdMsg => handleLockUsersInMeetingCmdMsg(m)
|
||||
case m: GetLockSettingsReqMsg => handleGetLockSettingsReqMsg(m)
|
||||
|
||||
|
@ -347,6 +347,20 @@ object UserLockedInMeetingEvtMsg { val NAME = "UserLockedInMeetingEvtMsg" }
|
||||
case class UserLockedInMeetingEvtMsg(header: BbbClientMsgHeader, body: UserLockedInMeetingEvtMsgBody) extends BbbCoreMsg
|
||||
case class UserLockedInMeetingEvtMsgBody(userId: String, locked: Boolean, lockedBy: String)
|
||||
|
||||
/**
|
||||
* Sent from client to lock user in meeting.
|
||||
*/
|
||||
object LockUserChatInMeetingCmdMsg { val NAME = "LockUserChatInMeetingCmdMsg" }
|
||||
case class LockUserChatInMeetingCmdMsg(header: BbbClientMsgHeader, body: LockUserChatInMeetingCmdMsgBody) extends StandardMsg
|
||||
case class LockUserChatInMeetingCmdMsgBody(userId: String, isLocked: Boolean, lockedBy: String)
|
||||
|
||||
/**
|
||||
* Send to client that user has been locked.
|
||||
*/
|
||||
object LockUserChatInMeetingEvtMsg { val NAME = "LockUserChatInMeetingEvtMsg" }
|
||||
case class LockUserChatInMeetingEvtMsg(header: BbbClientMsgHeader, body: LockUserChatInMeetingEvtMsgBody) extends BbbCoreMsg
|
||||
case class LockUserChatInMeetingEvtMsgBody(userId: String, isLocked: Boolean)
|
||||
|
||||
/**
|
||||
* Sent by client to lock users.
|
||||
*/
|
||||
|
@ -7,6 +7,7 @@ import handleMeetingLocksChange from './handlers/meetingLockChange';
|
||||
import handleGuestPolicyChanged from './handlers/guestPolicyChanged';
|
||||
import handleGuestLobbyMessageChanged from './handlers/guestLobbyMessageChanged';
|
||||
import handleUserLockChange from './handlers/userLockChange';
|
||||
import handleUserChatLockChange from './handlers/handleUserChatLockChange';
|
||||
import handleRecordingStatusChange from './handlers/recordingStatusChange';
|
||||
import handleRecordingTimerChange from './handlers/recordingTimerChange';
|
||||
import handleTimeRemainingUpdate from './handlers/timeRemainingUpdate';
|
||||
@ -24,6 +25,7 @@ RedisPubSub.on('MeetingEndingEvtMsg', handleMeetingEnd);
|
||||
RedisPubSub.on('MeetingDestroyedEvtMsg', handleMeetingDestruction);
|
||||
RedisPubSub.on('LockSettingsInMeetingChangedEvtMsg', handleMeetingLocksChange);
|
||||
RedisPubSub.on('UserLockedInMeetingEvtMsg', handleUserLockChange);
|
||||
RedisPubSub.on('LockUserChatInMeetingEvtMsg', handleUserChatLockChange);
|
||||
RedisPubSub.on('RecordingStatusChangedEvtMsg', handleRecordingStatusChange);
|
||||
RedisPubSub.on('UpdateRecordingTimerEvtMsg', handleRecordingTimerChange);
|
||||
RedisPubSub.on('WebcamsOnlyForModeratorChangedEvtMsg', handleChangeWebcamOnlyModerator);
|
||||
|
@ -0,0 +1,5 @@
|
||||
import changeUserChatLock from '../modifiers/changeUserChatLock';
|
||||
|
||||
export default async function handleUserChatLockChange({ body }, meetingId) {
|
||||
await changeUserChatLock(meetingId, body);
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import Users from '/imports/api/users';
|
||||
import Meetings from '/imports/api/meetings';
|
||||
import { check } from 'meteor/check';
|
||||
|
||||
export default async function changeUserChatLock(meetingId, payload) {
|
||||
check(meetingId, String);
|
||||
check(payload, {
|
||||
userId: String,
|
||||
isLocked: Boolean,
|
||||
});
|
||||
|
||||
const { userId, isLocked } = payload;
|
||||
|
||||
const userSelector = {
|
||||
meetingId,
|
||||
userId,
|
||||
};
|
||||
|
||||
const userModifier = {
|
||||
$set: {
|
||||
chatLocked: isLocked,
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
const { numberAffected } = await Users.upsertAsync(userSelector, userModifier);
|
||||
|
||||
if ( numberAffected ) {
|
||||
Logger.info(`Updated lock settings in meeting ${meetingId}: disablePublicChat=${isLocked}`);
|
||||
} else {
|
||||
Logger.info(`Kept lock settings in meeting ${meetingId}`);
|
||||
}
|
||||
} catch (err) {
|
||||
Logger.error(`Changing user chat lock setting: ${err}`);
|
||||
}
|
||||
}
|
@ -10,6 +10,7 @@ import assignPresenter from './methods/assignPresenter';
|
||||
import changeRole from './methods/changeRole';
|
||||
import removeUser from './methods/removeUser';
|
||||
import toggleUserLock from './methods/toggleUserLock';
|
||||
import toggleUserChatLock from './methods/toggleUserChatLock';
|
||||
import setUserEffectiveConnectionType from './methods/setUserEffectiveConnectionType';
|
||||
import userActivitySign from './methods/userActivitySign';
|
||||
import userLeftMeeting from './methods/userLeftMeeting';
|
||||
@ -31,6 +32,7 @@ Meteor.methods({
|
||||
removeUser,
|
||||
validateAuthToken,
|
||||
toggleUserLock,
|
||||
toggleUserChatLock,
|
||||
setUserEffectiveConnectionType,
|
||||
userActivitySign,
|
||||
userLeftMeeting,
|
||||
|
@ -0,0 +1,34 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { check } from 'meteor/check';
|
||||
import RedisPubSub from '/imports/startup/server/redis';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
import { extractCredentials } from '/imports/api/common/server/helpers';
|
||||
|
||||
export default function toggleUserChatLock(userId, isLocked) {
|
||||
try {
|
||||
const REDIS_CONFIG = Meteor.settings.private.redis;
|
||||
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
|
||||
const EVENT_NAME = 'LockUserChatInMeetingCmdMsg';
|
||||
|
||||
const { meetingId, requesterUserId: lockedBy } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(lockedBy, String);
|
||||
check(userId, String);
|
||||
check(isLocked, Boolean);
|
||||
|
||||
const payload = {
|
||||
lockedBy,
|
||||
userId,
|
||||
isLocked,
|
||||
};
|
||||
|
||||
Logger.verbose('Updated chat lock status for user', {
|
||||
meetingId, userId, isLocked, lockedBy,
|
||||
});
|
||||
|
||||
RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, lockedBy, payload);
|
||||
} catch (err) {
|
||||
Logger.error(`Exception while invoking method toggleUserChatLock ${err.stack}`);
|
||||
}
|
||||
}
|
@ -150,6 +150,8 @@ const ChatContainer = (props) => {
|
||||
partnerIsLoggedOut = !!(users[Auth.meetingID][idUser]?.loggedOut
|
||||
|| users[Auth.meetingID][idUser]?.ejected);
|
||||
isChatLocked = isChatLockedPrivate && !(users[Auth.meetingID][idUser]?.role === ROLE_MODERATOR);
|
||||
} else if (users[Auth.meetingID][Auth.userID]?.chatLocked === true) {
|
||||
isChatLocked = true;
|
||||
} else {
|
||||
isChatLocked = isChatLockedPublic;
|
||||
}
|
||||
|
@ -169,6 +169,10 @@ const isChatLocked = (receiverID) => {
|
||||
}
|
||||
}
|
||||
|
||||
if (user.chatLocked === true && user.role !== ROLE_MODERATOR) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
|
@ -674,6 +674,10 @@ const getGroupChatPrivate = (senderUserId, receiver) => {
|
||||
}
|
||||
};
|
||||
|
||||
const toggleUserChatLock = (userId, isLocked) => {
|
||||
makeCall('toggleUserChatLock', userId, isLocked);
|
||||
}
|
||||
|
||||
const toggleUserLock = (userId, lockStatus) => {
|
||||
makeCall('toggleUserLock', userId, lockStatus);
|
||||
};
|
||||
@ -820,6 +824,7 @@ export default {
|
||||
roving,
|
||||
getCustomLogoUrl,
|
||||
getGroupChatPrivate,
|
||||
toggleUserChatLock,
|
||||
hasBreakoutRoom,
|
||||
getEmojiList: () => EMOJI_STATUSES,
|
||||
getEmoji,
|
||||
|
@ -47,6 +47,14 @@ const messages = defineMessages({
|
||||
id: 'app.audio.backLabel',
|
||||
description: 'label for option to hide emoji menu',
|
||||
},
|
||||
lockPublicChat: {
|
||||
id: 'app.userList.menu.lockPublicChat.label',
|
||||
description: 'label for option to lock user\'s public chat',
|
||||
},
|
||||
unlockPublicChat: {
|
||||
id: 'app.userList.menu.unlockPublicChat.label',
|
||||
description: 'label for option to lock user\'s public chat',
|
||||
},
|
||||
StartPrivateChat: {
|
||||
id: 'app.userList.menu.chat.label',
|
||||
description: 'label for option to start a new private chat',
|
||||
@ -175,6 +183,7 @@ const propTypes = {
|
||||
normalizeEmojiName: PropTypes.func.isRequired,
|
||||
getScrollContainerRef: PropTypes.func.isRequired,
|
||||
toggleUserLock: PropTypes.func.isRequired,
|
||||
toggleUserChatLock: PropTypes.func.isRequired,
|
||||
isMeteorConnected: PropTypes.bool.isRequired,
|
||||
isMe: PropTypes.func.isRequired,
|
||||
};
|
||||
@ -286,6 +295,7 @@ class UserListItem extends PureComponent {
|
||||
voiceUser,
|
||||
getAvailableActions,
|
||||
getGroupChatPrivate,
|
||||
toggleUserChatLock,
|
||||
getEmojiList,
|
||||
setEmojiStatus,
|
||||
setUserAway,
|
||||
@ -345,6 +355,7 @@ class UserListItem extends PureComponent {
|
||||
|
||||
const { allowUserLookup } = Meteor.settings.public.app;
|
||||
const userLocked = user.locked && user.role !== ROLE_MODERATOR;
|
||||
const userChatLocked = user.chatLocked;
|
||||
|
||||
const availableActions = [
|
||||
{
|
||||
@ -406,6 +417,29 @@ class UserListItem extends PureComponent {
|
||||
icon: 'chat',
|
||||
dataTest: 'startPrivateChat',
|
||||
},
|
||||
{
|
||||
allowed: isChatEnabled()
|
||||
&& user.role !== ROLE_MODERATOR
|
||||
&& currentUser.role === ROLE_MODERATOR
|
||||
&& !isDialInUser
|
||||
&& isMeteorConnected,
|
||||
key: 'lockChat',
|
||||
label: userChatLocked ? intl.formatMessage(messages.unlockPublicChat) : intl.formatMessage(messages.lockPublicChat),
|
||||
onClick: () => {
|
||||
this.handleClose();
|
||||
toggleUserChatLock(user.userId, !userChatLocked);
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_SIDEBAR_CONTENT_IS_OPEN,
|
||||
value: true,
|
||||
});
|
||||
layoutContextDispatch({
|
||||
type: ACTIONS.SET_SIDEBAR_CONTENT_PANEL,
|
||||
value: PANELS.CHAT,
|
||||
});
|
||||
},
|
||||
icon: userChatLocked ? 'unlock' : 'lock',
|
||||
dataTest: 'togglePublicChat',
|
||||
},
|
||||
{
|
||||
allowed: allowedToResetStatus
|
||||
&& user.emoji !== 'none'
|
||||
@ -786,7 +820,7 @@ class UserListItem extends PureComponent {
|
||||
);
|
||||
}
|
||||
|
||||
if (isThisMeetingLocked && user.locked && user.role !== ROLE_MODERATOR) {
|
||||
if (((isThisMeetingLocked && user.locked) || user.chatLocked) && user.role !== ROLE_MODERATOR) {
|
||||
userNameSub.push(
|
||||
<span key={uniqueId('lock-')}>
|
||||
<Icon iconName="lock" />
|
||||
|
@ -14,6 +14,7 @@ const UserListItemContainer = (props) => {
|
||||
toggleVoice,
|
||||
removeUser,
|
||||
toggleUserLock,
|
||||
toggleUserChatLock,
|
||||
changeRole,
|
||||
ejectUserCameras,
|
||||
assignPresenter,
|
||||
@ -29,6 +30,7 @@ const UserListItemContainer = (props) => {
|
||||
toggleVoice,
|
||||
removeUser,
|
||||
toggleUserLock,
|
||||
toggleUserChatLock,
|
||||
changeRole,
|
||||
ejectUserCameras,
|
||||
assignPresenter,
|
||||
|
@ -151,6 +151,8 @@
|
||||
"app.userList.menu.away": "Set yourself as away",
|
||||
"app.userList.menu.notAway": "Set yourself as active",
|
||||
"app.userList.menu.chat.label": "Start a private chat",
|
||||
"app.userList.menu.lockPublicChat.label": "Lock public chat",
|
||||
"app.userList.menu.unlockPublicChat.label": "Unlock public chat",
|
||||
"app.userList.menu.clearStatus.label": "Clear status",
|
||||
"app.userList.menu.removeUser.label": "Remove user",
|
||||
"app.userList.menu.removeConfirmation.label": "Remove user ({0})",
|
||||
|
Loading…
Reference in New Issue
Block a user