SEBSERV-148 added HTML and script for zoom proctoring
This commit is contained in:
parent
67d1e3fba1
commit
3350e4eece
20 changed files with 510 additions and 127 deletions
|
@ -25,6 +25,8 @@ public class ProctoringRoomConnection {
|
|||
public static final String ATTR_ACCESS_TOKEN = "accessToken";
|
||||
public static final String ATTR_CONNECTION_URL = "connectionURL";
|
||||
public static final String ATTR_USER_NAME = "userName";
|
||||
public static final String ATTR_ROOM_KEY = "roomKey";
|
||||
public static final String ATTR_API_KEY = "apiKey";
|
||||
|
||||
@JsonProperty(ProctoringServiceSettings.ATTR_SERVER_TYPE)
|
||||
public final ProctoringServerType proctoringServerType;
|
||||
|
@ -45,7 +47,13 @@ public class ProctoringRoomConnection {
|
|||
public final String subject;
|
||||
|
||||
@JsonProperty(ATTR_ACCESS_TOKEN)
|
||||
public final String accessToken;
|
||||
public final CharSequence accessToken;
|
||||
|
||||
@JsonProperty(ATTR_ROOM_KEY)
|
||||
public final CharSequence roomKey;
|
||||
|
||||
@JsonProperty(ATTR_API_KEY)
|
||||
public final CharSequence apiKey;
|
||||
|
||||
@JsonProperty(ATTR_USER_NAME)
|
||||
public final String userName;
|
||||
|
@ -58,7 +66,9 @@ public class ProctoringRoomConnection {
|
|||
@JsonProperty(ATTR_SERVER_URL) final String serverURL,
|
||||
@JsonProperty(ATTR_ROOM_NAME) final String roomName,
|
||||
@JsonProperty(ATTR_SUBJECT) final String subject,
|
||||
@JsonProperty(ATTR_ACCESS_TOKEN) final String accessToken,
|
||||
@JsonProperty(ATTR_ACCESS_TOKEN) final CharSequence accessToken,
|
||||
@JsonProperty(ATTR_ROOM_KEY) final CharSequence roomKey,
|
||||
@JsonProperty(ATTR_API_KEY) final CharSequence apiKey,
|
||||
@JsonProperty(ATTR_USER_NAME) final String userName) {
|
||||
|
||||
this.proctoringServerType = proctoringServerType;
|
||||
|
@ -68,6 +78,8 @@ public class ProctoringRoomConnection {
|
|||
this.roomName = roomName;
|
||||
this.subject = subject;
|
||||
this.accessToken = accessToken;
|
||||
this.roomKey = roomKey;
|
||||
this.apiKey = apiKey;
|
||||
this.userName = userName;
|
||||
}
|
||||
|
||||
|
@ -83,10 +95,22 @@ public class ProctoringRoomConnection {
|
|||
return this.serverHost;
|
||||
}
|
||||
|
||||
public String getAccessToken() {
|
||||
public CharSequence getAccessToken() {
|
||||
return this.accessToken;
|
||||
}
|
||||
|
||||
public CharSequence getRoomKey() {
|
||||
return this.roomKey;
|
||||
}
|
||||
|
||||
public CharSequence getApiKey() {
|
||||
return this.apiKey;
|
||||
}
|
||||
|
||||
public String getUserName() {
|
||||
return this.userName;
|
||||
}
|
||||
|
||||
public String getServerURL() {
|
||||
return this.serverURL;
|
||||
}
|
||||
|
|
|
@ -46,16 +46,18 @@ public final class ClientInstruction {
|
|||
public static final String JITSI_TOKEN = "jitsiMeetToken";
|
||||
public static final String JITSI_RECEIVE_AUDIO = "jitsiMeetReceiveAudio";
|
||||
public static final String JITSI_RECEIVE_VIDEO = "jitsiMeetReceiveVideo";
|
||||
public static final String JITSI_ALLOW_CHAT = "jitsiMeetFeatureFlagChat";
|
||||
public static final String JITSI_ALLOW_CHAT = "jitsiFeatureFlagChat";
|
||||
|
||||
public static final String ZOOM_URL = "zoomMeetServerURL";
|
||||
public static final String ZOOM_ROOM = "zoomMeetRoom";
|
||||
public static final String ZOOM_ROOM_SUBJECT = "zoomMeetSubject";
|
||||
public static final String ZOOM_URL = "zoomServerURL";
|
||||
public static final String ZOOM_ROOM = "zoomRoom";
|
||||
public static final String ZOOM_ROOM_SUBJECT = "zoomSubject";
|
||||
public static final String ZOOM_USER_NAME = "zoomUserName";
|
||||
public static final String ZOOM_TOKEN = "zoomMeetToken";
|
||||
public static final String ZOOM_RECEIVE_AUDIO = "zoomMeetReceiveAudio";
|
||||
public static final String ZOOM_RECEIVE_VIDEO = "zoomMeetReceiveVideo";
|
||||
public static final String ZOOM_ALLOW_CHAT = "zoomMeetFeatureFlagChat";
|
||||
public static final String ZOOM_API_KEY = "zoomAPIKey";
|
||||
public static final String ZOOM_TOKEN = "zoomToken";
|
||||
public static final String ZOOM_MEETING_KEY = "zoomMeetingKey";
|
||||
public static final String ZOOM_RECEIVE_AUDIO = "zoomReceiveAudio";
|
||||
public static final String ZOOM_RECEIVE_VIDEO = "zoomReceiveVideo";
|
||||
public static final String ZOOM_ALLOW_CHAT = "zoomFeatureFlagChat";
|
||||
}
|
||||
|
||||
public interface SEB_RECONFIGURE_SETTINGS {
|
||||
|
@ -63,9 +65,9 @@ public final class ClientInstruction {
|
|||
public static final String JITSI_RECEIVE_VIDEO = "jitsiMeetReceiveVideo";
|
||||
public static final String JITSI_ALLOW_CHAT = "jitsiMeetFeatureFlagChat";
|
||||
|
||||
public static final String ZOOM_RECEIVE_AUDIO = "zoomMeetReceiveAudio";
|
||||
public static final String ZOOM_RECEIVE_VIDEO = "zoomMeetReceiveVideo";
|
||||
public static final String ZOOM_ALLOW_CHAT = "zoomMeetFeatureFlagChat";
|
||||
public static final String ZOOM_RECEIVE_AUDIO = "zoomReceiveAudio";
|
||||
public static final String ZOOM_RECEIVE_VIDEO = "zoomReceiveVideo";
|
||||
public static final String ZOOM_ALLOW_CHAT = "zoomFeatureFlagChat";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
package ch.ethz.seb.sebserver.gui;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
|
@ -17,6 +18,8 @@ import javax.servlet.http.HttpServletRequest;
|
|||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
|
@ -25,62 +28,22 @@ import org.springframework.web.context.support.WebApplicationContextUtils;
|
|||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.AuthorizationContextHolder;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.SEBServerAuthorizationContext;
|
||||
import ch.ethz.seb.sebserver.gui.service.session.ProctoringGUIService;
|
||||
import ch.ethz.seb.sebserver.gui.service.session.ProctoringGUIService.ProctoringWindowData;
|
||||
import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService;
|
||||
import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService.ProctoringWindowData;
|
||||
import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringWindowScriptResolver;
|
||||
|
||||
@Component
|
||||
@GuiProfile
|
||||
public class ProctoringServlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 3475978419653411800L;
|
||||
private static final Logger log = LoggerFactory.getLogger(ProctoringServlet.class);
|
||||
|
||||
// @formatter:off
|
||||
private static final String JITSI_WINDOW_HTML =
|
||||
"<!DOCTYPE html>" +
|
||||
"<html>" +
|
||||
"<head>" +
|
||||
" <title></title>" +
|
||||
" <script src='https://%s/external_api.js'></script>" +
|
||||
"</head>" +
|
||||
"" +
|
||||
"<body>" +
|
||||
"<div id=\"proctoring\"></div> " +
|
||||
"</body>" +
|
||||
"<script>" +
|
||||
" const options = {\n" +
|
||||
" parentNode: document.querySelector('#proctoring'),\n" +
|
||||
" roomName: '%s',\n" +
|
||||
// " width: window.innerWidth,\n" +
|
||||
" height: window.innerHeight - 4,\n" +
|
||||
" jwt: '%s',\n" +
|
||||
" configOverwrite: { startAudioOnly: false, startWithAudioMuted: true, startWithVideoMuted: false, disable1On1Mode: true },\n" +
|
||||
" interfaceConfigOverwrite: { " +
|
||||
"TOOLBAR_BUTTONS: [\r\n" +
|
||||
" 'microphone', 'camera',\r\n" +
|
||||
" 'fodeviceselection', 'profile', 'chat', 'recording',\r\n" +
|
||||
" 'livestreaming', 'settings',\r\n" +
|
||||
" 'videoquality', 'filmstrip', 'feedback',\r\n" +
|
||||
" 'tileview', 'help', 'mute-everyone', 'security'\r\n" +
|
||||
" ],"
|
||||
+ "SHOW_WATERMARK_FOR_GUESTS: false, "
|
||||
+ "RECENT_LIST_ENABLED: false, "
|
||||
+ "HIDE_INVITE_MORE_HEADER: true, "
|
||||
+ "DISABLE_RINGING: true, "
|
||||
+ "DISABLE_PRESENCE_STATUS: true, "
|
||||
+ "DISABLE_JOIN_LEAVE_NOTIFICATIONS: true, "
|
||||
+ "GENERATE_ROOMNAMES_ON_WELCOME_PAGE: false, "
|
||||
+ "MOBILE_APP_PROMO: false, "
|
||||
+ "SHOW_JITSI_WATERMARK: false, "
|
||||
+ "DISABLE_PRESENCE_STATUS: true, "
|
||||
+ "DISABLE_RINGING: true, "
|
||||
+ "DISABLE_VIDEO_BACKGROUND: false, "
|
||||
+ "filmStripOnly: false }\n" +
|
||||
" }\n" +
|
||||
" const meetAPI = new JitsiMeetExternalAPI(\"%s\", options);\n" +
|
||||
" meetAPI.executeCommand('subject', '%s');\n" +
|
||||
"</script>" +
|
||||
"</html>";
|
||||
// @formatter:on
|
||||
private final Collection<ProctoringWindowScriptResolver> proctoringWindowScriptResolver;
|
||||
|
||||
public ProctoringServlet(final Collection<ProctoringWindowScriptResolver> proctoringWindowScriptResolver) {
|
||||
this.proctoringWindowScriptResolver = proctoringWindowScriptResolver;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(
|
||||
|
@ -103,21 +66,17 @@ public class ProctoringServlet extends HttpServlet {
|
|||
(ProctoringWindowData) httpSession
|
||||
.getAttribute(ProctoringGUIService.SESSION_ATTR_PROCTORING_DATA);
|
||||
|
||||
switch (proctoringData.connectionData.proctoringServerType) {
|
||||
case JITSI_MEET: {
|
||||
final String script = String.format(
|
||||
JITSI_WINDOW_HTML,
|
||||
proctoringData.connectionData.serverHost,
|
||||
proctoringData.connectionData.roomName,
|
||||
proctoringData.connectionData.accessToken,
|
||||
proctoringData.connectionData.serverHost,
|
||||
proctoringData.connectionData.subject);
|
||||
resp.getOutputStream().println(script);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new RuntimeException(
|
||||
"Unsupported proctoring server type: " + proctoringData.connectionData.proctoringServerType);
|
||||
final String script = this.proctoringWindowScriptResolver.stream()
|
||||
.filter(resolver -> resolver.applies(proctoringData))
|
||||
.findFirst()
|
||||
.map(resolver -> resolver.getProctoringWindowScript(proctoringData))
|
||||
.orElse(null);
|
||||
|
||||
if (script == null) {
|
||||
log.error("Failed to get proctoring window script for data: {}", proctoringData);
|
||||
resp.getOutputStream().println("Failed to get proctoring window script");
|
||||
} else {
|
||||
resp.getOutputStream().println(script);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -67,7 +67,7 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetProcto
|
|||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
|
||||
import ch.ethz.seb.sebserver.gui.service.session.ClientConnectionDetails;
|
||||
import ch.ethz.seb.sebserver.gui.service.session.InstructionProcessor;
|
||||
import ch.ethz.seb.sebserver.gui.service.session.ProctoringGUIService;
|
||||
import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService;
|
||||
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition;
|
||||
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition.TableFilterAttribute;
|
||||
import ch.ethz.seb.sebserver.gui.table.EntityTable;
|
||||
|
|
|
@ -81,7 +81,7 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetTownha
|
|||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
|
||||
import ch.ethz.seb.sebserver.gui.service.session.ClientConnectionTable;
|
||||
import ch.ethz.seb.sebserver.gui.service.session.InstructionProcessor;
|
||||
import ch.ethz.seb.sebserver.gui.service.session.ProctoringGUIService;
|
||||
import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService;
|
||||
import ch.ethz.seb.sebserver.gui.widget.Message;
|
||||
|
||||
@Lazy
|
||||
|
|
|
@ -33,8 +33,8 @@ import ch.ethz.seb.sebserver.gui.service.page.RemoteProctoringView;
|
|||
import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.AuthorizationContextHolder;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.IllegalUserSessionStateException;
|
||||
import ch.ethz.seb.sebserver.gui.service.session.ProctoringGUIService;
|
||||
import ch.ethz.seb.sebserver.gui.service.session.ProctoringGUIService.ProctoringWindowData;
|
||||
import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService;
|
||||
import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService.ProctoringWindowData;
|
||||
import ch.ethz.seb.sebserver.gui.widget.Message;
|
||||
|
||||
@Lazy
|
||||
|
|
|
@ -33,8 +33,8 @@ import ch.ethz.seb.sebserver.gui.service.page.PageContext;
|
|||
import ch.ethz.seb.sebserver.gui.service.page.PageService;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.RemoteProctoringView;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.SendProctoringReconfigurationAttributes;
|
||||
import ch.ethz.seb.sebserver.gui.service.session.ProctoringGUIService;
|
||||
import ch.ethz.seb.sebserver.gui.service.session.ProctoringGUIService.ProctoringWindowData;
|
||||
import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService;
|
||||
import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService.ProctoringWindowData;
|
||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
|
||||
|
||||
@Component
|
||||
|
|
|
@ -90,8 +90,9 @@ public class ServerPushService {
|
|||
log.warn(
|
||||
"Failed to stop Server Push Session on: {}. "
|
||||
+ "It seems that the UISession is not available anymore. "
|
||||
+ "This may source from a connection interruption",
|
||||
Thread.currentThread().getName(), e);
|
||||
+ "This may source from a connection interruption. Cause: {}",
|
||||
Thread.currentThread().getName(),
|
||||
e.getMessage());
|
||||
}
|
||||
|
||||
});
|
||||
|
|
|
@ -35,7 +35,7 @@ import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
|
|||
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
|
||||
import ch.ethz.seb.sebserver.gui.service.session.ProctoringGUIService;
|
||||
import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService;
|
||||
|
||||
@Component
|
||||
@GuiProfile
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* Copyright (c) 2021 ETH Zürich, Educational Development and Technology (LET)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ch.ethz.seb.sebserver.gui.service.session.proctoring;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.text.StringSubstitutor;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings.ProctoringServerType;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService.ProctoringWindowData;
|
||||
|
||||
@Lazy
|
||||
@Service
|
||||
@GuiProfile
|
||||
public class JitsiWindowScriptResolver implements ProctoringWindowScriptResolver {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(JitsiWindowScriptResolver.class);
|
||||
|
||||
private static final String ATTR_SUBJECT = "_subject_";
|
||||
private static final String ATTR_ACCESS_TOKEN = "_accessToken_";
|
||||
private static final String ATTR_ROOM_NAME = "_roomName_";
|
||||
private static final String ATTR_HOST = "_host_";
|
||||
|
||||
// @formatter:off
|
||||
private static final String JITSI_WINDOW_HTML =
|
||||
"<!DOCTYPE html>" +
|
||||
"<html>" +
|
||||
"<head>" +
|
||||
" <title></title>" +
|
||||
" <script src='https://%%_" + ATTR_HOST + "_%%/external_api.js'></script>" +
|
||||
"</head>" +
|
||||
"" +
|
||||
"<body>" +
|
||||
"<div id=\"proctoring\"></div> " +
|
||||
"</body>" +
|
||||
"<script>" +
|
||||
" const options = {\n" +
|
||||
" parentNode: document.querySelector('#proctoring'),\n" +
|
||||
" roomName: '%%_" + ATTR_ROOM_NAME + "_%%',\n" +
|
||||
// " width: window.innerWidth,\n" +
|
||||
" height: window.innerHeight - 4,\n" +
|
||||
" jwt: '%%_" + ATTR_ACCESS_TOKEN + "_%%',\n" +
|
||||
" configOverwrite: { startAudioOnly: false, startWithAudioMuted: true, startWithVideoMuted: false, disable1On1Mode: true },\n" +
|
||||
" interfaceConfigOverwrite: { " +
|
||||
"TOOLBAR_BUTTONS: [\r\n" +
|
||||
" 'microphone', 'camera',\r\n" +
|
||||
" 'fodeviceselection', 'profile', 'chat', 'recording',\r\n" +
|
||||
" 'livestreaming', 'settings',\r\n" +
|
||||
" 'videoquality', 'filmstrip', 'feedback',\r\n" +
|
||||
" 'tileview', 'help', 'mute-everyone', 'security'\r\n" +
|
||||
" ],"
|
||||
+ "SHOW_WATERMARK_FOR_GUESTS: false, "
|
||||
+ "RECENT_LIST_ENABLED: false, "
|
||||
+ "HIDE_INVITE_MORE_HEADER: true, "
|
||||
+ "DISABLE_RINGING: true, "
|
||||
+ "DISABLE_PRESENCE_STATUS: true, "
|
||||
+ "DISABLE_JOIN_LEAVE_NOTIFICATIONS: true, "
|
||||
+ "GENERATE_ROOMNAMES_ON_WELCOME_PAGE: false, "
|
||||
+ "MOBILE_APP_PROMO: false, "
|
||||
+ "SHOW_JITSI_WATERMARK: false, "
|
||||
+ "DISABLE_PRESENCE_STATUS: true, "
|
||||
+ "DISABLE_RINGING: true, "
|
||||
+ "DISABLE_VIDEO_BACKGROUND: false, "
|
||||
+ "filmStripOnly: false }\n" +
|
||||
" }\n" +
|
||||
" const meetAPI = new JitsiMeetExternalAPI(\"%%_" + ATTR_HOST + "_%%\", options);\n" +
|
||||
" meetAPI.executeCommand('subject', '%%_" + ATTR_SUBJECT + "_%%');\n" +
|
||||
"</script>" +
|
||||
"</html>";
|
||||
// @formatter:on
|
||||
|
||||
@Override
|
||||
public boolean applies(final ProctoringWindowData data) {
|
||||
try {
|
||||
return data.connectionData.proctoringServerType == ProctoringServerType.JITSI_MEET;
|
||||
} catch (final Exception e) {
|
||||
log.error("Failed to verify responsibility. Cause: {}", e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProctoringWindowScript(final ProctoringWindowData data) {
|
||||
final Map<String, String> args = new HashMap<>();
|
||||
args.put(ATTR_HOST, data.connectionData.serverHost);
|
||||
args.put(ATTR_ROOM_NAME, data.connectionData.roomName);
|
||||
args.put(ATTR_ACCESS_TOKEN, String.valueOf(data.connectionData.accessToken));
|
||||
args.put(ATTR_SUBJECT, data.connectionData.subject);
|
||||
|
||||
return new StringSubstitutor(args, "%%_", "_%%")
|
||||
.replace(JITSI_WINDOW_HTML);
|
||||
}
|
||||
|
||||
}
|
|
@ -6,7 +6,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ch.ethz.seb.sebserver.gui.service.session;
|
||||
package ch.ethz.seb.sebserver.gui.service.session.proctoring;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Copyright (c) 2021 ETH Zürich, Educational Development and Technology (LET)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ch.ethz.seb.sebserver.gui.service.session.proctoring;
|
||||
|
||||
import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService.ProctoringWindowData;
|
||||
|
||||
public interface ProctoringWindowScriptResolver {
|
||||
|
||||
boolean applies(ProctoringWindowData data);
|
||||
|
||||
String getProctoringWindowScript(ProctoringWindowData data);
|
||||
|
||||
}
|
|
@ -0,0 +1,173 @@
|
|||
/*
|
||||
* Copyright (c) 2021 ETH Zürich, Educational Development and Technology (LET)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ch.ethz.seb.sebserver.gui.service.session.proctoring;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.text.StringSubstitutor;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings.ProctoringServerType;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService.ProctoringWindowData;
|
||||
|
||||
@Lazy
|
||||
@Service
|
||||
@GuiProfile
|
||||
public class ZoomWindowScriptResolver implements ProctoringWindowScriptResolver {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ZoomWindowScriptResolver.class);
|
||||
|
||||
private static final String ATTR_SUBJECT = "_subject_";
|
||||
private static final String ATTR_API_KEY = "_apiKey_";
|
||||
private static final String ATTR_ACCESS_TOKEN = "_accessToken_";
|
||||
private static final String ATTR_ROOM_KEY = "_roomKey_";
|
||||
private static final String ATTR_ROOM_NAME = "_roomName_";
|
||||
private static final String ATTR_HOST = "_host_";
|
||||
private static final String ATTR_USER_NAME = "_username_";
|
||||
|
||||
// @formatter:off
|
||||
private static final String ZOOM_WINDOW_HTML =
|
||||
"<html>\n"
|
||||
+ " <head>\n"
|
||||
+ " <meta charset=\"utf-8\" />\n"
|
||||
+ " <link type=\"text/css\" rel=\"stylesheet\" href=\"https://source.zoom.us/1.8.1/css/bootstrap.css\" />\n"
|
||||
+ " <link type=\"text/css\" rel=\"stylesheet\" href=\"https://source.zoom.us/1.8.1/css/react-select.css\" />\n"
|
||||
+ " </head>\n"
|
||||
+ " <body>\n"
|
||||
+ " <script src=\"https://source.zoom.us/1.8.1/lib/vendor/react.min.js\"></script>\n"
|
||||
+ " <script src=\"https://source.zoom.us/1.8.1/lib/vendor/react-dom.min.js\"></script>\n"
|
||||
+ " <script src=\"https://source.zoom.us/1.8.1/lib/vendor/redux.min.js\"></script>\n"
|
||||
+ " <script src=\"https://source.zoom.us/1.8.1/lib/vendor/redux-thunk.min.js\"></script>\n"
|
||||
+ " <script src=\"https://source.zoom.us/1.8.1/lib/vendor/jquery.min.js\"></script>\n"
|
||||
+ " <script src=\"https://source.zoom.us/1.8.1/lib/vendor/lodash.min.js\"></script>\n"
|
||||
+ " <script src=\"https://source.zoom.us/zoom-meeting-1.8.1.min.js\"></script>\n"
|
||||
+ " <script src=\"https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9/crypto-js.min.js\"></script>\n"
|
||||
+ " <script type=\"text/javascript\">\n"
|
||||
+ "\n"
|
||||
+ " console.log(\"Checking system requirements...\");\n"
|
||||
+ " console.log(JSON.stringify(ZoomMtg.checkSystemRequirements()));\n"
|
||||
+ "\n"
|
||||
+ " console.log(\"Initializing Zoom...\");\n"
|
||||
+ " ZoomMtg.setZoomJSLib('https://source.zoom.us/1.8.1/lib', '/av');\n"
|
||||
+ " ZoomMtg.preLoadWasm();\n"
|
||||
+ " ZoomMtg.prepareJssdk();\n"
|
||||
+ "\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"
|
||||
+ " };\n"
|
||||
+ "\n"
|
||||
+ " const signature = '%%_\" + ATTR_ACCESS_TOKEN + \"_%%';\n"
|
||||
+ "\n"
|
||||
+ " console.log(\"Initializing meeting...\");\n"
|
||||
+ "\n"
|
||||
+ " // See documentation: https://zoom.github.io/sample-app-web/ZoomMtg.html#init\n"
|
||||
+ " ZoomMtg.init({\n"
|
||||
+ " debug: true, //optional\n"
|
||||
+ " leaveUrl: config.leaveUrl, //required\n"
|
||||
+ " // webEndpoint: 'PSO web domain', // PSO option\n"
|
||||
+ " showMeetingHeader: true, //option\n"
|
||||
+ " disableInvite: false, //optional\n"
|
||||
+ " disableCallOut: false, //optional\n"
|
||||
+ " disableRecord: false, //optional\n"
|
||||
+ " disableJoinAudio: false, //optional\n"
|
||||
+ " audioPanelAlwaysOpen: true, //optional\n"
|
||||
+ " showPureSharingContent: false, //optional\n"
|
||||
+ " isSupportAV: true, //optional,\n"
|
||||
+ " isSupportChat: false, //optional,\n"
|
||||
+ " isSupportQA: true, //optional,\n"
|
||||
+ " isSupportCC: true, //optional,\n"
|
||||
+ " screenShare: true, //optional,\n"
|
||||
+ " rwcBackup: '', //optional,\n"
|
||||
+ " videoDrag: true, //optional,\n"
|
||||
+ " sharingMode: 'both', //optional,\n"
|
||||
+ " videoHeader: true, //optional,\n"
|
||||
+ " isLockBottom: true, // optional,\n"
|
||||
+ " isSupportNonverbal: true, // optional,\n"
|
||||
+ " isShowJoiningErrorDialog: true, // optional,\n"
|
||||
+ " inviteUrlFormat: '', // optional\n"
|
||||
+ " loginWindow: { // optional,\n"
|
||||
+ " width: 400,\n"
|
||||
+ " height: 380\n"
|
||||
+ " },\n"
|
||||
+ " // meetingInfo: [ // optional\n"
|
||||
+ " // 'topic',\n"
|
||||
+ " // 'host',\n"
|
||||
+ " // 'mn',\n"
|
||||
+ " // 'pwd',\n"
|
||||
+ " // 'telPwd',\n"
|
||||
+ " // 'invite',\n"
|
||||
+ " // 'participant',\n"
|
||||
+ " // 'dc'\n"
|
||||
+ " // ],\n"
|
||||
+ " disableVoIP: false, // optional\n"
|
||||
+ " disableReport: false, // optional\n"
|
||||
+ " error: function (res) {\n"
|
||||
+ " console.warn(\"INIT ERROR\")\n"
|
||||
+ " console.log(res)\n"
|
||||
+ " },\n"
|
||||
+ " success: function () {\n"
|
||||
+ " ZoomMtg.join({\n"
|
||||
+ " signature: signature,\n"
|
||||
+ " apiKey: API_KEY,\n"
|
||||
+ " meetingNumber: config.meetingNumber,\n"
|
||||
+ " userName: config.userName,\n"
|
||||
+ " /* passWord: meetConfig.passWord, */\n"
|
||||
+ " error(res) {\n"
|
||||
+ " console.warn(\"JOIN ERROR\")\n"
|
||||
+ " console.log(res)\n"
|
||||
+ " }\n"
|
||||
+ " })\n"
|
||||
+ " }\n"
|
||||
+ " })\n"
|
||||
+ " </script>\n"
|
||||
+ " </body>\n"
|
||||
+ "</html>";
|
||||
// @formatter:on
|
||||
|
||||
@Override
|
||||
public boolean applies(final ProctoringWindowData data) {
|
||||
try {
|
||||
return data.connectionData.proctoringServerType == ProctoringServerType.ZOOM;
|
||||
} catch (final Exception e) {
|
||||
log.error("Failed to verify responsibility. Cause: {}", e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProctoringWindowScript(final ProctoringWindowData data) {
|
||||
final Map<String, String> args = new HashMap<>();
|
||||
args.put(ATTR_HOST, data.connectionData.serverHost);
|
||||
args.put(ATTR_ROOM_NAME, data.connectionData.roomName);
|
||||
args.put(ATTR_ACCESS_TOKEN, String.valueOf(data.connectionData.accessToken));
|
||||
args.put(ATTR_API_KEY, String.valueOf(data.connectionData.apiKey));
|
||||
if (StringUtils.isNotBlank(data.connectionData.roomKey)) {
|
||||
args.put(ATTR_ROOM_KEY, String.valueOf(data.connectionData.roomKey));
|
||||
} else {
|
||||
args.put(ATTR_ROOM_KEY, "");
|
||||
}
|
||||
args.put(ATTR_SUBJECT, data.connectionData.subject);
|
||||
args.put(ATTR_USER_NAME, data.connectionData.userName);
|
||||
|
||||
return new StringSubstitutor(args, "%%_", "_%%")
|
||||
.replace(ZOOM_WINDOW_HTML);
|
||||
}
|
||||
|
||||
}
|
|
@ -16,35 +16,99 @@ import ch.ethz.seb.sebserver.gbl.model.session.RemoteProctoringRoom;
|
|||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.proctoring.NewRoom;
|
||||
|
||||
/** Data access for RemoteProctoringRoom domain objects. */
|
||||
public interface RemoteProctoringRoomDAO {
|
||||
|
||||
Result<Collection<RemoteProctoringRoom>> getCollectingRoomsForExam(Long examId);
|
||||
/** Get all collecting room records that exists for a given exam.
|
||||
*
|
||||
* @param examId the exam identifier
|
||||
* @return Result refer to the collection of all collecting room records for the given exam or to an error when
|
||||
* happened */
|
||||
Result<Collection<RemoteProctoringRoom>> getCollectingRooms(Long examId);
|
||||
|
||||
//Result<Collection<RemoteProctoringRoom>> getRoomsOfExam(Long examId);
|
||||
/** Get all room records that exists for a given exam.
|
||||
*
|
||||
* @param examId the exam identifier
|
||||
* @return Result refer to the collection of all room records for the given exam or to an error when
|
||||
* happened */
|
||||
Result<Collection<RemoteProctoringRoom>> getRooms(Long examId);
|
||||
|
||||
/** The the room record with given identifier (PK).
|
||||
*
|
||||
* @param roomId the room record identifier
|
||||
* @return Result refer to the room record or to an error when happened */
|
||||
Result<RemoteProctoringRoom> getRoom(Long roomId);
|
||||
|
||||
/** Get the room record with specified name for a given exam.
|
||||
*
|
||||
* @param examId the exam identifier
|
||||
* @param roomName the name of the room
|
||||
* @return Result refer to the room record or to an error when happened */
|
||||
Result<RemoteProctoringRoom> getRoom(Long examId, String roomName);
|
||||
|
||||
/** Get the room name for the room with given identifier.
|
||||
*
|
||||
* @param roomId the room record identifier (PK)
|
||||
* @return Result refer to the rooms name or to an error when happened */
|
||||
Result<String> getRoomName(Long roomId);
|
||||
|
||||
/** Create the town hall room for a given exam. Uses the given room data to create the record
|
||||
*
|
||||
* @param examId the exam identifier
|
||||
* @param room the room data to save to record
|
||||
* @return Result refer to the created room record or to an error when happened */
|
||||
Result<RemoteProctoringRoom> createTownhallRoom(Long examId, NewRoom room);
|
||||
|
||||
/** Get the town hall room record for a given exam if existing.
|
||||
*
|
||||
* @param examId the exam identifier
|
||||
* @return Result refer to the town-hall room record or to an error when happened. */
|
||||
Result<RemoteProctoringRoom> getTownhallRoom(Long examId);
|
||||
|
||||
/** Delete the town-hall room record for a given exam.
|
||||
*
|
||||
* @param examId the exam identifier
|
||||
* @return Result refer to the entity key of the former town-hall room record or to an error when happened */
|
||||
Result<EntityKey> deleteTownhallRoom(Long examId);
|
||||
|
||||
/** Create a break-out room for a given exam. Uses the given room data to create the record
|
||||
*
|
||||
* @param examId the exam identifier
|
||||
* @param room the room data to save to record
|
||||
* @param connectionTokens comma separated list of SEB client connection tokens that joins the new break-out room
|
||||
* @return Result refer to the created break-out room record or to an error when happened */
|
||||
Result<RemoteProctoringRoom> createBreakOutRoom(Long examId, NewRoom room, String connectionTokens);
|
||||
|
||||
/** Delete the room record with given id.
|
||||
*
|
||||
* @param roomId the room identifier (PK)
|
||||
* @return Result refer to the entity key of the former room record or to an error when happened */
|
||||
Result<EntityKey> deleteRoom(Long roomId);
|
||||
|
||||
/** Delete all room records for a given exam.
|
||||
*
|
||||
* @param examId the exam identifier
|
||||
* @return Result refer to a collection of entity keys for all delete room records or to an error when happened */
|
||||
Result<Collection<EntityKey>> deleteRooms(Long examId);
|
||||
|
||||
/** This reserves a place in a collecting room on a given exam.
|
||||
* Creates a new collecting room record depending on the roomMaxSize and the how many connection
|
||||
* already have been collected within the actual collecting room.
|
||||
*
|
||||
* @param examId the exam identifier
|
||||
* @param roomMaxSize the maximum size of connection collected in one collecting room
|
||||
* @param newRoomFunction Function to create data for a new collecting room if needed.
|
||||
* @return Result refer to the collecting room record of place or to an error when happened */
|
||||
Result<RemoteProctoringRoom> reservePlaceInCollectingRoom(
|
||||
Long examId,
|
||||
int roomMaxSize,
|
||||
Function<Long, Result<NewRoom>> newRoomFunction);
|
||||
|
||||
/** Releases a place in the actual collecting room for a given exam.
|
||||
*
|
||||
* @param examId the exam identifier
|
||||
* @param roomId the room record identifier (PK)
|
||||
* @return Result refer to the actual collecting room record or to an error when happened */
|
||||
Result<RemoteProctoringRoom> releasePlaceInCollectingRoom(final Long examId, Long roomId);
|
||||
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@ public class RemoteProctoringRoomDAOImpl implements RemoteProctoringRoomDAO {
|
|||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public Result<Collection<RemoteProctoringRoom>> getCollectingRoomsForExam(final Long examId) {
|
||||
public Result<Collection<RemoteProctoringRoom>> getCollectingRooms(final Long examId) {
|
||||
return Result.tryCatch(() -> this.remoteProctoringRoomRecordMapper.selectByExample()
|
||||
.where(RemoteProctoringRoomRecordDynamicSqlSupport.examId, isEqualTo(examId))
|
||||
.and(RemoteProctoringRoomRecordDynamicSqlSupport.townhallRoom, isEqualTo(0))
|
||||
|
@ -66,6 +66,18 @@ public class RemoteProctoringRoomDAOImpl implements RemoteProctoringRoomDAO {
|
|||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public Result<Collection<RemoteProctoringRoom>> getRooms(final Long examId) {
|
||||
return Result.tryCatch(() -> this.remoteProctoringRoomRecordMapper.selectByExample()
|
||||
.where(RemoteProctoringRoomRecordDynamicSqlSupport.examId, isEqualTo(examId))
|
||||
.build()
|
||||
.execute()
|
||||
.stream()
|
||||
.map(this::toDomainModel)
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public Result<RemoteProctoringRoom> getRoom(final Long roomId) {
|
||||
|
@ -75,6 +87,7 @@ public class RemoteProctoringRoomDAOImpl implements RemoteProctoringRoomDAO {
|
|||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public Result<RemoteProctoringRoom> getRoom(final Long examId, final String roomName) {
|
||||
return Result.tryCatch(() -> {
|
||||
return this.remoteProctoringRoomRecordMapper.selectByExample()
|
||||
|
|
|
@ -81,7 +81,9 @@ public class LmsAPIServiceImpl implements LmsAPIService {
|
|||
return;
|
||||
}
|
||||
|
||||
log.debug("LmsSetup changed. Update cache by removing eventually used references");
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("LmsSetup changed. Update cache by removing eventually used references");
|
||||
}
|
||||
|
||||
this.cache.remove(new CacheKey(lmsSetup.getModelId(), 0));
|
||||
}
|
||||
|
@ -105,11 +107,6 @@ public class LmsAPIServiceImpl implements LmsAPIService {
|
|||
|
||||
@Override
|
||||
public Result<LmsAPITemplate> getLmsAPITemplate(final String lmsSetupId) {
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Get LmsAPITemplate for id: {}", lmsSetupId);
|
||||
}
|
||||
|
||||
return Result.tryCatch(() -> this.lmsSetupDAO
|
||||
.byModelId(lmsSetupId)
|
||||
.getOrThrow())
|
||||
|
@ -214,7 +211,6 @@ public class LmsAPIServiceImpl implements LmsAPIService {
|
|||
.forEach(this.cache::remove);
|
||||
// get from cache
|
||||
return this.cache.get(new CacheKey(lmsSetup.getModelId(), 0));
|
||||
|
||||
}
|
||||
|
||||
private LmsAPITemplate createLmsSetupTemplate(final LmsSetup lmsSetup) {
|
||||
|
|
|
@ -30,7 +30,7 @@ public interface ExamProctoringService {
|
|||
*
|
||||
* @param proctoringSettings the settings to test
|
||||
* @return Result refer to true if the settings are correct and the proctoring server can be accessed. */
|
||||
Result<Boolean> testExamProctoring(final ProctoringServiceSettings proctoringSettings);
|
||||
Result<Boolean> testExamProctoring(ProctoringServiceSettings proctoringSettings);
|
||||
|
||||
/** Gets the room connection data for a certain room for the proctor.
|
||||
*
|
||||
|
@ -45,21 +45,14 @@ public interface ExamProctoringService {
|
|||
String subject);
|
||||
|
||||
Result<ProctoringRoomConnection> getClientRoomConnection(
|
||||
final ProctoringServiceSettings proctoringSettings,
|
||||
final String connectionToken,
|
||||
final String roomName,
|
||||
final String subject);
|
||||
ProctoringServiceSettings proctoringSettings,
|
||||
String connectionToken,
|
||||
String roomName,
|
||||
String subject);
|
||||
|
||||
// Result<ProctoringRoomConnection> getClientCollectingRoomConnection(
|
||||
// final ProctoringServiceSettings proctoringSettings,
|
||||
// final String connectionToken,
|
||||
// final String roomName,
|
||||
// final String subject);
|
||||
Map<String, String> createJoinInstructionAttributes(ProctoringRoomConnection proctoringConnection);
|
||||
|
||||
Map<String, String> createJoinInstructionAttributes(
|
||||
final ProctoringRoomConnection proctoringConnection);
|
||||
|
||||
Result<Void> disposeServiceRoomsForExam(Exam exam);
|
||||
Result<Void> disposeServiceRoomsForExam(ProctoringServiceSettings proctoringSettings, Exam exam);
|
||||
|
||||
default String verifyRoomName(final String requestedRoomName, final String connectionToken) {
|
||||
if (StringUtils.isNotBlank(requestedRoomName)) {
|
||||
|
|
|
@ -70,7 +70,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
|
|||
|
||||
@Override
|
||||
public Result<Collection<RemoteProctoringRoom>> getProctoringCollectingRooms(final Long examId) {
|
||||
return this.remoteProctoringRoomDAO.getCollectingRoomsForExam(examId);
|
||||
return this.remoteProctoringRoomDAO.getCollectingRooms(examId);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -115,9 +115,14 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
|
|||
|
||||
return Result.tryCatch(() -> {
|
||||
|
||||
final ProctoringServiceSettings settings = this.examSessionService
|
||||
.getRunningExam(exam.id)
|
||||
.flatMap(this.examAdminService::getProctoringServiceSettings)
|
||||
.getOrThrow();
|
||||
|
||||
this.examAdminService
|
||||
.getExamProctoringService(exam)
|
||||
.flatMap(service -> service.disposeServiceRoomsForExam(exam))
|
||||
.flatMap(service -> service.disposeServiceRoomsForExam(settings, exam))
|
||||
.onError(error -> log.error("Failed to dispose proctoring service rooms for exam: {} / {}",
|
||||
exam.name,
|
||||
exam.externalId,
|
||||
|
|
|
@ -164,7 +164,10 @@ public class JitsiProctoringService implements ExamProctoringService {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Result<Void> disposeServiceRoomsForExam(final Exam exam) {
|
||||
public Result<Void> disposeServiceRoomsForExam(
|
||||
final ProctoringServiceSettings proctoringSettings,
|
||||
final Exam exam) {
|
||||
|
||||
// NOTE: Since Jitsi rooms are generated and disposed automatically we don't need to do anything here
|
||||
return Result.EMPTY;
|
||||
}
|
||||
|
@ -234,7 +237,7 @@ public class JitsiProctoringService implements ExamProctoringService {
|
|||
}
|
||||
attributes.put(
|
||||
ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.JITSI_TOKEN,
|
||||
proctoringConnection.accessToken);
|
||||
String.valueOf(proctoringConnection.accessToken));
|
||||
|
||||
return attributes;
|
||||
}
|
||||
|
@ -324,6 +327,8 @@ public class JitsiProctoringService implements ExamProctoringService {
|
|||
roomName,
|
||||
subject,
|
||||
token,
|
||||
null,
|
||||
null,
|
||||
clientName);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -204,17 +204,27 @@ public class ZoomProctoringService implements ExamProctoringService {
|
|||
attributes.put(
|
||||
ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.ZOOM_ROOM,
|
||||
proctoringConnection.roomName);
|
||||
attributes.put(
|
||||
ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.ZOOM_TOKEN,
|
||||
String.valueOf(proctoringConnection.accessToken));
|
||||
if (StringUtils.isNotBlank(proctoringConnection.apiKey)) {
|
||||
attributes.put(
|
||||
ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.ZOOM_API_KEY,
|
||||
String.valueOf(proctoringConnection.apiKey));
|
||||
}
|
||||
if (StringUtils.isNotBlank(proctoringConnection.roomKey)) {
|
||||
attributes.put(
|
||||
ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.ZOOM_MEETING_KEY,
|
||||
String.valueOf(proctoringConnection.roomKey));
|
||||
}
|
||||
attributes.put(
|
||||
ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.ZOOM_USER_NAME,
|
||||
proctoringConnection.userName);
|
||||
if (StringUtils.isNotBlank(proctoringConnection.subject)) {
|
||||
attributes.put(
|
||||
ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.ZOOM_ROOM_SUBJECT,
|
||||
proctoringConnection.subject);
|
||||
}
|
||||
attributes.put(
|
||||
ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.ZOOM_TOKEN,
|
||||
proctoringConnection.accessToken);
|
||||
attributes.put(
|
||||
ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.ZOOM_USER_NAME,
|
||||
proctoringConnection.userName);
|
||||
|
||||
return attributes;
|
||||
}
|
||||
|
@ -250,6 +260,8 @@ public class ZoomProctoringService implements ExamProctoringService {
|
|||
roomName,
|
||||
subject,
|
||||
jwt,
|
||||
credentials.accessToken,
|
||||
credentials.clientId,
|
||||
this.authorizationService.getUserService().getCurrentUser().getUsername());
|
||||
});
|
||||
}
|
||||
|
@ -290,18 +302,29 @@ public class ZoomProctoringService implements ExamProctoringService {
|
|||
roomName,
|
||||
subject,
|
||||
jwt,
|
||||
credentials.accessToken,
|
||||
credentials.clientId,
|
||||
clientConnection.clientConnection.userSessionId);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Void> disposeServiceRoomsForExam(final Exam exam) {
|
||||
public Result<Void> disposeServiceRoomsForExam(
|
||||
final ProctoringServiceSettings proctoringSettings,
|
||||
final Exam exam) {
|
||||
|
||||
return Result.tryCatch(() -> {
|
||||
//this.remoteProctoringRoomDAO.getRoomsOfExam(exam.id);
|
||||
this.remoteProctoringRoomDAO
|
||||
.getRooms(exam.id)
|
||||
.getOrThrow()
|
||||
.stream()
|
||||
.forEach(room -> {
|
||||
disposeBreakOutRoom(proctoringSettings, room.name)
|
||||
.onError(error -> log.warn("Failed to dispose proctoring room record for: {} cause: {}",
|
||||
room,
|
||||
error.getMessage()));
|
||||
});
|
||||
});
|
||||
// Get all rooms of the exam
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -492,7 +515,7 @@ public class ZoomProctoringService implements ExamProctoringService {
|
|||
final String jwtHeaderPart = urlEncoder.encodeToString(
|
||||
ZOOM_ACCESS_TOKEN_HEADER.getBytes(StandardCharsets.UTF_8));
|
||||
final String jwtPayload = String.format(
|
||||
ZOOM_API_ACCESS_TOKEN_PAYLOAD.replaceAll(" ", "").replaceAll("\n", ""),
|
||||
ZOOM_MEETING_ACCESS_TOKEN_PAYLOAD.replaceAll(" ", "").replaceAll("\n", ""),
|
||||
credentials.clientIdAsString(),
|
||||
iat,
|
||||
exp,
|
||||
|
|
Loading…
Reference in a new issue