SEBSERV-475 implemented

This commit is contained in:
anhefti 2023-11-22 12:12:53 +01:00
parent 455d9809c3
commit 0d5d7b3894
6 changed files with 81 additions and 46 deletions

View file

@ -131,7 +131,7 @@ public final class ClientConnection implements GrantEntity {
@JsonIgnore @JsonIgnore
public final Long remoteProctoringRoomId; public final Long remoteProctoringRoomId;
@JsonIgnore @JsonIgnore
public final String virtualClientId; public final String sebClientUserId;
@JsonIgnore @JsonIgnore
public final Long creationTime; public final Long creationTime;
@JsonIgnore @JsonIgnore
@ -170,7 +170,7 @@ public final class ClientConnection implements GrantEntity {
this.userSessionId = userSessionId; this.userSessionId = userSessionId;
this.info = info; this.info = info;
this.vdi = false; this.vdi = false;
this.virtualClientId = null; this.sebClientUserId = null;
this.vdiPairToken = null; this.vdiPairToken = null;
this.creationTime = 0L; this.creationTime = 0L;
this.updateTime = 0L; this.updateTime = 0L;
@ -199,7 +199,7 @@ public final class ClientConnection implements GrantEntity {
final String seb_os_name, final String seb_os_name,
final String seb_machine_name, final String seb_machine_name,
final String seb_version, final String seb_version,
final String virtualClientId, final String sebClientUserId,
final Boolean vdi, final Boolean vdi,
final String vdiPairToken, final String vdiPairToken,
final Long creationTime, final Long creationTime,
@ -222,7 +222,7 @@ public final class ClientConnection implements GrantEntity {
this.sebOSName = seb_os_name; this.sebOSName = seb_os_name;
this.sebMachineName = seb_machine_name; this.sebMachineName = seb_machine_name;
this.sebVersion = seb_version; this.sebVersion = seb_version;
this.virtualClientId = virtualClientId; this.sebClientUserId = sebClientUserId;
this.vdi = vdi; this.vdi = vdi;
this.vdiPairToken = vdiPairToken; this.vdiPairToken = vdiPairToken;
this.creationTime = creationTime; this.creationTime = creationTime;
@ -296,8 +296,8 @@ public final class ClientConnection implements GrantEntity {
} }
@JsonIgnore @JsonIgnore
public String getVirtualClientId() { public String getSebClientUserId() {
return this.virtualClientId; return this.sebClientUserId;
} }
@JsonIgnore // not used yet on GUI side @JsonIgnore // not used yet on GUI side
@ -441,7 +441,7 @@ public final class ClientConnection implements GrantEntity {
builder.append(", remoteProctoringRoomId="); builder.append(", remoteProctoringRoomId=");
builder.append(this.remoteProctoringRoomId); builder.append(this.remoteProctoringRoomId);
builder.append(", virtualClientId="); builder.append(", virtualClientId=");
builder.append(this.virtualClientId); builder.append(this.sebClientUserId);
builder.append(", creationTime="); builder.append(", creationTime=");
builder.append(this.creationTime); builder.append(this.creationTime);
builder.append(", updateTime="); builder.append(", updateTime=");

View file

@ -366,7 +366,7 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
data.connectionToken, data.connectionToken,
null, null,
data.clientAddress, data.clientAddress,
data.virtualClientId, data.sebClientUserId,
BooleanUtils.toInteger(data.vdi, 1, 0, 0), BooleanUtils.toInteger(data.vdi, 1, 0, 0),
data.vdiPairToken, data.vdiPairToken,
millisecondsNow, millisecondsNow,
@ -408,7 +408,7 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
null, null,
data.userSessionId, data.userSessionId,
data.clientAddress, data.clientAddress,
data.virtualClientId, data.sebClientUserId,
BooleanUtils.toInteger(data.vdi, 1, 0, 0), BooleanUtils.toInteger(data.vdi, 1, 0, 0),
data.vdiPairToken, data.vdiPairToken,
null, null,

View file

@ -84,7 +84,7 @@ public interface ExamSessionService {
Result<Collection<APIMessage>> checkExamConsistency(Long examId); Result<Collection<APIMessage>> checkExamConsistency(Long examId);
/** Use this to check if a specified Exam has currently active SEB Client connections. /** Use this to check if a specified Exam has currently active SEB Client connections.
* * <p>
* Active SEB Client connections are established connections that are not yet closed and * Active SEB Client connections are established connections that are not yet closed and
* open connection attempts. * open connection attempts.
* *
@ -163,7 +163,7 @@ public interface ExamSessionService {
OutputStream out); OutputStream out);
/** Get current ClientConnectionData for a specified active SEB client connection. /** Get current ClientConnectionData for a specified active SEB client connection.
* * <p>
* active SEB client connections are connections that were initialized by a SEB client * active SEB client connections are connections that were initialized by a SEB client
* on the particular server instance. * on the particular server instance.
* *

View file

@ -57,7 +57,7 @@ public interface SEBClientSessionService extends ExamUpdateTask, SessionUpdateTa
/** Notify a SEB client event for live indication and storing to database. /** Notify a SEB client event for live indication and storing to database.
* *
* @param connectionToken the connection token * @param connectionToken the connection token
* @param event The SEB client event data */ * @param jsonBody The SEB client event JSON data */
void notifyClientEvent(String connectionToken, String jsonBody); void notifyClientEvent(String connectionToken, String jsonBody);
/** This is used to confirm SEB instructions that must be confirmed by the SEB client. /** This is used to confirm SEB instructions that must be confirmed by the SEB client.

View file

@ -10,11 +10,15 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl;
import java.security.Principal; import java.security.Principal;
import java.util.Collection; import java.util.Collection;
import java.util.Objects;
import java.util.UUID; 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.model.Entity;
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
import ch.ethz.seb.sebserver.gbl.util.Utils;
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.slf4j.Logger; import org.slf4j.Logger;
@ -67,6 +71,7 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
private final ExamAdminService examAdminService; private final ExamAdminService examAdminService;
private final DistributedIndicatorValueService distributedPingCache; private final DistributedIndicatorValueService distributedPingCache;
private final SecurityKeyService securityKeyService; private final SecurityKeyService securityKeyService;
private final SEBClientEventBatchService sebClientEventBatchService;
private final boolean isDistributedSetup; private final boolean isDistributedSetup;
protected SEBClientConnectionServiceImpl( protected SEBClientConnectionServiceImpl(
@ -76,7 +81,8 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
final DistributedIndicatorValueService distributedPingCache, final DistributedIndicatorValueService distributedPingCache,
final ClientIndicatorFactory clientIndicatorFactory, final ClientIndicatorFactory clientIndicatorFactory,
final SecurityKeyService securityKeyService, final SecurityKeyService securityKeyService,
final WebserviceInfo webserviceInfo) { final WebserviceInfo webserviceInfo,
final SEBClientEventBatchService sebClientEventBatchService) {
this.examSessionService = examSessionService; this.examSessionService = examSessionService;
this.examSessionCacheService = examSessionService.getExamSessionCacheService(); this.examSessionCacheService = examSessionService.getExamSessionCacheService();
@ -87,6 +93,7 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
this.distributedPingCache = distributedPingCache; this.distributedPingCache = distributedPingCache;
this.securityKeyService = securityKeyService; this.securityKeyService = securityKeyService;
this.isDistributedSetup = webserviceInfo.isDistributed(); this.isDistributedSetup = webserviceInfo.isDistributed();
this.sebClientEventBatchService = sebClientEventBatchService;
} }
@Override @Override
@ -113,7 +120,7 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
if (clientConfig == null) { if (clientConfig == null) {
log.error("Illegal client connection request: requested connection config name: {}", log.error("Illegal client connection request: requested connection config name: {}",
principal.getName()); (principal != null) ? principal.getName() : clientAddress);
throw new AccessDeniedException("Unknown or illegal client access"); throw new AccessDeniedException("Unknown or illegal client access");
} }
@ -226,12 +233,12 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
if (StringUtils.isNoneBlank(clientAddress) && if (StringUtils.isNoneBlank(clientAddress) &&
StringUtils.isNotBlank(clientConnection.clientAddress) && StringUtils.isNotBlank(clientConnection.clientAddress) &&
!clientAddress.equals(clientConnection.clientAddress)) { !clientAddress.equals(clientConnection.clientAddress)) {
// log SEB client IP address change
log.error( log.error(
"ClientConnection integrity violation: client address mismatch: {}, {}", "ClientConnection integrity violation: client address mismatch: {}, {}",
clientAddress, clientAddress,
clientConnection.clientAddress); clientConnection.clientAddress);
throw new IllegalArgumentException( sebLogClientAddressMismatch(clientAddress, clientConnection);
"ClientConnection integrity violation: client address mismatch");
} }
if (examId != null) { if (examId != null) {
@ -327,26 +334,26 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
ClientConnection clientConnection = getClientConnection(connectionToken); ClientConnection clientConnection = getClientConnection(connectionToken);
// connection integrity check // overall connection status integrity check
if (clientConnection.status == ConnectionStatus.ACTIVE) { if (!clientConnection.status.clientActiveStatus) {
// connection already established. Check if IP is the same
if (StringUtils.isNoneBlank(clientAddress) &&
StringUtils.isNotBlank(clientConnection.clientAddress) &&
!clientAddress.equals(clientConnection.clientAddress)) {
log.warn(
"ClientConnection integrity violation: client address mismatch: {}, {}",
clientAddress,
clientConnection.clientAddress);
throw new IllegalArgumentException(
"ClientConnection integrity violation: client address mismatch");
}
} else if (!clientConnection.status.clientActiveStatus) {
log.warn("ClientConnection integrity violation: client connection is not in expected state: {}", log.warn("ClientConnection integrity violation: client connection is not in expected state: {}",
clientConnection); clientConnection);
throw new IllegalArgumentException( throw new IllegalArgumentException(
"ClientConnection integrity violation: client connection is not in expected state"); "ClientConnection integrity violation: client connection is not in expected state");
} }
// check IP address change
if (StringUtils.isNoneBlank(clientAddress) &&
StringUtils.isNotBlank(clientConnection.clientAddress) &&
!clientAddress.equals(clientConnection.clientAddress)) {
// log client IP address change
log.warn(
"ClientConnection integrity violation: client address mismatch: {}, {}",
clientAddress,
clientConnection.clientAddress);
sebLogClientAddressMismatch(clientAddress, clientConnection);
}
if (log.isDebugEnabled()) { if (log.isDebugEnabled()) {
log.debug( log.debug(
"SEB client connection, establish ClientConnection for " "SEB client connection, establish ClientConnection for "
@ -391,7 +398,7 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
final Long currentExamId = (examId != null) ? examId : clientConnection.examId; final Long currentExamId = (examId != null) ? examId : clientConnection.examId;
final String currentVdiConnectionId = (clientId != null) final String currentVdiConnectionId = (clientId != null)
? clientId ? clientId
: clientConnection.virtualClientId; : clientConnection.sebClientUserId;
// create new ClientConnection for update // create new ClientConnection for update
final ClientConnection establishedClientConnection = new ClientConnection( final ClientConnection establishedClientConnection = new ClientConnection(
@ -438,12 +445,13 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
throw new IllegalStateException("ClientConnection integrity violation"); throw new IllegalStateException("ClientConnection integrity violation");
} }
final ClientConnection connectionToSave = handleVDISetup( // Removed this since VDI integration was postponed and has not been reactivated since then.
currentVdiConnectionId, // final ClientConnection connectionToSave = handleVDISetup(
establishedClientConnection); // currentVdiConnectionId,
// establishedClientConnection);
//
final ClientConnection updatedClientConnection = this.clientConnectionDAO final ClientConnection updatedClientConnection = this.clientConnectionDAO
.save(connectionToSave) .save(establishedClientConnection)
.getOrThrow(); .getOrThrow();
// check exam integrity for established connection // check exam integrity for established connection
@ -467,13 +475,15 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
log.warn("Failed to load ClientConnectionDataInternal into cache on update"); log.warn("Failed to load ClientConnectionDataInternal into cache on update");
} else if (log.isDebugEnabled()) { } else if (log.isDebugEnabled()) {
log.debug("SEB client connection, successfully established ClientConnection: {}", log.debug("SEB client connection, successfully established ClientConnection: {}",
updatedClientConnection); establishedClientConnection);
} }
return updatedClientConnection; return establishedClientConnection;
}); });
} }
private ClientConnection handleVDISetup( private ClientConnection handleVDISetup(
final String currentVdiConnectionId, final String currentVdiConnectionId,
final ClientConnection establishedClientConnection) { final ClientConnection establishedClientConnection) {
@ -485,7 +495,7 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
final Result<ClientConnectionRecord> vdiPairConnectionResult = final Result<ClientConnectionRecord> vdiPairConnectionResult =
this.clientConnectionDAO.getVDIPairCompanion( this.clientConnectionDAO.getVDIPairCompanion(
establishedClientConnection.examId, establishedClientConnection.examId,
establishedClientConnection.virtualClientId); establishedClientConnection.sebClientUserId);
if (!vdiPairConnectionResult.hasValue()) { if (!vdiPairConnectionResult.hasValue()) {
return establishedClientConnection; return establishedClientConnection;
@ -506,7 +516,7 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
null, null,
null, null,
null, null,
establishedClientConnection.virtualClientId, establishedClientConnection.sebClientUserId,
null, null,
vdiPairCompanion.getConnectionToken(), vdiPairCompanion.getConnectionToken(),
null, null,
@ -554,7 +564,7 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
.byConnectionToken(connectionToken) .byConnectionToken(connectionToken)
.getOrThrow(); .getOrThrow();
ClientConnection updatedClientConnection; final ClientConnection updatedClientConnection;
if (clientConnection.status != ConnectionStatus.CLOSED) { if (clientConnection.status != ConnectionStatus.CLOSED) {
updatedClientConnection = saveInState( updatedClientConnection = saveInState(
clientConnection, clientConnection,
@ -614,7 +624,7 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
.byConnectionToken(connectionToken) .byConnectionToken(connectionToken)
.getOrThrow(); .getOrThrow();
ClientConnection updatedClientConnection; final ClientConnection updatedClientConnection;
if (DISABLE_STATE_PREDICATE.test(clientConnection)) { if (DISABLE_STATE_PREDICATE.test(clientConnection)) {
updatedClientConnection = saveInState( updatedClientConnection = saveInState(
@ -658,12 +668,37 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
.map(token -> disableConnection(token, institutionId) .map(token -> disableConnection(token, institutionId)
.onError(error -> log.error("Failed to disable SEB client connection: {}", token)) .onError(error -> log.error("Failed to disable SEB client connection: {}", token))
.getOr(null)) .getOr(null))
.filter(clientConnection -> clientConnection != null) .filter(Objects::nonNull)
.map(clientConnection -> clientConnection.getEntityKey()) .map(Entity::getEntityKey)
.collect(Collectors.toList()); .collect(Collectors.toList());
}); });
} }
// SEBSERV-475 IP address change during handshake is possible but is logged within SEB logs
private void sebLogClientAddressMismatch(
final String clientAddress,
final ClientConnection clientConnection) {
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 user, final String address) { private void checkExamRunning(final Long examId, final String user, final String address) {
if (examId != null && !this.examSessionService.isExamRunning(examId)) { if (examId != null && !this.examSessionService.isExamRunning(examId)) {
examNotRunningException(examId, user, address); examNotRunningException(examId, user, address);
@ -730,7 +765,7 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
} }
} }
// try to get user account display name // try to get user account display name (SEBSERV-228)
String accountId = userSessionId; String accountId = userSessionId;
try { try {
final String newAccountId = this.examSessionService final String newAccountId = this.examSessionService

View file

@ -176,7 +176,7 @@ public class SEBClientEventBatchService {
log.debug("SEBClientEventBatchService worker {} processes batch of size {} in {} ms", log.debug("SEBClientEventBatchService worker {} processes batch of size {} in {} ms",
workerName, workerName,
size, size,
start - Utils.getMillisecondsNow()); Utils.getMillisecondsNow() - start);
} }
} catch (final Exception e) { } catch (final Exception e) {