diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/ProctoringRoomConnection.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/ProctoringRoomConnection.java index f44447de..403443cc 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/ProctoringRoomConnection.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/ProctoringRoomConnection.java @@ -25,6 +25,8 @@ public class ProctoringRoomConnection { public static final String ATTR_ACCESS_TOKEN = "accessToken"; public static final String ATTR_CONNECTION_URL = "connectionURL"; public static final String ATTR_USER_NAME = "userName"; + public static final String ATTR_ROOM_KEY = "roomKey"; + public static final String ATTR_API_KEY = "apiKey"; @JsonProperty(ProctoringServiceSettings.ATTR_SERVER_TYPE) public final ProctoringServerType proctoringServerType; @@ -45,7 +47,13 @@ public class ProctoringRoomConnection { public final String subject; @JsonProperty(ATTR_ACCESS_TOKEN) - public final String accessToken; + public final CharSequence accessToken; + + @JsonProperty(ATTR_ROOM_KEY) + public final CharSequence roomKey; + + @JsonProperty(ATTR_API_KEY) + public final CharSequence apiKey; @JsonProperty(ATTR_USER_NAME) public final String userName; @@ -58,7 +66,9 @@ public class ProctoringRoomConnection { @JsonProperty(ATTR_SERVER_URL) final String serverURL, @JsonProperty(ATTR_ROOM_NAME) final String roomName, @JsonProperty(ATTR_SUBJECT) final String subject, - @JsonProperty(ATTR_ACCESS_TOKEN) final String accessToken, + @JsonProperty(ATTR_ACCESS_TOKEN) final CharSequence accessToken, + @JsonProperty(ATTR_ROOM_KEY) final CharSequence roomKey, + @JsonProperty(ATTR_API_KEY) final CharSequence apiKey, @JsonProperty(ATTR_USER_NAME) final String userName) { this.proctoringServerType = proctoringServerType; @@ -68,6 +78,8 @@ public class ProctoringRoomConnection { this.roomName = roomName; this.subject = subject; this.accessToken = accessToken; + this.roomKey = roomKey; + this.apiKey = apiKey; this.userName = userName; } @@ -83,10 +95,22 @@ public class ProctoringRoomConnection { return this.serverHost; } - public String getAccessToken() { + public CharSequence getAccessToken() { return this.accessToken; } + public CharSequence getRoomKey() { + return this.roomKey; + } + + public CharSequence getApiKey() { + return this.apiKey; + } + + public String getUserName() { + return this.userName; + } + public String getServerURL() { return this.serverURL; } diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientInstruction.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientInstruction.java index eb1bdf24..3c5aa2b1 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientInstruction.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientInstruction.java @@ -46,16 +46,18 @@ public final class ClientInstruction { public static final String JITSI_TOKEN = "jitsiMeetToken"; public static final String JITSI_RECEIVE_AUDIO = "jitsiMeetReceiveAudio"; public static final String JITSI_RECEIVE_VIDEO = "jitsiMeetReceiveVideo"; - public static final String JITSI_ALLOW_CHAT = "jitsiMeetFeatureFlagChat"; + public static final String JITSI_ALLOW_CHAT = "jitsiFeatureFlagChat"; - public static final String ZOOM_URL = "zoomMeetServerURL"; - public static final String ZOOM_ROOM = "zoomMeetRoom"; - public static final String ZOOM_ROOM_SUBJECT = "zoomMeetSubject"; + public static final String ZOOM_URL = "zoomServerURL"; + public static final String ZOOM_ROOM = "zoomRoom"; + public static final String ZOOM_ROOM_SUBJECT = "zoomSubject"; public static final String ZOOM_USER_NAME = "zoomUserName"; - public static final String ZOOM_TOKEN = "zoomMeetToken"; - public static final String ZOOM_RECEIVE_AUDIO = "zoomMeetReceiveAudio"; - public static final String ZOOM_RECEIVE_VIDEO = "zoomMeetReceiveVideo"; - public static final String ZOOM_ALLOW_CHAT = "zoomMeetFeatureFlagChat"; + public static final String ZOOM_API_KEY = "zoomAPIKey"; + public static final String ZOOM_TOKEN = "zoomToken"; + public static final String ZOOM_MEETING_KEY = "zoomMeetingKey"; + public static final String ZOOM_RECEIVE_AUDIO = "zoomReceiveAudio"; + public static final String ZOOM_RECEIVE_VIDEO = "zoomReceiveVideo"; + public static final String ZOOM_ALLOW_CHAT = "zoomFeatureFlagChat"; } public interface SEB_RECONFIGURE_SETTINGS { @@ -63,9 +65,9 @@ public final class ClientInstruction { public static final String JITSI_RECEIVE_VIDEO = "jitsiMeetReceiveVideo"; public static final String JITSI_ALLOW_CHAT = "jitsiMeetFeatureFlagChat"; - public static final String ZOOM_RECEIVE_AUDIO = "zoomMeetReceiveAudio"; - public static final String ZOOM_RECEIVE_VIDEO = "zoomMeetReceiveVideo"; - public static final String ZOOM_ALLOW_CHAT = "zoomMeetFeatureFlagChat"; + public static final String ZOOM_RECEIVE_AUDIO = "zoomReceiveAudio"; + public static final String ZOOM_RECEIVE_VIDEO = "zoomReceiveVideo"; + public static final String ZOOM_ALLOW_CHAT = "zoomFeatureFlagChat"; } } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/ProctoringServlet.java b/src/main/java/ch/ethz/seb/sebserver/gui/ProctoringServlet.java index 5a3b1944..08acad15 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/ProctoringServlet.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/ProctoringServlet.java @@ -9,6 +9,7 @@ package ch.ethz.seb.sebserver.gui; import java.io.IOException; +import java.util.Collection; import javax.servlet.ServletContext; import javax.servlet.ServletException; @@ -17,6 +18,8 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; import org.springframework.web.context.WebApplicationContext; @@ -25,62 +28,22 @@ import org.springframework.web.context.support.WebApplicationContextUtils; import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.AuthorizationContextHolder; import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.SEBServerAuthorizationContext; -import ch.ethz.seb.sebserver.gui.service.session.ProctoringGUIService; -import ch.ethz.seb.sebserver.gui.service.session.ProctoringGUIService.ProctoringWindowData; +import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService; +import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService.ProctoringWindowData; +import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringWindowScriptResolver; @Component @GuiProfile public class ProctoringServlet extends HttpServlet { private static final long serialVersionUID = 3475978419653411800L; + private static final Logger log = LoggerFactory.getLogger(ProctoringServlet.class); - // @formatter:off - private static final String JITSI_WINDOW_HTML = - "" + - "" + - "" + - " " + - " " + - "" + - "" + - "" + - "
" + - "" + - "" + - ""; - // @formatter:on + private final Collection proctoringWindowScriptResolver; + + public ProctoringServlet(final Collection proctoringWindowScriptResolver) { + this.proctoringWindowScriptResolver = proctoringWindowScriptResolver; + } @Override protected void doGet( @@ -103,21 +66,17 @@ public class ProctoringServlet extends HttpServlet { (ProctoringWindowData) httpSession .getAttribute(ProctoringGUIService.SESSION_ATTR_PROCTORING_DATA); - switch (proctoringData.connectionData.proctoringServerType) { - case JITSI_MEET: { - final String script = String.format( - JITSI_WINDOW_HTML, - proctoringData.connectionData.serverHost, - proctoringData.connectionData.roomName, - proctoringData.connectionData.accessToken, - proctoringData.connectionData.serverHost, - proctoringData.connectionData.subject); - resp.getOutputStream().println(script); - break; - } - default: - throw new RuntimeException( - "Unsupported proctoring server type: " + proctoringData.connectionData.proctoringServerType); + final String script = this.proctoringWindowScriptResolver.stream() + .filter(resolver -> resolver.applies(proctoringData)) + .findFirst() + .map(resolver -> resolver.getProctoringWindowScript(proctoringData)) + .orElse(null); + + if (script == null) { + log.error("Failed to get proctoring window script for data: {}", proctoringData); + resp.getOutputStream().println("Failed to get proctoring window script"); + } else { + resp.getOutputStream().println(script); } } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringClientConnection.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringClientConnection.java index 5a38f437..45b1eabd 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringClientConnection.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringClientConnection.java @@ -67,7 +67,7 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetProcto import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser; import ch.ethz.seb.sebserver.gui.service.session.ClientConnectionDetails; import ch.ethz.seb.sebserver.gui.service.session.InstructionProcessor; -import ch.ethz.seb.sebserver.gui.service.session.ProctoringGUIService; +import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService; import ch.ethz.seb.sebserver.gui.table.ColumnDefinition; import ch.ethz.seb.sebserver.gui.table.ColumnDefinition.TableFilterAttribute; import ch.ethz.seb.sebserver.gui.table.EntityTable; diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringRunningExam.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringRunningExam.java index 35e06ee4..1606456b 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringRunningExam.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringRunningExam.java @@ -81,7 +81,7 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetTownha import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser; import ch.ethz.seb.sebserver.gui.service.session.ClientConnectionTable; import ch.ethz.seb.sebserver.gui.service.session.InstructionProcessor; -import ch.ethz.seb.sebserver.gui.service.session.ProctoringGUIService; +import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService; import ch.ethz.seb.sebserver.gui.widget.Message; @Lazy diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/ComposerServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/ComposerServiceImpl.java index 7e4af6a3..6e021cb7 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/ComposerServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/ComposerServiceImpl.java @@ -33,8 +33,8 @@ import ch.ethz.seb.sebserver.gui.service.page.RemoteProctoringView; import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer; import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.AuthorizationContextHolder; import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.IllegalUserSessionStateException; -import ch.ethz.seb.sebserver.gui.service.session.ProctoringGUIService; -import ch.ethz.seb.sebserver.gui.service.session.ProctoringGUIService.ProctoringWindowData; +import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService; +import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService.ProctoringWindowData; import ch.ethz.seb.sebserver.gui.widget.Message; @Lazy diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/JitsiMeetProctoringView.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/JitsiMeetProctoringView.java index 9707eb5e..86b37772 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/JitsiMeetProctoringView.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/JitsiMeetProctoringView.java @@ -33,8 +33,8 @@ import ch.ethz.seb.sebserver.gui.service.page.PageContext; import ch.ethz.seb.sebserver.gui.service.page.PageService; import ch.ethz.seb.sebserver.gui.service.page.RemoteProctoringView; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.SendProctoringReconfigurationAttributes; -import ch.ethz.seb.sebserver.gui.service.session.ProctoringGUIService; -import ch.ethz.seb.sebserver.gui.service.session.ProctoringGUIService.ProctoringWindowData; +import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService; +import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService.ProctoringWindowData; import ch.ethz.seb.sebserver.gui.widget.WidgetFactory; @Component diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/push/ServerPushService.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/push/ServerPushService.java index 8fb7269b..291a8acb 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/push/ServerPushService.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/push/ServerPushService.java @@ -90,8 +90,9 @@ public class ServerPushService { log.warn( "Failed to stop Server Push Session on: {}. " + "It seems that the UISession is not available anymore. " - + "This may source from a connection interruption", - Thread.currentThread().getName(), e); + + "This may source from a connection interruption. Cause: {}", + Thread.currentThread().getName(), + e.getMessage()); } }); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/auth/CurrentUser.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/auth/CurrentUser.java index d05cdc48..cb0d8dc3 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/auth/CurrentUser.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/remote/webservice/auth/CurrentUser.java @@ -35,7 +35,7 @@ import ch.ethz.seb.sebserver.gbl.model.user.UserInfo; import ch.ethz.seb.sebserver.gbl.model.user.UserRole; import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService; -import ch.ethz.seb.sebserver.gui.service.session.ProctoringGUIService; +import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService; @Component @GuiProfile diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/proctoring/JitsiWindowScriptResolver.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/proctoring/JitsiWindowScriptResolver.java new file mode 100644 index 00000000..a1baea70 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/proctoring/JitsiWindowScriptResolver.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2021 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package ch.ethz.seb.sebserver.gui.service.session.proctoring; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.text.StringSubstitutor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; + +import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings.ProctoringServerType; +import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; +import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService.ProctoringWindowData; + +@Lazy +@Service +@GuiProfile +public class JitsiWindowScriptResolver implements ProctoringWindowScriptResolver { + + private static final Logger log = LoggerFactory.getLogger(JitsiWindowScriptResolver.class); + + private static final String ATTR_SUBJECT = "_subject_"; + private static final String ATTR_ACCESS_TOKEN = "_accessToken_"; + private static final String ATTR_ROOM_NAME = "_roomName_"; + private static final String ATTR_HOST = "_host_"; + + // @formatter:off + private static final String JITSI_WINDOW_HTML = + "" + + "" + + "" + + " " + + " " + + "" + + "" + + "" + + "
" + + "" + + "" + + ""; + // @formatter:on + + @Override + public boolean applies(final ProctoringWindowData data) { + try { + return data.connectionData.proctoringServerType == ProctoringServerType.JITSI_MEET; + } catch (final Exception e) { + log.error("Failed to verify responsibility. Cause: {}", e.getMessage()); + return false; + } + } + + @Override + public String getProctoringWindowScript(final ProctoringWindowData data) { + final Map args = new HashMap<>(); + args.put(ATTR_HOST, data.connectionData.serverHost); + args.put(ATTR_ROOM_NAME, data.connectionData.roomName); + args.put(ATTR_ACCESS_TOKEN, String.valueOf(data.connectionData.accessToken)); + args.put(ATTR_SUBJECT, data.connectionData.subject); + + return new StringSubstitutor(args, "%%_", "_%%") + .replace(JITSI_WINDOW_HTML); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ProctoringGUIService.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/proctoring/ProctoringGUIService.java similarity index 96% rename from src/main/java/ch/ethz/seb/sebserver/gui/service/session/ProctoringGUIService.java rename to src/main/java/ch/ethz/seb/sebserver/gui/service/session/proctoring/ProctoringGUIService.java index 269f6424..30ecce8c 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ProctoringGUIService.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/proctoring/ProctoringGUIService.java @@ -6,7 +6,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -package ch.ethz.seb.sebserver.gui.service.session; +package ch.ethz.seb.sebserver.gui.service.session.proctoring; import java.util.Collection; import java.util.HashMap; diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/proctoring/ProctoringWindowScriptResolver.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/proctoring/ProctoringWindowScriptResolver.java new file mode 100644 index 00000000..84332244 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/proctoring/ProctoringWindowScriptResolver.java @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2021 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package ch.ethz.seb.sebserver.gui.service.session.proctoring; + +import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService.ProctoringWindowData; + +public interface ProctoringWindowScriptResolver { + + boolean applies(ProctoringWindowData data); + + String getProctoringWindowScript(ProctoringWindowData data); + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/proctoring/ZoomWindowScriptResolver.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/proctoring/ZoomWindowScriptResolver.java new file mode 100644 index 00000000..57cfa9b5 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/proctoring/ZoomWindowScriptResolver.java @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2021 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package ch.ethz.seb.sebserver.gui.service.session.proctoring; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.text.StringSubstitutor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; + +import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings.ProctoringServerType; +import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; +import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService.ProctoringWindowData; + +@Lazy +@Service +@GuiProfile +public class ZoomWindowScriptResolver implements ProctoringWindowScriptResolver { + + private static final Logger log = LoggerFactory.getLogger(ZoomWindowScriptResolver.class); + + private static final String ATTR_SUBJECT = "_subject_"; + private static final String ATTR_API_KEY = "_apiKey_"; + private static final String ATTR_ACCESS_TOKEN = "_accessToken_"; + private static final String ATTR_ROOM_KEY = "_roomKey_"; + private static final String ATTR_ROOM_NAME = "_roomName_"; + private static final String ATTR_HOST = "_host_"; + private static final String ATTR_USER_NAME = "_username_"; + + // @formatter:off + private static final String ZOOM_WINDOW_HTML = + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + ""; + // @formatter:on + + @Override + public boolean applies(final ProctoringWindowData data) { + try { + return data.connectionData.proctoringServerType == ProctoringServerType.ZOOM; + } catch (final Exception e) { + log.error("Failed to verify responsibility. Cause: {}", e.getMessage()); + return false; + } + } + + @Override + public String getProctoringWindowScript(final ProctoringWindowData data) { + final Map args = new HashMap<>(); + args.put(ATTR_HOST, data.connectionData.serverHost); + args.put(ATTR_ROOM_NAME, data.connectionData.roomName); + args.put(ATTR_ACCESS_TOKEN, String.valueOf(data.connectionData.accessToken)); + args.put(ATTR_API_KEY, String.valueOf(data.connectionData.apiKey)); + if (StringUtils.isNotBlank(data.connectionData.roomKey)) { + args.put(ATTR_ROOM_KEY, String.valueOf(data.connectionData.roomKey)); + } else { + args.put(ATTR_ROOM_KEY, ""); + } + args.put(ATTR_SUBJECT, data.connectionData.subject); + args.put(ATTR_USER_NAME, data.connectionData.userName); + + return new StringSubstitutor(args, "%%_", "_%%") + .replace(ZOOM_WINDOW_HTML); + } + +} diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/RemoteProctoringRoomDAO.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/RemoteProctoringRoomDAO.java index 82e3f63e..d1d936f0 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/RemoteProctoringRoomDAO.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/RemoteProctoringRoomDAO.java @@ -16,35 +16,99 @@ import ch.ethz.seb.sebserver.gbl.model.session.RemoteProctoringRoom; import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.proctoring.NewRoom; +/** Data access for RemoteProctoringRoom domain objects. */ public interface RemoteProctoringRoomDAO { - Result> getCollectingRoomsForExam(Long examId); + /** Get all collecting room records that exists for a given exam. + * + * @param examId the exam identifier + * @return Result refer to the collection of all collecting room records for the given exam or to an error when + * happened */ + Result> getCollectingRooms(Long examId); - //Result> getRoomsOfExam(Long examId); + /** Get all room records that exists for a given exam. + * + * @param examId the exam identifier + * @return Result refer to the collection of all room records for the given exam or to an error when + * happened */ + Result> getRooms(Long examId); + /** The the room record with given identifier (PK). + * + * @param roomId the room record identifier + * @return Result refer to the room record or to an error when happened */ Result getRoom(Long roomId); + /** Get the room record with specified name for a given exam. + * + * @param examId the exam identifier + * @param roomName the name of the room + * @return Result refer to the room record or to an error when happened */ Result getRoom(Long examId, String roomName); + /** Get the room name for the room with given identifier. + * + * @param roomId the room record identifier (PK) + * @return Result refer to the rooms name or to an error when happened */ Result getRoomName(Long roomId); + /** Create the town hall room for a given exam. Uses the given room data to create the record + * + * @param examId the exam identifier + * @param room the room data to save to record + * @return Result refer to the created room record or to an error when happened */ Result createTownhallRoom(Long examId, NewRoom room); + /** Get the town hall room record for a given exam if existing. + * + * @param examId the exam identifier + * @return Result refer to the town-hall room record or to an error when happened. */ Result getTownhallRoom(Long examId); + /** Delete the town-hall room record for a given exam. + * + * @param examId the exam identifier + * @return Result refer to the entity key of the former town-hall room record or to an error when happened */ Result deleteTownhallRoom(Long examId); + /** Create a break-out room for a given exam. Uses the given room data to create the record + * + * @param examId the exam identifier + * @param room the room data to save to record + * @param connectionTokens comma separated list of SEB client connection tokens that joins the new break-out room + * @return Result refer to the created break-out room record or to an error when happened */ Result createBreakOutRoom(Long examId, NewRoom room, String connectionTokens); + /** Delete the room record with given id. + * + * @param roomId the room identifier (PK) + * @return Result refer to the entity key of the former room record or to an error when happened */ Result deleteRoom(Long roomId); + /** Delete all room records for a given exam. + * + * @param examId the exam identifier + * @return Result refer to a collection of entity keys for all delete room records or to an error when happened */ Result> deleteRooms(Long examId); + /** This reserves a place in a collecting room on a given exam. + * Creates a new collecting room record depending on the roomMaxSize and the how many connection + * already have been collected within the actual collecting room. + * + * @param examId the exam identifier + * @param roomMaxSize the maximum size of connection collected in one collecting room + * @param newRoomFunction Function to create data for a new collecting room if needed. + * @return Result refer to the collecting room record of place or to an error when happened */ Result reservePlaceInCollectingRoom( Long examId, int roomMaxSize, Function> newRoomFunction); + /** Releases a place in the actual collecting room for a given exam. + * + * @param examId the exam identifier + * @param roomId the room record identifier (PK) + * @return Result refer to the actual collecting room record or to an error when happened */ Result releasePlaceInCollectingRoom(final Long examId, Long roomId); } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/RemoteProctoringRoomDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/RemoteProctoringRoomDAOImpl.java index 68f64112..41840c6a 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/RemoteProctoringRoomDAOImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/RemoteProctoringRoomDAOImpl.java @@ -54,7 +54,7 @@ public class RemoteProctoringRoomDAOImpl implements RemoteProctoringRoomDAO { @Override @Transactional(readOnly = true) - public Result> getCollectingRoomsForExam(final Long examId) { + public Result> getCollectingRooms(final Long examId) { return Result.tryCatch(() -> this.remoteProctoringRoomRecordMapper.selectByExample() .where(RemoteProctoringRoomRecordDynamicSqlSupport.examId, isEqualTo(examId)) .and(RemoteProctoringRoomRecordDynamicSqlSupport.townhallRoom, isEqualTo(0)) @@ -66,6 +66,18 @@ public class RemoteProctoringRoomDAOImpl implements RemoteProctoringRoomDAO { .collect(Collectors.toList())); } + @Override + @Transactional(readOnly = true) + public Result> getRooms(final Long examId) { + return Result.tryCatch(() -> this.remoteProctoringRoomRecordMapper.selectByExample() + .where(RemoteProctoringRoomRecordDynamicSqlSupport.examId, isEqualTo(examId)) + .build() + .execute() + .stream() + .map(this::toDomainModel) + .collect(Collectors.toList())); + } + @Override @Transactional(readOnly = true) public Result getRoom(final Long roomId) { @@ -75,6 +87,7 @@ public class RemoteProctoringRoomDAOImpl implements RemoteProctoringRoomDAO { } @Override + @Transactional(readOnly = true) public Result getRoom(final Long examId, final String roomName) { return Result.tryCatch(() -> { return this.remoteProctoringRoomRecordMapper.selectByExample() diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/LmsAPIServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/LmsAPIServiceImpl.java index a4a2672f..1553f9fa 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/LmsAPIServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/lms/impl/LmsAPIServiceImpl.java @@ -81,7 +81,9 @@ public class LmsAPIServiceImpl implements LmsAPIService { return; } - log.debug("LmsSetup changed. Update cache by removing eventually used references"); + if (log.isDebugEnabled()) { + log.debug("LmsSetup changed. Update cache by removing eventually used references"); + } this.cache.remove(new CacheKey(lmsSetup.getModelId(), 0)); } @@ -105,11 +107,6 @@ public class LmsAPIServiceImpl implements LmsAPIService { @Override public Result getLmsAPITemplate(final String lmsSetupId) { - - if (log.isDebugEnabled()) { - log.debug("Get LmsAPITemplate for id: {}", lmsSetupId); - } - return Result.tryCatch(() -> this.lmsSetupDAO .byModelId(lmsSetupId) .getOrThrow()) @@ -214,7 +211,6 @@ public class LmsAPIServiceImpl implements LmsAPIService { .forEach(this.cache::remove); // get from cache return this.cache.get(new CacheKey(lmsSetup.getModelId(), 0)); - } private LmsAPITemplate createLmsSetupTemplate(final LmsSetup lmsSetup) { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamProctoringService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamProctoringService.java index b2192a1e..a96b86b9 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamProctoringService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamProctoringService.java @@ -30,7 +30,7 @@ public interface ExamProctoringService { * * @param proctoringSettings the settings to test * @return Result refer to true if the settings are correct and the proctoring server can be accessed. */ - Result testExamProctoring(final ProctoringServiceSettings proctoringSettings); + Result testExamProctoring(ProctoringServiceSettings proctoringSettings); /** Gets the room connection data for a certain room for the proctor. * @@ -45,21 +45,14 @@ public interface ExamProctoringService { String subject); Result getClientRoomConnection( - final ProctoringServiceSettings proctoringSettings, - final String connectionToken, - final String roomName, - final String subject); + ProctoringServiceSettings proctoringSettings, + String connectionToken, + String roomName, + String subject); -// Result getClientCollectingRoomConnection( -// final ProctoringServiceSettings proctoringSettings, -// final String connectionToken, -// final String roomName, -// final String subject); + Map createJoinInstructionAttributes(ProctoringRoomConnection proctoringConnection); - Map createJoinInstructionAttributes( - final ProctoringRoomConnection proctoringConnection); - - Result disposeServiceRoomsForExam(Exam exam); + Result disposeServiceRoomsForExam(ProctoringServiceSettings proctoringSettings, Exam exam); default String verifyRoomName(final String requestedRoomName, final String connectionToken) { if (StringUtils.isNotBlank(requestedRoomName)) { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ExamProctoringRoomServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ExamProctoringRoomServiceImpl.java index cb7a0f66..ce395bec 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ExamProctoringRoomServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ExamProctoringRoomServiceImpl.java @@ -70,7 +70,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService @Override public Result> getProctoringCollectingRooms(final Long examId) { - return this.remoteProctoringRoomDAO.getCollectingRoomsForExam(examId); + return this.remoteProctoringRoomDAO.getCollectingRooms(examId); } @Override @@ -115,9 +115,14 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService return Result.tryCatch(() -> { + final ProctoringServiceSettings settings = this.examSessionService + .getRunningExam(exam.id) + .flatMap(this.examAdminService::getProctoringServiceSettings) + .getOrThrow(); + this.examAdminService .getExamProctoringService(exam) - .flatMap(service -> service.disposeServiceRoomsForExam(exam)) + .flatMap(service -> service.disposeServiceRoomsForExam(settings, exam)) .onError(error -> log.error("Failed to dispose proctoring service rooms for exam: {} / {}", exam.name, exam.externalId, diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/JitsiProctoringService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/JitsiProctoringService.java index de6e9e65..9b4bae6f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/JitsiProctoringService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/JitsiProctoringService.java @@ -164,7 +164,10 @@ public class JitsiProctoringService implements ExamProctoringService { } @Override - public Result disposeServiceRoomsForExam(final Exam exam) { + public Result disposeServiceRoomsForExam( + final ProctoringServiceSettings proctoringSettings, + final Exam exam) { + // NOTE: Since Jitsi rooms are generated and disposed automatically we don't need to do anything here return Result.EMPTY; } @@ -234,7 +237,7 @@ public class JitsiProctoringService implements ExamProctoringService { } attributes.put( ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.JITSI_TOKEN, - proctoringConnection.accessToken); + String.valueOf(proctoringConnection.accessToken)); return attributes; } @@ -324,6 +327,8 @@ public class JitsiProctoringService implements ExamProctoringService { roomName, subject, token, + null, + null, clientName); }); } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ZoomProctoringService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ZoomProctoringService.java index 32aa5981..2e2f408c 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ZoomProctoringService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ZoomProctoringService.java @@ -204,17 +204,27 @@ public class ZoomProctoringService implements ExamProctoringService { attributes.put( ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.ZOOM_ROOM, proctoringConnection.roomName); + attributes.put( + ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.ZOOM_TOKEN, + String.valueOf(proctoringConnection.accessToken)); + if (StringUtils.isNotBlank(proctoringConnection.apiKey)) { + attributes.put( + ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.ZOOM_API_KEY, + String.valueOf(proctoringConnection.apiKey)); + } + if (StringUtils.isNotBlank(proctoringConnection.roomKey)) { + attributes.put( + ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.ZOOM_MEETING_KEY, + String.valueOf(proctoringConnection.roomKey)); + } + attributes.put( + ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.ZOOM_USER_NAME, + proctoringConnection.userName); if (StringUtils.isNotBlank(proctoringConnection.subject)) { attributes.put( ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.ZOOM_ROOM_SUBJECT, proctoringConnection.subject); } - attributes.put( - ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.ZOOM_TOKEN, - proctoringConnection.accessToken); - attributes.put( - ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.ZOOM_USER_NAME, - proctoringConnection.userName); return attributes; } @@ -250,6 +260,8 @@ public class ZoomProctoringService implements ExamProctoringService { roomName, subject, jwt, + credentials.accessToken, + credentials.clientId, this.authorizationService.getUserService().getCurrentUser().getUsername()); }); } @@ -290,18 +302,29 @@ public class ZoomProctoringService implements ExamProctoringService { roomName, subject, jwt, + credentials.accessToken, + credentials.clientId, clientConnection.clientConnection.userSessionId); }); } @Override - public Result disposeServiceRoomsForExam(final Exam exam) { + public Result disposeServiceRoomsForExam( + final ProctoringServiceSettings proctoringSettings, + final Exam exam) { return Result.tryCatch(() -> { - //this.remoteProctoringRoomDAO.getRoomsOfExam(exam.id); + this.remoteProctoringRoomDAO + .getRooms(exam.id) + .getOrThrow() + .stream() + .forEach(room -> { + disposeBreakOutRoom(proctoringSettings, room.name) + .onError(error -> log.warn("Failed to dispose proctoring room record for: {} cause: {}", + room, + error.getMessage())); + }); }); - // Get all rooms of the exam - } @Override @@ -492,7 +515,7 @@ public class ZoomProctoringService implements ExamProctoringService { final String jwtHeaderPart = urlEncoder.encodeToString( ZOOM_ACCESS_TOKEN_HEADER.getBytes(StandardCharsets.UTF_8)); final String jwtPayload = String.format( - ZOOM_API_ACCESS_TOKEN_PAYLOAD.replaceAll(" ", "").replaceAll("\n", ""), + ZOOM_MEETING_ACCESS_TOKEN_PAYLOAD.replaceAll(" ", "").replaceAll("\n", ""), credentials.clientIdAsString(), iat, exp,