diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/PermissionCheck.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/PermissionCheck.scala index 9c57ac580d..760203a56d 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/PermissionCheck.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/PermissionCheck.scala @@ -85,7 +85,8 @@ object PermissionCheck { outGW: OutMsgRouter, liveMeeting: LiveMeeting): Unit = { val ejectedBy = SystemUser.ID - UsersApp.ejectUserFromMeeting(outGW, liveMeeting, userId, ejectedBy, reason, EjectReasonCode.PERMISSION_FAILED) + UsersApp.ejectUserFromMeeting(outGW, liveMeeting, userId, ejectedBy, reason, EjectReasonCode.PERMISSION_FAILED, ban = false) + // send a system message to force disconnection Sender.sendDisconnectClientSysMsg(meetingId, userId, ejectedBy, reason, outGW) } diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/EjectDuplicateUserReqMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/EjectDuplicateUserReqMsgHdlr.scala index f502556385..f7111af84f 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/EjectDuplicateUserReqMsgHdlr.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/EjectDuplicateUserReqMsgHdlr.scala @@ -17,7 +17,8 @@ trait EjectDuplicateUserReqMsgHdlr { val ejectedBy = SystemUser.ID val reason = "user ejected because of duplicate external userid" - UsersApp.ejectUserFromMeeting(outGW, liveMeeting, userId, ejectedBy, reason, EjectReasonCode.DUPLICATE_USER) + UsersApp.ejectUserFromMeeting(outGW, liveMeeting, userId, ejectedBy, reason, EjectReasonCode.DUPLICATE_USER, ban = false) + // send a system message to force disconnection Sender.sendDisconnectClientSysMsg(meetingId, userId, ejectedBy, EjectReasonCode.DUPLICATE_USER, outGW) } diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/EjectUserFromMeetingCmdMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/EjectUserFromMeetingCmdMsgHdlr.scala index 4915c73f80..49ceb6e0dd 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/EjectUserFromMeetingCmdMsgHdlr.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/EjectUserFromMeetingCmdMsgHdlr.scala @@ -16,6 +16,7 @@ trait EjectUserFromMeetingCmdMsgHdlr extends RightsManagementTrait { val meetingId = liveMeeting.props.meetingProp.intId val userId = msg.body.userId val ejectedBy = msg.body.ejectedBy + val banUser = msg.body.banUser if (permissionFailed( PermissionCheck.MOD_LEVEL, @@ -33,6 +34,8 @@ trait EjectUserFromMeetingCmdMsgHdlr extends RightsManagementTrait { ejectedByUser <- RegisteredUsers.findWithUserId(ejectedBy, liveMeeting.registeredUsers) } yield { if (registeredUser.externId != ejectedByUser.externId) { + val ban = banUser + // Eject users //println("****************** User " + ejectedBy + " ejecting user " + userId) // User might have joined using multiple browsers. @@ -40,7 +43,18 @@ trait EjectUserFromMeetingCmdMsgHdlr extends RightsManagementTrait { // ralam april 21, 2020 RegisteredUsers.findAllWithExternUserId(registeredUser.externId, liveMeeting.registeredUsers) foreach { ru => //println("****************** User " + ejectedBy + " ejecting other user " + ru.id) - UsersApp.ejectUserFromMeeting(outGW, liveMeeting, ru.id, ejectedBy, reason, EjectReasonCode.EJECT_USER) + UsersApp.ejectUserFromMeeting( + outGW, + liveMeeting, + ru.id, + ejectedBy, + reason, + EjectReasonCode.EJECT_USER, + ban + ) + + log.info("Eject userId=" + userId + " by " + ejectedBy + " and ban=" + banUser) + // send a system message to force disconnection Sender.sendDisconnectClientSysMsg(meetingId, ru.id, ejectedBy, EjectReasonCode.EJECT_USER, outGW) } @@ -48,7 +62,15 @@ trait EjectUserFromMeetingCmdMsgHdlr extends RightsManagementTrait { // User is ejecting self, so just eject this userid not all sessions if joined using multiple // browsers. ralam april 23, 2020 //println("****************** User " + ejectedBy + " ejecting self " + userId) - UsersApp.ejectUserFromMeeting(outGW, liveMeeting, userId, ejectedBy, reason, EjectReasonCode.EJECT_USER) + UsersApp.ejectUserFromMeeting( + outGW, + liveMeeting, + userId, + ejectedBy, + reason, + EjectReasonCode.EJECT_USER, + ban = false + ) // send a system message to force disconnection Sender.sendDisconnectClientSysMsg(meetingId, userId, ejectedBy, EjectReasonCode.EJECT_USER, outGW) } @@ -70,7 +92,15 @@ trait EjectUserFromMeetingSysMsgHdlr { val ejectedBy = msg.body.ejectedBy val reason = "user ejected by a component on system" - UsersApp.ejectUserFromMeeting(outGW, liveMeeting, userId, ejectedBy, reason, EjectReasonCode.SYSTEM_EJECT_USER) + UsersApp.ejectUserFromMeeting( + outGW, + liveMeeting, + userId, + ejectedBy, + reason, + EjectReasonCode.SYSTEM_EJECT_USER, + ban = false + ) // send a system message to force disconnection Sender.sendDisconnectClientSysMsg(meetingId, userId, ejectedBy, EjectReasonCode.SYSTEM_EJECT_USER, outGW) } diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/UsersApp.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/UsersApp.scala index 57955f1b43..fa5738fffa 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/UsersApp.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/UsersApp.scala @@ -99,13 +99,14 @@ object UsersApp { } def ejectUserFromMeeting(outGW: OutMsgRouter, liveMeeting: LiveMeeting, - userId: String, ejectedBy: String, reason: String, reasonCode: String): Unit = { + userId: String, ejectedBy: String, reason: String, + reasonCode: String, ban: Boolean): Unit = { val meetingId = liveMeeting.props.meetingProp.intId for { user <- Users2x.ejectFromMeeting(liveMeeting.users2x, userId) - reguser <- RegisteredUsers.eject(userId, liveMeeting.registeredUsers, ejectedBy) + reguser <- RegisteredUsers.eject(userId, liveMeeting.registeredUsers, ban) } yield { sendUserEjectedMessageToClient(outGW, meetingId, userId, ejectedBy, reason, reasonCode) sendUserLeftMeetingToAllClients(outGW, meetingId, userId) diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/ValidateAuthTokenReqMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/ValidateAuthTokenReqMsgHdlr.scala index bae7853a5b..69614b8835 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/ValidateAuthTokenReqMsgHdlr.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/ValidateAuthTokenReqMsgHdlr.scala @@ -25,13 +25,13 @@ trait ValidateAuthTokenReqMsgHdlr extends HandlerHelpers { regUser match { case Some(u) => - // Check if ejected user is rejoining. + // Check if banned user is rejoining. // Fail validation if ejected user is rejoining. // ralam april 21, 2020 - if (u.guestStatus == GuestStatus.ALLOW && !u.ejected) { + if (u.guestStatus == GuestStatus.ALLOW && !u.banned) { userValidated(u, state) } else { - if (u.ejected) { + if (u.banned) { failReason = "Ejected user rejoining" failReasonCode = EjectReasonCode.EJECTED_USER_REJOINING } diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/RegisteredUsers.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/RegisteredUsers.scala index 052b4e8595..17e7f429ce 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/RegisteredUsers.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/RegisteredUsers.scala @@ -62,12 +62,12 @@ object RegisteredUsers { findWithExternUserId(user.externId, users) match { case Some(u) => - if (u.ejected) { - // Ejected user is rejoining. Don't add so that validate token + if (u.banned) { + // Banned user is rejoining. Don't add so that validate token // will fail and can't join. // ralam april 21, 2020 - val ejectedUser = user.copy(ejected = true) - users.save(ejectedUser) + val bannedUser = user.copy(banned = true) + users.save(bannedUser) } else { // If user hasn't been ejected, we allow user to join // as the user might be joining using 2 browsers for @@ -81,16 +81,16 @@ object RegisteredUsers { } - private def banUser(ejectedUser: RegisteredUser, users: RegisteredUsers, ejectedByUser: RegisteredUser): RegisteredUser = { + private def banOrEjectUser(ejectedUser: RegisteredUser, users: RegisteredUsers, ban: Boolean): RegisteredUser = { // Some users join with multiple browser to manage the meeting. // Don't black list a user ejecting oneself. // ralam april 23, 2020 - if (ejectedUser.externId != ejectedByUser.externId) { + if (ban) { // Set a flag that user has been ejected. We flag the user instead of // removing so we can eject when user tries to rejoin with the same // external userid. // ralam april 21, 2020 - val u = ejectedUser.modify(_.ejected).setTo(true) + val u = ejectedUser.modify(_.banned).setTo(true) users.save(u) u } else { @@ -98,12 +98,11 @@ object RegisteredUsers { ejectedUser } } - def eject(id: String, users: RegisteredUsers, ejectedBy: String): Option[RegisteredUser] = { + def eject(id: String, users: RegisteredUsers, ban: Boolean): Option[RegisteredUser] = { for { ru <- findWithUserId(id, users) - eu <- findWithUserId(ejectedBy, users) } yield { - banUser(ru, users, eu) + banOrEjectUser(ru, users, ban) } } @@ -166,6 +165,6 @@ case class RegisteredUser( registeredOn: Long, joined: Boolean, markAsJoinTimedOut: Boolean, - ejected: Boolean + banned: Boolean ) diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/MeetingActor.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/MeetingActor.scala index 48ab65be1e..c0a1735a92 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/MeetingActor.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/MeetingActor.scala @@ -749,7 +749,16 @@ class MeetingActor( users foreach { u => val respondedOnTime = (lastUserInactivityInspectSentOn - expiryTracker.userInactivityThresholdInMs) < u.lastActivityTime && (lastUserInactivityInspectSentOn + expiryTracker.userActivitySignResponseDelayInMs) > u.lastActivityTime if (!respondedOnTime) { - UsersApp.ejectUserFromMeeting(outGW, liveMeeting, u.intId, SystemUser.ID, "User inactive for too long.", EjectReasonCode.USER_INACTIVITY) + UsersApp.ejectUserFromMeeting( + outGW, + liveMeeting, + u.intId, + SystemUser.ID, + "User inactive for too long.", + EjectReasonCode.USER_INACTIVITY, + ban = false + ) + Sender.sendDisconnectClientSysMsg(liveMeeting.props.meetingProp.intId, u.intId, SystemUser.ID, EjectReasonCode.USER_INACTIVITY, outGW) } } diff --git a/akka-bbb-apps/src/universal/conf/application.conf b/akka-bbb-apps/src/universal/conf/application.conf index 5590361095..4bf02465c1 100755 --- a/akka-bbb-apps/src/universal/conf/application.conf +++ b/akka-bbb-apps/src/universal/conf/application.conf @@ -65,7 +65,7 @@ sharedNotes { } http { - interface = "0.0.0.0" + interface = "127.0.0.1" port = 9999 } diff --git a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/UsersMgs.scala b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/UsersMgs.scala index 4936931676..d658d52365 100755 --- a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/UsersMgs.scala +++ b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/UsersMgs.scala @@ -221,7 +221,7 @@ case class UserRoleChangedEvtMsgBody(userId: String, role: String, changedBy: St */ object EjectUserFromMeetingCmdMsg { val NAME = "EjectUserFromMeetingCmdMsg" } case class EjectUserFromMeetingCmdMsg(header: BbbClientMsgHeader, body: EjectUserFromMeetingCmdMsgBody) extends StandardMsg -case class EjectUserFromMeetingCmdMsgBody(userId: String, ejectedBy: String) +case class EjectUserFromMeetingCmdMsgBody(userId: String, ejectedBy: String, banUser: Boolean) /** * Sent from client to lock user in meeting. diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/api/ParamsProcessorUtil.java b/bbb-common-web/src/main/java/org/bigbluebutton/api/ParamsProcessorUtil.java index a6953c0dea..fa5b0fbb73 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/api/ParamsProcessorUtil.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/api/ParamsProcessorUtil.java @@ -24,14 +24,9 @@ import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.SortedSet; -import java.util.TreeSet; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -123,23 +118,16 @@ public class ParamsProcessorUtil { private Boolean defaultAllowDuplicateExtUserid = true; private String formatConfNum(String s) { - if (s.length() == 5) { - StringBuilder confNumDash = new StringBuilder(s); - confNumDash.insert(2, '-'); - return confNumDash.toString(); - } else if (s.length() == 6 || s.length() == 7) { - StringBuilder confNumDash = new StringBuilder(s); - confNumDash.insert(3, '-'); - return confNumDash.toString(); - } else if (s.length() == 8) { - StringBuilder confNumDash = new StringBuilder(s); - confNumDash.insert(4, '-'); - return confNumDash.toString(); - } else if (s.length() == 9) { - StringBuilder confNumDash = new StringBuilder(s); - confNumDash.insert(3, '-'); - confNumDash.insert(7, '-'); - return confNumDash.toString(); + if (s.length() > 5) { + Long confNumL = Long.parseLong(s); + + Locale numFormatLocale = new Locale("en", "US"); + String formatPattern = "#,###"; + DecimalFormatSymbols unusualSymbols = new DecimalFormatSymbols(numFormatLocale); + unusualSymbols.setGroupingSeparator(' '); + DecimalFormat numFormatter = new DecimalFormat(formatPattern, unusualSymbols); + numFormatter.setGroupingSize(3); + return numFormatter.format(confNumL); } return s; diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/api/domain/Meeting.java b/bbb-common-web/src/main/java/org/bigbluebutton/api/domain/Meeting.java index 4709fe49de..d83c6076b0 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/api/domain/Meeting.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/api/domain/Meeting.java @@ -362,7 +362,7 @@ public class Meeting { } else if (GuestPolicy.ALWAYS_DENY.equals(guestPolicy)) { return GuestPolicy.DENY; } else if (GuestPolicy.ASK_MODERATOR.equals(guestPolicy)) { - if (guest || (!ROLE_MODERATOR.equals(role) && authned)) { + if (guest || (!ROLE_MODERATOR.equals(role) && !authned)) { return GuestPolicy.WAIT ; } return GuestPolicy.ALLOW; diff --git a/bigbluebutton-client/src/assets/presentation.css b/bigbluebutton-client/src/assets/presentation.css deleted file mode 100644 index f06130b69c..0000000000 --- a/bigbluebutton-client/src/assets/presentation.css +++ /dev/null @@ -1,87 +0,0 @@ -/* -//////////////////////////////////////////////////////////////////////////////// -// -// Copyright (C) 2003-2006 Adobe Macromedia Software LLC and its licensors. -// All Rights Reserved. -// The following is Sample Code and is subject to all restrictions on such code -// as contained in the End User License Agreement accompanying this product. -// If you have received this file from a source other than Adobe, -// then your use, modification, or distribution of it requires -// the prior written permission of Adobe. -// -//////////////////////////////////////////////////////////////////////////////// -*/ -Application -{ - backgroundColor: #484842; -} - -CarouselImage -{ - frameColor: #9e9c8d; - frameThickness: 1; - frameSize: 5; -} - -ToolTip -{ - backgroundColor: #484842; - color: #ffffff; -} - -.button -{ - themeColor: #b7babc; -} - -.thumbnailRolledOver -{ - backgroundColor: #787872; -} - -.thumbnailSelected -{ - backgroundColor: #383832; -} - -.thumbnailTitleBar -{ - fontSize: 12; - fontWeight: "bold"; - color: #666666; -} - -.thumbnailListBorderBox -{ - backgroundColor: #9e9c8d; -} - -.thumbnailList -{ - borderColor: #9e9c8d; - backgroundColor: #9e9c8d; - selectionColor: #9e9c8d; - rollOverColor: #9e9c8d; - themeColor: #383832; - borderStyle: "solid"; - cornerRadius: 10; -} - -.photoDescription -{ - fontWeight: "bold"; - fontSize: 14; - color: #ffffff; -} - -.photoName -{ - fontSize: 12; - color: #ffffff; -} - -.slideshowControlBar -{ - backgroundAlpha: .6; - backgroundColor: #5b5b5b; -} diff --git a/bigbluebutton-config/bin/bbb-conf b/bigbluebutton-config/bin/bbb-conf index b4a0f14d13..d6d36bba2a 100755 --- a/bigbluebutton-config/bin/bbb-conf +++ b/bigbluebutton-config/bin/bbb-conf @@ -1538,6 +1538,23 @@ check_state() { echo "#" fi + FREESWITCH_SIP=$(netstat -anlt | grep :5066 | grep -v tcp6 | grep LISTEN | sed 's/ [ ]*/ /g' | cut -d' ' -f4 | sed 's/:5066//g') + KURENTO_SIP=$(yq r $KURENTO_CONFIG freeswitch.sip_ip) + + if [ ! -z "$FREESWITCH_SIP" ]; then + if [ "$FREESWITCH_SIP" != "$KURENTO_SIP" ]; then + echo + echo "#" + echo "# Kurento is will try to connect to $KURENTO_SIP but FreeSWITCH is listening on $FREESWITCH_SIP for port 5066" + echo "#" + echo "# To fix, run the commands" + echo "#" + echo "# sudo yq w -i /usr/local/bigbluebutton/bbb-webrtc-sfu/config/default.yml freeswitch.sip_ip $FREESWITCH_SIP" + echo "# sudo chown bigbluebutton:bigbluebutton /usr/local/bigbluebutton/bbb-webrtc-sfu/config/default.yml" + echo "#" + fi + fi + exit 0 } @@ -1657,6 +1674,7 @@ if [ $CHECK ]; then echo "$KURENTO_CONFIG (Kurento SFU)" echo " kurento.ip: $(yq r $KURENTO_CONFIG kurento[0].ip)" echo " kurento.url: $(yq r $KURENTO_CONFIG kurento[0].url)" + echo " kurento.sip_ip: $(yq r $KURENTO_CONFIG freeswitch.sip_ip)" echo " localIpAddress: $(yq r $KURENTO_CONFIG localIpAddress)" echo " recordScreenSharing: $(yq r $KURENTO_CONFIG recordScreenSharing)" echo " recordWebcams: $(yq r $KURENTO_CONFIG recordWebcams)" diff --git a/bigbluebutton-config/cron.daily/bigbluebutton b/bigbluebutton-config/cron.daily/bigbluebutton index 165e35ca47..2a2f657111 100755 --- a/bigbluebutton-config/cron.daily/bigbluebutton +++ b/bigbluebutton-config/cron.daily/bigbluebutton @@ -120,7 +120,7 @@ remove_raw_of_published_recordings(){ done } -#remove_raw_of_published_recordings +remove_raw_of_published_recordings # # Remove old *.afm and *.pfb files from /tmp directory (if any exist) diff --git a/bigbluebutton-html5/imports/api/audio/client/bridge/kurento.js b/bigbluebutton-html5/imports/api/audio/client/bridge/kurento.js index 9d290dbe13..f999150b8b 100755 --- a/bigbluebutton-html5/imports/api/audio/client/bridge/kurento.js +++ b/bigbluebutton-html5/imports/api/audio/client/bridge/kurento.js @@ -1,6 +1,6 @@ import BaseAudioBridge from './base'; import Auth from '/imports/ui/services/auth'; -import { fetchWebRTCMappedStunTurnServers } from '/imports/utils/fetchStunTurnServers'; +import { fetchWebRTCMappedStunTurnServers, getMappedFallbackStun } from '/imports/utils/fetchStunTurnServers'; import playAndRetry from '/imports/utils/mediaElementPlayRetry'; import logger from '/imports/startup/client/logger'; @@ -64,6 +64,7 @@ export default class KurentoAudioBridge extends BaseAudioBridge { } catch (error) { logger.error({ logCode: 'sfuaudiobridge_stunturn_fetch_failed' }, 'SFU audio bridge failed to fetch STUN/TURN info, using default servers'); + iceServers = getMappedFallbackStun(); } finally { logger.debug({ logCode: 'sfuaudiobridge_stunturn_fetch_sucess', extraInfo: { iceServers } }, 'SFU audio bridge got STUN/TURN servers'); diff --git a/bigbluebutton-html5/imports/api/audio/client/bridge/sip.js b/bigbluebutton-html5/imports/api/audio/client/bridge/sip.js index 3c72abbfcb..12f8d734a7 100755 --- a/bigbluebutton-html5/imports/api/audio/client/bridge/sip.js +++ b/bigbluebutton-html5/imports/api/audio/client/bridge/sip.js @@ -1,7 +1,7 @@ import browser from 'browser-detect'; import BaseAudioBridge from './base'; import logger from '/imports/startup/client/logger'; -import { fetchStunTurnServers } from '/imports/utils/fetchStunTurnServers'; +import { fetchStunTurnServers, getFallbackStun } from '/imports/utils/fetchStunTurnServers'; import { isUnifiedPlan, toUnifiedPlan, @@ -85,6 +85,22 @@ class SIPSession { }); } + async getIceServers(sessionToken) { + try { + const iceServers = await fetchStunTurnServers(sessionToken); + return iceServers; + } catch (error) { + logger.error({ + logCode: 'sip_js_fetchstunturninfo_error', + extraInfo: { + errorCode: error.code, + errorMessage: error.message, + }, + }, 'Full audio bridge failed to fetch STUN/TURN info'); + return getFallbackStun(); + } + } + doCall(options) { const { isListenOnly, @@ -105,7 +121,7 @@ class SIPSession { this.user.callerIdName = callerIdName; this.callOptions = options; - return fetchStunTurnServers(sessionToken) + return this.getIceServers(sessionToken) .then(this.createUserAgent.bind(this)) .then(this.inviteUserAgent.bind(this)) .then(this.setupEventHandlers.bind(this)); diff --git a/bigbluebutton-html5/imports/api/breakouts/server/publishers.js b/bigbluebutton-html5/imports/api/breakouts/server/publishers.js index d0040bd87e..405414e284 100755 --- a/bigbluebutton-html5/imports/api/breakouts/server/publishers.js +++ b/bigbluebutton-html5/imports/api/breakouts/server/publishers.js @@ -6,7 +6,7 @@ import { extractCredentials } from '/imports/api/common/server/helpers'; const ROLE_MODERATOR = Meteor.settings.public.user.role_moderator; -function breakouts(moderator = false) { +function breakouts() { if (!this.userId) { return Breakouts.find({ meetingId: '' }); } @@ -14,18 +14,16 @@ function breakouts(moderator = false) { const { meetingId, requesterUserId } = extractCredentials(this.userId); Logger.debug(`Publishing Breakouts for ${meetingId} ${requesterUserId}`); - if (moderator) { - const User = Users.findOne({ userId: requesterUserId, meetingId }); - if (!!User && User.role === ROLE_MODERATOR) { - const presenterSelector = { - $or: [ - { parentMeetingId: meetingId }, - { breakoutId: meetingId }, - ], - }; + const User = Users.findOne({ userId: requesterUserId, meetingId }, { fields: { role: 1 } }); + if (!!User && User.role === ROLE_MODERATOR) { + const presenterSelector = { + $or: [ + { parentMeetingId: meetingId }, + { breakoutId: meetingId }, + ], + }; - return Breakouts.find(presenterSelector); - } + return Breakouts.find(presenterSelector); } const selector = { diff --git a/bigbluebutton-html5/imports/api/external-videos/server/methods/initializeExternalVideo.js b/bigbluebutton-html5/imports/api/external-videos/server/methods/initializeExternalVideo.js index 2c76ad38fe..03f9ca9b79 100644 --- a/bigbluebutton-html5/imports/api/external-videos/server/methods/initializeExternalVideo.js +++ b/bigbluebutton-html5/imports/api/external-videos/server/methods/initializeExternalVideo.js @@ -2,25 +2,16 @@ import { extractCredentials } from '/imports/api/common/server/helpers'; import Logger from '/imports/startup/server/logger'; const allowRecentMessages = (eventName, message) => { - const LATE_MESSAGE_THRESHOLD = 3000; - const { userId, meetingId, time, - timestamp, rate, state, } = message; - if (timestamp > Date.now() - LATE_MESSAGE_THRESHOLD) { - Logger.debug(`ExternalVideo Streamer auth allowed userId: ${userId}, meetingId: ${meetingId}, event: ${eventName}, time: ${time}, timestamp: ${timestamp/1000} rate: ${rate}, state: ${state}`); - return true; - } - - Logger.debug(`ExternalVideo Streamer auth rejected userId: ${userId}, meetingId: ${meetingId}, event: ${eventName}, time: ${time}, timestamp: ${timestamp/1000} rate: ${rate}, state: ${state}`); - - return false; + Logger.debug(`ExternalVideo Streamer auth allowed userId: ${userId}, meetingId: ${meetingId}, event: ${eventName}, time: ${time} rate: ${rate}, state: ${state}`); + return true; }; export default function initializeExternalVideo() { diff --git a/bigbluebutton-html5/imports/api/meetings/server/publishers.js b/bigbluebutton-html5/imports/api/meetings/server/publishers.js index 997c79b66b..dfbfb74a3b 100755 --- a/bigbluebutton-html5/imports/api/meetings/server/publishers.js +++ b/bigbluebutton-html5/imports/api/meetings/server/publishers.js @@ -6,7 +6,7 @@ import { extractCredentials } from '/imports/api/common/server/helpers'; const ROLE_MODERATOR = Meteor.settings.public.user.role_moderator; -function meetings(isModerator = false) { +function meetings() { if (!this.userId) { return Meetings.find({ meetingId: '' }); } @@ -20,19 +20,18 @@ function meetings(isModerator = false) { ], }; - if (isModerator) { - const User = Users.findOne({ userId: requesterUserId, meetingId }); - if (!!User && User.role === ROLE_MODERATOR) { - selector.$or.push({ - 'meetingProp.isBreakout': true, - 'breakoutProps.parentId': meetingId, - }); - } + const User = Users.findOne({ userId: requesterUserId, meetingId }, { fields: { role: 1 } }); + if (!!User && User.role === ROLE_MODERATOR) { + selector.$or.push({ + 'meetingProp.isBreakout': true, + 'breakoutProps.parentId': meetingId, + }); } const options = { fields: { password: false, + 'welcomeProp.modOnlyMessage': false, }, }; diff --git a/bigbluebutton-html5/imports/api/screenshare/client/bridge/kurento.js b/bigbluebutton-html5/imports/api/screenshare/client/bridge/kurento.js index 3ad3aa1c25..761e383ede 100755 --- a/bigbluebutton-html5/imports/api/screenshare/client/bridge/kurento.js +++ b/bigbluebutton-html5/imports/api/screenshare/client/bridge/kurento.js @@ -1,6 +1,6 @@ import Auth from '/imports/ui/services/auth'; import BridgeService from './service'; -import { fetchWebRTCMappedStunTurnServers } from '/imports/utils/fetchStunTurnServers'; +import { fetchWebRTCMappedStunTurnServers, getMappedFallbackStun } from '/imports/utils/fetchStunTurnServers'; import playAndRetry from '/imports/utils/mediaElementPlayRetry'; import logger from '/imports/startup/client/logger'; @@ -8,8 +8,8 @@ const SFU_CONFIG = Meteor.settings.public.kurento; const SFU_URL = SFU_CONFIG.wsUrl; const CHROME_DEFAULT_EXTENSION_KEY = SFU_CONFIG.chromeDefaultExtensionKey; const CHROME_CUSTOM_EXTENSION_KEY = SFU_CONFIG.chromeExtensionKey; -const CHROME_SCREENSHARE_SOURCES = SFU_CONFIG.chromeScreenshareSources; -const FIREFOX_SCREENSHARE_SOURCE = SFU_CONFIG.firefoxScreenshareSource; +const CHROME_SCREENSHARE_SOURCES = SFU_CONFIG.screenshare.chromeScreenshareSources; +const FIREFOX_SCREENSHARE_SOURCE = SFU_CONFIG.screenshare.firefoxScreenshareSource; const SCREENSHARE_VIDEO_TAG = 'screenshareVideo'; const CHROME_EXTENSION_KEY = CHROME_CUSTOM_EXTENSION_KEY === 'KEY' ? CHROME_DEFAULT_EXTENSION_KEY : CHROME_CUSTOM_EXTENSION_KEY; @@ -72,6 +72,7 @@ export default class KurentoScreenshareBridge { } catch (error) { logger.error({ logCode: 'screenshare_viwer_fetchstunturninfo_error', extraInfo: { error } }, 'Screenshare bridge failed to fetch STUN/TURN info, using default'); + iceServers = getMappedFallbackStun(); } finally { const options = { wsUrl: Auth.authenticateURL(SFU_URL), @@ -161,13 +162,15 @@ export default class KurentoScreenshareBridge { window.kurentoExitVideo(); } - async kurentoShareScreen(onFail) { + async kurentoShareScreen(onFail, stream) { let iceServers = []; try { iceServers = await fetchWebRTCMappedStunTurnServers(getSessionToken()); } catch (error) { logger.error({ logCode: 'screenshare_presenter_fetchstunturninfo_error' }, + 'Screenshare bridge failed to fetch STUN/TURN info, using default'); + iceServers = getMappedFallbackStun(); } finally { const options = { wsUrl: Auth.authenticateURL(SFU_URL), @@ -193,6 +196,8 @@ export default class KurentoScreenshareBridge { }, 'Screenshare presenter started succesfully'); }; + options.stream = stream || undefined; + window.kurentoShareScreen( SCREENSHARE_VIDEO_TAG, BridgeService.getConferenceBridge(), diff --git a/bigbluebutton-html5/imports/api/screenshare/client/bridge/service.js b/bigbluebutton-html5/imports/api/screenshare/client/bridge/service.js index b1be0cc429..a8c0c90175 100644 --- a/bigbluebutton-html5/imports/api/screenshare/client/bridge/service.js +++ b/bigbluebutton-html5/imports/api/screenshare/client/bridge/service.js @@ -1,7 +1,82 @@ import Meetings from '/imports/api/meetings'; +import logger from '/imports/startup/client/logger'; + +const { + constraints: GDM_CONSTRAINTS, +} = Meteor.settings.public.kurento.screenshare; + +const hasDisplayMedia = (typeof navigator.getDisplayMedia === 'function' + || (navigator.mediaDevices && typeof navigator.mediaDevices.getDisplayMedia === 'function')); const getConferenceBridge = () => Meetings.findOne().voiceProp.voiceConf; -export default { - getConferenceBridge, +const getScreenStream = async () => { + const gDMCallback = (stream) => { + if (typeof stream.getVideoTracks === 'function' + && typeof constraints.video === 'object') { + stream.getVideoTracks().forEach((track) => { + if (typeof track.applyConstraints === 'function') { + track.applyConstraints(constraints.video).catch((error) => { + logger.warn({ + logCode: 'screenshare_videoconstraint_failed', + extraInfo: { errorName: error.name, errorCode: error.code }, + }, + 'Error applying screenshare video constraint'); + }); + } + }); + } + + if (typeof stream.getAudioTracks === 'function' + && typeof constraints.audio === 'object') { + stream.getAudioTracks().forEach((track) => { + if (typeof track.applyConstraints === 'function') { + track.applyConstraints(constraints.audio).catch((error) => { + logger.warn({ + logCode: 'screenshare_audioconstraint_failed', + extraInfo: { errorName: error.name, errorCode: error.code }, + }, 'Error applying screenshare audio constraint'); + }); + } + }); + } + + return Promise.resolve(stream); + }; + + const constraints = hasDisplayMedia ? GDM_CONSTRAINTS : null; + + // getDisplayMedia isn't supported, generate no stream and let the legacy + // constraint fetcher work its way on kurento-extension.js + if (constraints == null) { + return Promise.resolve(); + } + if (typeof navigator.getDisplayMedia === 'function') { + return navigator.getDisplayMedia(constraints) + .then(gDMCallback) + .catch((error) => { + logger.error({ + logCode: 'screenshare_getdisplaymedia_failed', + extraInfo: { errorName: error.name, errorCode: error.code }, + }, 'getDisplayMedia call failed'); + return Promise.resolve(); + }); + } if (navigator.mediaDevices && typeof navigator.mediaDevices.getDisplayMedia === 'function') { + return navigator.mediaDevices.getDisplayMedia(constraints) + .then(gDMCallback) + .catch((error) => { + logger.error({ + logCode: 'screenshare_getdisplaymedia_failed', + extraInfo: { errorName: error.name, errorCode: error.code }, + }, 'getDisplayMedia call failed'); + return Promise.resolve(); + }); + } +}; + + +export default { + hasDisplayMedia, + getConferenceBridge, + getScreenStream, }; diff --git a/bigbluebutton-html5/imports/api/users/server/handlers/validateAuthToken.js b/bigbluebutton-html5/imports/api/users/server/handlers/validateAuthToken.js index dae1bf3b4a..ff37339c6e 100644 --- a/bigbluebutton-html5/imports/api/users/server/handlers/validateAuthToken.js +++ b/bigbluebutton-html5/imports/api/users/server/handlers/validateAuthToken.js @@ -15,7 +15,9 @@ const clearOtherSessions = (sessionUserId, current = false) => { }; export default function handleValidateAuthToken({ body }, meetingId) { - const { userId, valid, authToken, waitForApproval } = body; + const { + userId, valid, authToken, waitForApproval, + } = body; check(userId, String); check(authToken, String); @@ -24,46 +26,50 @@ export default function handleValidateAuthToken({ body }, meetingId) { const pendingAuths = pendingAuthenticationsStore.take(meetingId, userId, authToken); - if(!valid) { - pendingAuths.forEach ( - pendingAuth => { + if (!valid) { + pendingAuths.forEach( + (pendingAuth) => { try { - const {methodInvocationObject} = pendingAuth; + const { methodInvocationObject } = pendingAuth; const connectionId = methodInvocationObject.connection.id; - methodInvocationObject.connection.close(); + // Schedule socket disconnection for this user, giving some time for client receiving the reason of disconnection + Meteor.setTimeout(() => { + methodInvocationObject.connection.close(); + }, 2000); + Logger.info(`Closed connection ${connectionId} due to invalid auth token.`); } catch (e) { Logger.error(`Error closing socket for meetingId '${meetingId}', userId '${userId}', authToken ${authToken}`); } - } + }, ); - + return; } - if(valid) { + if (valid) { // Define user ID on connections - pendingAuths.forEach ( - pendingAuth => { - const {methodInvocationObject} = pendingAuth; + pendingAuths.forEach( + (pendingAuth) => { + const { methodInvocationObject } = pendingAuth; - /* Logic migrated from validateAuthToken method ( postponed to only run in case of success response ) - Begin */ - const sessionId = `${meetingId}--${userId}`; - methodInvocationObject.setUserId(sessionId); + /* Logic migrated from validateAuthToken method ( postponed to only run in case of success response ) - Begin */ + const sessionId = `${meetingId}--${userId}`; + methodInvocationObject.setUserId(sessionId); - const User = Users.findOne({ - meetingId, - userId: userId, - }); - - if (!User) { - createDummyUser(meetingId, userId, authToken); - } - - setConnectionIdAndAuthToken(meetingId, userId, methodInvocationObject.connection.id, authToken); - /* End of logic migrated from validateAuthToken */ + const User = Users.findOne({ + meetingId, + userId, + }); + + if (!User) { + createDummyUser(meetingId, userId, authToken); } + + setConnectionIdAndAuthToken(meetingId, userId, methodInvocationObject.connection.id, authToken); + /* End of logic migrated from validateAuthToken */ + }, ); } diff --git a/bigbluebutton-html5/imports/api/users/server/methods/removeUser.js b/bigbluebutton-html5/imports/api/users/server/methods/removeUser.js index afe41d6f9f..6649b481ea 100644 --- a/bigbluebutton-html5/imports/api/users/server/methods/removeUser.js +++ b/bigbluebutton-html5/imports/api/users/server/methods/removeUser.js @@ -3,7 +3,7 @@ import { check } from 'meteor/check'; import RedisPubSub from '/imports/startup/server/redis'; import { extractCredentials } from '/imports/api/common/server/helpers'; -export default function removeUser(userId) { +export default function removeUser(userId, banUser) { const REDIS_CONFIG = Meteor.settings.private.redis; const CHANNEL = REDIS_CONFIG.channels.toAkkaApps; const EVENT_NAME = 'EjectUserFromMeetingCmdMsg'; @@ -15,6 +15,7 @@ export default function removeUser(userId) { const payload = { userId, ejectedBy, + banUser, }; return RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, ejectedBy, payload); diff --git a/bigbluebutton-html5/imports/api/users/server/publishers.js b/bigbluebutton-html5/imports/api/users/server/publishers.js index 861fb357e1..d7c0297160 100644 --- a/bigbluebutton-html5/imports/api/users/server/publishers.js +++ b/bigbluebutton-html5/imports/api/users/server/publishers.js @@ -51,7 +51,7 @@ function publishCurrentUser(...args) { Meteor.publish('current-user', publishCurrentUser); -function users(isModerator = false) { +function users() { if (!this.userId) { return Users.find({ meetingId: '' }); } @@ -63,15 +63,13 @@ function users(isModerator = false) { ], }; - if (isModerator) { - const User = Users.findOne({ userId: requesterUserId, meetingId }); - if (!!User && User.role === ROLE_MODERATOR) { - selector.$or.push({ - 'breakoutProps.isBreakoutUser': true, - 'breakoutProps.parentId': meetingId, - connectionStatus: 'online', - }); - } + const User = Users.findOne({ userId: requesterUserId, meetingId }, { fields: { role: 1 } }); + if (!!User && User.role === ROLE_MODERATOR) { + selector.$or.push({ + 'breakoutProps.isBreakoutUser': true, + 'breakoutProps.parentId': meetingId, + connectionStatus: 'online', + }); } const options = { diff --git a/bigbluebutton-html5/imports/api/users/server/store/pendingAuthentications.js b/bigbluebutton-html5/imports/api/users/server/store/pendingAuthentications.js index c181ac8f95..97b13cc714 100644 --- a/bigbluebutton-html5/imports/api/users/server/store/pendingAuthentications.js +++ b/bigbluebutton-html5/imports/api/users/server/store/pendingAuthentications.js @@ -1,43 +1,48 @@ import Logger from '/imports/startup/server/logger'; class PendingAuthentitcations { - constructor () { - Logger.debug("PendingAuthentitcations :: constructor"); - this.store = []; + constructor() { + Logger.debug('PendingAuthentitcations :: constructor'); + this.store = []; + } + + generateKey(meetingId, userId, authToken) { + // Protect against separator injection + meetingId = meetingId.replace(/ /g, ''); + userId = userId.replace(/ /g, ''); + authToken = authToken.replace(/ /g, ''); + + // Space separated key + return `${meetingId} ${userId} ${authToken}`; + } + + add(meetingId, userId, authToken, methodInvocationObject) { + Logger.debug('PendingAuthentitcations :: add', { meetingId, userId, authToken }); + this.store.push({ + key: this.generateKey(meetingId, userId, authToken), + meetingId, + userId, + authToken, + methodInvocationObject, + }); + } + + take(meetingId, userId, authToken) { + const key = this.generateKey(meetingId, userId, authToken); + Logger.debug('PendingAuthentitcations :: take', { + key, meetingId, userId, authToken, + }); + + // find matches + const matches = this.store.filter(e => e.key === key); + + // remove matches (if any) + if (matches.length) { + this.store = this.store.filter(e => e.key !== key); } - generateKey (meetingId, userId, authToken) { - // Protect against separator injection - meetingId = meetingId.replace(/ /g, ''); - userId = userId.replace(/ /g, ''); - authToken = authToken.replace(/ /g, ''); - - // Space separated key - return '${meetingId} ${userId} ${authToken}'; - } - - add (meetingId, userId, authToken, methodInvocationObject) { - Logger.debug("PendingAuthentitcations :: add", {meetingId, userId, authToken}); - this.store.push({ - key: this.generateKey(meetingId, userId, authToken), - meetingId, userId, authToken, methodInvocationObject - }); - } - - take (meetingId, userId, authToken) { - Logger.debug("PendingAuthentitcations :: take", {meetingId, userId, authToken}); - const key = this.generateKey(meetingId, userId, authToken); - - // find matches - const matches = this.store.filter( e => e.key === key ); - - // remove matches (if any) - if(matches.length) { - this.store = this.store.filter( e => e.key !== key ) ; - } - - // return matches - return matches; - } + // return matches + return matches; + } } -export default new PendingAuthentitcations(); \ No newline at end of file +export default new PendingAuthentitcations(); diff --git a/bigbluebutton-html5/imports/startup/client/intl.jsx b/bigbluebutton-html5/imports/startup/client/intl.jsx index dfb1a10b5b..3e139e57c7 100644 --- a/bigbluebutton-html5/imports/startup/client/intl.jsx +++ b/bigbluebutton-html5/imports/startup/client/intl.jsx @@ -32,6 +32,7 @@ import it from 'react-intl/locale-data/it'; import ja from 'react-intl/locale-data/ja'; import ka from 'react-intl/locale-data/ka'; import km from 'react-intl/locale-data/km'; +import kn from 'react-intl/locale-data/kn'; import ko from 'react-intl/locale-data/ko'; import lt from 'react-intl/locale-data/lt'; import lv from 'react-intl/locale-data/lv'; @@ -79,6 +80,7 @@ addLocaleData([ ...ja, ...ka, ...km, + ...kn, ...ko, ...lt, ...lv, diff --git a/bigbluebutton-html5/imports/startup/server/redis.js b/bigbluebutton-html5/imports/startup/server/redis.js index f8901d3520..5894152a5e 100755 --- a/bigbluebutton-html5/imports/startup/server/redis.js +++ b/bigbluebutton-html5/imports/startup/server/redis.js @@ -239,6 +239,9 @@ class RedisPubSub { userId, }; + if (!meetingId || !userId) { + return Logger.warn(`Interrupted publishing of ${JSON.stringify(header)} due to missing data`); + } const envelope = makeEnvelope(channel, eventName, header, payload, { meetingId, userId }); return this.pub.publish(channel, envelope, RedisPubSub.handlePublishError); diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/desktop-share/component.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/desktop-share/component.jsx index e0c5080a16..c2a148fc3b 100755 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/desktop-share/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/desktop-share/component.jsx @@ -9,6 +9,7 @@ import cx from 'classnames'; import Modal from '/imports/ui/components/modal/simple/component'; import { withModalMounter } from '../../modal/service'; import { styles } from '../styles'; +import ScreenshareBridgeService from '/imports/api/screenshare/client/bridge/service'; const propTypes = { intl: intlShape.isRequired, @@ -55,9 +56,9 @@ const intlMessages = defineMessages({ id: 'app.screenshare.notSupportedError', description: 'error message when trying to share screen in unsafe environments', }, - noSafariScreenShare: { - id: 'app.media.screenshare.safariNotSupported', - descriptions: 'error message when trying to share screen on safari', + screenShareNotSupported: { + id: 'app.media.screenshare.notSupported', + descriptions: 'error message when trying share screen on unsupported browsers', }, screenShareUnavailable: { id: 'app.media.screenshare.unavailable', @@ -114,7 +115,7 @@ const isMobileBrowser = (BROWSER_RESULTS ? BROWSER_RESULTS.mobile : false) || (BROWSER_RESULTS && BROWSER_RESULTS.os ? BROWSER_RESULTS.os.includes('Android') // mobile flag doesn't always work : false); -const isSafari = BROWSER_RESULTS.name === 'safari'; +const IS_SAFARI = BROWSER_RESULTS.name === 'safari'; const DesktopShare = ({ intl, @@ -182,7 +183,7 @@ const DesktopShare = ({ circle size="lg" onClick={isVideoBroadcasting ? handleUnshareScreen : () => { - if (isSafari) { + if (IS_SAFARI && !ScreenshareBridgeService.hasDisplayMedia) { return mountModal( {intl.formatMessage(intlMessages.screenShareUnavailable)} -

{intl.formatMessage(intlMessages.noSafariScreenShare)}

+

{intl.formatMessage(intlMessages.screenShareNotSupported)}

); } handleShareScreen(onFail); diff --git a/bigbluebutton-html5/imports/ui/components/audio/audio-controls/component.jsx b/bigbluebutton-html5/imports/ui/components/audio/audio-controls/component.jsx index 90be874a03..abf8323c27 100755 --- a/bigbluebutton-html5/imports/ui/components/audio/audio-controls/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/audio/audio-controls/component.jsx @@ -92,7 +92,7 @@ class AudioControls extends PureComponent { icon={muted ? 'mute' : 'unmute'} size="lg" circle - accessKey={shortcuts.toggleMute} + accessKey={shortcuts.togglemute} /> ) : null}