SEBSERV-335 fixed corner cases
This commit is contained in:
parent
5faed87288
commit
4a8a2adc8f
18 changed files with 262 additions and 104 deletions
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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(" ", " ");
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
}
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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= (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
|
||||||
|
|
Loading…
Reference in a new issue