diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/AllowedSEBVersion.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/AllowedSEBVersion.java index cb6e83f4..3e640ce1 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/AllowedSEBVersion.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/AllowedSEBVersion.java @@ -27,8 +27,8 @@ public class AllowedSEBVersion { public final int major; public final int minor; public final int patch; - public final boolean alianceEdition; - public final boolean minimal; + public boolean alianceEdition; + public boolean minimal; public final boolean isValidFormat; public AllowedSEBVersion(final String wholeVersionString) { @@ -61,29 +61,43 @@ public class AllowedSEBVersion { valid = false; } this.minor = num; - try { - num = Integer.valueOf(split[3]); - } catch (final Exception e) { - valid = false; + if (split.length >= 3) { + try { + num = Integer.valueOf(split[3]); + } catch (final Exception e) { + num = 0; + if (split[3].equals(ALIANCE_EDITION_IDENTIFIER)) { + this.alianceEdition = true; + } else if (split[3].equals(MINIMAL_IDENTIFIER)) { + this.minimal = true; + } else { + valid = false; + } + } + } else { + num = 0; } this.patch = num; - if (split.length > 4 && ALIANCE_EDITION_IDENTIFIER.equalsIgnoreCase(split[4])) { - this.alianceEdition = true; - if (split.length > 5 && MINIMAL_IDENTIFIER.equalsIgnoreCase(split[5])) { + if (valid && split.length > 4) { + if (!this.alianceEdition && split[4].equals(ALIANCE_EDITION_IDENTIFIER)) { + this.alianceEdition = true; + } else if (!this.minimal && split[4].equals(MINIMAL_IDENTIFIER)) { this.minimal = true; } else { - this.minimal = false; - } - } else { - this.alianceEdition = false; - if (split.length > 4 && MINIMAL_IDENTIFIER.equalsIgnoreCase(split[4])) { - this.minimal = true; - } else { - this.minimal = false; + valid = false; } } + if (valid && split.length > 5) { + if (!this.alianceEdition && split[5].equals(ALIANCE_EDITION_IDENTIFIER)) { + this.alianceEdition = true; + } else if (!this.minimal && split[5].equals(MINIMAL_IDENTIFIER)) { + this.minimal = true; + } else { + valid = false; + } + } this.isValidFormat = valid; } @@ -91,9 +105,12 @@ public class AllowedSEBVersion { if (Objects.equals(this.osTypeString, clientVersion.osTypeString)) { if (this.minimal) { // check greater or equals minimum version - return this.major <= clientVersion.major || - this.minor <= clientVersion.minor || - this.patch <= clientVersion.patch; + return this.major < clientVersion.major + || (this.major == clientVersion.major + && this.minor < clientVersion.minor) + || (this.major == clientVersion.major + && this.minor == clientVersion.minor + && this.patch <= clientVersion.patch); } else { // check exact match return this.major == clientVersion.major && diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientConnection.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientConnection.java index 48f8552e..2bc1d97a 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientConnection.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientConnection.java @@ -222,7 +222,7 @@ public final class ClientConnection implements GrantEntity { .append(Constants.LIST_SEPARATOR) .append(getOSInfo(seb_os_name)) .append(Constants.LIST_SEPARATOR) - .append((clientAddress != null) ? "IP: " + clientAddress : Constants.EMPTY_NOTE) + .append((clientAddress != null) ? " IP:" + clientAddress : Constants.EMPTY_NOTE) .toString(); this.securityCheckGranted = securityCheckGranted; @@ -442,13 +442,13 @@ public final class ClientConnection implements GrantEntity { } private String getSEBInfo(final String seb_version) { - return (seb_version != null) ? "SEBV: " + seb_version : Constants.EMPTY_NOTE; + return (seb_version != null) ? "SEB:" + seb_version : Constants.EMPTY_NOTE; } private String getOSInfo(final String seb_os_name) { if (seb_os_name != null) { final String[] split = StringUtils.split(seb_os_name, Constants.LIST_SEPARATOR); - return "OSV: " + split[0]; + return " OS:" + split[0]; } return Constants.EMPTY_NOTE; } diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientMonitoringData.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientMonitoringData.java index 9f0b55d9..de6cfa4c 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientMonitoringData.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/session/ClientMonitoringData.java @@ -24,12 +24,13 @@ public class ClientMonitoringData implements ClientMonitoringDataView { public final Long id; public final ConnectionStatus status; public final Map indicatorVals; - public final Integer notificationFlag; + public final int notificationFlag; public final boolean missingPing; public final boolean grantChecked; public final boolean grantDenied; public final boolean pendingNotification; + public final boolean sebVersionDenied; @JsonCreator public ClientMonitoringData( @@ -41,11 +42,12 @@ public class ClientMonitoringData implements ClientMonitoringDataView { this.id = id; this.status = status; this.indicatorVals = indicatorVals; - this.notificationFlag = notificationFlag; + this.notificationFlag = notificationFlag != null ? notificationFlag : -1; this.missingPing = notificationFlag != null && (notificationFlag & FLAG_MISSING_PING) > 0; this.grantChecked = notificationFlag == null || (notificationFlag & FLAG_GRANT_NOT_CHECKED) == 0; this.grantDenied = notificationFlag != null && (notificationFlag & FLAG_GRANT_DENIED) > 0; this.pendingNotification = notificationFlag != null && (notificationFlag & FLAG_PENDING_NOTIFICATION) > 0; + this.sebVersionDenied = notificationFlag != null && (notificationFlag & FLAG_INVALID_SEB_VERSION) > 0; } @Override diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/ResourceService.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/ResourceService.java index a6e7a26c..7c1e8e3f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/ResourceService.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/ResourceService.java @@ -100,6 +100,7 @@ public class ResourceService { 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 DENIED_CLIENT_SEB_VERSION_NAME_KEY = "SEB_VERSION_DENIED"; public static final Comparator> RESOURCE_COMPARATOR = Comparator.comparing(t -> t._2); public static final Comparator> RESOURCE_COMPARATOR_TUPLE_3 = Comparator.comparing(t -> t._2); @@ -638,6 +639,7 @@ public class ResourceService { final String grantMissingText = this.i18nSupport.getText( SEB_CONNECTION_STATUS_KEY_PREFIX + MISSING_CLIENT_SEC_GRANT_NAME_KEY, MISSING_CLIENT_SEC_GRANT_NAME_KEY); + final EnumMap 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()))); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/TextFieldListBuilder.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/TextFieldListBuilder.java index b90f238f..a0c24f62 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/TextFieldListBuilder.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/examconfig/impl/TextFieldListBuilder.java @@ -80,7 +80,8 @@ public class TextFieldListBuilder extends AbstractTableFieldBuilder { innerGrid, new LocTextKey(attributeNameKey), 3, - this.widgetFactory); + this.widgetFactory, + !viewContext.isReadonly()); WidgetFactory.setTestId(textListInput, attributeNameKey); textListInput.setLayoutData(gridData); diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionDetails.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionDetails.java index 8a85dcbb..87719e08 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionDetails.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionDetails.java @@ -179,6 +179,17 @@ public class ClientConnectionDetails implements MonitoringEntry { return this.grantDenied; } + @Override + public int incidentFlag() { + return -1; + } + + @Override + public boolean sebVersionDenied() { + return this.connectionData.clientConnection.clientVersionGranted != null && + !this.connectionData.clientConnection.clientVersionGranted; + } + @Override public boolean showNoGrantCheckApplied() { return this.checkSecurityGrant; diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionTable.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionTable.java index 688e2196..ed671c48 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionTable.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ClientConnectionTable.java @@ -94,6 +94,7 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate private final PageService pageService; private final Exam exam; private final boolean checkSecurityGrant; + private final boolean checkSEBVersion; private final boolean distributedSetup; private final Map indicatorMapping; @@ -131,6 +132,7 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate this.exam = exam; this.checkSecurityGrant = BooleanUtils.toBoolean( exam.additionalAttributes.get(Exam.ADDITIONAL_ATTR_SIGNATURE_KEY_CHECK_ENABLED)); + this.checkSEBVersion = exam.additionalAttributes.containsKey(Exam.ADDITIONAL_ATTR_ALLOWED_SEB_VERSIONS); this.distributedSetup = distributedSetup; final WidgetFactory widgetFactory = pageService.getWidgetFactory(); @@ -488,6 +490,16 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate return this.monitoringData.grantDenied; } + @Override + public boolean sebVersionDenied() { + return this.monitoringData.sebVersionDenied; + } + + @Override + public int incidentFlag() { + return this.monitoringData.notificationFlag; + } + @Override public boolean showNoGrantCheckApplied() { return ClientConnectionTable.this.checkSecurityGrant; @@ -506,20 +518,22 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate } private void updateData(final TableItem tableItem) { - tableItem.setText(0, getConnectionIdentifier()); + int row = 0; + tableItem.setText(row++, getConnectionIdentifier()); if (ClientConnectionTable.this.hasClientGroups) { - tableItem.setText(1, getGroupInfo()); - tableItem.setText(2, getConnectionInfo()); - tableItem.setText( - 3, - ClientConnectionTable.this.localizedClientConnectionStatusNameFunction.apply(this)); - } else { - tableItem.setText(1, getConnectionInfo()); - tableItem.setText( - 2, - ClientConnectionTable.this.localizedClientConnectionStatusNameFunction.apply(this)); + tableItem.setText(row++, getGroupInfo()); } - + tableItem.setText(row++, getConnectionInfo()); + if (ClientConnectionTable.this.checkSEBVersion) { + if (sebVersionDenied()) { + tableItem.setBackground(row - 1, ClientConnectionTable.this.colorData.color2); + } else { + tableItem.setBackground(row - 1, ClientConnectionTable.this.colorData.color1); + } + } + tableItem.setText( + row++, + ClientConnectionTable.this.localizedClientConnectionStatusNameFunction.apply(this)); if (this.monitoringData != null) { updateConnectionStatusColor(tableItem); updateNotifications(tableItem); @@ -546,9 +560,11 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate if (BooleanUtils.isTrue(this.monitoringData.pendingNotification)) { tableItem.setImage(0, WidgetFactory.ImageIcon.NOTIFICATION.getImage(ClientConnectionTable.this.table.getDisplay())); + tableItem.setBackground(0, ClientConnectionTable.this.colorData.color2); } else { if (tableItem.getImage(0) != null) { tableItem.setImage(0, null); + tableItem.setBackground(0, ClientConnectionTable.this.colorData.color1); } } } @@ -745,6 +761,7 @@ public final class ClientConnectionTable implements FullPageMonitoringGUIUpdate private ClientConnectionTable getOuterType() { return ClientConnectionTable.this; } + } private void fetchStaticClientConnectionData() { diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ColorData.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ColorData.java index 156fa450..8ee8430a 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ColorData.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/ColorData.java @@ -14,7 +14,7 @@ import org.eclipse.swt.widgets.Display; import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus; -import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData; +import ch.ethz.seb.sebserver.gbl.model.session.ClientMonitoringDataView; import ch.ethz.seb.sebserver.gbl.util.Utils; public class ColorData { @@ -42,10 +42,21 @@ public class ColorData { } switch (status) { - case ACTIVE: - return (entry.grantChecked() && entry.grantDenied()) - ? this.color3 : (entry.hasMissingPing()) - ? this.color2 : this.color1; + case CONNECTION_REQUESTED: + case AUTHENTICATED: + case ACTIVE: { + final int incidentFlag = entry.incidentFlag(); + if (incidentFlag > 0) { + if ((incidentFlag & ClientMonitoringDataView.FLAG_GRANT_DENIED) > 0) { + return this.color3; + } + if ((incidentFlag & (ClientMonitoringDataView.FLAG_GRANT_NOT_CHECKED + | ClientMonitoringDataView.FLAG_MISSING_PING)) > 0) { + return this.color2; + } + } + return this.color1; + } default: return this.defaultColor; } @@ -55,41 +66,25 @@ public class ColorData { return Utils.darkColorContrast(statusColor.getRGB()) ? this.darkColor : this.lightColor; } - int statusWeight(final ClientConnectionData connectionData) { - if (connectionData == null) { - return 100; - } - - switch (connectionData.clientConnection.status) { - case CONNECTION_REQUESTED: - case AUTHENTICATED: - return 1; - case ACTIVE: - return (connectionData.clientConnection.securityCheckGranted) - ? -1 : (connectionData.missingPing) - ? 0 : 2; - case CLOSED: - return 3; - default: - return 10; - } - } - int statusWeight(final MonitoringEntry entry) { if (entry == null) { return 100; } - final Boolean grantDenied = entry.grantDenied(); switch (entry.getStatus()) { case CONNECTION_REQUESTED: - case AUTHENTICATED: + case AUTHENTICATED: { + if (entry.incidentFlag() > 0) { + return -1; + } return 1; - case ACTIVE: - return (grantDenied == null) - ? -1 : (grantDenied) - ? -2 : (entry.hasMissingPing()) - ? 0 : 2; + } + case ACTIVE: { + if (entry.incidentFlag() > 0) { + return -1; + } + return 2; + } case CLOSED: return 4; default: diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/MonitoringEntry.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/MonitoringEntry.java index 1c1bf511..4ed9f598 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/session/MonitoringEntry.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/session/MonitoringEntry.java @@ -14,12 +14,16 @@ public interface MonitoringEntry { ConnectionStatus getStatus(); + int incidentFlag(); + boolean hasMissingPing(); boolean grantChecked(); boolean grantDenied(); + boolean sebVersionDenied(); + boolean showNoGrantCheckApplied(); } \ No newline at end of file diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/widget/TextListInput.java b/src/main/java/ch/ethz/seb/sebserver/gui/widget/TextListInput.java index 4d0343e6..6153cd0b 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/widget/TextListInput.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/widget/TextListInput.java @@ -12,7 +12,6 @@ import java.util.ArrayList; import java.util.List; import org.apache.commons.lang3.StringUtils; -import org.eclipse.rap.rwt.RWT; import org.eclipse.swt.SWT; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; @@ -26,7 +25,6 @@ import org.eclipse.swt.widgets.Text; import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.util.Utils; import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey; -import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant; import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.ImageIcon; public class TextListInput extends Composite { @@ -44,16 +42,20 @@ public class TextListInput extends Composite { private Listener valueChangeEventListener = null; + private boolean isEditable = true; + public TextListInput( final Composite parent, final LocTextKey nameKey, final int initialSize, - final WidgetFactory widgetFactory) { + final WidgetFactory widgetFactory, + final boolean editable) { super(parent, SWT.NONE); this.nameKey = nameKey; this.initialSize = initialSize; this.widgetFactory = widgetFactory; + this.isEditable = editable; // main grid layout GridLayout gridLayout = new GridLayout(1, false); @@ -93,9 +95,6 @@ public class TextListInput extends Composite { this.addAction.setLayoutData(gridData); this.content = header; - for (int i = 0; i < initialSize; i++) { - addRow(null); - } } public void addListener(final Listener valueChangeEventListener) { @@ -123,7 +122,6 @@ public class TextListInput extends Composite { } public void setValue(final String value) { - System.out.println("************ value: " + value); if (StringUtils.isBlank(value)) { // clear rows new ArrayList<>(this.list).stream().forEach(row -> row.deleteRow()); @@ -148,8 +146,9 @@ public class TextListInput extends Composite { } public void setEditable(final boolean b) { + this.isEditable = b; this.addAction.setEnabled(b); - this.list.stream().forEach(row -> row.setEditable(b)); + this.setValue(getValue()); } private final class Row { @@ -162,17 +161,23 @@ public class TextListInput extends Composite { TextListInput.this.content, TextListInput.this.nameKey); GridData gridData = new GridData(SWT.FILL, SWT.CENTER, true, true); - this.textInput.setLayoutData(gridData); - this.addListener(); + this.textInput.setEditable(TextListInput.this.isEditable); + if (TextListInput.this.isEditable) { + this.textInput.setLayoutData(gridData); + this.addListener(); + this.deleteButton = TextListInput.this.widgetFactory.imageButton( + ImageIcon.REMOVE_BOX, + TextListInput.this.content, + new LocTextKey(TextListInput.this.nameKey.name + ".removeAction"), + deleteEvent -> deleteRow()); + gridData = new GridData(SWT.RIGHT, SWT.CENTER, false, true); + gridData.widthHint = ACTION_COLUMN_WIDTH; + this.deleteButton.setLayoutData(gridData); + this.deleteButton.setEnabled(TextListInput.this.isEditable); + } else { + this.deleteButton = null; + } - this.deleteButton = TextListInput.this.widgetFactory.imageButton( - ImageIcon.REMOVE_BOX, - TextListInput.this.content, - new LocTextKey(TextListInput.this.nameKey.name + ".removeAction"), - deleteEvent -> deleteRow()); - gridData = new GridData(SWT.RIGHT, SWT.CENTER, false, true); - gridData.widthHint = ACTION_COLUMN_WIDTH; - this.deleteButton.setLayoutData(gridData); } private void addListener() { @@ -185,20 +190,24 @@ public class TextListInput extends Composite { public void deleteRow() { TextListInput.this.list.remove(this); this.textInput.dispose(); - this.deleteButton.dispose(); + if (this.deleteButton != null) { + this.deleteButton.dispose(); + } TextListInput.this.content.getParent().getParent().layout(true, true); if (TextListInput.this.valueChangeEventListener != null) { TextListInput.this.valueChangeEventListener.handleEvent(null); } } - public void setEditable(final boolean e) { - this.textInput.setEditable(e); - this.textInput.setData( - RWT.CUSTOM_VARIANT, - e ? null : CustomVariant.CONFIG_INPUT_READONLY.key); - this.deleteButton.setEnabled(e); - } +// public void setEditable(final boolean e) { +// this.textInput.setEditable(e); +// this.textInput.setData( +// RWT.CUSTOM_VARIANT, +// e ? null : CustomVariant.CONFIG_INPUT_READONLY.key); +// if (this.deleteButton != null) { +// this.deleteButton.setEnabled(e); +// } +// } } } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/ExamAdminService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/ExamAdminService.java index 57b3347f..01f52938 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/ExamAdminService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/ExamAdminService.java @@ -104,6 +104,11 @@ public interface ExamAdminService { return isProctoringEnabled(exam.id); } + /** Updates needed additional attributes from assigned exam configuration for the exam + * + * @param examId The exam identifier */ + void updateAdditionalExamConfigAttributes(final Long examId); + /** This indicates if proctoring is set and enabled for a certain exam. * * @param examId the exam identifier diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/ExamConfigurationValueService.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/ExamConfigurationValueService.java index c6456dd4..15cf3c68 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/ExamConfigurationValueService.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/ExamConfigurationValueService.java @@ -12,6 +12,7 @@ public interface ExamConfigurationValueService { public static final String CONFIG_ATTR_NAME_QUIT_LINK = "quitURL"; public static final String CONFIG_ATTR_NAME_QUIT_SECRET = "hashedQuitPassword"; + public static final String CONFIG_ATTR_NAME_ALLOWED_SEB_VERSION = "sebAllowedVersions"; /** Get the actual SEB settings attribute value for the exam configuration mapped as default configuration * to the given exam @@ -19,7 +20,21 @@ public interface ExamConfigurationValueService { * @param examId The exam identifier * @param configAttributeName The name of the SEB settings attribute * @return The current value of the above SEB settings attribute and given exam. */ - String getMappedDefaultConfigAttributeValue(Long examId, String configAttributeName); + default String getMappedDefaultConfigAttributeValue(final Long examId, final String configAttributeName) { + return getMappedDefaultConfigAttributeValue(examId, configAttributeName, null); + } + + /** Get the actual SEB settings attribute value for the exam configuration mapped as default configuration + * to the given exam + * + * @param examId The exam identifier + * @param configAttributeName The name of the SEB settings attribute + * @param The default value that is given back if there is no value from configuration + * @return The current value of the above SEB settings attribute and given exam. */ + String getMappedDefaultConfigAttributeValue( + Long examId, + String configAttributeName, + String defaultValue); /** Get the quitPassword SEB Setting from the Exam Configuration that is applied to the given exam. * diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamAdminServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamAdminServiceImpl.java index 19f443c1..4a483aeb 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamAdminServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamAdminServiceImpl.java @@ -46,6 +46,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationNodeDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamConfigurationMapDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamAdminService; +import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamConfigurationValueService; import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ProctoringAdminService; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPITemplate; @@ -67,6 +68,7 @@ public class ExamAdminServiceImpl implements ExamAdminService { private final LmsAPIService lmsAPIService; private final boolean appSignatureKeyEnabled; private final int defaultNumericalTrustThreshold; + private final ExamConfigurationValueService examConfigurationValueService; protected ExamAdminServiceImpl( final ExamDAO examDAO, @@ -75,6 +77,7 @@ public class ExamAdminServiceImpl implements ExamAdminService { final ConfigurationNodeDAO configurationNodeDAO, final ExamConfigurationMapDAO examConfigurationMapDAO, final LmsAPIService lmsAPIService, + final ExamConfigurationValueService examConfigurationValueService, final @Value("${sebserver.webservice.api.admin.exam.app.signature.key.enabled:false}") boolean appSignatureKeyEnabled, final @Value("${sebserver.webservice.api.admin.exam.app.signature.key.numerical.threshold:2}") int defaultNumericalTrustThreshold) { @@ -84,6 +87,7 @@ public class ExamAdminServiceImpl implements ExamAdminService { this.configurationNodeDAO = configurationNodeDAO; this.examConfigurationMapDAO = examConfigurationMapDAO; this.lmsAPIService = lmsAPIService; + this.examConfigurationValueService = examConfigurationValueService; this.appSignatureKeyEnabled = appSignatureKeyEnabled; this.defaultNumericalTrustThreshold = defaultNumericalTrustThreshold; } @@ -198,6 +202,30 @@ public class ExamAdminServiceImpl implements ExamAdminService { .onError(error -> log.error("Failed to check SEB restriction: ", error)); } + @Override + public void updateAdditionalExamConfigAttributes(final Long examId) { + try { + final String allowedSEBVersion = this.examConfigurationValueService + .getAllowedSEBVersion(examId); + + if (StringUtils.isNotBlank(allowedSEBVersion)) { + this.additionalAttributesDAO.saveAdditionalAttribute( + EntityType.EXAM, + examId, Exam.ADDITIONAL_ATTR_ALLOWED_SEB_VERSIONS, + allowedSEBVersion) + .getOrThrow(); + } else { + this.additionalAttributesDAO.delete( + EntityType.EXAM, + examId, Exam.ADDITIONAL_ATTR_ALLOWED_SEB_VERSIONS); + } + + } catch (final Exception e) { + log.error("Unexpected error while trying to save additional Exam Configuration settings for exam: {}", + examId, e); + } + } + @Override public Result getProctoringServiceSettings(final Long examId) { return this.proctoringAdminService.getProctoringSettings(new EntityKey(examId, EntityType.EXAM)); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamConfigurationValueServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamConfigurationValueServiceImpl.java index f76e40fc..55234517 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamConfigurationValueServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamConfigurationValueServiceImpl.java @@ -50,7 +50,11 @@ public class ExamConfigurationValueServiceImpl implements ExamConfigurationValue } @Override - public String getMappedDefaultConfigAttributeValue(final Long examId, final String configAttributeName) { + public String getMappedDefaultConfigAttributeValue( + final Long examId, + final String configAttributeName, + final String defaultValue) { + try { final Long configId = this.examConfigurationMapDAO @@ -62,7 +66,7 @@ public class ExamConfigurationValueServiceImpl implements ExamConfigurationValue .getOr(null); if (configId == null) { - return null; + return defaultValue; } final Long attrId = this.configurationAttributeDAO @@ -73,11 +77,18 @@ public class ExamConfigurationValueServiceImpl implements ExamConfigurationValue return this.configurationValueDAO .getConfigAttributeValue(configId, attrId) - .getOrThrow(); + .onError(error -> log.warn( + "Failed to get exam config attribute: {} {} error: {}", + examId, + configAttributeName, + error.getMessage())) + .getOr(defaultValue); } catch (final Exception e) { - log.error("Unexpected error while trying to extract SEB settings attribute value:", e); - return null; + if (defaultValue == null) { + log.error("Unexpected error while trying to extract SEB settings attribute value:", e); + } + return defaultValue; } } @@ -130,10 +141,10 @@ public class ExamConfigurationValueServiceImpl implements ExamConfigurationValue return getMappedDefaultConfigAttributeValue( examId, - CONFIG_ATTR_NAME_QUIT_LINK); + CONFIG_ATTR_NAME_ALLOWED_SEB_VERSION, + StringUtils.EMPTY); } catch (final Exception e) { - log.error("Failed to get SEB restriction with quit link: ", e); return null; } } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ClientConnectionDataInternal.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ClientConnectionDataInternal.java index f79556a7..59a64d3c 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ClientConnectionDataInternal.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ClientConnectionDataInternal.java @@ -153,7 +153,8 @@ public class ClientConnectionDataInternal extends ClientConnectionData { | (isMissingPing() ? ClientMonitoringDataView.FLAG_MISSING_PING : 0) | (isPendingNotification() ? ClientMonitoringDataView.FLAG_PENDING_NOTIFICATION : 0) | (!isGrantChecked() ? ClientMonitoringDataView.FLAG_GRANT_NOT_CHECKED : 0) - | (isGrantDenied() ? ClientMonitoringDataView.FLAG_GRANT_DENIED : 0); + | (isGrantDenied() ? ClientMonitoringDataView.FLAG_GRANT_DENIED : 0) + | (isSEBVersionDenied() ? ClientMonitoringDataView.FLAG_INVALID_SEB_VERSION : 0); return (flag > 0) ? flag : null; } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamConfigUpdateServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamConfigUpdateServiceImpl.java index b5576433..7a4a16df 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamConfigUpdateServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamConfigUpdateServiceImpl.java @@ -29,6 +29,7 @@ import ch.ethz.seb.sebserver.gbl.util.Result; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamConfigurationMapDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO; +import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamAdminService; import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamConfigUpdateService; import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService; @@ -44,19 +45,22 @@ public class ExamConfigUpdateServiceImpl implements ExamConfigUpdateService { private final ExamConfigurationMapDAO examConfigurationMapDAO; private final ExamSessionService examSessionService; private final ExamUpdateHandler examUpdateHandler; + private final ExamAdminService examAdminService; protected ExamConfigUpdateServiceImpl( final ExamDAO examDAO, final ConfigurationDAO configurationDAO, final ExamConfigurationMapDAO examConfigurationMapDAO, final ExamSessionService examSessionService, - final ExamUpdateHandler examUpdateHandler) { + final ExamUpdateHandler examUpdateHandler, + final ExamAdminService examAdminService) { this.examDAO = examDAO; this.configurationDAO = configurationDAO; this.examConfigurationMapDAO = examConfigurationMapDAO; this.examSessionService = examSessionService; this.examUpdateHandler = examUpdateHandler; + this.examAdminService = examAdminService; } // processing: @@ -133,6 +137,7 @@ public class ExamConfigUpdateServiceImpl implements ExamConfigUpdateService { .flatMap(e -> this.examDAO.setSEBRestriction(e.id, true)) .onError(t -> log.error("Failed to update SEB Client restriction for Exam: {}", exam, t)); } + this.examAdminService.updateAdditionalExamConfigAttributes(exam.id); } // evict each Exam from cache and release the update-lock on DB diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientVersionServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientVersionServiceImpl.java index 8f8bd08e..6147890f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientVersionServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/SEBClientVersionServiceImpl.java @@ -96,8 +96,8 @@ public class SEBClientVersionServiceImpl implements SEBClientVersionService { final String[] versionNumberSplit = StringUtils.split(versioNumber, Constants.DOT); final int major = extractVersionNumber(versionNumberSplit[0]); - final int minor = extractVersionNumber(versionNumberSplit[1]); - final int patch = extractVersionNumber(versionNumberSplit[2]); + final int minor = (versionNumberSplit.length > 1) ? extractVersionNumber(versionNumberSplit[1]) : 0; + final int patch = (versionNumberSplit.length > 2) ? extractVersionNumber(versionNumberSplit[2]) : 0; final ClientVersion version = new ClientVersion(osType, major, minor, patch); return allowedSEBVersions @@ -106,7 +106,9 @@ public class SEBClientVersionServiceImpl implements SEBClientVersionService { .findFirst() .isPresent(); } catch (final Exception e) { - log.warn("Unexpected error while trying to parse SEB version number in: {} {}", clientOSName, + log.warn( + "Invalid SEB version number in: {} {}", + clientOSName, clientVersion); return false; } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAdministrationController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAdministrationController.java index 67086713..c871b17d 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAdministrationController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamAdministrationController.java @@ -64,13 +64,11 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.Authorization import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.UserService; import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.impl.SEBServerUser; import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionService; -import ch.ethz.seb.sebserver.webservice.servicelayer.dao.AdditionalAttributesDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamAdminService; -import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamConfigurationValueService; import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamTemplateService; import ch.ethz.seb.sebserver.webservice.servicelayer.institution.SecurityKeyService; import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService; @@ -95,8 +93,6 @@ public class ExamAdministrationController extends EntityController { private final SEBRestrictionService sebRestrictionService; private final SecurityKeyService securityKeyService; private final ExamProctoringRoomService examProctoringRoomService; - private final ExamConfigurationValueService examConfigurationValueService; - private final AdditionalAttributesDAO additionalAttributesDAO; public ExamAdministrationController( final AuthorizationService authorization, @@ -112,9 +108,7 @@ public class ExamAdministrationController extends EntityController { final ExamSessionService examSessionService, final SEBRestrictionService sebRestrictionService, final SecurityKeyService securityKeyService, - final ExamProctoringRoomService examProctoringRoomService, - final ExamConfigurationValueService examConfigurationValueService, - final AdditionalAttributesDAO additionalAttributesDAO) { + final ExamProctoringRoomService examProctoringRoomService) { super(authorization, bulkActionService, @@ -132,8 +126,6 @@ public class ExamAdministrationController extends EntityController { this.sebRestrictionService = sebRestrictionService; this.securityKeyService = securityKeyService; this.examProctoringRoomService = examProctoringRoomService; - this.examConfigurationValueService = examConfigurationValueService; - this.additionalAttributesDAO = additionalAttributesDAO; } @Override @@ -607,7 +599,7 @@ public class ExamAdministrationController extends EntityController { @Override protected Result notifySaved(final Exam entity) { return Result.tryCatch(() -> { - this.saveAdditionalExamConfigAttributes(entity); + this.examAdminService.updateAdditionalExamConfigAttributes(entity.id); this.examSessionService.flushCache(entity); return entity; }); @@ -711,29 +703,6 @@ public class ExamAdministrationController extends EntityController { }); } - private void saveAdditionalExamConfigAttributes(final Exam entity) { - try { - final String allowedSEBVersion = this.examConfigurationValueService - .getAllowedSEBVersion(entity.id); - - if (StringUtils.isNotBlank(allowedSEBVersion)) { - this.additionalAttributesDAO.saveAdditionalAttribute( - EntityType.EXAM, - entity.id, Exam.ADDITIONAL_ATTR_ALLOWED_SEB_VERSIONS, - allowedSEBVersion) - .getOrThrow(); - } else { - this.additionalAttributesDAO.delete( - EntityType.EXAM, - entity.id, Exam.ADDITIONAL_ATTR_ALLOWED_SEB_VERSIONS); - } - - } catch (final Exception e) { - log.error("Unexpected error while trying to save additional Exam Configuration settings for exam: {}", - entity, e); - } - } - static Function, List> pageSort(final String sort) { final String sortBy = PageSortOrder.decode(sort); diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamConfigurationMappingController.java b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamConfigurationMappingController.java index 66f1aa09..f9df75d5 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamConfigurationMappingController.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamConfigurationMappingController.java @@ -47,6 +47,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ConfigurationNodeDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.EntityDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO; import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO; +import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamAdminService; import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamConfigUpdateService; import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService; import ch.ethz.seb.sebserver.webservice.servicelayer.validation.BeanValidationService; @@ -60,6 +61,7 @@ public class ExamConfigurationMappingController extends EntityController notifyCreated(final ExamConfigurationMap entity) { + this.examAdminService.updateAdditionalExamConfigAttributes(entity.examId); // update the attached configurations state to "In Use" return this.configurationNodeDAO.save(new ConfigurationNode( entity.configurationNodeId, @@ -200,6 +205,8 @@ public class ExamConfigurationMappingController extends EntityController> notifyDeleted( final Pair pair) { + + this.examAdminService.updateAdditionalExamConfigAttributes(pair.a.examId); // update the attached configurations state to "Ready" return this.configurationNodeDAO.save(new ConfigurationNode( pair.a.configurationNodeId, diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index a5355639..89a32893 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -2218,6 +2218,7 @@ sebserver.monitoring.exam.connection.status.DISABLED=Canceled sebserver.monitoring.exam.connection.status.MISSING_PING=Missing sebserver.monitoring.exam.connection.status.MISSING_GRANT=  (No ASK Grant) sebserver.monitoring.exam.connection.status.GRANT_DENIED=ASK Grant Denied +sebserver.monitoring.exam.connection.status.SEB_VERSION_DENIED=SEB Version Invalid sebserver.monitoring.lock.title=Lock SEB Clients sebserver.monitoring.lock.form.info.title=Info