refactor proctoring

This commit is contained in:
anhefti 2021-02-17 08:43:27 +01:00
parent ad7f06e521
commit 39f4c85d22
13 changed files with 368 additions and 398 deletions

View file

@ -52,6 +52,13 @@ public interface ClientConnectionDAO extends
return getConnectionTokens(examId); 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 /** Get a collection of all client connections records that needs a room update
* and that are in the status ACTIVE. * and that are in the status ACTIVE.
* This also flags the involved connections for no update needed within the * This also flags the involved connections for no update needed within the

View file

@ -146,6 +146,25 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
.collect(Collectors.toList())); .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 @Override
@Transactional @Transactional
public Result<Collection<ClientConnectionRecord>> getAllConnectionIdsForRoomUpdateActive() { public Result<Collection<ClientConnectionRecord>> getAllConnectionIdsForRoomUpdateActive() {

View file

@ -45,25 +45,25 @@ public interface ExamAdminService {
* *
* @param examId the exam instance * @param examId the exam instance
* @return Result refer to ExamProctoring data for the exam. */ * @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) { if (exam == null || exam.id == null) {
return Result.ofRuntimeError("Invalid Exam model"); 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. /** Get ExamProctoring data for a certain exam to an error when happened.
* *
* @param examId the exam identifier * @param examId the exam identifier
* @return Result refer to ExamProctoring data for the exam. */ * @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. /** Save the given ExamProctoring data for an existing Exam.
* *
* @param examId the exam identifier * @param examId the exam identifier
* @param examProctoring The ExamProctoring data to save for the exam * @param examProctoring The ExamProctoring data to save for the exam
* @return Result refer to saved ExamProctoring data or to an error when happened. */ * @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. /** 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. /** Get the exam proctoring service implementation of specified type.
* *
* @param type exam proctoring service server type * @param type exam proctoring service server type
* @return Result refer to the ExamProctoringService or to an error when happened */ * @return ExamProctoringService instance */
public Result<ExamProctoringService> getExamProctoringService(final ProctoringServerType type); 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);
}
} }

View file

