SEBSERV-335 fixed corner cases

This commit is contained in:
anhefti 2022-12-05 17:04:23 +01:00
parent 5faed87288
commit 4a8a2adc8f
18 changed files with 262 additions and 104 deletions

View file

@ -13,8 +13,6 @@ import java.util.EnumSet;
import java.util.List; import java.util.List;
import java.util.function.Predicate; import java.util.function.Predicate;
import org.apache.commons.lang3.BooleanUtils;
import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@ -327,11 +325,6 @@ public final class ClientConnection implements GrantEntity {
return this.securityCheckGranted; return this.securityCheckGranted;
} }
@JsonIgnore
public boolean isSecurityCheckGranted() {
return BooleanUtils.isTrue(this.securityCheckGranted);
}
@Override @Override
public int hashCode() { public int hashCode() {
final int prime = 31; final int prime = 31;

View file

@ -25,7 +25,7 @@ public class ClientMonitoringData implements ClientMonitoringDataView {
public final ConnectionStatus status; public final ConnectionStatus status;
public final Map<Long, String> indicatorVals; public final Map<Long, String> indicatorVals;
public final boolean missingPing; public final boolean missingPing;
public final boolean missingGrant; public final Boolean grantDenied;
public final boolean pendingNotification; public final boolean pendingNotification;
@JsonCreator @JsonCreator
@ -34,14 +34,14 @@ public class ClientMonitoringData implements ClientMonitoringDataView {
@JsonProperty(ATTR_STATUS) final ConnectionStatus status, @JsonProperty(ATTR_STATUS) final ConnectionStatus status,
@JsonProperty(ATTR_INDICATOR_VALUES) final Map<Long, String> indicatorVals, @JsonProperty(ATTR_INDICATOR_VALUES) final Map<Long, String> indicatorVals,
@JsonProperty(ATTR_MISSING_PING) final boolean missingPing, @JsonProperty(ATTR_MISSING_PING) final boolean missingPing,
@JsonProperty(ATTR_MISSING_GRANT) final boolean missingGrant, @JsonProperty(ATTR_GRANT_DENIED) final Boolean grantDenied,
@JsonProperty(ATTR_PENDING_NOTIFICATION) final boolean pendingNotification) { @JsonProperty(ATTR_PENDING_NOTIFICATION) final boolean pendingNotification) {
this.id = id; this.id = id;
this.status = status; this.status = status;
this.indicatorVals = indicatorVals; this.indicatorVals = indicatorVals;
this.missingPing = missingPing; this.missingPing = missingPing;
this.missingGrant = missingGrant; this.grantDenied = grantDenied;
this.pendingNotification = pendingNotification; this.pendingNotification = pendingNotification;
} }
@ -66,9 +66,8 @@ public class ClientMonitoringData implements ClientMonitoringDataView {
} }
@Override @Override
public boolean isMissingGrant() { public Boolean isGrantDenied() {
// TODO Auto-generated method stub return this.grantDenied;
return false;
} }
@Override @Override
@ -76,6 +75,12 @@ public class ClientMonitoringData implements ClientMonitoringDataView {
return this.pendingNotification; return this.pendingNotification;
} }
public boolean hasChanged(final ClientMonitoringData other) {
return this.status != other.status ||
this.missingPing != other.missingPing ||
!Objects.equals(this.grantDenied, other.grantDenied);
}
public boolean indicatorValuesEquals(final ClientMonitoringData other) { public boolean indicatorValuesEquals(final ClientMonitoringData other) {
return Objects.equals(this.indicatorVals, other.indicatorVals); return Objects.equals(this.indicatorVals, other.indicatorVals);
} }

View file

