diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/api/MeetingService.java b/bbb-common-web/src/main/java/org/bigbluebutton/api/MeetingService.java index 25a2665ec98ee772c5ac68abc53d22aef7eb2850..e5d4cdad2c79da150a5eafdd35797345b66a735f 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/api/MeetingService.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/api/MeetingService.java @@ -82,7 +82,8 @@ import org.bigbluebutton.api2.IBbbWebApiGWApp; import org.bigbluebutton.api2.domain.UploadedTrack; import org.bigbluebutton.common2.redis.RedisStorageService; import org.bigbluebutton.presentation.PresentationUrlDownloadService; -import org.bigbluebutton.web.services.RegisteredUserCleanupTimerTask; +import org.bigbluebutton.web.services.UserCleanupTimerTask; +import org.bigbluebutton.web.services.EnteredUserCleanupTimerTask; import org.bigbluebutton.web.services.callback.CallbackUrlService; import org.bigbluebutton.web.services.callback.MeetingEndedEvent; import org.bigbluebutton.web.services.turn.StunTurnService; @@ -107,12 +108,16 @@ public class MeetingService implements MessageListener { private final ConcurrentMap<String, UserSession> sessions; private RecordingService recordingService; - private RegisteredUserCleanupTimerTask registeredUserCleaner; + private UserCleanupTimerTask userCleaner; + private EnteredUserCleanupTimerTask enteredUserCleaner; private StunTurnService stunTurnService; private RedisStorageService storeService; private CallbackUrlService callbackUrlService; private boolean keepEvents; + private long usersTimeout; + private long enteredUsersTimeout; + private ParamsProcessorUtil paramsProcessorUtil; private PresentationUrlDownloadService presDownloadService; @@ -179,22 +184,63 @@ public class MeetingService implements MessageListener { } /** - * Remove registered users who did not successfully joined the meeting. + * Remove users who did not successfully reconnected to the meeting. */ - public void purgeRegisteredUsers() { + public void purgeUsers() { for (AbstractMap.Entry<String, Meeting> entry : this.meetings.entrySet()) { Long now = System.currentTimeMillis(); Meeting meeting = entry.getValue(); - ConcurrentMap<String, User> users = meeting.getUsersMap(); + for (AbstractMap.Entry<String, User> userEntry : meeting.getUsersMap().entrySet()) { + String userId = userEntry.getKey(); + User user = userEntry.getValue(); + + if (!user.hasLeft()) continue; + + long elapsedTime = now - user.getLeftOn(); + if (elapsedTime >= usersTimeout) { + meeting.removeUser(userId); + + Map<String, Object> logData = new HashMap<>(); + logData.put("meetingId", meeting.getInternalId()); + logData.put("userId", userId); + logData.put("logCode", "removed_user"); + logData.put("description", "User left and was removed from the meeting."); - for (AbstractMap.Entry<String, RegisteredUser> registeredUser : meeting.getRegisteredUsers().entrySet()) { - String registeredUserID = registeredUser.getKey(); - RegisteredUser registeredUserDate = registeredUser.getValue(); + Gson gson = new Gson(); + String logStr = gson.toJson(logData); - long elapsedTime = now - registeredUserDate.registeredOn; - if (elapsedTime >= 60000 && !users.containsKey(registeredUserID)) { - meeting.userUnregistered(registeredUserID); + log.info(" --analytics-- data={}", logStr); + } + } + } + } + + /** + * Remove entered users who did not join. + */ + public void purgeEnteredUsers() { + for (AbstractMap.Entry<String, Meeting> entry : this.meetings.entrySet()) { + Long now = System.currentTimeMillis(); + Meeting meeting = entry.getValue(); + + for (AbstractMap.Entry<String, Long> enteredUser : meeting.getEnteredUsers().entrySet()) { + String userId = enteredUser.getKey(); + + long elapsedTime = now - enteredUser.getValue(); + if (elapsedTime >= enteredUsersTimeout) { + meeting.removeEnteredUser(userId); + + Map<String, Object> logData = new HashMap<>(); + logData.put("meetingId", meeting.getInternalId()); + logData.put("userId", userId); + logData.put("logCode", "purged_entered_user"); + logData.put("description", "Purged user that called ENTER from the API but never joined"); + + Gson gson = new Gson(); + String logStr = gson.toJson(logData); + + log.info(" --analytics-- data={}", logStr); } } } @@ -817,13 +863,6 @@ public class MeetingService implements MessageListener { // the meeting ended. m.setEndTime(System.currentTimeMillis()); } - - RegisteredUser userRegistered = m.userUnregistered(message.userId); - if (userRegistered != null) { - log.info("User unregistered from meeting"); - } else { - log.info("User was not unregistered from meeting because it was not found"); - } } } } @@ -1036,7 +1075,8 @@ public class MeetingService implements MessageListener { public void stop() { processMessage = false; - registeredUserCleaner.stop(); + userCleaner.stop(); + enteredUserCleaner.stop(); } public void setRecordingService(RecordingService s) { @@ -1055,12 +1095,16 @@ public class MeetingService implements MessageListener { this.gw = gw; } + public void setEnteredUserCleanupTimerTask(EnteredUserCleanupTimerTask c) { + enteredUserCleaner = c; + enteredUserCleaner.setMeetingService(this); + enteredUserCleaner.start(); + } - public void setRegisteredUserCleanupTimerTask( - RegisteredUserCleanupTimerTask c) { - registeredUserCleaner = c; - registeredUserCleaner.setMeetingService(this); - registeredUserCleaner.start(); + public void setUserCleanupTimerTask(UserCleanupTimerTask c) { + userCleaner = c; + userCleaner.setMeetingService(this); + userCleaner.start(); } public void setStunTurnService(StunTurnService s) { @@ -1070,4 +1114,12 @@ public class MeetingService implements MessageListener { public void setKeepEvents(boolean value) { keepEvents = value; } + + public void setUsersTimeout(long value) { + usersTimeout = value; + } + + public void setEnteredUsersTimeout(long value) { + enteredUsersTimeout = value; + } } 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 4ee757a942efe30a3548f3260c6ac74114000ae2..a19b62d7732fa8201b6692d43439f20d3804fd13 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 @@ -73,6 +73,7 @@ public class Meeting { private Map<String, Object> userCustomData; private final ConcurrentMap<String, User> users; private final ConcurrentMap<String, RegisteredUser> registeredUsers; + private final ConcurrentMap<String, Long> enteredUsers; private final ConcurrentMap<String, Config> configs; private final Boolean isBreakout; private final List<String> breakoutRooms = new ArrayList<>(); @@ -134,6 +135,7 @@ public class Meeting { users = new ConcurrentHashMap<>(); registeredUsers = new ConcurrentHashMap<>(); + enteredUsers = new ConcurrentHashMap<>();; configs = new ConcurrentHashMap<>(); } @@ -449,12 +451,28 @@ public class Meeting { } public void userJoined(User user) { - userHasJoined = true; - this.users.put(user.getInternalUserId(), user); + User u = getUserById(user.getInternalUserId()); + if (u != null) { + u.joined(); + } else { + if (!userHasJoined) userHasJoined = true; + this.users.put(user.getInternalUserId(), user); + // Clean this user up from the entered user's list + removeEnteredUser(user.getInternalUserId()); + } + } + + public User userLeft(String userId) { + User user = getUserById(userId); + if (user != null) { + user.left(); + } + + return user; } - public User userLeft(String userid){ - return users.remove(userid); + public User removeUser(String userId) { + return this.users.remove(userId); } public User getUserById(String id){ @@ -604,6 +622,29 @@ public class Meeting { return registeredUsers; } + public ConcurrentMap<String, Long> getEnteredUsers() { + return this.enteredUsers; + } + + public void userEntered(String userId) { + // Skip if user already joined + User u = getUserById(userId); + if (u != null) return; + + if (!enteredUsers.containsKey(userId)) { + Long time = System.currentTimeMillis(); + this.enteredUsers.put(userId, time); + } + } + + public Long removeEnteredUser(String userId) { + return this.enteredUsers.remove(userId); + } + + public Long getEnteredUserById(String userId) { + return this.enteredUsers.get(userId); + } + /*** * Meeting Builder * diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/api/domain/User.java b/bbb-common-web/src/main/java/org/bigbluebutton/api/domain/User.java index abd32ff6ca67535aae3eff212e6c6acc924c0d26..59521552c3041b30ef9e527d1635b3d614919fd8 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/api/domain/User.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/api/domain/User.java @@ -38,6 +38,7 @@ public class User { private Boolean voiceJoined = false; private String clientType; private List<String> streams; + private Long leftOn = null; public User(String internalUserId, String externalUserId, @@ -90,6 +91,22 @@ public class User { return this.guestStatus; } + public Boolean hasLeft() { + return leftOn != null; + } + + public void joined() { + this.leftOn = null; + } + + public void left() { + this.leftOn = System.currentTimeMillis(); + } + + public Long getLeftOn() { + return this.leftOn; + } + public String getFullname() { return fullname; } diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/web/services/RegisteredUserCleanupTimerTask.java b/bbb-common-web/src/main/java/org/bigbluebutton/web/services/EnteredUserCleanupTimerTask.java similarity index 89% rename from bbb-common-web/src/main/java/org/bigbluebutton/web/services/RegisteredUserCleanupTimerTask.java rename to bbb-common-web/src/main/java/org/bigbluebutton/web/services/EnteredUserCleanupTimerTask.java index e4e0844572ad90a2d9a6fa4702d3b21e02656b3c..7ee01ea6ef9ea2c0ec786694d7a0a059bb63f817 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/web/services/RegisteredUserCleanupTimerTask.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/web/services/EnteredUserCleanupTimerTask.java @@ -1,7 +1,7 @@ /** * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/ * -* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below). +* Copyright (c) 2020 BigBlueButton Inc. and by respective authors (see below). * * This program is free software; you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free Software @@ -25,11 +25,11 @@ import java.util.concurrent.TimeUnit; import org.bigbluebutton.api.MeetingService; -public class RegisteredUserCleanupTimerTask { +public class EnteredUserCleanupTimerTask { private MeetingService service; private ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(1); - private long runEvery = 60000; + private long runEvery = 30000; public void setMeetingService(MeetingService svc) { this.service = svc; @@ -50,7 +50,7 @@ public class RegisteredUserCleanupTimerTask { private class CleanupTask implements Runnable { @Override public void run() { - service.purgeRegisteredUsers(); + service.purgeEnteredUsers(); } } -} \ No newline at end of file +} diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/web/services/UserCleanupTimerTask.java b/bbb-common-web/src/main/java/org/bigbluebutton/web/services/UserCleanupTimerTask.java new file mode 100755 index 0000000000000000000000000000000000000000..b11fab3f81cfdcfd31999b19f05fe34e95dea0f1 --- /dev/null +++ b/bbb-common-web/src/main/java/org/bigbluebutton/web/services/UserCleanupTimerTask.java @@ -0,0 +1,56 @@ +/** +* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/ +* +* Copyright (c) 2020 BigBlueButton Inc. and by respective authors (see below). +* +* This program is free software; you can redistribute it and/or modify it under the +* terms of the GNU Lesser General Public License as published by the Free Software +* Foundation; either version 3.0 of the License, or (at your option) any later +* version. +* +* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY +* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License along +* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. +* +*/ + +package org.bigbluebutton.web.services; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import org.bigbluebutton.api.MeetingService; + +public class UserCleanupTimerTask { + + private MeetingService service; + private ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(1); + private long runEvery = 15000; + + public void setMeetingService(MeetingService svc) { + this.service = svc; + } + + public void start() { + scheduledThreadPool.scheduleWithFixedDelay(new CleanupTask(), 60000, runEvery, TimeUnit.MILLISECONDS); + } + + public void stop() { + scheduledThreadPool.shutdownNow(); + } + + public void setRunEvery(long v) { + runEvery = v; + } + + private class CleanupTask implements Runnable { + @Override + public void run() { + service.purgeUsers(); + } + } +} diff --git a/bigbluebutton-web/grails-app/conf/bigbluebutton.properties b/bigbluebutton-web/grails-app/conf/bigbluebutton.properties index cb809d0910dbf6f4badf83f4ad0f2955c0bbb53d..85bf953de58ff6a9b6f612b575fa93bf36387fff 100755 --- a/bigbluebutton-web/grails-app/conf/bigbluebutton.properties +++ b/bigbluebutton-web/grails-app/conf/bigbluebutton.properties @@ -215,6 +215,16 @@ allowModsToUnmuteUsers=false # Saves meeting events even if the meeting is not recorded keepEvents=false +# Timeout (millis) to remove a joined user after her/his left event without a rejoin +# e.g. regular user left event +# Default 60s +usersTimeout=60000 + +# Timeout (millis) to remove users that called the enter API but did not join +# e.g. user's client hanged between the enter call and join event +# Default 45s +enteredUsersTimeout=45000 + #---------------------------------------------------- # This URL is where the BBB client is accessible. When a user sucessfully # enters a name and password, she is redirected here to load the client. diff --git a/bigbluebutton-web/grails-app/conf/spring/resources.xml b/bigbluebutton-web/grails-app/conf/spring/resources.xml index 79b90ec521bac1af0f99f4b4181007d7d3ccf4ec..b543e378abfbf4dae3198e8ef4c8496fa180b141 100755 --- a/bigbluebutton-web/grails-app/conf/spring/resources.xml +++ b/bigbluebutton-web/grails-app/conf/spring/resources.xml @@ -33,7 +33,8 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. </property> </bean> - <bean id="registeredUserCleanupTimerTask" class="org.bigbluebutton.web.services.RegisteredUserCleanupTimerTask"/> + <bean id="userCleanupTimerTask" class="org.bigbluebutton.web.services.UserCleanupTimerTask"/> + <bean id="enteredUserCleanupTimerTask" class="org.bigbluebutton.web.services.EnteredUserCleanupTimerTask"/> <bean id="keepAliveService" class="org.bigbluebutton.web.services.KeepAliveService" init-method="start" destroy-method="stop"> @@ -47,10 +48,13 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. <property name="presDownloadService" ref="presDownloadService"/> <property name="paramsProcessorUtil" ref="paramsProcessorUtil"/> <property name="stunTurnService" ref="stunTurnService"/> - <property name="registeredUserCleanupTimerTask" ref="registeredUserCleanupTimerTask"/> + <property name="userCleanupTimerTask" ref="userCleanupTimerTask"/> + <property name="enteredUserCleanupTimerTask" ref="enteredUserCleanupTimerTask"/> <property name="gw" ref="bbbWebApiGWApp"/> <property name="callbackUrlService" ref="callbackUrlService"/> <property name="keepEvents" value="${keepEvents}"/> + <property name="usersTimeout" value="${usersTimeout}"/> + <property name="enteredUsersTimeout" value="${enteredUsersTimeout}"/> </bean> <bean id="oldMessageReceivedGW" class="org.bigbluebutton.api2.bus.OldMessageReceivedGW"> 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 c31b826c833ea7333ed8f6a47089e1aa35b0bd0f..5b98e5d5ffc5dc0a8704ba842fd85a06c9d34d72 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 @@ -431,14 +431,9 @@ class ApiController { us.avatarURL = meeting.defaultAvatarURL } - // Register user into the meeting. - meetingService.registerUser(us.meetingID, us.internalUserId, us.fullname, us.role, us.externUserID, - us.authToken, us.avatarURL, us.guest, us.authed, guestStatusVal) + String meetingId = meeting.getInternalId() - // Validate if the maxParticipants limit has been reached based on registeredUsers. If so, complain. - // when maxUsers is set to 0, the validation is ignored - int maxUsers = meeting.getMaxUsers(); - if (maxUsers > 0 && meeting.getRegisteredUsers().size() >= maxUsers) { + if (hasReachedMaxParticipants(meeting, us)) { // BEGIN - backward compatibility invalid("maxParticipantsReached", "The number of participants allowed for this meeting has been reached.", REDIRECT_RESPONSE); return @@ -449,6 +444,20 @@ class ApiController { return; } + // Register user into the meeting. + meetingService.registerUser( + us.meetingID, + us.internalUserId, + us.fullname, + us.role, + us.externUserID, + us.authToken, + us.avatarURL, + us.guest, + us.authed, + guestStatusVal + ) + //Identify which of these to logs should be used. sessionToken or user-token log.info("Session sessionToken for " + us.fullname + " [" + session[sessionToken] + "]") log.info("Session user-token for " + us.fullname + " [" + session['user-token'] + "]") @@ -1170,24 +1179,8 @@ class ApiController { String logoutUrl = paramsProcessorUtil.getDefaultLogoutUrl() boolean reject = false - String sessionToken = null - UserSession us = null - - if (StringUtils.isEmpty(params.sessionToken)) { - log.info("No session for user in conference.") - reject = true - } else { - sessionToken = StringUtils.strip(params.sessionToken) - log.info("Getting ConfigXml for SessionToken = " + sessionToken) - if (!session[sessionToken]) { - reject = true - } else { - us = meetingService.getUserSessionWithAuthToken(sessionToken); - if (us == null) reject = true - } - } - - if (reject) { + String sessionToken = sanitizeSessionToken(params.sessionToken) + if (!hasValidSession(sessionToken)) { response.addHeader("Cache-Control", "no-cache") withFormat { xml { @@ -1195,6 +1188,7 @@ class ApiController { } } } else { + UserSession us = getUserSession(sessionToken) if (StringUtils.isEmpty(us.configXML)) { // BEGIN - backward compatibility invalid("noConfigFound", "We could not find a config for this request.", REDIRECT_RESPONSE); @@ -1232,44 +1226,26 @@ class ApiController { log.debug CONTROLLER_NAME + "#${API_CALL}" ApiErrors errors = new ApiErrors() boolean reject = false; + String sessionToken = sanitizeSessionToken(params.sessionToken) - if (StringUtils.isEmpty(params.sessionToken)) { - log.debug("SessionToken is missing.") - } - - String sessionToken = StringUtils.strip(params.sessionToken) - - UserSession us = null; + UserSession us = getUserSession(sessionToken); Meeting meeting = null; - UserSession userSession = null; - if (sessionToken == null || meetingService.getUserSessionWithAuthToken(sessionToken) == null) { + if (us == null) { log.debug("No user with session token.") reject = true; } else { - us = meetingService.getUserSessionWithAuthToken(sessionToken); meeting = meetingService.getMeeting(us.meetingID); if (meeting == null || meeting.isForciblyEnded()) { log.debug("Meeting not found.") reject = true } - userSession = meetingService.getUserSessionWithAuthToken(sessionToken) - if (userSession == null) { - log.debug("Session with user not found.") - reject = true - } - } // Determine the logout url so we can send the user there. - String logoutUrl = paramsProcessorUtil.getDefaultLogoutUrl() - - if (us != null) { - logoutUrl = us.logoutUrl - } + String logoutUrl = us != null ? us.logoutUrl : paramsProcessorUtil.getDefaultLogoutUrl() if (reject) { - log.info("No session for user in conference.") response.addHeader("Cache-Control", "no-cache") withFormat { json { @@ -1305,7 +1281,7 @@ class ApiController { clientURL = params.clientURL; } - String guestWaitStatus = userSession.guestStatus + String guestWaitStatus = us.guestStatus log.debug("GuestWaitStatus = " + guestWaitStatus) @@ -1393,48 +1369,34 @@ class ApiController { def enter = { boolean reject = false; - if (StringUtils.isEmpty(params.sessionToken)) { - println("SessionToken is missing.") - } - - String sessionToken = StringUtils.strip(params.sessionToken) - - UserSession us = null; + String sessionToken = sanitizeSessionToken(params.sessionToken) + UserSession us = getUserSession(sessionToken); Meeting meeting = null; - UserSession userSession = null; - - Boolean allowEnterWithoutSession = false; - // Depending on configuration, allow ENTER requests to proceed without session - if (paramsProcessorUtil.getAllowRequestsWithoutSession()) { - allowEnterWithoutSession = paramsProcessorUtil.getAllowRequestsWithoutSession(); - } String respMessage = "Session " + sessionToken + " not found." - if (!sessionToken || meetingService.getUserSessionWithAuthToken(sessionToken) == null || (!allowEnterWithoutSession && !session[sessionToken])) { + if (!hasValidSession(sessionToken)) { reject = true; - respMessage = "Session " + sessionToken + " not found." } else { - us = meetingService.getUserSessionWithAuthToken(sessionToken); - if (us == null) { - respMessage = "Session " + sessionToken + " not found." + meeting = meetingService.getMeeting(us.meetingID); + if (meeting == null || meeting.isForciblyEnded()) { reject = true + respMessage = "Meeting not found or ended for session " + sessionToken + "." } else { - meeting = meetingService.getMeeting(us.meetingID); - if (meeting == null || meeting.isForciblyEnded()) { - reject = true - respMessage = "Meeting not found or ended for session " + sessionToken + "." - } - if (us.guestStatus.equals(GuestPolicy.DENY)) { - respMessage = "User denied for user with session " + sessionToken + "." - reject = true + if (hasReachedMaxParticipants(meeting, us)) { + reject = true; + respMessage = "The number of participants allowed for this meeting has been reached."; + } else { + meeting.userEntered(us.internalUserId); } } + if (us.guestStatus.equals(GuestPolicy.DENY)) { + respMessage = "User denied for user with session " + sessionToken + "." + reject = true + } } if (reject) { - log.info("No session for user in conference.") - // Determine the logout url so we can send the user there. String logoutUrl = paramsProcessorUtil.getDefaultLogoutUrl() @@ -1549,25 +1511,13 @@ class ApiController { def stuns = { boolean reject = false; - UserSession us = null; + String sessionToken = sanitizeSessionToken(params.sessionToken) + UserSession us = getUserSession(sessionToken); Meeting meeting = null; - String sessionToken = null - - if (!StringUtils.isEmpty(params.sessionToken)) { - sessionToken = StringUtils.strip(params.sessionToken) - println("Session token = [" + sessionToken + "]") - } - Boolean allowStunsWithoutSession = false; - // Depending on configuration, allow STUNS requests to proceed without session - if (paramsProcessorUtil.getAllowRequestsWithoutSession()) { - allowStunsWithoutSession = paramsProcessorUtil.getAllowRequestsWithoutSession(); - } - - if (sessionToken == null || meetingService.getUserSessionWithAuthToken(sessionToken) == null || (!allowStunsWithoutSession && !session[sessionToken])) { + if (!hasValidSession(sessionToken)) { reject = true; } else { - us = meetingService.getUserSessionWithAuthToken(sessionToken); meeting = meetingService.getMeeting(us.meetingID); if (meeting == null || meeting.isForciblyEnded()) { reject = true @@ -1575,8 +1525,6 @@ class ApiController { } if (reject) { - log.info("No session for user in conference.") - String logoutUrl = paramsProcessorUtil.getDefaultLogoutUrl() response.addHeader("Cache-Control", "no-cache") @@ -1633,12 +1581,7 @@ class ApiController { *************************************************/ def signOut = { - String sessionToken = null - - if (!StringUtils.isEmpty(params.sessionToken)) { - sessionToken = StringUtils.strip(params.sessionToken) - println("SessionToken = " + sessionToken) - } + String sessionToken = sanitizeSessionToken(params.sessionToken) Meeting meeting = null; @@ -2155,6 +2098,76 @@ class ApiController { } } + def getUserSession(token) { + if (token == null) { + return null + } + + UserSession us = meetingService.getUserSessionWithAuthToken(token) + if (us == null) { + log.info("Cannot find UserSession for token ${token}") + } + + return us + } + + def sanitizeSessionToken(param) { + if (param == null) { + log.info("sanitizeSessionToken: token is null") + return null + } + + if (StringUtils.isEmpty(param)) { + log.info("sanitizeSessionToken: token is empty") + return null + } + + return StringUtils.strip(param) + } + + private Boolean hasValidSession(token) { + UserSession us = getUserSession(token) + if (us == null) { + return false + } + + if (!session[token]) { + log.info("Session for token ${token} not found") + + Boolean allowRequestsWithoutSession = paramsProcessorUtil.getAllowRequestsWithoutSession() + if (!allowRequestsWithoutSession) { + log.info("Meeting related to ${token} doesn't allow requests without session") + return false + } + } + + log.info("Token ${token} is valid") + return true + } + + // Validate maxParticipants constraint + private Boolean hasReachedMaxParticipants(meeting, us) { + // Meeting object calls it maxUsers to build up the drama + int maxParticipants = meeting.getMaxUsers(); + // When is set to 0, the validation is ignored + Boolean enabled = maxParticipants > 0; + // Users refreshing page or reconnecting must be identified + Boolean rejoin = meeting.getUserById(us.internalUserId) != null; + // Users that passed enter once, still not joined but somehow re-entered + Boolean reenter = meeting.getEnteredUserById(us.internalUserId) != null; + // Users that already joined the meeting + int joinedUsers = meeting.getUsers().size() + // Users that are entering the meeting + int enteredUsers = meeting.getEnteredUsers().size() + + Boolean reachedMax = (joinedUsers + enteredUsers) >= maxParticipants; + if (enabled && !rejoin && !reenter && reachedMax) { + return true; + } + + return false; + } + private void respondWithErrors(errorList, redirectResponse = false) { log.debug CONTROLLER_NAME + "#invalid" if (redirectResponse) {