diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/ExternalProcessExecutor.java b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/ExternalProcessExecutor.java index c92943eb4d..d583f1eaad 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/ExternalProcessExecutor.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/ExternalProcessExecutor.java @@ -19,66 +19,76 @@ package org.bigbluebutton.presentation.imp; -import java.util.Timer; -import java.util.TimerTask; +import java.io.File; +import java.io.IOException; +import java.time.Duration; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * A wrapper class the executes an external command. - * - * See http://kylecartmell.com/?p=9 + * A wrapper class the executes an external command. * * @author Richard Alam - * + * @author Marcel Hellkamp */ public class ExternalProcessExecutor { private static Logger log = LoggerFactory.getLogger(ExternalProcessExecutor.class); - + // Replace with ProcessBuilder.Redirect.DISCARD in java 9+ + private static File DISCARD = new File( + System.getProperty("os.name").startsWith("Windows") ? "NUL" : "/dev/null"); + + /** + * Run COMMAND for at most timeoutMillis while ignoring any output. + * + * @deprecated The COMMAND string is split on whitespace to create an argument + * list. This won't work for arguments that contain whitespace. Use + * {@link #exec(List, Duration)} instead. + * + * @param COMMAND A single command or whitespace separated list of + * arguments. + * @param timeoutMillis Timeout in milliseconds. + * @return true if the command terminated in time with an exit value of 0. + */ + @Deprecated public boolean exec(String COMMAND, long timeoutMillis) { - Timer timer = new Timer(false); - Process p = null; - try { - InterruptTimerTask interrupter = new InterruptTimerTask(Thread.currentThread()); - timer.schedule(interrupter, timeoutMillis); - p = Runtime.getRuntime().exec(COMMAND); - int result = p.waitFor(); - if (result == 0) { - return true; - } else { - return false; - } - - } catch(Exception e) { - log.info("TIMEDOUT excuting : {}", COMMAND); - if (p != null) { - p.destroy(); - } - } finally { - timer.cancel(); // If the process returns within the timeout period, we have to stop the interrupter - // so that it does not unexpectedly interrupt some other code later. - - Thread.interrupted(); // We need to clear the interrupt flag on the current thread just in case - // interrupter executed after waitFor had already returned but before timer.cancel - // took effect. - // - // Oh, and there's also Sun bug 6420270 to worry about here. - } - return false; + return exec(Arrays.asList(COMMAND.split("[ \\t\\n\\r\\f]+")), Duration.ofMillis(timeoutMillis)); } - - class InterruptTimerTask extends TimerTask { - private Thread thread; + /** + * Run a command for a limited amount of time while ignoring any output. + * + * @param cmd List containing the program and its arguments. + * @param timeout Maximum execution time. + * @return true if the command terminated in time with an exit value of 0. + */ + public boolean exec(List cmd, Duration timeout) { - public InterruptTimerTask(Thread t) { - this.thread = t; - } + ProcessBuilder pb = new ProcessBuilder(cmd); + pb.redirectError(DISCARD); + pb.redirectOutput(DISCARD); - public void run() { - thread.interrupt(); - } + Process proc; + try { + proc = pb.start(); + } catch (IOException e) { + log.error("Failed to execute: {}", String.join(" ", cmd), e); + return false; + } + try { + if (!proc.waitFor(timeout.toMillis(), TimeUnit.MILLISECONDS)) { + log.warn("TIMEDOUT excuting: {}", String.join(" ", cmd)); + proc.destroy(); + } + return !proc.isAlive() && proc.exitValue() == 0; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + proc.destroy(); + return false; + } } } diff --git a/bigbluebutton-config/bigbluebutton-release b/bigbluebutton-config/bigbluebutton-release index c7c9a17c89..1d4cfab586 100644 --- a/bigbluebutton-config/bigbluebutton-release +++ b/bigbluebutton-config/bigbluebutton-release @@ -1,2 +1 @@ BIGBLUEBUTTON_RELEASE=2.4-beta-1 - diff --git a/bigbluebutton-html5/imports/api/breakouts/server/publishers.js b/bigbluebutton-html5/imports/api/breakouts/server/publishers.js index 1e9b6f0e0d..f3c39edd06 100755 --- a/bigbluebutton-html5/imports/api/breakouts/server/publishers.js +++ b/bigbluebutton-html5/imports/api/breakouts/server/publishers.js @@ -45,7 +45,28 @@ function breakouts(role) { ], }; - return Breakouts.find(selector); + const fields = { + fields: { + users: { + $elemMatch: { + // do not allow users to obtain 'redirectToHtml5JoinURL' for others + userId, + }, + }, + breakoutId: 1, + externalId: 1, + freeJoin: 1, + isDefaultName: 1, + joinedUsers: 1, + name: 1, + parentMeetingId: 1, + sequence: 1, + shortName: 1, + timeRemaining: 1, + }, + }; + + return Breakouts.find(selector, fields); } function publish(...args) { diff --git a/bigbluebutton-html5/imports/api/connection-status/server/methods/addConnectionStatus.js b/bigbluebutton-html5/imports/api/connection-status/server/methods/addConnectionStatus.js index 0c1c923014..f451f0d6af 100644 --- a/bigbluebutton-html5/imports/api/connection-status/server/methods/addConnectionStatus.js +++ b/bigbluebutton-html5/imports/api/connection-status/server/methods/addConnectionStatus.js @@ -11,8 +11,6 @@ const logConnectionStatus = (meetingId, userId, status, type, value) => { Logger.info(`Connection status updated: meetingId=${meetingId} userId=${userId} status=${status} type=${type}`); break; case 'warning': - // Skip - break; case 'danger': case 'critical': switch (type) { diff --git a/bigbluebutton-html5/imports/api/group-chat-msg/server/publishers.js b/bigbluebutton-html5/imports/api/group-chat-msg/server/publishers.js index 82317c35ee..9b4c17639a 100644 --- a/bigbluebutton-html5/imports/api/group-chat-msg/server/publishers.js +++ b/bigbluebutton-html5/imports/api/group-chat-msg/server/publishers.js @@ -25,7 +25,7 @@ function groupChatMsg(chatsIds) { timestamp: { $gte: User.authTokenValidatedTime }, $or: [ { meetingId, chatId: { $eq: PUBLIC_GROUP_CHAT_ID } }, - { chatId: { $in: chatsIds } }, + { meetingId, chatId: { $in: chatsIds } }, ], }; return GroupChatMsg.find(selector); diff --git a/bigbluebutton-html5/imports/api/guest-users/server/publishers.js b/bigbluebutton-html5/imports/api/guest-users/server/publishers.js index bc0b063a22..136559b95f 100644 --- a/bigbluebutton-html5/imports/api/guest-users/server/publishers.js +++ b/bigbluebutton-html5/imports/api/guest-users/server/publishers.js @@ -1,18 +1,30 @@ import GuestUsers from '/imports/api/guest-users/'; +import Users from '/imports/api/users'; import { Meteor } from 'meteor/meteor'; import Logger from '/imports/startup/server/logger'; import AuthTokenValidation, { ValidationStates } from '/imports/api/auth-token-validation'; +const ROLE_MODERATOR = Meteor.settings.public.user.role_moderator; + function guestUsers() { const tokenValidation = AuthTokenValidation.findOne({ connectionId: this.connection.id }); if (!tokenValidation || tokenValidation.validationStatus !== ValidationStates.VALIDATED) { - Logger.warn(`Publishing GuestUsers was requested by unauth connection ${this.connection.id}`); + Logger.warn(`Publishing GuestUser was requested by unauth connection ${this.connection.id}`); return GuestUsers.find({ meetingId: '' }); } const { meetingId, userId } = tokenValidation; + const User = Users.findOne({ userId, meetingId }, { fields: { role: 1 } }); + if (!User || User.role !== ROLE_MODERATOR) { + Logger.warn( + 'Publishing current-poll was requested by non-moderator connection', + { meetingId, userId, connectionId: this.connection.id }, + ); + return GuestUsers.find({ meetingId: '' }); + } + Logger.debug(`Publishing GuestUsers for ${meetingId} ${userId}`); return GuestUsers.find({ meetingId }); diff --git a/bigbluebutton-html5/imports/api/polls/server/publishers.js b/bigbluebutton-html5/imports/api/polls/server/publishers.js index 14552057c5..2dc37c9895 100644 --- a/bigbluebutton-html5/imports/api/polls/server/publishers.js +++ b/bigbluebutton-html5/imports/api/polls/server/publishers.js @@ -1,18 +1,39 @@ import { Meteor } from 'meteor/meteor'; import Logger from '/imports/startup/server/logger'; +import Users from '/imports/api/users'; import Polls from '/imports/api/polls'; -import AuthTokenValidation, { ValidationStates } from '/imports/api/auth-token-validation'; +import AuthTokenValidation, { + ValidationStates, +} from '/imports/api/auth-token-validation'; + +const ROLE_MODERATOR = Meteor.settings.public.user.role_moderator; function currentPoll(secretPoll) { - const tokenValidation = AuthTokenValidation.findOne({ connectionId: this.connection.id }); + const tokenValidation = AuthTokenValidation.findOne({ + connectionId: this.connection.id, + }); - if (!tokenValidation || tokenValidation.validationStatus !== ValidationStates.VALIDATED) { - Logger.warn(`Publishing Polls was requested by unauth connection ${this.connection.id}`); + if ( + !tokenValidation || + tokenValidation.validationStatus !== ValidationStates.VALIDATED + ) { + Logger.warn( + `Publishing Polls was requested by unauth connection ${this.connection.id}` + ); return Polls.find({ meetingId: '' }); } const { meetingId, userId } = tokenValidation; + const User = Users.findOne({ userId, meetingId }, { fields: { role: 1 } }); + if (!User || User.role !== ROLE_MODERATOR) { + Logger.warn( + 'Publishing current-poll was requested by non-moderator connection', + { meetingId, userId, connectionId: this.connection.id } + ); + return Polls.find({ meetingId: '' }); + } + Logger.debug('Publishing Polls', { meetingId, userId }); const selector = { @@ -38,10 +59,17 @@ function publishCurrentPoll(...args) { Meteor.publish('current-poll', publishCurrentPoll); function polls() { - const tokenValidation = AuthTokenValidation.findOne({ connectionId: this.connection.id }); + const tokenValidation = AuthTokenValidation.findOne({ + connectionId: this.connection.id, + }); - if (!tokenValidation || tokenValidation.validationStatus !== ValidationStates.VALIDATED) { - Logger.warn(`Publishing Polls was requested by unauth connection ${this.connection.id}`); + if ( + !tokenValidation || + tokenValidation.validationStatus !== ValidationStates.VALIDATED + ) { + Logger.warn( + `Publishing Polls was requested by unauth connection ${this.connection.id}` + ); return Polls.find({ meetingId: '' }); } diff --git a/bigbluebutton-html5/imports/ui/components/captions/pad/component.jsx b/bigbluebutton-html5/imports/ui/components/captions/pad/component.jsx index 6e4fc459cd..487a061246 100644 --- a/bigbluebutton-html5/imports/ui/components/captions/pad/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/captions/pad/component.jsx @@ -86,13 +86,15 @@ class Pad extends PureComponent { this.toggleListen = this.toggleListen.bind(this); this.handleListen = this.handleListen.bind(this); - this.recognition.addEventListener('end', () => { - const { listening } = this.state; - if (listening) { - notify(intl.formatMessage(intlMessages.speechRecognitionStop), 'info', 'warning'); - this.stopListen(); - } - }); + if (this.recognition) { + this.recognition.addEventListener('end', () => { + const { listening } = this.state; + if (listening) { + notify(intl.formatMessage(intlMessages.speechRecognitionStop), 'info', 'warning'); + this.stopListen(); + } + }); + } } componentDidUpdate() { @@ -103,8 +105,13 @@ class Pad extends PureComponent { } = this.props; if (this.recognition) { + if (ownerId !== currentUserId) { + this.recognition.stop(); + } else if (this.state.listening && this.recognition.lang !== locale) { + this.recognition.stop(); + this.stopListen(); + } this.recognition.lang = locale; - if (ownerId !== currentUserId) this.recognition.stop(); } }