From 50456b8d9b41876fe26c66d17bf191247ff9373f Mon Sep 17 00:00:00 2001 From: anhefti Date: Mon, 27 Nov 2023 13:15:36 +0100 Subject: [PATCH] SEBSERV-440 implemented --- .../seb/sebserver/gbl/api/APIMessage.java | 5 +- .../gbl/model/session/ClientConnection.java | 2 +- .../dao/impl/ClientConnectionDAOImpl.java | 2 +- .../session/SEBClientConnectionService.java | 15 + .../session/SEBClientInstructionService.java | 18 +- .../session/SEBClientSessionService.java | 4 +- .../session/impl/ExamSessionServiceImpl.java | 41 +- .../impl/SEBClientConnectionServiceImpl.java | 665 +++++++++--------- .../impl/SEBClientInstructionServiceImpl.java | 42 +- .../SEBClientNotificationServiceImpl.java | 4 +- .../impl/SEBClientPingBatchService.java | 23 +- .../impl/SEBClientPingBlockingService.java | 22 +- .../RemoteProctoringRoomServiceImpl.java | 2 + .../ScreenProctoringServiceImpl.java | 1 + .../proctoring/ZoomProctoringService.java | 1 + .../weblayer/api/APIExceptionHandler.java | 2 +- .../weblayer/api/ExamAPI_V1_Controller.java | 90 +-- .../api/ExamMonitoringController.java | 9 +- .../oauth/WebClientDetailsService.java | 4 +- .../integration/UseCasesIntegrationTest.java | 4 +- .../api/exam/SEBConnectionAPITest.java | 247 +++++++ .../api/exam/SebConnectionTest.java | 6 +- .../SEBClientInstructionServiceTest.java | 6 +- 23 files changed, 749 insertions(+), 466 deletions(-) create mode 100644 src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/exam/SEBConnectionAPITest.java diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/api/APIMessage.java b/src/main/java/ch/ethz/seb/sebserver/gbl/api/APIMessage.java index 4a3f8a32..dfa794d1 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/api/APIMessage.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/api/APIMessage.java @@ -74,7 +74,10 @@ public class APIMessage implements Serializable { EXAM_IMPORT_ERROR_AUTO_CONFIG("1610", HttpStatus.PARTIAL_CONTENT, "Failed to automatically create and link exam configuration from the exam template to the exam"), EXAM_IMPORT_ERROR_AUTO_CONFIG_LINKING("1611", HttpStatus.PARTIAL_CONTENT, - "Failed to automatically link auto-generated exam configuration to the exam"); + "Failed to automatically link auto-generated exam configuration to the exam"), + + CLIENT_CONNECTION_INTEGRITY_VIOLATION("1700", HttpStatus.NOT_ACCEPTABLE, + "SEB client connection is not in valid state to apply requested operation"); public final String messageCode; public final HttpStatus httpStatus; diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientConnection.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientConnection.java index 9fb3c3c4..4c93e705 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientConnection.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientConnection.java @@ -440,7 +440,7 @@ public final class ClientConnection implements GrantEntity { builder.append(this.clientAddress); builder.append(", remoteProctoringRoomId="); builder.append(this.remoteProctoringRoomId); - builder.append(", virtualClientId="); + builder.append(", sebClientUserId="); builder.append(this.sebClientUserId); builder.append(", creationTime="); builder.append(this.creationTime); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ClientConnectionDAOImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ClientConnectionDAOImpl.java index 545b07a4..9ae6f7ea 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ClientConnectionDAOImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/ClientConnectionDAOImpl.java @@ -364,7 +364,7 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO { data.examId, ConnectionStatus.CONNECTION_REQUESTED.name(), data.connectionToken, - null, + data.userSessionId, data.clientAddress, data.sebClientUserId, BooleanUtils.toInteger(data.vdi, 1, 0, 0), diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/SEBClientConnectionService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/SEBClientConnectionService.java index ba0a8161..c28fe531 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/SEBClientConnectionService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/SEBClientConnectionService.java @@ -8,6 +8,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session; +import javax.servlet.http.HttpServletResponse; import java.security.Principal; import java.util.Collection; @@ -158,4 +159,18 @@ public interface SEBClientConnectionService { * happened */ Result> disableConnections(final String[] connectionTokens, final Long institutionId); + /** Streams the requested exam configuration to given HttpServletResponse output stream + * + * @param institutionId the institution identifier + * @param examId the exam identifier + * @param connectionToken the connection identifier token + * @param ipAddress the IP Address of the SEB client request + * @param response HttpServletResponse instance to stream the exam configuration to */ + void streamExamConfig( + Long institutionId, + Long examId, + String connectionToken, + String ipAddress, + HttpServletResponse response); + } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/SEBClientInstructionService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/SEBClientInstructionService.java index 878b37da..9b25ea73 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/SEBClientInstructionService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/SEBClientInstructionService.java @@ -28,12 +28,12 @@ import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.webservice.WebserviceInfo; /** Service for SEB instruction handling. - * + *

* SEB instructions are sent as response of a SEB Ping on a active SEB Connection * If there is an instruction in the queue for a specified SEB Client. */ public interface SEBClientInstructionService { - static final Logger log = LoggerFactory.getLogger(SEBClientInstructionService.class); + Logger log = LoggerFactory.getLogger(SEBClientInstructionService.class); /** Get the underling WebserviceInfo * @@ -46,10 +46,10 @@ public interface SEBClientInstructionService { void init(); /** Used to register a SEB client instruction for one or more active client connections - * within an other background thread. This is none-blocking. + * within another background thread. This is none-blocking. * * @param clientInstruction the ClientInstruction instance to register - * @return A Result refer to a void marker or to an error if happened */ + **/ @Async(AsyncServiceSpringConfig.EXECUTOR_BEAN_NAME) default void registerInstructionAsync(final ClientInstruction clientInstruction) { registerInstruction(clientInstruction, false) @@ -69,7 +69,7 @@ public interface SEBClientInstructionService { /** Used to register a SEB client instruction for one or more active client connections * * @param clientInstruction the ClientInstruction instance to register - * @param needsConfirm indicates whether the SEB instruction needs a confirmation or not + * @param needsConfirmation indicates whether the SEB instruction needs a confirmation or not * @return A Result refer to a void marker or to an error if happened */ default Result registerInstruction( final ClientInstruction clientInstruction, @@ -91,6 +91,7 @@ public interface SEBClientInstructionService { * @param type The InstructionType * @param attributes The instruction's attributes * @param connectionToken a connectionToken to register the instruction for. + * @param checkActive indicates if the involved client connection shall be checked for active status or not * @param needsConfirm indicates whether the SEB instruction needs a confirmation or not * @return A Result refer to a void marker or to an error if happened */ Result registerInstruction( @@ -98,6 +99,7 @@ public interface SEBClientInstructionService { InstructionType type, Map attributes, String connectionToken, + boolean checkActive, boolean needsConfirm); /** Used to register a SEB client instruction for one or more active client connections @@ -117,9 +119,9 @@ public interface SEBClientInstructionService { /** Get a SEB instruction for the specified SEB Client connection or null of there * is currently no SEB instruction in the queue. - * + *

* NOTE: If this call returns a SEB instruction instance, this instance is considered - * as processed for the specified SEB Client afterwards and will be removed from the queue + * as processed for the specified SEB Client afterward and will be removed from the queue * * @param connectionToken the SEB Client connection token * @return SEB instruction to sent to the SEB Client or null */ @@ -131,7 +133,7 @@ public interface SEBClientInstructionService { * @param instructionConfirm the instruction confirm identifier */ void confirmInstructionDone(String connectionToken, String instructionConfirm); - /** Used to cleanup out-dated instructions on the persistent storage */ + /** Used to clean up out-dated instructions on the persistent storage */ void cleanupInstructions(); } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/SEBClientSessionService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/SEBClientSessionService.java index 30adf21b..1db3a674 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/SEBClientSessionService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/SEBClientSessionService.java @@ -43,7 +43,7 @@ public interface SEBClientSessionService extends ExamUpdateTask, SessionUpdateTa /** Used to update the app signature key grants of all active SEB connections that miss a grant */ void updateASKGrants(); - /** Used to cleanup old instructions from the persistent storage */ + /** Used to clean up old instructions from the persistent storage */ void cleanupInstructions(); /** Notify a ping for a certain client connection. @@ -66,7 +66,7 @@ public interface SEBClientSessionService extends ExamUpdateTask, SessionUpdateTa * @param instructionConfirm the instruction confirm identifier */ void confirmInstructionDone(String connectionToken, String instructionConfirm); - /** Use this to get the get the specific indicator values for a given client connection. + /** Use this to get the specific indicator values for a given client connection. * * @param clientConnection The client connection values * @return Result refer to ClientConnectionData instance containing the given clientConnection plus the indicator diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java index 591622e8..edd0f8a7 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamSessionServiceImpl.java @@ -227,27 +227,30 @@ public class ExamSessionServiceImpl implements ExamSessionService { updateExamCache(examId); } - final Exam exam = this.examSessionCacheService.getRunningExam(examId); + return Result.tryCatch(() -> { - if (this.examSessionCacheService.isRunning(exam)) { - if (log.isTraceEnabled()) { - log.trace("Exam {} is running and cached", examId); + final Exam exam = this.examSessionCacheService.getRunningExam(examId); + + if (this.examSessionCacheService.isRunning(exam)) { + if (log.isTraceEnabled()) { + log.trace("Exam {} is running and cached", examId); + } + + return exam; + } else { + if (exam != null) { + log.info("Exam {} is not running anymore. Flush caches", exam); + flushCache(exam); + } + + if (log.isDebugEnabled()) { + log.info("Exam {} is not currently running", examId); + } + + throw new NoSuchElementException( + "No currently running exam found for id: " + examId); } - - return Result.of(exam); - } else { - if (exam != null) { - log.info("Exam {} is not running anymore. Flush caches", exam); - flushCache(exam); - } - - if (log.isDebugEnabled()) { - log.info("Exam {} is not currently running", examId); - } - - return Result.ofError(new NoSuchElementException( - "No currently running exam found for id: " + examId)); - } + }); } @Override diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientConnectionServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientConnectionServiceImpl.java index e51be127..9eb75d05 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientConnectionServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientConnectionServiceImpl.java @@ -8,22 +8,28 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletResponse; import java.security.Principal; -import java.util.Collection; -import java.util.Objects; -import java.util.UUID; +import java.util.*; import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; +import ch.ethz.seb.sebserver.gbl.api.APIMessage; +import ch.ethz.seb.sebserver.gbl.api.JSONMapper; import ch.ethz.seb.sebserver.gbl.model.Entity; import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent; +import ch.ethz.seb.sebserver.gbl.model.session.ClientInstruction; import ch.ethz.seb.sebserver.gbl.util.Utils; +import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientInstructionService; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.tomcat.jni.Address; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Lazy; +import org.springframework.http.HttpStatus; import org.springframework.security.access.AccessDeniedException; import org.springframework.stereotype.Service; @@ -38,7 +44,6 @@ import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData; import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile; import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.webservice.WebserviceInfo; -import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.ClientConnectionRecord; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientConnectionDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.SEBClientConfigDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamAdminService; @@ -72,6 +77,8 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic private final DistributedIndicatorValueService distributedPingCache; private final SecurityKeyService securityKeyService; private final SEBClientEventBatchService sebClientEventBatchService; + private final SEBClientInstructionService sebClientInstructionService; + private final JSONMapper jsonMapper; private final boolean isDistributedSetup; protected SEBClientConnectionServiceImpl( @@ -82,7 +89,9 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic final ClientIndicatorFactory clientIndicatorFactory, final SecurityKeyService securityKeyService, final WebserviceInfo webserviceInfo, - final SEBClientEventBatchService sebClientEventBatchService) { + final SEBClientEventBatchService sebClientEventBatchService, + final SEBClientInstructionService sebClientInstructionService, + final JSONMapper jsonMapper) { this.examSessionService = examSessionService; this.examSessionCacheService = examSessionService.getExamSessionCacheService(); @@ -94,6 +103,8 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic this.securityKeyService = securityKeyService; this.isDistributedSetup = webserviceInfo.isDistributed(); this.sebClientEventBatchService = sebClientEventBatchService; + this.sebClientInstructionService = sebClientInstructionService; + this.jsonMapper = jsonMapper; } @Override @@ -143,29 +154,38 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic clientId); } + final String connectionToken = createToken(); + if (examId != null) { checkExamIntegrity( examId, + null, institutionId, - (principal != null) ? principal.getName() : "--", + connectionToken, clientAddress); } + final String updateUserSessionId = updateUserSessionId( + examId, + null, + clientId, + sebMachineName, + null); + // Create ClientConnection in status CONNECTION_REQUESTED for further processing - final String connectionToken = createToken(); final ClientConnection clientConnection = this.clientConnectionDAO.createNew(new ClientConnection( null, - institutionId, // Set the institution identifier that was checked against integrity before - examId, // Set the exam identifier if available otherwise it is null - ConnectionStatus.CONNECTION_REQUESTED, // Initial state - connectionToken, // The generated connection token that identifies this connection - null, - (clientAddress != null) ? clientAddress : Constants.EMPTY_NOTE, // The IP address of the connecting client, verified on SEB Server side + institutionId, + examId, + ConnectionStatus.CONNECTION_REQUESTED, + connectionToken, + updateUserSessionId, + (clientAddress != null) ? clientAddress : Constants.EMPTY_NOTE, sebOsName, sebMachineName, sebVersion, - clientId, // The client identifier sent by the SEB client if available - clientConfig.vdiType != VDIType.NO, // The VDI flag to indicate if this is a VDI prime connection + clientId, + clientConfig.vdiType != VDIType.NO, null, null, null, @@ -218,83 +238,58 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic return Result.tryCatch(() -> { - ClientConnection clientConnection = getClientConnection(connectionToken); + final ClientConnection clientConnection = getClientConnection(connectionToken); + final Long _examId = clientConnection.examId != null ? clientConnection.examId : examId; + checkInstitutionalIntegrity(institutionId, clientConnection); - checkExamIntegrity(examId, clientConnection); - - // connection integrity check - if (!clientConnection.status.clientActiveStatus) { - log.error( - "ClientConnection integrity violation: client connection is not in expected state: {}", - clientConnection); - throw new IllegalArgumentException( - "ClientConnection integrity violation: client connection is not in expected state"); - } - if (StringUtils.isNoneBlank(clientAddress) && - StringUtils.isNotBlank(clientConnection.clientAddress) && - !clientAddress.equals(clientConnection.clientAddress)) { - // log SEB client IP address change - log.error( - "ClientConnection integrity violation: client address mismatch: {}, {}", - clientAddress, - clientConnection.clientAddress); - sebLogClientAddressMismatch(clientAddress, clientConnection); - } - - if (examId != null) { - checkExamIntegrity( - examId, - institutionId, - StringUtils.isNoneBlank(userSessionId) ? userSessionId : clientConnection.userSessionId, - clientConnection.clientAddress); - } + connectionStatusIntegrityCheck(clientConnection, clientAddress); + checkExamIntegrity(examId, clientConnection.examId, institutionId, clientConnection.connectionToken, clientConnection.info); if (log.isDebugEnabled()) { log.debug( - "SEB client connection, update ClientConnection for " - + "connectionToken {} " - + "institutionId: {}" - + "exam: {} " - + "client address: {} " - + "userSessionId: {}" - + "clientId: {}", - connectionToken, - institutionId, - examId, - clientAddress, - userSessionId, - clientId); + "SEB client connection, update ClientConnection for connectionToken {} institutionId: {} exam: {} client address: {} userSessionId: {} clientId: {}", + connectionToken, institutionId, examId, clientAddress, userSessionId, clientId); } - // userSessionId integrity check - clientConnection = updateUserSessionId(userSessionId, clientConnection, examId); + final String updateUserSessionId = updateUserSessionId( + _examId, + userSessionId, + clientId, + sebMachineName, + clientConnection); + + final ConnectionStatus currentStatus = clientConnection.getStatus(); + final String signatureHash = StringUtils.isNotBlank(appSignatureKey) + ? getSignatureHash(appSignatureKey, connectionToken, _examId) + : null; + final ClientConnection updateConnection = new ClientConnection( + clientConnection.id, + null, + examId, + (userSessionId != null && currentStatus == ConnectionStatus.CONNECTION_REQUESTED) + ? ConnectionStatus.AUTHENTICATED + : null, + null, + updateUserSessionId, + StringUtils.isNotBlank(clientAddress) ? clientAddress : null, + StringUtils.isNotBlank(sebOsName) && clientConnection.sebOSName == null ? sebOsName : null, + StringUtils.isNotBlank(sebMachineName) && clientConnection.sebMachineName == null ? sebMachineName : null, + StringUtils.isNotBlank(sebVersion) && clientConnection.sebVersion == null ? sebVersion : null, + StringUtils.isNotBlank(clientId) && clientConnection.sebClientUserId == null ? clientId : null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + signatureHash, + null); + final ClientConnection updatedClientConnection = this.clientConnectionDAO - .save(new ClientConnection( - clientConnection.id, - null, - examId, - (userSessionId != null) ? ConnectionStatus.AUTHENTICATED : null, - null, - clientConnection.userSessionId, - StringUtils.isNoneBlank(clientAddress) ? clientAddress : null, - StringUtils.isNoneBlank(sebOsName) ? sebOsName : null, - StringUtils.isNoneBlank(sebMachineName) ? sebMachineName : null, - StringUtils.isNoneBlank(sebVersion) ? sebVersion : null, - StringUtils.isNoneBlank(clientId) ? clientId : null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - getSignatureHash( - appSignatureKey, - connectionToken, - clientConnection.examId != null ? clientConnection.examId : examId), - null)) + .save(updateConnection) .getOrThrow(); // initialize distributed indicator value caches if possible and needed @@ -332,62 +327,31 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic return Result.tryCatch(() -> { - ClientConnection clientConnection = getClientConnection(connectionToken); + final ClientConnection clientConnection = getClientConnection(connectionToken); + final Long _examId = clientConnection.examId != null ? clientConnection.examId : examId; - // overall connection status integrity check - if (!clientConnection.status.clientActiveStatus) { - log.warn("ClientConnection integrity violation: client connection is not in expected state: {}", - clientConnection); - throw new IllegalArgumentException( - "ClientConnection integrity violation: client connection is not in expected state"); - } - - // check IP address change - if (StringUtils.isNoneBlank(clientAddress) && - StringUtils.isNotBlank(clientConnection.clientAddress) && - !clientAddress.equals(clientConnection.clientAddress)) { - // log client IP address change - log.warn( - "ClientConnection integrity violation: client address mismatch: {}, {}", - clientAddress, - clientConnection.clientAddress); - sebLogClientAddressMismatch(clientAddress, clientConnection); - } + connectionStatusIntegrityCheck(clientConnection, clientAddress); + checkInstitutionalIntegrity(institutionId, clientConnection); + checkExamIntegrity( + examId, + clientConnection.examId, + institutionId, + clientConnection.connectionToken, + clientConnection.info); if (log.isDebugEnabled()) { log.debug( - "SEB client connection, establish ClientConnection for " - + "connectionToken {} " - + "institutionId: {}" - + "exam: {} " - + "client address: {} " - + "userSessionId: {}" - + "clientId: {}", - connectionToken, - institutionId, - examId, - clientAddress, - userSessionId, - clientId); + "SEB client connection, establish ClientConnection for connectionToken {} institutionId: {} exam: {} client address: {} userSessionId: {} clientId: {}", + connectionToken, institutionId, examId, clientAddress, userSessionId, clientId); } - checkInstitutionalIntegrity(institutionId, clientConnection); - checkExamIntegrity(examId, clientConnection); - clientConnection = updateUserSessionId(userSessionId, clientConnection, examId); - - // connection integrity check - if (clientConnection.status != ConnectionStatus.ACTIVE) { - if (clientConnection.status == ConnectionStatus.CONNECTION_REQUESTED) { - log.warn("ClientConnection integrity warning: client connection is not authenticated: {}", - clientConnection); - } else if (clientConnection.status != ConnectionStatus.AUTHENTICATED) { - log.error("ClientConnection integrity violation: client connection is not in expected state: {}", - clientConnection); - throw new IllegalArgumentException( - "ClientConnection integrity violation: client connection is not in expected state"); - } - } + final String updateUserSessionId = updateUserSessionId( + _examId, + userSessionId, + clientId, + sebMachineName, + clientConnection); final Boolean proctoringEnabled = this.examAdminService .isProctoringEnabled(clientConnection.examId) .getOr(false); @@ -407,12 +371,12 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic currentExamId, ConnectionStatus.ACTIVE, null, - clientConnection.userSessionId, - StringUtils.isNoneBlank(clientAddress) ? clientAddress : null, - StringUtils.isNoneBlank(sebOsName) ? sebOsName : null, - StringUtils.isNoneBlank(sebMachineName) ? sebMachineName : null, - StringUtils.isNoneBlank(sebVersion) ? sebVersion : null, - StringUtils.isNoneBlank(clientId) ? clientId : null, + updateUserSessionId, + StringUtils.isNotBlank(clientAddress) ? clientAddress : null, + StringUtils.isNotBlank(sebOsName) ? sebOsName : null, + StringUtils.isNotBlank(sebMachineName) ? sebMachineName : null, + StringUtils.isNotBlank(sebVersion) ? sebVersion : null, + StringUtils.isNotBlank(clientId) ? clientId : null, null, null, null, @@ -422,10 +386,7 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic null, proctoringEnabled, null, - getSignatureHash( - appSignatureKey, - connectionToken, - clientConnection.examId != null ? clientConnection.examId : examId), + getSignatureHash(appSignatureKey, connectionToken, _examId), null); // ClientConnection integrity check @@ -445,22 +406,10 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic throw new IllegalStateException("ClientConnection integrity violation"); } -// Removed this since VDI integration was postponed and has not been reactivated since then. -// final ClientConnection connectionToSave = handleVDISetup( -// currentVdiConnectionId, -// establishedClientConnection); -// final ClientConnection updatedClientConnection = this.clientConnectionDAO .save(establishedClientConnection) .getOrThrow(); - // check exam integrity for established connection - checkExamIntegrity( - establishedClientConnection.examId, - institutionId, - establishedClientConnection.userSessionId, - establishedClientConnection.clientAddress); - // initialize distributed indicator value caches if possible and needed if (examId != null && this.isDistributedSetup) { this.clientIndicatorFactory.initializeDistributedCaches(clientConnection); @@ -482,66 +431,6 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic }); } - - - private ClientConnection handleVDISetup( - final String currentVdiConnectionId, - final ClientConnection establishedClientConnection) { - - if (currentVdiConnectionId == null) { - return establishedClientConnection; - } - - final Result vdiPairConnectionResult = - this.clientConnectionDAO.getVDIPairCompanion( - establishedClientConnection.examId, - establishedClientConnection.sebClientUserId); - - if (!vdiPairConnectionResult.hasValue()) { - return establishedClientConnection; - } - - final ClientConnectionRecord vdiPairCompanion = vdiPairConnectionResult.get(); - final Long vdiExamId = (establishedClientConnection.examId != null) - ? establishedClientConnection.examId - : vdiPairCompanion.getExamId(); - final ClientConnection updatedConnection = new ClientConnection( - establishedClientConnection.id, - null, - vdiExamId, - establishedClientConnection.status, - null, - establishedClientConnection.userSessionId, - null, - null, - null, - null, - establishedClientConnection.sebClientUserId, - null, - vdiPairCompanion.getConnectionToken(), - null, - null, - null, - establishedClientConnection.screenProctoringGroupUpdate, - null, - establishedClientConnection.remoteProctoringRoomUpdate, - null, - null, - null); - - // Update other connection with token and exam id - final ClientConnection connection = this.clientConnectionDAO - .save(new ClientConnection( - vdiPairCompanion.getId(), null, - vdiExamId, null, null, null, null, null, null, - establishedClientConnection.connectionToken, - null, null, null, null, null, null, null, null, null, null, null, null)) - .getOrThrow(); - - reloadConnectionCache(vdiPairCompanion.getConnectionToken(), connection.examId); - return updatedConnection; - } - @Override public Result closeConnection( final String connectionToken, @@ -605,10 +494,8 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic .getConnectionData(connectionToken) .getOrThrow(); - // An active connection can only be disabled if we have a missing ping - if (connectionData.clientConnection.status == ConnectionStatus.ACTIVE && - !BooleanUtils.isTrue(connectionData.getMissingPing())) { - + // A connection can only be disabled if we have a missing ping + if (!BooleanUtils.isTrue(connectionData.getMissingPing())) { return connectionData.clientConnection; } @@ -662,46 +549,162 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic @Override public Result> disableConnections(final String[] connectionTokens, final Long institutionId) { - return Result.tryCatch(() -> { - - return Stream.of(connectionTokens) - .map(token -> disableConnection(token, institutionId) - .onError(error -> log.error("Failed to disable SEB client connection: {}", token)) - .getOr(null)) - .filter(Objects::nonNull) - .map(Entity::getEntityKey) - .collect(Collectors.toList()); - }); + return Result.tryCatch(() -> Stream.of(connectionTokens) + .map(token -> disableConnection(token, institutionId) + .onError(error -> log.error("Failed to disable SEB client connection: {}", token)) + .getOr(null)) + .filter(Objects::nonNull) + .map(Entity::getEntityKey) + .collect(Collectors.toList())); } - // SEBSERV-475 IP address change during handshake is possible but is logged within SEB logs - private void sebLogClientAddressMismatch( - final String clientAddress, - final ClientConnection clientConnection) { + public void streamExamConfig( + final Long institutionId, + final Long examId, + final String connectionToken, + final String ipAddress, + final HttpServletResponse response) { try { - final long now = Utils.getMillisecondsNow(); - this.sebClientEventBatchService.accept(new SEBClientEventBatchService.EventData( - clientConnection.connectionToken, - now, - new ClientEvent( - null, - clientConnection.id, - ClientEvent.EventType.WARN_LOG, - now, now, null, - "SEB Client IP address changed: " + - clientConnection.clientAddress + - " -> " + - clientAddress - ))); + + // if an examId is provided with the request, update the connection first + if (examId != null) { + final ClientConnection connection = this.updateClientConnection( + connectionToken, + institutionId, + examId, + null, + null, + null, + null, + null, + null, + null) + .getOrThrow(); + + if (log.isDebugEnabled()) { + log.debug("Updated connection: {}", connection); + } + } else { + + // check connection status + final ClientConnectionDataInternal cc = this.examSessionCacheService + .getClientConnection(connectionToken); + if (cc != null) { + connectionStatusIntegrityCheck(cc.clientConnection, ipAddress); + } + } + + final ServletOutputStream outputStream = response.getOutputStream(); + + try { + + this.examSessionService + .streamDefaultExamConfig( + institutionId, + connectionToken, + outputStream); + + response.setStatus(HttpStatus.OK.value()); + + } catch (final Exception e) { + final APIMessage errorMessage = APIMessage.ErrorMessage.GENERIC.of(e.getMessage()); + outputStream.write(Utils.toByteArray(this.jsonMapper.writeValueAsString(errorMessage))); + response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); + + } finally { + outputStream.flush(); + outputStream.close(); + } + + } catch (final IllegalArgumentException e) { + final Collection errorMessages = Arrays.asList( + APIMessage.ErrorMessage.CLIENT_CONNECTION_INTEGRITY_VIOLATION.of(e.getMessage())); + response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); + writeSEBClientErrors(response, errorMessages); } catch (final Exception e) { - log.error("Failed to log SEB client IP address change: ", e); + log.error( + "Unexpected error while trying to stream SEB Exam Configuration to client with connection: {}", + connectionToken, e); + + final Collection errorMessages = Arrays.asList( + APIMessage.ErrorMessage.GENERIC.of(e.getMessage())); + response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); + writeSEBClientErrors(response, errorMessages); } } - private void checkExamRunning(final Long examId, final String user, final String address) { + private void writeSEBClientErrors(HttpServletResponse response, Collection errorMessages) { + try { + response.getOutputStream().write(Utils.toByteArray(this.jsonMapper.writeValueAsString(errorMessages))); + } catch (final Exception e1) { + log.error("Failed to write error to response: ", e1); + } + } + + private void connectionStatusIntegrityCheck( + final ClientConnection clientConnection, + final String clientAddress) { + + // overall connection status integrity check + if (!clientConnection.status.clientActiveStatus) { + log.warn( + "ClientConnection integrity violation: client connection is not in expected state: {}", + clientConnection); + + // SEBSERV-440 send quit instruction to SEB + sebClientInstructionService.registerInstruction( + clientConnection.examId, + ClientInstruction.InstructionType.SEB_QUIT, + Collections.emptyMap(), + clientConnection.connectionToken, + false, + false + ); + + throw new IllegalArgumentException( + "ClientConnection integrity violation: client connection is not in expected state"); + } + + // SEBSERV-475 IP address change during handshake is possible but is logged within SEB logs + if (StringUtils.isNoneBlank(clientAddress) && + StringUtils.isNotBlank(clientConnection.clientAddress) && + !clientAddress.equals(clientConnection.clientAddress)) { + + // log SEB client IP address change + log.warn( + "ClientConnection integrity violation: client address mismatch: {}, {}", + clientAddress, + clientConnection.clientAddress); + + try { + final long now = Utils.getMillisecondsNow(); + this.sebClientEventBatchService.accept(new SEBClientEventBatchService.EventData( + clientConnection.connectionToken, + now, + new ClientEvent( + null, + clientConnection.id, + ClientEvent.EventType.WARN_LOG, + now, now, null, + "SEB Client IP address changed: " + + clientConnection.clientAddress + + " -> " + + clientAddress + ))); + } catch (final Exception e) { + log.error("Failed to log SEB client IP address change: ", e); + } + } + } + + + + private void checkExamRunning(final Long examId, final String ccToken, final String ccInfo) { if (examId != null && !this.examSessionService.isExamRunning(examId)) { - examNotRunningException(examId, user, address); + log.warn("The exam {} is not running. Called by: {} info {}", examId, ccToken, ccInfo); + throw new APIConstraintViolationException( + "The exam " + examId + " is not running"); } } @@ -727,99 +730,123 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic return UUID.randomUUID().toString(); } - private void examNotRunningException(final Long examId, final String user, final String address) { - log.warn("The exam {} is not running. Called by: {} | on: {}", examId, user, address); - throw new APIConstraintViolationException( - "The exam " + examId + " is not running"); - } - private void checkExamIntegrity(final Long examId, final ClientConnection clientConnection) { - if (examId != null && - clientConnection.examId != null && - !examId.equals(clientConnection.examId)) { - - log.error("Exam integrity violation: another examId is already set for the connection: {}", - clientConnection); - throw new IllegalArgumentException( - "Exam integrity violation: another examId is already set for the connection"); - } - checkExamRunning(examId, clientConnection.userSessionId, clientConnection.clientAddress); - } - - private ClientConnection updateUserSessionId( + private String updateUserSessionId( + final Long examId, final String userSessionId, - ClientConnection clientConnection, - final Long examId) { + final String clientId, + final String sebMachineName, + final ClientConnection clientConnection) { - if (StringUtils.isNoneBlank(userSessionId)) { - if (StringUtils.isNoneBlank(clientConnection.userSessionId)) { - if (clientConnection.userSessionId.contains(userSessionId)) { - if (log.isDebugEnabled()) { - log.debug("SEB sent LMS userSessionId but clientConnection has already a userSessionId"); + try { + + if (clientConnection == null) { + if (StringUtils.isNotBlank(clientId)) { + return clientId; + } + if (StringUtils.isNotBlank(sebMachineName)) { + return sebMachineName; + } + return null; + } + + // we don't have real userSessionId yet, so we use a placeholder of available + if (StringUtils.isBlank(userSessionId)) { + if (clientConnection.userSessionId == null) { + if (clientConnection.sebClientUserId != null) { + return clientConnection.sebClientUserId; + } + if (StringUtils.isNotBlank(clientId)) { + return clientId; + } + if (clientConnection.sebMachineName != null) { + return clientConnection.sebMachineName; + } + if (StringUtils.isNotBlank(sebMachineName)) { + return sebMachineName; + } + if (clientConnection.clientAddress != null) { + return clientConnection.clientAddress; + } + return null; + } else if (clientConnection.userSessionId.equals(clientConnection.clientAddress)) { + if (clientConnection.sebClientUserId != null) { + return clientConnection.sebClientUserId; + } + if (StringUtils.isNotBlank(clientId)) { + return clientId; + } + if (clientConnection.sebMachineName != null) { + return clientConnection.sebMachineName; + } + if (StringUtils.isNotBlank(sebMachineName)) { + return sebMachineName; } - return clientConnection; - } else { - log.warn( - "Possible client integrity violation: clientConnection has already a userSessionId: {} : {}", - userSessionId, clientConnection.userSessionId); } + + return null; } - // try to get user account display name (SEBSERV-228) - String accountId = userSessionId; - try { - final String newAccountId = this.examSessionService - .getRunningExam((clientConnection.examId != null) - ? clientConnection.examId - : examId) - .flatMap(exam -> this.examSessionService.getLmsAPIService().getLmsAPITemplate(exam.lmsSetupId)) - .map(template -> template.getExamineeName(userSessionId)) - .getOr(userSessionId); - - if (StringUtils.isNotBlank(clientConnection.userSessionId)) { - accountId = newAccountId + - Constants.SPACE + - Constants.EMBEDDED_LIST_SEPARATOR + - Constants.SPACE + - clientConnection.userSessionId; - } else { - accountId = newAccountId; - } - } catch (final Exception e) { - log.warn("Unexpected error while trying to get user account display name: {}", e.getMessage()); + if (examId == null) { + return null; } - // create new ClientConnection for update - final ClientConnection authenticatedClientConnection = new ClientConnection( - clientConnection.id, - null, null, - ConnectionStatus.AUTHENTICATED, null, - accountId, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null); + // we got a userSessionId, so we want to resolve it via LMS binding + final String accountId = this.examSessionService + .getRunningExam(examId) + .flatMap(exam -> this.examSessionService + .getLmsAPIService() + .getLmsAPITemplate(exam.lmsSetupId)) + .map(template -> template.getExamineeName(userSessionId)) + .getOr(userSessionId); - clientConnection = this.clientConnectionDAO - .save(authenticatedClientConnection) - .getOrThrow(); + // if userSessionId is not set yet or a placeholder is set, just use the new account name + if (clientConnection.userSessionId == null || + clientConnection.userSessionId.equals(clientConnection.sebClientUserId) || + clientConnection.userSessionId.equals(clientConnection.sebMachineName) || + clientConnection.userSessionId.equals(clientConnection.clientAddress)) { + return accountId; + } + + // otherwise apply new name + return accountId + + Constants.SPACE + + Constants.EMBEDDED_LIST_SEPARATOR + + Constants.SPACE + + clientConnection.userSessionId; + } catch (final Exception e) { + log.error("Unexpected error while try to update userSessionId for connection: {}", clientConnection, e); + return null; } - return clientConnection; } private void checkExamIntegrity( final Long examId, + final Long currentExamId, final Long institutionId, - final String user, - final String address) { + final String ccToken, + final String ccInfo) { + + if (examId == null) { + return; + } 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 updateExamCache = this.examSessionService.updateExamCache(examId); if (updateExamCache.hasError()) { log.warn("Failed to update Exam-Cache for Exam: {}", examId); } } + if (currentExamId != null && !examId.equals(currentExamId)) { + log.error("Exam integrity violation: another examId is already set for the connection: {}", ccToken); + throw new IllegalArgumentException( + "Exam integrity violation: another examId is already set for the connection"); + } + // check Exam is running and not locked - checkExamRunning(examId, user, address); + checkExamRunning(examId, ccToken, ccInfo); if (this.examSessionService.isExamLocked(examId)) { throw new APIConstraintViolationException( "Exam is currently on update and locked for new SEB Client connections"); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientInstructionServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientInstructionServiceImpl.java index 42eaff19..46545bf8 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientInstructionServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientInstructionServiceImpl.java @@ -103,34 +103,36 @@ public class SEBClientInstructionServiceImpl implements SEBClientInstructionServ final InstructionType type, final Map attributes, final String connectionToken, + final boolean checkActive, final boolean needsConfirm) { return Result.tryCatch(() -> { - final boolean isActive = this.clientConnectionDAO + if (checkActive && !this.clientConnectionDAO .isInInstructionStatus(examId, connectionToken) - .getOr(false); + .getOr(false)) { - if (isActive) { - try { - - final String attributesString = (attributes != null && !attributes.isEmpty()) - ? this.jsonMapper.writeValueAsString(attributes) - : null; - - this.clientInstructionDAO - .insert(examId, type, attributesString, connectionToken, needsConfirm) - .map(this::putToCache) - .onError(error -> log.error("Failed to register instruction: {}", error.getMessage())) - .getOrThrow(); - - } catch (final Exception e) { - throw new RuntimeException("Unexpected: ", e); - } - } else { log.warn( - "The SEB client connection : {} is not in a ready state to process instructions. Instruction registration has been skipped", + "The SEB client connection : {} is not in a ready state to process instructions. " + + "Instruction registration has been skipped", connectionToken); + return; + } + + try { + + final String attributesString = (attributes != null && !attributes.isEmpty()) + ? this.jsonMapper.writeValueAsString(attributes) + : null; + + this.clientInstructionDAO + .insert(examId, type, attributesString, connectionToken, needsConfirm) + .map(this::putToCache) + .onError(error -> log.error("Failed to register instruction: {}", error.getMessage())) + .getOrThrow(); + + } catch (final Exception e) { + throw new RuntimeException("Unexpected: ", e); } }); } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientNotificationServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientNotificationServiceImpl.java index 6c2decb6..777ddda3 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientNotificationServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientNotificationServiceImpl.java @@ -154,7 +154,9 @@ public class SEBClientNotificationServiceImpl implements SEBClientNotificationSe InstructionType.NOTIFICATION_CONFIRM, connectionToken, attributes); - this.sebClientInstructionService.registerInstruction(clientInstruction); + this.sebClientInstructionService + .registerInstruction(clientInstruction) + .onError(error -> log.error("Failed to confirm instruction SEB client side: ", error)); return notification; } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientPingBatchService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientPingBatchService.java index 5f49dcfa..d2625075 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientPingBatchService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientPingBatchService.java @@ -8,6 +8,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl; +import java.util.Collections; import java.util.HashSet; import java.util.Map; import java.util.Set; @@ -15,6 +16,8 @@ import java.util.concurrent.ScheduledFuture; import javax.annotation.PreDestroy; +import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection; +import ch.ethz.seb.sebserver.gbl.model.session.ClientInstruction; import org.apache.commons.lang3.StringUtils; import org.ehcache.impl.internal.concurrent.ConcurrentHashMap; import org.slf4j.Logger; @@ -42,7 +45,6 @@ public class SEBClientPingBatchService implements SEBClientPingService { private final ExamSessionCacheService examSessionCacheService; private final SEBClientInstructionService sebClientInstructionService; - private final Set pingKeys = new HashSet<>(); private final Map pings = new ConcurrentHashMap<>(); private final Map instructions = new ConcurrentHashMap<>(); @@ -69,7 +71,6 @@ public class SEBClientPingBatchService implements SEBClientPingService { try { this.pingKeys.clear(); this.pingKeys.addAll(this.pings.keySet()); - //final Set connections = new HashSet<>(this.pings.keySet()); this.pingKeys.stream().forEach(cid -> processPing( cid, this.pings.remove(cid), @@ -119,11 +120,23 @@ public class SEBClientPingBatchService implements SEBClientPingService { return; } - final ClientConnectionDataInternal activeClientConnection = this.examSessionCacheService + final ClientConnectionDataInternal connectionData = this.examSessionCacheService .getClientConnection(connectionToken); - if (activeClientConnection != null) { - activeClientConnection.notifyPing(timestamp); + if (connectionData != null) { + if (connectionData.clientConnection.status == ClientConnection.ConnectionStatus.DISABLED) { + // SEBSERV-440 send quit instruction to SEB + sebClientInstructionService.registerInstruction( + connectionData.clientConnection.examId, + ClientInstruction.InstructionType.SEB_QUIT, + Collections.emptyMap(), + connectionData.clientConnection.connectionToken, + false, + false + ); + } + + connectionData.notifyPing(timestamp); } else { log.error("Failed to get ClientConnectionDataInternal for: {}", connectionToken); } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientPingBlockingService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientPingBlockingService.java index 50045221..e1469a1d 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientPingBlockingService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientPingBlockingService.java @@ -8,6 +8,10 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl; +import java.util.Collections; + +import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection; +import ch.ethz.seb.sebserver.gbl.model.session.ClientInstruction; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -50,11 +54,23 @@ public class SEBClientPingBlockingService implements SEBClientPingService { return null; } - final ClientConnectionDataInternal activeClientConnection = this.examSessionCacheService + final ClientConnectionDataInternal connectionData = this.examSessionCacheService .getClientConnection(connectionToken); - if (activeClientConnection != null) { - activeClientConnection.notifyPing(Utils.getMillisecondsNow()); + if (connectionData != null) { + if (connectionData.clientConnection.status == ClientConnection.ConnectionStatus.DISABLED) { + // SEBSERV-440 send quit instruction to SEB + sebClientInstructionService.registerInstruction( + connectionData.clientConnection.examId, + ClientInstruction.InstructionType.SEB_QUIT, + Collections.emptyMap(), + connectionData.clientConnection.connectionToken, + false, + false + ); + } + + connectionData.notifyPing(Utils.getMillisecondsNow()); } else { log.error("Failed to get ClientConnectionDataInternal for: {}", connectionToken); } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/RemoteProctoringRoomServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/RemoteProctoringRoomServiceImpl.java index b7f07ba5..fef45649 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/RemoteProctoringRoomServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/RemoteProctoringRoomServiceImpl.java @@ -653,6 +653,7 @@ public class RemoteProctoringRoomServiceImpl implements RemoteProctoringRoomServ InstructionType.SEB_RECONFIGURE_SETTINGS, attributes, connectionToken, + true, true) .onError(error -> log.error( "Failed to register reconfiguring instruction for connection: {}", @@ -852,6 +853,7 @@ public class RemoteProctoringRoomServiceImpl implements RemoteProctoringRoomServ InstructionType.SEB_PROCTORING, attributes, connectionToken, + true, true) .onError(error -> log.error("Failed to send join instruction: {}", connectionToken, error)) .getOrThrow(); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ScreenProctoringServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ScreenProctoringServiceImpl.java index 4f92a3f9..fd68c1df 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ScreenProctoringServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ScreenProctoringServiceImpl.java @@ -476,6 +476,7 @@ public class ScreenProctoringServiceImpl implements ScreenProctoringService { InstructionType.SEB_PROCTORING, attributes, ccRecord.getConnectionToken(), + true, true) .onError(error -> log.error( "Failed to register screen proctoring join instruction for SEB connection: {}", diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ZoomProctoringService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ZoomProctoringService.java index 64d354ab..3e4c6626 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ZoomProctoringService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/proctoring/ZoomProctoringService.java @@ -524,6 +524,7 @@ public class ZoomProctoringService implements RemoteProctoringService { InstructionType.SEB_PROCTORING, attributes, connectionToken, + true, true) .onError(error -> log.error("Failed to send join instruction: {}", connectionToken, error)); } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/APIExceptionHandler.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/APIExceptionHandler.java index 2ccf8d5d..88407162 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/APIExceptionHandler.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/APIExceptionHandler.java @@ -210,7 +210,7 @@ public class APIExceptionHandler extends ResponseEntityExceptionHandler { } @ExceptionHandler(AccessDeniedException.class) - public ResponseEntity handleUnexpected( + public ResponseEntity handleAccessDenied( final AccessDeniedException ex, final WebRequest request) { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPI_V1_Controller.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPI_V1_Controller.java index db47e307..408530ab 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPI_V1_Controller.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAPI_V1_Controller.java @@ -14,6 +14,7 @@ import java.security.Principal; import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; import java.util.stream.Collectors; @@ -136,7 +137,7 @@ public class ExamAPI_V1_Controller { clientConnection.connectionToken); // Crate list of running exams - List result; + final List result; if (examId == null) { result = this.examSessionService.getRunningExamsForInstitution(institutionId) .getOrThrow() @@ -189,7 +190,7 @@ public class ExamAPI_V1_Controller { @RequestParam(name = API.EXAM_API_USER_SESSION_ID, required = false) final String userSessionId, @RequestParam(name = API.EXAM_API_PARAM_SEB_VERSION, required = false) final String sebVersion, @RequestParam(name = API.EXAM_API_PARAM_SEB_OS_NAME, required = false) final String sebOSName, - @RequestParam(name = API.EXAM_API_PARAM_SEB_MACHINE_NAME, required = false) final String sebMachinName, + @RequestParam(name = API.EXAM_API_PARAM_SEB_MACHINE_NAME, required = false) final String sebMachineName, @RequestParam( name = API.EXAM_API_PARAM_SIGNATURE_KEY, required = false) final String browserSignatureKey, @@ -212,7 +213,7 @@ public class ExamAPI_V1_Controller { remoteAddr, sebVersion, sebOSName, - sebMachinName, + sebMachineName, userSessionId, clientId, browserSignatureKey) @@ -315,8 +316,22 @@ public class ExamAPI_V1_Controller { final HttpServletRequest request, final HttpServletResponse response) { + Long examId; + try { + examId = Long.parseLong(Objects.requireNonNull(formParams.getFirst(API.EXAM_API_PARAM_EXAM_ID))); + } catch (final Exception e) { + examId = null; + } + final Long _examId = examId; + final String remoteAddr = this.getClientAddress(request); + return CompletableFuture.runAsync( - () -> streamExamConfig(connectionToken, formParams, principal, response), + () -> this.sebClientConnectionService.streamExamConfig( + getInstitutionId(principal), + _examId, + connectionToken, + remoteAddr, + response), this.executor); } @@ -328,7 +343,6 @@ public class ExamAPI_V1_Controller { public void ping(final HttpServletRequest request, final HttpServletResponse response) { final String connectionToken = request.getHeader(API.EXAM_API_SEB_CONNECTION_TOKEN); - //final String pingNumString = request.getParameter(API.EXAM_API_PING_NUMBER); final String instructionConfirm = request.getParameter(API.EXAM_API_PING_INSTRUCTION_CONFIRM); final String instruction = this.sebClientSessionService @@ -373,73 +387,7 @@ public class ExamAPI_V1_Controller { .getOr(null)); } - private void streamExamConfig( - final String connectionToken, - final MultiValueMap formParams, - final Principal principal, - final HttpServletResponse response) { - final Long institutionId = getInstitutionId(principal); - - try { - - // if an examId is provided with the request, update the connection first - if (formParams != null && formParams.containsKey(API.EXAM_API_PARAM_EXAM_ID)) { - final String examId = formParams.getFirst(API.EXAM_API_PARAM_EXAM_ID); - final ClientConnection connection = this.sebClientConnectionService.updateClientConnection( - connectionToken, - institutionId, - Long.valueOf(examId), - null, - null, - null, - null, - null, - null, - null) - .getOrThrow(); - - if (log.isDebugEnabled()) { - log.debug("Updated connection: {}", connection); - } - } - - final ServletOutputStream outputStream = response.getOutputStream(); - - try { - - this.examSessionService - .streamDefaultExamConfig( - institutionId, - connectionToken, - outputStream); - - response.setStatus(HttpStatus.OK.value()); - - } catch (final Exception e) { - final APIMessage errorMessage = APIMessage.ErrorMessage.GENERIC.of(e.getMessage()); - outputStream.write(Utils.toByteArray(this.jsonMapper.writeValueAsString(errorMessage))); - response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); - - } finally { - outputStream.flush(); - outputStream.close(); - } - - } catch (final Exception e) { - log.error("Unexpected error while trying to stream SEB Exam Configuration to client with connection: {}", - connectionToken, - e); - - final APIMessage errorMessage = APIMessage.ErrorMessage.GENERIC.of(e.getMessage()); - response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); - try { - response.getOutputStream().write(Utils.toByteArray(this.jsonMapper.writeValueAsString(errorMessage))); - } catch (final Exception e1) { - log.error("Failed to write error to response: ", e1); - } - } - } private String getClientAddress(final HttpServletRequest request) { try { diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamMonitoringController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamMonitoringController.java index d031db26..1da8efd9 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamMonitoringController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamMonitoringController.java @@ -463,6 +463,7 @@ public class ExamMonitoringController { checkPrivileges(institutionId, examId); + // TODO do this in async in new thread if (connectionToken.contains(Constants.LIST_SEPARATOR)) { // If we have a bunch of client connections to disable, make it asynchronously and respond to the caller immediately this.executor.execute(() -> { @@ -563,8 +564,8 @@ public class ExamMonitoringController { final EnumSet filterStates = EnumSet.noneOf(ConnectionStatus.class); if (StringUtils.isNotBlank(hiddenStates)) { final String[] split = StringUtils.split(hiddenStates, Constants.LIST_SEPARATOR); - for (int i = 0; i < split.length; i++) { - filterStates.add(ConnectionStatus.valueOf(split[i])); + for (final String s : split) { + filterStates.add(ConnectionStatus.valueOf(s)); } } @@ -583,8 +584,8 @@ public class ExamMonitoringController { if (StringUtils.isNotBlank(hiddenClientGroups)) { filterClientGroups = new HashSet<>(); final String[] split = StringUtils.split(hiddenClientGroups, Constants.LIST_SEPARATOR); - for (int i = 0; i < split.length; i++) { - filterClientGroups.add(Long.parseLong(split[i])); + for (final String s : split) { + filterClientGroups.add(Long.parseLong(s)); } } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/oauth/WebClientDetailsService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/oauth/WebClientDetailsService.java index 4bad3ce4..7a2bcfa6 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/oauth/WebClientDetailsService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/oauth/WebClientDetailsService.java @@ -21,7 +21,7 @@ import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ClientConfigService; /** A ClientDetailsService to manage different API clients of SEB Server webservice API. - * + *

* Currently supporting two client types for the two different API's on * SEB Server webservice; * - Administration API for administrative purpose using password grant type with refresh token @@ -44,7 +44,7 @@ public class WebClientDetailsService implements ClientDetailsService { } /** Load a client by the client id. This method must not return null. - * + *

* This checks first if the given clientId matches the client id of AdminAPIClientDetails. * If not, iterating through LMSSetup's and matches the sebClientname of each LMSSetup. * If there is a match, a ClientDetails object is created from LMSSetup and returned. diff --git a/src/test/java/ch/ethz/seb/sebserver/gui/integration/UseCasesIntegrationTest.java b/src/test/java/ch/ethz/seb/sebserver/gui/integration/UseCasesIntegrationTest.java index 8ec497f6..c8708c22 100644 --- a/src/test/java/ch/ethz/seb/sebserver/gui/integration/UseCasesIntegrationTest.java +++ b/src/test/java/ch/ethz/seb/sebserver/gui/integration/UseCasesIntegrationTest.java @@ -2437,7 +2437,7 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest { connections = connectionsCall.get(); assertFalse(connections.isEmpty()); conData = connections.iterator().next(); - assertEquals("DISABLED", conData.clientConnection.status.name()); + assertEquals("CLOSED", conData.clientConnection.status.name()); // get client logs final Result> clientLogPage = restService @@ -2520,7 +2520,7 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest { assertFalse(ccDataPage.content.isEmpty()); final ClientConnectionData clientConnectionData = ccDataPage.content.get(0); assertNotNull(clientConnectionData); - assertEquals("DISABLED", clientConnectionData.clientConnection.status.toString()); + assertEquals("CLOSED", clientConnectionData.clientConnection.status.toString()); connectionDatacall = restService .getBuilder(GetFinishedExamClientConnectionPage.class) diff --git a/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/exam/SEBConnectionAPITest.java b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/exam/SEBConnectionAPITest.java new file mode 100644 index 00000000..20fecdc9 --- /dev/null +++ b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/exam/SEBConnectionAPITest.java @@ -0,0 +1,247 @@ +package ch.ethz.seb.sebserver.webservice.integration.api.exam; + +import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.security.Principal; +import java.util.Collection; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + + +import ch.ethz.seb.sebserver.gbl.api.API; +import ch.ethz.seb.sebserver.gbl.api.JSONMapper; +import ch.ethz.seb.sebserver.gbl.model.Page; +import ch.ethz.seb.sebserver.gbl.model.exam.Exam; +import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection; +import ch.ethz.seb.sebserver.gbl.model.session.RunningExamInfo; +import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.UserService; +import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ClientConnectionDAO; +import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserDAO; +import ch.ethz.seb.sebserver.webservice.weblayer.api.APIExceptionHandler; +import ch.ethz.seb.sebserver.webservice.weblayer.api.ExamAPI_V1_Controller; +import ch.ethz.seb.sebserver.webservice.weblayer.api.ExamAdministrationController; +import org.junit.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.ResponseEntity; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.context.request.async.StandardServletAsyncWebRequest; + +@Sql(scripts = { "classpath:schema-test.sql", "classpath:data-test.sql", "classpath:data-test-additional.sql" }) +public class SEBConnectionAPITest extends ExamAPIIntegrationTester { + + @Autowired + private ExamAPI_V1_Controller examAPI_V1_Controller; + @Autowired + private ExamAdministrationController examAdministrationController; + @Autowired + private ClientConnectionDAO clientConnectionDAO; + @Autowired + private APIExceptionHandler apiExceptionHandler; + @Autowired + private UserDAO userDAO; + @MockBean + private UserService userService; + + @Test + public void testRunningExam6Available() { + + Mockito.when(userService.getCurrentUser()).thenReturn(userDAO.sebServerUserByUsername("admin").get()); + + final HttpServletRequest requestMock = Mockito.mock(HttpServletRequest.class); + Page page = examAdministrationController.getPage( + 1L, + 1, + 10, + null, + null, + requestMock); + + assertNotNull(page); + final Optional runningExam = page.content.stream().filter(e -> e.status == Exam.ExamStatus.RUNNING).findFirst(); + assertTrue(runningExam.isPresent()); + final Exam exam = runningExam.get(); + assertEquals("Exam [id=2, institutionId=1, lmsSetupId=1, externalId=quiz6, name=quiz6, description=null, startTime=null, endTime=null, startURL=https://test.lms.mockup, type=MANAGED, owner=admin, supporter=[admin], status=RUNNING, browserExamKeys=null, active=true, lastUpdate=null]", exam.toString()); + } + + @Test + public void testBadRequestError() throws Exception { + final Principal principal = Mockito.mock(Principal.class); + final HttpServletRequest request = Mockito.mock(HttpServletRequest.class); + final HttpServletResponse response = new MockHttpServletResponse(); + + final CompletableFuture> apiCall = examAPI_V1_Controller.handshakeCreate( + null, + null, + null, + null, + principal, + request, + response); + + try { + apiCall.get(); + } catch (final Exception e) { + final ResponseEntity responseEntity = apiExceptionHandler + .handleAccessDenied( + (AccessDeniedException) e.getCause(), + new StandardServletAsyncWebRequest(request, response)); + + final String jsonBody = new JSONMapper().writeValueAsString(responseEntity.getBody()); + assertEquals("[{\"messageCode\":\"1001\",\"systemMessage\":\"FORBIDDEN\",\"details\":\"Unknown or illegal client access\",\"attributes\":[]}]", jsonBody); + } + } + + @Test + public void testOrdinarySEBHandshakeWithKnownExam() throws Exception { + + // ****************************************************************** + // 1. Handshake creation + HttpServletResponse httpServletResponse = handshakePOST( + 1L, + 2L, + "baba", + "Win", + "3.5", + "m2000"); + + // 1.1 Assert response + assertEquals("200", String.valueOf(httpServletResponse.getStatus())); + final String connectionToken = httpServletResponse.getHeader("SEBConnectionToken"); + + assertNotNull(connectionToken); + assertNotNull(httpServletResponse.getHeader("SEBExamSalt")); + assertEquals( + "[{\"examId\":\"2\",\"name\":\"quiz6\",\"url\":\"https://test.lms.mockup\",\"lmsType\":\"MOCKUP\"}]", + ((MockHttpServletResponse) httpServletResponse).getContentAsString()); + + // 1.2 Assert persistent data + ClientConnection clientConnection = clientConnectionDAO + .byConnectionToken(connectionToken) + .getOrThrow(); + assertNotNull(clientConnection); + assertEquals("CONNECTION_REQUESTED", clientConnection.getStatus().name()); + assertEquals(connectionToken, clientConnection.connectionToken); + assertEquals(1, (long) clientConnection.institutionId); + assertEquals(2, (long) clientConnection.examId); + assertEquals("127.0.0.1", clientConnection.clientAddress); + assertEquals("baba", clientConnection.sebClientUserId); + assertEquals("Win", clientConnection.sebOSName); + assertEquals("3.5", clientConnection.sebVersion); + assertEquals("m2000", clientConnection.sebMachineName); + // userSessionId should be client id if not given yet + assertEquals("baba", clientConnection.userSessionId); + + // ****************************************************************** + // 2. Handshake update + httpServletResponse = handshakePATCH( + connectionToken, + 2L, + "John Doe", + "baba2", + "Mac", + "3.7", + "fvbfvfsv", + null); + + // 2.1 Assert response + assertEquals("200", String.valueOf(httpServletResponse.getStatus())); + assertNotNull(httpServletResponse.getHeader("SEBExamSalt")); + + // 2.2 Assert persistent data + clientConnection = clientConnectionDAO + .byConnectionToken(connectionToken) + .getOrThrow(); + assertNotNull(clientConnection); + assertEquals("AUTHENTICATED", clientConnection.getStatus().name()); + assertEquals(connectionToken, clientConnection.connectionToken); + assertEquals(1, (long) clientConnection.institutionId); + assertEquals(2, (long) clientConnection.examId); + assertEquals("127.0.0.1", clientConnection.clientAddress); + assertEquals("baba", clientConnection.sebClientUserId); // should not be possible to overwrite previous setting + assertEquals("Win", clientConnection.sebOSName); // should not be possible to overwrite previous setting + assertEquals("3.5", clientConnection.sebVersion); // should not be possible to overwrite previous setting + assertEquals("m2000", clientConnection.sebMachineName); // should not be possible to overwrite previous setting + + } + + private HttpServletResponse handshakePOST( + final Long institutionId, + final Long examId, + final String client_id, + final String seb_os_name, + final String seb_version, + final String seb_machine_name) throws Exception { + + final Principal principal = Mockito.mock(Principal.class); + final HttpServletRequest request = new MockHttpServletRequest("POST", "/exam-api/v1/handshake ") ; + final HttpServletResponse response = new MockHttpServletResponse(); + Mockito.when(principal.getName()).thenReturn("test"); + + final MultiValueMap formParams = new LinkedMultiValueMap<>(); + formParams.add(API.EXAM_API_PARAM_SEB_VERSION, seb_version); + formParams.add(API.EXAM_API_PARAM_SEB_OS_NAME, seb_os_name); + formParams.add(API.EXAM_API_PARAM_SEB_MACHINE_NAME, seb_machine_name); + + final CompletableFuture> apiCall = examAPI_V1_Controller + .handshakeCreate(institutionId, examId, client_id, formParams, principal, request, response); + + response.getOutputStream().print(new JSONMapper().writeValueAsString(apiCall.get())); + return response; + } + + private HttpServletResponse handshakePATCH( + final String connectionToken, + final Long examId, + final String userSessionId, + final String client_id, + final String seb_os_name, + final String seb_version, + final String seb_machine_name, + final String askHash) throws Exception { + + final Principal principal = Mockito.mock(Principal.class); + final HttpServletRequest request = new MockHttpServletRequest("PATCH", "/exam-api/v1/handshake ") ; + final HttpServletResponse response = new MockHttpServletResponse(); + Mockito.when(principal.getName()).thenReturn("test"); + + examAPI_V1_Controller.handshakeUpdate( + connectionToken, examId, userSessionId, seb_version, seb_os_name, + seb_machine_name, askHash, client_id, principal, request, response) + .get(); + + return response; + } + + private HttpServletResponse handshakePUT( + final String connectionToken, + final Long examId, + final String userSessionId, + final String client_id, + final String seb_os_name, + final String seb_version, + final String seb_machine_name, + final String askHash) throws Exception { + + final Principal principal = Mockito.mock(Principal.class); + final HttpServletRequest request = new MockHttpServletRequest("PUT", "/exam-api/v1/handshake ") ; + final HttpServletResponse response = new MockHttpServletResponse(); + Mockito.when(principal.getName()).thenReturn("test"); + + examAPI_V1_Controller.handshakeEstablish( + connectionToken, examId, userSessionId, seb_version, seb_os_name, + seb_machine_name, askHash, client_id, principal, request, response) + .get(); + + return response; + } +} diff --git a/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/exam/SebConnectionTest.java b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/exam/SebConnectionTest.java index 88b30db6..a55a2e61 100644 --- a/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/exam/SebConnectionTest.java +++ b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/exam/SebConnectionTest.java @@ -441,15 +441,15 @@ public class SebConnectionTest extends ExamAPIIntegrationTester { .build() .execute(); - assertTrue(records.size() == 1); + assertEquals(1, records.size()); final ClientConnectionRecord clientConnectionRecord = records.get(0); assertEquals("1", String.valueOf(clientConnectionRecord.getInstitutionId())); assertEquals("2", String.valueOf(clientConnectionRecord.getExamId())); assertEquals("CLOSED", String.valueOf(clientConnectionRecord.getStatus())); assertNotNull(clientConnectionRecord.getConnectionToken()); assertNotNull(clientConnectionRecord.getClientAddress()); - assertNull(clientConnectionRecord.getExamUserSessionId()); - assertTrue(clientConnectionRecord.getVdi() == 0); + assertNotNull(clientConnectionRecord.getExamUserSessionId()); + assertEquals(0, (int) clientConnectionRecord.getVdi()); assertNull(clientConnectionRecord.getVirtualClientAddress()); // check cache after update diff --git a/src/test/java/ch/ethz/seb/sebserver/webservice/integration/services/SEBClientInstructionServiceTest.java b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/services/SEBClientInstructionServiceTest.java index fced5f69..9ecdf5d5 100644 --- a/src/test/java/ch/ethz/seb/sebserver/webservice/integration/services/SEBClientInstructionServiceTest.java +++ b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/services/SEBClientInstructionServiceTest.java @@ -66,7 +66,7 @@ public class SEBClientInstructionServiceTest extends AdministrationAPIIntegratio // register instruction this.sebClientInstructionService.registerInstruction( - 2L, InstructionType.SEB_QUIT, Collections.emptyMap(), "testToken", false); + 2L, InstructionType.SEB_QUIT, Collections.emptyMap(), "testToken", true,false); // check on DB all = this.clientInstructionDAO @@ -100,7 +100,7 @@ public class SEBClientInstructionServiceTest extends AdministrationAPIIntegratio public void testRegisterWithConfirm() { // register instruction this.sebClientInstructionService.registerInstruction( - 2L, InstructionType.SEB_RECONFIGURE_SETTINGS, Collections.emptyMap(), "testToken", true); + 2L, InstructionType.SEB_RECONFIGURE_SETTINGS, Collections.emptyMap(), "testToken", true,true); // check on DB Collection all = this.clientInstructionDAO @@ -142,7 +142,7 @@ public class SEBClientInstructionServiceTest extends AdministrationAPIIntegratio attributes.put("attr1", "123"); attributes.put("attr2", "345"); this.sebClientInstructionService.registerInstruction( - 2L, InstructionType.SEB_RECONFIGURE_SETTINGS, attributes, "testToken", true); + 2L, InstructionType.SEB_RECONFIGURE_SETTINGS, attributes, "testToken", true,true); // check on DB Collection all = this.clientInstructionDAO