SEBSERV-490 join proctoring in Ready state only for Exam without LMS

This commit is contained in:
anhefti 2023-12-21 16:02:51 +01:00
parent cfa9fba9cb
commit 74d4b1ff7d
4 changed files with 62 additions and 45 deletions

View file

@ -251,7 +251,9 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
final Collection<ClientConnectionRecord> records = this.clientConnectionRecordMapper final Collection<ClientConnectionRecord> records = this.clientConnectionRecordMapper
.selectByExample() .selectByExample()
.where(ClientConnectionRecordDynamicSqlSupport.remoteProctoringRoomUpdate, isNotEqualTo(0)) .where(ClientConnectionRecordDynamicSqlSupport.remoteProctoringRoomUpdate, isNotEqualTo(0))
.and(ClientConnectionRecordDynamicSqlSupport.status, isEqualTo(ConnectionStatus.ACTIVE.name())) .and(ClientConnectionRecordDynamicSqlSupport.remoteProctoringRoomId, isNull())
.and(ClientConnectionRecordDynamicSqlSupport.status, isEqualTo(ConnectionStatus.ACTIVE.name()),
or(ClientConnectionRecordDynamicSqlSupport.status, isEqualTo(ConnectionStatus.READY.name())))
.build() .build()
.execute(); .execute();
@ -292,7 +294,7 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
.build() .build()
.execute() .execute()
.stream() .stream()
.map(r -> r.getConnectionToken()) .map(ClientConnectionRecord::getConnectionToken)
.collect(Collectors.toList()); .collect(Collectors.toList());
}); });
} }
@ -305,6 +307,7 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
.selectByExample() .selectByExample()
.where(ClientConnectionRecordDynamicSqlSupport.remoteProctoringRoomUpdate, isNotEqualTo(0)) .where(ClientConnectionRecordDynamicSqlSupport.remoteProctoringRoomUpdate, isNotEqualTo(0))
.and(ClientConnectionRecordDynamicSqlSupport.status, isNotEqualTo(ConnectionStatus.ACTIVE.name())) .and(ClientConnectionRecordDynamicSqlSupport.status, isNotEqualTo(ConnectionStatus.ACTIVE.name()))
.and(ClientConnectionRecordDynamicSqlSupport.status, isNotEqualTo(ConnectionStatus.READY.name()))
.build() .build()
.execute(); .execute();
@ -551,7 +554,8 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
.selectByExample() .selectByExample()
.where(ClientConnectionRecordDynamicSqlSupport.screenProctoringGroupUpdate, isNotEqualTo((byte) 0)) .where(ClientConnectionRecordDynamicSqlSupport.screenProctoringGroupUpdate, isNotEqualTo((byte) 0))
.and(ClientConnectionRecordDynamicSqlSupport.examId, isIn(examIds)) .and(ClientConnectionRecordDynamicSqlSupport.examId, isIn(examIds))
.and(ClientConnectionRecordDynamicSqlSupport.status, isEqualTo(ConnectionStatus.ACTIVE.name())) .and(ClientConnectionRecordDynamicSqlSupport.status, isEqualTo(ConnectionStatus.ACTIVE.name()),
or(ClientConnectionRecordDynamicSqlSupport.status, isEqualTo(ConnectionStatus.READY.name())))
.build() .build()
.execute(); .execute();
@ -588,7 +592,7 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
this.clientConnectionRecordMapper::update, this.clientConnectionRecordMapper::update,
ClientConnectionRecordDynamicSqlSupport.clientConnectionRecord) ClientConnectionRecordDynamicSqlSupport.clientConnectionRecord)
.set(ClientConnectionRecordDynamicSqlSupport.screenProctoringGroupId).equalTo(groupId) .set(ClientConnectionRecordDynamicSqlSupport.screenProctoringGroupId).equalTo(groupId)
//.set(ClientConnectionRecordDynamicSqlSupport.screenProctoringGroupUpdate).equalTo((byte) 0) .set(ClientConnectionRecordDynamicSqlSupport.screenProctoringGroupUpdate).equalTo((byte) 0)
.where(ClientConnectionRecordDynamicSqlSupport.id, isEqualTo(connectionId)) .where(ClientConnectionRecordDynamicSqlSupport.id, isEqualTo(connectionId))
.build() .build()
.execute(); .execute();

View file

