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 403443cc..264858ef 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 @@ -27,6 +27,7 @@ public class ProctoringRoomConnection { public static final String ATTR_USER_NAME = "userName"; public static final String ATTR_ROOM_KEY = "roomKey"; public static final String ATTR_API_KEY = "apiKey"; + public static final String ATTR_MEETING_ID = "meetingId"; @JsonProperty(ProctoringServiceSettings.ATTR_SERVER_TYPE) public final ProctoringServerType proctoringServerType; @@ -55,6 +56,9 @@ public class ProctoringRoomConnection { @JsonProperty(ATTR_API_KEY) public final CharSequence apiKey; + @JsonProperty(ATTR_MEETING_ID) + public final String meetingId; + @JsonProperty(ATTR_USER_NAME) public final String userName; @@ -69,6 +73,7 @@ public class ProctoringRoomConnection { @JsonProperty(ATTR_ACCESS_TOKEN) final CharSequence accessToken, @JsonProperty(ATTR_ROOM_KEY) final CharSequence roomKey, @JsonProperty(ATTR_API_KEY) final CharSequence apiKey, + @JsonProperty(ATTR_MEETING_ID) final String meetingId, @JsonProperty(ATTR_USER_NAME) final String userName) { this.proctoringServerType = proctoringServerType; @@ -80,6 +85,7 @@ public class ProctoringRoomConnection { this.accessToken = accessToken; this.roomKey = roomKey; this.apiKey = apiKey; + this.meetingId = meetingId; this.userName = userName; } @@ -123,6 +129,10 @@ public class ProctoringRoomConnection { return this.subject; } + public String getMeetingId() { + return this.meetingId; + } + @Override public String toString() { final StringBuilder builder = new StringBuilder(); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/RemoteProctoringView.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/RemoteProctoringView.java index a8fc4f3f..2c1be80f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/RemoteProctoringView.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/RemoteProctoringView.java @@ -16,4 +16,5 @@ public interface RemoteProctoringView extends TemplateComposer { * * @return the remote proctoring server type this remote proctoring view can handle. */ ProctoringServerType serverType(); + } 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 86b37772..4680f122 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 @@ -75,6 +75,11 @@ public class JitsiMeetProctoringView implements RemoteProctoringView { this.remoteProctoringViewServletEndpoint = remoteProctoringViewServletEndpoint; } + @Override + public ProctoringServerType serverType() { + return ProctoringServerType.JITSI_MEET; + } + @Override public void compose(final PageContext pageContext) { @@ -227,18 +232,6 @@ public class JitsiMeetProctoringView implements RemoteProctoringView { sendReconfigurationAttributes(examId, roomName, state); } - @Override - public ProctoringServerType serverType() { - return ProctoringServerType.JITSI_MEET; - } - - private static final class BroadcastActionState { - public static final String KEY_NAME = "BroadcastActionState"; - boolean audio = false; - boolean video = false; - boolean chat = false; - } - private void closeRoom(final ProctoringWindowData proctoringWindowData) { this.pageService .getCurrentUser() @@ -246,4 +239,11 @@ public class JitsiMeetProctoringView implements RemoteProctoringView { .closeRoomWindow(proctoringWindowData.windowName); } + static final class BroadcastActionState { + public static final String KEY_NAME = "BroadcastActionState"; + boolean audio = false; + boolean video = false; + boolean chat = false; + } + } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/ZoomProctoringView.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/ZoomProctoringView.java new file mode 100644 index 00000000..6f27c4b4 --- /dev/null +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/ZoomProctoringView.java @@ -0,0 +1,249 @@ +/* + * 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.page.impl; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.browser.Browser; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.layout.RowData; +import org.eclipse.swt.layout.RowLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import ch.ethz.seb.sebserver.gbl.Constants; +import ch.ethz.seb.sebserver.gbl.api.API; +import ch.ethz.seb.sebserver.gbl.model.Domain; +import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings.ProctoringServerType; +import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; +import ch.ethz.seb.sebserver.gui.GuiServiceInfo; +import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; +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.proctoring.ProctoringGUIService; +import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService.ProctoringWindowData; +import ch.ethz.seb.sebserver.gui.widget.WidgetFactory; + +@Component +@GuiProfile +public class ZoomProctoringView implements RemoteProctoringView { + + private static final Logger log = LoggerFactory.getLogger(ZoomProctoringView.class); + + private static final LocTextKey CLOSE_WINDOW_TEXT_KEY = + new LocTextKey("sebserver.monitoring.exam.proctoring.action.close"); + private static final LocTextKey BROADCAST_AUDIO_ON_TEXT_KEY = + new LocTextKey("sebserver.monitoring.exam.proctoring.action.broadcaston.audio"); + private static final LocTextKey BROADCAST_AUDIO_OFF_TEXT_KEY = + new LocTextKey("sebserver.monitoring.exam.proctoring.action.broadcastoff.audio"); + private static final LocTextKey BROADCAST_VIDEO_ON_TEXT_KEY = + new LocTextKey("sebserver.monitoring.exam.proctoring.action.broadcaston.video"); + private static final LocTextKey BROADCAST_VIDEO_OFF_TEXT_KEY = + new LocTextKey("sebserver.monitoring.exam.proctoring.action.broadcastoff.video"); + private static final LocTextKey CHAT_ON_TEXT_KEY = + new LocTextKey("sebserver.monitoring.exam.proctoring.action.broadcaston.chat"); + private static final LocTextKey CHAT_OFF_TEXT_KEY = + new LocTextKey("sebserver.monitoring.exam.proctoring.action.broadcastoff.chat"); + + private final PageService pageService; + private final GuiServiceInfo guiServiceInfo; + private final String remoteProctoringEndpoint; + private final String remoteProctoringViewServletEndpoint; + + public ZoomProctoringView( + final PageService pageService, + final GuiServiceInfo guiServiceInfo, + @Value("${sebserver.gui.remote.proctoring.entrypoint:/remote-proctoring}") final String remoteProctoringEndpoint, + @Value("${sebserver.gui.remote.proctoring.api-servler.endpoint:/remote-view-servlet}") final String remoteProctoringViewServletEndpoint) { + + this.pageService = pageService; + this.guiServiceInfo = guiServiceInfo; + this.remoteProctoringEndpoint = remoteProctoringEndpoint; + this.remoteProctoringViewServletEndpoint = remoteProctoringViewServletEndpoint; + } + + @Override + public ProctoringServerType serverType() { + return ProctoringServerType.ZOOM; + } + + @Override + public void compose(final PageContext pageContext) { + final ProctoringWindowData proctoringWindowData = ProctoringGUIService.getCurrentProctoringWindowData(); + + final Composite parent = pageContext.getParent(); + + final Composite content = new Composite(parent, SWT.NONE | SWT.NO_SCROLL); + final GridLayout gridLayout = new GridLayout(); + + content.setLayout(gridLayout); + final GridData headerCell = new GridData(SWT.FILL, SWT.FILL, true, true); + content.setLayoutData(headerCell); + + parent.addListener(SWT.Dispose, event -> closeRoom(proctoringWindowData)); + + final String url = this.guiServiceInfo + .getExternalServerURIBuilder() + .toUriString() + + this.remoteProctoringEndpoint + + this.remoteProctoringViewServletEndpoint + + Constants.SLASH; + + if (log.isDebugEnabled()) { + log.debug("Open proctoring Servlet in IFrame with URL: {}", url); + } + + final Browser browser = new Browser(content, SWT.NONE | SWT.NO_SCROLL); + browser.setLayout(new GridLayout()); + final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true); + browser.setLayoutData(gridData); + browser.setUrl(url); + browser.setBackground(new Color(parent.getDisplay(), 100, 100, 100)); + + final Composite footer = new Composite(content, SWT.NONE | SWT.NO_SCROLL); + footer.setLayout(new RowLayout()); + final GridData footerLayout = new GridData(SWT.CENTER, SWT.BOTTOM, true, false); + footerLayout.heightHint = 40; + footer.setLayoutData(footerLayout); + + final WidgetFactory widgetFactory = this.pageService.getWidgetFactory(); + + final Button closeAction = widgetFactory.buttonLocalized(footer, CLOSE_WINDOW_TEXT_KEY); + closeAction.setLayoutData(new RowData(150, 30)); + closeAction.addListener(SWT.Selection, event -> closeRoom(proctoringWindowData)); + + final BroadcastActionState broadcastActionState = new BroadcastActionState(); + + final Button broadcastAudioAction = widgetFactory.buttonLocalized(footer, BROADCAST_AUDIO_ON_TEXT_KEY); + broadcastAudioAction.setLayoutData(new RowData(150, 30)); + broadcastAudioAction.addListener(SWT.Selection, event -> toggleBroadcastAudio( + proctoringWindowData.examId, + proctoringWindowData.connectionData.roomName, + broadcastAudioAction)); + broadcastAudioAction.setData(BroadcastActionState.KEY_NAME, broadcastActionState); + + final Button broadcastVideoAction = widgetFactory.buttonLocalized(footer, BROADCAST_VIDEO_ON_TEXT_KEY); + broadcastVideoAction.setLayoutData(new RowData(150, 30)); + broadcastVideoAction.addListener(SWT.Selection, event -> toggleBroadcastVideo( + proctoringWindowData.examId, + proctoringWindowData.connectionData.roomName, + broadcastVideoAction, + broadcastAudioAction)); + broadcastVideoAction.setData(BroadcastActionState.KEY_NAME, broadcastActionState); + + final Button chatAction = widgetFactory.buttonLocalized(footer, CHAT_ON_TEXT_KEY); + chatAction.setLayoutData(new RowData(150, 30)); + chatAction.addListener(SWT.Selection, event -> toggleChat( + proctoringWindowData.examId, + proctoringWindowData.connectionData.roomName, + chatAction)); + chatAction.setData(BroadcastActionState.KEY_NAME, broadcastActionState); + + } + + private void sendReconfigurationAttributes( + final String examId, + final String roomName, + final BroadcastActionState state) { + + this.pageService.getRestService().getBuilder(SendProctoringReconfigurationAttributes.class) + .withURIVariable(API.PARAM_MODEL_ID, examId) + .withFormParam(Domain.REMOTE_PROCTORING_ROOM.ATTR_ID, roomName) + .withFormParam( + + API.EXAM_PROCTORING_ATTR_RECEIVE_AUDIO, + state.audio ? Constants.TRUE_STRING : Constants.FALSE_STRING) + .withFormParam( + API.EXAM_PROCTORING_ATTR_RECEIVE_VIDEO, + state.video ? Constants.TRUE_STRING : Constants.FALSE_STRING) + .withFormParam( + API.EXAM_PROCTORING_ATTR_ALLOW_CHAT, + state.chat ? Constants.TRUE_STRING : Constants.FALSE_STRING) + .call() + .onError(error -> log.error("Failed to send broadcast attributes to clients in room: {} cause: {}", + roomName, + error.getMessage())); + + } + + private void toggleBroadcastAudio( + final String examId, + final String roomName, + final Button broadcastAction) { + + final BroadcastActionState state = + (BroadcastActionState) broadcastAction.getData(BroadcastActionState.KEY_NAME); + + this.pageService.getPolyglotPageService().injectI18n( + broadcastAction, + state.audio ? BROADCAST_AUDIO_ON_TEXT_KEY : BROADCAST_AUDIO_OFF_TEXT_KEY); + + state.audio = !state.audio; + sendReconfigurationAttributes(examId, roomName, state); + } + + private void toggleBroadcastVideo( + final String examId, + final String roomName, + final Button videoAction, + final Button audioAction) { + + final BroadcastActionState state = + (BroadcastActionState) videoAction.getData(BroadcastActionState.KEY_NAME); + + this.pageService.getPolyglotPageService().injectI18n( + audioAction, + state.video ? BROADCAST_AUDIO_ON_TEXT_KEY : BROADCAST_AUDIO_OFF_TEXT_KEY); + this.pageService.getPolyglotPageService().injectI18n( + videoAction, + state.video ? BROADCAST_VIDEO_ON_TEXT_KEY : BROADCAST_VIDEO_OFF_TEXT_KEY); + + state.video = !state.video; + state.audio = state.video; + sendReconfigurationAttributes(examId, roomName, state); + } + + private void toggleChat( + final String examId, + final String roomName, + final Button broadcastAction) { + + final BroadcastActionState state = + (BroadcastActionState) broadcastAction.getData(BroadcastActionState.KEY_NAME); + + this.pageService.getPolyglotPageService().injectI18n( + broadcastAction, + state.chat ? CHAT_ON_TEXT_KEY : CHAT_OFF_TEXT_KEY); + + state.chat = !state.chat; + sendReconfigurationAttributes(examId, roomName, state); + } + + private void closeRoom(final ProctoringWindowData proctoringWindowData) { + this.pageService + .getCurrentUser() + .getProctoringGUIService() + .closeRoomWindow(proctoringWindowData.windowName); + } + + static final class BroadcastActionState { + public static final String KEY_NAME = "BroadcastActionState"; + boolean audio = false; + boolean video = false; + boolean chat = false; + } + +} 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 index 57cfa9b5..ebc9b692 100644 --- 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 @@ -64,16 +64,16 @@ public class ZoomWindowScriptResolver implements ProctoringWindowScriptResolver + " ZoomMtg.preLoadWasm();\n" + " ZoomMtg.prepareJssdk();\n" + "\n" - + " const API_KEY = \"%%_\" + ATTR_API_KEY + \"_%%\";\n" + + " const API_KEY = \"%%_" + ATTR_API_KEY + "_%%\";\n" + " const config = {\n" - + " meetingNumber: %%_\" + ATTR_ROOM_NAME + \"_%%,\n" - + " leaveUrl: '%%_\" + ATTR_HOST + \"_%%',\n" - + " userName: '%%_\" + ATTR_USER_NAME + \"_%%',\n" - + " passWord: '%%_\" + ATTR_ROOM_KEY + \"_%%',\n" - + " role: 0 // 1 for host; 0 for attendee\n" + + " meetingNumber: %%_" + ATTR_ROOM_NAME + "_%%,\n" + + " leaveUrl: '%%_" + ATTR_HOST + "_%%',\n" + + " userName: '%%_" + ATTR_USER_NAME + "_%%',\n" + + " passWord: '%%_" + ATTR_ROOM_KEY + "_%%',\n" + + " role: 1 // 1 for host; 0 for attendee\n" + " };\n" + "\n" - + " const signature = '%%_\" + ATTR_ACCESS_TOKEN + \"_%%';\n" + + " const signature = '%%_" + ATTR_ACCESS_TOKEN + "_%%';\n" + "\n" + " console.log(\"Initializing meeting...\");\n" + "\n" @@ -155,7 +155,7 @@ public class ZoomWindowScriptResolver implements ProctoringWindowScriptResolver 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_ROOM_NAME, data.connectionData.meetingId); 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)) { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/ExamAdminService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/ExamAdminService.java index c1e0c0bc..f9480b8a 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/ExamAdminService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/ExamAdminService.java @@ -41,16 +41,16 @@ public interface ExamAdminService { * @return Result refer to the restriction flag or to an error when happened */ Result isRestricted(Exam exam); - /** Get the proctoring service settings for a certain exam to an error when happened. - * - * @param examId the exam instance - * @return Result refer to proctoring service settings for the exam. */ - default Result getProctoringServiceSettings(final Exam exam) { - if (exam == null || exam.id == null) { - return Result.ofRuntimeError("Invalid Exam model"); - } - return getProctoringServiceSettings(exam.id); - } +// /** Get the proctoring service settings for a certain exam to an error when happened. +// * +// * @param examId the exam instance +// * @return Result refer to proctoring service settings for the exam. */ +// default Result getProctoringServiceSettings(final Exam exam) { +// if (exam == null || exam.id == null) { +// return Result.ofRuntimeError("Invalid Exam model"); +// } +// return getProctoringServiceSettings(exam.id); +// } /** Get proctoring service settings for a certain exam to an error when happened. * @@ -90,29 +90,38 @@ public interface ExamAdminService { * @return ExamProctoringService instance */ Result getExamProctoringService(final ProctoringServerType type); - /** Get the exam proctoring service implementation of specified type. - * - * @param settings the ProctoringSettings that defines the ProctoringServerType - * @return ExamProctoringService instance */ - default Result getExamProctoringService(final ProctoringServiceSettings settings) { - return Result.tryCatch(() -> getExamProctoringService(settings.serverType).getOrThrow()); - } - - /** Get the exam proctoring service implementation for specified exam. - * - * @param exam the exam instance - * @return ExamProctoringService instance */ - default Result getExamProctoringService(final Exam exam) { - return Result.tryCatch(() -> getExamProctoringService(exam.id).getOrThrow()); - } - /** Get the exam proctoring service implementation for specified exam. * * @param examId the exam identifier * @return ExamProctoringService instance */ default Result getExamProctoringService(final Long examId) { return getProctoringServiceSettings(examId) - .flatMap(this::getExamProctoringService); + .flatMap(settings -> getExamProctoringService(settings.serverType)); } +// /** Get the exam proctoring service implementation of specified type. +// * +// * @param settings the ProctoringSettings that defines the ProctoringServerType +// * @return ExamProctoringService instance */ +// default Result getExamProctoringService(final ProctoringServiceSettings settings) { +// return Result.tryCatch(() -> getExamProctoringService(settings.serverType).getOrThrow()); +// } +// +// /** Get the exam proctoring service implementation for specified exam. +// * +// * @param exam the exam instance +// * @return ExamProctoringService instance */ +// default Result getExamProctoringService(final Exam exam) { +// return Result.tryCatch(() -> getExamProctoringService(exam.id).getOrThrow()); +// } +// +// /** Get the exam proctoring service implementation for specified exam. +// * +// * @param examId the exam identifier +// * @return ExamProctoringService instance */ +// default Result getExamProctoringService(final Long examId) { +// return getProctoringServiceSettings(examId) +// .flatMap(this::getExamProctoringService); +// } + } 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 a96b86b9..7bb2be4d 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 @@ -12,7 +12,6 @@ import java.util.Map; import org.apache.commons.lang3.StringUtils; -import ch.ethz.seb.sebserver.gbl.model.exam.Exam; import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringRoomConnection; import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings; import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings.ProctoringServerType; @@ -52,7 +51,7 @@ public interface ExamProctoringService { Map createJoinInstructionAttributes(ProctoringRoomConnection proctoringConnection); - Result disposeServiceRoomsForExam(ProctoringServiceSettings proctoringSettings, Exam exam); + Result disposeServiceRoomsForExam(Long examId, ProctoringServiceSettings proctoringSettings); 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 ce395bec..795aa657 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 @@ -115,14 +115,13 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService return Result.tryCatch(() -> { - final ProctoringServiceSettings settings = this.examSessionService - .getRunningExam(exam.id) - .flatMap(this.examAdminService::getProctoringServiceSettings) + final ProctoringServiceSettings proctoringSettings = this.examAdminService + .getProctoringServiceSettings(exam.id) .getOrThrow(); this.examAdminService - .getExamProctoringService(exam) - .flatMap(service -> service.disposeServiceRoomsForExam(settings, exam)) + .getExamProctoringService(proctoringSettings.serverType) + .flatMap(service -> service.disposeServiceRoomsForExam(exam.id, proctoringSettings)) .onError(error -> log.error("Failed to dispose proctoring service rooms for exam: {} / {}", exam.name, exam.externalId, @@ -143,9 +142,9 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService } return Result.tryCatch(() -> { - final ProctoringServiceSettings settings = this.examSessionService - .getRunningExam(examId) - .flatMap(this.examAdminService::getProctoringServiceSettings) + + final ProctoringServiceSettings settings = this.examAdminService + .getProctoringServiceSettings(examId) .getOrThrow(); final ExamProctoringService examProctoringService = this.examAdminService @@ -182,9 +181,8 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService return Result.tryCatch(() -> { - final ProctoringServiceSettings settings = this.examSessionService - .getRunningExam(examId) - .flatMap(this.examAdminService::getProctoringServiceSettings) + final ProctoringServiceSettings settings = this.examAdminService + .getProctoringServiceSettings(examId) .getOrThrow(); final ExamProctoringService examProctoringService = this.examAdminService @@ -213,13 +211,12 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService public Result closeProctoringRoom(final Long examId, final String roomName) { return Result.tryCatch(() -> { - final ProctoringServiceSettings proctoringSettings = this.examSessionService - .getRunningExam(examId) - .flatMap(this.examAdminService::getProctoringServiceSettings) + final ProctoringServiceSettings settings = this.examAdminService + .getProctoringServiceSettings(examId) .getOrThrow(); final ExamProctoringService examProctoringService = this.examAdminService - .getExamProctoringService(proctoringSettings.serverType) + .getExamProctoringService(settings.serverType) .getOrThrow(); // Get room @@ -228,9 +225,9 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService .getOrThrow(); if (!remoteProctoringRoom.breakOutConnections.isEmpty()) { - closeBreakOutRoom(examId, proctoringSettings, examProctoringService, remoteProctoringRoom); + closeBreakOutRoom(examId, settings, examProctoringService, remoteProctoringRoom); } else if (remoteProctoringRoom.townhallRoom) { - closeTownhall(examId, proctoringSettings, examProctoringService); + closeTownhall(examId, settings, examProctoringService); } else { closeCollectingRoom(examId, roomName, examProctoringService); } @@ -301,7 +298,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService .getOrThrow(); final ExamProctoringService examProctoringService = this.examAdminService - .getExamProctoringService(examId) + .getExamProctoringService(proctoringSettings.serverType) .getOrThrow(); return this.remoteProctoringRoomDAO.reservePlaceInCollectingRoom( @@ -415,13 +412,12 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService return Result.tryCatch(() -> { - final ProctoringServiceSettings proctoringSettings = this.examSessionService - .getRunningExam(examId) - .flatMap(this.examAdminService::getProctoringServiceSettings) + final ProctoringServiceSettings settings = this.examAdminService + .getProctoringServiceSettings(examId) .getOrThrow(); final ExamProctoringService examProctoringService = this.examAdminService - .getExamProctoringService(proctoringSettings.serverType) + .getExamProctoringService(settings.serverType) .getOrThrow(); final RemoteProctoringRoom room = this.remoteProctoringRoomDAO 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 9b4bae6f..36f2179d 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 @@ -165,9 +165,8 @@ public class JitsiProctoringService implements ExamProctoringService { @Override public Result disposeServiceRoomsForExam( - final ProctoringServiceSettings proctoringSettings, - final Exam exam) { - + final Long examId, + final ProctoringServiceSettings proctoringSettings) { // NOTE: Since Jitsi rooms are generated and disposed automatically we don't need to do anything here return Result.EMPTY; } @@ -329,6 +328,7 @@ public class JitsiProctoringService implements ExamProctoringService { token, null, 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 fa2f1911..61f6675c 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 @@ -20,6 +20,7 @@ import java.util.stream.Collectors; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; +import javax.xml.bind.DatatypeConverter; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; @@ -49,7 +50,6 @@ import ch.ethz.seb.sebserver.gbl.api.JSONMapper; import ch.ethz.seb.sebserver.gbl.async.AsyncService; import ch.ethz.seb.sebserver.gbl.async.CircuitBreaker; import ch.ethz.seb.sebserver.gbl.client.ClientCredentials; -import ch.ethz.seb.sebserver.gbl.model.exam.Exam; import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringRoomConnection; import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings; import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings.ProctoringServerType; @@ -167,19 +167,17 @@ public class ZoomProctoringService implements ExamProctoringService { throw new APIMessageException( APIMessage.ErrorMessage.BINDING_ERROR, String.valueOf(result.getStatusCode())); + } else { + // TODO this is just for cleaning up along development process. + // Remove this before finish up the Zoom integration + + try { + + } catch (final Exception e) { + log.error("Failed to dev-cleanup rooms: ", e); + } } -// else { -// final UserPageResponse response = this.jsonMapper.readValue( -// result.getBody(), -// UserPageResponse.class); -// -// System.out.println(response); -// -// final ResponseEntity createUser = this.zoomRestTemplate -// .createUser(credentials, "TestRoom"); -// -// System.out.println(response); -// } + } catch (final Exception e) { log.error("Failed to access Zoom service at: {}", proctoringSettings.serverURL, e); throw new APIMessageException(APIMessage.ErrorMessage.BINDING_ERROR, e.getMessage()); @@ -247,10 +245,13 @@ public class ZoomProctoringService implements ExamProctoringService { final ClientCredentials credentials = new ClientCredentials( proctoringSettings.appKey, - this.cryptor.decrypt(proctoringSettings.appSecret), - this.cryptor.decrypt(remoteProctoringRoom.joinKey)); + proctoringSettings.appSecret, + remoteProctoringRoom.joinKey); - final String jwt = this.createJWTForMeetingAccess(credentials, subject); + final String jwt = this.createJWTForMeetingAccess( + credentials, + String.valueOf(additionalZoomRoomData.meeting_id), + true); return new ProctoringRoomConnection( ProctoringServerType.ZOOM, @@ -260,8 +261,9 @@ public class ZoomProctoringService implements ExamProctoringService { roomName, subject, jwt, - credentials.accessToken, + this.cryptor.decrypt(credentials.accessToken), credentials.clientId, + String.valueOf(additionalZoomRoomData.meeting_id), this.authorizationService.getUserService().getCurrentUser().getUsername()); }); } @@ -285,10 +287,13 @@ public class ZoomProctoringService implements ExamProctoringService { final ClientCredentials credentials = new ClientCredentials( proctoringSettings.appKey, - this.cryptor.decrypt(proctoringSettings.appSecret), - this.cryptor.decrypt(remoteProctoringRoom.joinKey)); + proctoringSettings.appSecret, + remoteProctoringRoom.joinKey); - final String jwt = this.createJWTForMeetingAccess(credentials, subject); + final String jwt = this.createJWTForMeetingAccess( + credentials, + String.valueOf(additionalZoomRoomData.meeting_id), + false); final ClientConnectionData clientConnection = this.examSessionService .getConnectionData(connectionToken) @@ -302,20 +307,22 @@ public class ZoomProctoringService implements ExamProctoringService { roomName, subject, jwt, - credentials.accessToken, + this.cryptor.decrypt(credentials.accessToken), credentials.clientId, + String.valueOf(additionalZoomRoomData.meeting_id), clientConnection.clientConnection.userSessionId); }); } @Override public Result disposeServiceRoomsForExam( - final ProctoringServiceSettings proctoringSettings, - final Exam exam) { + final Long examId, + final ProctoringServiceSettings proctoringSettings) { return Result.tryCatch(() -> { + this.remoteProctoringRoomDAO - .getRooms(exam.id) + .getRooms(examId) .getOrThrow() .stream() .forEach(room -> { @@ -361,9 +368,7 @@ public class ZoomProctoringService implements ExamProctoringService { .getRoom(proctoringSettings.examId, roomName) .getOrThrow(); - AdditionalZoomRoomData additionalZoomRoomData; - - additionalZoomRoomData = this.jsonMapper.readValue( + final AdditionalZoomRoomData additionalZoomRoomData = this.jsonMapper.readValue( roomData.getAdditionalRoomData(), AdditionalZoomRoomData.class); @@ -414,9 +419,9 @@ public class ZoomProctoringService implements ExamProctoringService { UserResponse.class); // Then create new meeting with the ad-hoc user/host - final CharSequence meetingPwd = UUID.randomUUID().toString(); + final CharSequence meetingPwd = UUID.randomUUID().toString().subSequence(0, 9); final ResponseEntity createMeeting = this.zoomRestTemplate.createMeeting( - roomName, + proctoringSettings.serverURL, credentials, userResponse.id, subject, @@ -429,6 +434,7 @@ public class ZoomProctoringService implements ExamProctoringService { // Create NewRoom data with all needed information to store persistent final AdditionalZoomRoomData additionalZoomRoomData = new AdditionalZoomRoomData( + meetingResponse.id, userResponse.id, meetingResponse.start_url, meetingResponse.join_url); @@ -438,7 +444,7 @@ public class ZoomProctoringService implements ExamProctoringService { return new NewRoom( roomName, subject, - this.cryptor.encrypt(meetingPwd), + this.cryptor.encrypt(meetingResponse.meetingPwd), additionalZoomRoomDataString); }); } @@ -506,43 +512,63 @@ public class ZoomProctoringService implements ExamProctoringService { private String createJWTForMeetingAccess( final ClientCredentials credentials, - final String subject) { + final String meetingId, + final boolean host) { try { - - final long iat = Utils.getMillisecondsNow() / 1000; - final long exp = iat + 7200; - + final String apiKey = credentials.clientIdAsString(); + final int status = host ? 1 : 0; final CharSequence decryptedSecret = this.cryptor.decrypt(credentials.secret); - final StringBuilder builder = new StringBuilder(); - final Encoder urlEncoder = Base64.getUrlEncoder().withoutPadding(); - final String jwtHeaderPart = urlEncoder.encodeToString( - ZOOM_ACCESS_TOKEN_HEADER.getBytes(StandardCharsets.UTF_8)); - final String jwtPayload = String.format( - ZOOM_MEETING_ACCESS_TOKEN_PAYLOAD.replaceAll(" ", "").replaceAll("\n", ""), - credentials.clientIdAsString(), - iat, - exp, - subject, - credentials.accessTokenAsString()); - final String jwtPayloadPart = urlEncoder.encodeToString( - jwtPayload.getBytes(StandardCharsets.UTF_8)); - final String message = jwtHeaderPart + "." + jwtPayloadPart; + final Mac hasher = Mac.getInstance("HmacSHA256"); + final String ts = Long.toString(System.currentTimeMillis() - 30000); + final String msg = String.format("%s%s%s%d", apiKey, meetingId, ts, status); - final Mac sha256_HMAC = Mac.getInstance(TOKEN_ENCODE_ALG); - final SecretKeySpec secret_key = new SecretKeySpec( - Utils.toByteArray(decryptedSecret), - TOKEN_ENCODE_ALG); - sha256_HMAC.init(secret_key); - final String hash = urlEncoder.encodeToString( - sha256_HMAC.doFinal(Utils.toByteArray(message))); + hasher.init(new SecretKeySpec(decryptedSecret.toString().getBytes(), "HmacSHA256")); - builder.append(message) - .append(".") - .append(hash); + final String message = Base64.getEncoder().encodeToString(msg.getBytes()); + final byte[] hash = hasher.doFinal(message.getBytes()); - return builder.toString(); + final String hashBase64Str = DatatypeConverter.printBase64Binary(hash); + final String tmpString = String.format("%s.%s.%s.%d.%s", apiKey, meetingId, ts, status, hashBase64Str); + final String encodedString = Base64.getEncoder().encodeToString(tmpString.getBytes()); + + return encodedString.replaceAll("\\=+$", ""); + +// final long iat = Utils.getMillisecondsNow() / 1000; +// final long exp = iat + 7200; +// +// final CharSequence decryptedSecret = this.cryptor.decrypt(credentials.secret); +// final CharSequence decryptedMeetingPWD = this.cryptor.decrypt(credentials.secret); +// final StringBuilder builder = new StringBuilder(); +// final Encoder urlEncoder = Base64.getUrlEncoder().withoutPadding(); +// +// final String jwtHeaderPart = urlEncoder.encodeToString( +// ZOOM_ACCESS_TOKEN_HEADER.getBytes(StandardCharsets.UTF_8)); +// final String jwtPayload = String.format( +// ZOOM_MEETING_ACCESS_TOKEN_PAYLOAD.replaceAll(" ", "").replaceAll("\n", ""), +// credentials.clientIdAsString(), +// iat, +// exp, +// subject, +// decryptedMeetingPWD); +// final String jwtPayloadPart = urlEncoder.encodeToString( +// jwtPayload.getBytes(StandardCharsets.UTF_8)); +// final String message = jwtHeaderPart + "." + jwtPayloadPart; +// +// final Mac sha256_HMAC = Mac.getInstance(TOKEN_ENCODE_ALG); +// final SecretKeySpec secret_key = new SecretKeySpec( +// Utils.toByteArray(decryptedSecret), +// TOKEN_ENCODE_ALG); +// sha256_HMAC.init(secret_key); +// final String hash = urlEncoder.encodeToString( +// sha256_HMAC.doFinal(Utils.toByteArray(message))); +// +// builder.append(message) +// .append(".") +// .append(hash); +// +// return builder.toString(); } catch (final Exception e) { throw new RuntimeException("Failed to create JWT for Zoom meeting access: ", e); } @@ -554,7 +580,7 @@ public class ZoomProctoringService implements ExamProctoringService { private static final String API_CREATE_USER_ENDPOINT = "v2/users"; private static final String API_DELETE_USER_ENDPOINT = "v2/users/{userid}?action=delete"; private static final String API_USER_CUST_CREATE = "custCreate"; - private static final String API_ZOOM_ROOM_USER = "ZoomRoomUser"; + private static final String API_ZOOM_ROOM_USER = "SEBProctoringRoomUser"; private static final String API_CREATE_MEETING_ENDPOINT = "v2/users/{userid}/meetings"; private static final String API_DELETE_MEETING_ENDPOINT = "v2/meetings/{meetingid}"; @@ -579,16 +605,23 @@ public class ZoomProctoringService implements ExamProctoringService { final String zoomServerUrl, final ClientCredentials credentials) { - final String url = UriComponentsBuilder - .fromUriString(zoomServerUrl) - .path(API_TEST_ENDPOINT) - .queryParam("status", "active") - .queryParam("page_size", "10") - .queryParam("page_number", "1") - .queryParam("data_type", "Json") - .build() - .toUriString(); - return exchange(url, HttpMethod.GET, credentials); + try { + + final String url = UriComponentsBuilder + .fromUriString(zoomServerUrl) + .path(API_TEST_ENDPOINT) + .queryParam("status", "active") + .queryParam("page_size", "10") + .queryParam("page_number", "1") + .queryParam("data_type", "Json") + .build() + .toUriString(); + return exchange(url, HttpMethod.GET, credentials); + + } catch (final Exception e) { + log.error("Failed to test zoom service connection: ", e); + return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); + } } public ResponseEntity createUser( @@ -617,7 +650,7 @@ public class ZoomProctoringService implements ExamProctoringService { } catch (final Exception e) { log.error("Failed to create Zoom ad-hoc user for room: {}", roomName, e); - throw new RuntimeException("Failed to create Zoom ad-hoc user", e); + return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } } @@ -645,7 +678,7 @@ public class ZoomProctoringService implements ExamProctoringService { } catch (final Exception e) { log.error("Failed to create Zoom ad-hoc meeting: {}", topic, e); - throw new RuntimeException("Failed to create Zoom ad-hoc meeting", e); + return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } } @@ -666,7 +699,7 @@ public class ZoomProctoringService implements ExamProctoringService { } catch (final Exception e) { log.error("Failed to delete Zoom ad-hoc meeting: {}", meetingId, e); - throw new RuntimeException("Failed to delete Zoom ad-hoc meeting", e); + return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } } @@ -687,7 +720,7 @@ public class ZoomProctoringService implements ExamProctoringService { } catch (final Exception e) { log.error("Failed to delete Zoom ad-hoc user with id: {}", userId, e); - throw new RuntimeException("Failed to delete Zoom ad-hoc user", e); + return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); } } @@ -728,9 +761,8 @@ public class ZoomProctoringService implements ExamProctoringService { httpEntity, String.class); - if (result.getStatusCode() != HttpStatus.OK) { - log.warn("Zoom API call to {} respond not 200 -> {}", url, result.getStatusCode()); - throw new RuntimeException("Error Response: " + result.getStatusCode()); + if (result.getStatusCode() != HttpStatus.OK && result.getStatusCode() != HttpStatus.CREATED) { + log.warn("Error response on Zoom API call to {} response status: {}", url, result.getStatusCode()); } return result; @@ -743,6 +775,8 @@ public class ZoomProctoringService implements ExamProctoringService { @JsonIgnoreProperties(ignoreUnknown = true) public static final class AdditionalZoomRoomData { + @JsonProperty("meeting_id") + private final Long meeting_id; @JsonProperty("user_id") public final String user_id; @JsonProperty("start_url") @@ -752,15 +786,16 @@ public class ZoomProctoringService implements ExamProctoringService { @JsonCreator public AdditionalZoomRoomData( + @JsonProperty("meeting_id") final Long meeting_id, @JsonProperty("user_id") final String user_id, @JsonProperty("start_url") final String start_url, @JsonProperty("join_url") final String join_url) { + this.meeting_id = meeting_id; this.user_id = user_id; this.start_url = start_url; this.join_url = join_url; } - } } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ZoomRoomRequestResponse.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ZoomRoomRequestResponse.java index 517beee2..54dbca0c 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ZoomRoomRequestResponse.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ZoomRoomRequestResponse.java @@ -77,12 +77,12 @@ public interface ZoomRoomRequestResponse { @JsonProperty final String email; @JsonProperty final int type; @JsonProperty final String first_name; - @JsonProperty final String lasr_name; - public UserInfo(final String email, final int type, final String first_name, final String lasr_name) { + @JsonProperty final String last_name; + public UserInfo(final String email, final int type, final String first_name, final String last_name) { this.email = email; this.type = type; this.first_name = first_name; - this.lasr_name = lasr_name; + this.last_name = last_name; } } } @@ -113,8 +113,9 @@ public interface ZoomRoomRequestResponse { @JsonIgnoreProperties(ignoreUnknown = true) static class CreateMeetingRequest { @JsonProperty final String topic; - @JsonProperty final int type = 1; // Instant meeting - @JsonProperty final String start_time = DateTime.now(DateTimeZone.UTC).toString("yyyy-MM-dd`T`HH:mm:ssZ"); + @JsonProperty final int type = 2; // Scheduled Meeting + @JsonProperty final String start_time = DateTime.now(DateTimeZone.UTC).toString("yyyy-MM-dd'T'HH:mm:ss"); + @JsonProperty final int duration = 60; @JsonProperty final CharSequence password; @JsonProperty final Settings settings; @@ -146,6 +147,8 @@ public interface ZoomRoomRequestResponse { final String status; final String uuid; final String host_id; + final CharSequence meetingPwd; + final CharSequence encryptedPwd; @JsonCreator public MeetingResponse( @@ -156,7 +159,10 @@ public interface ZoomRoomRequestResponse { @JsonProperty("duration") final Integer duration, @JsonProperty("status") final String status, @JsonProperty("uuid") final String uuid, - @JsonProperty("host_id") final String host_id) { + @JsonProperty("host_id") final String host_id, + @JsonProperty("password") final CharSequence meetingPwd, + @JsonProperty("encrypted_password") final CharSequence encryptedPwd) { + this.id = id; this.join_url = join_url; this.start_url = start_url; @@ -165,6 +171,8 @@ public interface ZoomRoomRequestResponse { this.status = status; this.uuid = uuid; this.host_id = host_id; + this.meetingPwd = meetingPwd; + this.encryptedPwd = encryptedPwd; } } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAdministrationController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAdministrationController.java index 214a0c2e..d48bf3aa 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAdministrationController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAdministrationController.java @@ -396,7 +396,7 @@ public class ExamAdministrationController extends EntityController { checkReadPrivilege(institutionId); return this.entityDAO.byPK(modelId) .flatMap(this.authorization::checkRead) - .flatMap(this.examAdminService::getProctoringServiceSettings) + .flatMap(exam -> this.examAdminService.getProctoringServiceSettings(exam.id)) .getOrThrow(); } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamProctoringController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamProctoringController.java index 7d9244e0..80d61ae0 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamProctoringController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamProctoringController.java @@ -111,7 +111,7 @@ public class ExamProctoringController { checkAccess(institutionId, examId); return this.examSessionService.getRunningExam(examId) .flatMap(this.authorization::checkRead) - .flatMap(this.examAdminService::getExamProctoringService) + .flatMap(exam -> this.examAdminService.getExamProctoringService(exam.id)) .flatMap(service -> service.getProctorRoomConnection( this.examAdminService.getProctoringServiceSettings(examId).getOrThrow(), roomName, diff --git a/src/main/resources/config/application.properties b/src/main/resources/config/application.properties index 023e512e..aa543423 100644 --- a/src/main/resources/config/application.properties +++ b/src/main/resources/config/application.properties @@ -36,7 +36,7 @@ logging.level.ch=INFO ### spring actuator configuration management.endpoints.web.base-path=/mprofile -management.endpoints.web.exposure.include=metrics,logfile,loggers,heapdump +management.endpoints.web.exposure.include=metrics,logfile,loggers,heapdump,health ########################################################## ### Overall Security Settings diff --git a/src/test/java/ch/ethz/seb/sebserver/gbl/util/ReplTest.java b/src/test/java/ch/ethz/seb/sebserver/gbl/util/ReplTest.java new file mode 100644 index 00000000..a1e91d46 --- /dev/null +++ b/src/test/java/ch/ethz/seb/sebserver/gbl/util/ReplTest.java @@ -0,0 +1,36 @@ +/* + * 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.gbl.util; + +import static org.junit.Assert.assertEquals; + +import java.util.UUID; + +import org.joda.time.DateTime; +import org.joda.time.DateTimeZone; +import org.junit.Ignore; +import org.junit.jupiter.api.Test; + +public class ReplTest { + + @Test + @Ignore + public void testDateFormatting() { + final String datestring = DateTime.now(DateTimeZone.UTC).toString("yyyy-MM-dd'T'HH:mm:ss"); + assertEquals("", datestring); + } + + @Test + @Ignore + public void testGenPwd() { + final CharSequence meetingPwd = UUID.randomUUID().toString().subSequence(0, 9); + assertEquals("", meetingPwd); + } + +}