refactor proctoring
This commit is contained in:
parent
ad7f06e521
commit
39f4c85d22
13 changed files with 368 additions and 398 deletions
|
@ -52,6 +52,13 @@ public interface ClientConnectionDAO extends
|
|||
return getConnectionTokens(examId);
|
||||
}
|
||||
|
||||
/** Get a list of all connection tokens of all connections of an exam
|
||||
* that are in state active
|
||||
*
|
||||
* @param examId The exam identifier
|
||||
* @return Result refer to the collection of connection tokens or to an error when happened */
|
||||
Result<Collection<String>> getActiveConnctionTokens(Long examId);
|
||||
|
||||
/** Get a collection of all client connections records that needs a room update
|
||||
* and that are in the status ACTIVE.
|
||||
* This also flags the involved connections for no update needed within the
|
||||
|
|
|
@ -146,6 +146,25 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
|
|||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public Result<Collection<String>> getActiveConnctionTokens(final Long examId) {
|
||||
return Result.tryCatch(() -> this.clientConnectionRecordMapper
|
||||
.selectByExample()
|
||||
.where(
|
||||
ClientConnectionRecordDynamicSqlSupport.examId,
|
||||
SqlBuilder.isEqualTo(examId))
|
||||
.and(
|
||||
ClientConnectionRecordDynamicSqlSupport.status,
|
||||
SqlBuilder.isEqualTo(ConnectionStatus.ACTIVE.name()))
|
||||
.build()
|
||||
.execute()
|
||||
.stream()
|
||||
.map(ClientConnectionRecord::getConnectionToken)
|
||||
.filter(StringUtils::isNotBlank)
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Result<Collection<ClientConnectionRecord>> getAllConnectionIdsForRoomUpdateActive() {
|
||||
|
|
|
@ -45,25 +45,25 @@ public interface ExamAdminService {
|
|||
*
|
||||
* @param examId the exam instance
|
||||
* @return Result refer to ExamProctoring data for the exam. */
|
||||
default Result<ProctoringSettings> getExamProctoring(final Exam exam) {
|
||||
default Result<ProctoringSettings> getExamProctoringSettings(final Exam exam) {
|
||||
if (exam == null || exam.id == null) {
|
||||
return Result.ofRuntimeError("Invalid Exam model");
|
||||
}
|
||||
return getExamProctoring(exam.id);
|
||||
return getExamProctoringSettings(exam.id);
|
||||
}
|
||||
|
||||
/** Get ExamProctoring data for a certain exam to an error when happened.
|
||||
*
|
||||
* @param examId the exam identifier
|
||||
* @return Result refer to ExamProctoring data for the exam. */
|
||||
Result<ProctoringSettings> getExamProctoring(Long examId);
|
||||
Result<ProctoringSettings> getExamProctoringSettings(Long examId);
|
||||
|
||||
/** Save the given ExamProctoring data for an existing Exam.
|
||||
*
|
||||
* @param examId the exam identifier
|
||||
* @param examProctoring The ExamProctoring data to save for the exam
|
||||
* @return Result refer to saved ExamProctoring data or to an error when happened. */
|
||||
Result<ProctoringSettings> saveExamProctoring(Long examId, ProctoringSettings examProctoring);
|
||||
Result<ProctoringSettings> saveExamProctoringSettings(Long examId, ProctoringSettings examProctoring);
|
||||
|
||||
/** This indicates if proctoring is set and enabled for a certain exam.
|
||||
*
|
||||
|
@ -85,7 +85,25 @@ public interface ExamAdminService {
|
|||
/** Get the exam proctoring service implementation of specified type.
|
||||
*
|
||||
* @param type exam proctoring service server type
|
||||
* @return Result refer to the ExamProctoringService or to an error when happened */
|
||||
public Result<ExamProctoringService> getExamProctoringService(final ProctoringServerType type);
|
||||
* @return ExamProctoringService instance */
|
||||
Result<ExamProctoringService> getExamProctoringService(final ProctoringServerType type);
|
||||
|
||||
/** Get the exam proctoring service implementation of specified type.
|
||||
*
|
||||
* @param settings the ProctoringSettings that defines the ProctoringServerType
|
||||
* @return ExamProctoringService instance */
|
||||
default Result<ExamProctoringService> getExamProctoringService(final ProctoringSettings settings) {
|
||||
return Result.tryCatch(() -> getExamProctoringService(settings.serverType).getOrThrow());
|
||||
}
|
||||
|
||||
default Result<ExamProctoringService> getExamProctoringService(final Exam exam) {
|
||||
return Result.tryCatch(() -> getExamProctoringService(exam.id).getOrThrow());
|
||||
}
|
||||
|
||||
default Result<ExamProctoringService> getExamProctoringService(final Long examId) {
|
||||
return getExamProctoringSettings(examId)
|
||||
.flatMap(this::getExamProctoringService);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -186,7 +186,7 @@ public class ExamAdminServiceImpl implements ExamAdminService {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Result<ProctoringSettings> getExamProctoring(final Long examId) {
|
||||
public Result<ProctoringSettings> getExamProctoringSettings(final Long examId) {
|
||||
return this.additionalAttributesDAO.getAdditionalAttributes(EntityType.EXAM, examId)
|
||||
.map(attrs -> attrs.stream()
|
||||
.collect(Collectors.toMap(
|
||||
|
@ -206,7 +206,8 @@ public class ExamAdminServiceImpl implements ExamAdminService {
|
|||
|
||||
@Override
|
||||
@Transactional
|
||||
public Result<ProctoringSettings> saveExamProctoring(final Long examId, final ProctoringSettings examProctoring) {
|
||||
public Result<ProctoringSettings> saveExamProctoringSettings(final Long examId,
|
||||
final ProctoringSettings examProctoring) {
|
||||
return Result.tryCatch(() -> {
|
||||
|
||||
this.additionalAttributesDAO.saveAdditionalAttribute(
|
||||
|
@ -264,7 +265,8 @@ public class ExamAdminServiceImpl implements ExamAdminService {
|
|||
|
||||
@Override
|
||||
public Result<ExamProctoringService> getExamProctoringService(final ProctoringServerType type) {
|
||||
return this.examProctoringServiceFactory.getExamProctoringService(type);
|
||||
return this.examProctoringServiceFactory
|
||||
.getExamProctoringService(type);
|
||||
}
|
||||
|
||||
private Boolean getEnabled(final Map<String, AdditionalAttributeRecord> mapping) {
|
||||
|
|
|
@ -14,6 +14,8 @@ import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
|
|||
import ch.ethz.seb.sebserver.gbl.model.session.RemoteProctoringRoom;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
|
||||
/** Defines functionality to deal with proctoring rooms in a generic way (independent from meeting service) */
|
||||
|
||||
public interface ExamProctoringRoomService {
|
||||
|
||||
/** Get all existing default proctoring rooms of an exam.
|
||||
|
@ -40,8 +42,10 @@ public interface ExamProctoringRoomService {
|
|||
* This attaches or detaches client connections from or to proctoring rooms of an exam in one batch.
|
||||
* New client connections that are coming in and are established only mark itself for
|
||||
* proctoring room update if proctoring is enabled for the specified exam. This batch processing
|
||||
* then makes the update synchronous to not create to to many rooms or several rooms with the same
|
||||
* name of an exam. */
|
||||
* then makes the update synchronously to keep track on room creation and naming
|
||||
*
|
||||
* If for a specified exam the town-hall room is active incoming client connection are instructed to
|
||||
* join the town-hall room. If not, incoming client connection are instructed to join a collecting room. */
|
||||
void updateProctoringCollectingRooms();
|
||||
|
||||
/** This creates a town-hall room for a specific exam. The exam must be active and running
|
||||
|
|
|
@ -8,11 +8,17 @@
|
|||
|
||||
package ch.ethz.seb.sebserver.webservice.servicelayer.session;
|
||||
|
||||
import java.util.Base64;
|
||||
import java.util.Base64.Encoder;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
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.SEBProctoringConnection;
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
|
||||
public interface ExamProctoringService {
|
||||
|
||||
|
@ -27,49 +33,69 @@ public interface ExamProctoringService {
|
|||
* @return Result refer to true if the settings are correct and the proctoring server can be accessed. */
|
||||
Result<Boolean> testExamProctoring(final ProctoringSettings examProctoring);
|
||||
|
||||
/** Used to get the proctor's room connection data.
|
||||
*
|
||||
* @param proctoringSettings the proctoring settings
|
||||
* @param roomName the name of the room
|
||||
* @param subject name of the room
|
||||
* @return SEBProctoringConnectionData that contains all connection data */
|
||||
Result<SEBProctoringConnection> createProctorPublicRoomConnection(
|
||||
final ProctoringSettings proctoringSettings,
|
||||
final String roomName,
|
||||
final String subject);
|
||||
Result<SEBProctoringConnection> getProctorRoomConnection(
|
||||
ProctoringSettings proctoringSettings,
|
||||
String roomName,
|
||||
String subject);
|
||||
|
||||
Result<SEBProctoringConnection> getClientExamCollectingRoomConnection(
|
||||
final ProctoringSettings proctoringSettings,
|
||||
final ClientConnection connection);
|
||||
Result<SEBProctoringConnection> sendJoinRoomToClients(
|
||||
ProctoringSettings proctoringSettings,
|
||||
Collection<String> clientConnectionTokens,
|
||||
String roomName,
|
||||
String subject);
|
||||
|
||||
Result<SEBProctoringConnection> getClientExamCollectingRoomConnection(
|
||||
final ProctoringSettings proctoringSettings,
|
||||
final String connectionToken,
|
||||
final String roomName,
|
||||
final String subject);
|
||||
Result<Void> sendJoinCollectingRoomToClients(
|
||||
ProctoringSettings proctoringSettings,
|
||||
Collection<String> clientConnectionTokens);
|
||||
|
||||
Result<SEBProctoringConnection> getClientRoomConnection(
|
||||
final ProctoringSettings examProctoring,
|
||||
final String connectionToken,
|
||||
final String roomName,
|
||||
final String subject);
|
||||
default String verifyRoomName(final String requestedRoomName, final String connectionToken) {
|
||||
if (StringUtils.isNotBlank(requestedRoomName)) {
|
||||
return requestedRoomName;
|
||||
}
|
||||
|
||||
Result<SEBProctoringConnection> createProctoringConnection(
|
||||
final ProctoringServerType proctoringServerType,
|
||||
final String connectionToken,
|
||||
final String url,
|
||||
final String appKey,
|
||||
final CharSequence appSecret,
|
||||
final String clientName,
|
||||
final String clientKey,
|
||||
final String roomName,
|
||||
final String subject,
|
||||
final Long expTime,
|
||||
final boolean moderator);
|
||||
final Encoder urlEncoder = Base64.getUrlEncoder().withoutPadding();
|
||||
return urlEncoder.encodeToString(
|
||||
Utils.toByteArray(connectionToken));
|
||||
}
|
||||
|
||||
Result<String> createClientAccessToken(
|
||||
final ProctoringSettings proctoringSettings,
|
||||
final String connectionToken,
|
||||
final String roomName);
|
||||
// /** Used to get the proctor's room connection data.
|
||||
// *
|
||||
// * @param proctoringSettings the proctoring settings
|
||||
// * @param roomName the name of the room
|
||||
// * @param subject name of the room
|
||||
// * @return SEBProctoringConnectionData that contains all connection data */
|
||||
// Result<SEBProctoringConnection> createProctorPublicRoomConnection(
|
||||
// final ProctoringSettings proctoringSettings,
|
||||
// final String roomName,
|
||||
// final String subject);
|
||||
//
|
||||
// Result<SEBProctoringConnection> getClientExamCollectingRoomConnection(
|
||||
// final ProctoringSettings proctoringSettings,
|
||||
// final ClientConnection connection);
|
||||
//
|
||||
// Result<SEBProctoringConnection> getClientExamCollectingRoomConnection(
|
||||
// final ProctoringSettings proctoringSettings,
|
||||
// final String connectionToken,
|
||||
// final String roomName,
|
||||
// final String subject);
|
||||
//
|
||||
// Result<SEBProctoringConnection> getClientRoomConnection(
|
||||
// final ProctoringSettings examProctoring,
|
||||
// final String connectionToken,
|
||||
// final String roomName,
|
||||
// final String subject);
|
||||
//
|
||||
// Result<SEBProctoringConnection> createProctoringConnection(
|
||||
// final ProctoringServerType proctoringServerType,
|
||||
// final String connectionToken,
|
||||
// final String url,
|
||||
// final String appKey,
|
||||
// final CharSequence appSecret,
|
||||
// final String clientName,
|
||||
// final String clientKey,
|
||||
// final String roomName,
|
||||
// final String subject,
|
||||
// final Long expTime,
|
||||
// final boolean moderator);
|
||||
|
||||
}
|
||||
|
|
|
@ -155,9 +155,12 @@ public interface ExamSessionService {
|
|||
Long examId,
|
||||
Predicate<ClientConnectionData> filter);
|
||||
|
||||
default Result<Collection<ClientConnectionData>> getAllActiveConnectionData(final Long examId) {
|
||||
return getConnectionData(examId, ACTIVE_CONNECTION_DATA_FILTER);
|
||||
}
|
||||
/** Gets all connection tokens of active client connection that are related to a specified exam
|
||||
* from persistence storage without caching involved.
|
||||
*
|
||||
* @param examId the exam identifier
|
||||
* @return Result refer to the collection of connection tokens or to an error when happened. */
|
||||
Result<Collection<String>> getActiveConnectionTokens(final Long examId);
|
||||
|
||||
/** Use this to check if the current cached running exam is up to date
|
||||
* and if not to flush the cache.
|
||||
|
|
|
@ -13,10 +13,16 @@ import java.security.InvalidKeyException;
|
|||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Base64;
|
||||
import java.util.Base64.Encoder;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
@ -26,8 +32,9 @@ 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.SEBProctoringConnection;
|
||||
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.ClientInstruction;
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientInstruction.InstructionType;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Cryptor;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
|
@ -36,12 +43,15 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.Authorization
|
|||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.RemoteProctoringRoomDAO;
|
||||
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.SEBClientInstructionService;
|
||||
|
||||
@Lazy
|
||||
@Service
|
||||
@WebServiceProfile
|
||||
public class ExamJITSIProctoringService implements ExamProctoringService {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ExamJITSIProctoringService.class);
|
||||
|
||||
private static final String JITSI_ACCESS_TOKEN_HEADER =
|
||||
"{\"alg\":\"HS256\",\"typ\":\"JWT\"}";
|
||||
|
||||
|
@ -51,17 +61,20 @@ public class ExamJITSIProctoringService implements ExamProctoringService {
|
|||
private final RemoteProctoringRoomDAO remoteProctoringRoomDAO;
|
||||
private final AuthorizationService authorizationService;
|
||||
private final ExamSessionService examSessionService;
|
||||
private final SEBClientInstructionService sebClientInstructionService;
|
||||
private final Cryptor cryptor;
|
||||
|
||||
protected ExamJITSIProctoringService(
|
||||
final RemoteProctoringRoomDAO remoteProctoringRoomDAO,
|
||||
final AuthorizationService authorizationService,
|
||||
final ExamSessionService examSessionService,
|
||||
final SEBClientInstructionService sebClientInstructionService,
|
||||
final Cryptor cryptor) {
|
||||
|
||||
this.remoteProctoringRoomDAO = remoteProctoringRoomDAO;
|
||||
this.authorizationService = authorizationService;
|
||||
this.examSessionService = examSessionService;
|
||||
this.sebClientInstructionService = sebClientInstructionService;
|
||||
this.cryptor = cryptor;
|
||||
}
|
||||
|
||||
|
@ -77,7 +90,124 @@ public class ExamJITSIProctoringService implements ExamProctoringService {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Result<SEBProctoringConnection> createProctorPublicRoomConnection(
|
||||
public Result<SEBProctoringConnection> getProctorRoomConnection(
|
||||
final ProctoringSettings proctoringSettings,
|
||||
final String roomName,
|
||||
final String subject) {
|
||||
|
||||
return this.createProctorPublicRoomConnection(
|
||||
proctoringSettings,
|
||||
roomName,
|
||||
StringUtils.isNoneBlank(subject) ? subject : roomName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<SEBProctoringConnection> sendJoinRoomToClients(
|
||||
final ProctoringSettings proctoringSettings,
|
||||
final Collection<String> clientConnectionTokens,
|
||||
final String roomName,
|
||||
final String subject) {
|
||||
|
||||
return Result.tryCatch(() -> {
|
||||
clientConnectionTokens
|
||||
.stream()
|
||||
.forEach(connectionToken -> {
|
||||
final SEBProctoringConnection proctoringConnection =
|
||||
getClientRoomConnection(
|
||||
proctoringSettings,
|
||||
connectionToken,
|
||||
verifyRoomName(roomName, connectionToken),
|
||||
(StringUtils.isNotBlank(subject)) ? subject : roomName)
|
||||
.onError(error -> log.error(
|
||||
"Failed to get client room connection data for {} cause: {}",
|
||||
connectionToken,
|
||||
error.getMessage()))
|
||||
.get();
|
||||
if (proctoringConnection != null) {
|
||||
sendJoinInstruction(
|
||||
proctoringSettings.examId,
|
||||
connectionToken,
|
||||
proctoringConnection);
|
||||
}
|
||||
});
|
||||
|
||||
return createProctorPublicRoomConnection(
|
||||
proctoringSettings,
|
||||
roomName,
|
||||
(StringUtils.isNotBlank(subject)) ? subject : roomName)
|
||||
.getOrThrow();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Void> sendJoinCollectingRoomToClients(
|
||||
final ProctoringSettings proctoringSettings,
|
||||
final Collection<String> clientConnectionTokens) {
|
||||
|
||||
return Result.tryCatch(() -> {
|
||||
clientConnectionTokens
|
||||
.stream()
|
||||
.forEach(connectionToken -> {
|
||||
final ClientConnectionData clientConnection = this.examSessionService
|
||||
.getConnectionData(connectionToken)
|
||||
.getOrThrow();
|
||||
final String roomName = this.remoteProctoringRoomDAO
|
||||
.getRoomName(clientConnection.clientConnection.getRemoteProctoringRoomId())
|
||||
.getOrThrow();
|
||||
|
||||
final SEBProctoringConnection proctoringConnection = getClientExamCollectingRoomConnection(
|
||||
proctoringSettings,
|
||||
clientConnection.clientConnection.connectionToken,
|
||||
roomName,
|
||||
clientConnection.clientConnection.userSessionId)
|
||||
.getOrThrow();
|
||||
|
||||
sendJoinInstruction(
|
||||
proctoringSettings.examId,
|
||||
clientConnection.clientConnection.connectionToken,
|
||||
proctoringConnection);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void sendJoinInstruction(
|
||||
final Long examId,
|
||||
final String connectionToken,
|
||||
final SEBProctoringConnection proctoringConnection) {
|
||||
|
||||
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,
|
||||
proctoringConnection.serverURL);
|
||||
attributes.put(
|
||||
ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.JITSI_ROOM,
|
||||
proctoringConnection.roomName);
|
||||
if (StringUtils.isNotBlank(proctoringConnection.subject)) {
|
||||
attributes.put(
|
||||
ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.JITSI_ROOM_SUBJECT,
|
||||
proctoringConnection.subject);
|
||||
}
|
||||
attributes.put(
|
||||
ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.JITSI_TOKEN,
|
||||
proctoringConnection.accessToken);
|
||||
|
||||
this.sebClientInstructionService.registerInstruction(
|
||||
examId,
|
||||
InstructionType.SEB_PROCTORING,
|
||||
attributes,
|
||||
connectionToken,
|
||||
true)
|
||||
.onError(error -> log.error("Failed to send join instruction: {}", connectionToken, error));
|
||||
}
|
||||
|
||||
private Result<SEBProctoringConnection> createProctorPublicRoomConnection(
|
||||
final ProctoringSettings proctoringSettings,
|
||||
final String roomName,
|
||||
final String subject) {
|
||||
|
@ -99,35 +229,7 @@ public class ExamJITSIProctoringService implements ExamProctoringService {
|
|||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<SEBProctoringConnection> getClientExamCollectingRoomConnection(
|
||||
final ProctoringSettings proctoringSettings,
|
||||
final ClientConnection connection) {
|
||||
|
||||
return Result.tryCatch(() -> {
|
||||
|
||||
final String roomName = this.remoteProctoringRoomDAO
|
||||
.getRoomName(connection.getRemoteProctoringRoomId())
|
||||
.getOrThrow();
|
||||
|
||||
return createProctoringConnection(
|
||||
proctoringSettings.serverType,
|
||||
null,
|
||||
proctoringSettings.serverURL,
|
||||
proctoringSettings.appKey,
|
||||
proctoringSettings.getAppSecret(),
|
||||
connection.userSessionId,
|
||||
"seb-client",
|
||||
roomName,
|
||||
connection.userSessionId,
|
||||
forExam(proctoringSettings),
|
||||
false)
|
||||
.getOrThrow();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<SEBProctoringConnection> getClientExamCollectingRoomConnection(
|
||||
private Result<SEBProctoringConnection> getClientExamCollectingRoomConnection(
|
||||
final ProctoringSettings proctoringSettings,
|
||||
final String connectionToken,
|
||||
final String roomName,
|
||||
|
@ -154,8 +256,7 @@ public class ExamJITSIProctoringService implements ExamProctoringService {
|
|||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<SEBProctoringConnection> getClientRoomConnection(
|
||||
private Result<SEBProctoringConnection> getClientRoomConnection(
|
||||
final ProctoringSettings proctoringSettings,
|
||||
final String connectionToken,
|
||||
final String roomName,
|
||||
|
@ -185,8 +286,7 @@ public class ExamJITSIProctoringService implements ExamProctoringService {
|
|||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<SEBProctoringConnection> createProctoringConnection(
|
||||
protected Result<SEBProctoringConnection> createProctoringConnection(
|
||||
final ProctoringServerType proctoringServerType,
|
||||
final String connectionToken,
|
||||
final String url,
|
||||
|
@ -227,35 +327,6 @@ public class ExamJITSIProctoringService implements ExamProctoringService {
|
|||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<String> createClientAccessToken(
|
||||
final ProctoringSettings proctoringSettings,
|
||||
final String connectionToken,
|
||||
final String roomName) {
|
||||
|
||||
return Result.tryCatch(() -> {
|
||||
|
||||
final ClientConnectionData connectionData = this.examSessionService
|
||||
.getConnectionData(connectionToken)
|
||||
.getOrThrow();
|
||||
|
||||
final String host = UriComponentsBuilder.fromHttpUrl(proctoringSettings.serverURL)
|
||||
.build()
|
||||
.getHost();
|
||||
final CharSequence decryptedSecret = this.cryptor.decrypt(proctoringSettings.appSecret);
|
||||
|
||||
return internalCreateAccessToken(
|
||||
proctoringSettings.appKey,
|
||||
decryptedSecret,
|
||||
connectionData.clientConnection.userSessionId,
|
||||
"seb-client",
|
||||
roomName,
|
||||
forExam(proctoringSettings),
|
||||
host,
|
||||
false);
|
||||
});
|
||||
}
|
||||
|
||||
protected String internalCreateAccessToken(
|
||||
final String appKey,
|
||||
final CharSequence appSecret,
|
||||
|
|
|
@ -8,9 +8,8 @@
|
|||
|
||||
package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
@ -19,12 +18,7 @@ import org.springframework.context.annotation.Lazy;
|
|||
import org.springframework.stereotype.Service;
|
||||
|
||||
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.SEBProctoringConnection;
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
|
||||
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.ClientInstruction.ProctoringInstructionMethod;
|
||||
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;
|
||||
|
@ -34,7 +28,6 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.RemoteProctoringRoomDAO
|
|||
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamAdminService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamProctoringRoomService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientInstructionService;
|
||||
|
||||
@Lazy
|
||||
@Service
|
||||
|
@ -45,20 +38,17 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
|
|||
|
||||
private final RemoteProctoringRoomDAO remoteProctoringRoomDAO;
|
||||
private final ClientConnectionDAO clientConnectionDAO;
|
||||
private final SEBClientInstructionService sebInstructionService;
|
||||
private final ExamAdminService examAdminService;
|
||||
private final ExamSessionService examSessionService;
|
||||
|
||||
public ExamProctoringRoomServiceImpl(
|
||||
final RemoteProctoringRoomDAO remoteProctoringRoomDAO,
|
||||
final ClientConnectionDAO clientConnectionDAO,
|
||||
final SEBClientInstructionService sebInstructionService,
|
||||
final ExamAdminService examAdminService,
|
||||
final ExamSessionService examSessionService) {
|
||||
|
||||
this.remoteProctoringRoomDAO = remoteProctoringRoomDAO;
|
||||
this.clientConnectionDAO = clientConnectionDAO;
|
||||
this.sebInstructionService = sebInstructionService;
|
||||
this.examAdminService = examAdminService;
|
||||
this.examSessionService = examSessionService;
|
||||
}
|
||||
|
@ -84,7 +74,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
|
|||
this.clientConnectionDAO.getAllConnectionIdsForRoomUpdateActive()
|
||||
.getOrThrow()
|
||||
.stream()
|
||||
.forEach(this::assignToRoom);
|
||||
.forEach(this::assignToCollectingRoom);
|
||||
|
||||
this.clientConnectionDAO.getAllConnectionIdsForRoomUpdateInactive()
|
||||
.getOrThrow()
|
||||
|
@ -114,40 +104,40 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
|
|||
this.remoteProctoringRoomDAO.deleteTownhallRoom(examId);
|
||||
}
|
||||
|
||||
private void assignToRoom(final ClientConnectionRecord cc) {
|
||||
private void assignToCollectingRoom(final ClientConnectionRecord cc) {
|
||||
try {
|
||||
|
||||
final RemoteProctoringRoom proctoringRoom = getProctoringRoom(
|
||||
cc.getExamId(),
|
||||
cc.getConnectionToken());
|
||||
|
||||
if (proctoringRoom != null) {
|
||||
this.clientConnectionDAO.assignToProctoringRoom(
|
||||
cc.getId(),
|
||||
cc.getConnectionToken(),
|
||||
proctoringRoom.id)
|
||||
.onError(error -> log.error("Failed to assign to proctoring room: ", error))
|
||||
.getOrThrow();
|
||||
this.clientConnectionDAO.assignToProctoringRoom(
|
||||
cc.getId(),
|
||||
cc.getConnectionToken(),
|
||||
proctoringRoom.id)
|
||||
.getOrThrow();
|
||||
|
||||
final Result<RemoteProctoringRoom> townhallRoomResult = this.remoteProctoringRoomDAO
|
||||
.getTownhallRoom(cc.getExamId());
|
||||
if (townhallRoomResult.hasValue()) {
|
||||
final RemoteProctoringRoom townhallRoom = townhallRoomResult.get();
|
||||
applyProcotringInstruction(
|
||||
cc.getExamId(),
|
||||
cc.getConnectionToken(),
|
||||
townhallRoom.name,
|
||||
townhallRoom.subject);
|
||||
} else {
|
||||
applyProcotringInstruction(
|
||||
cc.getExamId(),
|
||||
cc.getConnectionToken(),
|
||||
proctoringRoom.name,
|
||||
proctoringRoom.subject);
|
||||
}
|
||||
final Result<RemoteProctoringRoom> townhallRoomResult = this.remoteProctoringRoomDAO
|
||||
.getTownhallRoom(cc.getExamId());
|
||||
|
||||
if (townhallRoomResult.hasValue()) {
|
||||
final RemoteProctoringRoom townhallRoom = townhallRoomResult.get();
|
||||
applyProcotringInstruction(
|
||||
cc.getExamId(),
|
||||
cc.getConnectionToken(),
|
||||
townhallRoom.name,
|
||||
townhallRoom.subject)
|
||||
.getOrThrow();
|
||||
} else {
|
||||
applyProcotringInstruction(
|
||||
cc.getExamId(),
|
||||
cc.getConnectionToken(),
|
||||
proctoringRoom.name,
|
||||
proctoringRoom.subject)
|
||||
.getOrThrow();
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
log.error("Failed to update client connection for proctoring room: ", e);
|
||||
this.clientConnectionDAO.setNeedsRoomUpdate(cc.getId());
|
||||
log.error("Failed to assign connection to collecting room: {}", cc, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -167,7 +157,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
|
|||
private RemoteProctoringRoom getProctoringRoom(final Long examId, final String connectionToken) {
|
||||
try {
|
||||
final ProctoringSettings proctoringSettings = this.examAdminService
|
||||
.getExamProctoring(examId)
|
||||
.getExamProctoringSettings(examId)
|
||||
.getOrThrow();
|
||||
return this.remoteProctoringRoomDAO.reservePlaceInCollectingRoom(
|
||||
examId,
|
||||
|
@ -184,63 +174,21 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
|
|||
}
|
||||
}
|
||||
|
||||
private void applyProcotringInstruction(
|
||||
private Result<Void> applyProcotringInstruction(
|
||||
final Long examId,
|
||||
final String connectionToken,
|
||||
final String roomName,
|
||||
final String subject) {
|
||||
|
||||
try {
|
||||
// apply a SEB_PROCOTIRNG instruction for the specified SEB client connection
|
||||
final ProctoringSettings proctoringSettings = this.examAdminService
|
||||
.getExamProctoring(examId)
|
||||
.getOrThrow();
|
||||
return this.examAdminService
|
||||
.getExamProctoringSettings(examId)
|
||||
.flatMap(proctoringSettings -> this.examAdminService
|
||||
.getExamProctoringService(proctoringSettings.serverType)
|
||||
.getOrThrow()
|
||||
.sendJoinCollectingRoomToClients(
|
||||
proctoringSettings,
|
||||
Arrays.asList(connectionToken)));
|
||||
|
||||
final SEBProctoringConnection proctoringData =
|
||||
this.examAdminService.getExamProctoringService(proctoringSettings.serverType)
|
||||
.flatMap(s -> s.getClientExamCollectingRoomConnection(
|
||||
proctoringSettings,
|
||||
connectionToken,
|
||||
roomName,
|
||||
subject))
|
||||
.getOrThrow();
|
||||
|
||||
final Map<String, String> attributes = new HashMap<>();
|
||||
attributes.put(
|
||||
ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.SERVICE_TYPE,
|
||||
ProctoringServerType.JITSI_MEET.name());
|
||||
attributes.put(
|
||||
ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.METHOD,
|
||||
ProctoringInstructionMethod.JOIN.name());
|
||||
|
||||
if (proctoringSettings.serverType == ProctoringServerType.JITSI_MEET) {
|
||||
|
||||
attributes.put(
|
||||
ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.JITSI_ROOM,
|
||||
proctoringData.roomName);
|
||||
attributes.put(
|
||||
ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.JITSI_ROOM_SUBJECT,
|
||||
proctoringData.subject);
|
||||
attributes.put(
|
||||
ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.JITSI_URL,
|
||||
proctoringData.serverURL);
|
||||
attributes.put(
|
||||
ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_PROCTORING.JITSI_TOKEN,
|
||||
proctoringData.accessToken);
|
||||
}
|
||||
|
||||
this.sebInstructionService.registerInstruction(
|
||||
examId,
|
||||
InstructionType.SEB_PROCTORING,
|
||||
attributes,
|
||||
connectionToken,
|
||||
true);
|
||||
|
||||
} catch (final Exception e) {
|
||||
log.error(
|
||||
"Failed to process proctoring initialization for established SEB client connection: {}",
|
||||
connectionToken, e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -379,6 +379,12 @@ public class ExamSessionServiceImpl implements ExamSessionService {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Collection<String>> getActiveConnectionTokens(final Long examId) {
|
||||
return this.clientConnectionDAO
|
||||
.getActiveConnctionTokens(examId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Exam> updateExamCache(final Long examId) {
|
||||
final Exam exam = this.examSessionCacheService.getRunningExam(examId);
|
||||
|
|
|
@ -396,7 +396,7 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
|
|||
checkReadPrivilege(institutionId);
|
||||
return this.entityDAO.byPK(modelId)
|
||||
.flatMap(this.authorization::checkRead)
|
||||
.flatMap(this.examAdminService::getExamProctoring)
|
||||
.flatMap(this.examAdminService::getExamProctoringSettings)
|
||||
.getOrThrow();
|
||||
}
|
||||
|
||||
|
@ -417,7 +417,7 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
|
|||
return this.entityDAO.byPK(examId)
|
||||
.flatMap(this.authorization::checkModify)
|
||||
.map(exam -> {
|
||||
this.examAdminService.saveExamProctoring(examId, examProctoring);
|
||||
this.examAdminService.saveExamProctoringSettings(examId, examProctoring);
|
||||
return exam;
|
||||
})
|
||||
.flatMap(this.userActivityLogDAO::logModify)
|
||||
|
|
|
@ -9,8 +9,6 @@
|
|||
package ch.ethz.seb.sebserver.webservice.weblayer.api;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.Base64.Encoder;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
@ -39,8 +37,6 @@ 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.gbl.util.Utils;
|
||||
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;
|
||||
|
@ -112,7 +108,7 @@ public class ExamProctoringController {
|
|||
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
|
||||
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public SEBProctoringConnection getProctorRoomData(
|
||||
public SEBProctoringConnection getProctorRoomConnection(
|
||||
@RequestParam(
|
||||
name = API.PARAM_INSTITUTION_ID,
|
||||
required = true,
|
||||
|
@ -125,13 +121,11 @@ public class ExamProctoringController {
|
|||
|
||||
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)))
|
||||
.flatMap(this.examAdminService::getExamProctoringService)
|
||||
.flatMap(service -> service.getProctorRoomConnection(
|
||||
this.examAdminService.getExamProctoringSettings(examId).getOrThrow(),
|
||||
roomName,
|
||||
StringUtils.isNoneBlank(subject) ? subject : roomName))
|
||||
.getOrThrow();
|
||||
}
|
||||
|
||||
|
@ -223,10 +217,17 @@ public class ExamProctoringController {
|
|||
|
||||
final ProctoringSettings proctoringSettings = this.examSessionService
|
||||
.getRunningExam(examId)
|
||||
.flatMap(this.examAdminService::getExamProctoring)
|
||||
.flatMap(this.examAdminService::getExamProctoringSettings)
|
||||
.getOrThrow();
|
||||
|
||||
sendJoinInstructions(connectionTokens, proctoringSettings);
|
||||
this.examAdminService
|
||||
.getExamProctoringService(proctoringSettings.serverType)
|
||||
.flatMap(service -> service.sendJoinCollectingRoomToClients(
|
||||
proctoringSettings,
|
||||
Arrays.asList(StringUtils.split(
|
||||
connectionTokens,
|
||||
Constants.LIST_SEPARATOR_CHAR))))
|
||||
.onError(error -> log.error("Failed to send rejoin collecting room to: {}", connectionTokens, error));
|
||||
}
|
||||
|
||||
@RequestMapping(
|
||||
|
@ -254,55 +255,19 @@ public class ExamProctoringController {
|
|||
|
||||
final ProctoringSettings settings = this.examSessionService
|
||||
.getRunningExam(examId)
|
||||
.flatMap(this.examAdminService::getExamProctoring)
|
||||
.flatMap(this.examAdminService::getExamProctoringSettings)
|
||||
.getOrThrow();
|
||||
|
||||
final ExamProctoringService examProctoringService = this.examAdminService
|
||||
return this.examAdminService
|
||||
.getExamProctoringService(settings.serverType)
|
||||
.flatMap(service -> service.sendJoinRoomToClients(
|
||||
settings,
|
||||
Arrays.asList(StringUtils.split(
|
||||
connectionTokens,
|
||||
Constants.LIST_SEPARATOR_CHAR)),
|
||||
roomName,
|
||||
subject))
|
||||
.getOrThrow();
|
||||
|
||||
if (StringUtils.isNotBlank(connectionTokens)) {
|
||||
|
||||
Arrays.asList(connectionTokens.split(Constants.LIST_SEPARATOR))
|
||||
.stream()
|
||||
.forEach(connectionToken -> {
|
||||
final SEBProctoringConnection proctoringConnection =
|
||||
examProctoringService
|
||||
.getClientRoomConnection(
|
||||
settings,
|
||||
connectionToken,
|
||||
verifyRoomName(roomName, connectionToken),
|
||||
(StringUtils.isNotBlank(subject)) ? subject : roomName)
|
||||
.onError(error -> log.error(
|
||||
"Failed to get client room connection data for {} cause: {}",
|
||||
connectionToken,
|
||||
error.getMessage()))
|
||||
.get();
|
||||
if (proctoringConnection != null) {
|
||||
sendJoinInstruction(settings.examId, connectionToken, proctoringConnection)
|
||||
.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();
|
||||
}
|
||||
|
||||
private String verifyRoomName(final String requestedRoomName, final String connectionToken) {
|
||||
if (StringUtils.isNotBlank(requestedRoomName)) {
|
||||
return requestedRoomName;
|
||||
}
|
||||
|
||||
final Encoder urlEncoder = Base64.getUrlEncoder().withoutPadding();
|
||||
return urlEncoder.encodeToString(
|
||||
Utils.toByteArray(connectionToken));
|
||||
}
|
||||
|
||||
@RequestMapping(
|
||||
|
@ -342,48 +307,27 @@ public class ExamProctoringController {
|
|||
|
||||
final ProctoringSettings settings = this.examSessionService
|
||||
.getRunningExam(examId)
|
||||
.flatMap(this.examAdminService::getExamProctoring)
|
||||
.flatMap(this.examAdminService::getExamProctoringSettings)
|
||||
.getOrThrow();
|
||||
|
||||
final ExamProctoringService examProctoringService = this.examAdminService
|
||||
// First create and get the town-hall room for specified exam
|
||||
final RemoteProctoringRoom townhallRoom = this.examProcotringRoomService
|
||||
.createTownhallRoom(examId, subject)
|
||||
.onError(error -> {
|
||||
log.error("Failed to create town-hall room: ", error);
|
||||
this.examProcotringRoomService.disposeTownhallRoom(examId);
|
||||
})
|
||||
.getOrThrow();
|
||||
|
||||
// Then send a join instruction to all active clients of the exam to join the town-hall
|
||||
return 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 RemoteProctoringRoom townhallRoom = this.examProcotringRoomService.createTownhallRoom(examId, subject)
|
||||
.onError(error -> this.examProcotringRoomService.disposeTownhallRoom(examId))
|
||||
.getOrThrow();
|
||||
|
||||
// get all active connections for the exam and send the join instruction
|
||||
this.examSessionService.getAllActiveConnectionData(examId)
|
||||
.getOrThrow()
|
||||
.stream()
|
||||
.forEach(cc -> {
|
||||
final SEBProctoringConnection data = examProctoringService
|
||||
.getClientRoomConnection(
|
||||
settings,
|
||||
cc.clientConnection.connectionToken,
|
||||
townhallRoom.name,
|
||||
townhallRoom.subject)
|
||||
.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,
|
||||
townhallRoom.name,
|
||||
townhallRoom.subject)
|
||||
.flatMap(service -> service.sendJoinRoomToClients(
|
||||
settings,
|
||||
this.examSessionService.getActiveConnectionTokens(examId)
|
||||
.getOrThrow(),
|
||||
townhallRoom.name,
|
||||
townhallRoom.subject))
|
||||
.getOrThrow();
|
||||
}
|
||||
|
||||
|
@ -403,7 +347,7 @@ public class ExamProctoringController {
|
|||
|
||||
final ProctoringSettings settings = this.examSessionService
|
||||
.getRunningExam(examId)
|
||||
.flatMap(this.examAdminService::getExamProctoring)
|
||||
.flatMap(this.examAdminService::getExamProctoringSettings)
|
||||
.getOrThrow();
|
||||
|
||||
final ExamProctoringService examProctoringService = this.examAdminService
|
||||
|
@ -413,25 +357,11 @@ public class ExamProctoringController {
|
|||
// first unregister the current room to collect all connection of the exam
|
||||
this.examProcotringRoomService.disposeTownhallRoom(examId);
|
||||
|
||||
// get all active connections for the exam and send the join instruction
|
||||
this.examSessionService.getConnectionData(
|
||||
examId,
|
||||
ExamSessionService.ACTIVE_CONNECTION_DATA_FILTER)
|
||||
.getOrThrow()
|
||||
.stream()
|
||||
.forEach(cc -> {
|
||||
examProctoringService
|
||||
.getClientExamCollectingRoomConnection(
|
||||
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()));
|
||||
});
|
||||
// then get all active connections for the exam and send the rejoin to collecting room instruction
|
||||
examProctoringService.sendJoinCollectingRoomToClients(
|
||||
settings,
|
||||
this.examSessionService.getActiveConnectionTokens(examId)
|
||||
.getOrThrow());
|
||||
}
|
||||
|
||||
private void sendBroadcastInstructions(
|
||||
|
@ -486,19 +416,19 @@ public class ExamProctoringController {
|
|||
|
||||
private void sendBroadcastInstructionToClientsInExam(final Long examId, final Map<String, String> attributes) {
|
||||
this.examSessionService
|
||||
.getAllActiveConnectionData(examId)
|
||||
.getActiveConnectionTokens(examId)
|
||||
.getOrThrow()
|
||||
.stream()
|
||||
.forEach(connection -> {
|
||||
.forEach(connectionToken -> {
|
||||
this.sebInstructionService.registerInstruction(
|
||||
examId,
|
||||
InstructionType.SEB_RECONFIGURE_SETTINGS,
|
||||
attributes,
|
||||
connection.clientConnection.connectionToken,
|
||||
connectionToken,
|
||||
true)
|
||||
.onError(error -> log.error(
|
||||
"Failed to register reconfiguring instruction for connection: {}",
|
||||
connection.clientConnection.connectionToken,
|
||||
connectionToken,
|
||||
error));
|
||||
});
|
||||
}
|
||||
|
@ -527,70 +457,6 @@ public class ExamProctoringController {
|
|||
});
|
||||
}
|
||||
|
||||
private void sendJoinInstructions(
|
||||
final String connectionTokens,
|
||||
final ProctoringSettings proctoringSettings) {
|
||||
|
||||
final ExamProctoringService examProctoringService = this.examAdminService
|
||||
.getExamProctoringService(proctoringSettings.serverType)
|
||||
.getOrThrow();
|
||||
|
||||
Arrays.asList(StringUtils.split(connectionTokens, Constants.LIST_SEPARATOR))
|
||||
.stream()
|
||||
.forEach(connectionToken -> {
|
||||
sendJoinInstructionToClient(proctoringSettings, examProctoringService, connectionToken);
|
||||
});
|
||||
}
|
||||
|
||||
private void sendJoinInstructionToClient(
|
||||
final ProctoringSettings proctoringSettings,
|
||||
final ExamProctoringService examProctoringService,
|
||||
final String connectionToken) {
|
||||
|
||||
this.examSessionService
|
||||
.getConnectionData(connectionToken)
|
||||
.flatMap(connection -> examProctoringService.getClientExamCollectingRoomConnection(
|
||||
proctoringSettings,
|
||||
connection.clientConnection))
|
||||
.flatMap(data -> this.sendJoinInstruction(
|
||||
proctoringSettings.examId,
|
||||
connectionToken, data))
|
||||
.onError(error -> log.error("Failed to send rejoin for: {} cause: {}",
|
||||
connectionToken,
|
||||
error.getMessage()));
|
||||
}
|
||||
|
||||
private Result<Void> sendJoinInstruction(
|
||||
final Long examId,
|
||||
final String connectionToken,
|
||||
final SEBProctoringConnection 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,
|
||||
|
|
|
@ -28,7 +28,7 @@ public class ExamJITSIProctoringServiceTest {
|
|||
final Cryptor cryptorMock = Mockito.mock(Cryptor.class);
|
||||
Mockito.when(cryptorMock.decrypt(Mockito.any())).thenReturn("fbvgeghergrgrthrehreg123");
|
||||
final ExamJITSIProctoringService examJITSIProctoringService =
|
||||
new ExamJITSIProctoringService(null, null, null, cryptorMock);
|
||||
new ExamJITSIProctoringService(null, null, null, null, cryptorMock);
|
||||
|
||||
String accessToken = examJITSIProctoringService.createPayload(
|
||||
"test-app",
|
||||
|
@ -62,7 +62,7 @@ public class ExamJITSIProctoringServiceTest {
|
|||
final Cryptor cryptorMock = Mockito.mock(Cryptor.class);
|
||||
Mockito.when(cryptorMock.decrypt(Mockito.any())).thenReturn("fbvgeghergrgrthrehreg123");
|
||||
final ExamJITSIProctoringService examJITSIProctoringService =
|
||||
new ExamJITSIProctoringService(null, null, null, cryptorMock);
|
||||
new ExamJITSIProctoringService(null, null, null, null, cryptorMock);
|
||||
final SEBProctoringConnection data = examJITSIProctoringService.createProctoringConnection(
|
||||
ProctoringServerType.JITSI_MEET,
|
||||
"connectionToken",
|
||||
|
|
Loading…
Reference in a new issue