@ -66,7 +66,7 @@ public interface RemoteProctoringRoomService extends SessionUpdateTask {
* 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 synchronously to keep track on room creation and naming * then makes the update synchronously to keep track on room creation and naming
* * <p>
* If for a specified exam the town-hall room is active incoming client connection are instructed to * 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. */ * join the town-hall room. If not, incoming client connection are instructed to join a collecting room. */
void updateProctoringCollectingRooms(); void updateProctoringCollectingRooms();

View file

@ -10,6 +10,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Comparator;
import java.util.List; import java.util.List;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -58,14 +59,10 @@ public class ExamSessionControlTask implements DisposableBean {
this.pingUpdateRate = pingUpdateRate; this.pingUpdateRate = pingUpdateRate;
this.examUpdateTasks = new ArrayList<>(examUpdateTasks); this.examUpdateTasks = new ArrayList<>(examUpdateTasks);
this.examUpdateTasks.sort((t1, t2) -> Integer.compare( this.examUpdateTasks.sort(Comparator.comparingInt(ExamUpdateTask::examUpdateTaskProcessingOrder));
t1.examUpdateTaskProcessingOrder(),
t2.examUpdateTaskProcessingOrder()));
this.sessionUpdateTasks = new ArrayList<>(sessionUpdateTasks); this.sessionUpdateTasks = new ArrayList<>(sessionUpdateTasks);
this.sessionUpdateTasks.sort((t1, t2) -> Integer.compare( this.sessionUpdateTasks.sort(Comparator.comparingInt(SessionUpdateTask::sessionUpdateTaskProcessingOrder));
t1.sessionUpdateTaskProcessingOrder(),
t2.sessionUpdateTaskProcessingOrder()));
} }
@EventListener(SEBServerInitEvent.class) @EventListener(SEBServerInitEvent.class)

View file

