SEBSERV-148 GUI impl
This commit is contained in:
parent
70064206cf
commit
ffe4b6301a
15 changed files with 505 additions and 162 deletions
|
@ -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();
|
||||
|
|
|
@ -16,4 +16,5 @@ public interface RemoteProctoringView extends TemplateComposer {
|
|||
*
|
||||
* @return the remote proctoring server type this remote proctoring view can handle. */
|
||||
ProctoringServerType serverType();
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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<String, String> 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)) {
|
||||
|
|
|
@ -41,16 +41,16 @@ public interface ExamAdminService {
|
|||
* @return Result refer to the restriction flag or to an error when happened */
|
||||
Result<Boolean> 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<ProctoringServiceSettings> 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<ProctoringServiceSettings> 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<ExamProctoringService> 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<ExamProctoringService> 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<ExamProctoringService> 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<ExamProctoringService> 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<ExamProctoringService> 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<ExamProctoringService> 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<ExamProctoringService> getExamProctoringService(final Long examId) {
|
||||
// return getProctoringServiceSettings(examId)
|
||||
// .flatMap(this::getExamProctoringService);
|
||||
// }
|
||||
|
||||
}
|
||||
|
|
|
@ -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<String, String> createJoinInstructionAttributes(ProctoringRoomConnection proctoringConnection);
|
||||
|
||||
Result<Void> disposeServiceRoomsForExam(ProctoringServiceSettings proctoringSettings, Exam exam);
|
||||
Result<Void> disposeServiceRoomsForExam(Long examId, ProctoringServiceSettings proctoringSettings);
|
||||
|
||||
default String verifyRoomName(final String requestedRoomName, final String connectionToken) {
|
||||
if (StringUtils.isNotBlank(requestedRoomName)) {
|
||||
|
|
|
@ -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<Void> 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
|
||||
|
|
|
@ -165,9 +165,8 @@ public class JitsiProctoringService implements ExamProctoringService {
|
|||
|
||||
@Override
|
||||
public Result<Void> 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);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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<String> 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<Void> 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<String> 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<String> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -396,7 +396,7 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
|
|||
checkReadPrivilege(institutionId);
|
||||
return this.entityDAO.byPK(modelId)
|
||||
.flatMap(this.authorization::checkRead)
|
||||
.flatMap(this.examAdminService::getProctoringServiceSettings)
|
||||
.flatMap(exam -> this.examAdminService.getProctoringServiceSettings(exam.id))
|
||||
.getOrThrow();
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
36
src/test/java/ch/ethz/seb/sebserver/gbl/util/ReplTest.java
Normal file
36
src/test/java/ch/ethz/seb/sebserver/gbl/util/ReplTest.java
Normal file
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue