diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/SystemConfiguration.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/SystemConfiguration.scala index 54c67fecf2..05c9415d49 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/SystemConfiguration.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/SystemConfiguration.scala @@ -43,6 +43,7 @@ trait SystemConfiguration { lazy val voiceConfRecordCodec = Try(config.getString("voiceConf.recordCodec")).getOrElse("wav") lazy val checkVoiceRecordingInterval = Try(config.getInt("voiceConf.checkRecordingInterval")).getOrElse(19) lazy val syncVoiceUsersStatusInterval = Try(config.getInt("voiceConf.syncUserStatusInterval")).getOrElse(43) + lazy val ejectRogueVoiceUsers = Try(config.getBoolean("voiceConf.ejectRogueVoiceUsers")).getOrElse(false) lazy val recordingChapterBreakLengthInMinutes = Try(config.getInt("recording.chapterBreakLengthInMinutes")).getOrElse(0) diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/VoiceApp.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/VoiceApp.scala index d2d374d8aa..4a49d961f1 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/VoiceApp.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/VoiceApp.scala @@ -1,15 +1,16 @@ package org.bigbluebutton.core.apps.voice +import org.bigbluebutton.SystemConfiguration import org.bigbluebutton.LockSettingsUtil import org.bigbluebutton.common2.msgs.{ BbbClientMsgHeader, BbbCommonEnvCoreMsg, BbbCoreEnvelope, ConfVoiceUser, MessageTypes, Routing, UserJoinedVoiceConfToClientEvtMsg, UserJoinedVoiceConfToClientEvtMsgBody, UserLeftVoiceConfToClientEvtMsg, UserLeftVoiceConfToClientEvtMsgBody, UserMutedVoiceEvtMsg, UserMutedVoiceEvtMsgBody } import org.bigbluebutton.core.apps.breakout.BreakoutHdlrHelpers import org.bigbluebutton.core.bus.InternalEventBus -import org.bigbluebutton.core.models.{ VoiceUserState, VoiceUsers } +import org.bigbluebutton.core.models.{ Users2x, VoiceUserState, VoiceUsers } import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter } import org.bigbluebutton.core2.MeetingStatus2x import org.bigbluebutton.core2.message.senders.MsgBuilder -object VoiceApp { +object VoiceApp extends SystemConfiguration { def genRecordPath( recordDir: String, @@ -136,6 +137,20 @@ object VoiceApp { // Update the user status to indicate they are still in the voice conference. VoiceUsers.setLastStatusUpdate(liveMeeting.voiceUsers, vu) } + + // Purge voice users that don't have a matching user record + // Avoid this if the meeting is a breakout room since might be real + // voice users participating + // Also avoid ejecting if the user is dial-in (v_*) + if (ejectRogueVoiceUsers && !liveMeeting.props.meetingProp.isBreakout && !cvu.intId.startsWith("v_")) { + Users2x.findWithIntId(liveMeeting.users2x, cvu.intId) match { + case Some(_) => + case None => + println(s"Ejecting rogue voice user. meetingId=${liveMeeting.props.meetingProp.intId} userId=${cvu.intId}") + val event = MsgBuilder.buildEjectUserFromVoiceConfSysMsg(liveMeeting.props.meetingProp.intId, liveMeeting.props.voiceProp.voiceConf, cvu.voiceUserId) + outGW.send(event) + } + } case None => handleUserJoinedVoiceConfEvtMsg( liveMeeting, diff --git a/akka-bbb-apps/src/universal/conf/application.conf b/akka-bbb-apps/src/universal/conf/application.conf index 564b152038..4318352efb 100755 --- a/akka-bbb-apps/src/universal/conf/application.conf +++ b/akka-bbb-apps/src/universal/conf/application.conf @@ -85,6 +85,8 @@ voiceConf { checkRecordingInterval = 23 # Internval seconds to sync voice users status. syncUserStatusInterval = 41 + # Voice users with no matching user record + ejectRogueVoiceUsers = true } recording { diff --git a/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/actions/GetUsersStatusCommand.java b/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/actions/GetUsersStatusCommand.java index 6c520c36d9..1d5c6ac8ea 100755 --- a/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/actions/GetUsersStatusCommand.java +++ b/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/actions/GetUsersStatusCommand.java @@ -85,10 +85,12 @@ public class GetUsersStatusCommand extends FreeswitchCommand { voiceUserId = callWithSess.group(1).trim(); clientSession = callWithSess.group(2).trim(); callerIdName = callWithSess.group(3).trim(); - } else - if (matcher.matches()) { + } else if (matcher.matches()) { voiceUserId = matcher.group(1).trim(); callerIdName = matcher.group(2).trim(); + } else { + // This is a caller using dial in or out + voiceUserId = "v_" + member.getId().toString(); } log.info("Conf user. uuid=" + uuid diff --git a/bigbluebutton-html5/imports/api/voice-users/server/methods/muteToggle.js b/bigbluebutton-html5/imports/api/voice-users/server/methods/muteToggle.js index a91c5cc78e..3f89e94ee9 100644 --- a/bigbluebutton-html5/imports/api/voice-users/server/methods/muteToggle.js +++ b/bigbluebutton-html5/imports/api/voice-users/server/methods/muteToggle.js @@ -6,7 +6,7 @@ import VoiceUsers from '/imports/api/voice-users'; import Meetings from '/imports/api/meetings'; import Logger from '/imports/startup/server/logger'; -export default function muteToggle(uId) { +export default function muteToggle(uId, toggle) { const REDIS_CONFIG = Meteor.settings.private.redis; const CHANNEL = REDIS_CONFIG.channels.toAkkaApps; const EVENT_NAME = 'MuteUserCmdMsg'; @@ -39,10 +39,18 @@ export default function muteToggle(uId) { } } + let _muted; + + if ((toggle === undefined) || (toggle === null)) { + _muted = !muted; + } else { + _muted = !!toggle; + } + const payload = { userId: userToMute, mutedBy: requesterUserId, - mute: !muted, + mute: _muted, }; RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload); diff --git a/bigbluebutton-html5/imports/ui/components/audio/service.js b/bigbluebutton-html5/imports/ui/components/audio/service.js index 7e111df651..2ba4d373b1 100755 --- a/bigbluebutton-html5/imports/ui/components/audio/service.js +++ b/bigbluebutton-html5/imports/ui/components/audio/service.js @@ -6,10 +6,41 @@ import Meetings from '/imports/api/meetings'; import { makeCall } from '/imports/ui/services/api'; import VoiceUsers from '/imports/api/voice-users'; import logger from '/imports/startup/client/logger'; +import { throttle } from 'lodash'; +import Storage from '../../services/storage/session'; const ROLE_MODERATOR = Meteor.settings.public.user.role_moderator; const TOGGLE_MUTE_THROTTLE_TIME = Meteor.settings.public.media.toggleMuteThrottleTime; +const MUTED_KEY = 'muted'; + +const recoverMicState = () => { + const muted = Storage.getItem(MUTED_KEY); + + if ((muted === undefined) || (muted === null)) { + return; + } + + logger.debug({ + logCode: 'audio_recover_mic_state', + }, `Audio recover previous mic state: muted = ${muted}`); + makeCall('toggleVoice', null, muted); +}; + +const audioEventHandler = (event) => { + if (!event) { + return; + } + + switch (event.name) { + case 'started': + recoverMicState(); + break; + default: + break; + } +}; + const init = (messages, intl) => { AudioManager.setAudioMessages(messages, intl); if (AudioManager.initialized) return; @@ -33,7 +64,7 @@ const init = (messages, intl) => { microphoneLockEnforced, }; - AudioManager.init(userData); + AudioManager.init(userData, audioEventHandler); }; const isVoiceUser = () => { @@ -46,6 +77,9 @@ const toggleMuteMicrophone = throttle(() => { const user = VoiceUsers.findOne({ meetingId: Auth.meetingID, intId: Auth.userID, }, { fields: { muted: 1 } }); + + Storage.setItem(MUTED_KEY, !user.muted); + if (user.muted) { logger.info({ logCode: 'audiomanager_unmute_audio', diff --git a/bigbluebutton-html5/imports/ui/components/external-video-player/service.js b/bigbluebutton-html5/imports/ui/components/external-video-player/service.js index 66bd3ea7cc..9de4de3c7a 100644 --- a/bigbluebutton-html5/imports/ui/components/external-video-player/service.js +++ b/bigbluebutton-html5/imports/ui/components/external-video-player/service.js @@ -11,7 +11,7 @@ import ReactPlayer from 'react-player'; import Panopto from './custom-players/panopto'; const isUrlValid = (url) => { - return ReactPlayer.canPlay(url) || Panopto.canPlay(url); + return /^https.*$/.test(url) && (ReactPlayer.canPlay(url) || Panopto.canPlay(url)); } const startWatching = (url) => { diff --git a/bigbluebutton-html5/imports/ui/services/audio-manager/index.js b/bigbluebutton-html5/imports/ui/services/audio-manager/index.js index 8ff349355f..f5c0208794 100755 --- a/bigbluebutton-html5/imports/ui/services/audio-manager/index.js +++ b/bigbluebutton-html5/imports/ui/services/audio-manager/index.js @@ -58,13 +58,14 @@ class AudioManager { this.monitor = this.monitor.bind(this); } - init(userData) { + init(userData, audioEventHandler) { this.bridge = new SIPBridge(userData); // no alternative as of 2019-03-08 if (this.useKurento) { this.listenOnlyBridge = new KurentoBridge(userData); } this.userData = userData; this.initialized = true; + this.audioEventHandler = audioEventHandler; } setAudioMessages(messages, intl) { @@ -338,6 +339,7 @@ class AudioManager { this.notify(this.intl.formatMessage(this.messages.info.JOINED_AUDIO)); logger.info({ logCode: 'audio_joined' }, 'Audio Joined'); if (STATS.enabled) this.monitor(); + this.audioEventHandler({ name: 'started' }); } } diff --git a/bigbluebutton-html5/private/config/settings.yml b/bigbluebutton-html5/private/config/settings.yml index cb90b82f13..98a1dda53d 100755 --- a/bigbluebutton-html5/private/config/settings.yml +++ b/bigbluebutton-html5/private/config/settings.yml @@ -249,7 +249,7 @@ public: # profile: a camera profile id from the cameraProfiles configuration array # that will be applied to all cameras when threshold is hit cameraQualityThresholds: - enabled: false + enabled: true thresholds: - threshold: 8 profile: low-u8 @@ -265,7 +265,7 @@ public: profile: low-u30 pagination: # whether to globally enable or disable pagination. - enabled: false + enabled: true # how long (in ms) the negotiation will be debounced after a page change. pageChangeDebounceTime: 2500 # video page sizes for DESKTOP endpoints. It stands for the number of SUBSCRIBER streams. @@ -273,11 +273,11 @@ public: # A page size of 0 (zero) means that the page size is unlimited (disabled). desktopPageSizes: moderator: 0 - viewer: 5 + viewer: 0 # video page sizes for MOBILE endpoints mobilePageSizes: - moderator: 2 - viewer: 2 + moderator: 6 + viewer: 4 syncUsersWithConnectionManager: enabled: false syncInterval: 60000 diff --git a/bigbluebutton-web/grails-app/controllers/org/bigbluebutton/web/controllers/ApiController.groovy b/bigbluebutton-web/grails-app/controllers/org/bigbluebutton/web/controllers/ApiController.groovy index 4e4f61a7db..9e4fd1028f 100755 --- a/bigbluebutton-web/grails-app/controllers/org/bigbluebutton/web/controllers/ApiController.groovy +++ b/bigbluebutton-web/grails-app/controllers/org/bigbluebutton/web/controllers/ApiController.groovy @@ -212,7 +212,12 @@ class ApiController { // BEGIN - backward compatibility if (StringUtils.isEmpty(params.checksum)) { - invalid("checksumError", "You did not pass the checksum security check", REDIRECT_RESPONSE) + invalid("checksumError", "You did not pass the checksum security check") + return + } + + if (!paramsProcessorUtil.isChecksumSame(API_CALL, params.checksum, request.getQueryString())) { + invalid("checksumError", "You did not pass the checksum security check") return } @@ -244,11 +249,6 @@ class ApiController { return } - if (!paramsProcessorUtil.isChecksumSame(API_CALL, params.checksum, request.getQueryString())) { - invalid("checksumError", "You did not pass the checksum security check", REDIRECT_RESPONSE) - return - } - // END - backward compatibility // Do we have a checksum? If none, complain.