@ -27,7 +27,7 @@ public interface ClientMonitoringDataView {
public static final String ATTR_INDICATOR_VALUES = "iv"; public static final String ATTR_INDICATOR_VALUES = "iv";
public static final String ATTR_CLIENT_GROUPS = "cg"; public static final String ATTR_CLIENT_GROUPS = "cg";
public static final String ATTR_MISSING_PING = "mp"; public static final String ATTR_MISSING_PING = "mp";
public static final String ATTR_MISSING_GRANT = "mg"; public static final String ATTR_GRANT_DENIED = "gd";
public static final String ATTR_PENDING_NOTIFICATION = "pn"; public static final String ATTR_PENDING_NOTIFICATION = "pn";
@JsonProperty(Domain.CLIENT_CONNECTION.ATTR_ID) @JsonProperty(Domain.CLIENT_CONNECTION.ATTR_ID)
@ -42,8 +42,8 @@ public interface ClientMonitoringDataView {
@JsonProperty(ATTR_MISSING_PING) @JsonProperty(ATTR_MISSING_PING)
boolean isMissingPing(); boolean isMissingPing();
@JsonProperty(ATTR_MISSING_GRANT) @JsonProperty(ATTR_GRANT_DENIED)
boolean isMissingGrant(); Boolean isGrantDenied();
@JsonProperty(ATTR_PENDING_NOTIFICATION) @JsonProperty(ATTR_PENDING_NOTIFICATION)
boolean isPendingNotification(); boolean isPendingNotification();

View file

@ -22,7 +22,7 @@ import ch.ethz.seb.sebserver.gbl.model.Domain;
public class ClientStaticData { public class ClientStaticData {
public static final ClientStaticData NULL_DATA = public static final ClientStaticData NULL_DATA =
new ClientStaticData(-1L, null, null, false, null, Collections.emptySet()); new ClientStaticData(-1L, null, null, null, null, Collections.emptySet());
@JsonProperty(Domain.CLIENT_CONNECTION.ATTR_ID) @JsonProperty(Domain.CLIENT_CONNECTION.ATTR_ID)
public final Long id; public final Long id;
@ -33,8 +33,8 @@ public class ClientStaticData {
@JsonProperty(Domain.CLIENT_CONNECTION.ATTR_EXAM_USER_SESSION_ID) @JsonProperty(Domain.CLIENT_CONNECTION.ATTR_EXAM_USER_SESSION_ID)
public final String userSessionId; public final String userSessionId;
@JsonProperty(Domain.CLIENT_CONNECTION.ATTR_SECURITY_CHECK_GRANTED) @JsonProperty(Domain.CLIENT_CONNECTION.ATTR_ASK)
public final boolean securityGrant; public final String ask;
@JsonProperty(ClientConnection.ATTR_INFO) @JsonProperty(ClientConnection.ATTR_INFO)
public final String info; public final String info;
@ -47,14 +47,14 @@ public class ClientStaticData {
@JsonProperty(Domain.CLIENT_CONNECTION.ATTR_ID) final Long id, @JsonProperty(Domain.CLIENT_CONNECTION.ATTR_ID) final Long id,
@JsonProperty(Domain.CLIENT_CONNECTION.ATTR_CONNECTION_TOKEN) final String connectionToken, @JsonProperty(Domain.CLIENT_CONNECTION.ATTR_CONNECTION_TOKEN) final String connectionToken,
@JsonProperty(Domain.CLIENT_CONNECTION.ATTR_EXAM_USER_SESSION_ID) final String userSessionId, @JsonProperty(Domain.CLIENT_CONNECTION.ATTR_EXAM_USER_SESSION_ID) final String userSessionId,
@JsonProperty(Domain.CLIENT_CONNECTION.ATTR_SECURITY_CHECK_GRANTED) final boolean securityGrant, @JsonProperty(Domain.CLIENT_CONNECTION.ATTR_ASK) final String ask,
@JsonProperty(ClientConnection.ATTR_INFO) final String info, @JsonProperty(ClientConnection.ATTR_INFO) final String info,
@JsonProperty(ClientConnectionData.ATTR_CLIENT_GROUPS) final Set<Long> groups) { @JsonProperty(ClientConnectionData.ATTR_CLIENT_GROUPS) final Set<Long> groups) {
this.id = id; this.id = id;
this.connectionToken = connectionToken; this.connectionToken = connectionToken;
this.userSessionId = userSessionId; this.userSessionId = userSessionId;
this.securityGrant = securityGrant; this.ask = ask;
this.info = info; this.info = info;
this.groups = groups; this.groups = groups;
} }
@ -71,8 +71,8 @@ public class ClientStaticData {
return this.userSessionId; return this.userSessionId;
} }
public boolean isSecurityGrant() { public String getAsk() {
return this.securityGrant; return this.ask;
} }
public String getInfo() { public String getInfo() {

View file

@ -835,6 +835,13 @@ public final class Utils {
return BooleanUtils.toIntegerObject(b, 1, 0, 0).byteValue(); return BooleanUtils.toIntegerObject(b, 1, 0, 0).byteValue();
} }
public static Boolean fromByteOrNull(final Byte b) {
if (b == null) {
return null;
}
return BooleanUtils.toBooleanObject(b);
}
public static Boolean fromByte(final Byte b) { public static Boolean fromByte(final Byte b) {
return BooleanUtils.toBooleanObject((b == null) ? 0 : b); return BooleanUtils.toBooleanObject((b == null) ? 0 : b);
} }

View file

@ -410,7 +410,7 @@ public class MonitoringClientConnection implements TemplateComposer {
.call() .call()
.getOrThrow(); .getOrThrow();
if (securityKey.id == null || securityKey.id < 0) { if (securityKey.key != null && (securityKey.id == null || securityKey.id < 0)) {
actionBuilder actionBuilder
.newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION_GRANT_SIGNATURE_KEY) .newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION_GRANT_SIGNATURE_KEY)
.withParentEntityKey(parentEntityKey) .withParentEntityKey(parentEntityKey)

View file

@ -98,6 +98,7 @@ public class ResourceService {
private static final Logger log = LoggerFactory.getLogger(ResourceService.class); private static final Logger log = LoggerFactory.getLogger(ResourceService.class);
private static final String MISSING_CLIENT_PING_NAME_KEY = "MISSING_PING"; private static final String MISSING_CLIENT_PING_NAME_KEY = "MISSING_PING";
private static final String DENIED_CLIENT_SEC_GRANT_NAME_KEY = "GRANT_DENIED";
private static final String MISSING_CLIENT_SEC_GRANT_NAME_KEY = "MISSING_GRANT"; private static final String MISSING_CLIENT_SEC_GRANT_NAME_KEY = "MISSING_GRANT";
public static final Comparator<Tuple<String>> RESOURCE_COMPARATOR = Comparator.comparing(t -> t._2); public static final Comparator<Tuple<String>> RESOURCE_COMPARATOR = Comparator.comparing(t -> t._2);
@ -625,46 +626,16 @@ public class ResourceService {
.getText(ResourceService.EXAMCONFIG_STATUS_PREFIX + config.configStatus.name()); .getText(ResourceService.EXAMCONFIG_STATUS_PREFIX + config.configStatus.name());
} }
// public Function<ClientConnectionData, String> localizedClientConnectionStatusNameFunction() {
//
// // Memoizing
// final String missing = this.i18nSupport.getText(
// SEB_CONNECTION_STATUS_KEY_PREFIX + MISSING_CLIENT_PING_NAME_KEY,
// MISSING_CLIENT_PING_NAME_KEY);
// final String missingGrant = this.i18nSupport.getText(
// SEB_CONNECTION_STATUS_KEY_PREFIX + MISSING_CLIENT_SEC_GRANT_NAME_KEY,
// MISSING_CLIENT_SEC_GRANT_NAME_KEY);
// final EnumMap<ConnectionStatus, String> localizedNames = new EnumMap<>(ConnectionStatus.class);
// Arrays.asList(ConnectionStatus.values()).stream().forEach(state -> localizedNames.put(state, this.i18nSupport
// .getText(SEB_CONNECTION_STATUS_KEY_PREFIX + state.name(), state.name())));
//
// return connectionData -> {
// if (connectionData == null) {
// localizedNames.get(ConnectionStatus.UNDEFINED);
// }
// if (connectionData.clientConnection.status.establishedStatus) {
// if (connectionData.c !connectionData.clientConnection.securityCheckGranted) {
// return missingGrant;
// }
// if (connectionData.missingPing) {
// return missing;
// }
// }
// if (connectionData.missingPing && connectionData.clientConnection.status.establishedStatus) {
// return missing;
// } else {
// return localizedNames.get(connectionData.clientConnection.status);
// }
// };
// }
public Function<MonitoringEntry, String> localizedClientMonitoringStatusNameFunction() { public Function<MonitoringEntry, String> localizedClientMonitoringStatusNameFunction() {
// Memoizing // Memoizing
final String missingPing = this.i18nSupport.getText( final String missingPing = this.i18nSupport.getText(
SEB_CONNECTION_STATUS_KEY_PREFIX + MISSING_CLIENT_PING_NAME_KEY, SEB_CONNECTION_STATUS_KEY_PREFIX + MISSING_CLIENT_PING_NAME_KEY,
MISSING_CLIENT_PING_NAME_KEY); MISSING_CLIENT_PING_NAME_KEY);
final String missingGrant = this.i18nSupport.getText( final String grantDeniedText = this.i18nSupport.getText(
SEB_CONNECTION_STATUS_KEY_PREFIX + DENIED_CLIENT_SEC_GRANT_NAME_KEY,
DENIED_CLIENT_SEC_GRANT_NAME_KEY);
final String grantMissingText = this.i18nSupport.getText(
SEB_CONNECTION_STATUS_KEY_PREFIX + MISSING_CLIENT_SEC_GRANT_NAME_KEY, SEB_CONNECTION_STATUS_KEY_PREFIX + MISSING_CLIENT_SEC_GRANT_NAME_KEY,
MISSING_CLIENT_SEC_GRANT_NAME_KEY); MISSING_CLIENT_SEC_GRANT_NAME_KEY);
final EnumMap<ConnectionStatus, String> localizedNames = new EnumMap<>(ConnectionStatus.class); final EnumMap<ConnectionStatus, String> localizedNames = new EnumMap<>(ConnectionStatus.class);
@ -674,12 +645,18 @@ public class ResourceService {
return monitoringEntry -> { return monitoringEntry -> {
final ConnectionStatus status = monitoringEntry.getStatus(); final ConnectionStatus status = monitoringEntry.getStatus();
if (status.establishedStatus) { if (status.establishedStatus) {
if (monitoringEntry.hasMissingGrant()) {
return missingGrant;
}
if (monitoringEntry.hasMissingPing()) { if (monitoringEntry.hasMissingPing()) {
return missingPing; return missingPing;
} }
final Boolean grantDenied = monitoringEntry.grantDenied();
if (grantDenied != null) {
if (grantDenied) {
return grantDeniedText;
}
} else if (monitoringEntry.showNoGrantCheckApplied()) {
return localizedNames.get(status) + grantMissingText;
}
} }
return localizedNames.get(status); return localizedNames.get(status);

View file

@ -77,6 +77,7 @@ public class ClientConnectionDetails implements MonitoringEntry {
public final boolean checkSecurityGrant; public final boolean checkSecurityGrant;
private ClientConnectionData connectionData = null; private ClientConnectionData connectionData = null;
public Boolean grantDenied = null;
private boolean statusChanged = true; private boolean statusChanged = true;
private boolean missingChanged = true; private boolean missingChanged = true;
private long startTime = -1; private long startTime = -1;
@ -98,6 +99,7 @@ public class ClientConnectionDetails implements MonitoringEntry {
this.colorData = new ColorData(display); this.colorData = new ColorData(display);
this.checkSecurityGrant = BooleanUtils.toBoolean( this.checkSecurityGrant = BooleanUtils.toBoolean(
exam.additionalAttributes.get(Exam.ADDITIONAL_ATTR_SIGNATURE_KEY_CHECK_ENABLED)); exam.additionalAttributes.get(Exam.ADDITIONAL_ATTR_SIGNATURE_KEY_CHECK_ENABLED));
this.indicatorMapping = IndicatorData.createFormIndicators( this.indicatorMapping = IndicatorData.createFormIndicators(
indicators, indicators,
display, display,
@ -167,9 +169,13 @@ public class ClientConnectionDetails implements MonitoringEntry {
} }
@Override @Override
public boolean hasMissingGrant() { public Boolean grantDenied() {
return (this.connectionData != null) return this.grantDenied;
? this.checkSecurityGrant && !this.connectionData.clientConnection.securityCheckGranted : false; }
@Override
public boolean showNoGrantCheckApplied() {
return this.checkSecurityGrant;
} }
public void setStatusChangeListener(final Consumer<ClientConnectionData> statusChangeListener) { public void setStatusChangeListener(final Consumer<ClientConnectionData> statusChangeListener) {
@ -194,6 +200,11 @@ public class ClientConnectionDetails implements MonitoringEntry {
.toBoolean(connectionData.missingPing); .toBoolean(connectionData.missingPing);
} }
this.connectionData = connectionData; this.connectionData = connectionData;
if (this.connectionData == null || this.connectionData.clientConnection.securityCheckGranted == null) {
this.grantDenied = null;
} else {
this.grantDenied = !this.connectionData.clientConnection.securityCheckGranted;
}
if (this.startTime < 0) { if (this.startTime < 0) {
this.startTime = System.currentTimeMillis(); this.startTime = System.currentTimeMillis();
} }
@ -230,11 +241,13 @@ public class ClientConnectionDetails implements MonitoringEntry {
getGroupInfo()); getGroupInfo());
} }
if (this.missingChanged) { if (this.missingChanged || this.statusChanged) {
// update status // update status
form.setFieldValue( String stateName = this.localizedClientConnectionStatusNameFunction.apply(this);
Domain.CLIENT_CONNECTION.ATTR_STATUS, if (stateName != null) {
this.localizedClientConnectionStatusNameFunction.apply(this)); stateName = stateName.replace("&nbsp;", " ");
}
form.setFieldValue(Domain.CLIENT_CONNECTION.ATTR_STATUS, stateName);
final Color statusColor = this.colorData.getStatusColor(this); final Color statusColor = this.colorData.getStatusColor(this);
final Color statusTextColor = this.colorData.getStatusTextColor(statusColor); final Color statusTextColor = this.colorData.getStatusTextColor(statusColor);
form.setFieldColor(Domain.CLIENT_CONNECTION.ATTR_STATUS, statusColor); form.setFieldColor(Domain.CLIENT_CONNECTION.ATTR_STATUS, statusColor);

View file

@ -479,9 +479,13 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate
} }
@Override @Override
public boolean hasMissingGrant() { public Boolean grantDenied() {
return (this.staticData != null) return this.monitoringData.grantDenied;
? ClientConnectionTable.this.checkSecurityGrant && !this.staticData.securityGrant : false; }
@Override
public boolean showNoGrantCheckApplied() {
return ClientConnectionTable.this.checkSecurityGrant;
} }
private void update(final TableItem tableItem, final boolean force) { private void update(final TableItem tableItem, final boolean force) {
@ -670,10 +674,7 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate
} }
boolean push(final ClientMonitoringData monitoringData) { boolean push(final ClientMonitoringData monitoringData) {
this.dataChanged = this.monitoringData == null || this.dataChanged = this.monitoringData == null || this.monitoringData.hasChanged(monitoringData);
this.monitoringData.status != monitoringData.status ||
this.monitoringData.missingPing != monitoringData.missingPing ||
this.monitoringData.missingGrant != monitoringData.missingGrant;
this.indicatorValueChanged = this.monitoringData == null || this.indicatorValueChanged = this.monitoringData == null ||
(this.monitoringData.status.clientActiveStatus (this.monitoringData.status.clientActiveStatus
&& !this.monitoringData.indicatorValuesEquals(monitoringData)); && !this.monitoringData.indicatorValuesEquals(monitoringData));
@ -739,7 +740,6 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate
private ClientConnectionTable getOuterType() { private ClientConnectionTable getOuterType() {
return ClientConnectionTable.this; return ClientConnectionTable.this;
} }
} }
private void fetchStaticClientConnectionData() { private void fetchStaticClientConnectionData() {

View file

@ -41,13 +41,12 @@ public class ColorData {
return this.defaultColor; return this.defaultColor;
} }
final Boolean grantDenied = entry.grantDenied();
switch (status) { switch (status) {
case ACTIVE: case ACTIVE:
return (entry.hasMissingGrant()) return (grantDenied != null && grantDenied)
? this.color3 ? this.color3 : (entry.hasMissingPing())
: (entry.hasMissingPing()) ? this.color2 : this.color1;
? this.color2
: this.color1;
default: default:
return this.defaultColor; return this.defaultColor;
} }
@ -67,8 +66,9 @@ public class ColorData {
case AUTHENTICATED: case AUTHENTICATED:
return 1; return 1;
case ACTIVE: case ACTIVE:
return (connectionData.clientConnection.securityCheckGranted) ? -1 return (connectionData.clientConnection.securityCheckGranted)
: (connectionData.missingPing) ? 0 : 2; ? -1 : (connectionData.missingPing)
? 0 : 2;
case CLOSED: case CLOSED:
return 3; return 3;
default: default:
@ -81,12 +81,16 @@ public class ColorData {
return 100; return 100;
} }
final Boolean grantDenied = entry.grantDenied();
switch (entry.getStatus()) { switch (entry.getStatus()) {
case CONNECTION_REQUESTED: case CONNECTION_REQUESTED:
case AUTHENTICATED: case AUTHENTICATED:
return 1; return 1;
case ACTIVE: case ACTIVE:
return (entry.hasMissingGrant()) ? -1 : (entry.hasMissingPing()) ? 0 : 2; return (grantDenied == null)
? -1 : (grantDenied)
? -2 : (entry.hasMissingPing())
? 0 : 2;
case CLOSED: case CLOSED:
return 4; return 4;
default: default:

View file

@ -16,5 +16,14 @@ public interface MonitoringEntry {
boolean hasMissingPing(); boolean hasMissingPing();
boolean hasMissingGrant(); /** Indicates the security key grant check state
* true = grant denied
* false = granted
* null = not checked yet
*
* @return the security key grant check state */
Boolean grantDenied();
boolean showNoGrantCheckApplied();
} }

View file

@ -0,0 +1,128 @@
/*
* Copyright (c) 2022 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package ch.ethz.seb.sebserver.webservice.datalayer.checks;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.DBIntegrityCheck;
@Lazy
@Component
@WebServiceProfile
public class TableCharsetCheck implements DBIntegrityCheck {
private static final String UTF8MB4_GENERAL_CI = "utf8mb4_general_ci";
private static final Logger log = LoggerFactory.getLogger(TableCharsetCheck.class);
private final DataSource dataSource;
private final String schemaName;
public TableCharsetCheck(
final DataSource dataSource,
final Environment environment) {
super();
this.dataSource = dataSource;
this.schemaName = environment.getProperty("sebserver.init.database.integrity.check.schema", (String) null);
}
@Override
public String name() {
return "TableCharsetCheck";
}
@Override
public String description() {
return "Checks the char-set and collation of DB tables if correct utf8mb4_general_ci is set";
}
@Override
public Result<String> applyCheck(final boolean tryFix) {
if (StringUtils.isEmpty(this.schemaName)) {
return Result.of("Skip check since sebserver.init.database.integrity.check.schema is not defined");
}
Connection connection = null;
try {
connection = this.dataSource.getConnection();
final PreparedStatement prepareStatement =
connection.prepareStatement(
"SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = \"" + this.schemaName + "\"");
prepareStatement.execute();
final ResultSet resultSet = prepareStatement.getResultSet();
final Map<String, String> tablesWithWrongCollation = new HashMap<>();
while (resultSet.next()) {
final String collation = resultSet.getString("TABLE_COLLATION");
if (!UTF8MB4_GENERAL_CI.equals(collation)) {
tablesWithWrongCollation.put(resultSet.getString("TABLE_NAME"), collation);
}
}
final Connection con = connection;
if (!tablesWithWrongCollation.isEmpty()) {
if (tryFix) {
tablesWithWrongCollation.entrySet().forEach(entry -> tryFix(con, entry));
} else {
return Result.of("Found tables with wrong collation: " + tablesWithWrongCollation);
}
}
} catch (final Exception e) {
log.error("Failed to apply database table check: ", e);
} finally {
if (connection != null) {
try {
connection.close();
} catch (final SQLException e) {
log.error("Failed to close connection: ", e);
}
}
}
return Result.of("OK");
}
private void tryFix(final Connection connection, final Map.Entry<String, String> entry) {
try {
log.info("Try to fix collation for table: {}", entry);
final PreparedStatement prepareStatement = connection.prepareStatement(
"ALTER TABLE " + entry.getKey() + " CONVERT TO CHARACTER SET 'utf8mb4' COLLATE '"
+ UTF8MB4_GENERAL_CI
+ "'");
prepareStatement.execute();
log.info("Successfully changed collision for table: {}", entry.getKey());
} catch (final Exception e) {
log.error("Failed to changed collision for table", e);
}
}
}

View file

@ -791,7 +791,10 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
SqlBuilder.isIn(ClientConnection.SECURE_CHECK_STATES)) SqlBuilder.isIn(ClientConnection.SECURE_CHECK_STATES))
.and( .and(
ClientConnectionRecordDynamicSqlSupport.securityCheckGranted, ClientConnectionRecordDynamicSqlSupport.securityCheckGranted,
SqlBuilder.isEqualTo(Constants.BYTE_FALSE)) SqlBuilder.isEqualTo(Constants.BYTE_FALSE), SqlBuilder.or(
ClientConnectionRecordDynamicSqlSupport.securityCheckGranted,
SqlBuilder.isNull()))
.and(ClientConnectionRecordDynamicSqlSupport.ask, SqlBuilder.isNotNull())
.build() .build()
.execute()); .execute());
} }
@ -864,7 +867,7 @@ public class ClientConnectionDAOImpl implements ClientConnectionDAO {
record.getUpdateTime(), record.getUpdateTime(),
record.getRemoteProctoringRoomId(), record.getRemoteProctoringRoomId(),
BooleanUtils.toBooleanObject(record.getRemoteProctoringRoomUpdate()), BooleanUtils.toBooleanObject(record.getRemoteProctoringRoomUpdate()),
Utils.fromByte(record.getSecurityCheckGranted()), Utils.fromByteOrNull(record.getSecurityCheckGranted()),
record.getAsk()); record.getAsk());
}); });
} }

View file

@ -22,6 +22,7 @@ import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.APIMessage.FieldValidationException; import ch.ethz.seb.sebserver.gbl.api.APIMessage.FieldValidationException;
import ch.ethz.seb.sebserver.gbl.api.EntityType; import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.Domain; import ch.ethz.seb.sebserver.gbl.model.Domain;
@ -181,7 +182,8 @@ public class SecurityKeyServiceImpl implements SecurityKeyService {
@Override @Override
public void updateAppSignatureKeyGrant(final ClientConnectionRecord record) { public void updateAppSignatureKeyGrant(final ClientConnectionRecord record) {
try { try {
if (!Utils.fromByte(record.getSecurityCheckGranted())) { final Byte securityCheckGranted = record.getSecurityCheckGranted();
if (securityCheckGranted == null || securityCheckGranted == Constants.BYTE_FALSE) {
final String token = record.getConnectionToken(); final String token = record.getConnectionToken();
if (applyAppSignatureCheck( if (applyAppSignatureCheck(
record.getInstitutionId(), record.getInstitutionId(),
@ -195,14 +197,9 @@ public class SecurityKeyServiceImpl implements SecurityKeyService {
log.debug("Update app-signature-key grant for client connection: {}", token); log.debug("Update app-signature-key grant for client connection: {}", token);
} }
this.clientConnectionDAO saveSecurityCheckState(record, true);
.save(new ClientConnection( } else if (securityCheckGranted == null) {
record.getId(), null, saveSecurityCheckState(record, false);
null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, true, null))
.onError(error -> log.error("Failed to save ClientConnection grant: ",
error))
.onSuccess(c -> this.examSessionCacheService.evictClientConnection(token));
} }
} }
} catch (final Exception e) { } catch (final Exception e) {
@ -365,4 +362,15 @@ public class SecurityKeyServiceImpl implements SecurityKeyService {
return m; return m;
} }
private void saveSecurityCheckState(final ClientConnectionRecord record, final Boolean checkStatus) {
this.clientConnectionDAO
.save(new ClientConnection(
record.getId(), null,
null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, checkStatus, null))
.onError(error -> log.error("Failed to save ClientConnection grant: ",
error))
.onSuccess(c -> this.examSessionCacheService.evictClientConnection(record.getConnectionToken()));
}
} }

View file

@ -44,6 +44,8 @@ public class ClientConnectionDataInternal extends ClientConnectionData {
PingIntervalClientIndicator pingIndicator = null; PingIntervalClientIndicator pingIndicator = null;
private final PendingNotificationIndication pendingNotificationIndication; private final PendingNotificationIndication pendingNotificationIndication;
private final Boolean grantDenied;
protected ClientConnectionDataInternal( protected ClientConnectionDataInternal(
final ClientConnection clientConnection, final ClientConnection clientConnection,
final PendingNotificationIndication pendingNotificationIndication, final PendingNotificationIndication pendingNotificationIndication,
@ -68,6 +70,12 @@ public class ClientConnectionDataInternal extends ClientConnectionData {
.add(clientIndicator); .add(clientIndicator);
} }
} }
if (clientConnection.securityCheckGranted == null) {
this.grantDenied = null;
} else {
this.grantDenied = !clientConnection.securityCheckGranted;
}
} }
public final void notifyPing(final long timestamp, final int pingNumber) { public final void notifyPing(final long timestamp, final int pingNumber) {
@ -143,9 +151,10 @@ public class ClientConnectionDataInternal extends ClientConnectionData {
} }
@Override @Override
public boolean isMissingGrant() { public Boolean isGrantDenied() {
return BooleanUtils.isFalse(ClientConnectionDataInternal.this.clientConnection.securityCheckGranted); return ClientConnectionDataInternal.this.grantDenied;
} }
}; };
/** This is a static monitoring connection data wrapper/holder */ /** This is a static monitoring connection data wrapper/holder */
@ -155,7 +164,7 @@ public class ClientConnectionDataInternal extends ClientConnectionData {
ClientConnectionDataInternal.this.clientConnection.id, ClientConnectionDataInternal.this.clientConnection.id,
ClientConnectionDataInternal.this.clientConnection.connectionToken, ClientConnectionDataInternal.this.clientConnection.connectionToken,
ClientConnectionDataInternal.this.clientConnection.userSessionId, ClientConnectionDataInternal.this.clientConnection.userSessionId,
ClientConnectionDataInternal.this.clientConnection.getSecurityCheckGranted(), ClientConnectionDataInternal.this.clientConnection.ask,
ClientConnectionDataInternal.this.clientConnection.info, ClientConnectionDataInternal.this.clientConnection.info,
this.groups); this.groups);

View file

@ -278,7 +278,7 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
null, null,
null, null,
null, null,
false, null,
getSignatureHash(appSignatureKey, connectionToken))) getSignatureHash(appSignatureKey, connectionToken)))
.getOrThrow(); .getOrThrow();
@ -400,7 +400,7 @@ public class SEBClientConnectionServiceImpl implements SEBClientConnectionServic
null, null,
null, null,
proctoringEnabled, proctoringEnabled,
false, null,
getSignatureHash(appSignatureKey, connectionToken)); getSignatureHash(appSignatureKey, connectionToken));
// ClientConnection integrity check // ClientConnection integrity check

View file

@ -9,6 +9,7 @@ sebserver.init.organisation.name=SEB Server
sebserver.init.adminaccount.username=sebserver-admin sebserver.init.adminaccount.username=sebserver-admin
sebserver.init.database.integrity.checks=true sebserver.init.database.integrity.checks=true
sebserver.init.database.integrity.try-fix=true sebserver.init.database.integrity.try-fix=true
sebserver.init.database.integrity.check.schema=SEBServer
### webservice caching ### webservice caching
spring.cache.jcache.provider=org.ehcache.jsr107.EhcacheCachingProvider spring.cache.jcache.provider=org.ehcache.jsr107.EhcacheCachingProvider

View file

@ -2077,7 +2077,8 @@ sebserver.monitoring.exam.connection.status.CLOSED=Closed
sebserver.monitoring.exam.connection.status.ABORTED=Aborted sebserver.monitoring.exam.connection.status.ABORTED=Aborted
sebserver.monitoring.exam.connection.status.DISABLED=Canceled sebserver.monitoring.exam.connection.status.DISABLED=Canceled
sebserver.monitoring.exam.connection.status.MISSING_PING=Missing sebserver.monitoring.exam.connection.status.MISSING_PING=Missing
sebserver.monitoring.exam.connection.status.MISSING_GRANT=Not Granted (ASK) sebserver.monitoring.exam.connection.status.MISSING_GRANT=&nbsp;&nbsp;(No ASK Grant)
sebserver.monitoring.exam.connection.status.GRANT_DENIED=ASK Grant Denied
sebserver.monitoring.lock.title=Lock SEB Clients sebserver.monitoring.lock.title=Lock SEB Clients
sebserver.monitoring.lock.form.info.title=Info sebserver.monitoring.lock.form.info.title=Info