From 4ef078ccf56d39b7ea406151bbdcd3b7de60db56 Mon Sep 17 00:00:00 2001 From: Guilherme Pereira Leme <69865537+GuiLeme@users.noreply.github.com> Date: Tue, 10 Oct 2023 21:00:20 -0300 Subject: [PATCH] feature: Override client settings through API /create call (#18782) * akka-with-client-configs * [akka-with-client-configs] - inserted client configs in akka * [issue-18588-create-override] - WIP * [akka-with-client-configs] - Remove unnecessary code * [issue-18588] - test some thesis * [akka-with-client-configs] - refactor to add jackson and immutable.Map * [issue-18588-create-override] - new architecture for overriding client configs] * [issue-18588-create-override] - implemented settings * Refactor on clientSettingsOverride module and add allowOverrideClientSettingsOnCreateCall conf --------- Co-authored-by: Gustavo Trott --- akka-bbb-apps/project/Dependencies.scala | 6 +- .../org/bigbluebutton/ClientSettings.scala | 27 ++++-- .../bigbluebutton/SystemConfiguration.scala | 12 ++- .../core/BigBlueButtonActor.scala | 6 -- .../core/db/PostgresProfile.scala | 1 + .../core/running/RunningMeeting.scala | 2 +- .../common2/domain/Meeting2x.scala | 3 +- .../org/bigbluebutton/api/MeetingService.java | 3 +- .../api/ParamsProcessorUtil.java | 18 ++-- .../org/bigbluebutton/api/domain/Meeting.java | 10 ++ .../bigbluebutton/api2/IBbbWebApiGWApp.java | 3 +- .../bigbluebutton/api2/BbbWebApiGWApp.scala | 6 +- .../meetings/server/modifiers/addMeeting.js | 1 + .../grails-app/conf/bigbluebutton.properties | 3 + .../grails-app/conf/spring/resources.xml | 1 + .../web/controllers/ApiController.groovy | 97 +++++++++++++------ 16 files changed, 135 insertions(+), 64 deletions(-) diff --git a/akka-bbb-apps/project/Dependencies.scala b/akka-bbb-apps/project/Dependencies.scala index cff514be37..f22fb3b889 100755 --- a/akka-bbb-apps/project/Dependencies.scala +++ b/akka-bbb-apps/project/Dependencies.scala @@ -37,6 +37,7 @@ object Dependencies { val scalaTest = "3.2.11" val mockito = "2.23.0" val akkaTestKit = "2.6.0" + val jacksonDataFormat = "2.13.5" } object Compile { @@ -65,6 +66,8 @@ object Dependencies { val slickHikaricp = "com.typesafe.slick" %% "slick-hikaricp" % Versions.slick val slickPg = "com.github.tminglei" %% "slick-pg" % Versions.slickPg val postgresql = "org.postgresql" % "postgresql" % Versions.postgresql + val jacksonDataFormat = "com.fasterxml.jackson.dataformat" % "jackson-dataformat-yaml" % Versions.jacksonDataFormat + val snakeYaml = "org.yaml" % "snakeyaml" } object Test { @@ -101,5 +104,6 @@ object Dependencies { Compile.slick, Compile.slickHikaricp, Compile.slickPg, - Compile.postgresql) ++ testing + Compile.postgresql, + Compile.jacksonDataFormat) ++ testing } diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/ClientSettings.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/ClientSettings.scala index 5f79469286..7e8dddf3e7 100644 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/ClientSettings.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/ClientSettings.scala @@ -1,23 +1,17 @@ package org.bigbluebutton -import com.typesafe.config.ConfigFactory +import org.bigbluebutton.common2.util.YamlUtil +import org.slf4j.LoggerFactory import java.io.{ ByteArrayInputStream, File } import scala.io.BufferedSource import scala.util.{ Failure, Success, Try } -object ClientSettings { - val config = ConfigFactory.load() +object ClientSettings extends SystemConfiguration { var clientSettingsFromFile: Map[String, Object] = Map("" -> "") + val logger = LoggerFactory.getLogger(this.getClass) def loadClientSettingsFromFile() = { - lazy val clientSettingsPath = Try(config.getString("client.clientSettingsFilePath")).getOrElse( - "/usr/share/meteor/bundle/programs/server/assets/app/config/settings.yml" - ) - lazy val clientSettingsPathOverride = Try(config.getString("client.clientSettingsOverrideFilePath")).getOrElse( - "/etc/bigbluebutton/bbb-html5.yml" - ) - val clientSettingsFile = scala.io.Source.fromFile(clientSettingsPath, "UTF-8") val clientSettingsFileOverrideToCheck = new File(clientSettingsPathOverride) @@ -45,4 +39,17 @@ object ClientSettings { } ) } + + def getClientSettingsWithOverride(clientSettingsOverrideJson: String): Map[String, Object] = { + if (clientSettingsOverrideJson.nonEmpty) { + val scalaMapClientOverride = common2.util.JsonUtil.toMap[Object](clientSettingsOverrideJson) + scalaMapClientOverride match { + case Success(clientSettingsOverrideAsMap) => YamlUtil.mergeImmutableMaps(clientSettingsFromFile, clientSettingsOverrideAsMap) + case Failure(msg) => + logger.debug("No valid JSON override of client configuration in create call: {}", msg) + clientSettingsFromFile + } + } else clientSettingsFromFile + } + } diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/SystemConfiguration.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/SystemConfiguration.scala index 0df81de5df..cdab3d7cb8 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/SystemConfiguration.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/SystemConfiguration.scala @@ -1,6 +1,9 @@ package org.bigbluebutton -import scala.util.Try +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory + +import scala.util.{ Failure, Success, Try } import com.typesafe.config.ConfigFactory trait SystemConfiguration { @@ -77,6 +80,13 @@ trait SystemConfiguration { lazy val analyticsIncludeChat = Try(config.getBoolean("analytics.includeChat")).getOrElse(true) + lazy val clientSettingsPath = Try(config.getString("client.clientSettingsFilePath")).getOrElse( + "/usr/share/meteor/bundle/programs/server/assets/app/config/settings.yml" + ) + lazy val clientSettingsPathOverride = Try(config.getString("client.clientSettingsOverrideFilePath")).getOrElse( + "/etc/bigbluebutton/bbb-html5.yml" + ) + // Grab the "interface" parameter from the http config val httpHost = config.getString("http.interface") // Grab the "port" parameter from the http config diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/BigBlueButtonActor.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/BigBlueButtonActor.scala index 3219206cab..51fbe9a903 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/BigBlueButtonActor.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/BigBlueButtonActor.scala @@ -19,12 +19,6 @@ import org.bigbluebutton.core.util.ColorPicker import org.bigbluebutton.core2.RunningMeetings import org.bigbluebutton.core2.message.senders.MsgBuilder import org.bigbluebutton.service.HealthzService -import org.bigbluebutton.common2 -import org.bigbluebutton.common2.util.YamlUtil - -import scala.jdk.CollectionConverters._ -import java.util -import scala.util.{ Failure, Success } object BigBlueButtonActor extends SystemConfiguration { def props( diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/db/PostgresProfile.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/db/PostgresProfile.scala index dd195ef5fa..b5f551117e 100644 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/db/PostgresProfile.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/db/PostgresProfile.scala @@ -34,6 +34,7 @@ object JsonUtils { case m: Map[_, _] => JsObject(m.asInstanceOf[Map[String, Any]].map { case (k, v) => k -> write(v) }) case l: List[_] => JsArray(l.map(write).toVector) case a: Array[_] => JsArray(a.map(write).toVector) + case null => JsNull case _ => throw new IllegalArgumentException(s"Unsupported type: ${x.getClass.getName}") // case _ => JsNull } diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/RunningMeeting.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/RunningMeeting.scala index d570129521..c3f81d35ed 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/RunningMeeting.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/RunningMeeting.scala @@ -35,7 +35,7 @@ class RunningMeeting(val props: DefaultProps, outGW: OutMessageGateway, private val deskshareModel = new ScreenshareModel private val audioCaptions = new AudioCaptions private val timerModel = new TimerModel - private val clientSettings = ClientSettings.clientSettingsFromFile + private val clientSettings: Map[String, Object] = ClientSettings.getClientSettingsWithOverride(props.overrideClientSettings) // meetingModel.setGuestPolicy(props.usersProp.guestPolicy) diff --git a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/domain/Meeting2x.scala b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/domain/Meeting2x.scala index d224b8f3a2..4a52394a70 100755 --- a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/domain/Meeting2x.scala +++ b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/domain/Meeting2x.scala @@ -89,7 +89,8 @@ case class DefaultProps( metadataProp: MetadataProp, lockSettingsProps: LockSettingsProps, systemProps: SystemProps, - groups: Vector[GroupProps] + groups: Vector[GroupProps], + overrideClientSettings: String ) case class StartEndTimeStatus(startTime: Long, endTime: Long) 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 2b1a3851f3..d06c10dc65 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 @@ -407,7 +407,8 @@ public class MeetingService implements MessageListener { m.getMuteOnStart(), m.getAllowModsToUnmuteUsers(), m.getAllowModsToEjectCameras(), m.getMeetingKeepEvents(), m.breakoutRoomsParams, m.lockSettingsParams, m.getHtml5InstanceId(), m.getGroups(), m.getDisabledFeatures(), m.getNotifyRecordingIsOn(), - m.getPresentationUploadExternalDescription(), m.getPresentationUploadExternalUrl()); + m.getPresentationUploadExternalDescription(), m.getPresentationUploadExternalUrl(), + m.getOverrideClientSettings()); } private String formatPrettyDate(Long timestamp) { 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 85d70ce4d1..6ab1604b0f 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 @@ -19,13 +19,10 @@ package org.bigbluebutton.api; -import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; -import java.text.DecimalFormat; -import java.text.DecimalFormatSymbols; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -37,12 +34,6 @@ import com.google.gson.JsonObject; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.StringUtils; -import org.apache.http.HttpEntity; -import org.apache.http.HttpResponse; -import org.apache.http.client.ClientProtocolException; -import org.apache.http.client.ResponseHandler; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.util.EntityUtils; import org.bigbluebutton.api.domain.BreakoutRoomsParams; import org.bigbluebutton.api.domain.LockSettingsParams; import org.bigbluebutton.api.domain.Meeting; @@ -145,6 +136,7 @@ public class ParamsProcessorUtil { private String bbbVersion = ""; private Boolean allowRevealOfBBBVersion = false; + private Boolean allowOverrideClientSettingsOnCreateCall = false; private String formatConfNum(String s) { if (s.length() > 5) { @@ -912,6 +904,10 @@ public class ParamsProcessorUtil { return allowRevealOfBBBVersion; } + public Boolean getAllowOverrideClientSettingsOnCreateCall() { + return allowOverrideClientSettingsOnCreateCall; + } + public String processWelcomeMessage(String message, Boolean isBreakout) { String welcomeMessage = message; if (StringUtils.isEmpty(message)) { @@ -1515,4 +1511,8 @@ public class ParamsProcessorUtil { this.allowRevealOfBBBVersion = allowVersion; } + public void setAllowOverrideClientSettingsOnCreateCall(Boolean allowOverrideClientSettingsOnCreateCall) { + this.allowOverrideClientSettingsOnCreateCall = allowOverrideClientSettingsOnCreateCall; + } + } 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 2a7c4d20c1..08f5dc1a0f 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 @@ -118,6 +118,8 @@ public class Meeting { private Integer html5InstanceId; + private String overrideClientSettings = ""; + public Meeting(Meeting.Builder builder) { name = builder.name; extMeetingId = builder.externalId; @@ -1131,4 +1133,12 @@ public class Meeting { return new Meeting(this); } } + + public String getOverrideClientSettings() { + return overrideClientSettings; + } + + public void setOverrideClientSettings(String overrideClientConfigs) { + this.overrideClientSettings = overrideClientConfigs; + } } diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/api2/IBbbWebApiGWApp.java b/bbb-common-web/src/main/java/org/bigbluebutton/api2/IBbbWebApiGWApp.java index 79af872378..ff3a15b645 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/api2/IBbbWebApiGWApp.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/api2/IBbbWebApiGWApp.java @@ -46,7 +46,8 @@ public interface IBbbWebApiGWApp { ArrayList disabledFeatures, Boolean notifyRecordingIsOn, String presentationUploadExternalDescription, - String presentationUploadExternalUrl); + String presentationUploadExternalUrl, + String overrideClientSettings); void registerUser(String meetingID, String internalUserId, String fullname, String role, String externUserID, String authToken, String sessionToken, String avatarURL, diff --git a/bbb-common-web/src/main/scala/org/bigbluebutton/api2/BbbWebApiGWApp.scala b/bbb-common-web/src/main/scala/org/bigbluebutton/api2/BbbWebApiGWApp.scala index b55d19ccf3..2e5f6b7831 100755 --- a/bbb-common-web/src/main/scala/org/bigbluebutton/api2/BbbWebApiGWApp.scala +++ b/bbb-common-web/src/main/scala/org/bigbluebutton/api2/BbbWebApiGWApp.scala @@ -153,7 +153,8 @@ class BbbWebApiGWApp( disabledFeatures: java.util.ArrayList[String], notifyRecordingIsOn: java.lang.Boolean, presentationUploadExternalDescription: String, - presentationUploadExternalUrl: String): Unit = { + presentationUploadExternalUrl: String, + overrideClientSettings: String): Unit = { val disabledFeaturesAsVector: Vector[String] = disabledFeatures.asScala.toVector @@ -246,7 +247,8 @@ class BbbWebApiGWApp( metadataProp, lockSettingsProps, systemProps, - groupsAsVector + groupsAsVector, + overrideClientSettings ) //meetingManagerActorRef ! new CreateMeetingMsg(defaultProps) diff --git a/bigbluebutton-html5/imports/api/meetings/server/modifiers/addMeeting.js b/bigbluebutton-html5/imports/api/meetings/server/modifiers/addMeeting.js index f9c6f62d39..571f4c0c6f 100755 --- a/bigbluebutton-html5/imports/api/meetings/server/modifiers/addMeeting.js +++ b/bigbluebutton-html5/imports/api/meetings/server/modifiers/addMeeting.js @@ -155,6 +155,7 @@ export default async function addMeeting(meeting) { html5InstanceId: Number, }, groups: Array, + overrideClientSettings: String, }); const { diff --git a/bigbluebutton-web/grails-app/conf/bigbluebutton.properties b/bigbluebutton-web/grails-app/conf/bigbluebutton.properties index 370396db30..0ceeac82b5 100644 --- a/bigbluebutton-web/grails-app/conf/bigbluebutton.properties +++ b/bigbluebutton-web/grails-app/conf/bigbluebutton.properties @@ -348,6 +348,9 @@ allowFetchAllRecordings=true # The directory where the pre-built configs are stored configDir=/var/bigbluebutton/configs +# Disable this option to avoid overriding client settings through /create call +allowOverrideClientSettingsOnCreateCall=true + # The directory to export Json with Meeting activities (used in Learning Dashboard) learningDashboardFilesDir=/var/bigbluebutton/learning-dashboard diff --git a/bigbluebutton-web/grails-app/conf/spring/resources.xml b/bigbluebutton-web/grails-app/conf/spring/resources.xml index 3515291768..977282f8d9 100755 --- a/bigbluebutton-web/grails-app/conf/spring/resources.xml +++ b/bigbluebutton-web/grails-app/conf/spring/resources.xml @@ -197,6 +197,7 @@ with BigBlueButton; if not, see . + 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 ed403d5d34..b39487b454 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 @@ -44,6 +44,7 @@ import org.bigbluebutton.web.services.turn.StunServer import org.bigbluebutton.web.services.turn.RemoteIceCandidate import org.json.JSONArray + import javax.servlet.ServletRequest class ApiController { @@ -63,6 +64,7 @@ class ApiController { ResponseBuilder responseBuilder = initResponseBuilder() ValidationService validationService + def initResponseBuilder = { String protocol = this.getClass().getResource("").getProtocol(); if (Objects.equals(protocol, "jar")) { @@ -150,11 +152,25 @@ class ApiController { Meeting newMeeting = paramsProcessorUtil.processCreateParams(params) + String requestBody = request.inputStream == null ? null : request.inputStream.text + requestBody = StringUtils.isEmpty(requestBody) ? null : requestBody + + def xmlModules = processRequestXmlModules(requestBody) + + // Set Client Settings Override + if(xmlModules.containsKey("clientSettingsOverride")) { + if(paramsProcessorUtil.getAllowOverrideClientSettingsOnCreateCall()) { + newMeeting.setOverrideClientSettings(xmlModules.get("clientSettingsOverride").text()) + } else { + log.warn("Module `clientSettingsOverride` provided but this options is disabled by `allowOverrideClientSettingsOnCreateCall=false` config."); + } + } + ApiErrors errors = new ApiErrors() if (meetingService.createMeeting(newMeeting)) { // See if the request came with pre-uploading of presentation. - uploadDocuments(newMeeting, false); // + uploadDocuments(xmlModules, newMeeting, false); // respondWithConference(newMeeting, null, null) } else { // Translate the external meeting id into an internal meeting id. @@ -1106,8 +1122,12 @@ class ApiController { Meeting meeting = ServiceUtils.findMeetingFromMeetingID(params.meetingID); - if (meeting != null){ - if (uploadDocuments(meeting, true)) { + if (meeting != null) { + String requestBody = request.inputStream == null ? null : request.inputStream.text + requestBody = StringUtils.isEmpty(requestBody) ? null : requestBody + + def xmlModules = processRequestXmlModules(requestBody) + if (uploadDocuments(xmlModules, meeting, true)) { withFormat { xml { render(text: responseBuilder.buildInsertDocumentResponse("Presentation is being uploaded", RESP_CODE_SUCCESS) @@ -1345,7 +1365,7 @@ class ApiController { } } - def uploadDocuments(conf, isFromInsertAPI) { + def uploadDocuments(xmlModules, conf, isFromInsertAPI) { if (conf.getDisabledFeatures().contains("presentation")) { log.warn("Presentation feature is disabled.") return false @@ -1365,8 +1385,6 @@ class ApiController { } Boolean isDefaultPresentationUsed = false; - String requestBody = request.inputStream == null ? null : request.inputStream.text; - requestBody = StringUtils.isEmpty(requestBody) ? null : requestBody; Boolean isDefaultPresentationCurrent = false; def listOfPresentation = [] def presentationListHasCurrent = false @@ -1374,40 +1392,43 @@ class ApiController { // This part of the code is responsible for organize the presentations in a certain order // It selects the one that has the current=true, and put it in the 0th place. // Afterwards, the 0th presentation is going to be uploaded first, which spares processing time - if (requestBody == null) { + if (!xmlModules.containsKey("presentation")) { if (isFromInsertAPI) { log.warn("Insert Document API called without a payload - ignoring") return; } listOfPresentation << [name: "default", current: true]; } else { - def xml = new XmlSlurper().parseText(requestBody); Boolean hasCurrent = false; - xml.children().each { module -> - log.debug("module config found: [${module.@name}]"); - - if ("presentation".equals(module.@name.toString())) { - for (document in module.children()) { - if (!StringUtils.isEmpty(document.@current.toString()) && java.lang.Boolean.parseBoolean( - document.@current.toString()) && !hasCurrent) { - listOfPresentation.add(0, document) - hasCurrent = true; - } else { - listOfPresentation << document - } - } - Boolean uploadDefault = !preUploadedPresentationOverrideDefault && !isDefaultPresentationUsed && !isFromInsertAPI; - if (uploadDefault) { - isDefaultPresentationCurrent = !hasCurrent; - hasCurrent = true - isDefaultPresentationUsed = true - if (isDefaultPresentationCurrent) { - listOfPresentation.add(0, [name: "default", current: true]) - } else { - listOfPresentation << [name: "default", current: false]; - } + Boolean hasPresentationModule = false; + if (xmlModules.containsKey("presentation")) { + def modulePresentation = xmlModules.get("presentation") + hasPresentationModule = true + for (document in modulePresentation.children()) { + if (!StringUtils.isEmpty(document.@current.toString()) && java.lang.Boolean.parseBoolean( + document.@current.toString()) && !hasCurrent) { + listOfPresentation.add(0, document) + hasCurrent = true; + } else { + listOfPresentation << document } } + + Boolean uploadDefault = !preUploadedPresentationOverrideDefault && !isDefaultPresentationUsed && !isFromInsertAPI; + if (uploadDefault) { + isDefaultPresentationCurrent = !hasCurrent; + hasCurrent = true + isDefaultPresentationUsed = true + if (isDefaultPresentationCurrent) { + listOfPresentation.add(0, [name: "default", current: true]) + } else { + listOfPresentation << [name: "default", current: false]; + } + } + } + if (!hasPresentationModule) { + hasCurrent = true + listOfPresentation.add(0, [name: "default", current: true]) } presentationListHasCurrent = hasCurrent; } @@ -1470,6 +1491,20 @@ class ApiController { return true } + def processRequestXmlModules(String requestBody) { + def xmlModules = [:] + + if (requestBody != null && requestBody != "") { + def xml = new XmlSlurper().parseText(requestBody) + xml.children().each { module -> + log.debug("module found: [${module.@name}]") + xmlModules.put(module.@name.toString(), module); + } + } + + return xmlModules + } + def processDocumentFromRawBytes(bytes, presOrigFilename, meetingId, current, isDownloadable, isRemovable, isInitialPresentation) { def uploadFailed = false