SEBSERV-139 added collecting all room feature

This commit is contained in:
anhefti 2020-10-28 15:20:58 +01:00
parent 6e3549dbb7
commit 057275ba49
No known key found for this signature in database
GPG key ID: E9AD9471B6BC114D
24 changed files with 863 additions and 561 deletions

View file

@ -128,14 +128,7 @@ public final class API {
public static final String EXAM_ADMINISTRATION_CHECK_RESTRICTION_PATH_SEGMENT = "/check-seb-restriction";
public static final String EXAM_ADMINISTRATION_CHECK_IMPORTED_PATH_SEGMENT = "/check-imported";
public static final String EXAM_ADMINISTRATION_SEB_RESTRICTION_CHAPTERS_PATH_SEGMENT = "/chapters";
public static final String PROCTORING_PATH_SEGMENT = "/proctoring";
public static final String PROCTORING_ROOMS_SEGMENT = "/rooms";
public static final String PROCTORING_JOIN_ROOM_PATH_SEGMENT = "/join";
public static final String PROCTORING_LEAVE_ROOM_PATH_SEGMENT = "/leave";
public static final String PROCTORING_REJOIN_EXAM_ROOM_PATH_SEGMENT = "/rejoin-exam-room";
public static final String PROCTORING_BROADCAST_ON_PATH_SEGMENT = "/broadcast-on";
public static final String PROCTORING_BROADCAST_OFF_PATH_SEGMENT = "/broadcast-off";
public static final String PROCTORING_ROOM_CONNECTIONS_PATH_SEGMENT = "/room-connections";
public static final String EXAM_ADMINISTRATION_PROCTORING_PATH_SEGMENT = "/proctoring";
public static final String EXAM_INDICATOR_ENDPOINT = "/indicator";
@ -177,6 +170,16 @@ public final class API {
public static final String EXAM_MONITORING_SEB_CONNECTION_TOKEN_PATH_SEGMENT =
"/{" + EXAM_API_SEB_CONNECTION_TOKEN + "}";
public static final String EXAM_PROCTORING_ENDPOINT = EXAM_MONITORING_ENDPOINT + "/proctoring";
public static final String EXAM_PROCTORING_ROOMS_SEGMENT = "/rooms";
public static final String EXAM_PROCTORING_JOIN_ROOM_PATH_SEGMENT = "/join";
public static final String EXAM_PROCTORING_REJOIN_COLLECTING_ROOM_PATH_SEGMENT = "/rejoin-collecting-room";
public static final String EXAM_PROCTORING_BROADCAST_ON_PATH_SEGMENT = "/broadcast-on";
public static final String EXAM_PROCTORING_BROADCAST_OFF_PATH_SEGMENT = "/broadcast-off";
public static final String EXAM_PROCTORING_ROOM_CONNECTIONS_PATH_SEGMENT = "/room-connections";
public static final String EXAM_PROCTORING_JON_ALL_COLLECTING_ROOM = "join-all-collecting-room";
public static final String EXAM_PROCTORING_REJON_ALL_COLLECTING_ROOM = "rejoin-all-collecting-room";
public static final String SEB_CLIENT_CONNECTION_ENDPOINT = "/seb-client-connection";
public static final String SEB_CLIENT_EVENT_ENDPOINT = "/seb-client-event";

View file

@ -372,16 +372,25 @@ public class MonitoringClientConnection implements TemplateComposer {
final String roomName = urlEncoder.encodeToString(Utils.toByteArray(connectionToken));
final String examId = action.getEntityKey().modelId;
final SEBProctoringConnectionData proctoringConnectionData = this.pageService.getCurrentUser()
.getProctoringGUIService()
.registerNewSingleProcotringRoom(
examId,
roomName,
connectionData.clientConnection.userSessionId,
connectionToken)
.getOrThrow();
final ProctoringGUIService proctoringGUIService = this.pageService
.getCurrentUser()
.getProctoringGUIService();
if (!proctoringGUIService.hasRoom(roomName)) {
final SEBProctoringConnectionData proctoringConnectionData = proctoringGUIService
.registerNewSingleProcotringRoom(
examId,
roomName,
connectionData.clientConnection.userSessionId,
connectionToken)
.onError(error -> log.error(
"Failed to open single proctoring room for connection {} {}",
connectionToken,
error.getMessage()))
.getOr(null);
ProctoringGUIService.setCurrentProctoringWindowData(examId, proctoringConnectionData);
}
ProctoringGUIService.setCurrentProctoringWindowData(examId, proctoringConnectionData);
final JavaScriptExecutor javaScriptExecutor = RWT.getClient().getService(JavaScriptExecutor.class);
final String script = String.format(
OPEN_SINGEL_ROOM_SCRIPT,
@ -389,9 +398,7 @@ public class MonitoringClientConnection implements TemplateComposer {
this.guiServiceInfo.getExternalServerURIBuilder().toUriString(),
this.remoteProctoringEndpoint);
javaScriptExecutor.execute(script);
this.pageService.getCurrentUser()
.getProctoringGUIService()
.registerProctoringWindow(roomName);
proctoringGUIService.registerProctoringWindow(roomName);
return action;
}

View file

@ -108,6 +108,8 @@ public class MonitoringRunningExam implements TemplateComposer {
new LocTextKey("sebserver.monitoring.exam.connection.action.instruction.quit.all.confirm");
private static final LocTextKey CONFIRM_DISABLE_SELECTED =
new LocTextKey("sebserver.monitoring.exam.connection.action.instruction.disable.selected.confirm");
private static final LocTextKey EXAM_ROOM_NAME =
new LocTextKey("sebserver.monitoring.exam.proctoring.room.all.name");
private final ServerPushService serverPushService;
private final PageService pageService;
@ -329,6 +331,13 @@ public class MonitoringRunningExam implements TemplateComposer {
.getOr(null);
if (proctoringSettings != null && proctoringSettings.enableProctoring) {
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_CREATE_ALL_PROCTOR_ROOM)
.withEntityKey(entityKey)
.withExec(this::createCollectingAllRoom)
.noEventPropagation()
.publish();
final Map<String, Pair<RemoteProctoringRoom, TreeItem>> availableRooms = new HashMap<>();
updateRoomActions(
entityKey,
@ -348,6 +357,41 @@ public class MonitoringRunningExam implements TemplateComposer {
}
}
private PageAction createCollectingAllRoom(final PageAction action) {
final EntityKey examId = action.getEntityKey();
final ProctoringGUIService proctoringGUIService = this.pageService
.getCurrentUser()
.getProctoringGUIService();
String activeAllRoomName = proctoringGUIService.getActiveAllRoom(examId.modelId);
if (activeAllRoomName == null) {
final SEBProctoringConnectionData proctoringConnectionData = proctoringGUIService
.registerAllProcotringRoom(
examId.modelId,
this.pageService.getI18nSupport().getText(EXAM_ROOM_NAME))
.onError(error -> log.error(
"Failed to open all collecting room for exam {} {}", examId.modelId, error.getMessage()))
.getOrThrow();
ProctoringGUIService.setCurrentProctoringWindowData(
examId.modelId,
proctoringConnectionData);
activeAllRoomName = proctoringConnectionData.roomName;
}
final JavaScriptExecutor javaScriptExecutor = RWT.getClient().getService(JavaScriptExecutor.class);
final String script = String.format(
OPEN_EXAM_COLLECTION_ROOM_SCRIPT,
activeAllRoomName,
this.guiServiceInfo.getExternalServerURIBuilder().toUriString(),
this.remoteProctoringEndpoint);
javaScriptExecutor.execute(script);
proctoringGUIService.registerProctoringWindow(activeAllRoomName);
return action;
}
private void updateRoomActions(
final EntityKey entityKey,
final PageContext pageContext,
@ -359,7 +403,8 @@ public class MonitoringRunningExam implements TemplateComposer {
this.pageService.getRestService().getBuilder(GetProcotringRooms.class)
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
.call()
.getOrThrow()
.onError(error -> log.error("Failed to update proctoring rooms on GUI {}", error.getMessage()))
.getOr(Collections.emptyList())
.stream()
.forEach(room -> {
if (rooms.containsKey(room.name)) {

View file

@ -704,6 +704,11 @@ public enum ActionDefinition {
ImageIcon.PROCTOR_ROOM,
PageStateDefinitionImpl.MONITORING_RUNNING_EXAM,
ActionCategory.PROCTORING),
MONITOR_EXAM_CREATE_ALL_PROCTOR_ROOM(
new LocTextKey("sebserver.monitoring.exam.action.proctoring.allRoom"),
ImageIcon.PROCTOR_ROOM,
PageStateDefinitionImpl.MONITORING_RUNNING_EXAM,
ActionCategory.PROCTORING),
LOGS_USER_ACTIVITY_LIST(
new LocTextKey("sebserver.logs.activity.userlogs"),

View file

@ -36,7 +36,7 @@ public class GetProctoringSettings extends RestCall<ProctoringSettings> {
MediaType.APPLICATION_JSON_UTF8,
API.EXAM_ADMINISTRATION_ENDPOINT
+ API.MODEL_ID_VAR_PATH_SEGMENT
+ API.PROCTORING_PATH_SEGMENT);
+ API.EXAM_ADMINISTRATION_PROCTORING_PATH_SEGMENT);
}
}

View file

@ -36,7 +36,7 @@ public class SaveProctoringSettings extends RestCall<Exam> {
MediaType.APPLICATION_JSON_UTF8,
API.EXAM_ADMINISTRATION_ENDPOINT
+ API.MODEL_ID_VAR_PATH_SEGMENT
+ API.PROCTORING_PATH_SEGMENT);
+ API.EXAM_ADMINISTRATION_PROCTORING_PATH_SEGMENT);
}
}

View file

@ -8,8 +8,6 @@
package ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session;
import java.util.List;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
@ -26,19 +24,19 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
@Lazy
@Component
@GuiProfile
public class LeaveRemoteProctoringRoom extends RestCall<List<SEBProctoringConnectionData>> {
public class CreateCollectingAllProctoringRoom extends RestCall<SEBProctoringConnectionData> {
public LeaveRemoteProctoringRoom() {
public CreateCollectingAllProctoringRoom() {
super(new TypeKey<>(
CallType.UNDEFINED,
EntityType.EXAM_PROCTOR_DATA,
new TypeReference<List<SEBProctoringConnectionData>>() {
new TypeReference<SEBProctoringConnectionData>() {
}),
HttpMethod.POST,
MediaType.APPLICATION_FORM_URLENCODED,
API.EXAM_MONITORING_ENDPOINT
API.EXAM_PROCTORING_ENDPOINT
+ API.MODEL_ID_VAR_PATH_SEGMENT
+ API.PROCTORING_PATH_SEGMENT
+ API.PROCTORING_LEAVE_ROOM_PATH_SEGMENT);
+ API.EXAM_PROCTORING_JON_ALL_COLLECTING_ROOM);
}
}

View file

@ -0,0 +1,41 @@
/*
* Copyright (c) 2020 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.remote.webservice.api.session;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.core.type.TypeReference;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
@Lazy
@Component
@GuiProfile
public class DisposeCollectingAllProctoringRoom extends RestCall<Void> {
public DisposeCollectingAllProctoringRoom() {
super(new TypeKey<>(
CallType.UNDEFINED,
EntityType.EXAM_PROCTOR_DATA,
new TypeReference<Void>() {
}),
HttpMethod.POST,
MediaType.APPLICATION_FORM_URLENCODED,
API.EXAM_PROCTORING_ENDPOINT
+ API.MODEL_ID_VAR_PATH_SEGMENT
+ API.EXAM_PROCTORING_REJON_ALL_COLLECTING_ROOM);
}
}

View file

@ -36,9 +36,8 @@ public class GetProcotringRooms extends RestCall<Collection<RemoteProctoringRoom
}),
HttpMethod.GET,
MediaType.APPLICATION_FORM_URLENCODED,
API.EXAM_MONITORING_ENDPOINT
API.EXAM_PROCTORING_ENDPOINT
+ API.MODEL_ID_VAR_PATH_SEGMENT
+ API.PROCTORING_PATH_SEGMENT
+ API.PROCTORING_ROOMS_SEGMENT);
+ API.EXAM_PROCTORING_ROOMS_SEGMENT);
}
}

View file

@ -34,9 +34,8 @@ public class GetProctorRoomConnectionData extends RestCall<SEBProctoringConnecti
}),
HttpMethod.GET,
MediaType.APPLICATION_FORM_URLENCODED,
API.EXAM_MONITORING_ENDPOINT
+ API.MODEL_ID_VAR_PATH_SEGMENT
+ API.PROCTORING_PATH_SEGMENT);
API.EXAM_PROCTORING_ENDPOINT
+ API.MODEL_ID_VAR_PATH_SEGMENT);
}
}

View file

@ -36,10 +36,9 @@ public class GetProctorRoomConnections extends RestCall<Collection<ClientConnect
}),
HttpMethod.GET,
MediaType.APPLICATION_FORM_URLENCODED,
API.EXAM_MONITORING_ENDPOINT
API.EXAM_PROCTORING_ENDPOINT
+ API.MODEL_ID_VAR_PATH_SEGMENT
+ API.PROCTORING_PATH_SEGMENT
+ API.PROCTORING_ROOM_CONNECTIONS_PATH_SEGMENT);
+ API.EXAM_PROCTORING_ROOM_CONNECTIONS_PATH_SEGMENT);
}
}

View file

@ -34,10 +34,9 @@ public class SendJoinRemoteProctoringRoom extends RestCall<SEBProctoringConnecti
}),
HttpMethod.POST,
MediaType.APPLICATION_FORM_URLENCODED,
API.EXAM_MONITORING_ENDPOINT
API.EXAM_PROCTORING_ENDPOINT
+ API.MODEL_ID_VAR_PATH_SEGMENT
+ API.PROCTORING_PATH_SEGMENT
+ API.PROCTORING_JOIN_ROOM_PATH_SEGMENT);
+ API.EXAM_PROCTORING_JOIN_ROOM_PATH_SEGMENT);
}
}

View file

@ -33,10 +33,9 @@ public class SendProctoringBroadcastOffInstruction extends RestCall<Void> {
}),
HttpMethod.POST,
MediaType.APPLICATION_FORM_URLENCODED,
API.EXAM_MONITORING_ENDPOINT
API.EXAM_PROCTORING_ENDPOINT
+ API.MODEL_ID_VAR_PATH_SEGMENT
+ API.PROCTORING_PATH_SEGMENT
+ API.PROCTORING_BROADCAST_OFF_PATH_SEGMENT);
+ API.EXAM_PROCTORING_BROADCAST_OFF_PATH_SEGMENT);
}
}

View file

@ -33,10 +33,9 @@ public class SendProctoringBroadcastOnInstruction extends RestCall<Void> {
}),
HttpMethod.POST,
MediaType.APPLICATION_FORM_URLENCODED,
API.EXAM_MONITORING_ENDPOINT
API.EXAM_PROCTORING_ENDPOINT
+ API.MODEL_ID_VAR_PATH_SEGMENT
+ API.PROCTORING_PATH_SEGMENT
+ API.PROCTORING_BROADCAST_ON_PATH_SEGMENT);
+ API.EXAM_PROCTORING_BROADCAST_ON_PATH_SEGMENT);
}
}

View file

@ -33,10 +33,9 @@ public class SendRejoinExamCollectionRoom extends RestCall<Void> {
}),
HttpMethod.POST,
MediaType.APPLICATION_FORM_URLENCODED,
API.EXAM_MONITORING_ENDPOINT
API.EXAM_PROCTORING_ENDPOINT
+ API.MODEL_ID_VAR_PATH_SEGMENT
+ API.PROCTORING_PATH_SEGMENT
+ API.PROCTORING_REJOIN_EXAM_ROOM_PATH_SEGMENT);
+ API.EXAM_PROCTORING_REJOIN_COLLECTING_ROOM_PATH_SEGMENT);
}
}

View file

@ -14,6 +14,7 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
@ -28,6 +29,8 @@ import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.model.exam.SEBProctoringConnectionData;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.CreateCollectingAllProctoringRoom;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.DisposeCollectingAllProctoringRoom;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.SendJoinRemoteProctoringRoom;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.SendRejoinExamCollectionRoom;
@ -84,26 +87,54 @@ public class ProctoringGUIService {
new ProctoringWindowData(examId, data));
}
public boolean hasRoom(final String roomName) {
return this.rooms.containsKey(roomName);
}
public String getActiveAllRoom(final String examId) {
return this.rooms
.values()
.stream()
.filter(data -> Objects.equals(data.examId, examId) && data.connections.isEmpty())
.findFirst()
.map(data -> data.roomName)
.orElseGet(() -> null);
}
public Result<SEBProctoringConnectionData> registerNewSingleProcotringRoom(
final String examId,
final String roomName,
final String subject,
final String connectionToken) {
return Result.tryCatch(() -> {
final SEBProctoringConnectionData connection =
this.restService.getBuilder(SendJoinRemoteProctoringRoom.class)
.withURIVariable(API.PARAM_MODEL_ID, examId)
.withFormParam(SEBProctoringConnectionData.ATTR_ROOM_NAME, roomName)
.withFormParam(SEBProctoringConnectionData.ATTR_SUBJECT, subject)
.withFormParam(API.EXAM_API_SEB_CONNECTION_TOKEN, connectionToken)
.call()
.getOrThrow();
return this.restService.getBuilder(SendJoinRemoteProctoringRoom.class)
.withURIVariable(API.PARAM_MODEL_ID, examId)
.withFormParam(SEBProctoringConnectionData.ATTR_ROOM_NAME, roomName)
.withFormParam(SEBProctoringConnectionData.ATTR_SUBJECT, subject)
.withFormParam(API.EXAM_API_SEB_CONNECTION_TOKEN, connectionToken)
.call()
.map(connection -> {
this.rooms.put(roomName, new RoomConnectionData(roomName, examId, connectionToken));
this.openWindows.add(roomName);
return connection;
});
}
this.rooms.put(roomName, new RoomConnectionData(roomName, examId, connectionToken));
this.openWindows.add(roomName);
return connection;
});
public Result<SEBProctoringConnectionData> registerAllProcotringRoom(
final String examId,
final String subject) {
return this.restService.getBuilder(CreateCollectingAllProctoringRoom.class)
.withURIVariable(API.PARAM_MODEL_ID, examId)
.withFormParam(SEBProctoringConnectionData.ATTR_SUBJECT, subject)
.call()
.map(connection -> {
this.rooms.put(
connection.roomName,
new RoomConnectionData(connection.roomName, examId));
this.openWindows.add(connection.roomName);
return connection;
});
}
public Result<SEBProctoringConnectionData> registerNewProcotringRoom(
@ -112,22 +143,19 @@ public class ProctoringGUIService {
final String subject,
final Collection<String> connectionTokens) {
return Result.tryCatch(() -> {
final SEBProctoringConnectionData connection =
this.restService.getBuilder(SendJoinRemoteProctoringRoom.class)
.withURIVariable(API.PARAM_MODEL_ID, examId)
.withFormParam(SEBProctoringConnectionData.ATTR_ROOM_NAME, roomName)
.withFormParam(SEBProctoringConnectionData.ATTR_SUBJECT, subject)
.withFormParam(
API.EXAM_API_SEB_CONNECTION_TOKEN,
StringUtils.join(connectionTokens, Constants.LIST_SEPARATOR_CHAR))
.call()
.getOrThrow();
this.rooms.put(roomName, new RoomConnectionData(roomName, examId, connectionTokens));
this.openWindows.add(roomName);
return connection;
});
return this.restService.getBuilder(SendJoinRemoteProctoringRoom.class)
.withURIVariable(API.PARAM_MODEL_ID, examId)
.withFormParam(SEBProctoringConnectionData.ATTR_ROOM_NAME, roomName)
.withFormParam(SEBProctoringConnectionData.ATTR_SUBJECT, subject)
.withFormParam(
API.EXAM_API_SEB_CONNECTION_TOKEN,
StringUtils.join(connectionTokens, Constants.LIST_SEPARATOR_CHAR))
.call()
.map(connection -> {
this.rooms.put(roomName, new RoomConnectionData(roomName, examId, connectionTokens));
this.openWindows.add(roomName);
return connection;
});
}
public void addConnectionsToRoom(
@ -147,7 +175,9 @@ public class ProctoringGUIService {
API.EXAM_API_SEB_CONNECTION_TOKEN,
StringUtils.join(connectionTokens, Constants.LIST_SEPARATOR_CHAR))
.call()
.getOrThrow();
.onError(error -> log.error("Failed to add connection to proctoring room: {} {}",
room,
error.getMessage()));
roomConnectionData.connections.addAll(connectionTokens);
}
}
@ -164,15 +194,24 @@ public class ProctoringGUIService {
final RoomConnectionData roomConnectionData = this.rooms.remove(name);
if (roomConnectionData != null) {
// send instruction to leave this room and join the own exam collection room
this.restService.getBuilder(SendRejoinExamCollectionRoom.class)
.withURIVariable(API.PARAM_MODEL_ID, roomConnectionData.examId)
.withFormParam(
API.EXAM_API_SEB_CONNECTION_TOKEN,
StringUtils.join(roomConnectionData.connections, Constants.LIST_SEPARATOR_CHAR))
.call()
.getOrThrow();
if (!roomConnectionData.connections.isEmpty()) {
this.restService.getBuilder(SendRejoinExamCollectionRoom.class)
.withURIVariable(API.PARAM_MODEL_ID, roomConnectionData.examId)
.withFormParam(
API.EXAM_API_SEB_CONNECTION_TOKEN,
StringUtils.join(roomConnectionData.connections, Constants.LIST_SEPARATOR_CHAR))
.call()
.onError(error -> log.error("Failed to close proctoring room: {} {}",
name,
error.getMessage()));
} else {
this.restService.getBuilder(DisposeCollectingAllProctoringRoom.class)
.withURIVariable(API.PARAM_MODEL_ID, roomConnectionData.examId)
.call()
.onError(error -> log.error("Failed to close proctoring room: {} {}",
name,
error.getMessage()));
}
}
}

View file

@ -11,6 +11,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings.ProctoringServerType;
import ch.ethz.seb.sebserver.gbl.model.exam.SEBProctoringConnectionData;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
import ch.ethz.seb.sebserver.gbl.util.Result;
public interface ExamProctoringService {
@ -20,14 +21,18 @@ public interface ExamProctoringService {
Result<Boolean> testExamProctoring(final ProctoringSettings examProctoring);
Result<SEBProctoringConnectionData> createProctorPublicRoomConnection(
final ProctoringSettings examProctoring,
final ProctoringSettings proctoringSettings,
final String roomName,
final String subject);
Result<SEBProctoringConnectionData> getClientExamCollectionRoomConnectionData(
final ProctoringSettings examProctoring,
final ProctoringSettings proctoringSettings,
final String connectionToken);
Result<SEBProctoringConnectionData> getClientExamCollectionRoomConnectionData(
final ProctoringSettings proctoringSettings,
final ClientConnection connection);
Result<SEBProctoringConnectionData> getClientExamCollectionRoomConnectionData(
final ProctoringSettings proctoringSettings,
final String connectionToken,

View file

@ -13,6 +13,7 @@ import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.Base64.Encoder;
import java.util.Collection;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
@ -26,6 +27,7 @@ import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings.ProctoringServerType;
import ch.ethz.seb.sebserver.gbl.model.exam.SEBProctoringConnectionData;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
import ch.ethz.seb.sebserver.gbl.model.session.RemoteProctoringRoom;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
@ -99,15 +101,27 @@ public class ExamJITSIProctoringService implements ExamProctoringService {
final ProctoringSettings proctoringSettings,
final String connectionToken) {
return this.examSessionService
.getConnectionData(connectionToken)
.flatMap(connection -> getClientExamCollectionRoomConnectionData(
proctoringSettings,
connection.clientConnection));
}
@Override
public Result<SEBProctoringConnectionData> getClientExamCollectionRoomConnectionData(
final ProctoringSettings proctoringSettings,
final ClientConnection connection) {
return Result.tryCatch(() -> {
final ClientConnectionData clientConnection = this.examSessionService
.getConnectionData(connectionToken)
.getOrThrow();
final RemoteProctoringRoom room = this.examSessionService
final Collection<RemoteProctoringRoom> remoteProctoringRooms = this.examSessionService
.getExamSessionCacheService()
.getRemoteProctoringRooms(clientConnection.clientConnection.examId)
.getRemoteProctoringRooms(connection.examId);
final RemoteProctoringRoom room = remoteProctoringRooms
.stream()
.filter(r -> r.id.equals(clientConnection.clientConnection.remoteProctoringRoomId))
.filter(r -> r.id.equals(connection.remoteProctoringRoomId))
.findFirst()
.orElseGet(() -> {
throw new RuntimeException("No exam proctoring room found for clientConnection");
@ -119,10 +133,10 @@ public class ExamJITSIProctoringService implements ExamProctoringService {
proctoringSettings.serverURL,
proctoringSettings.appKey,
proctoringSettings.getAppSecret(),
clientConnection.clientConnection.userSessionId,
connection.userSessionId,
"seb-client",
room.name,
clientConnection.clientConnection.userSessionId,
connection.userSessionId,
forExam(proctoringSettings))
.getOrThrow();
});

View file

@ -124,7 +124,8 @@ public class ExamProcotringRoomServiceImpl implements ExamProcotringRoomService
.build()
.execute();
flagUpdated(toUpdate).stream()
flagUpdated(toUpdate)
.stream()
.forEach(cc -> {
if (ConnectionStatus.ACTIVE.name().equals(cc.getStatus())) {
assignToRoom(cc);
@ -156,13 +157,14 @@ public class ExamProcotringRoomServiceImpl implements ExamProcotringRoomService
ACTIVE_COLLECTING_ALL_ROOM_ATTRIBUTE_NAME);
}
private Result<String> getActiveCollectingAllRoom(final Long examId) {
private String getActiveCollectingAllRoom(final Long examId) {
return this.additionalAttributesDAO
.getAdditionalAttribute(
EntityType.EXAM,
examId,
ACTIVE_COLLECTING_ALL_ROOM_ATTRIBUTE_NAME)
.map(attr -> attr.getValue());
.map(attr -> attr.getValue())
.getOr(null);
}
// TODO considering doing bulk update here
@ -202,7 +204,13 @@ public class ExamProcotringRoomServiceImpl implements ExamProcotringRoomService
proctoringRoom.id,
0));
this.examSessionCacheService.evictRemoteProctoringRooms(cc.getExamId());
applyProcotringInstruction(cc.getExamId(), cc.getConnectionToken(), proctoringRoom.name);
this.examSessionCacheService.evictClientConnection(cc.getConnectionToken());
final String activeCollectingAllRoom = getActiveCollectingAllRoom(cc.getExamId());
if (activeCollectingAllRoom != null) {
applyProcotringInstruction(cc.getExamId(), cc.getConnectionToken(), activeCollectingAllRoom);
} else {
applyProcotringInstruction(cc.getExamId(), cc.getConnectionToken(), proctoringRoom.name);
}
}
} catch (final Exception e) {
log.error("Failed to process proctoring room update for client connection: {}", cc.getConnectionToken(), e);

View file

@ -21,7 +21,6 @@ import java.util.stream.Collectors;
import org.apache.commons.lang3.BooleanUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.access.AccessDeniedException;
@ -314,9 +313,15 @@ public class ExamSessionServiceImpl implements ExamSessionService {
@Override
public Result<ClientConnectionData> getConnectionData(final String connectionToken) {
return Result.tryCatch(() -> {
final Cache cache = this.cacheManager.getCache(ExamSessionCacheService.CACHE_NAME_ACTIVE_CLIENT_CONNECTION);
return cache.get(connectionToken, ClientConnectionData.class);
final ClientConnectionDataInternal activeClientConnection = this.examSessionCacheService
.getActiveClientConnection(connectionToken);
if (activeClientConnection == null) {
throw new NoSuchElementException("Client Connection with token: " + connectionToken);
}
return activeClientConnection;
});
}

View file

@ -383,7 +383,7 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
@RequestMapping(
path = API.MODEL_ID_VAR_PATH_SEGMENT
+ API.PROCTORING_PATH_SEGMENT,
+ API.EXAM_ADMINISTRATION_PROCTORING_PATH_SEGMENT,
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public ProctoringSettings getExamProctoring(
@ -402,7 +402,7 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
@RequestMapping(
path = API.MODEL_ID_VAR_PATH_SEGMENT
+ API.PROCTORING_PATH_SEGMENT,
+ API.EXAM_ADMINISTRATION_PROCTORING_PATH_SEGMENT,
method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public Exam saveExamProctoring(

View file

@ -9,19 +9,14 @@
package ch.ethz.seb.sebserver.webservice.weblayer.api;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -44,25 +39,16 @@ import ch.ethz.seb.sebserver.gbl.api.authorization.PrivilegeType;
import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.Page;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings;
import ch.ethz.seb.sebserver.gbl.model.exam.SEBProctoringConnectionData;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
import ch.ethz.seb.sebserver.gbl.model.session.ClientInstruction;
import ch.ethz.seb.sebserver.gbl.model.session.ClientInstruction.InstructionType;
import ch.ethz.seb.sebserver.gbl.model.session.RemoteProctoringRoom;
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationService;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.PermissionDeniedException;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.UserService;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamAdminService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamProcotringRoomService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamProctoringService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientConnectionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBInstructionService;
@ -76,27 +62,21 @@ public class ExamMonitoringController {
private final SEBClientConnectionService sebClientConnectionService;
private final ExamSessionService examSessionService;
private final ExamAdminService examAdminService;
private final SEBInstructionService sebInstructionService;
private final AuthorizationService authorization;
private final PaginationService paginationService;
private final ExamProcotringRoomService examProcotringRoomService;
public ExamMonitoringController(
final ExamAdminService examAdminService,
final SEBClientConnectionService sebClientConnectionService,
final SEBInstructionService sebInstructionService,
final AuthorizationService authorization,
final PaginationService paginationService,
final ExamProcotringRoomService examProcotringRoomService) {
final PaginationService paginationService) {
this.examAdminService = examAdminService;
this.sebClientConnectionService = sebClientConnectionService;
this.examSessionService = sebClientConnectionService.getExamSessionService();
this.sebInstructionService = sebInstructionService;
this.authorization = authorization;
this.paginationService = paginationService;
this.examProcotringRoomService = examProcotringRoomService;
}
/** This is called by Spring to initialize the WebDataBinder and is used here to
@ -289,387 +269,6 @@ public class ExamMonitoringController {
}
//***********************************************************************************************
//**** Proctoring
@RequestMapping(
path = API.MODEL_ID_VAR_PATH_SEGMENT
+ API.PROCTORING_PATH_SEGMENT
+ API.PROCTORING_ROOMS_SEGMENT,
method = RequestMethod.GET,
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public Collection<RemoteProctoringRoom> getDefaultProcotringRoomsOfExam(@RequestParam(
name = API.PARAM_INSTITUTION_ID,
required = true,
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
@PathVariable(name = API.PARAM_MODEL_ID) final Long examId) {
return this.examProcotringRoomService
.getProctoringRooms(examId)
.getOrThrow();
}
@RequestMapping(
path = API.MODEL_ID_VAR_PATH_SEGMENT
+ API.PROCTORING_PATH_SEGMENT,
method = RequestMethod.GET,
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public SEBProctoringConnectionData getProctorRoomData(
@RequestParam(
name = API.PARAM_INSTITUTION_ID,
required = true,
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
@PathVariable(name = API.PARAM_MODEL_ID) final Long examId,
@RequestParam(name = SEBProctoringConnectionData.ATTR_ROOM_NAME, required = true) final String roomName,
@RequestParam(name = SEBProctoringConnectionData.ATTR_SUBJECT, required = false) final String subject) {
this.authorization.check(
PrivilegeType.READ,
EntityType.EXAM,
institutionId);
this.authorization.checkRead(
this.examSessionService.getExamDAO().byPK(examId).getOrThrow());
return this.examSessionService.getRunningExam(examId)
.flatMap(this.authorization::checkRead)
.flatMap(this.examAdminService::getExamProctoring)
.flatMap(proc -> this.examAdminService
.getExamProctoringService(proc.serverType)
.flatMap(s -> s.createProctorPublicRoomConnection(
proc,
roomName,
StringUtils.isNoneBlank(subject) ? subject : roomName)))
.getOrThrow();
}
@RequestMapping(
path = API.MODEL_ID_VAR_PATH_SEGMENT
+ API.PROCTORING_PATH_SEGMENT
+ API.PROCTORING_ROOM_CONNECTIONS_PATH_SEGMENT,
method = RequestMethod.GET,
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public Collection<ClientConnection> getProctorRoomConnectionData(
@RequestParam(
name = API.PARAM_INSTITUTION_ID,
required = true,
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
@PathVariable(name = API.PARAM_MODEL_ID) final Long examId,
@RequestParam(
name = Domain.REMOTE_PROCTORING_ROOM.ATTR_ID,
required = true) final String roomName) {
this.authorization.check(
PrivilegeType.READ,
EntityType.EXAM,
institutionId);
this.authorization.checkRead(
this.examSessionService.getExamDAO().byPK(examId).getOrThrow());
return this.examProcotringRoomService.getRoomConnections(examId, roomName)
.getOrThrow();
}
@RequestMapping(
path = API.MODEL_ID_VAR_PATH_SEGMENT
+ API.PROCTORING_PATH_SEGMENT
+ API.PROCTORING_BROADCAST_ON_PATH_SEGMENT,
method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public void sendBroadcastOn(
@RequestParam(
name = API.PARAM_INSTITUTION_ID,
required = true,
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
@PathVariable(name = API.PARAM_MODEL_ID) final Long examId,
@RequestParam(
name = Domain.REMOTE_PROCTORING_ROOM.ATTR_ID,
required = false) final String roomName,
@RequestParam(
name = API.EXAM_API_SEB_CONNECTION_TOKEN,
required = false) final String connectionTokens,
@RequestParam(
name = ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_RECEIVE_AUDIO,
required = false) final Boolean sendReceiveAudio,
@RequestParam(
name = ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_RECEIVE_VIDEO,
required = false) final Boolean sendReceiveVideo,
@RequestParam(
name = ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_ALLOW_CHAT,
required = false) final Boolean sendAllowChat) {
this.authorization.check(
PrivilegeType.READ,
EntityType.EXAM,
institutionId);
this.authorization.checkRead(
this.examSessionService.getExamDAO().byPK(examId).getOrThrow());
final Map<String, String> attributes = createProctorInstructionAttributes(
sendReceiveAudio,
sendReceiveVideo,
sendAllowChat,
Constants.TRUE_STRING);
sendProctoringInstructions(examId, roomName, connectionTokens, attributes);
}
@RequestMapping(
path = API.MODEL_ID_VAR_PATH_SEGMENT
+ API.PROCTORING_PATH_SEGMENT
+ API.PROCTORING_BROADCAST_OFF_PATH_SEGMENT,
method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public void sendBroadcastOff(
@RequestParam(
name = API.PARAM_INSTITUTION_ID,
required = true,
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
@PathVariable(name = API.PARAM_MODEL_ID) final Long examId,
@RequestParam(
name = Domain.REMOTE_PROCTORING_ROOM.ATTR_ID,
required = true) final String roomName,
@RequestParam(
name = API.EXAM_API_SEB_CONNECTION_TOKEN,
required = true) final String connectionTokens,
@RequestParam(
name = ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_RECEIVE_AUDIO,
required = false) final Boolean sendReceiveAudio,
@RequestParam(
name = ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_RECEIVE_VIDEO,
required = false) final Boolean sendReceiveVideo,
@RequestParam(
name = ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_ALLOW_CHAT,
required = false) final Boolean sendAllowChat) {
this.authorization.check(
PrivilegeType.READ,
EntityType.EXAM,
institutionId);
this.authorization.checkRead(
this.examSessionService.getExamDAO().byPK(examId).getOrThrow());
final Map<String, String> attributes = createProctorInstructionAttributes(
sendReceiveAudio,
sendReceiveVideo,
sendAllowChat,
Constants.FALSE_STRING);
if (attributes.isEmpty()) {
log.warn("Missing reconfigure instruction attributes. Skip sending empty instruction to SEB clients");
return;
}
sendProctoringInstructions(examId, roomName, connectionTokens, attributes);
}
@RequestMapping(
path = API.MODEL_ID_VAR_PATH_SEGMENT
+ API.PROCTORING_PATH_SEGMENT
+ API.PROCTORING_REJOIN_EXAM_ROOM_PATH_SEGMENT,
method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public void sendRejoinExamCollectionRoomToClients(
@RequestParam(
name = API.PARAM_INSTITUTION_ID,
required = true,
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
@PathVariable(name = API.PARAM_MODEL_ID) final Long examId,
@RequestParam(
name = API.EXAM_API_SEB_CONNECTION_TOKEN,
required = true) final String connectionTokens) {
this.authorization.check(
PrivilegeType.READ,
EntityType.EXAM,
institutionId);
this.authorization.checkRead(
this.examSessionService.getExamDAO().byPK(examId).getOrThrow());
final ProctoringSettings settings = this.examSessionService
.getRunningExam(examId)
.flatMap(this.authorization::checkRead)
.flatMap(this.examAdminService::getExamProctoring)
.getOrThrow();
final ExamProctoringService examProctoringService = this.examAdminService
.getExamProctoringService(settings.serverType)
.getOrThrow();
Arrays.asList(StringUtils.split(connectionTokens, Constants.LIST_SEPARATOR))
.stream()
.forEach(connectionToken -> {
final Result<Void> result = examProctoringService
.getClientExamCollectionRoomConnectionData(
settings,
connectionToken)
.flatMap(data -> this.sendJoinInstruction(examId, connectionTokens, data));
if (result.hasError()) {
// TODO log
}
});
}
@RequestMapping(
path = API.MODEL_ID_VAR_PATH_SEGMENT
+ API.PROCTORING_PATH_SEGMENT
+ API.PROCTORING_JOIN_ROOM_PATH_SEGMENT,
method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public SEBProctoringConnectionData sendJoinProctoringRoomToClients(
@RequestParam(
name = API.PARAM_INSTITUTION_ID,
required = true,
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
@PathVariable(name = API.PARAM_MODEL_ID) final Long examId,
@RequestParam(
name = SEBProctoringConnectionData.ATTR_ROOM_NAME,
required = true) final String roomName,
@RequestParam(
name = SEBProctoringConnectionData.ATTR_SUBJECT,
required = false) final String subject,
@RequestParam(
name = API.EXAM_API_SEB_CONNECTION_TOKEN,
required = true) final String connectionTokens) {
this.authorization.check(
PrivilegeType.READ,
EntityType.EXAM,
institutionId);
this.authorization.checkRead(
this.examSessionService.getExamDAO().byPK(examId).getOrThrow());
final ProctoringSettings settings = this.examSessionService
.getRunningExam(examId)
.flatMap(this.authorization::checkRead)
.flatMap(this.examAdminService::getExamProctoring)
.getOrThrow();
final ExamProctoringService examProctoringService = this.examAdminService
.getExamProctoringService(settings.serverType)
.getOrThrow();
if (StringUtils.isNotBlank(connectionTokens)) {
final boolean single = connectionTokens.contains(Constants.LIST_SEPARATOR);
(single
? Arrays.asList(StringUtils.split(connectionTokens, Constants.LIST_SEPARATOR))
: Arrays.asList(connectionTokens))
.stream()
.map(connectionToken -> {
final SEBProctoringConnectionData data = (single)
? examProctoringService
.getClientRoomConnectionData(settings, connectionToken)
.getOrThrow()
: examProctoringService
.getClientRoomConnectionData(
settings,
connectionToken,
roomName,
(StringUtils.isNotBlank(subject)) ? subject : roomName)
.getOrThrow();
sendJoinInstruction(examId, connectionToken, data)
.onError(error -> log.error(
"Failed to send proctoring leave instruction to client: {} ",
connectionToken, error));
return data;
}).collect(Collectors.toList());
}
return examProctoringService.createProctorPublicRoomConnection(
settings,
roomName,
(StringUtils.isNotBlank(subject)) ? subject : roomName)
.getOrThrow();
}
private void sendProctoringInstructions(
final Long examId,
final String roomName,
final String connectionTokens,
final Map<String, String> attributes) {
if (attributes.isEmpty()) {
log.warn("Missing reconfigure instruction attributes. Skip sending empty instruction to SEB clients");
return;
}
if (StringUtils.isNotBlank(connectionTokens)) {
final boolean single = connectionTokens.contains(Constants.LIST_SEPARATOR);
(single
? Arrays.asList(StringUtils.split(connectionTokens, Constants.LIST_SEPARATOR))
: Arrays.asList(connectionTokens))
.stream()
.forEach(connectionToken -> {
this.sebInstructionService.registerInstruction(
examId,
InstructionType.SEB_RECONFIGURE_SETTINGS,
attributes,
connectionToken,
true)
.onError(error -> log.error(
"Failed to register reconfiguring instruction for connection: {}",
connectionToken,
error));
});
} else if (StringUtils.isNotBlank(roomName)) {
this.examProcotringRoomService.getRoomConnections(examId, roomName)
.getOrThrow()
.stream()
.forEach(connection -> {
this.sebInstructionService.registerInstruction(
examId,
InstructionType.SEB_RECONFIGURE_SETTINGS,
attributes,
connection.connectionToken,
true)
.onError(error -> log.error(
"Failed to register reconfiguring instruction for connection: {}",
connection.connectionToken,
error));
});
} else {
throw new RuntimeException("API attribute validation error: missing "
+ Domain.REMOTE_PROCTORING_ROOM.ATTR_ID + " and/or" +
API.EXAM_API_SEB_CONNECTION_TOKEN + " attribute");
}
}
private Map<String, String> createProctorInstructionAttributes(
final Boolean sendReceiveAudio,
final Boolean sendReceiveVideo,
final Boolean sendAllowChat,
final String flagValue) {
final Map<String, String> attributes = new HashMap<>();
if (BooleanUtils.isTrue(sendReceiveAudio)) {
attributes.put(
ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_RECEIVE_AUDIO,
flagValue);
}
if (BooleanUtils.isTrue(sendReceiveVideo)) {
attributes.put(
ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_RECEIVE_VIDEO,
flagValue);
}
if (BooleanUtils.isTrue(sendAllowChat)) {
attributes.put(
ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_ALLOW_CHAT,
flagValue);
}
return attributes;
}
//**** Proctoring
//***********************************************************************************************
private boolean hasRunningExamPrivilege(final Long examId, final Long institution) {
return hasRunningExamPrivilege(
this.examSessionService.getRunningExam(examId).getOr(null),
@ -685,53 +284,4 @@ public class ExamMonitoringController {
return exam.institutionId.equals(institution) && exam.isOwner(userId);
}
private Result<Void> sendJoinInstruction(
final Long examId,
final String connectionToken,
final SEBProctoringConnectionData data) {
final Map<String, String> attributes = new HashMap<>();
attributes.put(
ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.SERVICE_TYPE,
ProctoringSettings.ProctoringServerType.JITSI_MEET.name());
attributes.put(
ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.METHOD,
ClientInstruction.ProctoringInstructionMethod.JOIN.name());
attributes.put(
ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.JITSI_URL,
data.serverURL);
attributes.put(
ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.JITSI_ROOM,
data.roomName);
attributes.put(
ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.JITSI_TOKEN,
data.accessToken);
return this.sebInstructionService.registerInstruction(
examId,
InstructionType.SEB_PROCTORING,
attributes,
connectionToken,
true);
}
// private Result<Void> sendLeaveInstruction(
// final Long examId,
// final String connectionToken,
// final SEBProctoringConnectionData data) {
//
// return sendProctorInstruction(
// examId,
// connectionToken,
// data,
// ClientInstruction.ProctoringInstructionMethod.LEAVE.name());
// }
// PRIVATE RESULT<VOID> SENDPROCTORINSTRUCTION(
// FINAL LONG EXAMID,
// FINAL STRING CONNECTIONTOKEN,
// FINAL SEBPROCTORINGCONNECTIONDATA DATA,
// FINAL STRING METHOD) {
//
// }
}

View file

@ -0,0 +1,587 @@
/*
* Copyright (c) 2020 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.webservice.weblayer.api;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.api.authorization.PrivilegeType;
import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings;
import ch.ethz.seb.sebserver.gbl.model.exam.SEBProctoringConnectionData;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus;
import ch.ethz.seb.sebserver.gbl.model.session.ClientInstruction;
import ch.ethz.seb.sebserver.gbl.model.session.ClientInstruction.InstructionType;
import ch.ethz.seb.sebserver.gbl.model.session.RemoteProctoringRoom;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationService;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.UserService;
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamAdminService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamProcotringRoomService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamProctoringService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBInstructionService;
@WebServiceProfile
@RestController
@RequestMapping("${sebserver.webservice.api.admin.endpoint}" + API.EXAM_PROCTORING_ENDPOINT)
public class ExamProctoringController {
private static final Logger log = LoggerFactory.getLogger(ExamProctoringController.class);
private final ExamProcotringRoomService examProcotringRoomService;
private final ExamAdminService examAdminService;
private final SEBInstructionService sebInstructionService;
private final AuthorizationService authorization;
private final ExamSessionService examSessionService;
public ExamProctoringController(
final ExamProcotringRoomService examProcotringRoomService,
final ExamAdminService examAdminService,
final SEBInstructionService sebInstructionService,
final AuthorizationService authorization,
final ExamSessionService examSessionService) {
this.examProcotringRoomService = examProcotringRoomService;
this.examAdminService = examAdminService;
this.sebInstructionService = sebInstructionService;
this.authorization = authorization;
this.examSessionService = examSessionService;
}
/** This is called by Spring to initialize the WebDataBinder and is used here to
* initialize the default value binding for the institutionId request-parameter
* that has the current users insitutionId as default.
*
* See also UserService.addUsersInstitutionDefaultPropertySupport */
@InitBinder
public void initBinder(final WebDataBinder binder) {
this.authorization
.getUserService()
.addUsersInstitutionDefaultPropertySupport(binder);
}
@RequestMapping(
path = API.MODEL_ID_VAR_PATH_SEGMENT
+ API.EXAM_PROCTORING_ROOMS_SEGMENT,
method = RequestMethod.GET,
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public Collection<RemoteProctoringRoom> getDefaultProcotringRoomsOfExam(
@RequestParam(
name = API.PARAM_INSTITUTION_ID,
required = true,
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
@PathVariable(name = API.PARAM_MODEL_ID) final Long examId) {
return this.examProcotringRoomService
.getProctoringRooms(examId)
.getOrThrow();
}
@RequestMapping(
path = API.MODEL_ID_VAR_PATH_SEGMENT,
method = RequestMethod.GET,
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public SEBProctoringConnectionData getProctorRoomData(
@RequestParam(
name = API.PARAM_INSTITUTION_ID,
required = true,
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
@PathVariable(name = API.PARAM_MODEL_ID) final Long examId,
@RequestParam(name = SEBProctoringConnectionData.ATTR_ROOM_NAME, required = true) final String roomName,
@RequestParam(name = SEBProctoringConnectionData.ATTR_SUBJECT, required = false) final String subject) {
checkAccess(institutionId, examId);
return this.examSessionService.getRunningExam(examId)
.flatMap(this.authorization::checkRead)
.flatMap(this.examAdminService::getExamProctoring)
.flatMap(proc -> this.examAdminService
.getExamProctoringService(proc.serverType)
.flatMap(s -> s.createProctorPublicRoomConnection(
proc,
roomName,
StringUtils.isNoneBlank(subject) ? subject : roomName)))
.getOrThrow();
}
@RequestMapping(
path = API.MODEL_ID_VAR_PATH_SEGMENT
+ API.EXAM_PROCTORING_ROOM_CONNECTIONS_PATH_SEGMENT,
method = RequestMethod.GET,
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public Collection<ClientConnection> getProctorRoomConnectionData(
@RequestParam(
name = API.PARAM_INSTITUTION_ID,
required = true,
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
@PathVariable(name = API.PARAM_MODEL_ID) final Long examId,
@RequestParam(
name = Domain.REMOTE_PROCTORING_ROOM.ATTR_ID,
required = true) final String roomName) {
checkAccess(institutionId, examId);
return this.examProcotringRoomService.getRoomConnections(examId, roomName)
.getOrThrow();
}
@RequestMapping(
path = API.MODEL_ID_VAR_PATH_SEGMENT
+ API.EXAM_PROCTORING_BROADCAST_ON_PATH_SEGMENT,
method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public void sendBroadcastOn(
@RequestParam(
name = API.PARAM_INSTITUTION_ID,
required = true,
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
@PathVariable(name = API.PARAM_MODEL_ID) final Long examId,
@RequestParam(
name = Domain.REMOTE_PROCTORING_ROOM.ATTR_ID,
required = false) final String roomName,
@RequestParam(
name = API.EXAM_API_SEB_CONNECTION_TOKEN,
required = false) final String connectionTokens,
@RequestParam(
name = ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_RECEIVE_AUDIO,
required = false) final Boolean sendReceiveAudio,
@RequestParam(
name = ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_RECEIVE_VIDEO,
required = false) final Boolean sendReceiveVideo,
@RequestParam(
name = ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_ALLOW_CHAT,
required = false) final Boolean sendAllowChat) {
checkAccess(institutionId, examId);
final Map<String, String> attributes = createProctorInstructionAttributes(
sendReceiveAudio,
sendReceiveVideo,
sendAllowChat,
Constants.TRUE_STRING);
sendProctoringInstructions(examId, roomName, connectionTokens, attributes);
}
@RequestMapping(
path = API.MODEL_ID_VAR_PATH_SEGMENT
+ API.EXAM_PROCTORING_BROADCAST_OFF_PATH_SEGMENT,
method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public void sendBroadcastOff(
@RequestParam(
name = API.PARAM_INSTITUTION_ID,
required = true,
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
@PathVariable(name = API.PARAM_MODEL_ID) final Long examId,
@RequestParam(
name = Domain.REMOTE_PROCTORING_ROOM.ATTR_ID,
required = true) final String roomName,
@RequestParam(
name = API.EXAM_API_SEB_CONNECTION_TOKEN,
required = true) final String connectionTokens,
@RequestParam(
name = ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_RECEIVE_AUDIO,
required = false) final Boolean sendReceiveAudio,
@RequestParam(
name = ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_RECEIVE_VIDEO,
required = false) final Boolean sendReceiveVideo,
@RequestParam(
name = ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_ALLOW_CHAT,
required = false) final Boolean sendAllowChat) {
checkAccess(institutionId, examId);
final Map<String, String> attributes = createProctorInstructionAttributes(
sendReceiveAudio,
sendReceiveVideo,
sendAllowChat,
Constants.FALSE_STRING);
if (attributes.isEmpty()) {
log.warn("Missing reconfigure instruction attributes. Skip sending empty instruction to SEB clients");
return;
}
sendProctoringInstructions(examId, roomName, connectionTokens, attributes);
}
@RequestMapping(
path = API.MODEL_ID_VAR_PATH_SEGMENT
+ API.EXAM_PROCTORING_REJOIN_COLLECTING_ROOM_PATH_SEGMENT,
method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public void sendRejoinExamCollectionRoomToClients(
@RequestParam(
name = API.PARAM_INSTITUTION_ID,
required = true,
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
@PathVariable(name = API.PARAM_MODEL_ID) final Long examId,
@RequestParam(
name = API.EXAM_API_SEB_CONNECTION_TOKEN,
required = true) final String connectionTokens) {
checkAccess(institutionId, examId);
final ProctoringSettings settings = this.examSessionService
.getRunningExam(examId)
.flatMap(this.examAdminService::getExamProctoring)
.getOrThrow();
final ExamProctoringService examProctoringService = this.examAdminService
.getExamProctoringService(settings.serverType)
.getOrThrow();
Arrays.asList(StringUtils.split(connectionTokens, Constants.LIST_SEPARATOR))
.stream()
.forEach(connectionToken -> {
examProctoringService
.getClientExamCollectionRoomConnectionData(
settings,
connectionToken)
.flatMap(data -> this.sendJoinInstruction(examId, connectionToken, data))
.onError(error -> log.error("Failed to send rejoin for: {} cause: {}",
connectionToken,
error.getMessage()));
});
}
@RequestMapping(
path = API.MODEL_ID_VAR_PATH_SEGMENT
+ API.EXAM_PROCTORING_JOIN_ROOM_PATH_SEGMENT,
method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public SEBProctoringConnectionData sendJoinProctoringRoomToClients(
@RequestParam(
name = API.PARAM_INSTITUTION_ID,
required = true,
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
@PathVariable(name = API.PARAM_MODEL_ID) final Long examId,
@RequestParam(
name = SEBProctoringConnectionData.ATTR_ROOM_NAME,
required = true) final String roomName,
@RequestParam(
name = SEBProctoringConnectionData.ATTR_SUBJECT,
required = false) final String subject,
@RequestParam(
name = API.EXAM_API_SEB_CONNECTION_TOKEN,
required = true) final String connectionTokens) {
checkAccess(institutionId, examId);
final ProctoringSettings settings = this.examSessionService
.getRunningExam(examId)
.flatMap(this.examAdminService::getExamProctoring)
.getOrThrow();
final ExamProctoringService examProctoringService = this.examAdminService
.getExamProctoringService(settings.serverType)
.getOrThrow();
if (StringUtils.isNotBlank(connectionTokens)) {
final boolean single = connectionTokens.contains(Constants.LIST_SEPARATOR);
(single
? Arrays.asList(StringUtils.split(connectionTokens, Constants.LIST_SEPARATOR))
: Arrays.asList(connectionTokens))
.stream()
.forEach(connectionToken -> {
final SEBProctoringConnectionData data = (single)
? examProctoringService
.getClientRoomConnectionData(settings, connectionToken)
.onError(error -> log.error(
"Failed to get client room connection data for {} cause: {}",
connectionToken,
error.getMessage()))
.get()
: examProctoringService
.getClientRoomConnectionData(
settings,
connectionToken,
roomName,
(StringUtils.isNotBlank(subject)) ? subject : roomName)
.onError(error -> log.error(
"Failed to get client room connection data for {} cause: {}",
connectionToken,
error.getMessage()))
.get();
if (data != null) {
sendJoinInstruction(examId, connectionToken, data)
.onError(error -> log.error(
"Failed to send proctoring leave instruction to client: {} cause: {}",
connectionToken,
error.getMessage()));
}
});
}
return examProctoringService.createProctorPublicRoomConnection(
settings,
roomName,
(StringUtils.isNotBlank(subject)) ? subject : roomName)
.getOrThrow();
}
@RequestMapping(
path = API.MODEL_ID_VAR_PATH_SEGMENT
+ API.EXAM_PROCTORING_JON_ALL_COLLECTING_ROOM,
method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public SEBProctoringConnectionData sendJoinAllCollectingRoomToClients(
@RequestParam(
name = API.PARAM_INSTITUTION_ID,
required = true,
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
@PathVariable(name = API.PARAM_MODEL_ID) final Long examId,
@RequestParam(
name = SEBProctoringConnectionData.ATTR_SUBJECT,
required = false) final String subject) {
checkAccess(institutionId, examId);
final ProctoringSettings settings = this.examSessionService
.getRunningExam(examId)
.flatMap(this.examAdminService::getExamProctoring)
.getOrThrow();
final ExamProctoringService examProctoringService = this.examAdminService
.getExamProctoringService(settings.serverType)
.getOrThrow();
// first create and register a room to collect all connection of the exam
// As long as the room exists new connections will join this room immediately
// after have been applied to the default collecting room
final String roomName = this.examProcotringRoomService.createCollectAllRoom(examId)
.onError(error -> this.examProcotringRoomService.disposeCollectAllRoom(examId))
.getOrThrow();
// get all active connections for the exam and send the join instruction
this.examSessionService.getConnectionData(
examId,
cData -> cData.clientConnection.status == ConnectionStatus.ACTIVE
|| cData.clientConnection.status == ConnectionStatus.CLOSED)
.getOrThrow()
.stream()
.forEach(cc -> {
final SEBProctoringConnectionData data = examProctoringService
.getClientRoomConnectionData(
settings,
cc.clientConnection.connectionToken,
roomName,
(StringUtils.isNotBlank(subject)) ? subject : roomName)
.onError(error -> log.error(
"Failed to get client room connection data for {} cause: {}",
cc.clientConnection.connectionToken,
error.getMessage()))
.get();
if (data != null) {
sendJoinInstruction(examId, cc.clientConnection.connectionToken, data)
.onError(error -> log.error(
"Failed to send proctoring leave instruction to client: {} ",
cc.clientConnection.connectionToken, error));
}
});
return examProctoringService.createProctorPublicRoomConnection(
settings,
roomName,
(StringUtils.isNotBlank(subject)) ? subject : roomName)
.getOrThrow();
}
@RequestMapping(
path = API.MODEL_ID_VAR_PATH_SEGMENT
+ API.EXAM_PROCTORING_REJON_ALL_COLLECTING_ROOM,
method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public void sendReJoinAllCollectingRoomToClients(
@RequestParam(
name = API.PARAM_INSTITUTION_ID,
required = true,
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
@PathVariable(name = API.PARAM_MODEL_ID) final Long examId) {
checkAccess(institutionId, examId);
final ProctoringSettings settings = this.examSessionService
.getRunningExam(examId)
.flatMap(this.examAdminService::getExamProctoring)
.getOrThrow();
final ExamProctoringService examProctoringService = this.examAdminService
.getExamProctoringService(settings.serverType)
.getOrThrow();
// first unregister the current room to collect all connection of the exam
this.examProcotringRoomService.disposeCollectAllRoom(examId);
// get all active connections for the exam and send the join instruction
this.examSessionService.getConnectionData(
examId,
cData -> cData.clientConnection.status == ConnectionStatus.ACTIVE
|| cData.clientConnection.status == ConnectionStatus.CLOSED)
.getOrThrow()
.stream()
.forEach(cc -> {
examProctoringService
.getClientExamCollectionRoomConnectionData(
settings,
cc.clientConnection)
.flatMap(data -> this.sendJoinInstruction(
examId,
cc.clientConnection.connectionToken,
data))
.onError(error -> log.error("Failed to send rejoin for: {} cause: {}",
cc.clientConnection.connectionToken,
error.getMessage()));
});
}
private void sendProctoringInstructions(
final Long examId,
final String roomName,
final String connectionTokens,
final Map<String, String> attributes) {
if (attributes.isEmpty()) {
log.warn("Missing reconfigure instruction attributes. Skip sending empty instruction to SEB clients");
return;
}
if (StringUtils.isNotBlank(connectionTokens)) {
final boolean single = connectionTokens.contains(Constants.LIST_SEPARATOR);
(single
? Arrays.asList(StringUtils.split(connectionTokens, Constants.LIST_SEPARATOR))
: Arrays.asList(connectionTokens))
.stream()
.forEach(connectionToken -> {
this.sebInstructionService.registerInstruction(
examId,
InstructionType.SEB_RECONFIGURE_SETTINGS,
attributes,
connectionToken,
true)
.onError(error -> log.error(
"Failed to register reconfiguring instruction for connection: {}",
connectionToken,
error));
});
} else if (StringUtils.isNotBlank(roomName)) {
this.examProcotringRoomService.getRoomConnections(examId, roomName)
.getOrThrow()
.stream()
.forEach(connection -> {
this.sebInstructionService.registerInstruction(
examId,
InstructionType.SEB_RECONFIGURE_SETTINGS,
attributes,
connection.connectionToken,
true)
.onError(error -> log.error(
"Failed to register reconfiguring instruction for connection: {}",
connection.connectionToken,
error));
});
} else {
throw new RuntimeException("API attribute validation error: missing "
+ Domain.REMOTE_PROCTORING_ROOM.ATTR_ID + " and/or" +
API.EXAM_API_SEB_CONNECTION_TOKEN + " attribute");
}
}
private Map<String, String> createProctorInstructionAttributes(
final Boolean sendReceiveAudio,
final Boolean sendReceiveVideo,
final Boolean sendAllowChat,
final String flagValue) {
final Map<String, String> attributes = new HashMap<>();
if (BooleanUtils.isTrue(sendReceiveAudio)) {
attributes.put(
ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_RECEIVE_AUDIO,
flagValue);
}
if (BooleanUtils.isTrue(sendReceiveVideo)) {
attributes.put(
ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_RECEIVE_VIDEO,
flagValue);
}
if (BooleanUtils.isTrue(sendAllowChat)) {
attributes.put(
ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_ALLOW_CHAT,
flagValue);
}
return attributes;
}
private Result<Void> sendJoinInstruction(
final Long examId,
final String connectionToken,
final SEBProctoringConnectionData data) {
final Map<String, String> attributes = new HashMap<>();
attributes.put(
ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.SERVICE_TYPE,
ProctoringSettings.ProctoringServerType.JITSI_MEET.name());
attributes.put(
ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.METHOD,
ClientInstruction.ProctoringInstructionMethod.JOIN.name());
attributes.put(
ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.JITSI_URL,
data.serverURL);
attributes.put(
ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.JITSI_ROOM,
data.roomName);
attributes.put(
ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.JITSI_TOKEN,
data.accessToken);
return this.sebInstructionService.registerInstruction(
examId,
InstructionType.SEB_PROCTORING,
attributes,
connectionToken,
true);
}
private void checkAccess(final Long institutionId, final Long examId) {
this.authorization.check(
PrivilegeType.READ,
EntityType.EXAM,
institutionId);
this.authorization.checkRead(this.examSessionService
.getExamDAO()
.byPK(examId)
.getOrThrow());
}
}

View file

@ -1449,7 +1449,9 @@ sebserver.monitoring.exam.action.list.view=Monitoring
sebserver.monitoring.exam.action.viewroom=View {0} ( {1} / {2} )
sebserver.exam.monitoring.action.category.filter=Filter
sebserver.exam.overall.action.category.proctoring=Proctoring
sebserver.monitoring.exam.action.proctoring.allRoom=View All
sebserver.monitoring.exam.proctoring.room.all.name=Exam Room
sebserver.monitoring.exam.proctoring.action.close=Close Window
sebserver.monitoring.exam.proctoring.action.broadcaston.audio=Start Audio Broadcast
sebserver.monitoring.exam.proctoring.action.broadcastoff.audio=End Audio Broadcast