From 9e6a40280e659671cb5d8bbba982909de40e30d0 Mon Sep 17 00:00:00 2001
From: Richard Alam <ritzalam@gmail.com>
Date: Sat, 5 Sep 2020 08:43:12 -0700
Subject: [PATCH]  - set html5 as default client  - add meetingEndedURL and
 endWhenNoModerator create param  - meetingEndedURL is complete  -
 endWhenNoModerator is partially implemented. Will be continued in another PR.

---
 .../java/org/bigbluebutton/api/ApiParams.java | 10 ++++++
 .../org/bigbluebutton/api/MeetingService.java | 36 ++++++++++++-------
 .../api/ParamsProcessorUtil.java              | 21 +++++++++++
 .../org/bigbluebutton/api/domain/Meeting.java | 22 +++++++++++-
 .../grails-app/conf/bigbluebutton.properties  |  9 +++--
 .../grails-app/conf/spring/resources.xml      |  1 +
 6 files changed, 84 insertions(+), 15 deletions(-)

diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/api/ApiParams.java b/bbb-common-web/src/main/java/org/bigbluebutton/api/ApiParams.java
index 5c2d04245b..5bfa44ec76 100755
--- a/bbb-common-web/src/main/java/org/bigbluebutton/api/ApiParams.java
+++ b/bbb-common-web/src/main/java/org/bigbluebutton/api/ApiParams.java
@@ -70,6 +70,16 @@ public class ApiParams {
     public static final String LOCK_SETTINGS_LOCK_ON_JOIN = "lockSettingsLockOnJoin";
     public static final String LOCK_SETTINGS_LOCK_ON_JOIN_CONFIGURABLE = "lockSettingsLockOnJoinConfigurable";
 
+    // New param passed on create call to callback when meeting ends.
+    // This is a duplicate of the endCallbackUrl meta param as we want this
+    // param to stay on the server and not propagated to client and recordings.
+    public static final String MEETING_ENDED_CALLBACK_URL = "meetingEndedURL";
+
+    // Param to end the meeting when there are no moderators after a certain period of time.
+    // Needed for classes where teacher gets disconnected and can't get back in. Prevents
+    // students from running amok.
+    public static final String END_WHEN_NO_MODERATOR = "endWhenNoModerator";
+
     private ApiParams() {
         throw new IllegalStateException("ApiParams is a utility class. Instanciation is forbidden.");
     }
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 7860a12ece..25a2665ec9 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
@@ -40,6 +40,7 @@ import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
 import java.util.concurrent.LinkedBlockingQueue;
 
+import org.apache.commons.lang3.StringUtils;
 import org.apache.http.client.utils.URIBuilder;
 import org.bigbluebutton.api.domain.GuestPolicy;
 import org.bigbluebutton.api.domain.Meeting;
@@ -716,27 +717,38 @@ public class MeetingService implements MessageListener {
 
       String endCallbackUrl = "endCallbackUrl".toLowerCase();
       Map<String, String> metadata = m.getMetadata();
-      if (!m.isBreakout() && metadata.containsKey(endCallbackUrl)) {
-        String callbackUrl = metadata.get(endCallbackUrl);
-        try {
+      if (!m.isBreakout()) {
+        if (metadata.containsKey(endCallbackUrl)) {
+          String callbackUrl = metadata.get(endCallbackUrl);
+          try {
             callbackUrl = new URIBuilder(new URI(callbackUrl))
-                    .addParameter("recordingmarks", m.haveRecordingMarks() ? "true" : "false")
-                    .addParameter("meetingID", m.getExternalId()).build().toURL().toString();
-            callbackUrlService.handleMessage(new MeetingEndedEvent(m.getInternalId(), m.getExternalId(), m.getName(), callbackUrl));
-        } catch (MalformedURLException e) {
-            log.error("Malformed URL in callback url=[{}]", callbackUrl, e);
-        } catch (URISyntaxException e) {
-            log.error("URI Syntax error in callback url=[{}]", callbackUrl, e);
-        } catch (Exception e) {
-          log.error("Error in callback url=[{}]", callbackUrl, e);
+              .addParameter("recordingmarks", m.haveRecordingMarks() ? "true" : "false")
+              .addParameter("meetingID", m.getExternalId()).build().toURL().toString();
+            MeetingEndedEvent event = new MeetingEndedEvent(m.getInternalId(), m.getExternalId(), m.getName(), callbackUrl);
+            processMeetingEndedCallback(event);
+          } catch (Exception e) {
+            log.error("Error in callback url=[{}]", callbackUrl, e);
+          }
         }
 
+        if (! StringUtils.isEmpty(m.getMeetingEndedCallbackURL())) {
+          String meetingEndedCallbackURL = m.getMeetingEndedCallbackURL();
+          callbackUrlService.handleMessage(new MeetingEndedEvent(m.getInternalId(), m.getExternalId(), m.getName(), meetingEndedCallbackURL));
+        }
       }
 
       processRemoveEndedMeeting(message);
     }
   }
 
+  private void processMeetingEndedCallback(MeetingEndedEvent event) {
+    try {
+      callbackUrlService.handleMessage(event);
+    } catch (Exception e) {
+      log.error("Error in callback url=[{}]", event.getCallbackUrl(), e);
+    }
+  }
+
   private void userJoined(UserJoined message) {
     Meeting m = getMeeting(message.meetingId);
     if (m != null) {
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 89930a94b7..c171e5e17c 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
@@ -116,6 +116,7 @@ public class ParamsProcessorUtil {
 	private Integer userInactivityThresholdInMinutes = 30;
     private Integer userActivitySignResponseDelayInMinutes = 5;
     private Boolean defaultAllowDuplicateExtUserid = true;
+	private Boolean defaultEndWhenNoModerator = false;
 
 	private String formatConfNum(String s) {
 		if (s.length() > 5) {
@@ -422,6 +423,15 @@ public class ParamsProcessorUtil {
             }
         }
 
+        boolean endWhenNoModerator = defaultEndWhenNoModerator;
+        if (!StringUtils.isEmpty(params.get(ApiParams.END_WHEN_NO_MODERATOR))) {
+          try {
+	          endWhenNoModerator = Boolean.parseBoolean(params.get(ApiParams.END_WHEN_NO_MODERATOR));
+          } catch (Exception ex) {
+            log.warn("Invalid param [endWhenNoModerator] for meeting=[{}]", internalMeetingId);
+          }
+        }
+
         String guestPolicy = defaultGuestPolicy;
         if (!StringUtils.isEmpty(params.get(ApiParams.GUEST_POLICY))) {
         	guestPolicy = params.get(ApiParams.GUEST_POLICY);
@@ -489,6 +499,11 @@ public class ParamsProcessorUtil {
             meeting.setModeratorOnlyMessage(moderatorOnlyMessage);
         }
 
+        if (!StringUtils.isEmpty(params.get(ApiParams.MEETING_ENDED_CALLBACK_URL))) {
+        	String meetingEndedCallbackURL = params.get(ApiParams.MEETING_ENDED_CALLBACK_URL);
+        	meeting.setMeetingEndedCallbackURL(meetingEndedCallbackURL);
+        }
+
         meeting.setMaxInactivityTimeoutMinutes(maxInactivityTimeoutMinutes);
         meeting.setWarnMinutesBeforeMax(warnMinutesBeforeMax);
         meeting.setMeetingExpireIfNoUserJoinedInMinutes(meetingExpireIfNoUserJoinedInMinutes);
@@ -1135,4 +1150,10 @@ public class ParamsProcessorUtil {
 	public void setAllowDuplicateExtUserid(Boolean allow) {
 		this.defaultAllowDuplicateExtUserid = allow;
 	}
+
+	public void setEndWhenNoModerator(Boolean val) {
+		this.defaultEndWhenNoModerator = val;
+	}
+
+
 }
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 624e85cf49..4ee757a942 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
@@ -94,6 +94,11 @@ public class Meeting {
 
 	public final Boolean allowDuplicateExtUserid;
 
+	private String meetingEndedCallbackURL = "";
+
+	public final Boolean endWhenNoModerator;
+
+
     public Meeting(Meeting.Builder builder) {
         name = builder.name;
         extMeetingId = builder.externalId;
@@ -122,7 +127,8 @@ public class Meeting {
         guestPolicy = builder.guestPolicy;
         breakoutRoomsParams = builder.breakoutRoomsParams;
         lockSettingsParams = builder.lockSettingsParams;
-		allowDuplicateExtUserid = builder.allowDuplicateExtUserid;
+        allowDuplicateExtUserid = builder.allowDuplicateExtUserid;
+        endWhenNoModerator = builder.endWhenNoModerator;
 
         userCustomData = new HashMap<>();
 
@@ -574,6 +580,14 @@ public class Meeting {
         this.userActivitySignResponseDelayInMinutes = userActivitySignResponseDelayInMinutes;
     }
 
+    public String getMeetingEndedCallbackURL() {
+    	return meetingEndedCallbackURL;
+    }
+
+    public void setMeetingEndedCallbackURL(String meetingEndedCallbackURL) {
+    	this.meetingEndedCallbackURL = meetingEndedCallbackURL;
+    }
+
 	public Map<String, Object> getUserCustomData(String userID){
 		return (Map<String, Object>) userCustomData.get(userID);
 	}
@@ -623,6 +637,7 @@ public class Meeting {
     	private BreakoutRoomsParams breakoutRoomsParams;
     	private LockSettingsParams lockSettingsParams;
 		private Boolean allowDuplicateExtUserid;
+		private Boolean endWhenNoModerator;
 
     	public Builder(String externalId, String internalId, long createTime) {
     		this.externalId = externalId;
@@ -754,6 +769,11 @@ public class Meeting {
     		this.allowDuplicateExtUserid = allowDuplicateExtUserid;
     		return this;
 		}
+
+		public Builder withEndWhenNoModerator(Boolean endWhenNoModerator) {
+    		this.endWhenNoModerator = endWhenNoModerator;
+    		return this;
+		}
     
     	public Meeting build() {
     		return new Meeting(this);
diff --git a/bigbluebutton-web/grails-app/conf/bigbluebutton.properties b/bigbluebutton-web/grails-app/conf/bigbluebutton.properties
index 26d818d40b..cb809d0910 100755
--- a/bigbluebutton-web/grails-app/conf/bigbluebutton.properties
+++ b/bigbluebutton-web/grails-app/conf/bigbluebutton.properties
@@ -235,10 +235,10 @@ defaultClientUrl=${bigbluebutton.web.serverURL}/client/BigBlueButton.html
 allowRequestsWithoutSession=false
 
 # Force all attendees to join the meeting using the HTML5 client
-attendeesJoinViaHTML5Client=false
+attendeesJoinViaHTML5Client=true
 
 # Force all moderators to join the meeting using the HTML5 client
-moderatorsJoinViaHTML5Client=false
+moderatorsJoinViaHTML5Client=true
 
 # The url of the BigBlueButton HTML5 client. Users will be redirected here when
 # successfully joining the meeting.
@@ -347,3 +347,8 @@ lockSettingsLockOnJoinConfigurable=false
 allowDuplicateExtUserid=true
 
 defaultTextTrackUrl=${bigbluebutton.web.serverURL}/bigbluebutton
+
+# Param to end the meeting when there are no moderators after a certain period of time.
+# Needed for classes where teacher gets disconnected and can't get back in. Prevents
+# students from running amok.
+endWhenNoModerator=false
diff --git a/bigbluebutton-web/grails-app/conf/spring/resources.xml b/bigbluebutton-web/grails-app/conf/spring/resources.xml
index df4684abf7..79b90ec521 100755
--- a/bigbluebutton-web/grails-app/conf/spring/resources.xml
+++ b/bigbluebutton-web/grails-app/conf/spring/resources.xml
@@ -158,6 +158,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
         <property name="lockSettingsLockOnJoin" value="${lockSettingsLockOnJoin}"/>
         <property name="lockSettingsLockOnJoinConfigurable" value="${lockSettingsLockOnJoinConfigurable}"/>
         <property name="allowDuplicateExtUserid" value="${allowDuplicateExtUserid}"/>
+        <property name="endWhenNoModerator" value="${endWhenNoModerator}"/>
     </bean>
 
     <import resource="doc-conversion.xml"/>
-- 
GitLab