@ -186,7 +186,7 @@ public class ExamAdminServiceImpl implements ExamAdminService {
} }
@Override @Override
public Result<ProctoringSettings> getExamProctoring(final Long examId) { public Result<ProctoringSettings> getExamProctoringSettings(final Long examId) {
return this.additionalAttributesDAO.getAdditionalAttributes(EntityType.EXAM, examId) return this.additionalAttributesDAO.getAdditionalAttributes(EntityType.EXAM, examId)
.map(attrs -> attrs.stream() .map(attrs -> attrs.stream()
.collect(Collectors.toMap( .collect(Collectors.toMap(
@ -206,7 +206,8 @@ public class ExamAdminServiceImpl implements ExamAdminService {
@Override @Override
@Transactional @Transactional
public Result<ProctoringSettings> saveExamProctoring(final Long examId, final ProctoringSettings examProctoring) { public Result<ProctoringSettings> saveExamProctoringSettings(final Long examId,
final ProctoringSettings examProctoring) {
return Result.tryCatch(() -> { return Result.tryCatch(() -> {
this.additionalAttributesDAO.saveAdditionalAttribute( this.additionalAttributesDAO.saveAdditionalAttribute(
@ -264,7 +265,8 @@ public class ExamAdminServiceImpl implements ExamAdminService {
@Override @Override
public Result<ExamProctoringService> getExamProctoringService(final ProctoringServerType type) { 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) { private Boolean getEnabled(final Map<String, AdditionalAttributeRecord> mapping) {

View file

@ -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.model.session.RemoteProctoringRoom;
import ch.ethz.seb.sebserver.gbl.util.Result; 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 { public interface ExamProctoringRoomService {
/** Get all existing default proctoring rooms of an exam. /** 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. * 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 * 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 * 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 * then makes the update synchronously to keep track on room creation and naming
* name of an exam. */ *
* 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(); void updateProctoringCollectingRooms();
/** This creates a town-hall room for a specific exam. The exam must be active and running /** This creates a town-hall room for a specific exam. The exam must be active and running

View file

@ -8,11 +8,17 @@
package ch.ethz.seb.sebserver.webservice.servicelayer.session; 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;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings.ProctoringServerType; 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.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.Result;
import ch.ethz.seb.sebserver.gbl.util.Utils;
public interface ExamProctoringService { 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. */ * @return Result refer to true if the settings are correct and the proctoring server can be accessed. */
Result<Boolean> testExamProctoring(final ProctoringSettings examProctoring); Result<Boolean> testExamProctoring(final ProctoringSettings examProctoring);
/** Used to get the proctor's room connection data. Result<SEBProctoringConnection> getProctorRoomConnection(
* ProctoringSettings proctoringSettings,
* @param proctoringSettings the proctoring settings String roomName,
* @param roomName the name of the room String subject);
* @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( Result<SEBProctoringConnection> sendJoinRoomToClients(
final ProctoringSettings proctoringSettings, ProctoringSettings proctoringSettings,
final ClientConnection connection); Collection<String> clientConnectionTokens,
String roomName,
String subject);
Result<SEBProctoringConnection> getClientExamCollectingRoomConnection( Result<Void> sendJoinCollectingRoomToClients(
final ProctoringSettings proctoringSettings, ProctoringSettings proctoringSettings,
final String connectionToken, Collection<String> clientConnectionTokens);
final String roomName,
final String subject);
Result<SEBProctoringConnection> getClientRoomConnection( default String verifyRoomName(final String requestedRoomName, final String connectionToken) {
final ProctoringSettings examProctoring, if (StringUtils.isNotBlank(requestedRoomName)) {
final String connectionToken, return requestedRoomName;
final String roomName, }
final String subject);
Result<SEBProctoringConnection> createProctoringConnection( final Encoder urlEncoder = Base64.getUrlEncoder().withoutPadding();
final ProctoringServerType proctoringServerType, return urlEncoder.encodeToString(
final String connectionToken, Utils.toByteArray(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);
Result<String> createClientAccessToken( // /** Used to get the proctor's room connection data.
final ProctoringSettings proctoringSettings, // *
final String connectionToken, // * @param proctoringSettings the proctoring settings
final String roomName); // * @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);
} }

View file

@ -155,9 +155,12 @@ public interface ExamSessionService {
Long examId, Long examId,
Predicate<ClientConnectionData> filter); Predicate<ClientConnectionData> filter);
default Result<Collection<ClientConnectionData>> getAllActiveConnectionData(final Long examId) { /** Gets all connection tokens of active client connection that are related to a specified exam
return getConnectionData(examId, ACTIVE_CONNECTION_DATA_FILTER); * 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 /** Use this to check if the current cached running exam is up to date
* and if not to flush the cache. * and if not to flush the cache.

View file

@ -13,10 +13,16 @@ import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.Base64; import java.util.Base64;
import java.util.Base64.Encoder; import java.util.Base64.Encoder;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.Mac; import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec; 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.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.util.UriComponentsBuilder; 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;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings.ProctoringServerType; 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.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.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.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Cryptor; import ch.ethz.seb.sebserver.gbl.util.Cryptor;
import ch.ethz.seb.sebserver.gbl.util.Result; 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.dao.RemoteProctoringRoomDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamProctoringService; 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.ExamSessionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientInstructionService;
@Lazy @Lazy
@Service @Service
@WebServiceProfile @WebServiceProfile
public class ExamJITSIProctoringService implements ExamProctoringService { public class ExamJITSIProctoringService implements ExamProctoringService {
private static final Logger log = LoggerFactory.getLogger(ExamJITSIProctoringService.class);
private static final String JITSI_ACCESS_TOKEN_HEADER = private static final String JITSI_ACCESS_TOKEN_HEADER =
"{\"alg\":\"HS256\",\"typ\":\"JWT\"}"; "{\"alg\":\"HS256\",\"typ\":\"JWT\"}";
@ -51,17 +61,20 @@ public class ExamJITSIProctoringService implements ExamProctoringService {
private final RemoteProctoringRoomDAO remoteProctoringRoomDAO; private final RemoteProctoringRoomDAO remoteProctoringRoomDAO;
private final AuthorizationService authorizationService; private final AuthorizationService authorizationService;
private final ExamSessionService examSessionService; private final ExamSessionService examSessionService;
private final SEBClientInstructionService sebClientInstructionService;
private final Cryptor cryptor; private final Cryptor cryptor;
protected ExamJITSIProctoringService( protected ExamJITSIProctoringService(
final RemoteProctoringRoomDAO remoteProctoringRoomDAO, final RemoteProctoringRoomDAO remoteProctoringRoomDAO,
final AuthorizationService authorizationService, final AuthorizationService authorizationService,
final ExamSessionService examSessionService, final ExamSessionService examSessionService,
final SEBClientInstructionService sebClientInstructionService,
final Cryptor cryptor) { final Cryptor cryptor) {
this.remoteProctoringRoomDAO = remoteProctoringRoomDAO; this.remoteProctoringRoomDAO = remoteProctoringRoomDAO;
this.authorizationService = authorizationService; this.authorizationService = authorizationService;
this.examSessionService = examSessionService; this.examSessionService = examSessionService;
this.sebClientInstructionService = sebClientInstructionService;
this.cryptor = cryptor; this.cryptor = cryptor;
} }
@ -77,7 +90,124 @@ public class ExamJITSIProctoringService implements ExamProctoringService {
} }
@Override @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 ProctoringSettings proctoringSettings,
final String roomName, final String roomName,
final String subject) { final String subject) {
@ -99,35 +229,7 @@ public class ExamJITSIProctoringService implements ExamProctoringService {
}); });
} }
@Override private Result<SEBProctoringConnection> getClientExamCollectingRoomConnection(
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(
final ProctoringSettings proctoringSettings, final ProctoringSettings proctoringSettings,
final String connectionToken, final String connectionToken,
final String roomName, final String roomName,
@ -154,8 +256,7 @@ public class ExamJITSIProctoringService implements ExamProctoringService {
}); });
} }
@Override private Result<SEBProctoringConnection> getClientRoomConnection(
public Result<SEBProctoringConnection> getClientRoomConnection(
final ProctoringSettings proctoringSettings, final ProctoringSettings proctoringSettings,
final String connectionToken, final String connectionToken,
final String roomName, final String roomName,
@ -185,8 +286,7 @@ public class ExamJITSIProctoringService implements ExamProctoringService {
} }
@Override protected Result<SEBProctoringConnection> createProctoringConnection(
public Result<SEBProctoringConnection> createProctoringConnection(
final ProctoringServerType proctoringServerType, final ProctoringServerType proctoringServerType,
final String connectionToken, final String connectionToken,
final String url, 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( protected String internalCreateAccessToken(
final String appKey, final String appKey,
final CharSequence appSecret, final CharSequence appSecret,

View file

@ -8,9 +8,8 @@
package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl; package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID; import java.util.UUID;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -19,12 +18,7 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringSettings; 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.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.model.session.RemoteProctoringRoom;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result; 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.exam.ExamAdminService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamProctoringRoomService; 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.ExamSessionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientInstructionService;
@Lazy @Lazy
@Service @Service
@ -45,20 +38,17 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
private final RemoteProctoringRoomDAO remoteProctoringRoomDAO; private final RemoteProctoringRoomDAO remoteProctoringRoomDAO;
private final ClientConnectionDAO clientConnectionDAO; private final ClientConnectionDAO clientConnectionDAO;
private final SEBClientInstructionService sebInstructionService;
private final ExamAdminService examAdminService; private final ExamAdminService examAdminService;
private final ExamSessionService examSessionService; private final ExamSessionService examSessionService;
public ExamProctoringRoomServiceImpl( public ExamProctoringRoomServiceImpl(
final RemoteProctoringRoomDAO remoteProctoringRoomDAO, final RemoteProctoringRoomDAO remoteProctoringRoomDAO,
final ClientConnectionDAO clientConnectionDAO, final ClientConnectionDAO clientConnectionDAO,
final SEBClientInstructionService sebInstructionService,
final ExamAdminService examAdminService, final ExamAdminService examAdminService,
final ExamSessionService examSessionService) { final ExamSessionService examSessionService) {
this.remoteProctoringRoomDAO = remoteProctoringRoomDAO; this.remoteProctoringRoomDAO = remoteProctoringRoomDAO;
this.clientConnectionDAO = clientConnectionDAO; this.clientConnectionDAO = clientConnectionDAO;
this.sebInstructionService = sebInstructionService;
this.examAdminService = examAdminService; this.examAdminService = examAdminService;
this.examSessionService = examSessionService; this.examSessionService = examSessionService;
} }
@ -84,7 +74,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
this.clientConnectionDAO.getAllConnectionIdsForRoomUpdateActive() this.clientConnectionDAO.getAllConnectionIdsForRoomUpdateActive()
.getOrThrow() .getOrThrow()
.stream() .stream()
.forEach(this::assignToRoom); .forEach(this::assignToCollectingRoom);
this.clientConnectionDAO.getAllConnectionIdsForRoomUpdateInactive() this.clientConnectionDAO.getAllConnectionIdsForRoomUpdateInactive()
.getOrThrow() .getOrThrow()
@ -114,40 +104,40 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
this.remoteProctoringRoomDAO.deleteTownhallRoom(examId); this.remoteProctoringRoomDAO.deleteTownhallRoom(examId);
} }
private void assignToRoom(final ClientConnectionRecord cc) { private void assignToCollectingRoom(final ClientConnectionRecord cc) {
try { try {
final RemoteProctoringRoom proctoringRoom = getProctoringRoom( final RemoteProctoringRoom proctoringRoom = getProctoringRoom(
cc.getExamId(), cc.getExamId(),
cc.getConnectionToken()); cc.getConnectionToken());
if (proctoringRoom != null) {
this.clientConnectionDAO.assignToProctoringRoom( this.clientConnectionDAO.assignToProctoringRoom(
cc.getId(), cc.getId(),
cc.getConnectionToken(), cc.getConnectionToken(),
proctoringRoom.id) proctoringRoom.id)
.onError(error -> log.error("Failed to assign to proctoring room: ", error))
.getOrThrow(); .getOrThrow();
final Result<RemoteProctoringRoom> townhallRoomResult = this.remoteProctoringRoomDAO final Result<RemoteProctoringRoom> townhallRoomResult = this.remoteProctoringRoomDAO
.getTownhallRoom(cc.getExamId()); .getTownhallRoom(cc.getExamId());
if (townhallRoomResult.hasValue()) { if (townhallRoomResult.hasValue()) {
final RemoteProctoringRoom townhallRoom = townhallRoomResult.get(); final RemoteProctoringRoom townhallRoom = townhallRoomResult.get();
applyProcotringInstruction( applyProcotringInstruction(
cc.getExamId(), cc.getExamId(),
cc.getConnectionToken(), cc.getConnectionToken(),
townhallRoom.name, townhallRoom.name,
townhallRoom.subject); townhallRoom.subject)
.getOrThrow();
} else { } else {
applyProcotringInstruction( applyProcotringInstruction(
cc.getExamId(), cc.getExamId(),
cc.getConnectionToken(), cc.getConnectionToken(),
proctoringRoom.name, proctoringRoom.name,
proctoringRoom.subject); proctoringRoom.subject)
} .getOrThrow();
} }
} catch (final Exception e) { } catch (final Exception e) {
log.error("Failed to update client connection for proctoring room: ", e); log.error("Failed to assign connection to collecting room: {}", cc, e);
this.clientConnectionDAO.setNeedsRoomUpdate(cc.getId());
} }
} }
@ -167,7 +157,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
private RemoteProctoringRoom getProctoringRoom(final Long examId, final String connectionToken) { private RemoteProctoringRoom getProctoringRoom(final Long examId, final String connectionToken) {
try { try {
final ProctoringSettings proctoringSettings = this.examAdminService final ProctoringSettings proctoringSettings = this.examAdminService
.getExamProctoring(examId) .getExamProctoringSettings(examId)
.getOrThrow(); .getOrThrow();
return this.remoteProctoringRoomDAO.reservePlaceInCollectingRoom( return this.remoteProctoringRoomDAO.reservePlaceInCollectingRoom(
examId, examId,
@ -184,63 +174,21 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
} }
} }
private void applyProcotringInstruction( private Result<Void> applyProcotringInstruction(
final Long examId, final Long examId,
final String connectionToken, final String connectionToken,
final String roomName, final String roomName,
final String subject) { final String subject) {
try { return this.examAdminService
// apply a SEB_PROCOTIRNG instruction for the specified SEB client connection .getExamProctoringSettings(examId)
final ProctoringSettings proctoringSettings = this.examAdminService .flatMap(proctoringSettings -> this.examAdminService
.getExamProctoring(examId) .getExamProctoringService(proctoringSettings.serverType)
.getOrThrow(); .getOrThrow()
.sendJoinCollectingRoomToClients(
final SEBProctoringConnection proctoringData =
this.examAdminService.getExamProctoringService(proctoringSettings.serverType)
.flatMap(s -> s.getClientExamCollectingRoomConnection(
proctoringSettings, proctoringSettings,
connectionToken, Arrays.asList(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);
}
} }
} }

View file

@ -379,6 +379,12 @@ public class ExamSessionServiceImpl implements ExamSessionService {
} }
} }
@Override
public Result<Collection<String>> getActiveConnectionTokens(final Long examId) {
return this.clientConnectionDAO
.getActiveConnctionTokens(examId);
}
@Override @Override
public Result<Exam> updateExamCache(final Long examId) { public Result<Exam> updateExamCache(final Long examId) {
final Exam exam = this.examSessionCacheService.getRunningExam(examId); final Exam exam = this.examSessionCacheService.getRunningExam(examId);

View file

@ -396,7 +396,7 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
checkReadPrivilege(institutionId); checkReadPrivilege(institutionId);
return this.entityDAO.byPK(modelId) return this.entityDAO.byPK(modelId)
.flatMap(this.authorization::checkRead) .flatMap(this.authorization::checkRead)
.flatMap(this.examAdminService::getExamProctoring) .flatMap(this.examAdminService::getExamProctoringSettings)
.getOrThrow(); .getOrThrow();
} }
@ -417,7 +417,7 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
return this.entityDAO.byPK(examId) return this.entityDAO.byPK(examId)
.flatMap(this.authorization::checkModify) .flatMap(this.authorization::checkModify)
.map(exam -> { .map(exam -> {
this.examAdminService.saveExamProctoring(examId, examProctoring); this.examAdminService.saveExamProctoringSettings(examId, examProctoring);
return exam; return exam;
}) })
.flatMap(this.userActivityLogDAO::logModify) .flatMap(this.userActivityLogDAO::logModify)

View file

@ -9,8 +9,6 @@
package ch.ethz.seb.sebserver.webservice.weblayer.api; package ch.ethz.seb.sebserver.webservice.weblayer.api;
import java.util.Arrays; import java.util.Arrays;
import java.util.Base64;
import java.util.Base64.Encoder;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; 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.ClientInstruction.InstructionType;
import ch.ethz.seb.sebserver.gbl.model.session.RemoteProctoringRoom; import ch.ethz.seb.sebserver.gbl.model.session.RemoteProctoringRoom;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; 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.AuthorizationService;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.UserService; 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.exam.ExamAdminService;
@ -112,7 +108,7 @@ public class ExamProctoringController {
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE) produces = MediaType.APPLICATION_JSON_VALUE)
public SEBProctoringConnection getProctorRoomData( public SEBProctoringConnection getProctorRoomConnection(
@RequestParam( @RequestParam(
name = API.PARAM_INSTITUTION_ID, name = API.PARAM_INSTITUTION_ID,
required = true, required = true,
@ -125,13 +121,11 @@ public class ExamProctoringController {
return this.examSessionService.getRunningExam(examId) return this.examSessionService.getRunningExam(examId)
.flatMap(this.authorization::checkRead) .flatMap(this.authorization::checkRead)
.flatMap(this.examAdminService::getExamProctoring) .flatMap(this.examAdminService::getExamProctoringService)
.flatMap(proc -> this.examAdminService .flatMap(service -> service.getProctorRoomConnection(
.getExamProctoringService(proc.serverType) this.examAdminService.getExamProctoringSettings(examId).getOrThrow(),
.flatMap(s -> s.createProctorPublicRoomConnection(
proc,
roomName, roomName,
StringUtils.isNoneBlank(subject) ? subject : roomName))) StringUtils.isNoneBlank(subject) ? subject : roomName))
.getOrThrow(); .getOrThrow();
} }
@ -223,10 +217,17 @@ public class ExamProctoringController {
final ProctoringSettings proctoringSettings = this.examSessionService final ProctoringSettings proctoringSettings = this.examSessionService
.getRunningExam(examId) .getRunningExam(examId)
.flatMap(this.examAdminService::getExamProctoring) .flatMap(this.examAdminService::getExamProctoringSettings)
.getOrThrow(); .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( @RequestMapping(
@ -254,57 +255,21 @@ public class ExamProctoringController {
final ProctoringSettings settings = this.examSessionService final ProctoringSettings settings = this.examSessionService
.getRunningExam(examId) .getRunningExam(examId)
.flatMap(this.examAdminService::getExamProctoring) .flatMap(this.examAdminService::getExamProctoringSettings)
.getOrThrow(); .getOrThrow();
final ExamProctoringService examProctoringService = this.examAdminService return this.examAdminService
.getExamProctoringService(settings.serverType) .getExamProctoringService(settings.serverType)
.getOrThrow(); .flatMap(service -> service.sendJoinRoomToClients(
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, settings,
Arrays.asList(StringUtils.split(
connectionTokens,
Constants.LIST_SEPARATOR_CHAR)),
roomName, roomName,
(StringUtils.isNotBlank(subject)) ? subject : roomName) subject))
.getOrThrow(); .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( @RequestMapping(
path = API.MODEL_ID_VAR_PATH_SEGMENT path = API.MODEL_ID_VAR_PATH_SEGMENT
+ API.EXAM_PROCTORING_TOWNHALL_ROOM_DATA, + API.EXAM_PROCTORING_TOWNHALL_ROOM_DATA,
@ -342,48 +307,27 @@ public class ExamProctoringController {
final ProctoringSettings settings = this.examSessionService final ProctoringSettings settings = this.examSessionService
.getRunningExam(examId) .getRunningExam(examId)
.flatMap(this.examAdminService::getExamProctoring) .flatMap(this.examAdminService::getExamProctoringSettings)
.getOrThrow(); .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) .getExamProctoringService(settings.serverType)
.getOrThrow(); .flatMap(service -> service.sendJoinRoomToClients(
// 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, settings,
cc.clientConnection.connectionToken, this.examSessionService.getActiveConnectionTokens(examId)
.getOrThrow(),
townhallRoom.name, townhallRoom.name,
townhallRoom.subject) 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)
.getOrThrow(); .getOrThrow();
} }
@ -403,7 +347,7 @@ public class ExamProctoringController {
final ProctoringSettings settings = this.examSessionService final ProctoringSettings settings = this.examSessionService
.getRunningExam(examId) .getRunningExam(examId)
.flatMap(this.examAdminService::getExamProctoring) .flatMap(this.examAdminService::getExamProctoringSettings)
.getOrThrow(); .getOrThrow();
final ExamProctoringService examProctoringService = this.examAdminService final ExamProctoringService examProctoringService = this.examAdminService
@ -413,25 +357,11 @@ public class ExamProctoringController {
// first unregister the current room to collect all connection of the exam // first unregister the current room to collect all connection of the exam
this.examProcotringRoomService.disposeTownhallRoom(examId); this.examProcotringRoomService.disposeTownhallRoom(examId);
// get all active connections for the exam and send the join instruction // then get all active connections for the exam and send the rejoin to collecting room instruction
this.examSessionService.getConnectionData( examProctoringService.sendJoinCollectingRoomToClients(
examId,
ExamSessionService.ACTIVE_CONNECTION_DATA_FILTER)
.getOrThrow()
.stream()
.forEach(cc -> {
examProctoringService
.getClientExamCollectingRoomConnection(
settings, settings,
cc.clientConnection) this.examSessionService.getActiveConnectionTokens(examId)
.flatMap(data -> this.sendJoinInstruction( .getOrThrow());
examId,
cc.clientConnection.connectionToken,
data))
.onError(error -> log.error("Failed to send rejoin for: {} cause: {}",
cc.clientConnection.connectionToken,
error.getMessage()));
});
} }
private void sendBroadcastInstructions( private void sendBroadcastInstructions(
@ -486,19 +416,19 @@ public class ExamProctoringController {
private void sendBroadcastInstructionToClientsInExam(final Long examId, final Map<String, String> attributes) { private void sendBroadcastInstructionToClientsInExam(final Long examId, final Map<String, String> attributes) {
this.examSessionService this.examSessionService
.getAllActiveConnectionData(examId) .getActiveConnectionTokens(examId)
.getOrThrow() .getOrThrow()
.stream() .stream()
.forEach(connection -> { .forEach(connectionToken -> {
this.sebInstructionService.registerInstruction( this.sebInstructionService.registerInstruction(
examId, examId,
InstructionType.SEB_RECONFIGURE_SETTINGS, InstructionType.SEB_RECONFIGURE_SETTINGS,
attributes, attributes,
connection.clientConnection.connectionToken, connectionToken,
true) true)
.onError(error -> log.error( .onError(error -> log.error(
"Failed to register reconfiguring instruction for connection: {}", "Failed to register reconfiguring instruction for connection: {}",
connection.clientConnection.connectionToken, connectionToken,
error)); 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) { private void checkAccess(final Long institutionId, final Long examId) {
this.authorization.check( this.authorization.check(
PrivilegeType.READ, PrivilegeType.READ,

View file

@ -28,7 +28,7 @@ public class ExamJITSIProctoringServiceTest {
final Cryptor cryptorMock = Mockito.mock(Cryptor.class); final Cryptor cryptorMock = Mockito.mock(Cryptor.class);
Mockito.when(cryptorMock.decrypt(Mockito.any())).thenReturn("fbvgeghergrgrthrehreg123"); Mockito.when(cryptorMock.decrypt(Mockito.any())).thenReturn("fbvgeghergrgrthrehreg123");
final ExamJITSIProctoringService examJITSIProctoringService = final ExamJITSIProctoringService examJITSIProctoringService =
new ExamJITSIProctoringService(null, null, null, cryptorMock); new ExamJITSIProctoringService(null, null, null, null, cryptorMock);
String accessToken = examJITSIProctoringService.createPayload( String accessToken = examJITSIProctoringService.createPayload(
"test-app", "test-app",
@ -62,7 +62,7 @@ public class ExamJITSIProctoringServiceTest {
final Cryptor cryptorMock = Mockito.mock(Cryptor.class); final Cryptor cryptorMock = Mockito.mock(Cryptor.class);
Mockito.when(cryptorMock.decrypt(Mockito.any())).thenReturn("fbvgeghergrgrthrehreg123"); Mockito.when(cryptorMock.decrypt(Mockito.any())).thenReturn("fbvgeghergrgrthrehreg123");
final ExamJITSIProctoringService examJITSIProctoringService = final ExamJITSIProctoringService examJITSIProctoringService =
new ExamJITSIProctoringService(null, null, null, cryptorMock); new ExamJITSIProctoringService(null, null, null, null, cryptorMock);
final SEBProctoringConnection data = examJITSIProctoringService.createProctoringConnection( final SEBProctoringConnection data = examJITSIProctoringService.createProctoringConnection(
ProctoringServerType.JITSI_MEET, ProctoringServerType.JITSI_MEET,
"connectionToken", "connectionToken",