SEBSERV-440 implemented
This commit is contained in:
parent
0d5d7b3894
commit
50456b8d9b
23 changed files with 749 additions and 466 deletions
|
@ -74,7 +74,10 @@ public class APIMessage implements Serializable {
|
||||||
EXAM_IMPORT_ERROR_AUTO_CONFIG("1610", HttpStatus.PARTIAL_CONTENT,
|
EXAM_IMPORT_ERROR_AUTO_CONFIG("1610", HttpStatus.PARTIAL_CONTENT,
|
||||||
"Failed to automatically create and link exam configuration from the exam template to the exam"),
|
"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,
|
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 String messageCode;
|
||||||
public final HttpStatus httpStatus;
|
public final HttpStatus httpStatus;
|
||||||
|
|
|
@ -440,7 +440,7 @@ public final class ClientConnection implements GrantEntity {
|
||||||
builder.append(this.clientAddress);
|
builder.append(this.clientAddress);
|
||||||
builder.append(", remoteProctoringRoomId=");
|
builder.append(", remoteProctoringRoomId=");
|
||||||
builder.append(this.remoteProctoringRoomId);
|
builder.append(this.remoteProctoringRoomId);
|
||||||
builder.append(", virtualClientId=");
|
builder.append(", sebClientUserId=");
|
||||||
builder.append(this.sebClientUserId);
|
builder.append(this.sebClientUserId);
|
||||||
builder.append(", creationTime=");
|
builder.append(", creationTime=");
|
||||||
builder.append(this.creationTime);
|
builder.append(this.creationTime);
|
||||||
|
|
|
@ -364,7 +364,7 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
|
||||||
data.examId,
|
data.examId,
|
||||||
ConnectionStatus.CONNECTION_REQUESTED.name(),
|
ConnectionStatus.CONNECTION_REQUESTED.name(),
|
||||||
data.connectionToken,
|
data.connectionToken,
|
||||||
null,
|
data.userSessionId,
|
||||||
data.clientAddress,
|
data.clientAddress,
|
||||||
data.sebClientUserId,
|
data.sebClientUserId,
|
||||||
BooleanUtils.toInteger(data.vdi, 1, 0, 0),
|
BooleanUtils.toInteger(data.vdi, 1, 0, 0),
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
package ch.ethz.seb.sebserver.webservice.servicelayer.session;
|
package ch.ethz.seb.sebserver.webservice.servicelayer.session;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import java.security.Principal;
|
import java.security.Principal;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
|
@ -158,4 +159,18 @@ public interface SEBClientConnectionService {
|
||||||
* happened */
|
* happened */
|
||||||
Result<Collection<EntityKey>> disableConnections(final String[] connectionTokens, final Long institutionId);
|
Result<Collection<EntityKey>> 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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,12 +28,12 @@ import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||||
import ch.ethz.seb.sebserver.webservice.WebserviceInfo;
|
import ch.ethz.seb.sebserver.webservice.WebserviceInfo;
|
||||||
|
|
||||||
/** Service for SEB instruction handling.
|
/** Service for SEB instruction handling.
|
||||||
*
|
* <p>
|
||||||
* SEB instructions are sent as response of a SEB Ping on a active SEB Connection
|
* 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. */
|
* If there is an instruction in the queue for a specified SEB Client. */
|
||||||
public interface SEBClientInstructionService {
|
public interface SEBClientInstructionService {
|
||||||
|
|
||||||
static final Logger log = LoggerFactory.getLogger(SEBClientInstructionService.class);
|
Logger log = LoggerFactory.getLogger(SEBClientInstructionService.class);
|
||||||
|
|
||||||
/** Get the underling WebserviceInfo
|
/** Get the underling WebserviceInfo
|
||||||
*
|
*
|
||||||
|
@ -46,10 +46,10 @@ public interface SEBClientInstructionService {
|
||||||
void init();
|
void init();
|
||||||
|
|
||||||
/** Used to register a SEB client instruction for one or more active client connections
|
/** 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
|
* @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)
|
@Async(AsyncServiceSpringConfig.EXECUTOR_BEAN_NAME)
|
||||||
default void registerInstructionAsync(final ClientInstruction clientInstruction) {
|
default void registerInstructionAsync(final ClientInstruction clientInstruction) {
|
||||||
registerInstruction(clientInstruction, false)
|
registerInstruction(clientInstruction, false)
|
||||||
|
@ -69,7 +69,7 @@ public interface SEBClientInstructionService {
|
||||||
/** Used to register a SEB client instruction for one or more active client connections
|
/** Used to register a SEB client instruction for one or more active client connections
|
||||||
*
|
*
|
||||||
* @param clientInstruction the ClientInstruction instance to register
|
* @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 */
|
* @return A Result refer to a void marker or to an error if happened */
|
||||||
default Result<Void> registerInstruction(
|
default Result<Void> registerInstruction(
|
||||||
final ClientInstruction clientInstruction,
|
final ClientInstruction clientInstruction,
|
||||||
|
@ -91,6 +91,7 @@ public interface SEBClientInstructionService {
|
||||||
* @param type The InstructionType
|
* @param type The InstructionType
|
||||||
* @param attributes The instruction's attributes
|
* @param attributes The instruction's attributes
|
||||||
* @param connectionToken a connectionToken to register the instruction for.
|
* @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
|
* @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 */
|
* @return A Result refer to a void marker or to an error if happened */
|
||||||
Result<Void> registerInstruction(
|
Result<Void> registerInstruction(
|
||||||
|
@ -98,6 +99,7 @@ public interface SEBClientInstructionService {
|
||||||
InstructionType type,
|
InstructionType type,
|
||||||
Map<String, String> attributes,
|
Map<String, String> attributes,
|
||||||
String connectionToken,
|
String connectionToken,
|
||||||
|
boolean checkActive,
|
||||||
boolean needsConfirm);
|
boolean needsConfirm);
|
||||||
|
|
||||||
/** Used to register a SEB client instruction for one or more active client connections
|
/** 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
|
/** Get a SEB instruction for the specified SEB Client connection or null of there
|
||||||
* is currently no SEB instruction in the queue.
|
* is currently no SEB instruction in the queue.
|
||||||
*
|
* <p>
|
||||||
* NOTE: If this call returns a SEB instruction instance, this instance is considered
|
* 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
|
* @param connectionToken the SEB Client connection token
|
||||||
* @return SEB instruction to sent to the SEB Client or null */
|
* @return SEB instruction to sent to the SEB Client or null */
|
||||||
|
@ -131,7 +133,7 @@ public interface SEBClientInstructionService {
|
||||||
* @param instructionConfirm the instruction confirm identifier */
|
* @param instructionConfirm the instruction confirm identifier */
|
||||||
void confirmInstructionDone(String connectionToken, String instructionConfirm);
|
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();
|
void cleanupInstructions();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 */
|
/** Used to update the app signature key grants of all active SEB connections that miss a grant */
|
||||||
void updateASKGrants();
|
void updateASKGrants();
|
||||||
|
|
||||||
/** Used to cleanup old instructions from the persistent storage */
|
/** Used to clean up old instructions from the persistent storage */
|
||||||
void cleanupInstructions();
|
void cleanupInstructions();
|
||||||
|
|
||||||
/** Notify a ping for a certain client connection.
|
/** Notify a ping for a certain client connection.
|
||||||
|
@ -66,7 +66,7 @@ public interface SEBClientSessionService extends ExamUpdateTask, SessionUpdateTa
|
||||||
* @param instructionConfirm the instruction confirm identifier */
|
* @param instructionConfirm the instruction confirm identifier */
|
||||||
void confirmInstructionDone(String connectionToken, String instructionConfirm);
|
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
|
* @param clientConnection The client connection values
|
||||||
* @return Result refer to ClientConnectionData instance containing the given clientConnection plus the indicator
|
* @return Result refer to ClientConnectionData instance containing the given clientConnection plus the indicator
|
||||||
|
|
|
@ -227,27 +227,30 @@ public class ExamSessionServiceImpl implements ExamSessionService {
|
||||||
updateExamCache(examId);
|
updateExamCache(examId);
|
||||||
}
|
}
|
||||||
|
|
||||||
final Exam exam = this.examSessionCacheService.getRunningExam(examId);
|
return Result.tryCatch(() -> {
|
||||||
|
|
||||||
if (this.examSessionCacheService.isRunning(exam)) {
|
final Exam exam = this.examSessionCacheService.getRunningExam(examId);
|
||||||
if (log.isTraceEnabled()) {
|
|
||||||
log.trace("Exam {} is running and cached", 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
|
@Override
|
||||||
|
|
|
@ -8,22 +8,28 @@
|
||||||
|
|
||||||
package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl;
|
package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl;
|
||||||
|
|
||||||
|
import javax.servlet.ServletOutputStream;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import java.security.Principal;
|
import java.security.Principal;
|
||||||
import java.util.Collection;
|
import java.util.*;
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
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.Entity;
|
||||||
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.util.Utils;
|
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.BooleanUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.tomcat.jni.Address;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.security.access.AccessDeniedException;
|
import org.springframework.security.access.AccessDeniedException;
|
||||||
import org.springframework.stereotype.Service;
|
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.profile.WebServiceProfile;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||||
import ch.ethz.seb.sebserver.webservice.WebserviceInfo;
|
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.ClientConnectionDAO;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.SEBClientConfigDAO;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.SEBClientConfigDAO;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamAdminService;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamAdminService;
|
||||||
|
@ -72,6 +77,8 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
|
||||||
private final DistributedIndicatorValueService distributedPingCache;
|
private final DistributedIndicatorValueService distributedPingCache;
|
||||||
private final SecurityKeyService securityKeyService;
|
private final SecurityKeyService securityKeyService;
|
||||||
private final SEBClientEventBatchService sebClientEventBatchService;
|
private final SEBClientEventBatchService sebClientEventBatchService;
|
||||||
|
private final SEBClientInstructionService sebClientInstructionService;
|
||||||
|
private final JSONMapper jsonMapper;
|
||||||
private final boolean isDistributedSetup;
|
private final boolean isDistributedSetup;
|
||||||
|
|
||||||
protected SEBClientConnectionServiceImpl(
|
protected SEBClientConnectionServiceImpl(
|
||||||
|
@ -82,7 +89,9 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
|
||||||
final ClientIndicatorFactory clientIndicatorFactory,
|
final ClientIndicatorFactory clientIndicatorFactory,
|
||||||
final SecurityKeyService securityKeyService,
|
final SecurityKeyService securityKeyService,
|
||||||
final WebserviceInfo webserviceInfo,
|
final WebserviceInfo webserviceInfo,
|
||||||
final SEBClientEventBatchService sebClientEventBatchService) {
|
final SEBClientEventBatchService sebClientEventBatchService,
|
||||||
|
final SEBClientInstructionService sebClientInstructionService,
|
||||||
|
final JSONMapper jsonMapper) {
|
||||||
|
|
||||||
this.examSessionService = examSessionService;
|
this.examSessionService = examSessionService;
|
||||||
this.examSessionCacheService = examSessionService.getExamSessionCacheService();
|
this.examSessionCacheService = examSessionService.getExamSessionCacheService();
|
||||||
|
@ -94,6 +103,8 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
|
||||||
this.securityKeyService = securityKeyService;
|
this.securityKeyService = securityKeyService;
|
||||||
this.isDistributedSetup = webserviceInfo.isDistributed();
|
this.isDistributedSetup = webserviceInfo.isDistributed();
|
||||||
this.sebClientEventBatchService = sebClientEventBatchService;
|
this.sebClientEventBatchService = sebClientEventBatchService;
|
||||||
|
this.sebClientInstructionService = sebClientInstructionService;
|
||||||
|
this.jsonMapper = jsonMapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -143,29 +154,38 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
|
||||||
clientId);
|
clientId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final String connectionToken = createToken();
|
||||||
|
|
||||||
if (examId != null) {
|
if (examId != null) {
|
||||||
checkExamIntegrity(
|
checkExamIntegrity(
|
||||||
examId,
|
examId,
|
||||||
|
null,
|
||||||
institutionId,
|
institutionId,
|
||||||
(principal != null) ? principal.getName() : "--",
|
connectionToken,
|
||||||
clientAddress);
|
clientAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final String updateUserSessionId = updateUserSessionId(
|
||||||
|
examId,
|
||||||
|
null,
|
||||||
|
clientId,
|
||||||
|
sebMachineName,
|
||||||
|
null);
|
||||||
|
|
||||||
// Create ClientConnection in status CONNECTION_REQUESTED for further processing
|
// Create ClientConnection in status CONNECTION_REQUESTED for further processing
|
||||||
final String connectionToken = createToken();
|
|
||||||
final ClientConnection clientConnection = this.clientConnectionDAO.createNew(new ClientConnection(
|
final ClientConnection clientConnection = this.clientConnectionDAO.createNew(new ClientConnection(
|
||||||
null,
|
null,
|
||||||
institutionId, // Set the institution identifier that was checked against integrity before
|
institutionId,
|
||||||
examId, // Set the exam identifier if available otherwise it is null
|
examId,
|
||||||
ConnectionStatus.CONNECTION_REQUESTED, // Initial state
|
ConnectionStatus.CONNECTION_REQUESTED,
|
||||||
connectionToken, // The generated connection token that identifies this connection
|
connectionToken,
|
||||||
null,
|
updateUserSessionId,
|
||||||
(clientAddress != null) ? clientAddress : Constants.EMPTY_NOTE, // The IP address of the connecting client, verified on SEB Server side
|
(clientAddress != null) ? clientAddress : Constants.EMPTY_NOTE,
|
||||||
sebOsName,
|
sebOsName,
|
||||||
sebMachineName,
|
sebMachineName,
|
||||||
sebVersion,
|
sebVersion,
|
||||||
clientId, // The client identifier sent by the SEB client if available
|
clientId,
|
||||||
clientConfig.vdiType != VDIType.NO, // The VDI flag to indicate if this is a VDI prime connection
|
clientConfig.vdiType != VDIType.NO,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
@ -218,83 +238,58 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
|
||||||
|
|
||||||
return Result.tryCatch(() -> {
|
return Result.tryCatch(() -> {
|
||||||
|
|
||||||
ClientConnection clientConnection = getClientConnection(connectionToken);
|
final ClientConnection clientConnection = getClientConnection(connectionToken);
|
||||||
|
final Long _examId = clientConnection.examId != null ? clientConnection.examId : examId;
|
||||||
|
|
||||||
checkInstitutionalIntegrity(institutionId, clientConnection);
|
checkInstitutionalIntegrity(institutionId, clientConnection);
|
||||||
checkExamIntegrity(examId, clientConnection);
|
connectionStatusIntegrityCheck(clientConnection, clientAddress);
|
||||||
|
checkExamIntegrity(examId, clientConnection.examId, institutionId, clientConnection.connectionToken, clientConnection.info);
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (log.isDebugEnabled()) {
|
if (log.isDebugEnabled()) {
|
||||||
log.debug(
|
log.debug(
|
||||||
"SEB client connection, update ClientConnection for "
|
"SEB client connection, update ClientConnection for connectionToken {} institutionId: {} exam: {} client address: {} userSessionId: {} clientId: {}",
|
||||||
+ "connectionToken {} "
|
connectionToken, institutionId, examId, clientAddress, userSessionId, clientId);
|
||||||
+ "institutionId: {}"
|
|
||||||
+ "exam: {} "
|
|
||||||
+ "client address: {} "
|
|
||||||
+ "userSessionId: {}"
|
|
||||||
+ "clientId: {}",
|
|
||||||
connectionToken,
|
|
||||||
institutionId,
|
|
||||||
examId,
|
|
||||||
clientAddress,
|
|
||||||
userSessionId,
|
|
||||||
clientId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// userSessionId integrity check
|
final String updateUserSessionId = updateUserSessionId(
|
||||||
clientConnection = updateUserSessionId(userSessionId, clientConnection, examId);
|
_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
|
final ClientConnection updatedClientConnection = this.clientConnectionDAO
|
||||||
.save(new ClientConnection(
|
.save(updateConnection)
|
||||||
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))
|
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
|
|
||||||
// initialize distributed indicator value caches if possible and needed
|
// initialize distributed indicator value caches if possible and needed
|
||||||
|
@ -332,62 +327,31 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
|
||||||
|
|
||||||
return Result.tryCatch(() -> {
|
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
|
connectionStatusIntegrityCheck(clientConnection, clientAddress);
|
||||||
if (!clientConnection.status.clientActiveStatus) {
|
checkInstitutionalIntegrity(institutionId, clientConnection);
|
||||||
log.warn("ClientConnection integrity violation: client connection is not in expected state: {}",
|
checkExamIntegrity(
|
||||||
clientConnection);
|
examId,
|
||||||
throw new IllegalArgumentException(
|
clientConnection.examId,
|
||||||
"ClientConnection integrity violation: client connection is not in expected state");
|
institutionId,
|
||||||
}
|
clientConnection.connectionToken,
|
||||||
|
clientConnection.info);
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (log.isDebugEnabled()) {
|
if (log.isDebugEnabled()) {
|
||||||
log.debug(
|
log.debug(
|
||||||
"SEB client connection, establish ClientConnection for "
|
"SEB client connection, establish ClientConnection for connectionToken {} institutionId: {} exam: {} client address: {} userSessionId: {} clientId: {}",
|
||||||
+ "connectionToken {} "
|
connectionToken, institutionId, examId, clientAddress, userSessionId, clientId);
|
||||||
+ "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
|
final Boolean proctoringEnabled = this.examAdminService
|
||||||
.isProctoringEnabled(clientConnection.examId)
|
.isProctoringEnabled(clientConnection.examId)
|
||||||
.getOr(false);
|
.getOr(false);
|
||||||
|
@ -407,12 +371,12 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
|
||||||
currentExamId,
|
currentExamId,
|
||||||
ConnectionStatus.ACTIVE,
|
ConnectionStatus.ACTIVE,
|
||||||
null,
|
null,
|
||||||
clientConnection.userSessionId,
|
updateUserSessionId,
|
||||||
StringUtils.isNoneBlank(clientAddress) ? clientAddress : null,
|
StringUtils.isNotBlank(clientAddress) ? clientAddress : null,
|
||||||
StringUtils.isNoneBlank(sebOsName) ? sebOsName : null,
|
StringUtils.isNotBlank(sebOsName) ? sebOsName : null,
|
||||||
StringUtils.isNoneBlank(sebMachineName) ? sebMachineName : null,
|
StringUtils.isNotBlank(sebMachineName) ? sebMachineName : null,
|
||||||
StringUtils.isNoneBlank(sebVersion) ? sebVersion : null,
|
StringUtils.isNotBlank(sebVersion) ? sebVersion : null,
|
||||||
StringUtils.isNoneBlank(clientId) ? clientId : null,
|
StringUtils.isNotBlank(clientId) ? clientId : null,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
@ -422,10 +386,7 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
|
||||||
null,
|
null,
|
||||||
proctoringEnabled,
|
proctoringEnabled,
|
||||||
null,
|
null,
|
||||||
getSignatureHash(
|
getSignatureHash(appSignatureKey, connectionToken, _examId),
|
||||||
appSignatureKey,
|
|
||||||
connectionToken,
|
|
||||||
clientConnection.examId != null ? clientConnection.examId : examId),
|
|
||||||
null);
|
null);
|
||||||
|
|
||||||
// ClientConnection integrity check
|
// ClientConnection integrity check
|
||||||
|
@ -445,22 +406,10 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
|
||||||
throw new IllegalStateException("ClientConnection integrity violation");
|
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
|
final ClientConnection updatedClientConnection = this.clientConnectionDAO
|
||||||
.save(establishedClientConnection)
|
.save(establishedClientConnection)
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
|
|
||||||
// check exam integrity for established connection
|
|
||||||
checkExamIntegrity(
|
|
||||||
establishedClientConnection.examId,
|
|
||||||
institutionId,
|
|
||||||
establishedClientConnection.userSessionId,
|
|
||||||
establishedClientConnection.clientAddress);
|
|
||||||
|
|
||||||
// initialize distributed indicator value caches if possible and needed
|
// initialize distributed indicator value caches if possible and needed
|
||||||
if (examId != null && this.isDistributedSetup) {
|
if (examId != null && this.isDistributedSetup) {
|
||||||
this.clientIndicatorFactory.initializeDistributedCaches(clientConnection);
|
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<ClientConnectionRecord> 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
|
@Override
|
||||||
public Result<ClientConnection> closeConnection(
|
public Result<ClientConnection> closeConnection(
|
||||||
final String connectionToken,
|
final String connectionToken,
|
||||||
|
@ -605,10 +494,8 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
|
||||||
.getConnectionData(connectionToken)
|
.getConnectionData(connectionToken)
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
|
|
||||||
// An active connection can only be disabled if we have a missing ping
|
// A connection can only be disabled if we have a missing ping
|
||||||
if (connectionData.clientConnection.status == ConnectionStatus.ACTIVE &&
|
if (!BooleanUtils.isTrue(connectionData.getMissingPing())) {
|
||||||
!BooleanUtils.isTrue(connectionData.getMissingPing())) {
|
|
||||||
|
|
||||||
return connectionData.clientConnection;
|
return connectionData.clientConnection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -662,46 +549,162 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Result<Collection<EntityKey>> disableConnections(final String[] connectionTokens, final Long institutionId) {
|
public Result<Collection<EntityKey>> disableConnections(final String[] connectionTokens, final Long institutionId) {
|
||||||
return Result.tryCatch(() -> {
|
return Result.tryCatch(() -> Stream.of(connectionTokens)
|
||||||
|
.map(token -> disableConnection(token, institutionId)
|
||||||
return Stream.of(connectionTokens)
|
.onError(error -> log.error("Failed to disable SEB client connection: {}", token))
|
||||||
.map(token -> disableConnection(token, institutionId)
|
.getOr(null))
|
||||||
.onError(error -> log.error("Failed to disable SEB client connection: {}", token))
|
.filter(Objects::nonNull)
|
||||||
.getOr(null))
|
.map(Entity::getEntityKey)
|
||||||
.filter(Objects::nonNull)
|
.collect(Collectors.toList()));
|
||||||
.map(Entity::getEntityKey)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SEBSERV-475 IP address change during handshake is possible but is logged within SEB logs
|
public void streamExamConfig(
|
||||||
private void sebLogClientAddressMismatch(
|
final Long institutionId,
|
||||||
final String clientAddress,
|
final Long examId,
|
||||||
final ClientConnection clientConnection) {
|
final String connectionToken,
|
||||||
|
final String ipAddress,
|
||||||
|
final HttpServletResponse response) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final long now = Utils.getMillisecondsNow();
|
|
||||||
this.sebClientEventBatchService.accept(new SEBClientEventBatchService.EventData(
|
// if an examId is provided with the request, update the connection first
|
||||||
clientConnection.connectionToken,
|
if (examId != null) {
|
||||||
now,
|
final ClientConnection connection = this.updateClientConnection(
|
||||||
new ClientEvent(
|
connectionToken,
|
||||||
null,
|
institutionId,
|
||||||
clientConnection.id,
|
examId,
|
||||||
ClientEvent.EventType.WARN_LOG,
|
null,
|
||||||
now, now, null,
|
null,
|
||||||
"SEB Client IP address changed: " +
|
null,
|
||||||
clientConnection.clientAddress +
|
null,
|
||||||
" -> " +
|
null,
|
||||||
clientAddress
|
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<APIMessage> 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) {
|
} 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<APIMessage> 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<APIMessage> 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)) {
|
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();
|
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) {
|
private String updateUserSessionId(
|
||||||
if (examId != null &&
|
final Long examId,
|
||||||
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(
|
|
||||||
final String userSessionId,
|
final String userSessionId,
|
||||||
ClientConnection clientConnection,
|
final String clientId,
|
||||||
final Long examId) {
|
final String sebMachineName,
|
||||||
|
final ClientConnection clientConnection) {
|
||||||
|
|
||||||
if (StringUtils.isNoneBlank(userSessionId)) {
|
try {
|
||||||
if (StringUtils.isNoneBlank(clientConnection.userSessionId)) {
|
|
||||||
if (clientConnection.userSessionId.contains(userSessionId)) {
|
if (clientConnection == null) {
|
||||||
if (log.isDebugEnabled()) {
|
if (StringUtils.isNotBlank(clientId)) {
|
||||||
log.debug("SEB sent LMS userSessionId but clientConnection has already a userSessionId");
|
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)
|
if (examId == null) {
|
||||||
String accountId = userSessionId;
|
return null;
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// create new ClientConnection for update
|
// we got a userSessionId, so we want to resolve it via LMS binding
|
||||||
final ClientConnection authenticatedClientConnection = new ClientConnection(
|
final String accountId = this.examSessionService
|
||||||
clientConnection.id,
|
.getRunningExam(examId)
|
||||||
null, null,
|
.flatMap(exam -> this.examSessionService
|
||||||
ConnectionStatus.AUTHENTICATED, null,
|
.getLmsAPIService()
|
||||||
accountId,
|
.getLmsAPITemplate(exam.lmsSetupId))
|
||||||
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null);
|
.map(template -> template.getExamineeName(userSessionId))
|
||||||
|
.getOr(userSessionId);
|
||||||
|
|
||||||
clientConnection = this.clientConnectionDAO
|
// if userSessionId is not set yet or a placeholder is set, just use the new account name
|
||||||
.save(authenticatedClientConnection)
|
if (clientConnection.userSessionId == null ||
|
||||||
.getOrThrow();
|
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(
|
private void checkExamIntegrity(
|
||||||
final Long examId,
|
final Long examId,
|
||||||
|
final Long currentExamId,
|
||||||
final Long institutionId,
|
final Long institutionId,
|
||||||
final String user,
|
final String ccToken,
|
||||||
final String address) {
|
final String ccInfo) {
|
||||||
|
|
||||||
|
if (examId == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
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);
|
final Result<Exam> updateExamCache = this.examSessionService.updateExamCache(examId);
|
||||||
if (updateExamCache.hasError()) {
|
if (updateExamCache.hasError()) {
|
||||||
log.warn("Failed to update Exam-Cache for Exam: {}", examId);
|
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
|
// check Exam is running and not locked
|
||||||
checkExamRunning(examId, user, address);
|
checkExamRunning(examId, ccToken, ccInfo);
|
||||||
if (this.examSessionService.isExamLocked(examId)) {
|
if (this.examSessionService.isExamLocked(examId)) {
|
||||||
throw new APIConstraintViolationException(
|
throw new APIConstraintViolationException(
|
||||||
"Exam is currently on update and locked for new SEB Client connections");
|
"Exam is currently on update and locked for new SEB Client connections");
|
||||||
|
|
|
@ -103,34 +103,36 @@ public class SEBClientInstructionServiceImpl implements SEBClientInstructionServ
|
||||||
final InstructionType type,
|
final InstructionType type,
|
||||||
final Map<String, String> attributes,
|
final Map<String, String> attributes,
|
||||||
final String connectionToken,
|
final String connectionToken,
|
||||||
|
final boolean checkActive,
|
||||||
final boolean needsConfirm) {
|
final boolean needsConfirm) {
|
||||||
|
|
||||||
return Result.tryCatch(() -> {
|
return Result.tryCatch(() -> {
|
||||||
|
|
||||||
final boolean isActive = this.clientConnectionDAO
|
if (checkActive && !this.clientConnectionDAO
|
||||||
.isInInstructionStatus(examId, connectionToken)
|
.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(
|
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);
|
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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -154,7 +154,9 @@ public class SEBClientNotificationServiceImpl implements SEBClientNotificationSe
|
||||||
InstructionType.NOTIFICATION_CONFIRM,
|
InstructionType.NOTIFICATION_CONFIRM,
|
||||||
connectionToken,
|
connectionToken,
|
||||||
attributes);
|
attributes);
|
||||||
this.sebClientInstructionService.registerInstruction(clientInstruction);
|
this.sebClientInstructionService
|
||||||
|
.registerInstruction(clientInstruction)
|
||||||
|
.onError(error -> log.error("Failed to confirm instruction SEB client side: ", error));
|
||||||
|
|
||||||
return notification;
|
return notification;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl;
|
package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -15,6 +16,8 @@ import java.util.concurrent.ScheduledFuture;
|
||||||
|
|
||||||
import javax.annotation.PreDestroy;
|
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.apache.commons.lang3.StringUtils;
|
||||||
import org.ehcache.impl.internal.concurrent.ConcurrentHashMap;
|
import org.ehcache.impl.internal.concurrent.ConcurrentHashMap;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -42,7 +45,6 @@ public class SEBClientPingBatchService implements SEBClientPingService {
|
||||||
private final ExamSessionCacheService examSessionCacheService;
|
private final ExamSessionCacheService examSessionCacheService;
|
||||||
private final SEBClientInstructionService sebClientInstructionService;
|
private final SEBClientInstructionService sebClientInstructionService;
|
||||||
|
|
||||||
|
|
||||||
private final Set<String> pingKeys = new HashSet<>();
|
private final Set<String> pingKeys = new HashSet<>();
|
||||||
private final Map<String, String> pings = new ConcurrentHashMap<>();
|
private final Map<String, String> pings = new ConcurrentHashMap<>();
|
||||||
private final Map<String, String> instructions = new ConcurrentHashMap<>();
|
private final Map<String, String> instructions = new ConcurrentHashMap<>();
|
||||||
|
@ -69,7 +71,6 @@ public class SEBClientPingBatchService implements SEBClientPingService {
|
||||||
try {
|
try {
|
||||||
this.pingKeys.clear();
|
this.pingKeys.clear();
|
||||||
this.pingKeys.addAll(this.pings.keySet());
|
this.pingKeys.addAll(this.pings.keySet());
|
||||||
//final Set<String> connections = new HashSet<>(this.pings.keySet());
|
|
||||||
this.pingKeys.stream().forEach(cid -> processPing(
|
this.pingKeys.stream().forEach(cid -> processPing(
|
||||||
cid,
|
cid,
|
||||||
this.pings.remove(cid),
|
this.pings.remove(cid),
|
||||||
|
@ -119,11 +120,23 @@ public class SEBClientPingBatchService implements SEBClientPingService {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final ClientConnectionDataInternal activeClientConnection = this.examSessionCacheService
|
final ClientConnectionDataInternal connectionData = this.examSessionCacheService
|
||||||
.getClientConnection(connectionToken);
|
.getClientConnection(connectionToken);
|
||||||
|
|
||||||
if (activeClientConnection != null) {
|
if (connectionData != null) {
|
||||||
activeClientConnection.notifyPing(timestamp);
|
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 {
|
} else {
|
||||||
log.error("Failed to get ClientConnectionDataInternal for: {}", connectionToken);
|
log.error("Failed to get ClientConnectionDataInternal for: {}", connectionToken);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,10 @@
|
||||||
|
|
||||||
package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl;
|
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.apache.commons.lang3.StringUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -50,11 +54,23 @@ public class SEBClientPingBlockingService implements SEBClientPingService {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
final ClientConnectionDataInternal activeClientConnection = this.examSessionCacheService
|
final ClientConnectionDataInternal connectionData = this.examSessionCacheService
|
||||||
.getClientConnection(connectionToken);
|
.getClientConnection(connectionToken);
|
||||||
|
|
||||||
if (activeClientConnection != null) {
|
if (connectionData != null) {
|
||||||
activeClientConnection.notifyPing(Utils.getMillisecondsNow());
|
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 {
|
} else {
|
||||||
log.error("Failed to get ClientConnectionDataInternal for: {}", connectionToken);
|
log.error("Failed to get ClientConnectionDataInternal for: {}", connectionToken);
|
||||||
}
|
}
|
||||||
|
|
|
@ -653,6 +653,7 @@ public class RemoteProctoringRoomServiceImpl implements RemoteProctoringRoomServ
|
||||||
InstructionType.SEB_RECONFIGURE_SETTINGS,
|
InstructionType.SEB_RECONFIGURE_SETTINGS,
|
||||||
attributes,
|
attributes,
|
||||||
connectionToken,
|
connectionToken,
|
||||||
|
true,
|
||||||
true)
|
true)
|
||||||
.onError(error -> log.error(
|
.onError(error -> log.error(
|
||||||
"Failed to register reconfiguring instruction for connection: {}",
|
"Failed to register reconfiguring instruction for connection: {}",
|
||||||
|
@ -852,6 +853,7 @@ public class RemoteProctoringRoomServiceImpl implements RemoteProctoringRoomServ
|
||||||
InstructionType.SEB_PROCTORING,
|
InstructionType.SEB_PROCTORING,
|
||||||
attributes,
|
attributes,
|
||||||
connectionToken,
|
connectionToken,
|
||||||
|
true,
|
||||||
true)
|
true)
|
||||||
.onError(error -> log.error("Failed to send join instruction: {}", connectionToken, error))
|
.onError(error -> log.error("Failed to send join instruction: {}", connectionToken, error))
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
|
|
|
@ -476,6 +476,7 @@ public class ScreenProctoringServiceImpl implements ScreenProctoringService {
|
||||||
InstructionType.SEB_PROCTORING,
|
InstructionType.SEB_PROCTORING,
|
||||||
attributes,
|
attributes,
|
||||||
ccRecord.getConnectionToken(),
|
ccRecord.getConnectionToken(),
|
||||||
|
true,
|
||||||
true)
|
true)
|
||||||
.onError(error -> log.error(
|
.onError(error -> log.error(
|
||||||
"Failed to register screen proctoring join instruction for SEB connection: {}",
|
"Failed to register screen proctoring join instruction for SEB connection: {}",
|
||||||
|
|
|
@ -524,6 +524,7 @@ public class ZoomProctoringService implements RemoteProctoringService {
|
||||||
InstructionType.SEB_PROCTORING,
|
InstructionType.SEB_PROCTORING,
|
||||||
attributes,
|
attributes,
|
||||||
connectionToken,
|
connectionToken,
|
||||||
|
true,
|
||||||
true)
|
true)
|
||||||
.onError(error -> log.error("Failed to send join instruction: {}", connectionToken, error));
|
.onError(error -> log.error("Failed to send join instruction: {}", connectionToken, error));
|
||||||
}
|
}
|
||||||
|
|
|
@ -210,7 +210,7 @@ public class APIExceptionHandler extends ResponseEntityExceptionHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ExceptionHandler(AccessDeniedException.class)
|
@ExceptionHandler(AccessDeniedException.class)
|
||||||
public ResponseEntity<Object> handleUnexpected(
|
public ResponseEntity<Object> handleAccessDenied(
|
||||||
final AccessDeniedException ex,
|
final AccessDeniedException ex,
|
||||||
final WebRequest request) {
|
final WebRequest request) {
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ import java.security.Principal;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
@ -136,7 +137,7 @@ public class ExamAPI_V1_Controller {
|
||||||
clientConnection.connectionToken);
|
clientConnection.connectionToken);
|
||||||
|
|
||||||
// Crate list of running exams
|
// Crate list of running exams
|
||||||
List<RunningExamInfo> result;
|
final List<RunningExamInfo> result;
|
||||||
if (examId == null) {
|
if (examId == null) {
|
||||||
result = this.examSessionService.getRunningExamsForInstitution(institutionId)
|
result = this.examSessionService.getRunningExamsForInstitution(institutionId)
|
||||||
.getOrThrow()
|
.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_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_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_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(
|
@RequestParam(
|
||||||
name = API.EXAM_API_PARAM_SIGNATURE_KEY,
|
name = API.EXAM_API_PARAM_SIGNATURE_KEY,
|
||||||
required = false) final String browserSignatureKey,
|
required = false) final String browserSignatureKey,
|
||||||
|
@ -212,7 +213,7 @@ public class ExamAPI_V1_Controller {
|
||||||
remoteAddr,
|
remoteAddr,
|
||||||
sebVersion,
|
sebVersion,
|
||||||
sebOSName,
|
sebOSName,
|
||||||
sebMachinName,
|
sebMachineName,
|
||||||
userSessionId,
|
userSessionId,
|
||||||
clientId,
|
clientId,
|
||||||
browserSignatureKey)
|
browserSignatureKey)
|
||||||
|
@ -315,8 +316,22 @@ public class ExamAPI_V1_Controller {
|
||||||
final HttpServletRequest request,
|
final HttpServletRequest request,
|
||||||
final HttpServletResponse response) {
|
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(
|
return CompletableFuture.runAsync(
|
||||||
() -> streamExamConfig(connectionToken, formParams, principal, response),
|
() -> this.sebClientConnectionService.streamExamConfig(
|
||||||
|
getInstitutionId(principal),
|
||||||
|
_examId,
|
||||||
|
connectionToken,
|
||||||
|
remoteAddr,
|
||||||
|
response),
|
||||||
this.executor);
|
this.executor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -328,7 +343,6 @@ public class ExamAPI_V1_Controller {
|
||||||
public void ping(final HttpServletRequest request, final HttpServletResponse response) {
|
public void ping(final HttpServletRequest request, final HttpServletResponse response) {
|
||||||
|
|
||||||
final String connectionToken = request.getHeader(API.EXAM_API_SEB_CONNECTION_TOKEN);
|
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 instructionConfirm = request.getParameter(API.EXAM_API_PING_INSTRUCTION_CONFIRM);
|
||||||
|
|
||||||
final String instruction = this.sebClientSessionService
|
final String instruction = this.sebClientSessionService
|
||||||
|
@ -373,73 +387,7 @@ public class ExamAPI_V1_Controller {
|
||||||
.getOr(null));
|
.getOr(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void streamExamConfig(
|
|
||||||
final String connectionToken,
|
|
||||||
final MultiValueMap<String, String> 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) {
|
private String getClientAddress(final HttpServletRequest request) {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -463,6 +463,7 @@ public class ExamMonitoringController {
|
||||||
|
|
||||||
checkPrivileges(institutionId, examId);
|
checkPrivileges(institutionId, examId);
|
||||||
|
|
||||||
|
// TODO do this in async in new thread
|
||||||
if (connectionToken.contains(Constants.LIST_SEPARATOR)) {
|
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
|
// If we have a bunch of client connections to disable, make it asynchronously and respond to the caller immediately
|
||||||
this.executor.execute(() -> {
|
this.executor.execute(() -> {
|
||||||
|
@ -563,8 +564,8 @@ public class ExamMonitoringController {
|
||||||
final EnumSet<ConnectionStatus> filterStates = EnumSet.noneOf(ConnectionStatus.class);
|
final EnumSet<ConnectionStatus> filterStates = EnumSet.noneOf(ConnectionStatus.class);
|
||||||
if (StringUtils.isNotBlank(hiddenStates)) {
|
if (StringUtils.isNotBlank(hiddenStates)) {
|
||||||
final String[] split = StringUtils.split(hiddenStates, Constants.LIST_SEPARATOR);
|
final String[] split = StringUtils.split(hiddenStates, Constants.LIST_SEPARATOR);
|
||||||
for (int i = 0; i < split.length; i++) {
|
for (final String s : split) {
|
||||||
filterStates.add(ConnectionStatus.valueOf(split[i]));
|
filterStates.add(ConnectionStatus.valueOf(s));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -583,8 +584,8 @@ public class ExamMonitoringController {
|
||||||
if (StringUtils.isNotBlank(hiddenClientGroups)) {
|
if (StringUtils.isNotBlank(hiddenClientGroups)) {
|
||||||
filterClientGroups = new HashSet<>();
|
filterClientGroups = new HashSet<>();
|
||||||
final String[] split = StringUtils.split(hiddenClientGroups, Constants.LIST_SEPARATOR);
|
final String[] split = StringUtils.split(hiddenClientGroups, Constants.LIST_SEPARATOR);
|
||||||
for (int i = 0; i < split.length; i++) {
|
for (final String s : split) {
|
||||||
filterClientGroups.add(Long.parseLong(split[i]));
|
filterClientGroups.add(Long.parseLong(s));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||||
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ClientConfigService;
|
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ClientConfigService;
|
||||||
|
|
||||||
/** A ClientDetailsService to manage different API clients of SEB Server webservice API.
|
/** A ClientDetailsService to manage different API clients of SEB Server webservice API.
|
||||||
*
|
* <p>
|
||||||
* Currently supporting two client types for the two different API's on
|
* Currently supporting two client types for the two different API's on
|
||||||
* SEB Server webservice;
|
* SEB Server webservice;
|
||||||
* - Administration API for administrative purpose using password grant type with refresh token
|
* - 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.
|
/** Load a client by the client id. This method must not return null.
|
||||||
*
|
* <p>
|
||||||
* This checks first if the given clientId matches the client id of AdminAPIClientDetails.
|
* 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 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.
|
* If there is a match, a ClientDetails object is created from LMSSetup and returned.
|
||||||
|
|
|
@ -2437,7 +2437,7 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
|
||||||
connections = connectionsCall.get();
|
connections = connectionsCall.get();
|
||||||
assertFalse(connections.isEmpty());
|
assertFalse(connections.isEmpty());
|
||||||
conData = connections.iterator().next();
|
conData = connections.iterator().next();
|
||||||
assertEquals("DISABLED", conData.clientConnection.status.name());
|
assertEquals("CLOSED", conData.clientConnection.status.name());
|
||||||
|
|
||||||
// get client logs
|
// get client logs
|
||||||
final Result<Page<ExtendedClientEvent>> clientLogPage = restService
|
final Result<Page<ExtendedClientEvent>> clientLogPage = restService
|
||||||
|
@ -2520,7 +2520,7 @@ public class UseCasesIntegrationTest extends GuiIntegrationTest {
|
||||||
assertFalse(ccDataPage.content.isEmpty());
|
assertFalse(ccDataPage.content.isEmpty());
|
||||||
final ClientConnectionData clientConnectionData = ccDataPage.content.get(0);
|
final ClientConnectionData clientConnectionData = ccDataPage.content.get(0);
|
||||||
assertNotNull(clientConnectionData);
|
assertNotNull(clientConnectionData);
|
||||||
assertEquals("DISABLED", clientConnectionData.clientConnection.status.toString());
|
assertEquals("CLOSED", clientConnectionData.clientConnection.status.toString());
|
||||||
|
|
||||||
connectionDatacall = restService
|
connectionDatacall = restService
|
||||||
.getBuilder(GetFinishedExamClientConnectionPage.class)
|
.getBuilder(GetFinishedExamClientConnectionPage.class)
|
||||||
|
|
|
@ -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<Exam> page = examAdministrationController.getPage(
|
||||||
|
1L,
|
||||||
|
1,
|
||||||
|
10,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
requestMock);
|
||||||
|
|
||||||
|
assertNotNull(page);
|
||||||
|
final Optional<Exam> 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<Collection<RunningExamInfo>> apiCall = examAPI_V1_Controller.handshakeCreate(
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
principal,
|
||||||
|
request,
|
||||||
|
response);
|
||||||
|
|
||||||
|
try {
|
||||||
|
apiCall.get();
|
||||||
|
} catch (final Exception e) {
|
||||||
|
final ResponseEntity<Object> 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<String, String> 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<Collection<RunningExamInfo>> 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -441,15 +441,15 @@ public class SebConnectionTest extends ExamAPIIntegrationTester {
|
||||||
.build()
|
.build()
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
assertTrue(records.size() == 1);
|
assertEquals(1, records.size());
|
||||||
final ClientConnectionRecord clientConnectionRecord = records.get(0);
|
final ClientConnectionRecord clientConnectionRecord = records.get(0);
|
||||||
assertEquals("1", String.valueOf(clientConnectionRecord.getInstitutionId()));
|
assertEquals("1", String.valueOf(clientConnectionRecord.getInstitutionId()));
|
||||||
assertEquals("2", String.valueOf(clientConnectionRecord.getExamId()));
|
assertEquals("2", String.valueOf(clientConnectionRecord.getExamId()));
|
||||||
assertEquals("CLOSED", String.valueOf(clientConnectionRecord.getStatus()));
|
assertEquals("CLOSED", String.valueOf(clientConnectionRecord.getStatus()));
|
||||||
assertNotNull(clientConnectionRecord.getConnectionToken());
|
assertNotNull(clientConnectionRecord.getConnectionToken());
|
||||||
assertNotNull(clientConnectionRecord.getClientAddress());
|
assertNotNull(clientConnectionRecord.getClientAddress());
|
||||||
assertNull(clientConnectionRecord.getExamUserSessionId());
|
assertNotNull(clientConnectionRecord.getExamUserSessionId());
|
||||||
assertTrue(clientConnectionRecord.getVdi() == 0);
|
assertEquals(0, (int) clientConnectionRecord.getVdi());
|
||||||
assertNull(clientConnectionRecord.getVirtualClientAddress());
|
assertNull(clientConnectionRecord.getVirtualClientAddress());
|
||||||
|
|
||||||
// check cache after update
|
// check cache after update
|
||||||
|
|
|
@ -66,7 +66,7 @@ public class SEBClientInstructionServiceTest extends AdministrationAPIIntegratio
|
||||||
|
|
||||||
// register instruction
|
// register instruction
|
||||||
this.sebClientInstructionService.registerInstruction(
|
this.sebClientInstructionService.registerInstruction(
|
||||||
2L, InstructionType.SEB_QUIT, Collections.emptyMap(), "testToken", false);
|
2L, InstructionType.SEB_QUIT, Collections.emptyMap(), "testToken", true,false);
|
||||||
|
|
||||||
// check on DB
|
// check on DB
|
||||||
all = this.clientInstructionDAO
|
all = this.clientInstructionDAO
|
||||||
|
@ -100,7 +100,7 @@ public class SEBClientInstructionServiceTest extends AdministrationAPIIntegratio
|
||||||
public void testRegisterWithConfirm() {
|
public void testRegisterWithConfirm() {
|
||||||
// register instruction
|
// register instruction
|
||||||
this.sebClientInstructionService.registerInstruction(
|
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
|
// check on DB
|
||||||
Collection<ClientInstructionRecord> all = this.clientInstructionDAO
|
Collection<ClientInstructionRecord> all = this.clientInstructionDAO
|
||||||
|
@ -142,7 +142,7 @@ public class SEBClientInstructionServiceTest extends AdministrationAPIIntegratio
|
||||||
attributes.put("attr1", "123");
|
attributes.put("attr1", "123");
|
||||||
attributes.put("attr2", "345");
|
attributes.put("attr2", "345");
|
||||||
this.sebClientInstructionService.registerInstruction(
|
this.sebClientInstructionService.registerInstruction(
|
||||||
2L, InstructionType.SEB_RECONFIGURE_SETTINGS, attributes, "testToken", true);
|
2L, InstructionType.SEB_RECONFIGURE_SETTINGS, attributes, "testToken", true,true);
|
||||||
|
|
||||||
// check on DB
|
// check on DB
|
||||||
Collection<ClientInstructionRecord> all = this.clientInstructionDAO
|
Collection<ClientInstructionRecord> all = this.clientInstructionDAO
|
||||||
|
|
Loading…
Reference in a new issue