@ -19,6 +19,8 @@ import java.util.stream.Stream;
import ch.ethz.seb.sebserver.gbl.api.APIMessage; import ch.ethz.seb.sebserver.gbl.api.APIMessage;
import ch.ethz.seb.sebserver.gbl.api.JSONMapper; import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
import ch.ethz.seb.sebserver.gbl.model.Entity; import ch.ethz.seb.sebserver.gbl.model.Entity;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings;
import ch.ethz.seb.sebserver.gbl.model.exam.ScreenProctoringSettings;
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent; import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
import ch.ethz.seb.sebserver.gbl.model.session.ClientInstruction; import ch.ethz.seb.sebserver.gbl.model.session.ClientInstruction;
import ch.ethz.seb.sebserver.gbl.util.Utils; import ch.ethz.seb.sebserver.gbl.util.Utils;
@ -258,16 +260,18 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
clientConnection); clientConnection);
final ConnectionStatus currentStatus = clientConnection.getStatus(); final ConnectionStatus currentStatus = clientConnection.getStatus();
final ConnectionStatus newStatus = (StringUtils.isNotBlank(userSessionId) && currentStatus == ConnectionStatus.READY)
? ConnectionStatus.ACTIVE
: currentStatus;
final String signatureHash = StringUtils.isNotBlank(appSignatureKey) final String signatureHash = StringUtils.isNotBlank(appSignatureKey)
? getSignatureHash(appSignatureKey, connectionToken, _examId) ? getSignatureHash(appSignatureKey, connectionToken, _examId)
: null; : null;
final ClientConnection updateConnection = new ClientConnection( final ClientConnection updateConnection = new ClientConnection(
clientConnection.id, clientConnection.id,
null, null,
examId, examId,
(StringUtils.isNotBlank(userSessionId) && currentStatus == ConnectionStatus.READY) newStatus,
? ConnectionStatus.ACTIVE
: null,
null, null,
updateUserSessionId, updateUserSessionId,
StringUtils.isNotBlank(clientAddress) ? clientAddress : null, StringUtils.isNotBlank(clientAddress) ? clientAddress : null,
@ -280,9 +284,9 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
null, null,
null, null,
null, null,
applyScreenProctoring(_examId, newStatus),
null, null,
null, applyProctoring(_examId, newStatus),
null,
null, null,
signatureHash, signatureHash,
null); null);
@ -311,6 +315,8 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
}); });
} }
@Override @Override
public Result<ClientConnection> establishClientConnection( public Result<ClientConnection> establishClientConnection(
final String connectionToken, final String connectionToken,
@ -331,12 +337,7 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
connectionStatusIntegrityCheck(clientConnection, clientAddress); connectionStatusIntegrityCheck(clientConnection, clientAddress);
checkInstitutionalIntegrity(institutionId, clientConnection); checkInstitutionalIntegrity(institutionId, clientConnection);
checkExamIntegrity( checkExamIntegrity(examId, clientConnection.examId, institutionId, clientConnection.connectionToken, clientConnection.info);
examId,
clientConnection.examId,
institutionId,
clientConnection.connectionToken,
clientConnection.info);
if (log.isDebugEnabled()) { if (log.isDebugEnabled()) {
log.debug( log.debug(
@ -350,28 +351,17 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
clientId, clientId,
sebMachineName, sebMachineName,
clientConnection); clientConnection);
final Boolean proctoringEnabled = this.examAdminService
.isProctoringEnabled(clientConnection.examId)
.getOr(false);
final Boolean screenProctoringEnabled = this.examAdminService
.isScreenProctoringEnabled(clientConnection.examId)
.getOr(false);
final Long currentExamId = (examId != null) ? examId : clientConnection.examId;
final String currentVdiConnectionId = (clientId != null)
? clientId
: clientConnection.sebClientUserId;
final ConnectionStatus newStatus = StringUtils.isNotBlank(userSessionId) || alreadyAuthenticated(clientConnection)
? ConnectionStatus.ACTIVE
: ConnectionStatus.READY;
// create new ClientConnection for update // create new ClientConnection for update
final ClientConnection establishedClientConnection = new ClientConnection( final ClientConnection establishedClientConnection = new ClientConnection(
clientConnection.id, clientConnection.id,
null, null,
currentExamId, _examId,
StringUtils.isNotBlank(userSessionId) || alreadyAuthenticated(clientConnection) newStatus,
? ConnectionStatus.ACTIVE
: ConnectionStatus.READY,
null, null,
updateUserSessionId, updateUserSessionId,
StringUtils.isNotBlank(clientAddress) ? clientAddress : null, StringUtils.isNotBlank(clientAddress) ? clientAddress : null,
@ -384,9 +374,9 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
null, null,
null, null,
null, null,
screenProctoringEnabled, applyScreenProctoring(_examId, newStatus),
null, null,
proctoringEnabled, applyProctoring(_examId, newStatus),
null, null,
getSignatureHash(appSignatureKey, connectionToken, _examId), getSignatureHash(appSignatureKey, connectionToken, _examId),
null); null);
@ -394,7 +384,7 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
// ClientConnection integrity check // ClientConnection integrity check
if (clientConnection.institutionId == null || if (clientConnection.institutionId == null ||
clientConnection.connectionToken == null || clientConnection.connectionToken == null ||
currentExamId == null || _examId == null ||
(clientConnection.clientAddress == null && clientAddress == null)) { (clientConnection.clientAddress == null && clientAddress == null)) {
log.error("ClientConnection integrity violation, clientConnection: {}, updatedClientConnection: {}", log.error("ClientConnection integrity violation, clientConnection: {}, updatedClientConnection: {}",
@ -835,10 +825,9 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
if (this.isDistributedSetup) { if (this.isDistributedSetup) {
// if the cached Exam is not up-to-date anymore, we have to update the cache first // if the cached Exam is not up-to-date anymore, we have to update the cache first
final Result<Exam> updateExamCache = this.examSessionService.updateExamCache(examId); this.examSessionService
if (updateExamCache.hasError()) { .updateExamCache(examId)
log.warn("Failed to update Exam-Cache for Exam: {}", examId); .onError(error -> log.warn("Failed to update Exam-Cache for Exam: {}", examId));
}
} }
if (currentExamId != null && !examId.equals(currentExamId)) { if (currentExamId != null && !examId.equals(currentExamId)) {
@ -922,4 +911,31 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
!clientConnection.userSessionId.equals(clientConnection.sebClientUserId) && !clientConnection.userSessionId.equals(clientConnection.sebClientUserId) &&
!clientConnection.userSessionId.equals(clientConnection.sebMachineName); !clientConnection.userSessionId.equals(clientConnection.sebMachineName);
} }
private boolean applyProctoring(final Long examId, final ConnectionStatus status) {
if (examId == null) {
return false;
}
final Exam exam = this.examSessionCacheService.getRunningExam(examId);
final boolean proctoringEnabled = exam != null && BooleanUtils.toBoolean(
exam.getAdditionalAttribute(ProctoringServiceSettings.ATTR_ENABLE_PROCTORING));
return isApplyProctoring(status, exam) && proctoringEnabled;
}
private boolean applyScreenProctoring(final Long examId, final ConnectionStatus status) {
if (examId == null) {
return false;
}
final Exam exam = this.examSessionCacheService.getRunningExam(examId);
final boolean screenProctoringEnabled = exam != null && BooleanUtils.toBoolean(
exam.getAdditionalAttribute(ScreenProctoringSettings.ATTR_ENABLE_SCREEN_PROCTORING));
return isApplyProctoring(status, exam) && screenProctoringEnabled;
}
private static boolean isApplyProctoring(final ConnectionStatus status, final Exam exam) {
return (exam != null && exam.lmsSetupId == null && status == ConnectionStatus.READY) ||
status == ConnectionStatus.ACTIVE;
}
} }